summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Include/internal/pycore_import.h20
-rw-r--r--Include/internal/pycore_pystate.h5
-rw-r--r--Include/internal/pycore_runtime_init.h5
-rw-r--r--Python/import.c175
-rw-r--r--Python/pystate.c128
5 files changed, 274 insertions, 59 deletions
diff --git a/Include/internal/pycore_import.h b/Include/internal/pycore_import.h
index 69ed627..7a78a91 100644
--- a/Include/internal/pycore_import.h
+++ b/Include/internal/pycore_import.h
@@ -14,13 +14,19 @@ 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 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)
+ 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/Include/internal/pycore_pystate.h b/Include/internal/pycore_pystate.h
index 7046ec8..b540862 100644
--- a/Include/internal/pycore_pystate.h
+++ b/Include/internal/pycore_pystate.h
@@ -127,6 +127,11 @@ 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 *);
+extern void _PyThreadState_BindDetached(PyThreadState *);
+extern void _PyThreadState_UnbindDetached(PyThreadState *);
+
static inline void
_PyThreadState_UpdateTracingState(PyThreadState *tstate)
diff --git a/Include/internal/pycore_runtime_init.h b/Include/internal/pycore_runtime_init.h
index 7cfa7c0..5b09a45 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 c68bd1f..a45b3bfa 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
@@ -878,71 +890,170 @@ gets even messier.
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)
{
- PyObject *extensions = EXTENSIONS;
- if (extensions == NULL) {
- return NULL;
- }
+ PyModuleDef *def = NULL;
+ extensions_lock_acquire();
+
PyObject *key = PyTuple_Pack(2, filename, name);
if (key == NULL) {
- return NULL;
+ goto finally;
+ }
+
+ PyObject *extensions = EXTENSIONS.dict;
+ if (extensions == NULL) {
+ goto finally;
}
- PyModuleDef *def = (PyModuleDef *)PyDict_GetItemWithError(extensions, key);
- Py_DECREF(key);
+ def = (PyModuleDef *)PyDict_GetItemWithError(extensions, key);
+
+finally:
+ Py_XDECREF(key);
+ extensions_lock_release();
return def;
}
static int
_extensions_cache_set(PyObject *filename, PyObject *name, PyModuleDef *def)
{
- PyObject *extensions = EXTENSIONS;
+ 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));
+
+ /* 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);
+ if (key == NULL) {
+ goto finally;
+ }
+
+ PyObject *extensions = EXTENSIONS.dict;
if (extensions == NULL) {
extensions = PyDict_New();
if (extensions == NULL) {
- return -1;
+ goto finally;
}
- EXTENSIONS = extensions;
+ EXTENSIONS.dict = extensions;
}
- PyObject *key = PyTuple_Pack(2, filename, name);
- if (key == NULL) {
- return -1;
+
+ 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;
}
- int res = PyDict_SetItem(extensions, key, (PyObject *)def);
- Py_DECREF(key);
+
+ /* This might trigger a resize, which is why we must switch
+ to the main interpreter. */
+ res = PyDict_SetItem(extensions, key, (PyObject *)def);
if (res < 0) {
- return -1;
+ res = -1;
+ goto finally;
}
- return 0;
+ res = 0;
+
+finally:
+ if (oldts != NULL) {
+ _PyThreadState_Swap(interp->runtime, oldts);
+ _PyThreadState_UnbindDetached(main_tstate);
+ Py_DECREF(name);
+ Py_DECREF(filename);
+ }
+ Py_XDECREF(key);
+ extensions_lock_release();
+ return res;
}
static int
_extensions_cache_delete(PyObject *filename, PyObject *name)
{
- PyObject *extensions = EXTENSIONS;
- if (extensions == NULL) {
- return 0;
- }
+ int res = -1;
+ PyThreadState *oldts = NULL;
+ extensions_lock_acquire();
+
PyObject *key = PyTuple_Pack(2, filename, name);
if (key == NULL) {
- return -1;
+ goto finally;
+ }
+
+ PyObject *extensions = EXTENSIONS.dict;
+ if (extensions == NULL) {
+ res = 0;
+ goto finally;
}
+
+ 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);
- return -1;
- }
- PyErr_Clear();
+ goto finally;
}
- Py_DECREF(key);
- return 0;
+ res = 0;
+
+finally:
+ if (oldts != NULL) {
+ _PyThreadState_Swap(interp->runtime, oldts);
+ _PyThreadState_UnbindDetached(main_tstate);
+ }
+ Py_XDECREF(key);
+ extensions_lock_release();
+ return res;
}
static void
_extensions_cache_clear_all(void)
{
- Py_CLEAR(EXTENSIONS);
+ /* The runtime (i.e. main interpreter) must be finalizing,
+ so we don't need to worry about the lock. */
+ // XXX assert(_Py_IsMainInterpreter(_PyInterpreterState_GET()));
+ Py_CLEAR(EXTENSIONS.dict);
+ _PyThreadState_ClearDetached(&EXTENSIONS.main_tstate);
}
@@ -2941,6 +3052,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.
diff --git a/Python/pystate.c b/Python/pystate.c
index b17efdb..1e59a8c 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) {
@@ -1347,6 +1357,19 @@ _PyThreadState_Init(PyThreadState *tstate)
Py_FatalError("_PyThreadState_Init() is for internal use only");
}
+
+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)
{
@@ -1421,7 +1444,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)
@@ -1454,18 +1476,11 @@ 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;
}
-
static void
zapthreads(PyInterpreterState *interp)
{
@@ -1552,6 +1567,75 @@ _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(
+ current_fast_get(tstate->interp->runtime)->interp));
+ assert(_Py_IsMainInterpreter(tstate->interp));
+ bind_tstate(tstate);
+ /* Unlike _PyThreadState_Bind(), we do not modify gilstate TSS. */
+}
+
+void
+_PyThreadState_UnbindDetached(PyThreadState *tstate)
+{
+ assert(!_Py_IsMainInterpreter(
+ current_fast_get(tstate->interp->runtime)->interp));
+ assert(_Py_IsMainInterpreter(tstate->interp));
+ 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
//----------