Repository navigation
Subinterpreters don't properly clean up threads #128639
Description
Activity
- addedextension-modulesC modules in the Modules dirC modules in the Modules dirtype-crashA hard crash of the interpreter, possibly with a core dumpA hard crash of the interpreter, possibly with a core dump3.13only security fixesonly security fixes3.14bugs and security fixesbugs and security fixes
on Jan 8, 2025 FWIW, I thought I had created an issue about cleaning up lingering thread states automatically. Would you mind double-checking and closing it, if so?
- marked Lingering Subinterpreter Subthread Keeps Program From Exiting #113146 as a duplicate of this issue
on May 19, 2025 - added a commit that references this issue
on May 19, 2025 For those who aren't me or Eric: we had to revert the fix because it breaks the test suite when run with
--single-process. I'm still trying to figure out why.@ericsnowcurrently I finally figured out the buildbot failures. This is an awesome bug.
Here's a repro that crashes with my patch applied:
import _interpreters import types import time interp = _interpreters.create( types.SimpleNamespace( use_main_obmalloc=False, allow_fork=False, allow_exec=False, allow_threads=True, allow_daemon_threads=True, check_multi_interp_extensions=True, gil='own', ) ) _interpreters.exec(interp, f"""if True: import threading import time def task(): time.sleep(1) threads = [threading.Thread(target=task, daemon=True) for _ in range(3)] for t in threads: t.start() """) _interpreters.destroy(interp) time.sleep(2)
The problem is that something subtly changed with daemon thread shutdown in-between writing my patch and merging it. So, after the
time.sleep()call, the thread would still be alive and crash. Unfortunately for me, I randomly chose 100 seconds as the sleep time, so if the process exited within those 100 seconds, nothing would crash. I'll put up the fixed version.Reacted by Eric SnowI have a following repro:
from concurrent import interpreters import _interpreters def func(*args, **kwargs): # pass # crash on main, ok on #134606 assert False # crash on both def clean_up_interpreters(): for interp in interpreters.list_all(): if interp.id == 0: # main continue try: interp.close() except _interpreters.InterpreterError: pass # already destroyed def test(): interp = interpreters.create() interp.call_in_thread(func) try: for _ in range(100): test() finally: clean_up_interpreters()I tested it on main and on #134606 (with merged main). func with
assert Falsecrashes with following stack trace:python315.dll!PyDict_GetItemWithError(_object * op, _object * key) Line 2450 C [Inline Frame] python315.dll!get_type_attr_as_size(_typeobject *) Line 30 C [Inline Frame] python315.dll!PyStructSequence_New(_typeobject *) Line 64 C python315.dll!make_unraisable_hook_args(_ts * tstate, _object * exc_type, _object * exc_value, _object * exc_tb, _object * err_msg, _object * obj) Line 1412 C python315.dll!format_unraisable_v(const char * format, char * va, _object * obj) Line 1670 C python315.dll!PyErr_FormatUnraisable(const char * format, ...) Line 1746 C > python315.dll!Py_EndInterpreter(_ts * tstate) Line 2449 C python315.dll!_PyXI_EndInterpreter(_is * interp, _ts * tstate, _ts * * p_save_tstate) Line 3285 C python315.dll!interp_destroy(_object * self, _object * args, _object * kwds) Line 946 C python315.dll!cfunction_call(_object * func, _object * args, _object * kwargs) Line 567 CReacted by Peter BiermaNo idea yet. Let's wait until we get thread cleanup fixed and then we can investigate that one in a new issue.
- marked concurrent.interpreters - buggy code leads to a crash during finalization #136568 as a duplicate of this issue
on Jul 12, 2025 - added a commit that references this issue
on Sep 17, 2025 - added a commit that references this issue
on Oct 7, 2025 FTR, repro from #128639 (comment) seems to have been fixed. It produces:
Exception in thread Thread-100 (_call): Traceback (most recent call last): File "X:\Work\cpython\main\Lib\threading.py", line 1075, in _bootstrap_inner self._context.run(self.run) ~~~~~~~~~~~~~~~~~^^^^^^^^^^ File "X:\Work\cpython\main\Lib\threading.py", line 1017, in run self._target(*self._args, **self._kwargs) ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "X:\Work\cpython\main\Lib\concurrent\interpreters\__init__.py", line 222, in _call raise ExecutionFailed(excinfo) concurrent.interpreters.ExecutionFailed: AssertionError: Uncaught in the interpreter: Traceback (most recent call last): File "X:\Work\cpython\issues\test.py", line 6, in func assert False # crash on both ^^^^^ AssertionErrorThanks!
Reacted by Peter Bierma
Metadata
Metadata
Assignees
Labels
Projects
- StatusShow more project fieldsDone
Crash report
What happened?
I found this issue a little while back, but I'm finally getting around to fixing it.
Currently, subinterpreter finalization assumes that there's only one thread left. So, any remaining threads--daemon or non-daemon--crash the interpreter upon finalizing:
This results in an assertion failure on my end:
So, there's a few things that need to get fixed:
Py_EndInterpreter, which will properly clean things up.PyThreadState_Delete. That should also happen inPy_EndInterpreter, after all threads have finished._PyRuntimeState_SetFinalizinghas been set, meaning that all threads will be already blocked, and thus cannot shutdown viathreading._shutdown, resulting in a deadlock.finalize_subinterpretersshould be called before that happens.CPython versions tested on:
CPython main branch
Operating systems tested on:
Linux
Output from running 'python -VV' on the command line:
No response
Linked PRs