Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-11-19 09:50:50

0001 // The QSBR APIs (quiescent state-based reclamation) provide a mechanism for
0002 // the free-threaded build to safely reclaim memory when there may be
0003 // concurrent accesses.
0004 //
0005 // Many operations in the free-threaded build are protected by locks. However,
0006 // in some cases, we want to allow reads to happen concurrently with updates.
0007 // In this case, we need to delay freeing ("reclaiming") any memory that may be
0008 // concurrently accessed by a reader. The QSBR APIs provide a way to do this.
0009 #ifndef Py_INTERNAL_QSBR_H
0010 #define Py_INTERNAL_QSBR_H
0011 
0012 #include <stdbool.h>
0013 #include <stdint.h>
0014 #include "pycore_lock.h"        // PyMutex
0015 
0016 #ifdef __cplusplus
0017 extern "C" {
0018 #endif
0019 
0020 #ifndef Py_BUILD_CORE
0021 #  error "this header requires Py_BUILD_CORE define"
0022 #endif
0023 
0024 // The shared write sequence is always odd and incremented by two. Detached
0025 // threads are indicated by a read sequence of zero. This avoids collisions
0026 // between the offline state and any valid sequence number even if the
0027 // sequences numbers wrap around.
0028 #define QSBR_OFFLINE 0
0029 #define QSBR_INITIAL 1
0030 #define QSBR_INCR    2
0031 
0032 // Wrap-around safe comparison. This is a holdover from the FreeBSD
0033 // implementation, which uses 32-bit sequence numbers. We currently use 64-bit
0034 // sequence numbers, so wrap-around is unlikely.
0035 #define QSBR_LT(a, b) ((int64_t)((a)-(b)) < 0)
0036 #define QSBR_LEQ(a, b) ((int64_t)((a)-(b)) <= 0)
0037 
0038 struct _qsbr_shared;
0039 struct _PyThreadStateImpl;  // forward declare to avoid circular dependency
0040 
0041 // Per-thread state
0042 struct _qsbr_thread_state {
0043     // Last observed write sequence (or 0 if detached)
0044     uint64_t seq;
0045 
0046     // Shared (per-interpreter) QSBR state
0047     struct _qsbr_shared *shared;
0048 
0049     // Thread state (or NULL)
0050     PyThreadState *tstate;
0051 
0052     // Used to defer advancing write sequence a fixed number of times
0053     int deferrals;
0054 
0055     // Is this thread state allocated?
0056     bool allocated;
0057     struct _qsbr_thread_state *freelist_next;
0058 };
0059 
0060 // Padding to avoid false sharing
0061 struct _qsbr_pad {
0062     struct _qsbr_thread_state qsbr;
0063     char __padding[64 - sizeof(struct _qsbr_thread_state)];
0064 };
0065 
0066 // Per-interpreter state
0067 struct _qsbr_shared {
0068     // Write sequence: always odd, incremented by two
0069     uint64_t wr_seq;
0070 
0071     // Minimum observed read sequence of all QSBR thread states
0072     uint64_t rd_seq;
0073 
0074     // Array of QSBR thread states.
0075     struct _qsbr_pad *array;
0076     Py_ssize_t size;
0077 
0078     // Freelist of unused _qsbr_thread_states (protected by mutex)
0079     PyMutex mutex;
0080     struct _qsbr_thread_state *freelist;
0081 };
0082 
0083 static inline uint64_t
0084 _Py_qsbr_shared_current(struct _qsbr_shared *shared)
0085 {
0086     return _Py_atomic_load_uint64_acquire(&shared->wr_seq);
0087 }
0088 
0089 // Reports a quiescent state: the caller no longer holds any pointer to shared
0090 // data not protected by locks or reference counts.
0091 static inline void
0092 _Py_qsbr_quiescent_state(struct _qsbr_thread_state *qsbr)
0093 {
0094     uint64_t seq = _Py_qsbr_shared_current(qsbr->shared);
0095     _Py_atomic_store_uint64_release(&qsbr->seq, seq);
0096 }
0097 
0098 // Have the read sequences advanced to the given goal? Like `_Py_qsbr_poll()`,
0099 // but does not perform a scan of threads.
0100 static inline bool
0101 _Py_qbsr_goal_reached(struct _qsbr_thread_state *qsbr, uint64_t goal)
0102 {
0103     uint64_t rd_seq = _Py_atomic_load_uint64(&qsbr->shared->rd_seq);
0104     return QSBR_LEQ(goal, rd_seq);
0105 }
0106 
0107 // Advance the write sequence and return the new goal. This should be called
0108 // after data is removed. The returned goal is used with `_Py_qsbr_poll()` to
0109 // determine when it is safe to reclaim (free) the memory.
0110 extern uint64_t
0111 _Py_qsbr_advance(struct _qsbr_shared *shared);
0112 
0113 // Batches requests to advance the write sequence. This advances the write
0114 // sequence every N calls, which reduces overhead but increases time to
0115 // reclamation. Returns the new goal.
0116 extern uint64_t
0117 _Py_qsbr_deferred_advance(struct _qsbr_thread_state *qsbr);
0118 
0119 // Have the read sequences advanced to the given goal? If this returns true,
0120 // it safe to reclaim any memory tagged with the goal (or earlier goal).
0121 extern bool
0122 _Py_qsbr_poll(struct _qsbr_thread_state *qsbr, uint64_t goal);
0123 
0124 // Called when thread attaches to interpreter
0125 extern void
0126 _Py_qsbr_attach(struct _qsbr_thread_state *qsbr);
0127 
0128 // Called when thread detaches from interpreter
0129 extern void
0130 _Py_qsbr_detach(struct _qsbr_thread_state *qsbr);
0131 
0132 // Reserves (allocates) a QSBR state and returns its index.
0133 extern Py_ssize_t
0134 _Py_qsbr_reserve(PyInterpreterState *interp);
0135 
0136 // Associates a PyThreadState with the QSBR state at the given index
0137 extern void
0138 _Py_qsbr_register(struct _PyThreadStateImpl *tstate,
0139                   PyInterpreterState *interp, Py_ssize_t index);
0140 
0141 // Disassociates a PyThreadState from the QSBR state and frees the QSBR state.
0142 extern void
0143 _Py_qsbr_unregister(PyThreadState *tstate);
0144 
0145 extern void
0146 _Py_qsbr_fini(PyInterpreterState *interp);
0147 
0148 extern void
0149 _Py_qsbr_after_fork(struct _PyThreadStateImpl *tstate);
0150 
0151 #ifdef __cplusplus
0152 }
0153 #endif
0154 #endif   /* !Py_INTERNAL_QSBR_H */