https://github.com/python/cpython/commit/6b1b90f7c4bd3590784075ef49a5710fabd4aca3
commit: 6b1b90f7c4bd3590784075ef49a5710fabd4aca3
branch: main
author: Kumar Aditya <[email protected]>
committer: kumaraditya303 <[email protected]>
date: 2026-08-30T13:04:55+05:30
summary:

gh-155725: Remove `PyGILState_Ensure` usage from `tracemalloc` (#156409)

files:
A 
Misc/NEWS.d/next/Core_and_Builtins/2026-08-26-10-00-00.gh-issue-155725.tRcMal.rst
M Include/internal/pycore_pystate.h
M Include/internal/pycore_tracemalloc.h
M Lib/test/test_tracemalloc.py
M Python/pystate.c
M Python/tracemalloc.c

diff --git a/Include/internal/pycore_pystate.h 
b/Include/internal/pycore_pystate.h
index ca6819d2cd44730..253d26fe3a3cd8b 100644
--- a/Include/internal/pycore_pystate.h
+++ b/Include/internal/pycore_pystate.h
@@ -359,6 +359,10 @@ struct PyInterpreterView {
 PyAPI_FUNC(Py_ssize_t) _PyInterpreterState_GuardCountdown(PyInterpreterState 
*interp);
 PyAPI_FUNC(PyInterpreterState *) 
_PyInterpreterGuard_GetInterpreter(PyInterpreterGuard *guard);
 
+extern int _PyInterpreterGuard_TryAcquire(PyInterpreterState *interp,
+                                          PyInterpreterGuard *guard);
+extern void _PyInterpreterGuard_Release(PyInterpreterGuard *guard);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/Include/internal/pycore_tracemalloc.h 
b/Include/internal/pycore_tracemalloc.h
index 9974ea3c4143fa6..0522b24cd8fc67d 100644
--- a/Include/internal/pycore_tracemalloc.h
+++ b/Include/internal/pycore_tracemalloc.h
@@ -44,9 +44,10 @@ struct
 __attribute__((packed))
 #endif
 tracemalloc_frame {
-    /* filename cannot be NULL: "<unknown>" is used if the Python frame
-       filename is NULL */
-    PyObject *filename;
+    /* Interned NUL terminated UTF-8 (surrogatepass) string.
+       Cannot be NULL: "<unknown>" is used if the Python frame filename
+       cannot be captured. */
+    const char *filename;
     unsigned int lineno;
 };
 
@@ -85,7 +86,7 @@ struct _tracemalloc_runtime_state {
        Protected by TABLES_LOCK(). */
     size_t peak_traced_memory;
     /* Hash table used as a set to intern filenames:
-       PyObject* => PyObject*.
+       char* (NUL terminated UTF-8 string) => NULL.
        Protected by the TABLES_LOCK(). */
     _Py_hashtable_t *filenames;
     /* Buffer to store a new traceback in traceback_new().
diff --git a/Lib/test/test_tracemalloc.py b/Lib/test/test_tracemalloc.py
index 9d3ff8a620b6f26..ee02fbd16af0c4e 100644
--- a/Lib/test/test_tracemalloc.py
+++ b/Lib/test/test_tracemalloc.py
@@ -1056,8 +1056,8 @@ def test_track(self):
         self.check_track(False)
 
     def test_track_without_gil(self):
-        # check that calling _PyTraceMalloc_Track() without holding the GIL
-        # works too
+        # check that calling PyTraceMalloc_Track() without the GIL
+        # (detached thread state) still captures the Python traceback
         self.check_track(True)
 
     def test_track_already_tracked(self):
diff --git 
a/Misc/NEWS.d/next/Core_and_Builtins/2026-08-26-10-00-00.gh-issue-155725.tRcMal.rst
 
b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-26-10-00-00.gh-issue-155725.tRcMal.rst
new file mode 100644
index 000000000000000..3e157446954f61e
--- /dev/null
+++ 
b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-26-10-00-00.gh-issue-155725.tRcMal.rst
@@ -0,0 +1,6 @@
+:mod:`tracemalloc` no longer acquires the :term:`GIL` nor creates a
+temporary thread state to trace memory allocations: traceback frames now
+store plain UTF-8 strings instead of Python str objects, and tracebacks are
+captured using the Python thread state already associated with the calling
+thread, even if it is not attached. Threads without a Python thread state
+record the traceback as ``<unknown>``.
diff --git a/Python/pystate.c b/Python/pystate.c
index 6b0d6a7a372c926..9a2dc9431f8bb24 100644
--- a/Python/pystate.c
+++ b/Python/pystate.c
@@ -3419,8 +3419,8 @@ PyInterpreterGuard_FromCurrent(void)
     return guard;
 }
 
-void
-PyInterpreterGuard_Close(PyInterpreterGuard *guard)
+static void
+release_interp_guard(PyInterpreterGuard *guard)
 {
     PyInterpreterState *interp = guard->interp;
     assert(interp != NULL);
@@ -3432,9 +3432,28 @@ PyInterpreterGuard_Close(PyInterpreterGuard *guard)
     }
 
     assert(old_value > 0);
+}
+
+void
+PyInterpreterGuard_Close(PyInterpreterGuard *guard)
+{
+    release_interp_guard(guard);
     PyMem_RawFree(guard);
 }
 
+int
+_PyInterpreterGuard_TryAcquire(PyInterpreterState *interp,
+                               PyInterpreterGuard *guard)
+{
+    return try_acquire_interp_guard(interp, guard);
+}
+
+void
+_PyInterpreterGuard_Release(PyInterpreterGuard *guard)
+{
+    release_interp_guard(guard);
+}
+
 PyInterpreterView *
 PyInterpreterView_FromCurrent(void)
 {
diff --git a/Python/tracemalloc.c b/Python/tracemalloc.c
index 0afc84e021817cf..8ca2134921ff913 100644
--- a/Python/tracemalloc.c
+++ b/Python/tracemalloc.c
@@ -7,8 +7,9 @@
 #include "pycore_lock.h"          // PyMutex_LockFlags()
 #include "pycore_object.h"        // _PyType_PreHeaderSize()
 #include "pycore_pymem.h"         // _Py_tracemalloc_config
+#include "pycore_pystate.h"       // _PyInterpreterGuard_TryAcquire()
 #include "pycore_runtime.h"       // _Py_ID()
-#include "pycore_traceback.h"     // _Py_DumpASCII()
+#include "pycore_traceback.h"     // _Py_DumpHexadecimal()
 
 #include <stdlib.h>               // malloc()
 
@@ -32,9 +33,10 @@ static int _PyTraceMalloc_TraceRef(PyObject *op, 
PyRefTracerEvent event,
 #define allocators _PyRuntime.tracemalloc.allocators
 
 
-/* This lock is needed because tracemalloc_free() is called without
-   the GIL held from PyMem_RawFree(). It cannot acquire the lock because it
-   would introduce a deadlock in _PyThreadState_DeleteCurrent(). */
+/* This lock protects the trace tables. It is acquired by threads which may
+   not have an attached thread state, such as tracemalloc_free() called from
+   PyMem_RawFree(): tracing never acquires the GIL nor attaches a thread
+   state. */
 #define tables_lock _PyRuntime.tracemalloc.tables_lock
 #define TABLES_LOCK() PyMutex_LockFlags(&tables_lock, _Py_LOCK_DONT_DETACH)
 #define TABLES_UNLOCK() PyMutex_Unlock(&tables_lock)
@@ -45,6 +47,10 @@ static int _PyTraceMalloc_TraceRef(PyObject *op, 
PyRefTracerEvent event,
 typedef struct tracemalloc_frame frame_t;
 typedef struct tracemalloc_traceback traceback_t;
 
+/* Filename used when the Python frame filename cannot be captured */
+static const char tracemalloc_unknown_filename[] = "<unknown>";
+#define UNKNOWN_FILENAME tracemalloc_unknown_filename
+
 #define TRACEBACK_SIZE(NFRAME) \
         (sizeof(traceback_t) + sizeof(frame_t) * (NFRAME))
 
@@ -126,24 +132,19 @@ set_reentrant(int reentrant)
 
 
 static Py_uhash_t
-hashtable_hash_pyobject(const void *key)
+hashtable_hash_filename(const void *key)
 {
-    PyObject *obj = (PyObject *)key;
-    return PyObject_Hash(obj);
+    const char *filename = (const char *)key;
+    return (Py_uhash_t)Py_HashBuffer(filename, (Py_ssize_t)strlen(filename));
 }
 
 
 static int
-hashtable_compare_unicode(const void *key1, const void *key2)
+hashtable_compare_filename(const void *key1, const void *key2)
 {
-    PyObject *obj1 = (PyObject *)key1;
-    PyObject *obj2 = (PyObject *)key2;
-    if (obj1 != NULL && obj2 != NULL) {
-        return (PyUnicode_Compare(obj1, obj2) == 0);
-    }
-    else {
-        return obj1 == obj2;
-    }
+    const char *filename1 = (const char *)key1;
+    const char *filename2 = (const char *)key2;
+    return (strcmp(filename1, filename2) == 0);
 }
 
 
@@ -181,6 +182,100 @@ raw_free(void *ptr)
 }
 
 
+/* Encode a str object to a NUL terminated UTF-8 (surrogatepass) string,
+   without using the Python C API. Return NULL on allocation failure. */
+static char *
+tracemalloc_encode_filename(PyObject *obj)
+{
+    int kind = PyUnicode_KIND(obj);
+    const void *data = PyUnicode_DATA(obj);
+    Py_ssize_t length = PyUnicode_GET_LENGTH(obj);
+
+    // worst case: 4 UTF-8 bytes per code point, plus the NUL terminator
+    if ((size_t)length > (SIZE_MAX - 1) / 4) {
+        return NULL;
+    }
+    char *buffer = raw_malloc((size_t)length * 4 + 1);
+    if (buffer == NULL) {
+        return NULL;
+    }
+
+    char *p = buffer;
+    for (Py_ssize_t i = 0; i < length; i++) {
+        Py_UCS4 ch = PyUnicode_READ(kind, data, i);
+        if (ch < 0x80) {
+            *p++ = (char)ch;
+        }
+        else if (ch < 0x800) {
+            *p++ = (char)(0xc0 | (ch >> 6));
+            *p++ = (char)(0x80 | (ch & 0x3f));
+        }
+        else if (ch < 0x10000) {
+            *p++ = (char)(0xe0 | (ch >> 12));
+            *p++ = (char)(0x80 | ((ch >> 6) & 0x3f));
+            *p++ = (char)(0x80 | (ch & 0x3f));
+        }
+        else {
+            *p++ = (char)(0xf0 | (ch >> 18));
+            *p++ = (char)(0x80 | ((ch >> 12) & 0x3f));
+            *p++ = (char)(0x80 | ((ch >> 6) & 0x3f));
+            *p++ = (char)(0x80 | (ch & 0x3f));
+        }
+    }
+    *p = '\0';
+    return buffer;
+}
+
+
+/* Intern a str object in the tracemalloc_filenames hash table as a NUL
+   terminated UTF-8 string. Return NULL on allocation failure.
+   The caller must hold the TABLES_LOCK(). */
+static const char *
+tracemalloc_intern_filename(PyObject *obj)
+{
+    assert(PyUnicode_Check(obj));
+
+    const char *utf8;
+    char *encoded = NULL;
+    if (PyUnicode_IS_COMPACT_ASCII(obj)) {
+        // ASCII string data is valid UTF-8 and is NUL terminated
+        utf8 = (const char *)PyUnicode_DATA(obj);
+    }
+    else {
+        encoded = tracemalloc_encode_filename(obj);
+        if (encoded == NULL) {
+            return NULL;
+        }
+        utf8 = encoded;
+    }
+
+    const char *result;
+    _Py_hashtable_entry_t *entry;
+    entry = _Py_hashtable_get_entry(tracemalloc_filenames, utf8);
+    if (entry != NULL) {
+        result = (const char *)entry->key;
+    }
+    else {
+        size_t size = strlen(utf8) + 1;
+        char *filename = raw_malloc(size);
+        if (filename == NULL) {
+            raw_free(encoded);
+            return NULL;
+        }
+        memcpy(filename, utf8, size);
+
+        if (_Py_hashtable_set(tracemalloc_filenames, filename, NULL) < 0) {
+            raw_free(filename);
+            raw_free(encoded);
+            return NULL;
+        }
+        result = filename;
+    }
+    raw_free(encoded);
+    return result;
+}
+
+
 static Py_uhash_t
 hashtable_hash_traceback(const void *key)
 {
@@ -209,8 +304,8 @@ hashtable_compare_traceback(const void *key1, const void 
*key2)
         if (frame1->lineno != frame2->lineno) {
             return 0;
         }
+        // Filenames are interned: compare by pointer
         if (frame1->filename != frame2->filename) {
-            assert(PyUnicode_Compare(frame1->filename, frame2->filename) != 0);
             return 0;
         }
     }
@@ -222,7 +317,7 @@ static void
 tracemalloc_get_frame(_PyInterpreterFrame *pyframe, frame_t *frame)
 {
     assert(PyStackRef_CodeCheck(pyframe->f_executable));
-    frame->filename = &_Py_STR(anon_unknown);
+    frame->filename = UNKNOWN_FILENAME;
 
     int lineno = -1;
     PyCodeObject *code = _PyFrame_GetCode(pyframe);
@@ -253,26 +348,15 @@ tracemalloc_get_frame(_PyInterpreterFrame *pyframe, 
frame_t *frame)
     }
 
     /* intern the filename */
-    _Py_hashtable_entry_t *entry;
-    entry = _Py_hashtable_get_entry(tracemalloc_filenames, filename);
-    if (entry != NULL) {
-        filename = (PyObject *)entry->key;
-    }
-    else {
-        /* tracemalloc_filenames is responsible to keep a reference
-           to the filename */
-        if (_Py_hashtable_set(tracemalloc_filenames, Py_NewRef(filename),
-                              NULL) < 0) {
-            Py_DECREF(filename);
+    const char *filename_copy = tracemalloc_intern_filename(filename);
+    if (filename_copy == NULL) {
 #ifdef TRACE_DEBUG
-            tracemalloc_error("failed to intern the filename");
+        tracemalloc_error("failed to intern the filename");
 #endif
-            return;
-        }
+        return;
     }
 
-    /* the tracemalloc_filenames table keeps a reference to the filename */
-    frame->filename = filename;
+    frame->filename = filename_copy;
 }
 
 
@@ -288,7 +372,8 @@ traceback_hash(traceback_t *traceback)
     x = 0x345678UL;
     frame = traceback->frames;
     while (--len >= 0) {
-        y = (Py_uhash_t)PyObject_Hash(frame->filename);
+        // Filenames are interned: hash the pointer
+        y = (Py_uhash_t)Py_HashPointer(frame->filename);
         y ^= (Py_uhash_t)frame->lineno;
         frame++;
 
@@ -303,11 +388,8 @@ traceback_hash(traceback_t *traceback)
 
 
 static void
-traceback_get_frames(traceback_t *traceback)
+traceback_get_frames(traceback_t *traceback, PyThreadState *tstate)
 {
-    PyThreadState *tstate = _PyThreadState_GET();
-    assert(tstate != NULL);
-
     _PyInterpreterFrame *pyframe = _PyThreadState_GetFrame(tstate);
     while (pyframe) {
         if (traceback->nframe < tracemalloc_config.max_nframe) {
@@ -329,13 +411,36 @@ traceback_new(void)
     traceback_t *traceback;
     _Py_hashtable_entry_t *entry;
 
-    _Py_AssertHoldsTstate();
+    // Capturing a traceback needs a thread state to walk the frame stack,
+    // but the thread state doesn't need to be attached: only the thread
+    // itself pushes and pops its own frames, and no Python object is used
+    // or modified. If not attached (e.g. the GIL was released), fall back
+    // to the thread state most recently bound to the thread, if any.
+    int detached = 0;
+    PyInterpreterGuard guard = {NULL};
+    PyThreadState *tstate = _PyThreadState_GET();
+    if (tstate == NULL) {
+        if (_PyInterpreterGuard_TryAcquire(_PyInterpreterState_Main(),
+                                           &guard) < 0) {
+            return tracemalloc_empty_traceback;
+        }
+        detached = 1;
+        tstate = PyGILState_GetThisThreadState();
+        if (tstate == NULL) {
+            // the thread never had a thread state: no frames to capture
+            _PyInterpreterGuard_Release(&guard);
+            return tracemalloc_empty_traceback;
+        }
+    }
 
     /* get frames */
     traceback = tracemalloc_traceback;
     traceback->nframe = 0;
     traceback->total_nframe = 0;
-    traceback_get_frames(traceback);
+    traceback_get_frames(traceback, tstate);
+    if (detached) {
+        _PyInterpreterGuard_Release(&guard);
+    }
     if (traceback->nframe == 0) {
         return tracemalloc_empty_traceback;
     }
@@ -497,8 +602,7 @@ tracemalloc_add_trace_unlocked(unsigned int domain, 
uintptr_t ptr,
 
 
 static void*
-tracemalloc_alloc(int need_gil, int use_calloc,
-                  void *ctx, size_t nelem, size_t elsize)
+tracemalloc_alloc(int use_calloc, void *ctx, size_t nelem, size_t elsize)
 {
     assert(elsize == 0 || nelem <= SIZE_MAX / elsize);
 
@@ -506,12 +610,9 @@ tracemalloc_alloc(int need_gil, int use_calloc,
 
     // Ignore reentrant call.
     //
-    // For example, PyObjet_Malloc() calls
+    // For example, PyObject_Malloc() calls
     // PyMem_Malloc() for allocations larger than 512 bytes: don't trace the
     // same memory allocation twice.
-    //
-    // If reentrant calls are not ignored, PyGILState_Ensure() can call
-    // PyMem_RawMalloc() which would call PyGILState_Ensure() again in a loop.
     if (!reentrant) {
         set_reentrant(1);
     }
@@ -532,10 +633,6 @@ tracemalloc_alloc(int need_gil, int use_calloc,
         goto done;
     }
 
-    PyGILState_STATE gil_state;
-    if (need_gil) {
-        gil_state = PyGILState_Ensure();
-    }
     TABLES_LOCK();
 
     if (tracemalloc_config.tracing) {
@@ -548,9 +645,6 @@ tracemalloc_alloc(int need_gil, int use_calloc,
     // else: gh-128679: tracemalloc.stop() was called by another thread
 
     TABLES_UNLOCK();
-    if (need_gil) {
-        PyGILState_Release(gil_state);
-    }
 
 done:
     if (!reentrant) {
@@ -561,7 +655,7 @@ tracemalloc_alloc(int need_gil, int use_calloc,
 
 
 static void*
-tracemalloc_realloc(int need_gil, void *ctx, void *ptr, size_t new_size)
+tracemalloc_realloc(void *ctx, void *ptr, size_t new_size)
 {
     int reentrant = get_reentrant();
 
@@ -582,10 +676,6 @@ tracemalloc_realloc(int need_gil, void *ctx, void *ptr, 
size_t new_size)
         goto done;
     }
 
-    PyGILState_STATE gil_state;
-    if (need_gil) {
-        gil_state = PyGILState_Ensure();
-    }
     TABLES_LOCK();
 
     if (!tracemalloc_config.tracing) {
@@ -610,8 +700,8 @@ tracemalloc_realloc(int need_gil, void *ctx, void *ptr, 
size_t new_size)
             // This case is very unlikely: a hash entry has just been released,
             // so the hash table should have at least one free entry.
             //
-            // The GIL and the table lock ensures that only one thread is
-            // allocating memory.
+            // The table lock ensures that no other thread touched the trace
+            // tables in the meantime.
             Py_FatalError("tracemalloc_realloc() failed to allocate a trace");
         }
     }
@@ -627,9 +717,6 @@ tracemalloc_realloc(int need_gil, void *ctx, void *ptr, 
size_t new_size)
 
 unlock:
     TABLES_UNLOCK();
-    if (need_gil) {
-        PyGILState_Release(gil_state);
-    }
 
 done:
     if (!reentrant) {
@@ -665,61 +752,22 @@ tracemalloc_free(void *ctx, void *ptr)
 
 
 static void*
-tracemalloc_malloc_gil(void *ctx, size_t size)
-{
-    return tracemalloc_alloc(0, 0, ctx, 1, size);
-}
-
-
-static void*
-tracemalloc_calloc_gil(void *ctx, size_t nelem, size_t elsize)
-{
-    return tracemalloc_alloc(0, 1, ctx, nelem, elsize);
-}
-
-
-static void*
-tracemalloc_realloc_gil(void *ctx, void *ptr, size_t new_size)
-{
-    return tracemalloc_realloc(0, ctx, ptr, new_size);
-}
-
-
-static void*
-tracemalloc_raw_malloc(void *ctx, size_t size)
+tracemalloc_malloc(void *ctx, size_t size)
 {
-    return tracemalloc_alloc(1, 0, ctx, 1, size);
+    return tracemalloc_alloc(0, ctx, 1, size);
 }
 
 
 static void*
-tracemalloc_raw_calloc(void *ctx, size_t nelem, size_t elsize)
+tracemalloc_calloc(void *ctx, size_t nelem, size_t elsize)
 {
-    return tracemalloc_alloc(1, 1, ctx, nelem, elsize);
-}
-
-
-static void*
-tracemalloc_raw_realloc(void *ctx, void *ptr, size_t new_size)
-{
-    return tracemalloc_realloc(1, ctx, ptr, new_size);
-}
-
-
-static void
-tracemalloc_clear_filename(void *value)
-{
-    PyObject *filename = (PyObject *)value;
-    Py_DECREF(filename);
+    return tracemalloc_alloc(1, ctx, nelem, elsize);
 }
 
 
 static void
 tracemalloc_clear_traces_unlocked(void)
 {
-    // Clearing tracemalloc_filenames requires the GIL to call Py_DECREF()
-    _Py_AssertHoldsTstate();
-
     set_reentrant(1);
 
     _Py_hashtable_clear(tracemalloc_traces);
@@ -745,9 +793,9 @@ _PyTraceMalloc_Init(void)
         return _PyStatus_NO_MEMORY();
     }
 
-    tracemalloc_filenames = hashtable_new(hashtable_hash_pyobject,
-                                          hashtable_compare_unicode,
-                                          tracemalloc_clear_filename, NULL);
+    tracemalloc_filenames = hashtable_new(hashtable_hash_filename,
+                                          hashtable_compare_filename,
+                                          raw_free, NULL);
 
     tracemalloc_tracebacks = hashtable_new(hashtable_hash_traceback,
                                            hashtable_compare_traceback,
@@ -770,8 +818,7 @@ _PyTraceMalloc_Init(void)
 
     tracemalloc_empty_traceback->nframe = 1;
     tracemalloc_empty_traceback->total_nframe = 1;
-    /* borrowed reference */
-    tracemalloc_empty_traceback->frames[0].filename = &_Py_STR(anon_unknown);
+    tracemalloc_empty_traceback->frames[0].filename = UNKNOWN_FILENAME;
     tracemalloc_empty_traceback->frames[0].lineno = 0;
     tracemalloc_empty_traceback->hash = 
traceback_hash(tracemalloc_empty_traceback);
 
@@ -829,20 +876,15 @@ _PyTraceMalloc_Start(int max_nframe)
     }
 
     PyMemAllocatorEx alloc;
-    alloc.malloc = tracemalloc_raw_malloc;
-    alloc.calloc = tracemalloc_raw_calloc;
-    alloc.realloc = tracemalloc_raw_realloc;
+    alloc.malloc = tracemalloc_malloc;
+    alloc.calloc = tracemalloc_calloc;
+    alloc.realloc = tracemalloc_realloc;
     alloc.free = tracemalloc_free;
 
     alloc.ctx = &allocators.raw;
     PyMem_GetAllocator(PYMEM_DOMAIN_RAW, &allocators.raw);
     PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &alloc);
 
-    alloc.malloc = tracemalloc_malloc_gil;
-    alloc.calloc = tracemalloc_calloc_gil;
-    alloc.realloc = tracemalloc_realloc_gil;
-    alloc.free = tracemalloc_free;
-
     alloc.ctx = &allocators.mem;
     PyMem_GetAllocator(PYMEM_DOMAIN_MEM, &allocators.mem);
     PyMem_SetAllocator(PYMEM_DOMAIN_MEM, &alloc);
@@ -898,17 +940,62 @@ _PyTraceMalloc_Stop(void)
 
 
 
+/* Convert an interned filename to a str object. intern_filenames
+   (const char* => str object, can be NULL) shares the str objects. */
 static PyObject*
-frame_to_pyobject(frame_t *frame)
+filename_to_pyobject(const char *filename, _Py_hashtable_t *intern_filenames)
+{
+    PyObject *filename_obj;
+    if (intern_filenames != NULL) {
+        filename_obj = _Py_hashtable_get(intern_filenames, filename);
+        if (filename_obj != NULL) {
+            return Py_NewRef(filename_obj);
+        }
+    }
+
+    if (filename == UNKNOWN_FILENAME) {
+        filename_obj = Py_NewRef(&_Py_STR(anon_unknown));
+    }
+    else {
+        filename_obj = PyUnicode_DecodeUTF8(filename,
+                                            (Py_ssize_t)strlen(filename),
+                                            "surrogatepass");
+        if (filename_obj == NULL) {
+            return NULL;
+        }
+    }
+
+    if (intern_filenames != NULL) {
+        if (_Py_hashtable_set(intern_filenames, filename, filename_obj) < 0) {
+            Py_DECREF(filename_obj);
+            PyErr_NoMemory();
+            return NULL;
+        }
+        /* intern_filenames keeps a new reference to filename_obj */
+        Py_INCREF(filename_obj);
+    }
+    return filename_obj;
+}
+
+
+static PyObject*
+frame_to_pyobject(frame_t *frame, _Py_hashtable_t *intern_filenames)
 {
     assert(get_reentrant());
 
+    PyObject *filename_obj = filename_to_pyobject(frame->filename,
+                                                  intern_filenames);
+    if (filename_obj == NULL) {
+        return NULL;
+    }
+
     PyObject *frame_obj = PyTuple_New(2);
     if (frame_obj == NULL) {
+        Py_DECREF(filename_obj);
         return NULL;
     }
 
-    PyTuple_SET_ITEM(frame_obj, 0, Py_NewRef(frame->filename));
+    PyTuple_SET_ITEM(frame_obj, 0, filename_obj);
 
     PyObject *lineno_obj = PyLong_FromUnsignedLong(frame->lineno);
     if (lineno_obj == NULL) {
@@ -922,7 +1009,8 @@ frame_to_pyobject(frame_t *frame)
 
 
 static PyObject*
-traceback_to_pyobject(traceback_t *traceback, _Py_hashtable_t *intern_table)
+traceback_to_pyobject(traceback_t *traceback, _Py_hashtable_t *intern_table,
+                      _Py_hashtable_t *intern_filenames)
 {
     PyObject *frames;
     if (intern_table != NULL) {
@@ -938,7 +1026,8 @@ traceback_to_pyobject(traceback_t *traceback, 
_Py_hashtable_t *intern_table)
     }
 
     for (int i=0; i < traceback->nframe; i++) {
-        PyObject *frame = frame_to_pyobject(&traceback->frames[i]);
+        PyObject *frame = frame_to_pyobject(&traceback->frames[i],
+                                            intern_filenames);
         if (frame == NULL) {
             Py_DECREF(frames);
             return NULL;
@@ -961,7 +1050,8 @@ traceback_to_pyobject(traceback_t *traceback, 
_Py_hashtable_t *intern_table)
 
 static PyObject*
 trace_to_pyobject(unsigned int domain, const trace_t *trace,
-                  _Py_hashtable_t *intern_tracebacks)
+                  _Py_hashtable_t *intern_tracebacks,
+                  _Py_hashtable_t *intern_filenames)
 {
     assert(get_reentrant());
 
@@ -984,7 +1074,8 @@ trace_to_pyobject(unsigned int domain, const trace_t 
*trace,
     }
     PyTuple_SET_ITEM(trace_obj, 1, obj);
 
-    obj = traceback_to_pyobject(trace->traceback, intern_tracebacks);
+    obj = traceback_to_pyobject(trace->traceback, intern_tracebacks,
+                                intern_filenames);
     if (obj == NULL) {
         Py_DECREF(trace_obj);
         return NULL;
@@ -1006,6 +1097,7 @@ typedef struct {
     _Py_hashtable_t *traces;
     _Py_hashtable_t *domains;
     _Py_hashtable_t *tracebacks;
+    _Py_hashtable_t *filenames;
     PyObject *list;
     unsigned int domain;
 } get_traces_t;
@@ -1100,7 +1192,8 @@ tracemalloc_get_traces_fill(_Py_hashtable_t *traces,
     const trace_t *trace = (const trace_t *)value;
 
     PyObject *tuple = trace_to_pyobject(get_traces->domain, trace,
-                                        get_traces->tracebacks);
+                                        get_traces->tracebacks,
+                                        get_traces->filenames);
     if (tuple == NULL) {
         return 1;
     }
@@ -1160,11 +1253,41 @@ tracemalloc_get_traceback_unlocked(unsigned int domain, 
uintptr_t ptr)
 
 #define PUTS(fd, str) (void)_Py_write_noraise(fd, str, (int)strlen(str))
 
+/* Dump an interned filename: write printable ASCII characters as-is,
+   escape the other bytes. The function is signal-safe. */
+static void
+_PyMem_DumpFilename(int fd, const char *filename)
+{
+    const size_t max_length = 500;
+    size_t length = strlen(filename);
+    int truncated = 0;
+    if (length > max_length) {
+        length = max_length;
+        truncated = 1;
+    }
+
+    for (size_t i = 0; i < length; i++) {
+        unsigned char ch = (unsigned char)filename[i];
+        if (' ' <= ch && ch <= 126) {
+            /* printable ASCII character */
+            char c = (char)ch;
+            (void)_Py_write_noraise(fd, &c, 1);
+        }
+        else {
+            PUTS(fd, "\\x");
+            _Py_DumpHexadecimal(fd, ch, 2);
+        }
+    }
+    if (truncated) {
+        PUTS(fd, "...");
+    }
+}
+
 static void
 _PyMem_DumpFrame(int fd, frame_t * frame)
 {
     PUTS(fd, "  File \"");
-    _Py_DumpASCII(fd, frame->filename);
+    _PyMem_DumpFilename(fd, frame->filename);
     PUTS(fd, "\", line ");
     _Py_DumpDecimal(fd, frame->lineno);
     PUTS(fd, "\n");
@@ -1221,7 +1344,6 @@ PyTraceMalloc_Track(unsigned int domain, uintptr_t ptr,
         /* tracemalloc is not tracing: do nothing */
         return -2;
     }
-    PyGILState_STATE gil_state = PyGILState_Ensure();
     TABLES_LOCK();
 
     int result;
@@ -1234,7 +1356,6 @@ PyTraceMalloc_Track(unsigned int domain, uintptr_t ptr,
     }
 
     TABLES_UNLOCK();
-    PyGILState_Release(gil_state);
     return result;
 }
 
@@ -1324,7 +1445,7 @@ _PyTraceMalloc_GetTraceback(unsigned int domain, 
uintptr_t ptr)
     PyObject *result;
     if (traceback) {
         set_reentrant(1);
-        result = traceback_to_pyobject(traceback, NULL);
+        result = traceback_to_pyobject(traceback, NULL, NULL);
         set_reentrant(0);
     }
     else {
@@ -1365,6 +1486,7 @@ _PyTraceMalloc_GetTraces(void)
     get_traces.traces = NULL;
     get_traces.domains = NULL;
     get_traces.tracebacks = NULL;
+    get_traces.filenames = NULL;
     get_traces.list = PyList_New(0);
     if (get_traces.list == NULL) {
         goto finally;
@@ -1383,6 +1505,15 @@ _PyTraceMalloc_GetTraces(void)
         goto no_memory;
     }
 
+    /* the filename hash table is used temporarily to share filename
+       str objects between tracebacks */
+    get_traces.filenames = hashtable_new(_Py_hashtable_hash_ptr,
+                                         _Py_hashtable_compare_direct,
+                                         NULL, tracemalloc_pyobject_decref);
+    if (get_traces.filenames == NULL) {
+        goto no_memory;
+    }
+
     // Copy all traces so tracemalloc_get_traces_fill() doesn't have to disable
     // temporarily tracemalloc which would impact other threads and so would
     // miss allocations while get_traces() is called.
@@ -1424,6 +1555,9 @@ _PyTraceMalloc_GetTraces(void)
     if (get_traces.tracebacks != NULL) {
         _Py_hashtable_destroy(get_traces.tracebacks);
     }
+    if (get_traces.filenames != NULL) {
+        _Py_hashtable_destroy(get_traces.filenames);
+    }
     if (get_traces.traces != NULL) {
         _Py_hashtable_destroy(get_traces.traces);
     }

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to