Skip to content

Isolate the tracemalloc Module Between Interpreters #101520

Description

@ericsnowcurrently

(See #100227.)

Currently the tracemalloc module has some state in _PyRuntimeState, including objects, which is shared by all interpreters. Interpreters should be isolated from each other, for a variety of reasons (including the possibility of a per-interpreter GIL). Isolating the module will involve moving some of the state to PyInterpreterState (and, under per-interpreter GIL, guarding other state with a global lock).


Analysis:

(expand)

Allocators

The module installs a custom allocator (using the PEP 445 API
(docs).

(expand)

the allocator functions:

domainfuncwrapswraps (reentrant)actually wraps

-

tracemalloc_alloc()<original malloc() or calloc()><---

-

tracemalloc_realloc()<original realloc() or free()><---

-

tracemalloc_raw_alloc()tracemalloc_alloc()*<original malloc() or calloc()>

-

tracemalloc_alloc_gil()tracemalloc_alloc()<original malloc() or calloc()>
raw
tracemalloc_raw_malloc()tracemalloc_alloc()<original malloc()>tracemalloc_raw_alloc()
tracemalloc_raw_calloc()tracemalloc_alloc()<original calloc()>tracemalloc_raw_alloc()
tracemalloc_raw_realloc()tracemalloc_realloc()*<original realloc()>
tracemalloc_free()<original free()><---
mem
obj
tracemalloc_malloc_gil()tracemalloc_alloc_gil()<---
tracemalloc_calloc_gil()tracemalloc_alloc_gil()<---
tracemalloc_realloc_gil()tracemalloc_realloc()<original realloc()>
tracemalloc_free()<original free()><---

* Note that tracemalloc_raw_alloc() wraps the tracemalloc_alloc() call
with PyGILState_Ensure()/PyGILState_Release().
Likewise for tracemalloc_raw_realloc() where it calls tracemalloc_realloc().
In no other case does an allocator function use the GILState API for any calls.

State

Fields

(expand)

https://github.com/python/cpython/blob/main/Include/internal/pycore_tracemalloc.h#L57-L107
https://github.com/python/cpython/blob/main/Include/internal/pycore_runtime.h#L147

raw

struct
#ifdef__GNUC__
__attribute__((packed))
#endif
tracemalloc_frame {
/* filename cannot be NULL: "<unknown>" is used if the Python frame
filename is NULL */
PyObject*filename;
unsigned intlineno;
};
#ifdef_MSC_VER
#pragma pack(pop)
#endif

structtracemalloc_traceback {
Py_uhash_thash;
/* Number of frames stored */
uint16_tnframe;
/* Total number of frames the traceback had */
uint16_ttotal_nframe;
structtracemalloc_frameframes[1];
};

structtracemalloc_traceback {
Py_uhash_thash;
/* Number of frames stored */
uint16_tnframe;
/* Total number of frames the traceback had */
uint16_ttotal_nframe;
structtracemalloc_frameframes[1];
};
struct_tracemalloc_runtime_state {
struct_PyTraceMalloc_Configconfig;
/* Protected by the GIL */
struct {
PyMemAllocatorExmem;
PyMemAllocatorExraw;
PyMemAllocatorExobj;
} allocators;
#if defined(TRACE_RAW_MALLOC)
PyThread_type_locktables_lock;
#endif
/* Size in bytes of currently traced memory.
Protected by TABLES_LOCK(). */
size_ttraced_memory;
/* Peak size in bytes of traced memory.
Protected by TABLES_LOCK(). */
size_tpeak_traced_memory;
/* Hash table used as a set to intern filenames:
PyObject* => PyObject*.
Protected by the GIL */
_Py_hashtable_t*filenames;
/* Buffer to store a new traceback in traceback_new().
Protected by the GIL. */
structtracemalloc_traceback*traceback;
/* Hash table used as a set to intern tracebacks:
traceback_t* => traceback_t*
Protected by the GIL */
_Py_hashtable_t*tracebacks;
/* pointer (void*) => trace (trace_t*).
Protected by TABLES_LOCK(). */
_Py_hashtable_t*traces;
/* domain (unsigned int) => traces (_Py_hashtable_t).
Protected by TABLES_LOCK(). */
_Py_hashtable_t*domains;
structtracemalloc_tracebackempty_traceback;
Py_tss_treentrant_key;
};

typedefstructtracemalloc_frameframe_t;
typedefstructtracemalloc_tracebacktraceback_t;

/* Trace of a memory block */
typedefstruct {
/* Size of the memory block in bytes */
size_tsize;
/* Traceback where the memory block was allocated */
traceback_t*traceback;
} trace_t;

nametypeprotected by#ifdefnotes
configstruct _PyTraceMalloc_Config
. initializedenum {}GIL
. tracingboolGIL
. max_nframeintGIL
allocatorssee PEP 445 (docs)
. memPyMemAllocatorExGIL
. rawPyMemAllocatorExGIL
. objPyMemAllocatorExGIL
tables_lockPyThread_type_lockGILTRACE_RAW_MALLOC
traced_memorysize_ttables_lock
peak_traced_memorysize_ttables_lock
filenames_Py_hashtable_t *GILinterned; effectively a set of objects
tracebackstruct tracemalloc_traceback *GILa temporary buffer
tracebacks_Py_hashtable_t *GILinterned; effectively a set of traceback_t
traces_Py_hashtable_t *tables_lockvoid-ptr -> trace_t
domains_Py_hashtable_t *tables_lockdomain -> _Py_hashtable_t * (per-domain traces)
empty_tracebackstruct tracemalloc_traceback???
reentrant_keyPy_tss_t???

notes:

  • each frame in struct tracemalloc_traceback holds a filename object
  • traceback_t is a typedef for struct tracemalloc_traceback
  • frame_t is a typedef for struct tracemalloc_frame

hold objects:

  • filenames
  • traceback (filename in each frame)
  • tracebacks (filename in each frame of each traceback)
  • traces (filename in each frame of each traceback)
  • domains (filename in each frame of each traceback in each domain)

Usage

(expand)

simple:

namecontextgetset
config
. initializedlifecycletracemalloc_init()
tracemalloc_deinit()
tracemalloc_init()
tracemalloc_deinit()
. tracingmodule_tracemalloc_is_tracing_impl()
_tracemalloc__get_traces_impl()
_tracemalloc_clear_traces_impl()
_tracemalloc_get_traceback_limit_impl()
_tracemalloc_get_traced_memory_impl()
_tracemalloc_reset_peak_impl()
C-APIPyTraceMalloc_Track()
PyTraceMalloc_Untrack()
_PyTraceMalloc_NewReference()
_PyMem_DumpTraceback()
lifecycletracemalloc_start()
tracemalloc_stop()
tracemalloc_start()
tracemalloc_stop()
internaltracemalloc_get_traceback()
. max_nframemodule_tracemalloc_get_traceback_limit_impl()
lifecycletracemalloc_start()
internaltraceback_get_frames()
allocators
. memlifecycletracemalloc_start() +
tracemalloc_stop()
. rawlifecycletracemalloc_init() +
tracemalloc_start() +
tracemalloc_stop()
internalraw_malloc()
raw_free()
. objlifecycletracemalloc_start() +
tracemalloc_stop()
tables_lockmodule_tracemalloc__get_traces_impl() +
_tracemalloc_get_tracemalloc_memory_impl() +
_tracemalloc_get_traced_memory_impl() +
_tracemalloc_reset_peak_impl() +
C-APIPyTraceMalloc_Track() +
PyTraceMalloc_Untrack() +
_PyTraceMalloc_NewReference() +
lifecycletracemalloc_init()
tracemalloc_deinit()
tracemalloc_init() *
tracemalloc_deinit() *
allocatortracemalloc_alloc() +
tracemalloc_realloc() +
tracemalloc_free() +
tracemalloc_realloc_gil() +
tracemalloc_raw_realloc() +
internaltracemalloc_get_traceback() +
tracemalloc_clear_traces() +
traced_memorymodule_tracemalloc_get_traced_memory_impl()
_tracemalloc_reset_peak_impl()
internaltracemalloc_add_trace()tracemalloc_add_trace()
tracemalloc_remove_trace()
tracemalloc_clear_traces()
peak_traced_memorymodule_tracemalloc_get_traced_memory_impl()_tracemalloc_reset_peak_impl()
internaltracemalloc_add_trace()tracemalloc_add_trace()
tracemalloc_clear_traces()
filenamesmodule_tracemalloc_get_tracemalloc_memory_impl()
lifecycletracemalloc_init() *
internaltracemalloc_get_frame()
tracemalloc_clear_traces() +
tracebacklifecycletracemalloc_stop()tracemalloc_start() *
tracemalloc_stop() *
internaltraceback_new() +
tracebacksmodule_tracemalloc_get_tracemalloc_memory_impl()
lifecycletracemalloc_init() *
tracemalloc_deinit() +
internaltraceback_new() +
tracemalloc_clear_traces() +
tracesmodule_tracemalloc__get_traces_impl()
_tracemalloc_get_tracemalloc_memory_impl()
C-API_PyTraceMalloc_NewReference()
lifecycletracemalloc_deinit() +tracemalloc_init() *
internaltracemalloc_get_traces_table()
tracemalloc_add_trace() (indirect)
tracemalloc_remove_trace() (indirect)
tracemalloc_get_traceback() (indirect)
tracemalloc_clear_traces() +
domainsmodule_tracemalloc__get_traces_impl()
_tracemalloc_get_tracemalloc_memory_impl()
lifecycletracemalloc_deinit() +tracemalloc_init() *
internaltracemalloc_get_traces_table()
tracemalloc_remove_trace() (indirect)
tracemalloc_get_traceback() (indirect)
tracemalloc_clear_traces() +
tracemalloc_add_trace() +
empty_tracebacklifecycletracemalloc_init() +
internaltraceback_new()
reentrant_keylifecycletracemalloc_init() +
tracemalloc_deinit() +
allocatortracemalloc_alloc_gil() (indirect) +
tracemalloc_realloc_gil() (indirect) +
tracemalloc_raw_alloc() (indirect) +
tracemalloc_raw_realloc() (indirect) +
internalget_reentrant()
set_reentrant() +

* the function allocates/deallocates the value (see below)
+ the function mutates the value (see below)

simple (extraneous):

namecontextallocate/deallocateget (assert-only)
config.tracinginternaltracemalloc_add_trace()
tracemalloc_remove_trace()
tables_locklifecycletracemalloc_init()
tracemalloc_deinit()
traced_memoryinternaltracemalloc_remove_trace()
filenameslifecycletracemalloc_init()
tracebacklifecycletracemalloc_start()
tracemalloc_stop()
tracemalloc_start()
tracebackslifecycletracemalloc_init()
traceslifecycletracemalloc_init()
domainslifecycletracemalloc_init()

mutation of complex fields:

namecontextinitializefinalizeclearmodify
allocators.memlifecycletracemalloc_start()
allocators.rawlifecycletracemalloc_init()
tracemalloc_start()
allocators.objlifecycletracemalloc_start()
tables_lockmodule_tracemalloc__get_traces_impl()
_tracemalloc_get_tracemalloc_memory_impl()
_tracemalloc_get_traced_memory_impl()
_tracemalloc_reset_peak_impl()
C-APIPyTraceMalloc_Track()
PyTraceMalloc_Untrack()
_PyTraceMalloc_NewReference()
allocatortracemalloc_alloc()tracemalloc_alloc()
tracemalloc_realloc()
tracemalloc_free()
tracemalloc_realloc_gil()
tracemalloc_raw_realloc()
internaltracemalloc_clear_traces()
tracemalloc_get_traceback()
filenamesinternaltracemalloc_clear_traces()tracemalloc_get_frame()
tracemalloc_clear_traces()
lifecycletracemalloc_deinit()
tracebackinternaltraceback_new()
tracebackslifecycletracemalloc_deinit()
internaltracemalloc_clear_traces()traceback_new()
traceslifecycletracemalloc_deinit()
internaltracemalloc_clear_traces()
domainslifecycletracemalloc_deinit()
internaltracemalloc_clear_traces()tracemalloc_add_trace()
reentrant_keylifecycletracemalloc_init()tracemalloc_deinit()
internalset_reentrant()

indirection:

namecontextdirectindirect
allocators.rawlifecycletracemalloc_start()
tracemalloc_copy_trace()
raw_malloc()
tracemalloc_stop()raw_free()
internaltraceback_new()
tracemalloc_add_trace()
tracemalloc_copy_trace()
raw_malloc()
traceback_new()
tracemalloc_add_trace()
tracemalloc_remove_trace()
raw_free()
tracesinternaltracemalloc_add_trace()
tracemalloc_remove_trace()
tracemalloc_get_traceback()
tracemalloc_get_traces_table()
domainsinternaltracemalloc_add_trace()
tracemalloc_remove_trace()
tracemalloc_get_traceback()
tracemalloc_get_traces_table()
reentrant_keyallocatortracemalloc_alloc_gil()
tracemalloc_realloc_gil()
tracemalloc_raw_alloc()
tracemalloc_raw_realloc()
get_reentrant()
tracemalloc_alloc_gil()
tracemalloc_realloc_gil()
tracemalloc_raw_alloc()
tracemalloc_raw_realloc()
set_reentrant()

Linked PRs

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    Status
    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions