Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 34k
gh-128639: Don't assume one thread in subinterpreter finalization with fixed daemon thread support#134606
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
ZeroIntensity merged 13 commits into python:main from ZeroIntensity:fix-subinterp-thread-shutdownSep 17, 2025
Uh oh!
There was an error while loading. Please reload this page.
Merged
gh-128639: Don't assume one thread in subinterpreter finalization with fixed daemon thread support #134606
Changes from all commits
Commits
Show all changes
13 commits Select commit Hold shift + click to select a range
b14aa73 Reapply "gh-128639: Don't assume one thread in subinterpreter finaliz…
ZeroIntensity 91d94d9 Fix daemon thread shutdown.
ZeroIntensity a569355 Stop the runtime instead of the interpreter.
ZeroIntensity 0146aec Merge branch 'main' into fix-subinterp-thread-shutdown
ZeroIntensity f85a291 Run the daemon thread test in a subprocess.
ZeroIntensity d287d54 Update Lib/test/test_interpreters/test_api.py
ZeroIntensity fe7a342 Merge branch 'main' into fix-subinterp-thread-shutdown
ZeroIntensity 193c49a Merge branch 'main' of https://github.com/python/cpython into fix-sub…
ZeroIntensity 62bd653 Remove broken assertion.
ZeroIntensity b029dd5 Avoid using list comprehensions in the tests.
ZeroIntensity 88b1cf6 Merge branch 'main' of https://github.com/python/cpython into fix-sub…
ZeroIntensity a8de87c Merge branch 'main' of https://github.com/python/cpython into fix-sub…
ZeroIntensity 6acb265 Merge branch 'main' into fix-subinterp-thread-shutdown
ZeroIntensity File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Jump to file
Failed to load files.
Loading
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
1 change: 1 addition & 0 deletions 1 Misc/NEWS.d/next/Core_and_Builtins/2025-01-08-12-52-47.gh-issue-128640.9nbh9z.rst
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Fix a crash when using threads inside of a subinterpreter. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -2000,6 +2000,7 @@ resolve_final_tstate(_PyRuntimeState *runtime) | ||
| } | ||
| else{ | ||
| /* Fall back to the current tstate. It's better than nothing. */ | ||
| // XXX No it's not | ||
| main_tstate = tstate; | ||
| } | ||
| } | ||
| @@ -2045,6 +2046,16 @@ _Py_Finalize(_PyRuntimeState *runtime) | ||
| _PyAtExit_Call(tstate->interp); | ||
| /* Clean up any lingering subinterpreters. | ||
| Two preconditions need to be met here: | ||
| - This has to happen before _PyRuntimeState_SetFinalizing is | ||
| called, or else threads might get prematurely blocked. | ||
| - The world must not be stopped, as finalizers can run. | ||
| */ | ||
| finalize_subinterpreters(); | ||
| assert(_PyThreadState_GET() == tstate); | ||
| /* Copy the core config, PyInterpreterState_Delete() free | ||
| @@ -2132,9 +2143,6 @@ _Py_Finalize(_PyRuntimeState *runtime) | ||
| _PyImport_FiniExternal(tstate->interp); | ||
| finalize_modules(tstate); | ||
| /* Clean up any lingering subinterpreters. */ | ||
| finalize_subinterpreters(); | ||
| /* Print debug stats if any */ | ||
| _PyEval_Fini(); | ||
| @@ -2416,9 +2424,8 @@ Py_NewInterpreter(void) | ||
| return tstate; | ||
| } | ||
| /* Delete an interpreter and its last thread. This requires that the | ||
| given thread state is current, that the thread has no remaining | ||
| frames, and that it is its interpreter's only remaining thread. | ||
| /* Delete an interpreter. This requires that the given thread state | ||
| is current, and that the thread has no remaining frames. | ||
| It is a fatal error to violate these constraints. | ||
| (Py_FinalizeEx() doesn't have these constraints -- it zaps | ||
| @@ -2448,15 +2455,20 @@ Py_EndInterpreter(PyThreadState *tstate) | ||
| _Py_FinishPendingCalls(tstate); | ||
| _PyAtExit_Call(tstate->interp); | ||
| if (tstate != interp->threads.head || tstate->next != NULL){ | ||
| Py_FatalError("not the last thread"); | ||
| } | ||
| _PyRuntimeState *runtime = interp->runtime; | ||
| _PyEval_StopTheWorldAll(runtime); | ||
| /* Remaining daemon threads will automatically exit | ||
| when they attempt to take the GIL (ex: PyEval_RestoreThread()). */ | ||
| _PyInterpreterState_SetFinalizing(interp, tstate); | ||
| PyThreadState *list = _PyThreadState_RemoveExcept(tstate); | ||
| for (PyThreadState *p = list; p != NULL; p = p->next){ | ||
| _PyThreadState_SetShuttingDown(p); | ||
| } | ||
| _PyEval_StartTheWorldAll(runtime); | ||
| _PyThreadState_DeleteList(list, /*is_after_fork=*/0); | ||
| // XXX Call something like _PyImport_Disable() here? | ||
| _PyImport_FiniExternal(tstate->interp); | ||
| @@ -2486,6 +2498,8 @@ finalize_subinterpreters(void) | ||
| PyInterpreterState *main_interp = _PyInterpreterState_Main(); | ||
| assert(final_tstate->interp == main_interp); | ||
| _PyRuntimeState *runtime = main_interp->runtime; | ||
| assert(!runtime->stoptheworld.world_stopped); | ||
| assert(_PyRuntimeState_GetFinalizing(runtime) == NULL); | ||
| struct pyinterpreters *interpreters = &runtime->interpreters; | ||
| /* Get the first interpreter in the list. */ | ||
| @@ -2514,27 +2528,17 @@ finalize_subinterpreters(void) | ||
| /* Clean up all remaining subinterpreters. */ | ||
| while (interp != NULL){ | ||
| assert(!_PyInterpreterState_IsRunningMain(interp)); | ||
ericsnowcurrently marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading. Please reload this page. | ||
| /* 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); | ||
| /* Make a tstate for finalization. */ | ||
| PyThreadState *tstate = _PyThreadState_NewBound(interp, _PyThreadState_WHENCE_FINI); | ||
| if (tstate == NULL){ | ||
| // XXX Some graceful way to always get a thread state? | ||
| Py_FatalError("thread state allocation failed"); | ||
| } | ||
| tstate = _PyThreadState_NewBound(interp, _PyThreadState_WHENCE_FINI); | ||
| /* Destroy the subinterpreter. */ | ||
| /* Enter the subinterpreter. */ | ||
| _PyThreadState_Attach(tstate); | ||
| /* Destroy the subinterpreter. */ | ||
| Py_EndInterpreter(tstate); | ||
| assert(_PyThreadState_GET() == NULL); | ||
Oops, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.