gh-124375: Avoid calling `_PyMem_ProcessDelayed` on other thread states (#124459)

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.
This commit is contained in:
Sam Gross 2024-10-15 12:09:35 -04:00 committed by GitHub
parent e97910cdb7
commit 54c6fcbefd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 13 additions and 6 deletions

View File

@ -0,0 +1 @@
Fix a crash in the free threading build when the GC runs concurrently with a new thread starting.

View File

@ -420,18 +420,24 @@ merge_queued_objects(_PyThreadStateImpl *tstate, struct collection_state *state)
static void static void
process_delayed_frees(PyInterpreterState *interp) process_delayed_frees(PyInterpreterState *interp)
{ {
// In STW status, we can observe the latest write sequence by // While we are in a "stop the world" pause, we can observe the latest
// advancing the write sequence immediately. // write sequence by advancing the write sequence immediately.
_Py_qsbr_advance(&interp->qsbr); _Py_qsbr_advance(&interp->qsbr);
_PyThreadStateImpl *current_tstate = (_PyThreadStateImpl *)_PyThreadState_GET(); _PyThreadStateImpl *current_tstate = (_PyThreadStateImpl *)_PyThreadState_GET();
_Py_qsbr_quiescent_state(current_tstate->qsbr); _Py_qsbr_quiescent_state(current_tstate->qsbr);
// Merge the queues from other threads into our own queue so that we can
// process all of the pending delayed free requests at once.
HEAD_LOCK(&_PyRuntime); HEAD_LOCK(&_PyRuntime);
PyThreadState *tstate = interp->threads.head; for (PyThreadState *p = interp->threads.head; p != NULL; p = p->next) {
while (tstate != NULL) { _PyThreadStateImpl *other = (_PyThreadStateImpl *)p;
_PyMem_ProcessDelayed(tstate); if (other != current_tstate) {
tstate = (PyThreadState *)tstate->next; llist_concat(&current_tstate->mem_free_queue, &other->mem_free_queue);
}
} }
HEAD_UNLOCK(&_PyRuntime); HEAD_UNLOCK(&_PyRuntime);
_PyMem_ProcessDelayed((PyThreadState *)current_tstate);
} }
// Subtract an incoming reference from the computed "gc_refs" refcount. // Subtract an incoming reference from the computed "gc_refs" refcount.