From 2117772f65a3b749702655ffc3261565091e2102 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 25 Sep 2023 12:49:53 -0600 Subject: [PATCH 01/13] Add PyThreadState._whence. --- Include/cpython/pystate.h | 8 ++++++++ Include/internal/pycore_pystate.h | 4 +++- Include/internal/pycore_runtime_init.h | 1 + Modules/_threadmodule.c | 2 +- Python/pylifecycle.c | 6 ++++-- Python/pystate.c | 23 +++++++++++++++-------- 6 files changed, 32 insertions(+), 12 deletions(-) diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index 7e4c57efc7c00c..4087bb821efe7a 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -92,6 +92,14 @@ struct _ts { /* padding to align to 4 bytes */ unsigned int :24; } _status; +#ifdef Py_BUILD_CORE +# define _PyThreadState_WHENCE_NOTSET -1 +# define _PyThreadState_WHENCE_UNKNOWN 0 +# define _PyThreadState_WHENCE_INTERP 1 +# define _PyThreadState_WHENCE_THREADING 2 +# define _PyThreadState_WHENCE_GILSTATE 3 +#endif + int _whence; int py_recursion_remaining; int py_recursion_limit; diff --git a/Include/internal/pycore_pystate.h b/Include/internal/pycore_pystate.h index 6a36dba3708e38..16f77dc5d2af02 100644 --- a/Include/internal/pycore_pystate.h +++ b/Include/internal/pycore_pystate.h @@ -139,7 +139,9 @@ static inline PyInterpreterState* _PyInterpreterState_GET(void) { // PyThreadState functions -extern PyThreadState * _PyThreadState_New(PyInterpreterState *interp); +extern PyThreadState * _PyThreadState_New( + PyInterpreterState *interp, + int whence); extern void _PyThreadState_Bind(PyThreadState *tstate); extern void _PyThreadState_DeleteExcept(PyThreadState *tstate); diff --git a/Include/internal/pycore_runtime_init.h b/Include/internal/pycore_runtime_init.h index 2deba02a89f33c..574a3c1a9db66c 100644 --- a/Include/internal/pycore_runtime_init.h +++ b/Include/internal/pycore_runtime_init.h @@ -185,6 +185,7 @@ extern PyTypeObject _PyExc_MemoryError; #define _PyThreadState_INIT \ { \ + ._whence = _PyThreadState_WHENCE_NOTSET, \ .py_recursion_limit = Py_DEFAULT_RECURSION_LIMIT, \ .context_ver = 1, \ } diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index ee46b37d92e128..86bd560b92ba6b 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -1205,7 +1205,7 @@ thread_PyThread_start_new_thread(PyObject *self, PyObject *fargs) if (boot == NULL) { return PyErr_NoMemory(); } - boot->tstate = _PyThreadState_New(interp); + boot->tstate = _PyThreadState_New(interp, _PyThreadState_WHENCE_THREADING); if (boot->tstate == NULL) { PyMem_RawFree(boot); if (!PyErr_Occurred()) { diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index c0323763f44890..eb10aa3562dfce 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -655,7 +655,8 @@ pycore_create_interpreter(_PyRuntimeState *runtime, return status; } - PyThreadState *tstate = _PyThreadState_New(interp); + PyThreadState *tstate = _PyThreadState_New(interp, + _PyThreadState_WHENCE_INTERP); if (tstate == NULL) { return _PyStatus_ERR("can't make first thread"); } @@ -2050,7 +2051,8 @@ new_interpreter(PyThreadState **tstate_p, const PyInterpreterConfig *config) return _PyStatus_OK(); } - PyThreadState *tstate = _PyThreadState_New(interp); + PyThreadState *tstate = _PyThreadState_New(interp, + _PyThreadState_WHENCE_INTERP); if (tstate == NULL) { PyInterpreterState_Delete(interp); *tstate_p = NULL; diff --git a/Python/pystate.c b/Python/pystate.c index fe056bf4687026..4d1f10ebbb0b56 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1353,7 +1353,7 @@ free_threadstate(PyThreadState *tstate) static void init_threadstate(PyThreadState *tstate, - PyInterpreterState *interp, uint64_t id) + PyInterpreterState *interp, uint64_t id, int whence) { if (tstate->_status.initialized) { Py_FatalError("thread state already initialized"); @@ -1366,6 +1366,10 @@ init_threadstate(PyThreadState *tstate, assert(tstate->next == NULL); assert(tstate->prev == NULL); + assert(tstate->_whence == _PyThreadState_WHENCE_NOTSET); + assert(whence >= 0 && whence <= _PyThreadState_WHENCE_GILSTATE); + tstate->_whence = whence; + assert(id > 0); tstate->id = id; @@ -1407,7 +1411,7 @@ add_threadstate(PyInterpreterState *interp, PyThreadState *tstate, } static PyThreadState * -new_threadstate(PyInterpreterState *interp) +new_threadstate(PyInterpreterState *interp, int whence) { PyThreadState *tstate; _PyRuntimeState *runtime = interp->runtime; @@ -1446,7 +1450,7 @@ new_threadstate(PyInterpreterState *interp) sizeof(*tstate)); } - init_threadstate(tstate, interp, id); + init_threadstate(tstate, interp, id, whence); add_threadstate(interp, tstate, old_head); HEAD_UNLOCK(runtime); @@ -1460,7 +1464,8 @@ new_threadstate(PyInterpreterState *interp) PyThreadState * PyThreadState_New(PyInterpreterState *interp) { - PyThreadState *tstate = new_threadstate(interp); + PyThreadState *tstate = new_threadstate(interp, + _PyThreadState_WHENCE_UNKNOWN); if (tstate) { bind_tstate(tstate); // This makes sure there's a gilstate tstate bound @@ -1474,16 +1479,16 @@ PyThreadState_New(PyInterpreterState *interp) // This must be followed by a call to _PyThreadState_Bind(); PyThreadState * -_PyThreadState_New(PyInterpreterState *interp) +_PyThreadState_New(PyInterpreterState *interp, int whence) { - return new_threadstate(interp); + return new_threadstate(interp, whence); } // We keep this for stable ABI compabibility. PyAPI_FUNC(PyThreadState*) _PyThreadState_Prealloc(PyInterpreterState *interp) { - return _PyThreadState_New(interp); + return _PyThreadState_New(interp, _PyThreadState_WHENCE_UNKNOWN); } // We keep this around for (accidental) stable ABI compatibility. @@ -2240,7 +2245,9 @@ PyGILState_Ensure(void) int has_gil; if (tcur == NULL) { /* Create a new Python thread state for this thread */ - tcur = new_threadstate(runtime->gilstate.autoInterpreterState); + // XXX Use PyInterpreterState_EnsureThreadState()? + tcur = new_threadstate(runtime->gilstate.autoInterpreterState, + _PyThreadState_WHENCE_GILSTATE); if (tcur == NULL) { Py_FatalError("Couldn't create thread-state for new thread"); } From 23828a342f3a3a3ef02fa79bea077742005a0252 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 25 Sep 2023 15:27:02 -0600 Subject: [PATCH 02/13] Add an assert. --- Python/pystate.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Python/pystate.c b/Python/pystate.c index 4d1f10ebbb0b56..2eaccf952974e2 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1585,6 +1585,9 @@ PyThreadState_Clear(PyThreadState *tstate) Py_CLEAR(tstate->context); if (tstate->on_delete != NULL) { + assert(tstate->_whence == _PyThreadState_WHENCE_THREADING + || (tstate->interp->finalizing + && tstate == _PyInterpreterState_GetFinalizing(tstate->interp))); tstate->on_delete(tstate->on_delete_data); } From b807906be130ae2b9c525815badc604e9be11413 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 25 Sep 2023 16:01:42 -0600 Subject: [PATCH 03/13] Set up interp->_initial_thread for re-use. --- Python/pystate.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Python/pystate.c b/Python/pystate.c index 2eaccf952974e2..27d1724508d507 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1338,7 +1338,14 @@ free_threadstate(PyThreadState *tstate) { // The initial thread state of the interpreter is allocated // as part of the interpreter state so should not be freed. - if (tstate != &tstate->interp->_initial_thread) { + if (tstate == &tstate->interp->_initial_thread) { + // Restore to _PyThreadState_INIT. + tstate = &tstate->interp->_initial_thread; + memcpy(tstate, + &initial._main_interpreter._initial_thread, + sizeof(*tstate)); + } + else { PyMem_RawFree(tstate); } } @@ -1438,6 +1445,7 @@ new_threadstate(PyInterpreterState *interp, int whence) 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); From 3e10a5c08d84277ccbbf9b960bbac7962b8c129e Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 25 Sep 2023 16:30:15 -0600 Subject: [PATCH 04/13] Use a local PyThreadState in _PyInterpreterState_IDDecref(). --- Python/pystate.c | 41 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/Python/pystate.c b/Python/pystate.c index 27d1724508d507..5bceb05ee10166 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1170,6 +1170,10 @@ _PyInterpreterState_IDIncref(PyInterpreterState *interp) } +static void PyThreadState_Init(PyThreadState *tstate, + PyInterpreterState *interp, int whence); +static void PyThreadState_Fini(PyThreadState *tstate); + void _PyInterpreterState_IDDecref(PyInterpreterState *interp) { @@ -1183,11 +1187,13 @@ _PyInterpreterState_IDDecref(PyInterpreterState *interp) PyThread_release_lock(interp->id_mutex); if (refcount == 0 && interp->requires_idref) { - // XXX Using the "head" thread isn't strictly correct. - PyThreadState *tstate = PyInterpreterState_ThreadHead(interp); + PyThreadState tstate; + PyThreadState_Init(&tstate, interp, _PyThreadState_WHENCE_INTERP); + _PyThreadState_Bind(&tstate); + // XXX Possible GILState issues? - PyThreadState *save_tstate = _PyThreadState_Swap(runtime, tstate); - Py_EndInterpreter(tstate); + PyThreadState *save_tstate = _PyThreadState_Swap(runtime, &tstate); + Py_EndInterpreter(&tstate); _PyThreadState_Swap(runtime, save_tstate); } } @@ -1727,6 +1733,33 @@ _PyThreadState_DeleteExcept(PyThreadState *tstate) } } +static void +PyThreadState_Init(PyThreadState *tstate, + PyInterpreterState *interp, int whence) +{ + HEAD_LOCK(interp->runtime); + + interp->threads.next_unique_id += 1; + uint64_t id = interp->threads.next_unique_id; + + // Set to _PyThreadState_INIT. + memcpy(tstate, + &initial._main_interpreter._initial_thread, + sizeof(*tstate)); + + init_threadstate(tstate, interp, id, whence); + add_threadstate(interp, tstate, interp->threads.head); + + HEAD_UNLOCK(interp->runtime); +} + +static void +PyThreadState_Fini(PyThreadState *tstate) +{ + PyThreadState_Clear(tstate); + tstate_delete_common(tstate); +} + //---------- // accessors From 15e978a68b545e6e01f88f8b5c3840570b03e76b Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 26 Sep 2023 14:32:39 -0600 Subject: [PATCH 05/13] Use a local PyThreadState in _xxsubinterpreters. --- Include/cpython/pystate.h | 8 ++++++ Include/internal/pycore_pystate.h | 2 +- Modules/_xxsubinterpretersmodule.c | 43 +++++++++--------------------- Python/pystate.c | 20 +++++++------- 4 files changed, 31 insertions(+), 42 deletions(-) diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index 4087bb821efe7a..fabecea1da5c98 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -98,6 +98,7 @@ struct _ts { # define _PyThreadState_WHENCE_INTERP 1 # define _PyThreadState_WHENCE_THREADING 2 # define _PyThreadState_WHENCE_GILSTATE 3 +# define _PyThreadState_WHENCE_EXEC 4 #endif int _whence; @@ -212,6 +213,13 @@ struct _ts { # define Py_C_RECURSION_LIMIT 1500 #endif +PyAPI_FUNC(void) PyThreadState_Init( + PyThreadState *tstate, + PyInterpreterState *interp, + int whence); +PyAPI_FUNC(void) PyThreadState_Fini(PyThreadState *tstate); +PyAPI_FUNC(void) PyThreadState_Bind(PyThreadState *tstate); + /* other API */ diff --git a/Include/internal/pycore_pystate.h b/Include/internal/pycore_pystate.h index 16f77dc5d2af02..45095770d7f4b2 100644 --- a/Include/internal/pycore_pystate.h +++ b/Include/internal/pycore_pystate.h @@ -142,7 +142,7 @@ static inline PyInterpreterState* _PyInterpreterState_GET(void) { extern PyThreadState * _PyThreadState_New( PyInterpreterState *interp, int whence); -extern void _PyThreadState_Bind(PyThreadState *tstate); +#define _PyThreadState_Bind(tstate) PyThreadState_Bind(tstate) extern void _PyThreadState_DeleteExcept(PyThreadState *tstate); // Export for '_testinternalcapi' shared extension diff --git a/Modules/_xxsubinterpretersmodule.c b/Modules/_xxsubinterpretersmodule.c index e1c7d4ab2fd78f..853ae96e8c790f 100644 --- a/Modules/_xxsubinterpretersmodule.c +++ b/Modules/_xxsubinterpretersmodule.c @@ -429,27 +429,12 @@ _run_script_in_interpreter(PyObject *mod, PyInterpreterState *interp, // Switch to interpreter. PyThreadState *save_tstate = NULL; + PyThreadState *tstate = NULL; + PyThreadState _tstate; if (interp != PyInterpreterState_Get()) { - // XXX gh-109860: Using the "head" thread isn't strictly correct. - PyThreadState *tstate = PyInterpreterState_ThreadHead(interp); - assert(tstate != NULL); - // Hack (until gh-109860): The interpreter's initial thread state - // is least likely to break. - while(tstate->next != NULL) { - tstate = tstate->next; - } - // We must do this check before switching interpreters, so any - // exception gets raised in the right one. - // XXX gh-109860: Drop this redundant check once we stop - // re-using tstates that might already be in use. - if (_PyInterpreterState_IsRunningMain(interp)) { - PyErr_SetString(PyExc_RuntimeError, - "interpreter already running"); - if (shared != NULL) { - _sharedns_free(shared); - } - return -1; - } + tstate = &_tstate; + PyThreadState_Init(tstate, interp, _PyThreadState_WHENCE_EXEC); + PyThreadState_Bind(tstate); // XXX Possible GILState issues? save_tstate = PyThreadState_Swap(tstate); } @@ -461,6 +446,7 @@ _run_script_in_interpreter(PyObject *mod, PyInterpreterState *interp, // Switch back. if (save_tstate != NULL) { PyThreadState_Swap(save_tstate); + PyThreadState_Fini(tstate); } // Propagate any exception out to the caller. @@ -502,6 +488,7 @@ interp_create(PyObject *self, PyObject *args, PyObject *kwds) const PyInterpreterConfig config = isolated ? (PyInterpreterConfig)_PyInterpreterConfig_INIT : (PyInterpreterConfig)_PyInterpreterConfig_LEGACY_INIT; + // XXX Possible GILState issues? PyThreadState *tstate = NULL; PyStatus status = Py_NewInterpreterFromConfig(&tstate, &config); @@ -517,6 +504,7 @@ interp_create(PyObject *self, PyObject *args, PyObject *kwds) return NULL; } assert(tstate != NULL); + PyInterpreterState *interp = PyThreadState_GetInterpreter(tstate); PyObject *idobj = PyInterpreterState_GetIDObject(interp); if (idobj == NULL) { @@ -573,17 +561,12 @@ interp_destroy(PyObject *self, PyObject *args, PyObject *kwds) } // Destroy the interpreter. - // XXX gh-109860: Using the "head" thread isn't strictly correct. - PyThreadState *tstate = PyInterpreterState_ThreadHead(interp); - assert(tstate != NULL); - // Hack (until gh-109860): The interpreter's initial thread state - // is least likely to break. - while(tstate->next != NULL) { - tstate = tstate->next; - } + PyThreadState tstate; + PyThreadState_Init(&tstate, interp, _PyThreadState_WHENCE_INTERP); + PyThreadState_Bind(&tstate); // XXX Possible GILState issues? - PyThreadState *save_tstate = PyThreadState_Swap(tstate); - Py_EndInterpreter(tstate); + PyThreadState *save_tstate = PyThreadState_Swap(&tstate); + Py_EndInterpreter(&tstate); PyThreadState_Swap(save_tstate); Py_RETURN_NONE; diff --git a/Python/pystate.c b/Python/pystate.c index 5bceb05ee10166..3e8e0dd2d590ac 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1170,10 +1170,6 @@ _PyInterpreterState_IDIncref(PyInterpreterState *interp) } -static void PyThreadState_Init(PyThreadState *tstate, - PyInterpreterState *interp, int whence); -static void PyThreadState_Fini(PyThreadState *tstate); - void _PyInterpreterState_IDDecref(PyInterpreterState *interp) { @@ -1380,7 +1376,7 @@ init_threadstate(PyThreadState *tstate, assert(tstate->prev == NULL); assert(tstate->_whence == _PyThreadState_WHENCE_NOTSET); - assert(whence >= 0 && whence <= _PyThreadState_WHENCE_GILSTATE); + assert(whence >= 0 && whence <= _PyThreadState_WHENCE_EXEC); tstate->_whence = whence; assert(id > 0); @@ -1599,9 +1595,10 @@ PyThreadState_Clear(PyThreadState *tstate) Py_CLEAR(tstate->context); if (tstate->on_delete != NULL) { - assert(tstate->_whence == _PyThreadState_WHENCE_THREADING - || (tstate->interp->finalizing - && tstate == _PyInterpreterState_GetFinalizing(tstate->interp))); + // XXX Once we fix gh-75698: + //assert(tstate->_whence == _PyThreadState_WHENCE_THREADING + // || (tstate->interp->finalizing + // && tstate == _PyInterpreterState_GetFinalizing(tstate->interp))); tstate->on_delete(tstate->on_delete_data); } @@ -1733,7 +1730,8 @@ _PyThreadState_DeleteExcept(PyThreadState *tstate) } } -static void + +void PyThreadState_Init(PyThreadState *tstate, PyInterpreterState *interp, int whence) { @@ -1753,7 +1751,7 @@ PyThreadState_Init(PyThreadState *tstate, HEAD_UNLOCK(interp->runtime); } -static void +void PyThreadState_Fini(PyThreadState *tstate) { PyThreadState_Clear(tstate); @@ -1993,7 +1991,7 @@ PyThreadState_Swap(PyThreadState *newts) void -_PyThreadState_Bind(PyThreadState *tstate) +PyThreadState_Bind(PyThreadState *tstate) { // gh-104690: If Python is being finalized and PyInterpreterState_Delete() // was called, tstate becomes a dangling pointer. From 0b60d6dd568b89b18dcbd73c5b3dd8affe6b929d Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 26 Sep 2023 17:01:53 -0600 Subject: [PATCH 06/13] Throw away the initial thread state. --- Modules/_xxsubinterpretersmodule.c | 4 ++++ Python/pystate.c | 2 -- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Modules/_xxsubinterpretersmodule.c b/Modules/_xxsubinterpretersmodule.c index 853ae96e8c790f..1674dbf52a7e93 100644 --- a/Modules/_xxsubinterpretersmodule.c +++ b/Modules/_xxsubinterpretersmodule.c @@ -514,6 +514,10 @@ interp_create(PyObject *self, PyObject *args, PyObject *kwds) PyThreadState_Swap(save_tstate); return NULL; } + + PyThreadState_Clear(tstate); + PyThreadState_Delete(tstate); + _PyInterpreterState_RequireIDRef(interp, 1); return idobj; } diff --git a/Python/pystate.c b/Python/pystate.c index 3e8e0dd2d590ac..7474a021bf79ba 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1408,8 +1408,6 @@ add_threadstate(PyInterpreterState *interp, PyThreadState *tstate, PyThreadState *next) { assert(interp->threads.head != tstate); - assert((next != NULL && tstate->id != 1) || - (next == NULL && tstate->id == 1)); if (next != NULL) { assert(next->prev == NULL || next->prev == tstate); next->prev = tstate; From 781cb53c186965b6e3af868bf2803db8149d490d Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 27 Sep 2023 09:20:06 -0600 Subject: [PATCH 07/13] Use existing API. --- Include/cpython/pystate.h | 7 ----- Include/internal/pycore_pystate.h | 2 +- Modules/_xxsubinterpretersmodule.c | 18 ++++++------- Python/pystate.c | 41 +++++------------------------- 4 files changed, 15 insertions(+), 53 deletions(-) diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index fabecea1da5c98..af5cc4a2f3bf63 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -213,13 +213,6 @@ struct _ts { # define Py_C_RECURSION_LIMIT 1500 #endif -PyAPI_FUNC(void) PyThreadState_Init( - PyThreadState *tstate, - PyInterpreterState *interp, - int whence); -PyAPI_FUNC(void) PyThreadState_Fini(PyThreadState *tstate); -PyAPI_FUNC(void) PyThreadState_Bind(PyThreadState *tstate); - /* other API */ diff --git a/Include/internal/pycore_pystate.h b/Include/internal/pycore_pystate.h index 45095770d7f4b2..16f77dc5d2af02 100644 --- a/Include/internal/pycore_pystate.h +++ b/Include/internal/pycore_pystate.h @@ -142,7 +142,7 @@ static inline PyInterpreterState* _PyInterpreterState_GET(void) { extern PyThreadState * _PyThreadState_New( PyInterpreterState *interp, int whence); -#define _PyThreadState_Bind(tstate) PyThreadState_Bind(tstate) +extern void _PyThreadState_Bind(PyThreadState *tstate); extern void _PyThreadState_DeleteExcept(PyThreadState *tstate); // Export for '_testinternalcapi' shared extension diff --git a/Modules/_xxsubinterpretersmodule.c b/Modules/_xxsubinterpretersmodule.c index 1674dbf52a7e93..784f90c8e9c192 100644 --- a/Modules/_xxsubinterpretersmodule.c +++ b/Modules/_xxsubinterpretersmodule.c @@ -430,11 +430,9 @@ _run_script_in_interpreter(PyObject *mod, PyInterpreterState *interp, // Switch to interpreter. PyThreadState *save_tstate = NULL; PyThreadState *tstate = NULL; - PyThreadState _tstate; if (interp != PyInterpreterState_Get()) { - tstate = &_tstate; - PyThreadState_Init(tstate, interp, _PyThreadState_WHENCE_EXEC); - PyThreadState_Bind(tstate); + tstate = PyThreadState_New(interp); + tstate->_whence = _PyThreadState_WHENCE_EXEC; // XXX Possible GILState issues? save_tstate = PyThreadState_Swap(tstate); } @@ -445,8 +443,9 @@ _run_script_in_interpreter(PyObject *mod, PyInterpreterState *interp, // Switch back. if (save_tstate != NULL) { + PyThreadState_Clear(tstate); PyThreadState_Swap(save_tstate); - PyThreadState_Fini(tstate); + PyThreadState_Delete(tstate); } // Propagate any exception out to the caller. @@ -565,12 +564,11 @@ interp_destroy(PyObject *self, PyObject *args, PyObject *kwds) } // Destroy the interpreter. - PyThreadState tstate; - PyThreadState_Init(&tstate, interp, _PyThreadState_WHENCE_INTERP); - PyThreadState_Bind(&tstate); + PyThreadState *tstate = PyThreadState_New(interp); + tstate->_whence = _PyThreadState_WHENCE_INTERP; // XXX Possible GILState issues? - PyThreadState *save_tstate = PyThreadState_Swap(&tstate); - Py_EndInterpreter(&tstate); + PyThreadState *save_tstate = PyThreadState_Swap(tstate); + Py_EndInterpreter(tstate); PyThreadState_Swap(save_tstate); Py_RETURN_NONE; diff --git a/Python/pystate.c b/Python/pystate.c index 7474a021bf79ba..639933e69f1588 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1183,13 +1183,13 @@ _PyInterpreterState_IDDecref(PyInterpreterState *interp) PyThread_release_lock(interp->id_mutex); if (refcount == 0 && interp->requires_idref) { - PyThreadState tstate; - PyThreadState_Init(&tstate, interp, _PyThreadState_WHENCE_INTERP); - _PyThreadState_Bind(&tstate); + PyThreadState *tstate = _PyThreadState_New(interp, + _PyThreadState_WHENCE_INTERP); + _PyThreadState_Bind(tstate); // XXX Possible GILState issues? - PyThreadState *save_tstate = _PyThreadState_Swap(runtime, &tstate); - Py_EndInterpreter(&tstate); + PyThreadState *save_tstate = _PyThreadState_Swap(runtime, tstate); + Py_EndInterpreter(tstate); _PyThreadState_Swap(runtime, save_tstate); } } @@ -1441,7 +1441,6 @@ new_threadstate(PyInterpreterState *interp, int whence) PyThreadState *old_head = interp->threads.head; if (old_head == NULL) { // It's the interpreter's initial thread state. - assert(id == 1); used_newtstate = 0; tstate = &interp->_initial_thread; } @@ -1729,34 +1728,6 @@ _PyThreadState_DeleteExcept(PyThreadState *tstate) } -void -PyThreadState_Init(PyThreadState *tstate, - PyInterpreterState *interp, int whence) -{ - HEAD_LOCK(interp->runtime); - - interp->threads.next_unique_id += 1; - uint64_t id = interp->threads.next_unique_id; - - // Set to _PyThreadState_INIT. - memcpy(tstate, - &initial._main_interpreter._initial_thread, - sizeof(*tstate)); - - init_threadstate(tstate, interp, id, whence); - add_threadstate(interp, tstate, interp->threads.head); - - HEAD_UNLOCK(interp->runtime); -} - -void -PyThreadState_Fini(PyThreadState *tstate) -{ - PyThreadState_Clear(tstate); - tstate_delete_common(tstate); -} - - //---------- // accessors //---------- @@ -1989,7 +1960,7 @@ PyThreadState_Swap(PyThreadState *newts) void -PyThreadState_Bind(PyThreadState *tstate) +_PyThreadState_Bind(PyThreadState *tstate) { // gh-104690: If Python is being finalized and PyInterpreterState_Delete() // was called, tstate becomes a dangling pointer. From cd288b5ff3e4c68f3fc8970c22d5726de88d55aa Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 27 Sep 2023 15:19:55 -0600 Subject: [PATCH 08/13] Release threading._main_thread._tstate_lock in _PyInterpreterState_SetNotRunningMain(). --- Lib/threading.py | 7 +++++-- Python/pystate.c | 27 ++++++++++++++++++++++----- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/Lib/threading.py b/Lib/threading.py index 0edfaf880f711a..417c7a69e551c7 100644 --- a/Lib/threading.py +++ b/Lib/threading.py @@ -1593,8 +1593,11 @@ def _shutdown(): # The main thread isn't finished yet, so its thread state lock can't # have been released. assert tlock is not None - assert tlock.locked() - tlock.release() + if tlock.locked(): + # It should have been released already by + # PyInterpreterState_SetNotRunningMain(), but there may be + # embedders that aren't calling that yet. + tlock.release() _main_thread._stop() else: # bpo-1596321: _shutdown() must be called in the main thread. diff --git a/Python/pystate.c b/Python/pystate.c index 639933e69f1588..3f95d78bf794af 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1113,7 +1113,20 @@ _PyInterpreterState_SetRunningMain(PyInterpreterState *interp) void _PyInterpreterState_SetNotRunningMain(PyInterpreterState *interp) { - assert(interp->threads.main == current_fast_get(&_PyRuntime)); + PyThreadState *tstate = interp->threads.main; + assert(tstate == current_fast_get(&_PyRuntime)); + + if (tstate->on_delete != NULL) { + // The threading module was imported for the first time in this + // thread, so it was set as threading._main_thread. (See gh-75698.) + // The thread has finished running the Python program so we mark + // the thread object as finished. + assert(tstate->_whence != _PyThreadState_WHENCE_THREADING); + tstate->on_delete(tstate->on_delete_data); + tstate->on_delete = NULL; + tstate->on_delete_data = NULL; + } + interp->threads.main = NULL; } @@ -1592,10 +1605,14 @@ PyThreadState_Clear(PyThreadState *tstate) Py_CLEAR(tstate->context); if (tstate->on_delete != NULL) { - // XXX Once we fix gh-75698: - //assert(tstate->_whence == _PyThreadState_WHENCE_THREADING - // || (tstate->interp->finalizing - // && tstate == _PyInterpreterState_GetFinalizing(tstate->interp))); + // For the "main" thread of each interpreter, this is meant + // to be done in PyInterpreterState_SetNotRunningMain(). + // Tha leaves threads created by the threading module. + // However, we also accommodate "main" threads that still + // don't call PyInterpreterState_SetNotRunningMain() yet. + assert(tstate->_whence == _PyThreadState_WHENCE_THREADING + || (tstate->interp->finalizing + && tstate == _PyInterpreterState_GetFinalizing(tstate->interp))); tstate->on_delete(tstate->on_delete_data); } From 4afdf7b57f090e8e00150723928aeb642370200e Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 27 Sep 2023 17:12:01 -0600 Subject: [PATCH 09/13] Drop an assert (fork is messy). --- Python/pystate.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Python/pystate.c b/Python/pystate.c index 3f95d78bf794af..40f2c0d6cf61e8 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1607,12 +1607,10 @@ PyThreadState_Clear(PyThreadState *tstate) if (tstate->on_delete != NULL) { // For the "main" thread of each interpreter, this is meant // to be done in PyInterpreterState_SetNotRunningMain(). - // Tha leaves threads created by the threading module. + // Tha leaves threads created by the threading module, + // and any threads killed by forking. // However, we also accommodate "main" threads that still // don't call PyInterpreterState_SetNotRunningMain() yet. - assert(tstate->_whence == _PyThreadState_WHENCE_THREADING - || (tstate->interp->finalizing - && tstate == _PyInterpreterState_GetFinalizing(tstate->interp))); tstate->on_delete(tstate->on_delete_data); } From aba79e38e5694010498e875b2c6219b01e51e4f0 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 27 Sep 2023 16:15:57 -0600 Subject: [PATCH 10/13] Add _PyInterpreterState_FailIfRunningMain(). --- Include/internal/pycore_pystate.h | 1 + Python/pystate.c | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/Include/internal/pycore_pystate.h b/Include/internal/pycore_pystate.h index 16f77dc5d2af02..5f8b576e4a69ab 100644 --- a/Include/internal/pycore_pystate.h +++ b/Include/internal/pycore_pystate.h @@ -48,6 +48,7 @@ _Py_IsMainInterpreterFinalizing(PyInterpreterState *interp) PyAPI_FUNC(int) _PyInterpreterState_SetRunningMain(PyInterpreterState *); PyAPI_FUNC(void) _PyInterpreterState_SetNotRunningMain(PyInterpreterState *); PyAPI_FUNC(int) _PyInterpreterState_IsRunningMain(PyInterpreterState *); +PyAPI_FUNC(int) _PyInterpreterState_FailIfRunningMain(PyInterpreterState *); static inline const PyConfig * diff --git a/Python/pystate.c b/Python/pystate.c index 40f2c0d6cf61e8..29f473b32b0b47 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1094,9 +1094,7 @@ _PyInterpreterState_DeleteExceptMain(_PyRuntimeState *runtime) int _PyInterpreterState_SetRunningMain(PyInterpreterState *interp) { - if (interp->threads.main != NULL) { - PyErr_SetString(PyExc_RuntimeError, - "interpreter already running"); + if (_PyInterpreterState_FailIfRunningMain(interp) < 0) { return -1; } PyThreadState *tstate = current_fast_get(&_PyRuntime); @@ -1136,6 +1134,17 @@ _PyInterpreterState_IsRunningMain(PyInterpreterState *interp) return (interp->threads.main != NULL); } +int +_PyInterpreterState_FailIfRunningMain(PyInterpreterState *interp) +{ + if (interp->threads.main != NULL) { + PyErr_SetString(PyExc_RuntimeError, + "interpreter already running"); + return -1; + } + return 0; +} + //---------- // accessors From 2c185543540c3e20dac5fc540d2eaca900bb0554 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 27 Sep 2023 16:18:12 -0600 Subject: [PATCH 11/13] Propagate "alraedy running" properly. --- Modules/_xxsubinterpretersmodule.c | 57 ++++++++++++++++++++++-------- 1 file changed, 43 insertions(+), 14 deletions(-) diff --git a/Modules/_xxsubinterpretersmodule.c b/Modules/_xxsubinterpretersmodule.c index 784f90c8e9c192..bcaf3154b93b48 100644 --- a/Modules/_xxsubinterpretersmodule.c +++ b/Modules/_xxsubinterpretersmodule.c @@ -242,6 +242,10 @@ _sharedns_apply(_sharedns *shared, PyObject *ns) // of the exception in the calling interpreter. typedef struct _sharedexception { + PyInterpreterState *interp; +#define ERR_NOT_SET 0 +#define ERR_ALREADY_RUNNING 1 + int code; const char *name; const char *msg; } _sharedexception; @@ -263,8 +267,19 @@ _sharedexception_clear(_sharedexception *exc) } static const char * -_sharedexception_bind(PyObject *exc, _sharedexception *sharedexc) +_sharedexception_bind(PyObject *exc, int code, _sharedexception *sharedexc) { + if (sharedexc->interp == NULL) { + sharedexc->interp = PyInterpreterState_Get(); + } + + if (code != ERR_NOT_SET) { + assert(exc == NULL); + assert(code > 0); + sharedexc->code = code; + return NULL; + } + assert(exc != NULL); const char *failure = NULL; @@ -301,6 +316,7 @@ _sharedexception_bind(PyObject *exc, _sharedexception *sharedexc) goto error; } } + PyErr_NoMemory(); return NULL; @@ -316,6 +332,7 @@ static void _sharedexception_apply(_sharedexception *exc, PyObject *wrapperclass) { if (exc->name != NULL) { + assert(exc->code == ERR_NOT_SET); if (exc->msg != NULL) { PyErr_Format(wrapperclass, "%s: %s", exc->name, exc->msg); } @@ -324,9 +341,16 @@ _sharedexception_apply(_sharedexception *exc, PyObject *wrapperclass) } } else if (exc->msg != NULL) { + assert(exc->code == ERR_NOT_SET); PyErr_SetString(wrapperclass, exc->msg); } + else if (exc->code == ERR_ALREADY_RUNNING) { + assert(exc->interp != NULL); + assert(_PyInterpreterState_IsRunningMain(exc->interp)); + _PyInterpreterState_FailIfRunningMain(exc->interp); + } else { + assert(exc->code == ERR_NOT_SET); PyErr_SetNone(wrapperclass); } } @@ -362,9 +386,16 @@ static int _run_script(PyInterpreterState *interp, const char *codestr, _sharedns *shared, _sharedexception *sharedexc) { + int errcode = ERR_NOT_SET; + if (_PyInterpreterState_SetRunningMain(interp) < 0) { - // We skip going through the shared exception. - return -1; + assert(PyErr_Occurred()); + // In the case where we didn't switch interpreters, it would + // be more efficient to leave the exception in place and return + // immediately. However, life is simpler if we don't. + PyErr_Clear(); + errcode = ERR_ALREADY_RUNNING; + goto error; } PyObject *excval = NULL; @@ -403,7 +434,7 @@ _run_script(PyInterpreterState *interp, const char *codestr, error: excval = PyErr_GetRaisedException(); - const char *failure = _sharedexception_bind(excval, sharedexc); + const char *failure = _sharedexception_bind(excval, errcode, sharedexc); if (failure != NULL) { fprintf(stderr, "RunFailedError: script raised an uncaught exception (%s)", @@ -412,7 +443,9 @@ _run_script(PyInterpreterState *interp, const char *codestr, } Py_XDECREF(excval); assert(!PyErr_Occurred()); - _PyInterpreterState_SetNotRunningMain(interp); + if (errcode != ERR_ALREADY_RUNNING) { + _PyInterpreterState_SetNotRunningMain(interp); + } return -1; } @@ -421,6 +454,7 @@ _run_script_in_interpreter(PyObject *mod, PyInterpreterState *interp, const char *codestr, PyObject *shareables) { module_state *state = get_module_state(mod); + assert(state != NULL); _sharedns *shared = _get_shared_ns(shareables); if (shared == NULL && PyErr_Occurred()) { @@ -438,7 +472,7 @@ _run_script_in_interpreter(PyObject *mod, PyInterpreterState *interp, } // Run the script. - _sharedexception exc = {NULL, NULL}; + _sharedexception exc = (_sharedexception){ .interp = interp }; int result = _run_script(interp, codestr, shared, &exc); // Switch back. @@ -449,15 +483,10 @@ _run_script_in_interpreter(PyObject *mod, PyInterpreterState *interp, } // Propagate any exception out to the caller. - if (exc.name != NULL) { - assert(state != NULL); + if (result < 0) { + assert(!PyErr_Occurred()); _sharedexception_apply(&exc, state->RunFailedError); - } - else if (result != 0) { - if (!PyErr_Occurred()) { - // We were unable to allocate a shared exception. - PyErr_NoMemory(); - } + assert(PyErr_Occurred()); } if (shared != NULL) { From 845c2bd36c1bdf96dba060329b07a6f05f0225bd Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 27 Sep 2023 16:38:40 -0600 Subject: [PATCH 12/13] Fix handling of no-memory in _sharedexception_bind(). --- Modules/_xxsubinterpretersmodule.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/Modules/_xxsubinterpretersmodule.c b/Modules/_xxsubinterpretersmodule.c index bcaf3154b93b48..700282efb8c619 100644 --- a/Modules/_xxsubinterpretersmodule.c +++ b/Modules/_xxsubinterpretersmodule.c @@ -244,7 +244,8 @@ _sharedns_apply(_sharedns *shared, PyObject *ns) typedef struct _sharedexception { PyInterpreterState *interp; #define ERR_NOT_SET 0 -#define ERR_ALREADY_RUNNING 1 +#define ERR_NO_MEMORY 1 +#define ERR_ALREADY_RUNNING 2 int code; const char *name; const char *msg; @@ -286,6 +287,7 @@ _sharedexception_bind(PyObject *exc, int code, _sharedexception *sharedexc) PyObject *nameobj = PyUnicode_FromString(Py_TYPE(exc)->tp_name); if (nameobj == NULL) { failure = "unable to format exception type name"; + code = ERR_NO_MEMORY; goto error; } sharedexc->name = _copy_raw_string(nameobj); @@ -296,6 +298,7 @@ _sharedexception_bind(PyObject *exc, int code, _sharedexception *sharedexc) } else { failure = "unable to encode and copy exception type name"; } + code = ERR_NO_MEMORY; goto error; } @@ -303,6 +306,7 @@ _sharedexception_bind(PyObject *exc, int code, _sharedexception *sharedexc) PyObject *msgobj = PyUnicode_FromFormat("%S", exc); if (msgobj == NULL) { failure = "unable to format exception message"; + code = ERR_NO_MEMORY; goto error; } sharedexc->msg = _copy_raw_string(msgobj); @@ -313,10 +317,10 @@ _sharedexception_bind(PyObject *exc, int code, _sharedexception *sharedexc) } else { failure = "unable to encode and copy exception message"; } + code = ERR_NO_MEMORY; goto error; } } - PyErr_NoMemory(); return NULL; @@ -324,7 +328,10 @@ _sharedexception_bind(PyObject *exc, int code, _sharedexception *sharedexc) assert(failure != NULL); PyErr_Clear(); _sharedexception_clear(sharedexc); - *sharedexc = no_exception; + *sharedexc = (_sharedexception){ + .interp = sharedexc->interp, + .code = code, + }; return failure; } @@ -344,6 +351,9 @@ _sharedexception_apply(_sharedexception *exc, PyObject *wrapperclass) assert(exc->code == ERR_NOT_SET); PyErr_SetString(wrapperclass, exc->msg); } + else if (exc->code == ERR_NO_MEMORY) { + PyErr_NoMemory(); + } else if (exc->code == ERR_ALREADY_RUNNING) { assert(exc->interp != NULL); assert(_PyInterpreterState_IsRunningMain(exc->interp)); @@ -439,13 +449,12 @@ _run_script(PyInterpreterState *interp, const char *codestr, fprintf(stderr, "RunFailedError: script raised an uncaught exception (%s)", failure); - PyErr_Clear(); } Py_XDECREF(excval); - assert(!PyErr_Occurred()); if (errcode != ERR_ALREADY_RUNNING) { _PyInterpreterState_SetNotRunningMain(interp); } + assert(!PyErr_Occurred()); return -1; } From fec49b2d301d0bb5d2a3166a5b310ca16d4f051d Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 2 Oct 2023 15:20:55 -0600 Subject: [PATCH 13/13] Fix some typos. --- Lib/threading.py | 2 +- Python/pystate.c | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Lib/threading.py b/Lib/threading.py index 417c7a69e551c7..41c3a9ff93856f 100644 --- a/Lib/threading.py +++ b/Lib/threading.py @@ -1595,7 +1595,7 @@ def _shutdown(): assert tlock is not None if tlock.locked(): # It should have been released already by - # PyInterpreterState_SetNotRunningMain(), but there may be + # _PyInterpreterState_SetNotRunningMain(), but there may be # embedders that aren't calling that yet. tlock.release() _main_thread._stop() diff --git a/Python/pystate.c b/Python/pystate.c index 29f473b32b0b47..25a957c31f0134 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1615,11 +1615,11 @@ PyThreadState_Clear(PyThreadState *tstate) if (tstate->on_delete != NULL) { // For the "main" thread of each interpreter, this is meant - // to be done in PyInterpreterState_SetNotRunningMain(). - // Tha leaves threads created by the threading module, + // to be done in _PyInterpreterState_SetNotRunningMain(). + // That leaves threads created by the threading module, // and any threads killed by forking. // However, we also accommodate "main" threads that still - // don't call PyInterpreterState_SetNotRunningMain() yet. + // don't call _PyInterpreterState_SetNotRunningMain() yet. tstate->on_delete(tstate->on_delete_data); }