From 00c69d22d7e1549a99fc7c171847500d36c4d562 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 13 Feb 2026 11:12:30 +0100 Subject: [PATCH 1/2] gh-144763: Don't detach the GIL in tracemalloc tracemalloc no longer detachs the GIL to acquire its internal lock. --- .../2026-02-13-11-14-18.gh-issue-144763.cDwnEE.rst | 2 ++ Python/tracemalloc.c | 13 ++++++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-02-13-11-14-18.gh-issue-144763.cDwnEE.rst diff --git a/Misc/NEWS.d/next/Library/2026-02-13-11-14-18.gh-issue-144763.cDwnEE.rst b/Misc/NEWS.d/next/Library/2026-02-13-11-14-18.gh-issue-144763.cDwnEE.rst new file mode 100644 index 00000000000000..a206f1cc5bbdbf --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-02-13-11-14-18.gh-issue-144763.cDwnEE.rst @@ -0,0 +1,2 @@ +Fix a race condition in :mod:`tracemalloc`: it no longer detachs the GIL to +acquire its internal lock. Patch by Victor Stinner. diff --git a/Python/tracemalloc.c b/Python/tracemalloc.c index cdd96925d1f27a..31995dd815b48a 100644 --- a/Python/tracemalloc.c +++ b/Python/tracemalloc.c @@ -36,7 +36,7 @@ static int _PyTraceMalloc_TraceRef(PyObject *op, PyRefTracerEvent event, the GIL held from PyMem_RawFree(). It cannot acquire the lock because it would introduce a deadlock in _PyThreadState_DeleteCurrent(). */ #define tables_lock _PyRuntime.tracemalloc.tables_lock -#define TABLES_LOCK() PyMutex_Lock(&tables_lock) +#define TABLES_LOCK() PyMutex_LockFlags(&tables_lock, _Py_LOCK_DONT_DETACH) #define TABLES_UNLOCK() PyMutex_Unlock(&tables_lock) @@ -224,13 +224,20 @@ tracemalloc_get_frame(_PyInterpreterFrame *pyframe, frame_t *frame) assert(PyStackRef_CodeCheck(pyframe->f_executable)); frame->filename = &_Py_STR(anon_unknown); - int lineno = PyUnstable_InterpreterFrame_GetLine(pyframe); + int lineno = -1; + PyCodeObject *code = _PyFrame_GetCode(pyframe); + // PyUnstable_InterpreterFrame_GetLine() cannot but used, since it uses + // a critical section which can trigger a deadlock. + int lasti = _PyFrame_SafeGetLasti(pyframe); + if (lasti >= 0) { + lineno = _PyCode_SafeAddr2Line(code, lasti); + } if (lineno < 0) { lineno = 0; } frame->lineno = (unsigned int)lineno; - PyObject *filename = _PyFrame_GetCode(pyframe)->co_filename; + PyObject *filename = code->co_filename; if (filename == NULL) { #ifdef TRACE_DEBUG tracemalloc_error("failed to get the filename of the code object"); From a91fc22a151f203499bb437d48e77dc0a1090c28 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 13 Feb 2026 17:18:41 +0100 Subject: [PATCH 2/2] Fix race condition in _PyTraceMalloc_Stop() Don't call PyRefTracer_SetTracer() while holding TABLES_LOCK(). --- Python/tracemalloc.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Python/tracemalloc.c b/Python/tracemalloc.c index 31995dd815b48a..370fff766f6336 100644 --- a/Python/tracemalloc.c +++ b/Python/tracemalloc.c @@ -870,7 +870,8 @@ _PyTraceMalloc_Stop(void) TABLES_LOCK(); if (!tracemalloc_config.tracing) { - goto done; + TABLES_UNLOCK(); + return; } /* stop tracing Python memory allocations */ @@ -887,10 +888,12 @@ _PyTraceMalloc_Stop(void) raw_free(tracemalloc_traceback); tracemalloc_traceback = NULL; - (void)PyRefTracer_SetTracer(NULL, NULL); - -done: TABLES_UNLOCK(); + + // Call it after TABLES_UNLOCK() since it calls _PyEval_StopTheWorldAll() + // which would lead to a deadlock with TABLES_LOCK() which doesn't release + // the GIL. + (void)PyRefTracer_SetTracer(NULL, NULL); }