From de4f844e3f5daaa5d7c82376b80a207af38dac17 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 18 Feb 2026 16:57:48 +0100 Subject: [PATCH] gh-144763: Fix race conditions in tracemalloc (#144779) Avoid PyUnstable_InterpreterFrame_GetLine() since it uses a critical section which can lead to a deadlock. _PyTraceMalloc_Stop() now also calls PyRefTracer_SetTracer() without holding TABLES_LOCK() to prevent another deadlock. Co-authored-by: Kumar Aditya (cherry picked from commit 83f4fffe3d78ba368c0d4864c42c7c9c9223f7d1) --- Python/tracemalloc.c | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/Python/tracemalloc.c b/Python/tracemalloc.c index 8ae12e545761ef..4795068d0a0618 100644 --- a/Python/tracemalloc.c +++ b/Python/tracemalloc.c @@ -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 = 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"); @@ -853,7 +860,8 @@ _PyTraceMalloc_Stop(void) TABLES_LOCK(); if (!tracemalloc_config.tracing) { - goto done; + TABLES_UNLOCK(); + return; } /* stop tracing Python memory allocations */ @@ -870,10 +878,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 detach + // the thread state. + (void)PyRefTracer_SetTracer(NULL, NULL); }