Skip to content

Commit 4d83f6d

Browse files
[3.13] gh-124375: Avoid calling _PyMem_ProcessDelayed on other thread states (GH-124459) (#125540)
This fixes a crash when running the PyO3 test suite on the free-threaded build. The `qsbr` field is initialized after the `PyThreadState` is added to the interpreter's linked list -- it might still be NULL. Instead, we "steal" the queue of to-be-freed memory blocks. This is always initialized (possibly empty) and protected by the stop the world pause. (cherry picked from commit 54c6fcb) Co-authored-by: Sam Gross <[email protected]>
1 parent 6c79bae commit 4d83f6d

File tree

2 files changed

+13
-6
lines changed

2 files changed

+13
-6
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix a crash in the free threading build when the GC runs concurrently with a new thread starting.

Python/gc_free_threading.c

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -340,18 +340,24 @@ merge_all_queued_objects(PyInterpreterState *interp, struct collection_state *st
340340
static void
341341
process_delayed_frees(PyInterpreterState *interp)
342342
{
343-
// In STW status, we can observe the latest write sequence by
344-
// advancing the write sequence immediately.
343+
// While we are in a "stop the world" pause, we can observe the latest
344+
// write sequence by advancing the write sequence immediately.
345345
_Py_qsbr_advance(&interp->qsbr);
346346
_PyThreadStateImpl *current_tstate = (_PyThreadStateImpl *)_PyThreadState_GET();
347347
_Py_qsbr_quiescent_state(current_tstate->qsbr);
348+
349+
// Merge the queues from other threads into our own queue so that we can
350+
// process all of the pending delayed free requests at once.
348351
HEAD_LOCK(&_PyRuntime);
349-
PyThreadState *tstate = interp->threads.head;
350-
while (tstate != NULL) {
351-
_PyMem_ProcessDelayed(tstate);
352-
tstate = (PyThreadState *)tstate->next;
352+
for (PyThreadState *p = interp->threads.head; p != NULL; p = p->next) {
353+
_PyThreadStateImpl *other = (_PyThreadStateImpl *)p;
354+
if (other != current_tstate) {
355+
llist_concat(&current_tstate->mem_free_queue, &other->mem_free_queue);
356+
}
353357
}
354358
HEAD_UNLOCK(&_PyRuntime);
359+
360+
_PyMem_ProcessDelayed((PyThreadState *)current_tstate);
355361
}
356362

357363
// Subtract an incoming reference from the computed "gc_refs" refcount.

0 commit comments

Comments
 (0)