From ff4eb3621768e59066c0d25144f9ac07ac63ab24 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 27 Mar 2023 17:14:13 -0600 Subject: [PATCH 01/14] Make _PyRuntime.imports.extensions a struct. --- Include/internal/pycore_import.h | 16 +++++++++------- Python/import.c | 10 +++++----- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/Include/internal/pycore_import.h b/Include/internal/pycore_import.h index 69ed6273b7e609..dfc78d056a505a 100644 --- a/Include/internal/pycore_import.h +++ b/Include/internal/pycore_import.h @@ -14,13 +14,15 @@ struct _import_runtime_state { which is just about every time an extension module is imported. See PyInterpreterState.modules_by_index for more info. */ Py_ssize_t last_module_index; - /* A dict mapping (filename, name) to PyModuleDef for modules. - Only legacy (single-phase init) extension modules are added - and only if they support multiple initialization (m_size >- 0) - or are imported in the main interpreter. - This is initialized lazily in _PyImport_FixupExtensionObject(). - Modules are added there and looked up in _imp.find_extension(). */ - PyObject *extensions; + struct { + /* A dict mapping (filename, name) to PyModuleDef for modules. + Only legacy (single-phase init) extension modules are added + and only if they support multiple initialization (m_size >- 0) + or are imported in the main interpreter. + This is initialized lazily in _PyImport_FixupExtensionObject(). + Modules are added there and looked up in _imp.find_extension(). */ + PyObject *dict; + } extensions; /* Package context -- the full module name for package imports */ const char * pkgcontext; }; diff --git a/Python/import.c b/Python/import.c index c68bd1f7db420d..611951b5d3c04b 100644 --- a/Python/import.c +++ b/Python/import.c @@ -881,7 +881,7 @@ gets even messier. static PyModuleDef * _extensions_cache_get(PyObject *filename, PyObject *name) { - PyObject *extensions = EXTENSIONS; + PyObject *extensions = EXTENSIONS.dict; if (extensions == NULL) { return NULL; } @@ -897,13 +897,13 @@ _extensions_cache_get(PyObject *filename, PyObject *name) static int _extensions_cache_set(PyObject *filename, PyObject *name, PyModuleDef *def) { - PyObject *extensions = EXTENSIONS; + PyObject *extensions = EXTENSIONS.dict; if (extensions == NULL) { extensions = PyDict_New(); if (extensions == NULL) { return -1; } - EXTENSIONS = extensions; + EXTENSIONS.dict = extensions; } PyObject *key = PyTuple_Pack(2, filename, name); if (key == NULL) { @@ -920,7 +920,7 @@ _extensions_cache_set(PyObject *filename, PyObject *name, PyModuleDef *def) static int _extensions_cache_delete(PyObject *filename, PyObject *name) { - PyObject *extensions = EXTENSIONS; + PyObject *extensions = EXTENSIONS.dict; if (extensions == NULL) { return 0; } @@ -942,7 +942,7 @@ _extensions_cache_delete(PyObject *filename, PyObject *name) static void _extensions_cache_clear_all(void) { - Py_CLEAR(EXTENSIONS); + Py_CLEAR(EXTENSIONS.dict); } From e558d765ecb03972d95f076daf3a73e9d5b998b5 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 21 Mar 2023 16:19:33 -0600 Subject: [PATCH 02/14] Factor out add_threadstate(). --- Python/pystate.c | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/Python/pystate.c b/Python/pystate.c index b17efdbefd124c..748f35b8fe4602 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1217,8 +1217,7 @@ free_threadstate(PyThreadState *tstate) static void init_threadstate(PyThreadState *tstate, - PyInterpreterState *interp, uint64_t id, - PyThreadState *next) + PyInterpreterState *interp, uint64_t id) { if (tstate->_status.initialized) { Py_FatalError("thread state already initialized"); @@ -1227,18 +1226,13 @@ init_threadstate(PyThreadState *tstate, assert(interp != NULL); tstate->interp = interp; + // next/prev are set in add_threadstate(). + assert(tstate->next == NULL); + assert(tstate->prev == NULL); + assert(id > 0); tstate->id = id; - assert(interp->threads.head == tstate); - assert((next != NULL && id != 1) || (next == NULL && id == 1)); - if (next != NULL) { - assert(next->prev == NULL || next->prev == tstate); - next->prev = tstate; - } - tstate->next = next; - assert(tstate->prev == NULL); - // thread_id and native_thread_id are set in bind_tstate(). tstate->py_recursion_limit = interp->ceval.recursion_limit, @@ -1259,6 +1253,22 @@ init_threadstate(PyThreadState *tstate, tstate->_status.initialized = 1; } +static void +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; + } + tstate->next = next; + assert(tstate->prev == NULL); + interp->threads.head = tstate; +} + static PyThreadState * new_threadstate(PyInterpreterState *interp) { @@ -1298,9 +1308,9 @@ new_threadstate(PyInterpreterState *interp) &initial._main_interpreter._initial_thread, sizeof(*tstate)); } - interp->threads.head = tstate; - init_threadstate(tstate, interp, id, old_head); + init_threadstate(tstate, interp, id); + add_threadstate(interp, tstate, old_head); HEAD_UNLOCK(runtime); if (!used_newtstate) { From acf5269bdf94009f72ae8650f6253398d9b6fbb7 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 21 Mar 2023 16:30:04 -0600 Subject: [PATCH 03/14] Add _PyThreadState_InitDetached(). --- Include/internal/pycore_pystate.h | 1 + Python/pystate.c | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/Include/internal/pycore_pystate.h b/Include/internal/pycore_pystate.h index 7046ec8d9adaaf..00d7d219bd535e 100644 --- a/Include/internal/pycore_pystate.h +++ b/Include/internal/pycore_pystate.h @@ -126,6 +126,7 @@ PyAPI_FUNC(void) _PyThreadState_Bind(PyThreadState *tstate); PyAPI_FUNC(void) _PyThreadState_Init( PyThreadState *tstate); PyAPI_FUNC(void) _PyThreadState_DeleteExcept(PyThreadState *tstate); +extern void _PyThreadState_InitDetached(PyThreadState *, PyInterpreterState *); static inline void diff --git a/Python/pystate.c b/Python/pystate.c index 748f35b8fe4602..82456d255079ad 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1357,6 +1357,20 @@ _PyThreadState_Init(PyThreadState *tstate) Py_FatalError("_PyThreadState_Init() is for internal use only"); } +void +_PyThreadState_InitDetached(PyThreadState *tstate, PyInterpreterState *interp) +{ + _PyRuntimeState *runtime = interp->runtime; + + HEAD_LOCK(runtime); + interp->threads.next_unique_id += 1; + uint64_t id = interp->threads.next_unique_id; + HEAD_UNLOCK(runtime); + + init_threadstate(tstate, interp, id); + // We do not call add_threadstate(). +} + void PyThreadState_Clear(PyThreadState *tstate) { From 181106e3eef47cdfb5b933b8bd3b6f71da48d36c Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 21 Mar 2023 16:46:31 -0600 Subject: [PATCH 04/14] Add _PyThreadState_ClearDetached(). --- Include/internal/pycore_pystate.h | 2 ++ Python/pystate.c | 36 ++++++++++++++++++++++++------- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/Include/internal/pycore_pystate.h b/Include/internal/pycore_pystate.h index 00d7d219bd535e..3316110a72c204 100644 --- a/Include/internal/pycore_pystate.h +++ b/Include/internal/pycore_pystate.h @@ -126,7 +126,9 @@ PyAPI_FUNC(void) _PyThreadState_Bind(PyThreadState *tstate); PyAPI_FUNC(void) _PyThreadState_Init( PyThreadState *tstate); PyAPI_FUNC(void) _PyThreadState_DeleteExcept(PyThreadState *tstate); + extern void _PyThreadState_InitDetached(PyThreadState *, PyInterpreterState *); +extern void _PyThreadState_ClearDetached(PyThreadState *); static inline void diff --git a/Python/pystate.c b/Python/pystate.c index 82456d255079ad..290dd7a870166b 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1371,6 +1371,19 @@ _PyThreadState_InitDetached(PyThreadState *tstate, PyInterpreterState *interp) // We do not call add_threadstate(). } + +static void +clear_datastack(PyThreadState *tstate) +{ + _PyStackChunk *chunk = tstate->datastack_chunk; + tstate->datastack_chunk = NULL; + while (chunk != NULL) { + _PyStackChunk *prev = chunk->previous; + _PyObject_VirtualFree(chunk, chunk->size); + chunk = prev; + } +} + void PyThreadState_Clear(PyThreadState *tstate) { @@ -1445,7 +1458,6 @@ PyThreadState_Clear(PyThreadState *tstate) // XXX Do it as early in the function as possible. } - /* Common code for PyThreadState_Delete() and PyThreadState_DeleteCurrent() */ static void tstate_delete_common(PyThreadState *tstate) @@ -1478,17 +1490,25 @@ tstate_delete_common(PyThreadState *tstate) unbind_tstate(tstate); // XXX Move to PyThreadState_Clear()? - _PyStackChunk *chunk = tstate->datastack_chunk; - tstate->datastack_chunk = NULL; - while (chunk != NULL) { - _PyStackChunk *prev = chunk->previous; - _PyObject_VirtualFree(chunk, chunk->size); - chunk = prev; - } + clear_datastack(tstate); tstate->_status.finalized = 1; } +void +_PyThreadState_ClearDetached(PyThreadState *tstate) +{ + assert(!tstate->_status.bound); + assert(!tstate->_status.bound_gilstate); + assert(tstate->datastack_chunk == NULL); + assert(tstate->thread_id == 0); + assert(tstate->native_thread_id == 0); + assert(tstate->next == NULL); + assert(tstate->prev == NULL); + + PyThreadState_Clear(tstate); + clear_datastack(tstate); +} static void zapthreads(PyInterpreterState *interp) From b8ffbba3b81b9b9241c23541057a1abba10e047a Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 27 Mar 2023 17:33:51 -0600 Subject: [PATCH 05/14] Add extensions_lock_acquire() and extensions_lock_release(). --- Python/import.c | 62 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 50 insertions(+), 12 deletions(-) diff --git a/Python/import.c b/Python/import.c index 611951b5d3c04b..b88c30dc2c1d1a 100644 --- a/Python/import.c +++ b/Python/import.c @@ -862,6 +862,18 @@ Generally, when multiple interpreters are involved, some of the above gets even messier. */ +static inline void +extensions_lock_acquire(void) +{ + // XXX For now the GIL is sufficient. +} + +static inline void +extensions_lock_release(void) +{ + // XXX For now the GIL is sufficient. +} + /* Magic for extension modules (built-in as well as dynamically loaded). To prevent initializing an extension module more than once, we keep a static dictionary 'extensions' keyed by the tuple @@ -881,67 +893,93 @@ gets even messier. static PyModuleDef * _extensions_cache_get(PyObject *filename, PyObject *name) { + PyModuleDef *def = NULL; + extensions_lock_acquire(); + PyObject *extensions = EXTENSIONS.dict; if (extensions == NULL) { - return NULL; + goto finally; } PyObject *key = PyTuple_Pack(2, filename, name); if (key == NULL) { - return NULL; + goto finally; } - PyModuleDef *def = (PyModuleDef *)PyDict_GetItemWithError(extensions, key); + def = (PyModuleDef *)PyDict_GetItemWithError(extensions, key); Py_DECREF(key); + +finally: + extensions_lock_release(); return def; } static int _extensions_cache_set(PyObject *filename, PyObject *name, PyModuleDef *def) { + int res = -1; + extensions_lock_acquire(); + PyObject *extensions = EXTENSIONS.dict; if (extensions == NULL) { extensions = PyDict_New(); if (extensions == NULL) { - return -1; + goto finally; } + extensions_lock_acquire(); EXTENSIONS.dict = extensions; } PyObject *key = PyTuple_Pack(2, filename, name); if (key == NULL) { - return -1; + goto finally; } - int res = PyDict_SetItem(extensions, key, (PyObject *)def); + res = PyDict_SetItem(extensions, key, (PyObject *)def); Py_DECREF(key); if (res < 0) { - return -1; + res = -1; + goto finally; } - return 0; + res = 0; + +finally: + extensions_lock_release(); + return res; } static int _extensions_cache_delete(PyObject *filename, PyObject *name) { + int res = -1; + extensions_lock_acquire(); + PyObject *extensions = EXTENSIONS.dict; if (extensions == NULL) { - return 0; + res = 0; + goto finally; } PyObject *key = PyTuple_Pack(2, filename, name); if (key == NULL) { - return -1; + goto finally; } if (PyDict_DelItem(extensions, key) < 0) { if (!PyErr_ExceptionMatches(PyExc_KeyError)) { Py_DECREF(key); - return -1; + goto finally; } PyErr_Clear(); } Py_DECREF(key); - return 0; + res = 0; + +finally: + extensions_lock_release(); + return res; } static void _extensions_cache_clear_all(void) { + /* The runtime (i.e. main interpreter) must be finalizing, + so we don't need to worry about the lock. */ + assert(_Py_IsMainInterpreter(_PyInterpreterState_GET())); Py_CLEAR(EXTENSIONS.dict); } From a86aea5b46d956efe68215ca91fb0320d116431c Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 27 Mar 2023 17:47:29 -0600 Subject: [PATCH 06/14] Add _PyRuntime.imports.extensions.main_tstate. --- Include/internal/pycore_import.h | 4 ++++ Include/internal/pycore_runtime_init.h | 5 +++++ Python/import.c | 14 ++++++++++++++ 3 files changed, 23 insertions(+) diff --git a/Include/internal/pycore_import.h b/Include/internal/pycore_import.h index dfc78d056a505a..7a78a91aa617e6 100644 --- a/Include/internal/pycore_import.h +++ b/Include/internal/pycore_import.h @@ -15,6 +15,10 @@ struct _import_runtime_state { See PyInterpreterState.modules_by_index for more info. */ Py_ssize_t last_module_index; struct { + /* A thread state tied to the main interpreter, + used exclusively for when the extensions dict is access/modified + from an arbitrary thread. */ + PyThreadState main_tstate; /* A dict mapping (filename, name) to PyModuleDef for modules. Only legacy (single-phase init) extension modules are added and only if they support multiple initialization (m_size >- 0) diff --git a/Include/internal/pycore_runtime_init.h b/Include/internal/pycore_runtime_init.h index 7cfa7c0c02494a..5b09a45e41cd84 100644 --- a/Include/internal/pycore_runtime_init.h +++ b/Include/internal/pycore_runtime_init.h @@ -41,6 +41,11 @@ extern PyTypeObject _PyExc_MemoryError; in accordance with the specification. */ \ .autoTSSkey = Py_tss_NEEDS_INIT, \ .parser = _parser_runtime_state_INIT, \ + .imports = { \ + .extensions = { \ + .main_tstate = _PyThreadState_INIT, \ + }, \ + }, \ .ceval = { \ .perf = _PyEval_RUNTIME_PERF_INIT, \ }, \ diff --git a/Python/import.c b/Python/import.c index b88c30dc2c1d1a..d8e325121c22db 100644 --- a/Python/import.c +++ b/Python/import.c @@ -890,6 +890,15 @@ extensions_lock_release(void) dictionary, to avoid loading shared libraries twice. */ +static void +_extensions_cache_init(void) +{ + /* The runtime (i.e. main interpreter) must be initializing, + so we don't need to worry about the lock. */ + _PyThreadState_InitDetached(&EXTENSIONS.main_tstate, + _PyInterpreterState_Main()); +} + static PyModuleDef * _extensions_cache_get(PyObject *filename, PyObject *name) { @@ -981,6 +990,7 @@ _extensions_cache_clear_all(void) so we don't need to worry about the lock. */ assert(_Py_IsMainInterpreter(_PyInterpreterState_GET())); Py_CLEAR(EXTENSIONS.dict); + _PyThreadState_ClearDetached(&EXTENSIONS.main_tstate); } @@ -2979,6 +2989,10 @@ _PyImport_Fini2(void) PyStatus _PyImport_InitCore(PyThreadState *tstate, PyObject *sysmod, int importlib) { + if (_Py_IsMainInterpreter(tstate->interp)) { + _extensions_cache_init(); + } + // XXX Initialize here: interp->modules and interp->import_func. // XXX Initialize here: sys.modules and sys.meta_path. From d79afe586c31bdb342f5aa7289f9320ed7d91c9d Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 27 Mar 2023 18:33:32 -0600 Subject: [PATCH 07/14] Add _PyThreadState_BindDetached() and _PyThreadState_UnbindDetached(). --- Include/internal/pycore_pystate.h | 2 + Python/pystate.c | 96 +++++++++++++++++++++---------- 2 files changed, 69 insertions(+), 29 deletions(-) diff --git a/Include/internal/pycore_pystate.h b/Include/internal/pycore_pystate.h index 3316110a72c204..b5408622d9d4b2 100644 --- a/Include/internal/pycore_pystate.h +++ b/Include/internal/pycore_pystate.h @@ -129,6 +129,8 @@ PyAPI_FUNC(void) _PyThreadState_DeleteExcept(PyThreadState *tstate); extern void _PyThreadState_InitDetached(PyThreadState *, PyInterpreterState *); extern void _PyThreadState_ClearDetached(PyThreadState *); +extern void _PyThreadState_BindDetached(PyThreadState *); +extern void _PyThreadState_UnbindDetached(PyThreadState *); static inline void diff --git a/Python/pystate.c b/Python/pystate.c index 290dd7a870166b..1883ee18b0ba49 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1357,20 +1357,6 @@ _PyThreadState_Init(PyThreadState *tstate) Py_FatalError("_PyThreadState_Init() is for internal use only"); } -void -_PyThreadState_InitDetached(PyThreadState *tstate, PyInterpreterState *interp) -{ - _PyRuntimeState *runtime = interp->runtime; - - HEAD_LOCK(runtime); - interp->threads.next_unique_id += 1; - uint64_t id = interp->threads.next_unique_id; - HEAD_UNLOCK(runtime); - - init_threadstate(tstate, interp, id); - // We do not call add_threadstate(). -} - static void clear_datastack(PyThreadState *tstate) @@ -1495,21 +1481,6 @@ tstate_delete_common(PyThreadState *tstate) tstate->_status.finalized = 1; } -void -_PyThreadState_ClearDetached(PyThreadState *tstate) -{ - assert(!tstate->_status.bound); - assert(!tstate->_status.bound_gilstate); - assert(tstate->datastack_chunk == NULL); - assert(tstate->thread_id == 0); - assert(tstate->native_thread_id == 0); - assert(tstate->next == NULL); - assert(tstate->prev == NULL); - - PyThreadState_Clear(tstate); - clear_datastack(tstate); -} - static void zapthreads(PyInterpreterState *interp) { @@ -1596,6 +1567,73 @@ _PyThreadState_DeleteExcept(PyThreadState *tstate) } +//------------------------- +// "detached" thread states +//------------------------- + +void +_PyThreadState_InitDetached(PyThreadState *tstate, PyInterpreterState *interp) +{ + _PyRuntimeState *runtime = interp->runtime; + + HEAD_LOCK(runtime); + interp->threads.next_unique_id += 1; + uint64_t id = interp->threads.next_unique_id; + HEAD_UNLOCK(runtime); + + init_threadstate(tstate, interp, id); + // We do not call add_threadstate(). +} + +void +_PyThreadState_ClearDetached(PyThreadState *tstate) +{ + assert(!tstate->_status.bound); + assert(!tstate->_status.bound_gilstate); + assert(tstate->datastack_chunk == NULL); + assert(tstate->thread_id == 0); + assert(tstate->native_thread_id == 0); + assert(tstate->next == NULL); + assert(tstate->prev == NULL); + + PyThreadState_Clear(tstate); + clear_datastack(tstate); +} + +void +_PyThreadState_BindDetached(PyThreadState *tstate) +{ + assert(_Py_IsMainInterpreter(tstate->interp)); + assert(_Py_IsMainInterpreter(_PyInterpreterState_GET())); + bind_tstate(tstate); + /* Unlike _PyThreadState_Bind(), we do not modify gilstate TSS. */ +} + +void +_PyThreadState_UnbindDetached(PyThreadState *tstate) +{ + assert(_Py_IsMainInterpreter(tstate->interp)); + assert(_Py_IsMainInterpreter(_PyInterpreterState_GET())); + assert(tstate_is_alive(tstate)); + assert(!tstate->_status.active); + assert(gilstate_tss_get(tstate->interp->runtime) != tstate); + + unbind_tstate(tstate); + + /* This thread state may be bound/unbound repeatedly, + so we must erase evidence that it was ever bound (or unbound). */ + tstate->_status.bound = 0; + tstate->_status.unbound = 0; + + /* We must fully unlink the thread state from any OS thread, + to allow it to be bound more than once. */ + tstate->thread_id = 0; +#ifdef PY_HAVE_THREAD_NATIVE_ID + tstate->native_thread_id = 0; +#endif +} + + //---------- // accessors //---------- From 51821860affc43f26478f6faddd9007424200132 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 27 Mar 2023 18:34:25 -0600 Subject: [PATCH 08/14] Share the extensions dict safely. --- Python/import.c | 88 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 71 insertions(+), 17 deletions(-) diff --git a/Python/import.c b/Python/import.c index d8e325121c22db..08b9bf830a420d 100644 --- a/Python/import.c +++ b/Python/import.c @@ -905,18 +905,19 @@ _extensions_cache_get(PyObject *filename, PyObject *name) PyModuleDef *def = NULL; extensions_lock_acquire(); - PyObject *extensions = EXTENSIONS.dict; - if (extensions == NULL) { - goto finally; - } PyObject *key = PyTuple_Pack(2, filename, name); if (key == NULL) { goto finally; } + + PyObject *extensions = EXTENSIONS.dict; + if (extensions == NULL) { + goto finally; + } def = (PyModuleDef *)PyDict_GetItemWithError(extensions, key); - Py_DECREF(key); finally: + Py_XDECREF(key); extensions_lock_release(); return def; } @@ -925,23 +926,49 @@ static int _extensions_cache_set(PyObject *filename, PyObject *name, PyModuleDef *def) { int res = -1; + PyThreadState *oldts = NULL; extensions_lock_acquire(); + /* Swap to the main interpreter, if necessary. This matters if + the dict hasn't been created yet or if the item isn't in the + dict yet. In both cases we must ensure the relevant objects + are created using the main interpreter. */ + PyThreadState *main_tstate = &EXTENSIONS.main_tstate; + PyInterpreterState *interp = _PyInterpreterState_GET(); + if (!_Py_IsMainInterpreter(interp)) { + _PyThreadState_BindDetached(main_tstate); + oldts = _PyThreadState_Swap(interp->runtime, main_tstate); + assert(!_Py_IsMainInterpreter(oldts->interp)); + } + + PyObject *key = PyTuple_Pack(2, filename, name); + if (key == NULL) { + goto finally; + } + PyObject *extensions = EXTENSIONS.dict; if (extensions == NULL) { extensions = PyDict_New(); if (extensions == NULL) { goto finally; } - extensions_lock_acquire(); EXTENSIONS.dict = extensions; } - PyObject *key = PyTuple_Pack(2, filename, name); - if (key == NULL) { + + PyModuleDef *actual = (PyModuleDef *)PyDict_GetItemWithError(extensions, key); + if (PyErr_Occurred()) { + goto finally; + } + else if (actual != NULL) { + /* We expect it to be static, so it must be the same pointer. */ + assert(def == actual); + res = 0; goto finally; } + + /* This might trigger a resize, which is why we must switch + to the main interpreter. */ res = PyDict_SetItem(extensions, key, (PyObject *)def); - Py_DECREF(key); if (res < 0) { res = -1; goto finally; @@ -949,6 +976,10 @@ _extensions_cache_set(PyObject *filename, PyObject *name, PyModuleDef *def) res = 0; finally: + if (oldts != NULL) { + _PyThreadState_UnbindDetached(main_tstate); + } + Py_XDECREF(key); extensions_lock_release(); return res; } @@ -957,28 +988,51 @@ static int _extensions_cache_delete(PyObject *filename, PyObject *name) { int res = -1; + PyThreadState *oldts = NULL; extensions_lock_acquire(); + PyObject *key = PyTuple_Pack(2, filename, name); + if (key == NULL) { + goto finally; + } + + _PyThreadState_BindDetached(&EXTENSIONS.main_tstate); + PyObject *extensions = EXTENSIONS.dict; if (extensions == NULL) { res = 0; goto finally; } - PyObject *key = PyTuple_Pack(2, filename, name); - if (key == NULL) { + + PyModuleDef *actual = (PyModuleDef *)PyDict_GetItemWithError(extensions, key); + if (PyErr_Occurred()) { goto finally; } + else if (actual == NULL) { + /* It was already removed or never added. */ + res = 0; + goto finally; + } + + /* Swap to the main interpreter, if necessary. */ + PyThreadState *main_tstate = &EXTENSIONS.main_tstate; + PyInterpreterState *interp = _PyInterpreterState_GET(); + if (!_Py_IsMainInterpreter(interp)) { + _PyThreadState_BindDetached(main_tstate); + oldts = _PyThreadState_Swap(interp->runtime, main_tstate); + assert(!_Py_IsMainInterpreter(oldts->interp)); + } + if (PyDict_DelItem(extensions, key) < 0) { - if (!PyErr_ExceptionMatches(PyExc_KeyError)) { - Py_DECREF(key); - goto finally; - } - PyErr_Clear(); + goto finally; } - Py_DECREF(key); res = 0; finally: + if (oldts != NULL) { + _PyThreadState_UnbindDetached(main_tstate); + } + Py_XDECREF(key); extensions_lock_release(); return res; } From f6db0a270b40c1ce230bdd1442b67a0a505e9269 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 27 Mar 2023 18:56:19 -0600 Subject: [PATCH 09/14] Intern the name and filename in the main interpreter. --- Python/import.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Python/import.c b/Python/import.c index 08b9bf830a420d..d2dd58eee86a2f 100644 --- a/Python/import.c +++ b/Python/import.c @@ -939,6 +939,13 @@ _extensions_cache_set(PyObject *filename, PyObject *name, PyModuleDef *def) _PyThreadState_BindDetached(main_tstate); oldts = _PyThreadState_Swap(interp->runtime, main_tstate); assert(!_Py_IsMainInterpreter(oldts->interp)); + + /* Make sure the name and filename objects are owned + by the main interpreter. */ + name = PyUnicode_InternFromString(PyUnicode_AsUTF8(name)); + assert(name != NULL); + filename = PyUnicode_InternFromString(PyUnicode_AsUTF8(filename)); + assert(filename != NULL); } PyObject *key = PyTuple_Pack(2, filename, name); From dac9c23e4194a6232fdd97cb6bd47c2ad32fda6e Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 28 Mar 2023 16:09:16 -0600 Subject: [PATCH 10/14] Fix the asserts in _PyThreadState_BindDetached() and _PyThreadState_UnbindDetached(). --- Python/pystate.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Python/pystate.c b/Python/pystate.c index 1883ee18b0ba49..1e59a8c5f89717 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1603,8 +1603,9 @@ _PyThreadState_ClearDetached(PyThreadState *tstate) void _PyThreadState_BindDetached(PyThreadState *tstate) { + assert(!_Py_IsMainInterpreter( + current_fast_get(tstate->interp->runtime)->interp)); assert(_Py_IsMainInterpreter(tstate->interp)); - assert(_Py_IsMainInterpreter(_PyInterpreterState_GET())); bind_tstate(tstate); /* Unlike _PyThreadState_Bind(), we do not modify gilstate TSS. */ } @@ -1612,8 +1613,9 @@ _PyThreadState_BindDetached(PyThreadState *tstate) void _PyThreadState_UnbindDetached(PyThreadState *tstate) { + assert(!_Py_IsMainInterpreter( + current_fast_get(tstate->interp->runtime)->interp)); assert(_Py_IsMainInterpreter(tstate->interp)); - assert(_Py_IsMainInterpreter(_PyInterpreterState_GET())); assert(tstate_is_alive(tstate)); assert(!tstate->_status.active); assert(gilstate_tss_get(tstate->interp->runtime) != tstate); From 8d442e2d79d7bc517f9eba4bec5f256be9d09b7f Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 28 Mar 2023 16:09:39 -0600 Subject: [PATCH 11/14] Swap back when done using main_tstate. --- Python/import.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Python/import.c b/Python/import.c index d2dd58eee86a2f..e73a2bda62db1c 100644 --- a/Python/import.c +++ b/Python/import.c @@ -984,6 +984,7 @@ _extensions_cache_set(PyObject *filename, PyObject *name, PyModuleDef *def) finally: if (oldts != NULL) { + _PyThreadState_Swap(interp->runtime, oldts); _PyThreadState_UnbindDetached(main_tstate); } Py_XDECREF(key); @@ -1037,6 +1038,7 @@ _extensions_cache_delete(PyObject *filename, PyObject *name) finally: if (oldts != NULL) { + _PyThreadState_Swap(interp->runtime, oldts); _PyThreadState_UnbindDetached(main_tstate); } Py_XDECREF(key); From ee70a96101040dac9fc07105a8022f40f557d317 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 28 Mar 2023 16:35:26 -0600 Subject: [PATCH 12/14] Disable a prematurely-restrictive assert. --- Python/import.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Python/import.c b/Python/import.c index e73a2bda62db1c..c58a7234ebb156 100644 --- a/Python/import.c +++ b/Python/import.c @@ -1051,7 +1051,7 @@ _extensions_cache_clear_all(void) { /* The runtime (i.e. main interpreter) must be finalizing, so we don't need to worry about the lock. */ - assert(_Py_IsMainInterpreter(_PyInterpreterState_GET())); + // XXX assert(_Py_IsMainInterpreter(_PyInterpreterState_GET())); Py_CLEAR(EXTENSIONS.dict); _PyThreadState_ClearDetached(&EXTENSIONS.main_tstate); } From e9c37fe6e04abefdba37a9472004488236adc99d Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 28 Mar 2023 17:23:54 -0600 Subject: [PATCH 13/14] Fix a copy-and-paste bug. --- Python/import.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/Python/import.c b/Python/import.c index c58a7234ebb156..fcb2b12558d77a 100644 --- a/Python/import.c +++ b/Python/import.c @@ -1004,8 +1004,6 @@ _extensions_cache_delete(PyObject *filename, PyObject *name) goto finally; } - _PyThreadState_BindDetached(&EXTENSIONS.main_tstate); - PyObject *extensions = EXTENSIONS.dict; if (extensions == NULL) { res = 0; From 8e005bf00be6f7f4e1ef2e3db154fd279e053ec3 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 29 Mar 2023 15:58:03 -0600 Subject: [PATCH 14/14] Decref name & filename if copied. --- Python/import.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Python/import.c b/Python/import.c index fcb2b12558d77a..a45b3bfaacb252 100644 --- a/Python/import.c +++ b/Python/import.c @@ -986,6 +986,8 @@ _extensions_cache_set(PyObject *filename, PyObject *name, PyModuleDef *def) if (oldts != NULL) { _PyThreadState_Swap(interp->runtime, oldts); _PyThreadState_UnbindDetached(main_tstate); + Py_DECREF(name); + Py_DECREF(filename); } Py_XDECREF(key); extensions_lock_release();