You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Creating a subinterpreter when Python is run with -X tracemalloc leads to segfault #134604
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:
/* Flush sys.stdout and sys.stderr (again, in case more was printed) */
if (flush_std_files() <0) {
status=-1;
}
/* Collect final garbage. This disposes of cycles created by
* class definitions, for example.
* XXX This is disabled because it caused too many problems. If
* XXX a __del__ or weakref callback triggers here, Python code has
* XXX a hard time running, because even the sys module has been
* XXX cleared out (sys.stdout is gone, sys.excepthook is gone, etc).
* XXX One symptom is a sequence of information-free messages
* XXX coming from threads (if a __del__ or callback is invoked,
* XXX other threads can execute too, and any exception they encounter
* XXX triggers a comedy of errors as subsystem after subsystem
* XXX fails to find what it *expects* to find in sys to help report
* XXX the exception and consequent unexpected failures). I've also
* XXX seen segfaults then, after adding print statements to the
* XXX Python code getting called.
*/
#if0
_PyGC_CollectIfEnabled();
#endif
/* Disable tracemalloc after all Python objects have been destroyed,
so it is possible to use tracemalloc in objects destructor. */
_PyTraceMalloc_Fini();
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.
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.