From b64ce9f329c50c18824d31e6d69da5786edd1048 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Thu, 19 Jan 2023 13:07:14 -0700 Subject: [PATCH 01/35] Factor out tstate_verify_not_active(). --- Python/pystate.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/Python/pystate.c b/Python/pystate.c index d31c1f166f222c..c32a65ac390931 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -81,6 +81,11 @@ current_fast_clear(_PyRuntimeState *runtime) _Py_atomic_store_relaxed(&runtime->tstate_current, (uintptr_t)NULL); } +#define tstate_verify_not_active(tstate) \ + if (tstate == current_fast_get((tstate)->interp->runtime)) { \ + _Py_FatalErrorFormat(__func__, "tstate %p is still current", tstate); \ + } + //------------------------------------------------ // the thread state bound to the current OS thread @@ -780,11 +785,13 @@ PyInterpreterState_Delete(PyInterpreterState *interp) { _PyRuntimeState *runtime = interp->runtime; struct pyinterpreters *interpreters = &runtime->interpreters; + zapthreads(interp, 0); _PyEval_FiniState(&interp->ceval); - /* Delete current thread. After this, many C API calls become crashy. */ + // XXX Move this above zapthreads(). + /* Unset current thread. After this, many C API calls become crashy. */ _PyThreadState_Swap(runtime, NULL); HEAD_LOCK(runtime); @@ -1281,7 +1288,6 @@ PyThreadState_Clear(PyThreadState *tstate) static void tstate_delete_common(PyThreadState *tstate) { - _Py_EnsureTstateNotNULL(tstate); PyInterpreterState *interp = tstate->interp; if (interp == NULL) { Py_FatalError("NULL interpreter"); @@ -1316,10 +1322,9 @@ tstate_delete_common(PyThreadState *tstate) static void _PyThreadState_Delete(PyThreadState *tstate, int check_current) { + _Py_EnsureTstateNotNULL(tstate); if (check_current) { - if (tstate == current_fast_get(tstate->interp->runtime)) { - _Py_FatalErrorFormat(__func__, "tstate %p is still current", tstate); - } + tstate_verify_not_active(tstate); } tstate_delete_common(tstate); free_threadstate(tstate); From 1be0ec4c0498df1c26a720a28ff5a25e48ff9415 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Thu, 19 Jan 2023 13:16:35 -0700 Subject: [PATCH 02/35] Drop the check_current param from _PyThreadState_Delete(). --- Python/pystate.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Python/pystate.c b/Python/pystate.c index c32a65ac390931..41050b4eac5403 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -38,7 +38,7 @@ extern "C" { #endif /* Forward declarations */ -static void _PyThreadState_Delete(PyThreadState *tstate, int check_current); +static void _PyThreadState_Delete(PyThreadState *tstate); /****************************************/ @@ -775,7 +775,10 @@ zapthreads(PyInterpreterState *interp, int check_current) /* No need to lock the mutex here because this should only happen when the threads are all really dead (XXX famous last words). */ while ((tstate = interp->threads.head) != NULL) { - _PyThreadState_Delete(tstate, check_current); + if (check_current) { + tstate_verify_not_active(tstate); + } + _PyThreadState_Delete(tstate); } } @@ -1320,12 +1323,8 @@ tstate_delete_common(PyThreadState *tstate) } static void -_PyThreadState_Delete(PyThreadState *tstate, int check_current) +_PyThreadState_Delete(PyThreadState *tstate) { - _Py_EnsureTstateNotNULL(tstate); - if (check_current) { - tstate_verify_not_active(tstate); - } tstate_delete_common(tstate); free_threadstate(tstate); } @@ -1334,7 +1333,9 @@ _PyThreadState_Delete(PyThreadState *tstate, int check_current) void PyThreadState_Delete(PyThreadState *tstate) { - _PyThreadState_Delete(tstate, 1); + _Py_EnsureTstateNotNULL(tstate); + tstate_verify_not_active(tstate); + _PyThreadState_Delete(tstate); } From 3454bf1fa6bb26ad03b8468ed8914796a5c6379f Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Thu, 19 Jan 2023 13:19:33 -0700 Subject: [PATCH 03/35] Drop _PyThreadState_Delete(). --- Python/pystate.c | 36 +++++++++++++++--------------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/Python/pystate.c b/Python/pystate.c index 41050b4eac5403..fdf0a9ab9a42fe 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -37,9 +37,6 @@ to avoid the expense of doing their own locking). extern "C" { #endif -/* Forward declarations */ -static void _PyThreadState_Delete(PyThreadState *tstate); - /****************************************/ /* helpers for the current thread state */ @@ -768,20 +765,7 @@ _PyInterpreterState_Clear(PyThreadState *tstate) } -static void -zapthreads(PyInterpreterState *interp, int check_current) -{ - PyThreadState *tstate; - /* No need to lock the mutex here because this should only happen - when the threads are all really dead (XXX famous last words). */ - while ((tstate = interp->threads.head) != NULL) { - if (check_current) { - tstate_verify_not_active(tstate); - } - _PyThreadState_Delete(tstate); - } -} - +static void zapthreads(PyInterpreterState *interp, int check_current); void PyInterpreterState_Delete(PyInterpreterState *interp) @@ -1322,11 +1306,20 @@ tstate_delete_common(PyThreadState *tstate) } } + static void -_PyThreadState_Delete(PyThreadState *tstate) +zapthreads(PyInterpreterState *interp, int check_current) { - tstate_delete_common(tstate); - free_threadstate(tstate); + PyThreadState *tstate; + /* No need to lock the mutex here because this should only happen + when the threads are all really dead (XXX famous last words). */ + while ((tstate = interp->threads.head) != NULL) { + if (check_current) { + tstate_verify_not_active(tstate); + } + tstate_delete_common(tstate); + free_threadstate(tstate); + } } @@ -1335,7 +1328,8 @@ PyThreadState_Delete(PyThreadState *tstate) { _Py_EnsureTstateNotNULL(tstate); tstate_verify_not_active(tstate); - _PyThreadState_Delete(tstate); + tstate_delete_common(tstate); + free_threadstate(tstate); } From a929e32eda9af4a1ce117be3dd8ce9635137aabf Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Thu, 19 Jan 2023 13:27:33 -0700 Subject: [PATCH 04/35] Unset the "current" thread state before zapping the threads instead of after. --- Python/pystate.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Python/pystate.c b/Python/pystate.c index fdf0a9ab9a42fe..bc8a26d732e046 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -773,14 +773,13 @@ PyInterpreterState_Delete(PyInterpreterState *interp) _PyRuntimeState *runtime = interp->runtime; struct pyinterpreters *interpreters = &runtime->interpreters; + /* Unset current thread. After this, many C API calls become crashy. */ + _PyThreadState_Swap(runtime, NULL); + zapthreads(interp, 0); _PyEval_FiniState(&interp->ceval); - // XXX Move this above zapthreads(). - /* Unset current thread. After this, many C API calls become crashy. */ - _PyThreadState_Swap(runtime, NULL); - HEAD_LOCK(runtime); PyInterpreterState **p; for (p = &interpreters->head; ; p = &(*p)->next) { From e6ddd92c3d1260ec30403bb285a85f44bb90f730 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Thu, 19 Jan 2023 13:34:50 -0700 Subject: [PATCH 05/35] Only clear the current thread if the interpreter matches. --- Python/pystate.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Python/pystate.c b/Python/pystate.c index bc8a26d732e046..ca8ea776557f7d 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -773,8 +773,11 @@ PyInterpreterState_Delete(PyInterpreterState *interp) _PyRuntimeState *runtime = interp->runtime; struct pyinterpreters *interpreters = &runtime->interpreters; - /* Unset current thread. After this, many C API calls become crashy. */ - _PyThreadState_Swap(runtime, NULL); + PyThreadState *tcur = current_fast_get(runtime); + if (tcur != NULL && interp == tcur->interp) { + /* Unset current thread. After this, many C API calls become crashy. */ + _PyThreadState_Swap(runtime, NULL); + } zapthreads(interp, 0); From 36c512f5af5ffdf6d79beafbd797dcd8b9b4f136 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Thu, 19 Jan 2023 13:37:05 -0700 Subject: [PATCH 06/35] Always "check_current" in zapthreads(). --- Python/pystate.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/Python/pystate.c b/Python/pystate.c index ca8ea776557f7d..50e0717b22151a 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -765,7 +765,7 @@ _PyInterpreterState_Clear(PyThreadState *tstate) } -static void zapthreads(PyInterpreterState *interp, int check_current); +static void zapthreads(PyInterpreterState *interp); void PyInterpreterState_Delete(PyInterpreterState *interp) @@ -779,7 +779,7 @@ PyInterpreterState_Delete(PyInterpreterState *interp) _PyThreadState_Swap(runtime, NULL); } - zapthreads(interp, 0); + zapthreads(interp); _PyEval_FiniState(&interp->ceval); @@ -840,7 +840,7 @@ _PyInterpreterState_DeleteExceptMain(_PyRuntimeState *runtime) } PyInterpreterState_Clear(interp); // XXX must activate? - zapthreads(interp, 1); + zapthreads(interp); if (interp->id_mutex != NULL) { PyThread_free_lock(interp->id_mutex); } @@ -1310,15 +1310,13 @@ tstate_delete_common(PyThreadState *tstate) static void -zapthreads(PyInterpreterState *interp, int check_current) +zapthreads(PyInterpreterState *interp) { PyThreadState *tstate; /* No need to lock the mutex here because this should only happen when the threads are all really dead (XXX famous last words). */ while ((tstate = interp->threads.head) != NULL) { - if (check_current) { - tstate_verify_not_active(tstate); - } + tstate_verify_not_active(tstate); tstate_delete_common(tstate); free_threadstate(tstate); } From d402a8234a8b076ec74187f1cce54fc557f5ff90 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Thu, 19 Jan 2023 14:13:19 -0700 Subject: [PATCH 07/35] Do not pass the runtime to _PyThreadState_DeleteExcept(). --- Include/internal/pycore_pystate.h | 4 +--- Python/ceval_gil.c | 2 +- Python/pylifecycle.c | 2 +- Python/pystate.c | 4 +++- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Include/internal/pycore_pystate.h b/Include/internal/pycore_pystate.h index 0e46693c1f1283..73d5e2dde60880 100644 --- a/Include/internal/pycore_pystate.h +++ b/Include/internal/pycore_pystate.h @@ -124,9 +124,7 @@ PyAPI_FUNC(void) _PyThreadState_Bind(PyThreadState *tstate); // We keep this around exclusively for stable ABI compatibility. PyAPI_FUNC(void) _PyThreadState_Init( PyThreadState *tstate); -PyAPI_FUNC(void) _PyThreadState_DeleteExcept( - _PyRuntimeState *runtime, - PyThreadState *tstate); +PyAPI_FUNC(void) _PyThreadState_DeleteExcept(PyThreadState *tstate); static inline void diff --git a/Python/ceval_gil.c b/Python/ceval_gil.c index 73d412ba4d71c9..1bf223348d28fa 100644 --- a/Python/ceval_gil.c +++ b/Python/ceval_gil.c @@ -624,7 +624,7 @@ _PyEval_ReInitThreads(PyThreadState *tstate) } /* Destroy all threads except the current one */ - _PyThreadState_DeleteExcept(runtime, tstate); + _PyThreadState_DeleteExcept(tstate); return _PyStatus_OK(); } #endif diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 5ef2d3f6aa72b0..a03949be1b5768 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -1867,7 +1867,7 @@ Py_FinalizeEx(void) _PyRuntimeState_SetFinalizing() has been called, no other Python thread can take the GIL at this point: if they try, they will exit immediately. */ - _PyThreadState_DeleteExcept(runtime, tstate); + _PyThreadState_DeleteExcept(tstate); /* Flush sys.stdout and sys.stderr */ if (flush_std_files() < 0) { diff --git a/Python/pystate.c b/Python/pystate.c index 50e0717b22151a..daacef97db40ba 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1359,9 +1359,11 @@ PyThreadState_DeleteCurrent(void) * be kept in those other interpreters. */ void -_PyThreadState_DeleteExcept(_PyRuntimeState *runtime, PyThreadState *tstate) +_PyThreadState_DeleteExcept(PyThreadState *tstate) { + assert(tstate != NULL); PyInterpreterState *interp = tstate->interp; + _PyRuntimeState *runtime = interp->runtime; HEAD_LOCK(runtime); /* Remove all thread states, except tstate, from the linked list of From 2ddfe71488bb79a2aef97cc677ce33f1819ced13 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Thu, 19 Jan 2023 15:29:51 -0700 Subject: [PATCH 08/35] Add some notes, TODO comments, and asserts. --- Python/pylifecycle.c | 32 ++++++++++++++++++++++++++++++++ Python/pystate.c | 25 ++++++++++++++++++++++++- 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index a03949be1b5768..d0bb0517fa4b41 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -1821,6 +1821,8 @@ Py_FinalizeEx(void) /* Get current thread state and interpreter pointer */ PyThreadState *tstate = _PyRuntimeState_GetThreadState(runtime); + // XXX assert(_Py_IsMainInterpreter(tstate->interp)); + // XXX assert(_Py_IsMainThread()); // Wrap up existing "threading"-module-created, non-daemon threads. wait_for_thread_shutdown(tstate); @@ -1869,6 +1871,22 @@ Py_FinalizeEx(void) immediately. */ _PyThreadState_DeleteExcept(tstate); + /* At this point no Python code should be running at all. + The only thread state left should be the main thread of the main + interpreter (AKA tstate), in which this code is running right now. + There may be other OS threads running but none of them will have + thread states associated with them, nor will be able to create + new thread states. + + Thus tstate is the only possible thread state from here on out. + It may still be used during finalization to run Python code as + needed or provide runtime state (e.g. sys.modules) but that will + happen sparingly. Furthermore, the order of finalization aims + to not need a thread (or interpreter) state as soon as possible. + */ + // XXX Make sure we are preventing the creating of any new thread states + // (or interpreters). + /* Flush sys.stdout and sys.stderr */ if (flush_std_files() < 0) { status = -1; @@ -1958,6 +1976,20 @@ Py_FinalizeEx(void) } #endif /* Py_TRACE_REFS */ + /* At this point there's almost no other Python code that will run, + nor interpreter state needed. The only possibility is the + finalizers of the objects stored on tstate (and tstate->interp), + which are triggered via finalize_interp_clear(). + + For now we operate as though none of those finalizers actually + need an operational thread state or interpreter. In reality, + those finalizers may rely on some part of tstate or + tstate->interp, and/or may raise exceptions + or otherwise fail. + */ + // XXX Do this sooner during finalization. + // XXX Ensure finalizer errors are handled properly. + finalize_interp_clear(tstate); finalize_interp_delete(tstate->interp); diff --git a/Python/pystate.c b/Python/pystate.c index daacef97db40ba..3eab48a2a58738 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -671,18 +671,35 @@ PyInterpreterState_New(void) static void interpreter_clear(PyInterpreterState *interp, PyThreadState *tstate) { + assert(interp != NULL && tstate != NULL); _PyRuntimeState *runtime = interp->runtime; + /* XXX Conditions we need to enforce: + + * the GIL must be held by the current thread + * tstate must be the "current" thread state (current_fast_get()) + * tstate->interp must be interp + * for the main interpreter, tstate must be the main thread + */ + if (_PySys_Audit(tstate, "cpython.PyInterpreterState_Clear", NULL) < 0) { _PyErr_Clear(tstate); } HEAD_LOCK(runtime); + // XXX Clear the current/main thread state last. for (PyThreadState *p = interp->threads.head; p != NULL; p = p->next) { PyThreadState_Clear(p); } HEAD_UNLOCK(runtime); + /* It is possible that any of the objects below have a finalizer + that runs Python code or otherwise relies on a thread state + or even the interpreter state. For now we trust that isn't + a problem. + */ + // XXX Make sure we properly deal with problematic finalizers. + Py_CLEAR(interp->audit_hooks); PyConfig_Clear(&interp->config); @@ -753,7 +770,6 @@ PyInterpreterState_Clear(PyInterpreterState *interp) // garbage. It can be different than the current Python thread state // of 'interp'. PyThreadState *current_tstate = current_fast_get(interp->runtime); - interpreter_clear(interp, current_tstate); } @@ -1226,6 +1242,10 @@ _PyThreadState_Init(PyThreadState *tstate) void PyThreadState_Clear(PyThreadState *tstate) { + // The GIL must be held by the current thread, + // which must not be the target. + // XXX Enforce that (check current_fast_get()). + int verbose = _PyInterpreterState_GetConfig(tstate->interp)->verbose; if (verbose && tstate->cframe->current_frame != NULL) { @@ -1270,6 +1290,9 @@ PyThreadState_Clear(PyThreadState *tstate) if (tstate->on_delete != NULL) { tstate->on_delete(tstate->on_delete_data); } + + // XXX Call _PyThreadStateSwap(runtime, NULL) here if "current". + // XXX Do it as early in the function as possible. } From 99de509f71bb7ebbe1dacd16085ba8c56fe900f8 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Thu, 19 Jan 2023 15:31:10 -0700 Subject: [PATCH 09/35] Mark the main interpreter as finalizing during runtime fini. --- Python/pylifecycle.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index d0bb0517fa4b41..5e3ef3a810af57 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -1824,6 +1824,9 @@ Py_FinalizeEx(void) // XXX assert(_Py_IsMainInterpreter(tstate->interp)); // XXX assert(_Py_IsMainThread()); + // Block some operations. + tstate->interp->finalizing = 1; + // Wrap up existing "threading"-module-created, non-daemon threads. wait_for_thread_shutdown(tstate); From b77ae5e4ec896e84e7dbf94374ff012815d71384 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Thu, 19 Jan 2023 16:25:18 -0700 Subject: [PATCH 10/35] Add more notes and TODO comments. --- Python/pystate.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Python/pystate.c b/Python/pystate.c index 3eab48a2a58738..efeb79b8786287 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1242,6 +1242,13 @@ _PyThreadState_Init(PyThreadState *tstate) void PyThreadState_Clear(PyThreadState *tstate) { + /* XXX Conditions we need to enforce: + + * the GIL must be held by the current thread + * current_fast_get()->interp must match tstate->interp + * for the main interpreter, current_fast_get() must be the main thread + */ + // The GIL must be held by the current thread, // which must not be the target. // XXX Enforce that (check current_fast_get()). @@ -1260,6 +1267,17 @@ PyThreadState_Clear(PyThreadState *tstate) "PyThreadState_Clear: warning: thread still has a frame\n"); } + /* At this point tstate shouldn't be used any more, + neither to run Python code nor for other uses. + + This is tricky when current_fast_get() == tstate, in the same way + as noted in interpreter_clear() above. The below finalizers + can possibly run Python code or otherwise use the partially + cleared thread state. For now we trust that isn't a problem + in practice. + */ + // XXX Deal with the possibility of problematic finalizers. + /* Don't clear tstate->pyframe: it is a borrowed reference */ Py_CLEAR(tstate->dict); From 9ca673c181e60fd78d19cd4b8dd1a6742f40d261 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Thu, 19 Jan 2023 16:56:19 -0700 Subject: [PATCH 11/35] Make PyThreadState._status more granular. --- Include/cpython/pystate.h | 17 ++++++++++++++++- Include/internal/pycore_pystate.h | 12 ------------ Python/pystate.c | 16 ++++++++++------ 3 files changed, 26 insertions(+), 19 deletions(-) diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index 3c1e70de3e4839..ebee45bc97c725 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -119,7 +119,22 @@ struct _ts { PyThreadState *next; PyInterpreterState *interp; - int _status; + struct { + /* Has been initialized to a safe state. + + In order to be effective, this must be set to 0 during or right + after allocation. */ + unsigned int initialized:1; + /* Has been bound to an OS thread. */ + unsigned int bound:1; + /* Has been unbound from its OS thread. */ + unsigned int unbound:1; + // XXX finalizing + // XXX cleared + // XXX finalized + /* padding to align to 4 bytes */ + unsigned int :29; + } _status; int py_recursion_remaining; int py_recursion_limit; diff --git a/Include/internal/pycore_pystate.h b/Include/internal/pycore_pystate.h index 73d5e2dde60880..432cae927a2de4 100644 --- a/Include/internal/pycore_pystate.h +++ b/Include/internal/pycore_pystate.h @@ -137,18 +137,6 @@ _PyThreadState_UpdateTracingState(PyThreadState *tstate) } -/* PyThreadState status */ - -#define PyThreadState_UNINITIALIZED 0 -/* Has been initialized to a safe state. - - In order to be effective, this must be set to 0 during or right - after allocation. */ -#define PyThreadState_INITIALIZED 1 -#define PyThreadState_BOUND 2 -#define PyThreadState_UNBOUND 3 - - /* Other */ PyAPI_FUNC(PyThreadState *) _PyThreadState_Swap( diff --git a/Python/pystate.c b/Python/pystate.c index efeb79b8786287..df0a2f755651a7 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -181,7 +181,8 @@ static void bind_tstate(PyThreadState *tstate) { assert(tstate != NULL); - assert(tstate->_status == PyThreadState_INITIALIZED); + assert(tstate->_status.initialized && !tstate->_status.bound); + // XXX tstate_alive() assert(tstate->thread_id == 0); assert(tstate->native_thread_id == 0); _PyRuntimeState *runtime = tstate->interp->runtime; @@ -214,14 +215,15 @@ bind_tstate(PyThreadState *tstate) tstate->native_thread_id = PyThread_get_thread_native_id(); #endif - tstate->_status = PyThreadState_BOUND; + tstate->_status.bound = 1; } static void unbind_tstate(PyThreadState *tstate) { assert(tstate != NULL); - assert(tstate->_status == PyThreadState_BOUND); + assert(tstate->_status.bound && !tstate->_status.unbound); + // XXX tstate_alive() assert(tstate->thread_id > 0); #ifdef PY_HAVE_THREAD_NATIVE_ID assert(tstate->native_thread_id > 0); @@ -239,7 +241,9 @@ unbind_tstate(PyThreadState *tstate) // Check the `_status` field to know if these values // are still valid. - tstate->_status = PyThreadState_UNBOUND; + // We leave tstate->_status.bound set to 1 + // to indicate it was previously bound. + tstate->_status.unbound = 1; } @@ -1124,7 +1128,7 @@ init_threadstate(PyThreadState *tstate, PyInterpreterState *interp, uint64_t id, PyThreadState *next) { - if (tstate->_status != PyThreadState_UNINITIALIZED) { + if (tstate->_status.initialized) { Py_FatalError("thread state already initialized"); } @@ -1160,7 +1164,7 @@ init_threadstate(PyThreadState *tstate, tstate->datastack_top = NULL; tstate->datastack_limit = NULL; - tstate->_status = PyThreadState_INITIALIZED; + tstate->_status.initialized = 1; } static PyThreadState * From 8a719576e7e85e5f4aa703843d3a252531ed7178 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Thu, 19 Jan 2023 17:33:30 -0700 Subject: [PATCH 12/35] Add more status fields. --- Include/cpython/pystate.h | 12 ++++++++---- Python/pystate.c | 39 +++++++++++++++++++++++++++++++++++---- 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index ebee45bc97c725..fde5179c7833b3 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -125,15 +125,19 @@ struct _ts { In order to be effective, this must be set to 0 during or right after allocation. */ unsigned int initialized:1; + /* Has been bound to an OS thread. */ unsigned int bound:1; /* Has been unbound from its OS thread. */ unsigned int unbound:1; - // XXX finalizing - // XXX cleared - // XXX finalized + + /* various stages of finalization */ + unsigned int finalizing:1; + unsigned int cleared:1; + unsigned int finalized:1; + /* padding to align to 4 bytes */ - unsigned int :29; + unsigned int :26; } _status; int py_recursion_remaining; diff --git a/Python/pystate.c b/Python/pystate.c index df0a2f755651a7..e19d80d643cb89 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -176,13 +176,20 @@ current_tss_reinit(_PyRuntimeState *runtime) } #endif +static inline int tstate_is_alive(PyThreadState *tstate); + +static inline int +tstate_is_bound(PyThreadState *tstate) +{ + return tstate->_status.bound && !tstate->_status.unbound; +} static void bind_tstate(PyThreadState *tstate) { assert(tstate != NULL); - assert(tstate->_status.initialized && !tstate->_status.bound); - // XXX tstate_alive() + assert(tstate_is_alive(tstate) && !tstate->_status.bound); + assert(!tstate->_status.unbound); // just in case assert(tstate->thread_id == 0); assert(tstate->native_thread_id == 0); _PyRuntimeState *runtime = tstate->interp->runtime; @@ -222,8 +229,8 @@ static void unbind_tstate(PyThreadState *tstate) { assert(tstate != NULL); - assert(tstate->_status.bound && !tstate->_status.unbound); - // XXX tstate_alive() + assert(tstate_is_bound(tstate)); + // XXX assert(tstate_is_alive(tstate) && tstate_is_bound(tstate)); assert(tstate->thread_id > 0); #ifdef PY_HAVE_THREAD_NATIVE_ID assert(tstate->native_thread_id > 0); @@ -1078,6 +1085,16 @@ _PyInterpreterState_LookUpID(int64_t requested_id) /* the per-thread runtime state */ /********************************/ +static inline int +tstate_is_alive(PyThreadState *tstate) +{ + return (tstate->_status.initialized && + !tstate->_status.finalized && + !tstate->_status.cleared && + !tstate->_status.finalizing); +} + + //---------- // lifecycle //---------- @@ -1246,6 +1263,10 @@ _PyThreadState_Init(PyThreadState *tstate) void PyThreadState_Clear(PyThreadState *tstate) { + assert(tstate->_status.initialized && !tstate->_status.cleared); + // XXX !tstate->_status.bound || tstate->_status.unbound + tstate->_status.finalizing = 1; // just in case + /* XXX Conditions we need to enforce: * the GIL must be held by the current thread @@ -1313,6 +1334,8 @@ PyThreadState_Clear(PyThreadState *tstate) tstate->on_delete(tstate->on_delete_data); } + tstate->_status.cleared = 1; + // XXX Call _PyThreadStateSwap(runtime, NULL) here if "current". // XXX Do it as early in the function as possible. } @@ -1322,6 +1345,8 @@ PyThreadState_Clear(PyThreadState *tstate) static void tstate_delete_common(PyThreadState *tstate) { + assert(tstate->_status.cleared && !tstate->_status.finalized); + PyInterpreterState *interp = tstate->interp; if (interp == NULL) { Py_FatalError("NULL interpreter"); @@ -1351,6 +1376,8 @@ tstate_delete_common(PyThreadState *tstate) _PyObject_VirtualFree(chunk, chunk->size); chunk = prev; } + + tstate->_status.finalized = 1; } @@ -1583,13 +1610,17 @@ PyThreadState * _PyThreadState_Swap(_PyRuntimeState *runtime, PyThreadState *newts) { PyThreadState *oldts = current_fast_get(runtime); + // XXX assert(oldts == NULL || tstate_is_alive(oldts)); + // XXX tstate_is_bound(oldts) if (newts == NULL) { current_fast_clear(runtime); } else { + assert(tstate_is_alive(newts) && tstate_is_bound(newts)); current_fast_set(runtime, newts); } + /* It should not be possible for more than one thread state to be used for a thread. Check this the best we can in debug builds. From 62d1a93058f959ed38b363c607696331bc8ad45a Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Thu, 19 Jan 2023 17:37:45 -0700 Subject: [PATCH 13/35] Add a TODO. --- Python/pystate.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Python/pystate.c b/Python/pystate.c index e19d80d643cb89..d7875a52e70a9c 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -866,6 +866,8 @@ _PyInterpreterState_DeleteExceptMain(_PyRuntimeState *runtime) continue; } + // XXX Won't this fail since PyInterpreterState_Clear() requires + // the "current" tstate to be set? PyInterpreterState_Clear(interp); // XXX must activate? zapthreads(interp); if (interp->id_mutex != NULL) { From 90cea929f54b3150cbc5529c7ca52f87c335a248 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Thu, 19 Jan 2023 12:51:10 -0700 Subject: [PATCH 14/35] Track active status. --- Include/cpython/pystate.h | 2 ++ Python/pystate.c | 30 +++++++++++++++++++++++++----- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index fde5179c7833b3..0f1715e0301416 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -130,6 +130,8 @@ struct _ts { unsigned int bound:1; /* Has been unbound from its OS thread. */ unsigned int unbound:1; + /* Currently in use (maybe holds the GIL). */ + unsigned int active:1; /* various stages of finalization */ unsigned int finalizing:1; diff --git a/Python/pystate.c b/Python/pystate.c index d7875a52e70a9c..23a480899d43cd 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1276,10 +1276,6 @@ PyThreadState_Clear(PyThreadState *tstate) * for the main interpreter, current_fast_get() must be the main thread */ - // The GIL must be held by the current thread, - // which must not be the target. - // XXX Enforce that (check current_fast_get()). - int verbose = _PyInterpreterState_GetConfig(tstate->interp)->verbose; if (verbose && tstate->cframe->current_frame != NULL) { @@ -1536,6 +1532,26 @@ PyThreadState_GetID(PyThreadState *tstate) } +static inline void +tstate_activate(PyThreadState *tstate) +{ + assert(tstate != NULL); + assert(tstate_is_alive(tstate) && tstate_is_bound(tstate)); + assert(!tstate->_status.active); + tstate->_status.active = 1; +} + +static inline void +tstate_deactivate(PyThreadState *tstate) +{ + assert(tstate != NULL); + assert(tstate_is_bound(tstate)); + // XXX assert(tstate_is_alive(tstate) && tstate_is_bound(tstate)); + assert(tstate->_status.active); + tstate->_status.active = 0; +} + + //---------- // other API //---------- @@ -1612,8 +1628,11 @@ PyThreadState * _PyThreadState_Swap(_PyRuntimeState *runtime, PyThreadState *newts) { PyThreadState *oldts = current_fast_get(runtime); - // XXX assert(oldts == NULL || tstate_is_alive(oldts)); // XXX tstate_is_bound(oldts) + if (oldts != NULL) { + // XXX assert(oldts == NULL || tstate_is_alive(oldts)); + tstate_deactivate(oldts); + } if (newts == NULL) { current_fast_clear(runtime); @@ -1621,6 +1640,7 @@ _PyThreadState_Swap(_PyRuntimeState *runtime, PyThreadState *newts) else { assert(tstate_is_alive(newts) && tstate_is_bound(newts)); current_fast_set(runtime, newts); + tstate_activate(newts); } /* It should not be possible for more than one thread state From 9965813cf6e9bb116fac607ce525503285fce5e2 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 20 Jan 2023 13:38:32 -0700 Subject: [PATCH 15/35] Clarify a TODO comment. --- Python/pystate.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Python/pystate.c b/Python/pystate.c index 23a480899d43cd..d8f1e4436a5e5d 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1266,7 +1266,7 @@ void PyThreadState_Clear(PyThreadState *tstate) { assert(tstate->_status.initialized && !tstate->_status.cleared); - // XXX !tstate->_status.bound || tstate->_status.unbound + // XXX assert(!tstate->_status.bound || tstate->_status.unbound); tstate->_status.finalizing = 1; // just in case /* XXX Conditions we need to enforce: From fd5048be35319855972c2be957ddee515fa88f22 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 20 Jan 2023 14:00:01 -0700 Subject: [PATCH 16/35] Associate "bound" and "active". --- Python/pystate.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Python/pystate.c b/Python/pystate.c index d8f1e4436a5e5d..453a05966b7719 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -190,6 +190,7 @@ bind_tstate(PyThreadState *tstate) assert(tstate != NULL); assert(tstate_is_alive(tstate) && !tstate->_status.bound); assert(!tstate->_status.unbound); // just in case + assert(!tstate->_status.active); assert(tstate->thread_id == 0); assert(tstate->native_thread_id == 0); _PyRuntimeState *runtime = tstate->interp->runtime; @@ -231,6 +232,7 @@ unbind_tstate(PyThreadState *tstate) assert(tstate != NULL); assert(tstate_is_bound(tstate)); // XXX assert(tstate_is_alive(tstate) && tstate_is_bound(tstate)); + // XXX assert(!tstate->_status.active); assert(tstate->thread_id > 0); #ifdef PY_HAVE_THREAD_NATIVE_ID assert(tstate->native_thread_id > 0); From 2105cd69f91584856f024f42f0c3559f7879e098 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 23 Jan 2023 14:34:43 -0700 Subject: [PATCH 17/35] _PyThreadState_Prealloc() -> _PyThreadState_New() --- Include/cpython/pystate.h | 2 -- Include/internal/pycore_pystate.h | 1 + Modules/_threadmodule.c | 2 +- Python/pylifecycle.c | 6 ++++-- Python/pystate.c | 5 +++-- 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index 0f1715e0301416..cd690a5354ee45 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -266,8 +266,6 @@ struct _ts { // Alias for backward compatibility with Python 3.8 #define _PyInterpreterState_Get PyInterpreterState_Get -PyAPI_FUNC(PyThreadState *) _PyThreadState_Prealloc(PyInterpreterState *); - /* Similar to PyThreadState_Get(), but don't issue a fatal error * if it is NULL. */ PyAPI_FUNC(PyThreadState *) _PyThreadState_UncheckedGet(void); diff --git a/Include/internal/pycore_pystate.h b/Include/internal/pycore_pystate.h index 432cae927a2de4..7046ec8d9adaaf 100644 --- a/Include/internal/pycore_pystate.h +++ b/Include/internal/pycore_pystate.h @@ -120,6 +120,7 @@ static inline PyInterpreterState* _PyInterpreterState_GET(void) { // PyThreadState functions +PyAPI_FUNC(PyThreadState *) _PyThreadState_New(PyInterpreterState *interp); PyAPI_FUNC(void) _PyThreadState_Bind(PyThreadState *tstate); // We keep this around exclusively for stable ABI compatibility. PyAPI_FUNC(void) _PyThreadState_Init( diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index bf4b6ec00e3e30..9c12c696757439 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -1161,7 +1161,7 @@ thread_PyThread_start_new_thread(PyObject *self, PyObject *fargs) return PyErr_NoMemory(); } boot->interp = _PyInterpreterState_GET(); - boot->tstate = _PyThreadState_Prealloc(boot->interp); + boot->tstate = _PyThreadState_New(boot->interp); if (boot->tstate == NULL) { PyMem_Free(boot); if (!PyErr_Occurred()) { diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 5e3ef3a810af57..a8a8e7f3d84f21 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -696,10 +696,11 @@ pycore_create_interpreter(_PyRuntimeState *runtime, const _PyInterpreterConfig config = _PyInterpreterConfig_LEGACY_INIT; init_interp_settings(interp, &config); - PyThreadState *tstate = PyThreadState_New(interp); + PyThreadState *tstate = _PyThreadState_New(interp); if (tstate == NULL) { return _PyStatus_ERR("can't make first thread"); } + _PyThreadState_Bind(tstate); (void) PyThreadState_Swap(tstate); status = init_interp_create_gil(tstate); @@ -2074,12 +2075,13 @@ new_interpreter(PyThreadState **tstate_p, const _PyInterpreterConfig *config) return _PyStatus_OK(); } - PyThreadState *tstate = PyThreadState_New(interp); + PyThreadState *tstate = _PyThreadState_New(interp); if (tstate == NULL) { PyInterpreterState_Delete(interp); *tstate_p = NULL; return _PyStatus_OK(); } + _PyThreadState_Bind(tstate); PyThreadState *save_tstate = PyThreadState_Swap(tstate); diff --git a/Python/pystate.c b/Python/pystate.c index 453a05966b7719..c94d577c45fab9 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1251,7 +1251,7 @@ PyThreadState_New(PyInterpreterState *interp) // This must be followed by a call to _PyThreadState_Bind(); PyThreadState * -_PyThreadState_Prealloc(PyInterpreterState *interp) +_PyThreadState_New(PyInterpreterState *interp) { return new_threadstate(interp); } @@ -2066,10 +2066,11 @@ PyGILState_Ensure(void) int has_gil; if (tcur == NULL) { /* Create a new Python thread state for this thread */ - tcur = PyThreadState_New(runtime->gilstate.autoInterpreterState); + tcur = new_threadstate(runtime->gilstate.autoInterpreterState); if (tcur == NULL) { Py_FatalError("Couldn't create thread-state for new thread"); } + bind_tstate(tcur); /* This is our thread state! We'll need to delete it in the matching call to PyGILState_Release(). */ From 8b110cfe0b40572b61e9f07a6dc28e8819378817 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 24 Jan 2023 15:59:43 -0700 Subject: [PATCH 18/35] Factor out tstate_tss_*(). --- Python/pystate.c | 122 +++++++++++++++++++++++++++-------------------- 1 file changed, 71 insertions(+), 51 deletions(-) diff --git a/Python/pystate.c b/Python/pystate.c index c94d577c45fab9..6d1e8b2ef0a5c2 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -88,64 +88,46 @@ current_fast_clear(_PyRuntimeState *runtime) // the thread state bound to the current OS thread //------------------------------------------------ -/* - The stored thread state is set by bind_tstate() (AKA PyThreadState_Bind(). - - The GIL does no need to be held for these. - */ - -static int -current_tss_initialized(_PyRuntimeState *runtime) +static inline int +tstate_tss_initialized(Py_tss_t *key) { - return PyThread_tss_is_created(&runtime->autoTSSkey); + return PyThread_tss_is_created(key); } -static PyStatus -current_tss_init(_PyRuntimeState *runtime) +static inline int +tstate_tss_init(Py_tss_t *key) { - assert(!current_tss_initialized(runtime)); - if (PyThread_tss_create(&runtime->autoTSSkey) != 0) { - return _PyStatus_NO_MEMORY(); - } - return _PyStatus_OK(); + assert(!tstate_tss_initialized(key)); + return PyThread_tss_create(key); } -static void -current_tss_fini(_PyRuntimeState *runtime) +static inline void +tstate_tss_fini(Py_tss_t *key) { - assert(current_tss_initialized(runtime)); - PyThread_tss_delete(&runtime->autoTSSkey); + assert(tstate_tss_initialized(key)); + PyThread_tss_delete(key); } static inline PyThreadState * -current_tss_get(_PyRuntimeState *runtime) +tstate_tss_get(Py_tss_t *key) { - assert(current_tss_initialized(runtime)); - return (PyThreadState *)PyThread_tss_get(&runtime->autoTSSkey); + assert(tstate_tss_initialized(key)); + return (PyThreadState *)PyThread_tss_get(key); } static inline int -_current_tss_set(_PyRuntimeState *runtime, PyThreadState *tstate) +tstate_tss_set(Py_tss_t *key, PyThreadState *tstate) { assert(tstate != NULL); - assert(current_tss_initialized(runtime)); - return PyThread_tss_set(&runtime->autoTSSkey, (void *)tstate); -} -static inline void -current_tss_set(_PyRuntimeState *runtime, PyThreadState *tstate) -{ - if (_current_tss_set(runtime, tstate) != 0) { - Py_FatalError("failed to set current tstate (TSS)"); - } + assert(tstate_tss_initialized(key)); + return PyThread_tss_set(key, (void *)tstate); } -static inline void -current_tss_clear(_PyRuntimeState *runtime) +static inline int +tstate_tss_clear(Py_tss_t *key) { - assert(current_tss_initialized(runtime)); - if (PyThread_tss_set(&runtime->autoTSSkey, NULL) != 0) { - Py_FatalError("failed to clear current tstate (TSS)"); - } + assert(tstate_tss_initialized(key)); + return PyThread_tss_set(key, (void *)NULL); } #ifdef HAVE_FORK @@ -154,28 +136,67 @@ current_tss_clear(_PyRuntimeState *runtime) * don't reset TSS upon fork(), see issue #10517. */ static PyStatus -current_tss_reinit(_PyRuntimeState *runtime) +tstate_tss_reinit(Py_tss_t *key) { - if (!current_tss_initialized(runtime)) { + if (!tstate_tss_initialized(key)) { return _PyStatus_OK(); } - PyThreadState *tstate = current_tss_get(runtime); + PyThreadState *tstate = tstate_tss_get(key); - current_tss_fini(runtime); - PyStatus status = current_tss_init(runtime); - if (_PyStatus_EXCEPTION(status)) { - return status; + tstate_tss_fini(key); + if (tstate_tss_init(key) != 0) { + return _PyStatus_NO_MEMORY(); } /* If the thread had an associated auto thread state, reassociate it with * the new key. */ - if (tstate && _current_tss_set(runtime, tstate) != 0) { - return _PyStatus_ERR("failed to set autoTSSkey"); + if (tstate && tstate_tss_set(key, tstate) != 0) { + return _PyStatus_ERR("failed to re-set autoTSSkey"); } return _PyStatus_OK(); } #endif + +/* + The stored thread state is set by bind_tstate() (AKA PyThreadState_Bind(). + + The GIL does no need to be held for these. + */ + +#define current_tss_initialized(runtime) \ + tstate_tss_initialized(&(runtime)->autoTSSkey) +#define current_tss_init(runtime) \ + tstate_tss_init(&(runtime)->autoTSSkey) +#define current_tss_fini(runtime) \ + tstate_tss_fini(&(runtime)->autoTSSkey) +#define current_tss_get(runtime) \ + tstate_tss_get(&(runtime)->autoTSSkey) +#define _current_tss_set(runtime, tstate) \ + tstate_tss_set(&(runtime)->autoTSSkey, tstate) +#define _current_tss_clear(runtime) \ + tstate_tss_clear(&(runtime)->autoTSSkey) +#define current_tss_reinit(runtime) \ + tstate_tss_reinit(&(runtime)->autoTSSkey) + +static inline void +current_tss_set(_PyRuntimeState *runtime, PyThreadState *tstate) +{ + assert(tstate != NULL && tstate->interp->runtime == runtime); + if (_current_tss_set(runtime, tstate) != 0) { + Py_FatalError("failed to set current tstate (TSS)"); + } +} + +static inline void +current_tss_clear(_PyRuntimeState *runtime) +{ + if (_current_tss_clear(runtime) != 0) { + Py_FatalError("failed to clear current tstate (TSS)"); + } +} + + static inline int tstate_is_alive(PyThreadState *tstate); static inline int @@ -409,10 +430,9 @@ _PyRuntimeState_Init(_PyRuntimeState *runtime) memcpy(runtime, &initial, sizeof(*runtime)); } - PyStatus status = current_tss_init(runtime); - if (_PyStatus_EXCEPTION(status)) { + if (current_tss_init(runtime) != 0) { _PyRuntimeState_Fini(runtime); - return status; + return _PyStatus_NO_MEMORY(); } if (PyThread_tss_create(&runtime->trashTSSkey) != 0) { From ca98d68b07d8738c587953284cde75a2df39c7c2 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 24 Jan 2023 16:31:54 -0700 Subject: [PATCH 19/35] current_tss_*() -> gilstate_tss_*(). --- Python/pystate.c | 64 ++++++++++++++++++++++++------------------------ 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/Python/pystate.c b/Python/pystate.c index 6d1e8b2ef0a5c2..f5a1a525798c10 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -164,34 +164,34 @@ tstate_tss_reinit(Py_tss_t *key) The GIL does no need to be held for these. */ -#define current_tss_initialized(runtime) \ +#define gilstate_tss_initialized(runtime) \ tstate_tss_initialized(&(runtime)->autoTSSkey) -#define current_tss_init(runtime) \ +#define gilstate_tss_init(runtime) \ tstate_tss_init(&(runtime)->autoTSSkey) -#define current_tss_fini(runtime) \ +#define gilstate_tss_fini(runtime) \ tstate_tss_fini(&(runtime)->autoTSSkey) -#define current_tss_get(runtime) \ +#define gilstate_tss_get(runtime) \ tstate_tss_get(&(runtime)->autoTSSkey) -#define _current_tss_set(runtime, tstate) \ +#define _gilstate_tss_set(runtime, tstate) \ tstate_tss_set(&(runtime)->autoTSSkey, tstate) -#define _current_tss_clear(runtime) \ +#define _gilstate_tss_clear(runtime) \ tstate_tss_clear(&(runtime)->autoTSSkey) -#define current_tss_reinit(runtime) \ +#define gilstate_tss_reinit(runtime) \ tstate_tss_reinit(&(runtime)->autoTSSkey) static inline void -current_tss_set(_PyRuntimeState *runtime, PyThreadState *tstate) +gilstate_tss_set(_PyRuntimeState *runtime, PyThreadState *tstate) { assert(tstate != NULL && tstate->interp->runtime == runtime); - if (_current_tss_set(runtime, tstate) != 0) { + if (_gilstate_tss_set(runtime, tstate) != 0) { Py_FatalError("failed to set current tstate (TSS)"); } } static inline void -current_tss_clear(_PyRuntimeState *runtime) +gilstate_tss_clear(_PyRuntimeState *runtime) { - if (_current_tss_clear(runtime) != 0) { + if (_gilstate_tss_clear(runtime) != 0) { Py_FatalError("failed to clear current tstate (TSS)"); } } @@ -235,8 +235,8 @@ bind_tstate(PyThreadState *tstate) (This is a better fix for SF bug #1010677 than the first one attempted.) */ // XXX Skipping like this does not play nice with multiple interpreters. - if (current_tss_get(runtime) == NULL) { - current_tss_set(runtime, tstate); + if (gilstate_tss_get(runtime) == NULL) { + gilstate_tss_set(runtime, tstate); } tstate->thread_id = PyThread_get_thread_ident(); @@ -260,10 +260,10 @@ unbind_tstate(PyThreadState *tstate) #endif _PyRuntimeState *runtime = tstate->interp->runtime; - if (current_tss_initialized(runtime) && - tstate == current_tss_get(runtime)) + if (gilstate_tss_initialized(runtime) && + tstate == gilstate_tss_get(runtime)) { - current_tss_clear(runtime); + gilstate_tss_clear(runtime); } // We leave thread_id and native_thraed_id alone @@ -297,7 +297,7 @@ holds_gil(PyThreadState *tstate) assert(tstate != NULL); _PyRuntimeState *runtime = tstate->interp->runtime; /* Must be the tstate for this thread */ - assert(tstate == current_tss_get(runtime)); + assert(tstate == gilstate_tss_get(runtime)); return tstate == current_fast_get(runtime); } @@ -430,7 +430,7 @@ _PyRuntimeState_Init(_PyRuntimeState *runtime) memcpy(runtime, &initial, sizeof(*runtime)); } - if (current_tss_init(runtime) != 0) { + if (gilstate_tss_init(runtime) != 0) { _PyRuntimeState_Fini(runtime); return _PyStatus_NO_MEMORY(); } @@ -449,8 +449,8 @@ _PyRuntimeState_Init(_PyRuntimeState *runtime) void _PyRuntimeState_Fini(_PyRuntimeState *runtime) { - if (current_tss_initialized(runtime)) { - current_tss_fini(runtime); + if (gilstate_tss_initialized(runtime)) { + gilstate_tss_fini(runtime); } if (PyThread_tss_is_created(&runtime->trashTSSkey)) { @@ -510,7 +510,7 @@ _PyRuntimeState_ReInitThreads(_PyRuntimeState *runtime) } - PyStatus status = current_tss_reinit(runtime); + PyStatus status = gilstate_tss_reinit(runtime); if (_PyStatus_EXCEPTION(status)) { return status; } @@ -1671,12 +1671,12 @@ _PyThreadState_Swap(_PyRuntimeState *runtime, PyThreadState *newts) */ // XXX The above isn't true when multiple interpreters are involved. #if defined(Py_DEBUG) - if (newts && current_tss_initialized(runtime)) { + if (newts && gilstate_tss_initialized(runtime)) { /* This can be called from PyEval_RestoreThread(). Similar to it, we need to ensure errno doesn't change. */ int err = errno; - PyThreadState *check = current_tss_get(runtime); + PyThreadState *check = gilstate_tss_get(runtime); if (check && check->interp == newts->interp && check != newts) { Py_FatalError("Invalid thread state for this thread"); } @@ -1985,7 +1985,7 @@ _PyGILState_Init(PyInterpreterState *interp) return _PyStatus_OK(); } _PyRuntimeState *runtime = interp->runtime; - assert(current_tss_get(runtime) == NULL); + assert(gilstate_tss_get(runtime) == NULL); assert(runtime->gilstate.autoInterpreterState == NULL); runtime->gilstate.autoInterpreterState = interp; return _PyStatus_OK(); @@ -2021,7 +2021,7 @@ _PyGILState_SetTstate(PyThreadState *tstate) _PyRuntimeState *runtime = tstate->interp->runtime; assert(runtime->gilstate.autoInterpreterState == tstate->interp); - assert(current_tss_get(runtime) == tstate); + assert(gilstate_tss_get(runtime) == tstate); assert(tstate->gilstate_counter == 1); #endif @@ -2040,10 +2040,10 @@ PyThreadState * PyGILState_GetThisThreadState(void) { _PyRuntimeState *runtime = &_PyRuntime; - if (!current_tss_initialized(runtime)) { + if (!gilstate_tss_initialized(runtime)) { return NULL; } - return current_tss_get(runtime); + return gilstate_tss_get(runtime); } int @@ -2054,7 +2054,7 @@ PyGILState_Check(void) return 1; } - if (!current_tss_initialized(runtime)) { + if (!gilstate_tss_initialized(runtime)) { return 1; } @@ -2063,7 +2063,7 @@ PyGILState_Check(void) return 0; } - return (tstate == current_tss_get(runtime)); + return (tstate == gilstate_tss_get(runtime)); } PyGILState_STATE @@ -2079,10 +2079,10 @@ PyGILState_Ensure(void) /* Ensure that _PyEval_InitThreads() and _PyGILState_Init() have been called by Py_Initialize() */ assert(_PyEval_ThreadsInitialized(runtime)); - assert(current_tss_initialized(runtime)); + assert(gilstate_tss_initialized(runtime)); assert(runtime->gilstate.autoInterpreterState != NULL); - PyThreadState *tcur = current_tss_get(runtime); + PyThreadState *tcur = gilstate_tss_get(runtime); int has_gil; if (tcur == NULL) { /* Create a new Python thread state for this thread */ @@ -2120,7 +2120,7 @@ void PyGILState_Release(PyGILState_STATE oldstate) { _PyRuntimeState *runtime = &_PyRuntime; - PyThreadState *tstate = current_tss_get(runtime); + PyThreadState *tstate = gilstate_tss_get(runtime); if (tstate == NULL) { Py_FatalError("auto-releasing thread-state, " "but no thread-state for this thread"); From 4f39976bf2ae723712b50b036815e8f7e6f91cbf Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 24 Jan 2023 16:42:30 -0700 Subject: [PATCH 20/35] Add bind_gilstate_tstate() and unbind_gilstate_tstate(). --- Include/cpython/pystate.h | 4 +- Python/pystate.c | 105 ++++++++++++++++++++++++++++---------- 2 files changed, 81 insertions(+), 28 deletions(-) diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index cd690a5354ee45..88bd1b7a82f2d6 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -130,6 +130,8 @@ struct _ts { unsigned int bound:1; /* Has been unbound from its OS thread. */ unsigned int unbound:1; + /* Has been bound aa current for the GILState API. */ + unsigned int bound_gilstate:1; /* Currently in use (maybe holds the GIL). */ unsigned int active:1; @@ -139,7 +141,7 @@ struct _ts { unsigned int finalized:1; /* padding to align to 4 bytes */ - unsigned int :26; + unsigned int :25; } _status; int py_recursion_remaining; diff --git a/Python/pystate.c b/Python/pystate.c index f5a1a525798c10..81f2783e734ddf 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -205,15 +205,73 @@ tstate_is_bound(PyThreadState *tstate) return tstate->_status.bound && !tstate->_status.unbound; } +static void bind_gilstate_tstate(PyThreadState *); +static void unbind_gilstate_tstate(PyThreadState *); + static void bind_tstate(PyThreadState *tstate) { assert(tstate != NULL); assert(tstate_is_alive(tstate) && !tstate->_status.bound); assert(!tstate->_status.unbound); // just in case + assert(!tstate->_status.bound_gilstate); + assert(tstate != gilstate_tss_get(tstate->interp->runtime)); assert(!tstate->_status.active); assert(tstate->thread_id == 0); assert(tstate->native_thread_id == 0); + + // Currently we don't necessarily store the thread state + // in thread-local storage (e.g. per-interpreter). + + tstate->thread_id = PyThread_get_thread_ident(); +#ifdef PY_HAVE_THREAD_NATIVE_ID + tstate->native_thread_id = PyThread_get_thread_native_id(); +#endif + + tstate->_status.bound = 1; + + // This make sure there's a gilstate tstate bound + // as soon as possible. + if (gilstate_tss_get(tstate->interp->runtime) == NULL) { + bind_gilstate_tstate(tstate); + } +} + +static void +unbind_tstate(PyThreadState *tstate) +{ + assert(tstate != NULL); + // XXX assert(tstate_is_alive(tstate)); + assert(tstate_is_bound(tstate)); + // XXX assert(!tstate->_status.active); + assert(tstate->thread_id > 0); +#ifdef PY_HAVE_THREAD_NATIVE_ID + assert(tstate->native_thread_id > 0); +#endif + + if (tstate->_status.bound_gilstate) { + unbind_gilstate_tstate(tstate); + } + + // We leave thread_id and native_thraed_id alone + // since they can be useful for debugging. + // Check the `_status` field to know if these values + // are still valid. + + // We leave tstate->_status.bound set to 1 + // to indicate it was previously bound. + tstate->_status.unbound = 1; +} + + +static void +bind_gilstate_tstate(PyThreadState *tstate) +{ + assert(tstate != NULL); + assert(tstate_is_alive(tstate)); + assert(tstate_is_bound(tstate)); + // XXX assert(!tstate->_status.active); + // XXX assert(!tstate->_status.bound_gilstate); _PyRuntimeState *runtime = tstate->interp->runtime; /* Stick the thread state for this thread in thread specific storage. @@ -238,42 +296,24 @@ bind_tstate(PyThreadState *tstate) if (gilstate_tss_get(runtime) == NULL) { gilstate_tss_set(runtime, tstate); } - - tstate->thread_id = PyThread_get_thread_ident(); -#ifdef PY_HAVE_THREAD_NATIVE_ID - tstate->native_thread_id = PyThread_get_thread_native_id(); -#endif - - tstate->_status.bound = 1; + tstate->_status.bound_gilstate = 1; } static void -unbind_tstate(PyThreadState *tstate) +unbind_gilstate_tstate(PyThreadState *tstate) { assert(tstate != NULL); + // XXX assert(tstate_is_alive(tstate)); assert(tstate_is_bound(tstate)); - // XXX assert(tstate_is_alive(tstate) && tstate_is_bound(tstate)); // XXX assert(!tstate->_status.active); - assert(tstate->thread_id > 0); -#ifdef PY_HAVE_THREAD_NATIVE_ID - assert(tstate->native_thread_id > 0); -#endif - _PyRuntimeState *runtime = tstate->interp->runtime; + assert(tstate->_status.bound_gilstate); + // XXX assert(tstate == gilstate_tss_get(tstate->interp->runtime)); - if (gilstate_tss_initialized(runtime) && - tstate == gilstate_tss_get(runtime)) - { - gilstate_tss_clear(runtime); + // XXX This check *should* always succeed. + if (tstate == gilstate_tss_get(tstate->interp->runtime)) { + gilstate_tss_clear(tstate->interp->runtime); } - - // We leave thread_id and native_thraed_id alone - // since they can be useful for debugging. - // Check the `_status` field to know if these values - // are still valid. - - // We leave tstate->_status.bound set to 1 - // to indicate it was previously bound. - tstate->_status.unbound = 1; + tstate->_status.bound_gilstate = 0; } @@ -1560,6 +1600,11 @@ tstate_activate(PyThreadState *tstate) assert(tstate != NULL); assert(tstate_is_alive(tstate) && tstate_is_bound(tstate)); assert(!tstate->_status.active); + + if (!tstate->_status.bound_gilstate) { + bind_gilstate_tstate(tstate); + } + tstate->_status.active = 1; } @@ -1570,7 +1615,11 @@ tstate_deactivate(PyThreadState *tstate) assert(tstate_is_bound(tstate)); // XXX assert(tstate_is_alive(tstate) && tstate_is_bound(tstate)); assert(tstate->_status.active); + tstate->_status.active = 0; + + // We do not unbind the gilstate tstate here. + // It will still be used in PyGILState_Ensure(). } @@ -2091,6 +2140,7 @@ PyGILState_Ensure(void) Py_FatalError("Couldn't create thread-state for new thread"); } bind_tstate(tcur); + bind_gilstate_tstate(tcur); /* This is our thread state! We'll need to delete it in the matching call to PyGILState_Release(). */ @@ -2146,6 +2196,7 @@ PyGILState_Release(PyGILState_STATE oldstate) if (tstate->gilstate_counter == 0) { /* can't have been locked when we created it */ assert(oldstate == PyGILState_UNLOCKED); + // XXX Unbind tstate here. PyThreadState_Clear(tstate); /* Delete the thread-state. Note this releases the GIL too! * It's vital that the GIL be held here, to avoid shutdown From 6e7366965117aaef56e8b08cd17eff4088ef2e5f Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 24 Jan 2023 13:41:24 -0700 Subject: [PATCH 21/35] Update some TODO comments. --- Python/pystate.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Python/pystate.c b/Python/pystate.c index 81f2783e734ddf..c4a379784e9956 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -744,7 +744,8 @@ PyInterpreterState_New(void) static void interpreter_clear(PyInterpreterState *interp, PyThreadState *tstate) { - assert(interp != NULL && tstate != NULL); + assert(interp != NULL); + assert(tstate != NULL); _PyRuntimeState *runtime = interp->runtime; /* XXX Conditions we need to enforce: @@ -754,6 +755,8 @@ interpreter_clear(PyInterpreterState *interp, PyThreadState *tstate) * tstate->interp must be interp * for the main interpreter, tstate must be the main thread */ + // XXX Ideally, we would not rely on any thread state in this function + // (and we would drop the "tstate" argument). if (_PySys_Audit(tstate, "cpython.PyInterpreterState_Clear", NULL) < 0) { _PyErr_Clear(tstate); @@ -862,6 +865,8 @@ PyInterpreterState_Delete(PyInterpreterState *interp) _PyRuntimeState *runtime = interp->runtime; struct pyinterpreters *interpreters = &runtime->interpreters; + // XXX Clearing the "current" thread state should happen before + // we start finalizing the interpreter (or the current thread state). PyThreadState *tcur = current_fast_get(runtime); if (tcur != NULL && interp == tcur->interp) { /* Unset current thread. After this, many C API calls become crashy. */ @@ -1612,8 +1617,8 @@ static inline void tstate_deactivate(PyThreadState *tstate) { assert(tstate != NULL); + // XXX assert(tstate_is_alive(tstate)); assert(tstate_is_bound(tstate)); - // XXX assert(tstate_is_alive(tstate) && tstate_is_bound(tstate)); assert(tstate->_status.active); tstate->_status.active = 0; From cc3540d4b769184960fe1ccef560083f3b712127 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 24 Jan 2023 13:42:21 -0700 Subject: [PATCH 22/35] Clean up _PyThreadState_Swap() a little. --- Python/pystate.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Python/pystate.c b/Python/pystate.c index c4a379784e9956..4108e8af4a3341 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1704,16 +1704,14 @@ PyThreadState * _PyThreadState_Swap(_PyRuntimeState *runtime, PyThreadState *newts) { PyThreadState *oldts = current_fast_get(runtime); - // XXX tstate_is_bound(oldts) + + current_fast_clear(runtime); + if (oldts != NULL) { - // XXX assert(oldts == NULL || tstate_is_alive(oldts)); + // XXX assert(tstate_is_alive(oldts) && tstate_is_bound(oldts)); tstate_deactivate(oldts); } - - if (newts == NULL) { - current_fast_clear(runtime); - } - else { + if (newts != NULL) { assert(tstate_is_alive(newts) && tstate_is_bound(newts)); current_fast_set(runtime, newts); tstate_activate(newts); @@ -1731,8 +1729,10 @@ _PyThreadState_Swap(_PyRuntimeState *runtime, PyThreadState *newts) */ int err = errno; PyThreadState *check = gilstate_tss_get(runtime); - if (check && check->interp == newts->interp && check != newts) { - Py_FatalError("Invalid thread state for this thread"); + if (check != newts) { + if (check && check->interp == newts->interp) { + Py_FatalError("Invalid thread state for this thread"); + } } errno = err; } From 1ce3841a81d3ba095f17438d03f4921fc7eb01d2 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 24 Jan 2023 17:56:39 -0700 Subject: [PATCH 23/35] Fix the stable ABI. --- Include/cpython/pystate.h | 4 ++++ Python/pystate.c | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index 88bd1b7a82f2d6..3d4559ce992218 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -268,6 +268,10 @@ struct _ts { // Alias for backward compatibility with Python 3.8 #define _PyInterpreterState_Get PyInterpreterState_Get +/* An alias for the internal _PyThreadState_New(), + kept for stable ABI compatibility. */ +PyAPI_FUNC(PyThreadState *) _PyThreadState_Prealloc(PyInterpreterState *); + /* Similar to PyThreadState_Get(), but don't issue a fatal error * if it is NULL. */ PyAPI_FUNC(PyThreadState *) _PyThreadState_UncheckedGet(void); diff --git a/Python/pystate.c b/Python/pystate.c index 4108e8af4a3341..d94587cf30264f 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1321,6 +1321,13 @@ _PyThreadState_New(PyInterpreterState *interp) return new_threadstate(interp); } +// We keep this for stable ABI compabibility. +PyThreadState * +_PyThreadState_Prealloc(PyInterpreterState *interp) +{ + return _PyThreadState_New(interp); +} + // We keep this around for (accidental) stable ABI compatibility. // Realisically, no extensions are using it. void From 121d328986b5efc1c113b57110795d7d91e4b567 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 25 Jan 2023 10:15:32 -0700 Subject: [PATCH 24/35] Fixes for multiprocessing. --- Python/pystate.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Python/pystate.c b/Python/pystate.c index d94587cf30264f..07d27874f58b13 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1610,7 +1610,8 @@ static inline void tstate_activate(PyThreadState *tstate) { assert(tstate != NULL); - assert(tstate_is_alive(tstate) && tstate_is_bound(tstate)); + // XXX assert(tstate_is_alive(tstate)); + assert(tstate_is_bound(tstate)); assert(!tstate->_status.active); if (!tstate->_status.bound_gilstate) { @@ -1719,7 +1720,8 @@ _PyThreadState_Swap(_PyRuntimeState *runtime, PyThreadState *newts) tstate_deactivate(oldts); } if (newts != NULL) { - assert(tstate_is_alive(newts) && tstate_is_bound(newts)); + // XXX assert(tstate_is_alive(newts)); + assert(tstate_is_bound(newts)); current_fast_set(runtime, newts); tstate_activate(newts); } From 8666362da66f3eb9fb6c7b179955e2c9122c32df Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 25 Jan 2023 11:22:45 -0700 Subject: [PATCH 25/35] Move a comment. --- Python/pystate.c | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/Python/pystate.c b/Python/pystate.c index 07d27874f58b13..45345f3d51fbcb 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -264,6 +264,24 @@ unbind_tstate(PyThreadState *tstate) } +/* Stick the thread state for this thread in thread specific storage. + + When a thread state is created for a thread by some mechanism + other than PyGILState_Ensure(), it's important that the GILState + machinery knows about it so it doesn't try to create another + thread state for the thread. + (This is a better fix for SF bug #1010677 than the first one attempted.) + + The only situation where you can legitimately have more than one + thread state for an OS level thread is when there are multiple + interpreters. + + The PyGILState_*() APIs don't work with multiple + interpreters (see bpo-10915 and bpo-15751), so this function + sets TSS only once. Thus, the first thread state created for that + given OS level thread will "win", which seems reasonable behaviour. +*/ + static void bind_gilstate_tstate(PyThreadState *tstate) { @@ -274,24 +292,6 @@ bind_gilstate_tstate(PyThreadState *tstate) // XXX assert(!tstate->_status.bound_gilstate); _PyRuntimeState *runtime = tstate->interp->runtime; - /* Stick the thread state for this thread in thread specific storage. - - The only situation where you can legitimately have more than one - thread state for an OS level thread is when there are multiple - interpreters. - - You shouldn't really be using the PyGILState_ APIs anyway (see issues - #10915 and #15751). - - The first thread state created for that given OS level thread will - "win", which seems reasonable behaviour. - */ - /* When a thread state is created for a thread by some mechanism - other than PyGILState_Ensure(), it's important that the GILState - machinery knows about it so it doesn't try to create another - thread state for the thread. - (This is a better fix for SF bug #1010677 than the first one attempted.) - */ // XXX Skipping like this does not play nice with multiple interpreters. if (gilstate_tss_get(runtime) == NULL) { gilstate_tss_set(runtime, tstate); From 52ac2d9e86868688194060a1a3807585538f0118 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 25 Jan 2023 14:17:34 -0700 Subject: [PATCH 26/35] Preserve errno more carefully. --- Python/pystate.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Python/pystate.c b/Python/pystate.c index 45345f3d51fbcb..4c0b5fcd8c5f44 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1711,6 +1711,12 @@ PyThreadState_Get(void) PyThreadState * _PyThreadState_Swap(_PyRuntimeState *runtime, PyThreadState *newts) { +#if defined(Py_DEBUG) + /* This can be called from PyEval_RestoreThread(). Similar + to it, we need to ensure errno doesn't change. + */ + int err = errno; +#endif PyThreadState *oldts = current_fast_get(runtime); current_fast_clear(runtime); @@ -1733,18 +1739,14 @@ _PyThreadState_Swap(_PyRuntimeState *runtime, PyThreadState *newts) // XXX The above isn't true when multiple interpreters are involved. #if defined(Py_DEBUG) if (newts && gilstate_tss_initialized(runtime)) { - /* This can be called from PyEval_RestoreThread(). Similar - to it, we need to ensure errno doesn't change. - */ - int err = errno; PyThreadState *check = gilstate_tss_get(runtime); if (check != newts) { if (check && check->interp == newts->interp) { Py_FatalError("Invalid thread state for this thread"); } } - errno = err; } + errno = err; #endif return oldts; } From f7ac19adfccc9712dc1f6e721d29c154a6e1f912 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 25 Jan 2023 14:43:48 -0700 Subject: [PATCH 27/35] Do not call bind_gilstate_tstate() in bind_tstate(). --- Python/pystate.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/Python/pystate.c b/Python/pystate.c index 4c0b5fcd8c5f44..9820e869b478c4 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -229,12 +229,6 @@ bind_tstate(PyThreadState *tstate) #endif tstate->_status.bound = 1; - - // This make sure there's a gilstate tstate bound - // as soon as possible. - if (gilstate_tss_get(tstate->interp->runtime) == NULL) { - bind_gilstate_tstate(tstate); - } } static void @@ -1310,6 +1304,11 @@ PyThreadState_New(PyInterpreterState *interp) PyThreadState *tstate = new_threadstate(interp); if (tstate) { bind_tstate(tstate); + // This make sure there's a gilstate tstate bound + // as soon as possible. + if (gilstate_tss_get(tstate->interp->runtime) == NULL) { + bind_gilstate_tstate(tstate); + } } return tstate; } @@ -1762,6 +1761,11 @@ void _PyThreadState_Bind(PyThreadState *tstate) { bind_tstate(tstate); + // This make sure there's a gilstate tstate bound + // as soon as possible. + if (gilstate_tss_get(tstate->interp->runtime) == NULL) { + bind_gilstate_tstate(tstate); + } } From a338248a201897b98a537156ac37367467871705 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 25 Jan 2023 14:56:09 -0700 Subject: [PATCH 28/35] Add an assert. --- Python/pystate.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Python/pystate.c b/Python/pystate.c index 9820e869b478c4..439ebdadb48ce1 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1613,6 +1613,8 @@ tstate_activate(PyThreadState *tstate) assert(tstate_is_bound(tstate)); assert(!tstate->_status.active); + assert(!tstate->_status.bound_gilstate || + tstate == gilstate_tss_get((tstate->interp->runtime))); if (!tstate->_status.bound_gilstate) { bind_gilstate_tstate(tstate); } From 5be78e9c79a90a3bba27de8c67076d7fbff36284 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 25 Jan 2023 14:56:36 -0700 Subject: [PATCH 29/35] Clean up bind_gilstate_tstate(). --- Python/pystate.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Python/pystate.c b/Python/pystate.c index 439ebdadb48ce1..f792d919400c13 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -283,13 +283,17 @@ bind_gilstate_tstate(PyThreadState *tstate) assert(tstate_is_alive(tstate)); assert(tstate_is_bound(tstate)); // XXX assert(!tstate->_status.active); - // XXX assert(!tstate->_status.bound_gilstate); + assert(!tstate->_status.bound_gilstate); + _PyRuntimeState *runtime = tstate->interp->runtime; + PyThreadState *tcur = gilstate_tss_get(runtime); + assert(tstate != tcur); - // XXX Skipping like this does not play nice with multiple interpreters. - if (gilstate_tss_get(runtime) == NULL) { - gilstate_tss_set(runtime, tstate); + if (tcur != NULL) { + // XXX Skipping like this does not play nice with multiple interpreters. + return; } + gilstate_tss_set(runtime, tstate); tstate->_status.bound_gilstate = 1; } From f019bd68fad5c32ad9603e37bedb4d62d9ce8df4 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 25 Jan 2023 14:59:47 -0700 Subject: [PATCH 30/35] Clear bound_gilstate for the old thread state. --- Python/pystate.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Python/pystate.c b/Python/pystate.c index f792d919400c13..f7ef89509c3fce 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -290,8 +290,11 @@ bind_gilstate_tstate(PyThreadState *tstate) assert(tstate != tcur); if (tcur != NULL) { + // The original gilstate implementation only respects the + // first thread state set. // XXX Skipping like this does not play nice with multiple interpreters. return; + tcur->_status.bound_gilstate = 0; } gilstate_tss_set(runtime, tstate); tstate->_status.bound_gilstate = 1; From 6869bfe09fd2941d87a3ab2a81786da0c59e0da6 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 25 Jan 2023 15:05:49 -0700 Subject: [PATCH 31/35] bound_gilstate and gilstate_tss_get() must match. --- Python/pystate.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Python/pystate.c b/Python/pystate.c index f7ef89509c3fce..671465dcb2ccf8 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -308,12 +308,9 @@ unbind_gilstate_tstate(PyThreadState *tstate) assert(tstate_is_bound(tstate)); // XXX assert(!tstate->_status.active); assert(tstate->_status.bound_gilstate); - // XXX assert(tstate == gilstate_tss_get(tstate->interp->runtime)); + assert(tstate == gilstate_tss_get(tstate->interp->runtime)); - // XXX This check *should* always succeed. - if (tstate == gilstate_tss_get(tstate->interp->runtime)) { - gilstate_tss_clear(tstate->interp->runtime); - } + gilstate_tss_clear(tstate->interp->runtime); tstate->_status.bound_gilstate = 0; } From f91d4582eabcc0cc34eeaa05439f41e137917643 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 25 Jan 2023 15:16:06 -0700 Subject: [PATCH 32/35] Add a blank line for clarity. --- Python/pystate.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Python/pystate.c b/Python/pystate.c index 671465dcb2ccf8..ac9236d8a6408b 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1730,6 +1730,7 @@ _PyThreadState_Swap(_PyRuntimeState *runtime, PyThreadState *newts) // XXX assert(tstate_is_alive(oldts) && tstate_is_bound(oldts)); tstate_deactivate(oldts); } + if (newts != NULL) { // XXX assert(tstate_is_alive(newts)); assert(tstate_is_bound(newts)); From 9b453988f5624a0bf5da621e8363f547b6c58983 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 25 Jan 2023 15:18:58 -0700 Subject: [PATCH 33/35] Do not call unbind_gilstate_tstate() in unbind_tstate(). --- Python/pystate.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Python/pystate.c b/Python/pystate.c index ac9236d8a6408b..e3963c7a77e960 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -243,10 +243,6 @@ unbind_tstate(PyThreadState *tstate) assert(tstate->native_thread_id > 0); #endif - if (tstate->_status.bound_gilstate) { - unbind_gilstate_tstate(tstate); - } - // We leave thread_id and native_thraed_id alone // since they can be useful for debugging. // Check the `_status` field to know if these values @@ -1440,7 +1436,11 @@ tstate_delete_common(PyThreadState *tstate) } HEAD_UNLOCK(runtime); - // XXX Do this in PyThreadState_Swap() (and assert not-equal here)? + // XXX Unbind in PyThreadState_Clear(), or earlier + // (and assert not-equal here)? + if (tstate->_status.bound_gilstate) { + unbind_gilstate_tstate(tstate); + } unbind_tstate(tstate); // XXX Move to PyThreadState_Clear()? From 98a2daeba3e739b1baea5412f05e4704b93c8495 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 25 Jan 2023 10:44:50 -0700 Subject: [PATCH 34/35] Fix comments. --- Python/pystate.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Python/pystate.c b/Python/pystate.c index e3963c7a77e960..f0a1dd7a63c681 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1304,7 +1304,7 @@ PyThreadState_New(PyInterpreterState *interp) PyThreadState *tstate = new_threadstate(interp); if (tstate) { bind_tstate(tstate); - // This make sure there's a gilstate tstate bound + // This makes sure there's a gilstate tstate bound // as soon as possible. if (gilstate_tss_get(tstate->interp->runtime) == NULL) { bind_gilstate_tstate(tstate); @@ -1768,7 +1768,7 @@ void _PyThreadState_Bind(PyThreadState *tstate) { bind_tstate(tstate); - // This make sure there's a gilstate tstate bound + // This makes sure there's a gilstate tstate bound // as soon as possible. if (gilstate_tss_get(tstate->interp->runtime) == NULL) { bind_gilstate_tstate(tstate); From afde19688c4d236f4b075c23171d3d0ab051f7a4 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 30 Jan 2023 11:28:16 -0700 Subject: [PATCH 35/35] Fix padding. --- Include/cpython/pystate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index 3d4559ce992218..f5db52f76e8f4f 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -141,7 +141,7 @@ struct _ts { unsigned int finalized:1; /* padding to align to 4 bytes */ - unsigned int :25; + unsigned int :24; } _status; int py_recursion_remaining;