Repository navigation
Unclosed interpreter causes segfault after fork() in child process. #139759
Description
Bug report
Bug description:
With concurrent.interpreters now available in 3.14 it's possible to trigger a probably already known issue with cleaning up interpreters after a fork.
This minimal example creates an interpreter (on the same thread), doesn't close it and then forks. The child process segfaults.
#!/usr/bin/python3.14
import os
from concurrent import interpreters
GLOBAL_INTERP = None
def create_subinter(do_close: bool) -> None:
global GLOBAL_INTERP
interp = interpreters.create()
GLOBAL_INTERP = interp
interp.exec("""
x = 42 * 23
""")
if do_close:
interp.close()
def main():
global GLOBAL_INTERP
create_subinter(do_close=False)
print("Before fork")
child_pid = os.fork()
if child_pid == 0:
print("in child")
else:
print("in parent")
if __name__ == "__main__":
main()There seems to be a more general issue with the implementation of PyOS_AfterFork_Child() or PyInterpreterState_Clear() specifically:
Lines 992 to 994 in a15aeec
| // XXX Won't this fail since PyInterpreterState_Clear() requires | |
| // the "current" tstate to be set? | |
| PyInterpreterState_Clear(interp); // XXX must activate? |
As the comment points out, the PyInterpreterState_Clear() won't be able to clear any interpreters, as the tstate is set to NULL before calling the function. Which finally results in a Py_DECREF on NULL.
I guess the expected behavior is that manually closing interpreters before a fork is not required and a working clear in the fork. While the combination of subinterpreters and fork() seems a bit obscure, we already ran into this issue in a CI setup.
It also seems that there is a lack of test coverage with the mentioned implementation in pystate.c. If appreciated, I could also follow up with a pull request adding more tests.
Full backtrace:
#0 _Py_DECREF_DecRefTotal () at Objects/object.c:287
#1 0x0000555555816177 in Py_DECREF (filename=filename@entry=0x555555908674 "Python/import.c", lineno=lineno@entry=4099, op=0x7ffff6ea0230) at ./Include/refcount.h:400
#2 0x000055555581d0af in _PyImport_ClearCore (interp=interp@entry=0x7ffff7a59020) at Python/import.c:4099
#3 0x000055555584a008 in PyInterpreterState_Clear (interp=interp@entry=0x7ffff7a59020) at Python/pystate.c:986
#4 0x000055555584a7a3 in _PyInterpreterState_DeleteExceptMain (runtime=runtime@entry=0x555555b809c0 <_PyRuntime>) at Python/pystate.c:1085
#5 0x000055555588f202 in PyOS_AfterFork_Child () at ./Modules/posixmodule.c:741
#6 0x000055555588f49a in os_fork_impl (module=<optimized out>) at ./Modules/posixmodule.c:8159
#7 0x000055555588f516 in os_fork (module=<optimized out>, _unused_ignored=_unused_ignored@entry=0x0) at ./Modules/clinic/posixmodule.c.h:4394
#8 0x00005555556ce81a in cfunction_vectorcall_NOARGS (func=0x7ffff711d3d0, args=<optimized out>, nargsf=<optimized out>, kwnames=<optimized out>) at Objects/methodobject.c:508
#9 0x0000555555676df0 in _PyObject_VectorcallTstate (tstate=0x555555bd1938 <_PyRuntime+331640>, callable=callable@entry=0x7ffff711d3d0, args=args@entry=0x7fffffffd628,
nargsf=9223372036854775808, kwnames=kwnames@entry=0x0) at ./Include/internal/pycore_call.h:169
#10 0x0000555555676ecc in PyObject_Vectorcall (callable=callable@entry=0x7ffff711d3d0, args=args@entry=0x7fffffffd628, nargsf=<optimized out>, kwnames=kwnames@entry=0x0)
at Objects/call.c:327
#11 0x00005555557b4ca6 in _PyEval_EvalFrameDefault (tstate=<optimized out>, frame=0x7ffff7fb2080, throwflag=0) at Python/generated_cases.c.h:1619
#12 0x00005555557d3d57 in _PyEval_EvalFrame (tstate=tstate@entry=0x555555bd1938 <_PyRuntime+331640>, frame=frame@entry=0x7ffff7fb2020, throwflag=throwflag@entry=0)
at ./Include/internal/pycore_ceval.h:119
#13 0x00005555557d3f28 in _PyEval_Vector (tstate=tstate@entry=0x555555bd1938 <_PyRuntime+331640>, func=func@entry=0x7ffff716ab10, locals=locals@entry=0x7ffff71782f0,
args=args@entry=0x0, argcount=argcount@entry=0, kwnames=kwnames@entry=0x0) at Python/ceval.c:1965
#14 0x00005555557d4024 in PyEval_EvalCode (co=co@entry=0x7ffff7bd23b0, globals=globals@entry=0x7ffff71782f0, locals=locals@entry=0x7ffff71782f0) at Python/ceval.c:857
#15 0x000055555584b13f in run_eval_code_obj (tstate=tstate@entry=0x555555bd1938 <_PyRuntime+331640>, co=co@entry=0x7ffff7bd23b0, globals=globals@entry=0x7ffff71782f0,
locals=locals@entry=0x7ffff71782f0) at Python/pythonrun.c:1365
#16 0x000055555584b2dc in run_mod (mod=mod@entry=0x555555d7f060, filename=filename@entry=0x7ffff7198400, globals=globals@entry=0x7ffff71782f0, locals=locals@entry=0x7ffff71782f0,
flags=flags@entry=0x7fffffffda28, arena=arena@entry=0x7ffff71e4a00, interactive_src=0x0, generate_new_source=0) at Python/pythonrun.c:1459
--Type <RET> for more, q to quit, c to continue without paging--
#17 0x000055555584bb5c in pyrun_file (fp=fp@entry=0x555555c49a60, filename=filename@entry=0x7ffff7198400, start=start@entry=257, globals=globals@entry=0x7ffff71782f0,
locals=locals@entry=0x7ffff71782f0, closeit=closeit@entry=1, flags=0x7fffffffda28) at Python/pythonrun.c:1293
#18 0x000055555584d3f0 in _PyRun_SimpleFileObject (fp=fp@entry=0x555555c49a60, filename=filename@entry=0x7ffff7198400, closeit=closeit@entry=1, flags=flags@entry=0x7fffffffda28)
at Python/pythonrun.c:521
#19 0x000055555584d5e6 in _PyRun_AnyFileObject (fp=fp@entry=0x555555c49a60, filename=filename@entry=0x7ffff7198400, closeit=closeit@entry=1, flags=flags@entry=0x7fffffffda28)
at Python/pythonrun.c:81
#20 0x0000555555876b67 in pymain_run_file_obj (program_name=program_name@entry=0x7ffff71b2640, filename=filename@entry=0x7ffff7198400, skip_source_first_line=0) at Modules/main.c:410
#21 0x0000555555876c8f in pymain_run_file (config=config@entry=0x555555b9c9f0 <_PyRuntime+114736>) at Modules/main.c:429
#22 0x00005555558777ca in pymain_run_python (exitcode=exitcode@entry=0x7fffffffdba4) at Modules/main.c:694
#23 0x0000555555877a7f in Py_RunMain () at Modules/main.c:775
#24 0x0000555555877af6 in pymain_main (args=args@entry=0x7fffffffdc00) at Modules/main.c:805
#25 0x0000555555877bc4 in Py_BytesMain (argc=<optimized out>, argv=<optimized out>) at Modules/main.c:829
#26 0x00005555555de166 in main (argc=<optimized out>, argv=<optimized out>) at ./Programs/python.c:15
CPython versions tested on:
3.14
Operating systems tested on:
Linux
Linked PRs
Metadata
Metadata
Assignees
Labels
Projects
- StatusShow more project fieldsTodo
- StatusShow more project fieldsTodo
Activity
efimov-mikhail commentedon Oct 8, 2025
Thanks for the report.
Unfortunately, I can't get a crash with this code snippet.
There is only
RuntimeWarningpresent.Could you please provide more information about your OS and environment?
aurelwildfellner commentedon Oct 8, 2025
This may be due to SIGSEV signal not propagating with the fork. You can see from the output that "in child" is not printed which indicates that the process has crashed.
You can also try to run it in gdb with the right follow-fork-mode:
gdb -ex "set follow-fork-mode child" --args ./python subinterpreteres_crash_139759.pyYou should see a SIGSEV and get an according backtrace (running with a debug build).
Tested with a debug build of the 3.14 branch.
gcc (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0
Ubuntu 24.04.2 LTS (all dependencies installed from system packages).
efimov-mikhail commentedon Oct 8, 2025
Confirmed on main branch,
gcc version 12.2.0 (Debian 12.2.0-14+deb12u1). Thanks!ZeroIntensity commentedon Oct 9, 2025
Would you like to submit a PR?
efimov-mikhail commentedon Oct 9, 2025
You're asking me or @aurelwildfellner ?
I can try to dig into this.
ZeroIntensity commentedon Oct 9, 2025
I was asking either of you.
efimov-mikhail commentedon Oct 12, 2025
I've made some progress in this investigation and want to finish this work and submit a PR.
Add test_unclosed_interpreter_on_fork to cover pythongh-139759
aurelwildfellner commentedon Mar 5, 2026
I have created a unittest covering this on a separate PR. Maybe it is useful when working on a fix or can be merged to main after we have a fix. - gh-145545
ZeroIntensity commentedon Mar 5, 2026
Oh, the issue is that we delete all the interpreters after forking:
cpython/Modules/posixmodule.c
Line 777 in 2cd0ddf
It's not immediately clear to me as to why. @ericsnowcurrently is the person to ask about that.
davidnich commentedon Oct 22, 2026
I can reproduce this without any third-party code:
os.fork()in the main interpreter while a single subinterpreter exists. The child either crashes or hangs inPyOS_AfterFork_Child(), depending on the version.3.14: the result is the same with
new_config("isolated"), and when no code was run in the subinterpreter. The crash with faulthandler on (3.14.6, macOS) is:3.11: the child waits forever in
PyOS_AfterFork_Child()->PyThread_acquire_lock_timed().We hit this in an embedding application. Its subinterpreters are created with
allow_fork = 0, and it forks from the main interpreter of a Python process. For now it skipsPyOS_BeforeFork()/PyOS_AfterFork_*()for its ownfork()calls while subinterpreters exist.aurelwildfellner commentedon Oct 23, 2026
I also rebased and updated the related pull request with the test. The test still fails as expected.