mirror of
https://github.com/python/cpython.git
synced 2025-08-04 08:59:19 +00:00
gh-100227: Make the Global PyModuleDef Cache Safe for Isolated Interpreters (gh-103084)
Sharing mutable (or non-immortal) objects between interpreters is generally not safe. We can work around that but not easily. There are two restrictions that are critical for objects that break interpreter isolation. The first is that the object's state be guarded by a global lock. For now the GIL meets this requirement, but a granular global lock is needed once we have a per-interpreter GIL. The second restriction is that the object (and, for a container, its items) be deallocated/resized only when the interpreter in which it was allocated is the current one. This is because every interpreter has (or will have, see gh-101660) its own object allocator. Deallocating an object with a different allocator can cause crashes. The dict for the cache of module defs is completely internal, which simplifies what we have to do to meet those requirements. To do so, we do the following: * add a mechanism for re-using a temporary thread state tied to the main interpreter in an arbitrary thread * add _PyRuntime.imports.extensions.main_tstate` * add _PyThreadState_InitDetached() and _PyThreadState_ClearDetached() (pystate.c) * add _PyThreadState_BindDetached() and _PyThreadState_UnbindDetached() (pystate.c) * make sure the cache dict (_PyRuntime.imports.extensions.dict) and its items are all owned by the main interpreter) * add a placeholder using for a granular global lock Note that the cache is only used for legacy extension modules and not for multi-phase init modules. https://github.com/python/cpython/issues/100227
This commit is contained in:
parent
121057aa36
commit
dcd6f226d6
5 changed files with 274 additions and 59 deletions
128
Python/pystate.c
128
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
|
||||
//----------
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue