mirror of
https://github.com/python/cpython.git
synced 2025-09-27 10:50:04 +00:00
gh-113433: Automatically Clean Up Subinterpreters in Py_Finalize() (gh-121060)
This change makes things a little less painful for some users. It also fixes a failing assert (gh-120765), by making sure all subinterpreters are destroyed before the main interpreter. As part of that, we make sure Py_Finalize() always runs with the main interpreter active.
This commit is contained in:
parent
1c13b29d54
commit
4be1f37b20
3 changed files with 151 additions and 9 deletions
|
@ -0,0 +1,2 @@
|
||||||
|
Subinterpreters now get cleaned up automatically during runtime
|
||||||
|
finalization.
|
|
@ -0,0 +1,2 @@
|
||||||
|
:c:func:`Py_Finalize()` and :c:func:`Py_FinalizeEx()` now always run with
|
||||||
|
the main interpreter active.
|
|
@ -73,6 +73,7 @@ static PyStatus init_sys_streams(PyThreadState *tstate);
|
||||||
static PyStatus init_android_streams(PyThreadState *tstate);
|
static PyStatus init_android_streams(PyThreadState *tstate);
|
||||||
#endif
|
#endif
|
||||||
static void wait_for_thread_shutdown(PyThreadState *tstate);
|
static void wait_for_thread_shutdown(PyThreadState *tstate);
|
||||||
|
static void finalize_subinterpreters(void);
|
||||||
static void call_ll_exitfuncs(_PyRuntimeState *runtime);
|
static void call_ll_exitfuncs(_PyRuntimeState *runtime);
|
||||||
|
|
||||||
/* The following places the `_PyRuntime` structure in a location that can be
|
/* The following places the `_PyRuntime` structure in a location that can be
|
||||||
|
@ -1907,20 +1908,73 @@ finalize_interp_delete(PyInterpreterState *interp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int
|
/* Conceptually, there isn't a good reason for Py_Finalize()
|
||||||
Py_FinalizeEx(void)
|
to be called in any other thread than the one where Py_Initialize()
|
||||||
|
was called. Consequently, it would make sense to fail if the thread
|
||||||
|
or thread state (or interpreter) don't match. However, such
|
||||||
|
constraints have never been enforced, and, as unlikely as it may be,
|
||||||
|
there may be users relying on the unconstrained behavior. Thus,
|
||||||
|
we do our best here to accommodate that possibility. */
|
||||||
|
|
||||||
|
static PyThreadState *
|
||||||
|
resolve_final_tstate(_PyRuntimeState *runtime)
|
||||||
|
{
|
||||||
|
PyThreadState *main_tstate = runtime->main_tstate;
|
||||||
|
assert(main_tstate != NULL);
|
||||||
|
assert(main_tstate->thread_id == runtime->main_thread);
|
||||||
|
PyInterpreterState *main_interp = _PyInterpreterState_Main();
|
||||||
|
assert(main_tstate->interp == main_interp);
|
||||||
|
|
||||||
|
PyThreadState *tstate = _PyThreadState_GET();
|
||||||
|
if (_Py_IsMainThread()) {
|
||||||
|
if (tstate != main_tstate) {
|
||||||
|
/* This implies that Py_Finalize() was called while
|
||||||
|
a non-main interpreter was active or while the main
|
||||||
|
tstate was temporarily swapped out with another.
|
||||||
|
Neither case should be allowed, but, until we get around
|
||||||
|
to fixing that (and Py_Exit()), we're letting it go. */
|
||||||
|
(void)PyThreadState_Swap(main_tstate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
/* This is another unfortunate case where Py_Finalize() was
|
||||||
|
called when it shouldn't have been. We can't simply switch
|
||||||
|
over to the main thread. At the least, however, we can make
|
||||||
|
sure the main interpreter is active. */
|
||||||
|
if (!_Py_IsMainInterpreter(tstate->interp)) {
|
||||||
|
/* We don't go to the trouble of updating runtime->main_tstate
|
||||||
|
since it will be dead soon anyway. */
|
||||||
|
main_tstate =
|
||||||
|
_PyThreadState_New(main_interp, _PyThreadState_WHENCE_FINI);
|
||||||
|
if (main_tstate != NULL) {
|
||||||
|
_PyThreadState_Bind(main_tstate);
|
||||||
|
(void)PyThreadState_Swap(main_tstate);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
/* Fall back to the current tstate. It's better than nothing. */
|
||||||
|
main_tstate = tstate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(main_tstate != NULL);
|
||||||
|
|
||||||
|
/* We might want to warn if main_tstate->current_frame != NULL. */
|
||||||
|
|
||||||
|
return main_tstate;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
_Py_Finalize(_PyRuntimeState *runtime)
|
||||||
{
|
{
|
||||||
int status = 0;
|
int status = 0;
|
||||||
|
|
||||||
_PyRuntimeState *runtime = &_PyRuntime;
|
/* Bail out early if already finalized (or never initialized). */
|
||||||
if (!runtime->initialized) {
|
if (!runtime->initialized) {
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Get current thread state and interpreter pointer */
|
/* Get final thread state pointer. */
|
||||||
PyThreadState *tstate = _PyThreadState_GET();
|
PyThreadState *tstate = resolve_final_tstate(runtime);
|
||||||
// XXX assert(_Py_IsMainInterpreter(tstate->interp));
|
|
||||||
// XXX assert(_Py_IsMainThread());
|
|
||||||
|
|
||||||
// Block some operations.
|
// Block some operations.
|
||||||
tstate->interp->finalizing = 1;
|
tstate->interp->finalizing = 1;
|
||||||
|
@ -1943,6 +1997,8 @@ Py_FinalizeEx(void)
|
||||||
|
|
||||||
_PyAtExit_Call(tstate->interp);
|
_PyAtExit_Call(tstate->interp);
|
||||||
|
|
||||||
|
assert(_PyThreadState_GET() == tstate);
|
||||||
|
|
||||||
/* Copy the core config, PyInterpreterState_Delete() free
|
/* Copy the core config, PyInterpreterState_Delete() free
|
||||||
the core config memory */
|
the core config memory */
|
||||||
#ifdef Py_REF_DEBUG
|
#ifdef Py_REF_DEBUG
|
||||||
|
@ -2023,6 +2079,9 @@ Py_FinalizeEx(void)
|
||||||
_PyImport_FiniExternal(tstate->interp);
|
_PyImport_FiniExternal(tstate->interp);
|
||||||
finalize_modules(tstate);
|
finalize_modules(tstate);
|
||||||
|
|
||||||
|
/* Clean up any lingering subinterpreters. */
|
||||||
|
finalize_subinterpreters();
|
||||||
|
|
||||||
/* Print debug stats if any */
|
/* Print debug stats if any */
|
||||||
_PyEval_Fini();
|
_PyEval_Fini();
|
||||||
|
|
||||||
|
@ -2140,10 +2199,16 @@ Py_FinalizeEx(void)
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
Py_FinalizeEx(void)
|
||||||
|
{
|
||||||
|
return _Py_Finalize(&_PyRuntime);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
Py_Finalize(void)
|
Py_Finalize(void)
|
||||||
{
|
{
|
||||||
Py_FinalizeEx();
|
(void)_Py_Finalize(&_PyRuntime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -2355,6 +2420,79 @@ _Py_IsInterpreterFinalizing(PyInterpreterState *interp)
|
||||||
return finalizing != NULL;
|
return finalizing != NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
finalize_subinterpreters(void)
|
||||||
|
{
|
||||||
|
PyThreadState *final_tstate = _PyThreadState_GET();
|
||||||
|
PyInterpreterState *main_interp = _PyInterpreterState_Main();
|
||||||
|
assert(final_tstate->interp == main_interp);
|
||||||
|
_PyRuntimeState *runtime = main_interp->runtime;
|
||||||
|
struct pyinterpreters *interpreters = &runtime->interpreters;
|
||||||
|
|
||||||
|
/* Get the first interpreter in the list. */
|
||||||
|
HEAD_LOCK(runtime);
|
||||||
|
PyInterpreterState *interp = interpreters->head;
|
||||||
|
if (interp == main_interp) {
|
||||||
|
interp = interp->next;
|
||||||
|
}
|
||||||
|
HEAD_UNLOCK(runtime);
|
||||||
|
|
||||||
|
/* Bail out if there are no subinterpreters left. */
|
||||||
|
if (interp == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Warn the user if they forgot to clean up subinterpreters. */
|
||||||
|
(void)PyErr_WarnEx(
|
||||||
|
PyExc_RuntimeWarning,
|
||||||
|
"remaining subinterpreters; "
|
||||||
|
"destroy them with _interpreters.destroy()",
|
||||||
|
0);
|
||||||
|
|
||||||
|
/* Swap out the current tstate, which we know must belong
|
||||||
|
to the main interpreter. */
|
||||||
|
_PyThreadState_Detach(final_tstate);
|
||||||
|
|
||||||
|
/* Clean up all remaining subinterpreters. */
|
||||||
|
while (interp != NULL) {
|
||||||
|
assert(!_PyInterpreterState_IsRunningMain(interp));
|
||||||
|
|
||||||
|
/* Find the tstate to use for fini. We assume the interpreter
|
||||||
|
will have at most one tstate at this point. */
|
||||||
|
PyThreadState *tstate = interp->threads.head;
|
||||||
|
if (tstate != NULL) {
|
||||||
|
/* Ideally we would be able to use tstate as-is, and rely
|
||||||
|
on it being in a ready state: no exception set, not
|
||||||
|
running anything (tstate->current_frame), matching the
|
||||||
|
current thread ID (tstate->thread_id). To play it safe,
|
||||||
|
we always delete it and use a fresh tstate instead. */
|
||||||
|
assert(tstate != final_tstate);
|
||||||
|
_PyThreadState_Attach(tstate);
|
||||||
|
PyThreadState_Clear(tstate);
|
||||||
|
_PyThreadState_Detach(tstate);
|
||||||
|
PyThreadState_Delete(tstate);
|
||||||
|
}
|
||||||
|
tstate = _PyThreadState_NewBound(interp, _PyThreadState_WHENCE_FINI);
|
||||||
|
|
||||||
|
/* Destroy the subinterpreter. */
|
||||||
|
_PyThreadState_Attach(tstate);
|
||||||
|
Py_EndInterpreter(tstate);
|
||||||
|
assert(_PyThreadState_GET() == NULL);
|
||||||
|
|
||||||
|
/* Advance to the next interpreter. */
|
||||||
|
HEAD_LOCK(runtime);
|
||||||
|
interp = interpreters->head;
|
||||||
|
if (interp == main_interp) {
|
||||||
|
interp = interp->next;
|
||||||
|
}
|
||||||
|
HEAD_UNLOCK(runtime);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Switch back to the main interpreter. */
|
||||||
|
_PyThreadState_Attach(final_tstate);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Add the __main__ module */
|
/* Add the __main__ module */
|
||||||
|
|
||||||
static PyStatus
|
static PyStatus
|
||||||
|
@ -3216,7 +3354,7 @@ Py_Exit(int sts)
|
||||||
if (tstate != NULL && _PyThreadState_IsRunningMain(tstate)) {
|
if (tstate != NULL && _PyThreadState_IsRunningMain(tstate)) {
|
||||||
_PyInterpreterState_SetNotRunningMain(tstate->interp);
|
_PyInterpreterState_SetNotRunningMain(tstate->interp);
|
||||||
}
|
}
|
||||||
if (Py_FinalizeEx() < 0) {
|
if (_Py_Finalize(&_PyRuntime) < 0) {
|
||||||
sts = 120;
|
sts = 120;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue