summaryrefslogtreecommitdiffstats
path: root/Python
diff options
context:
space:
mode:
authorEric Snow <ericsnowcurrently@gmail.com>2024-12-02 18:41:57 (GMT)
committerGitHub <noreply@github.com>2024-12-02 18:41:57 (GMT)
commit219b8266db52508eb947fa1ba2cf4aa9e8569685 (patch)
tree80a5c08ebcb507c5b0bf912d30de068c0f322cef /Python
parent059114c0a0bc60c1d180eefa54e8483e3a8e9b5e (diff)
downloadcpython-219b8266db52508eb947fa1ba2cf4aa9e8569685.zip
cpython-219b8266db52508eb947fa1ba2cf4aa9e8569685.tar.gz
cpython-219b8266db52508eb947fa1ba2cf4aa9e8569685.tar.bz2
[3.13] gh-126914: Store the Preallocated Thread State's Pointer in a PyInterpreterState Field (gh-127114)
This approach eliminates the originally reported race. It also gets rid of the deadlock reported in gh-96071, so we can remove the workaround added then. This is mostly a cherry-pick of 1c0a104 (AKA gh-126989). The difference is we add PyInterpreterState.threads_preallocated at the end of PyInterpreterState, instead of adding PyInterpreterState.threads.preallocated. That avoids ABI disruption.
Diffstat (limited to 'Python')
-rw-r--r--Python/pystate.c92
1 files changed, 46 insertions, 46 deletions
diff --git a/Python/pystate.c b/Python/pystate.c
index 528847d..900cba0 100644
--- a/Python/pystate.c
+++ b/Python/pystate.c
@@ -632,6 +632,8 @@ init_interpreter(PyInterpreterState *interp,
assert(next != NULL || (interp == runtime->interpreters.main));
interp->next = next;
+ interp->threads_preallocated = &interp->_initial_thread;
+
// We would call _PyObject_InitState() at this point
// if interp->feature_flags were alredy set.
@@ -767,7 +769,6 @@ PyInterpreterState_New(void)
return interp;
}
-
static void
interpreter_clear(PyInterpreterState *interp, PyThreadState *tstate)
{
@@ -906,6 +907,8 @@ interpreter_clear(PyInterpreterState *interp, PyThreadState *tstate)
// XXX Once we have one allocator per interpreter (i.e.
// per-interpreter GC) we must ensure that all of the interpreter's
// objects have been cleaned up at the point.
+
+ // If we had a freelist of thread states, we would clear it here.
}
@@ -1427,22 +1430,45 @@ allocate_chunk(int size_in_bytes, _PyStackChunk* previous)
return res;
}
+static void
+reset_threadstate(_PyThreadStateImpl *tstate)
+{
+ // Set to _PyThreadState_INIT directly?
+ memcpy(tstate,
+ &initial._main_interpreter._initial_thread,
+ sizeof(*tstate));
+}
+
static _PyThreadStateImpl *
-alloc_threadstate(void)
+alloc_threadstate(PyInterpreterState *interp)
{
- return PyMem_RawCalloc(1, sizeof(_PyThreadStateImpl));
+ _PyThreadStateImpl *tstate;
+
+ // Try the preallocated tstate first.
+ tstate = _Py_atomic_exchange_ptr(&interp->threads_preallocated, NULL);
+
+ // Fall back to the allocator.
+ if (tstate == NULL) {
+ tstate = PyMem_RawCalloc(1, sizeof(_PyThreadStateImpl));
+ if (tstate == NULL) {
+ return NULL;
+ }
+ reset_threadstate(tstate);
+ }
+ return tstate;
}
static void
free_threadstate(_PyThreadStateImpl *tstate)
{
+ PyInterpreterState *interp = tstate->base.interp;
// The initial thread state of the interpreter is allocated
// as part of the interpreter state so should not be freed.
- if (tstate == &tstate->base.interp->_initial_thread) {
- // Restore to _PyThreadState_INIT.
- memcpy(tstate,
- &initial._main_interpreter._initial_thread,
- sizeof(*tstate));
+ if (tstate == &interp->_initial_thread) {
+ // Make it available again.
+ reset_threadstate(tstate);
+ assert(interp->threads_preallocated == NULL);
+ _Py_atomic_store_ptr(&interp->threads_preallocated, tstate);
}
else {
PyMem_RawFree(tstate);
@@ -1533,68 +1559,42 @@ add_threadstate(PyInterpreterState *interp, PyThreadState *tstate,
static PyThreadState *
new_threadstate(PyInterpreterState *interp, int whence)
{
- _PyThreadStateImpl *tstate;
- _PyRuntimeState *runtime = interp->runtime;
- // We don't need to allocate a thread state for the main interpreter
- // (the common case), but doing it later for the other case revealed a
- // reentrancy problem (deadlock). So for now we always allocate before
- // taking the interpreters lock. See GH-96071.
- _PyThreadStateImpl *new_tstate = alloc_threadstate();
- int used_newtstate;
- if (new_tstate == NULL) {
+ // Allocate the thread state.
+ _PyThreadStateImpl *tstate = alloc_threadstate(interp);
+ if (tstate == NULL) {
return NULL;
}
+
#ifdef Py_GIL_DISABLED
Py_ssize_t qsbr_idx = _Py_qsbr_reserve(interp);
if (qsbr_idx < 0) {
- PyMem_RawFree(new_tstate);
+ free_threadstate(tstate);
return NULL;
}
#endif
/* We serialize concurrent creation to protect global state. */
- HEAD_LOCK(runtime);
+ HEAD_LOCK(interp->runtime);
+ // Initialize the new thread state.
interp->threads.next_unique_id += 1;
uint64_t id = interp->threads.next_unique_id;
+ init_threadstate(tstate, interp, id, whence);
- // Allocate the thread state and add it to the interpreter.
+ // Add the new thread state to the interpreter.
PyThreadState *old_head = interp->threads.head;
- if (old_head == NULL) {
- // It's the interpreter's initial thread state.
- used_newtstate = 0;
- tstate = &interp->_initial_thread;
- }
- // XXX Re-use interp->_initial_thread if not in use?
- else {
- // Every valid interpreter must have at least one thread.
- assert(id > 1);
- assert(old_head->prev == NULL);
- used_newtstate = 1;
- tstate = new_tstate;
- // Set to _PyThreadState_INIT.
- memcpy(tstate,
- &initial._main_interpreter._initial_thread,
- sizeof(*tstate));
- }
-
- init_threadstate(tstate, interp, id, whence);
add_threadstate(interp, (PyThreadState *)tstate, old_head);
- HEAD_UNLOCK(runtime);
- if (!used_newtstate) {
- // Must be called with lock unlocked to avoid re-entrancy deadlock.
- PyMem_RawFree(new_tstate);
- }
- else {
+ HEAD_UNLOCK(interp->runtime);
#ifdef Py_GIL_DISABLED
+ if (id == 1) {
if (_Py_atomic_load_int(&interp->gc.immortalize) == 0) {
// Immortalize objects marked as using deferred reference counting
// the first time a non-main thread is created.
_PyGC_ImmortalizeDeferredObjects(interp);
}
-#endif
}
+#endif
#ifdef Py_GIL_DISABLED
// Must be called with lock unlocked to avoid lock ordering deadlocks.