Skip to content
Closed
1 change: 1 addition & 0 deletions Include/internal/pycore_interp_structs.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
18 changes: 15 additions & 3 deletions Python/pystate.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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);
}


Expand Down Expand Up @@ -1427,6 +1435,8 @@ free_threadstate(_PyThreadStateImpl *tstate)
else {
PyMem_RawFree(tstate);
}

release_interp_owner(interp);
}

static void
Expand Down Expand Up @@ -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);
Expand Down
Loading