bpo-46008: Move Py*State init into distinct functions. (gh-29977)

Previously, basic initialization of PyInterprterState happened in PyInterpreterState_New() (along with allocation and adding the new interpreter to the runtime state). This prevented us from initializing interpreter states that were allocated separately (e.g. statically or in a free list). We've addressed that here by factoring out a separate function just for initialization. We've done the same for PyThreadState. _PyRuntimeState was sorted out when we added it since _PyRuntime is statically allocated. However, here we update the existing init code to line up with the functions for PyInterpreterState and PyThreadState.

https://bugs.python.org/issue46008
This commit is contained in:
Eric Snow 2021-12-07 18:59:49 -07:00 committed by GitHub
parent 758b74e71e
commit 32a67246b0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 309 additions and 113 deletions

View file

@ -46,20 +46,54 @@ static PyThreadState *_PyGILState_GetThisThreadState(struct _gilstate_runtime_st
static void _PyThreadState_Delete(PyThreadState *tstate, int check_current);
static PyStatus
_PyRuntimeState_Init_impl(_PyRuntimeState *runtime)
static int
alloc_for_runtime(PyThread_type_lock *plock1, PyThread_type_lock *plock2,
PyThread_type_lock *plock3)
{
/* We preserve the hook across init, because there is
currently no public API to set it between runtime
initialization and interpreter initialization. */
void *open_code_hook = runtime->open_code_hook;
void *open_code_userdata = runtime->open_code_userdata;
_Py_AuditHookEntry *audit_hook_head = runtime->audit_hook_head;
// bpo-42882: Preserve next_index value if Py_Initialize()/Py_Finalize()
// is called multiple times.
Py_ssize_t unicode_next_index = runtime->unicode_ids.next_index;
/* Force default allocator, since _PyRuntimeState_Fini() must
use the same allocator than this function. */
PyMemAllocatorEx old_alloc;
_PyMem_SetDefaultAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
memset(runtime, 0, sizeof(*runtime));
PyThread_type_lock lock1 = PyThread_allocate_lock();
if (lock1 == NULL) {
return -1;
}
PyThread_type_lock lock2 = PyThread_allocate_lock();
if (lock2 == NULL) {
PyThread_free_lock(lock1);
return -1;
}
PyThread_type_lock lock3 = PyThread_allocate_lock();
if (lock3 == NULL) {
PyThread_free_lock(lock1);
PyThread_free_lock(lock2);
return -1;
}
PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
*plock1 = lock1;
*plock2 = lock2;
*plock3 = lock3;
return 0;
}
static void
init_runtime(_PyRuntimeState *runtime,
void *open_code_hook, void *open_code_userdata,
_Py_AuditHookEntry *audit_hook_head,
Py_ssize_t unicode_next_index,
PyThread_type_lock unicode_ids_mutex,
PyThread_type_lock interpreters_mutex,
PyThread_type_lock xidregistry_mutex)
{
if (runtime->_initialized) {
_PyRuntimeState_reset(runtime);
assert(!runtime->initialized);
}
runtime->open_code_hook = open_code_hook;
runtime->open_code_userdata = open_code_userdata;
@ -76,41 +110,44 @@ _PyRuntimeState_Init_impl(_PyRuntimeState *runtime)
Py_tss_t initial = Py_tss_NEEDS_INIT;
runtime->gilstate.autoTSSkey = initial;
runtime->interpreters.mutex = PyThread_allocate_lock();
if (runtime->interpreters.mutex == NULL) {
return _PyStatus_NO_MEMORY();
}
runtime->interpreters.mutex = interpreters_mutex;
// This prevents interpreters from getting created
// until _PyInterpreterState_Enable() is called.
runtime->interpreters.next_id = -1;
runtime->xidregistry.mutex = PyThread_allocate_lock();
if (runtime->xidregistry.mutex == NULL) {
return _PyStatus_NO_MEMORY();
}
runtime->xidregistry.mutex = xidregistry_mutex;
// Set it to the ID of the main thread of the main interpreter.
runtime->main_thread = PyThread_get_thread_ident();
runtime->unicode_ids.lock = PyThread_allocate_lock();
if (runtime->unicode_ids.lock == NULL) {
return _PyStatus_NO_MEMORY();
}
runtime->unicode_ids.next_index = unicode_next_index;
runtime->unicode_ids.lock = unicode_ids_mutex;
return _PyStatus_OK();
runtime->_initialized = 1;
}
PyStatus
_PyRuntimeState_Init(_PyRuntimeState *runtime)
{
/* Force default allocator, since _PyRuntimeState_Fini() must
use the same allocator than this function. */
PyMemAllocatorEx old_alloc;
_PyMem_SetDefaultAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
/* We preserve the hook across init, because there is
currently no public API to set it between runtime
initialization and interpreter initialization. */
void *open_code_hook = runtime->open_code_hook;
void *open_code_userdata = runtime->open_code_userdata;
_Py_AuditHookEntry *audit_hook_head = runtime->audit_hook_head;
// bpo-42882: Preserve next_index value if Py_Initialize()/Py_Finalize()
// is called multiple times.
Py_ssize_t unicode_next_index = runtime->unicode_ids.next_index;
PyStatus status = _PyRuntimeState_Init_impl(runtime);
PyThread_type_lock lock1, lock2, lock3;
if (alloc_for_runtime(&lock1, &lock2, &lock3) != 0) {
return _PyStatus_NO_MEMORY();
}
PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
return status;
init_runtime(runtime, open_code_hook, open_code_userdata, audit_hook_head,
unicode_next_index, lock1, lock2, lock3);
return _PyStatus_OK();
}
void
@ -204,37 +241,51 @@ _PyInterpreterState_Enable(_PyRuntimeState *runtime)
return _PyStatus_OK();
}
PyInterpreterState *
PyInterpreterState_New(void)
static PyInterpreterState *
alloc_interpreter(void)
{
PyThreadState *tstate = _PyThreadState_GET();
/* tstate is NULL when Py_InitializeFromConfig() calls
PyInterpreterState_New() to create the main interpreter. */
if (_PySys_Audit(tstate, "cpython.PyInterpreterState_New", NULL) < 0) {
return NULL;
return PyMem_RawCalloc(1, sizeof(PyInterpreterState));
}
static void
free_interpreter(PyInterpreterState *interp)
{
PyMem_RawFree(interp);
}
/* Get the interpreter state to a minimal consistent state.
Further init happens in pylifecycle.c before it can be used.
All fields not initialized here are expected to be zeroed out,
e.g. by PyMem_RawCalloc() or memset().
The runtime state is not manipulated. Instead it is assumed that
the interpreter is getting added to the runtime.
*/
static void
init_interpreter(PyInterpreterState *interp,
_PyRuntimeState *runtime, int64_t id,
PyInterpreterState *next,
PyThread_type_lock pending_lock)
{
if (interp->_initialized) {
Py_FatalError("interpreter already initialized");
}
PyInterpreterState *interp = PyMem_RawCalloc(1, sizeof(PyInterpreterState));
if (interp == NULL) {
return NULL;
}
interp->id_refcount = -1;
/* Don't get runtime from tstate since tstate can be NULL */
_PyRuntimeState *runtime = &_PyRuntime;
assert(runtime != NULL);
interp->runtime = runtime;
PyThread_type_lock pending_lock = PyThread_allocate_lock();
if (pending_lock == NULL) {
goto out_of_memory;
}
assert(id > 0 || (id == 0 && interp == runtime->interpreters.main));
interp->id = id;
interp->id_refcount = -1;
assert(runtime->interpreters.head == interp);
assert(next != NULL || (interp == runtime->interpreters.main));
interp->next = next;
_PyEval_InitState(&interp->ceval, pending_lock);
_PyGC_InitState(&interp->gc);
PyConfig_InitPythonConfig(&interp->config);
_PyType_InitCache(interp);
interp->eval_frame = NULL;
#ifdef HAVE_DLOPEN
#if HAVE_DECL_RTLD_NOW
@ -244,45 +295,90 @@ PyInterpreterState_New(void)
#endif
#endif
struct pyinterpreters *interpreters = &runtime->interpreters;
interp->_initialized = 1;
}
HEAD_LOCK(runtime);
if (interpreters->next_id < 0) {
/* overflow or Py_Initialize() not called! */
if (tstate != NULL) {
_PyErr_SetString(tstate, PyExc_RuntimeError,
"failed to get an interpreter ID");
}
PyMem_RawFree(interp);
interp = NULL;
}
else {
interp->id = interpreters->next_id;
interpreters->next_id += 1;
interp->next = interpreters->head;
if (interpreters->main == NULL) {
interpreters->main = interp;
}
interpreters->head = interp;
}
HEAD_UNLOCK(runtime);
PyInterpreterState *
PyInterpreterState_New(void)
{
PyInterpreterState *interp;
PyThreadState *tstate = _PyThreadState_GET();
if (interp == NULL) {
/* tstate is NULL when Py_InitializeFromConfig() calls
PyInterpreterState_New() to create the main interpreter. */
if (_PySys_Audit(tstate, "cpython.PyInterpreterState_New", NULL) < 0) {
return NULL;
}
interp->threads.next_unique_id = 0;
interp->audit_hooks = NULL;
return interp;
out_of_memory:
if (tstate != NULL) {
_PyErr_NoMemory(tstate);
PyThread_type_lock pending_lock = PyThread_allocate_lock();
if (pending_lock == NULL) {
if (tstate != NULL) {
_PyErr_NoMemory(tstate);
}
return NULL;
}
PyMem_RawFree(interp);
/* Don't get runtime from tstate since tstate can be NULL. */
_PyRuntimeState *runtime = &_PyRuntime;
struct pyinterpreters *interpreters = &runtime->interpreters;
/* We completely serialize creation of multiple interpreters, since
it simplifies things here and blocking concurrent calls isn't a problem.
Regardless, we must fully block subinterpreter creation until
after the main interpreter is created. */
HEAD_LOCK(runtime);
int64_t id = interpreters->next_id;
interpreters->next_id += 1;
// Allocate the interpreter and add it to the runtime state.
PyInterpreterState *old_head = interpreters->head;
if (old_head == NULL) {
// We are creating the main interpreter.
assert(interpreters->main == NULL);
assert(id == 0);
interp = alloc_interpreter();
if (interp == NULL) {
goto error;
}
assert(interp->id == 0);
assert(interp->next == NULL);
interpreters->main = interp;
}
else {
assert(id != 0);
assert(interpreters->main != NULL);
interp = alloc_interpreter();
if (interp == NULL) {
goto error;
}
if (id < 0) {
/* overflow or Py_Initialize() not called yet! */
if (tstate != NULL) {
_PyErr_SetString(tstate, PyExc_RuntimeError,
"failed to get an interpreter ID");
}
goto error;
}
}
interpreters->head = interp;
init_interpreter(interp, runtime, id, old_head, pending_lock);
HEAD_UNLOCK(runtime);
return interp;
error:
HEAD_UNLOCK(runtime);
PyThread_free_lock(pending_lock);
if (interp != NULL) {
free_interpreter(interp);
}
return NULL;
}
@ -415,7 +511,7 @@ PyInterpreterState_Delete(PyInterpreterState *interp)
if (interp->id_mutex != NULL) {
PyThread_free_lock(interp->id_mutex);
}
PyMem_RawFree(interp);
free_interpreter(interp);
}
@ -453,7 +549,7 @@ _PyInterpreterState_DeleteExceptMain(_PyRuntimeState *runtime)
}
PyInterpreterState *prev_interp = interp;
interp = interp->next;
PyMem_RawFree(prev_interp);
free_interpreter(prev_interp);
}
HEAD_UNLOCK(runtime);
@ -631,46 +727,121 @@ allocate_chunk(int size_in_bytes, _PyStackChunk* previous)
}
static PyThreadState *
new_threadstate(PyInterpreterState *interp)
alloc_threadstate(void)
{
_PyRuntimeState *runtime = interp->runtime;
PyThreadState *tstate = (PyThreadState *)PyMem_RawCalloc(1, sizeof(PyThreadState));
if (tstate == NULL) {
return NULL;
return PyMem_RawCalloc(1, sizeof(PyThreadState));
}
static void
free_threadstate(PyThreadState *tstate)
{
PyMem_RawFree(tstate);
}
/* Get the thread state to a minimal consistent state.
Further init happens in pylifecycle.c before it can be used.
All fields not initialized here are expected to be zeroed out,
e.g. by PyMem_RawCalloc() or memset().
The interpreter state is not manipulated. Instead it is assumed that
the thread is getting added to the interpreter.
*/
static void
init_threadstate(PyThreadState *tstate,
PyInterpreterState *interp, uint64_t id,
PyThreadState *next,
_PyStackChunk *datastack_chunk)
{
if (tstate->_initialized) {
Py_FatalError("thread state already initialized");
}
assert(interp != NULL);
tstate->interp = interp;
tstate->recursion_limit = interp->ceval.recursion_limit;
tstate->recursion_remaining = interp->ceval.recursion_limit;
tstate->cframe = &tstate->root_cframe;
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;
tstate->prev = NULL;
tstate->thread_id = PyThread_get_thread_ident();
#ifdef PY_HAVE_THREAD_NATIVE_ID
tstate->native_thread_id = PyThread_get_thread_native_id();
#endif
tstate->exc_info = &tstate->exc_state;
tstate->context_ver = 1;
tstate->datastack_chunk = allocate_chunk(DATA_STACK_CHUNK_SIZE, NULL);
if (tstate->datastack_chunk == NULL) {
PyMem_RawFree(tstate);
return NULL;
}
tstate->recursion_limit = interp->ceval.recursion_limit,
tstate->recursion_remaining = interp->ceval.recursion_limit,
tstate->exc_info = &tstate->exc_state;
tstate->cframe = &tstate->root_cframe;
assert(datastack_chunk != NULL);
tstate->datastack_chunk = datastack_chunk;
/* If top points to entry 0, then _PyThreadState_PopFrame will try to pop this chunk */
tstate->datastack_top = &tstate->datastack_chunk->data[1];
tstate->datastack_limit = (PyObject **)(((char *)tstate->datastack_chunk) + DATA_STACK_CHUNK_SIZE);
HEAD_LOCK(runtime);
tstate->id = ++interp->threads.next_unique_id;
tstate->next = interp->threads.head;
if (tstate->next)
tstate->next->prev = tstate;
interp->threads.head = tstate;
HEAD_UNLOCK(runtime);
tstate->_initialized = 1;
}
static PyThreadState *
new_threadstate(PyInterpreterState *interp)
{
PyThreadState *tstate;
_PyRuntimeState *runtime = interp->runtime;
_PyStackChunk *datastack_chunk = allocate_chunk(DATA_STACK_CHUNK_SIZE, NULL);
if (datastack_chunk == NULL) {
return NULL;
}
/* We serialize concurrent creation to protect global state. */
HEAD_LOCK(runtime);
interp->threads.next_unique_id += 1;
uint64_t id = interp->threads.next_unique_id;
// Allocate the thread state and add it to the interpreter.
PyThreadState *old_head = interp->threads.head;
if (old_head == NULL) {
// It's the interpreter's initial thread state.
assert(id == 1);
tstate = alloc_threadstate();
if (tstate == NULL) {
goto error;
}
}
else {
// Every valid interpreter must have at least one thread.
assert(id > 1);
assert(old_head->prev == NULL);
tstate = alloc_threadstate();
if (tstate == NULL) {
goto error;
}
}
interp->threads.head = tstate;
init_threadstate(tstate, interp, id, old_head, datastack_chunk);
HEAD_UNLOCK(runtime);
return tstate;
error:
HEAD_UNLOCK(runtime);
_PyObject_VirtualFree(datastack_chunk, datastack_chunk->size);
return NULL;
}
PyThreadState *
@ -931,7 +1102,7 @@ _PyThreadState_Delete(PyThreadState *tstate, int check_current)
}
}
tstate_delete_common(tstate, gilstate);
PyMem_RawFree(tstate);
free_threadstate(tstate);
}
@ -950,7 +1121,7 @@ _PyThreadState_DeleteCurrent(PyThreadState *tstate)
tstate_delete_common(tstate, gilstate);
_PyRuntimeGILState_SetThreadState(gilstate, NULL);
_PyEval_ReleaseLock(tstate);
PyMem_RawFree(tstate);
free_threadstate(tstate);
}
void