diff --git a/Include/internal/pycore_interp_structs.h b/Include/internal/pycore_interp_structs.h index badc97808c6132..409c0b86342b77 100644 --- a/Include/internal/pycore_interp_structs.h +++ b/Include/internal/pycore_interp_structs.h @@ -965,6 +965,7 @@ struct _is { # endif #endif + Py_ssize_t owners; /* the initial PyInterpreterState.threads.head */ _PyThreadStateImpl _initial_thread; // _initial_thread should be the last field of PyInterpreterState. diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-10-16-20-06-33.gh-issue-140138.a_fhrh.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-10-16-20-06-33.gh-issue-140138.a_fhrh.rst new file mode 100644 index 00000000000000..42a7ceec06c412 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-10-16-20-06-33.gh-issue-140138.a_fhrh.rst @@ -0,0 +1 @@ +Fix a TSAN-reported data race and potential use-after-free when shutting down a subinterpreter with running daemon threads in free-threaded (NOGIL) builds. diff --git a/Python/pystate.c b/Python/pystate.c index af7828d6a030ab..f30b9737c01a46 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -488,6 +488,15 @@ free_interpreter(PyInterpreterState *interp) } } +static inline void +release_interp_owner(PyInterpreterState *interp) +{ + Py_ssize_t prev = _Py_atomic_add_ssize(&interp->owners, -1); + if (prev == 1) { + free_interpreter(interp); + } +} + #ifndef NDEBUG static inline int check_interpreter_whence(long); #endif @@ -537,6 +546,7 @@ init_interpreter(PyInterpreterState *interp, interp->id = id; interp->id_refcount = 0; + interp->owners = 1; assert(runtime->interpreters.head == interp); assert(next != NULL || (interp == runtime->interpreters.main)); @@ -960,10 +970,8 @@ PyInterpreterState_Delete(PyInterpreterState *interp) HEAD_UNLOCK(runtime); _Py_qsbr_fini(interp); - _PyObject_FiniState(interp); - - free_interpreter(interp); + release_interp_owner(interp); } @@ -1427,6 +1435,8 @@ free_threadstate(_PyThreadStateImpl *tstate) else { PyMem_RawFree(tstate); } + + release_interp_owner(interp); } static void @@ -1553,6 +1563,8 @@ new_threadstate(PyInterpreterState *interp, int whence) uint64_t id = interp->threads.next_unique_id; init_threadstate(tstate, interp, id, whence); + _Py_atomic_add_ssize(&interp->owners, 1); + // Add the new thread state to the interpreter. PyThreadState *old_head = interp->threads.head; add_threadstate(interp, (PyThreadState *)tstate, old_head);