Description
Crash report
What happened?
Minimal reproducer:
import _interpreters
_interpreters.create()
python -X tracemalloc subtest.py
This also fails if one submits a task to concurrent.futures.InterpreterPoolExecutor
.
Example output when run with -X tracemalloc
:
Python/tracemalloc.c:705: _Py_NegativeRefcount: Assertion failed: object has negative ref count
<object at 0x200041899d0 is freed>
Fatal Python error: _PyObject_AssertFailed: _PyObject_AssertFailed
Python runtime state: finalizing (tstate=0x0000559f74721680)
Stack (most recent call first):
<no Python frame>
Aborted (core dumped)
That points here:
Lines 701 to 706 in f478331
I presume the issue is that the filename is not populated for the subinterpreter?
I'm on Ubuntu 24.04 on an x86_64 machine if that makes a difference. My latest configure command line is:
./configure --config-cache --with-pydebug --disable-gil CC=clang-20
Note that this also reproduces with the GIL enabled, (but it just outputs an unhelpful "segmentation fault").
CPython versions tested on:
CPython main branch
Operating systems tested on:
Linux
Output from running 'python -VV' on the command line:
Python 3.15.0a0 experimental free-threading build (heads/main:f478331f989, May 23 2025, 16:17:11) [Clang 20.1.5 (++20250430014901+ebfae55af454-1exp120250430014920.111)]
Linked PRs
Metadata
Metadata
Assignees
Labels
Projects
Status
Activity
emmatyping commentedon May 25, 2025
Looked into this a bit more. I'm not very familiar with subinterpreters internals or the Python runtime lifecycle, so apologies if the following is wrong.
It seems that tracemalloc holds references to frame information, which is not safe with multiple interpreters that may be destroyed before tracemalloc drops it's references. I was able to make the minimal reproducer pass by moving
finalize_subinterpreters()
after_PyTraceMalloc_Fini()
is called:cpython/Python/pylifecycle.c
Lines 2128 to 2159 in 2fd09b0
This works because frame(s) created by the
_interpreters.create()
call then live longer than tracemalloc's references to them.However, that won't work in the general case. Since subinterpreters can be created and destroyed at will, tracemalloc could always potentially hang on to dead references. For example, the following leads to a segfault even after re-ordering finalization steps:
I think a potential solution might be to copy the filename from the Python frame to the tracemalloc frame? That way the information is not lost when a subinterpreter is destroyed. If that does sound like a good idea, I can go ahead and work on a PR to do that.
emmatyping commentedon May 25, 2025
Hm, copying is tricky because making a new unicode object can deadlock the interpreter, I guess we could store the filename as a
char *
to avoid that?emmatyping commentedon May 26, 2025
See also #126315, seems like tracemalloc needs more work to be safe with multiple threads/interpreters.
tracemalloc
aborts when run from threads in no-gil #126315