@@ -146,13 +146,15 @@ mi_page_qsbr_should_delay(mi_page_qsbr_t *page, mi_heap_qsbr_t *heap,
146146 page -> goal = QSBR_INITIAL ;
147147 return true;
148148 }
149+ struct _qsbr_thread_state * qsbr = NULL ;
150+ PyThreadState * tstate = _PyThreadState_GET ();
151+ if (tstate != NULL && & tstate -> interp -> qsbr == heap -> shared &&
152+ _Py_atomic_load_int_relaxed (& tstate -> state ) == _Py_THREAD_ATTACHED )
153+ {
154+ qsbr = ((_PyThreadStateImpl * )tstate )-> qsbr ;
155+ }
149156 if (page -> goal == 0 ) {
150- struct _qsbr_thread_state * qsbr = NULL ;
151- PyThreadState * tstate = _PyThreadState_GET ();
152- if (tstate != NULL && & tstate -> interp -> qsbr == heap -> shared &&
153- _Py_atomic_load_int_relaxed (& tstate -> state ) == _Py_THREAD_ATTACHED )
154- {
155- qsbr = ((_PyThreadStateImpl * )tstate )-> qsbr ;
157+ if (qsbr != NULL ) {
156158 qsbr -> should_process = true;
157159 }
158160 if (qsbr == NULL || should_advance_qsbr_for_page (qsbr , page_size )) {
@@ -161,8 +163,16 @@ mi_page_qsbr_should_delay(mi_page_qsbr_t *page, mi_heap_qsbr_t *heap,
161163 else {
162164 page -> goal = _Py_qsbr_shared_next (heap -> shared );
163165 }
166+ // A newly assigned goal is always ahead of the read sequence.
167+ return true;
168+ }
169+ if (qsbr != NULL ) {
170+ // Refresh the read sequence so empty pages can be freed and reused
171+ // without waiting for _PyMem_ProcessDelayed(). Polling takes no locks,
172+ // and the thread array cannot be resized while this thread is attached.
173+ return !_Py_qsbr_poll (qsbr , page -> goal );
164174 }
165- // Hooks can run under allocator locks, so only consult cached progress.
175+ // Without an attached thread, only consult cached progress.
166176 uint64_t rd_seq = _Py_atomic_load_uint64 (& heap -> shared -> rd_seq );
167177 return !QSBR_LEQ (page -> goal , rd_seq );
168178}
0 commit comments