Skip to content

Commit 169e88a

Browse files
vstinnermcepl
authored andcommitted
bpo-40609: _tracemalloc allocates traces (pythonGH-20064)
Rewrite _tracemalloc to store "trace_t*" rather than directly "trace_t" in traces hash tables. Traces are now allocated on the heap memory, outside the hash table. Add tracemalloc_copy_traces() and tracemalloc_copy_domains() helper functions. Remove _Py_hashtable_copy() function since there is no API to copy a key or a value. Remove also _Py_hashtable_delete() function which was commented.
1 parent fbead89 commit 169e88a

File tree

3 files changed

+192
-88
lines changed

3 files changed

+192
-88
lines changed

Modules/_tracemalloc.c

Lines changed: 192 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ static traceback_t *tracemalloc_traceback = NULL;
141141
Protected by the GIL */
142142
static _Py_hashtable_t *tracemalloc_tracebacks = NULL;
143143

144-
/* pointer (void*) => trace (trace_t).
144+
/* pointer (void*) => trace (trace_t*).
145145
Protected by TABLES_LOCK(). */
146146
static _Py_hashtable_t *tracemalloc_traces = NULL;
147147

@@ -516,14 +516,23 @@ traceback_new(void)
516516
}
517517

518518

519-
static int
520-
tracemalloc_use_domain_cb(_Py_hashtable_t *old_traces,
521-
_Py_hashtable_entry_t *entry, void *user_data)
519+
static void
520+
tracemalloc_destroy_trace_cb(_Py_hashtable_t *traces,
521+
_Py_hashtable_entry_t *entry)
522522
{
523-
return hashtable_new(sizeof(trace_t),
523+
trace_t *trace;
524+
_Py_HASHTABLE_ENTRY_READ_DATA(traces, entry, trace);
525+
raw_free(trace);
526+
}
527+
528+
529+
static _Py_hashtable_t*
530+
tracemalloc_create_traces_table(void)
531+
{
532+
return hashtable_new(sizeof(trace_t*),
524533
_Py_hashtable_hash_ptr,
525534
_Py_hashtable_compare_direct,
526-
NULL);
535+
tracemalloc_destroy_trace_cb);
527536
}
528537

529538
_Py_HASHTABLE_ENTRY_READ_KEY(old_traces, entry, ptr);
@@ -569,8 +578,13 @@ tracemalloc_remove_trace(_PyTraceMalloc_domain_t domain, uintptr_t ptr)
569578
return;
570579
}
571580

572-
assert(tracemalloc_traced_memory >= trace.size);
573-
tracemalloc_traced_memory -= trace.size;
581+
trace_t *trace;
582+
if (!_Py_HASHTABLE_POP(traces, TO_PTR(ptr), trace)) {
583+
return;
584+
}
585+
assert(tracemalloc_traced_memory >= trace->size);
586+
tracemalloc_traced_memory -= trace->size;
587+
raw_free(trace);
574588
}
575589

576590
#define REMOVE_TRACE(ptr) \
@@ -609,19 +623,24 @@ tracemalloc_add_trace(_PyTraceMalloc_domain_t domain, uintptr_t ptr,
609623
entry = _Py_HASHTABLE_GET_ENTRY(tracemalloc_traces, ptr);
610624
}
611625

626+
_Py_hashtable_entry_t* entry = _Py_HASHTABLE_GET_ENTRY(traces, ptr);
612627
if (entry != NULL) {
613628
/* the memory block is already tracked */
614-
_Py_HASHTABLE_ENTRY_READ_DATA(tracemalloc_traces, entry, trace);
615-
assert(tracemalloc_traced_memory >= trace.size);
616-
tracemalloc_traced_memory -= trace.size;
629+
trace_t *trace;
630+
_Py_HASHTABLE_ENTRY_READ_DATA(traces, entry, trace);
631+
assert(tracemalloc_traced_memory >= trace->size);
632+
tracemalloc_traced_memory -= trace->size;
617633

618-
trace.size = size;
619-
trace.traceback = traceback;
620-
_Py_HASHTABLE_ENTRY_WRITE_DATA(tracemalloc_traces, entry, trace);
634+
trace->size = size;
635+
trace->traceback = traceback;
621636
}
622637
else {
623-
trace.size = size;
624-
trace.traceback = traceback;
638+
trace_t *trace = raw_malloc(sizeof(trace_t));
639+
if (trace == NULL) {
640+
return -1;
641+
}
642+
trace->size = size;
643+
trace->traceback = traceback;
625644

626645
if (tracemalloc_config.use_domain) {
627646
res = _Py_HASHTABLE_SET(tracemalloc_traces, key, trace);
@@ -630,6 +649,7 @@ tracemalloc_add_trace(_PyTraceMalloc_domain_t domain, uintptr_t ptr,
630649
res = _Py_HASHTABLE_SET(tracemalloc_traces, ptr, trace);
631650
}
632651
if (res != 0) {
652+
raw_free(trace);
633653
return res;
634654
}
635655
}
@@ -1275,13 +1295,94 @@ typedef struct {
12751295
PyObject *list;
12761296
} get_traces_t;
12771297

1298+
1299+
static int
1300+
tracemalloc_copy_trace(_Py_hashtable_t *traces,
1301+
_Py_hashtable_entry_t *entry,
1302+
void *traces2_raw)
1303+
{
1304+
_Py_hashtable_t *traces2 = (_Py_hashtable_t *)traces2_raw;
1305+
1306+
trace_t *trace;
1307+
_Py_HASHTABLE_ENTRY_READ_DATA(traces, entry, trace);
1308+
1309+
trace_t *trace2 = raw_malloc(sizeof(trace_t));
1310+
if (traces2 == NULL) {
1311+
return -1;
1312+
}
1313+
*trace2 = *trace;
1314+
if (_Py_HASHTABLE_SET(traces2, entry->key, trace2) < 0) {
1315+
raw_free(trace2);
1316+
return -1;
1317+
}
1318+
return 0;
1319+
}
1320+
1321+
1322+
static _Py_hashtable_t*
1323+
tracemalloc_copy_traces(_Py_hashtable_t *traces)
1324+
{
1325+
_Py_hashtable_t *traces2 = tracemalloc_create_traces_table();
1326+
if (traces2 == NULL) {
1327+
return NULL;
1328+
}
1329+
1330+
int err = _Py_hashtable_foreach(traces,
1331+
tracemalloc_copy_trace,
1332+
traces2);
1333+
if (err) {
1334+
_Py_hashtable_destroy(traces2);
1335+
return NULL;
1336+
}
1337+
return traces2;
1338+
}
1339+
1340+
1341+
static int
1342+
tracemalloc_copy_domain(_Py_hashtable_t *domains,
1343+
_Py_hashtable_entry_t *entry,
1344+
void *domains2_raw)
1345+
{
1346+
_Py_hashtable_t *domains2 = (_Py_hashtable_t *)domains2_raw;
1347+
1348+
unsigned int domain = (unsigned int)FROM_PTR(entry->key);
1349+
_Py_hashtable_t *traces;
1350+
_Py_HASHTABLE_ENTRY_READ_DATA(domains, entry, traces);
1351+
1352+
_Py_hashtable_t *traces2 = tracemalloc_copy_traces(traces);
1353+
if (_Py_HASHTABLE_SET(domains2, TO_PTR(domain), traces2) < 0) {
1354+
_Py_hashtable_destroy(traces2);
1355+
return -1;
1356+
}
1357+
return 0;
1358+
}
1359+
1360+
1361+
static _Py_hashtable_t*
1362+
tracemalloc_copy_domains(_Py_hashtable_t *domains)
1363+
{
1364+
_Py_hashtable_t *domains2 = tracemalloc_create_domains_table();
1365+
if (domains2 == NULL) {
1366+
return NULL;
1367+
}
1368+
1369+
int err = _Py_hashtable_foreach(domains,
1370+
tracemalloc_copy_domain,
1371+
domains2);
1372+
if (err) {
1373+
_Py_hashtable_destroy(domains2);
1374+
return NULL;
1375+
}
1376+
return domains2;
1377+
}
1378+
1379+
12781380
static int
12791381
tracemalloc_get_traces_fill(_Py_hashtable_t *traces, _Py_hashtable_entry_t *entry,
12801382
void *user_data)
12811383
{
12821384
get_traces_t *get_traces = user_data;
1283-
_PyTraceMalloc_domain_t domain;
1284-
trace_t trace;
1385+
trace_t *trace;
12851386
PyObject *tracemalloc_obj;
12861387
int res;
12871388

@@ -1295,7 +1396,7 @@ tracemalloc_get_traces_fill(_Py_hashtable_t *traces, _Py_hashtable_entry_t *entr
12951396
}
12961397
_Py_HASHTABLE_ENTRY_READ_DATA(traces, entry, trace);
12971398

1298-
tracemalloc_obj = trace_to_pyobject(domain, &trace, get_traces->tracebacks);
1399+
tracemalloc_obj = trace_to_pyobject(get_traces->domain, trace, get_traces->tracebacks);
12991400
if (tracemalloc_obj == NULL)
13001401
return 1;
13011402

@@ -1368,22 +1469,37 @@ py_tracemalloc_get_traces(PyObject *self, PyObject *obj)
13681469
_Py_hashtable_compare_direct,
13691470
tracemalloc_pyobject_decref_cb);
13701471
if (get_traces.tracebacks == NULL) {
1371-
PyErr_NoMemory();
1372-
goto error;
1472+
goto no_memory;
13731473
}
13741474

1475+
// Copy all traces so tracemalloc_get_traces_fill() doesn't have to disable
1476+
// temporarily tracemalloc which would impact other threads and so would
1477+
// miss allocations while get_traces() is called.
13751478
TABLES_LOCK();
1376-
get_traces.traces = _Py_hashtable_copy(tracemalloc_traces);
1479+
get_traces.traces = tracemalloc_copy_traces(tracemalloc_traces);
13771480
TABLES_UNLOCK();
13781481

13791482
if (get_traces.traces == NULL) {
1380-
PyErr_NoMemory();
1381-
goto error;
1483+
goto no_memory;
1484+
}
1485+
1486+
TABLES_LOCK();
1487+
get_traces.domains = tracemalloc_copy_domains(tracemalloc_domains);
1488+
TABLES_UNLOCK();
1489+
1490+
if (get_traces.domains == NULL) {
1491+
goto no_memory;
13821492
}
13831493

13841494
set_reentrant(1);
1385-
err = _Py_hashtable_foreach(get_traces.traces,
1386-
tracemalloc_get_traces_fill, &get_traces);
1495+
int err = _Py_hashtable_foreach(get_traces.traces,
1496+
tracemalloc_get_traces_fill,
1497+
&get_traces);
1498+
if (!err) {
1499+
err = _Py_hashtable_foreach(get_traces.domains,
1500+
tracemalloc_get_traces_domain,
1501+
&get_traces);
1502+
}
13871503
set_reentrant(0);
13881504
if (err)
13891505
goto error;
@@ -1408,7 +1524,7 @@ py_tracemalloc_get_traces(PyObject *self, PyObject *obj)
14081524
static traceback_t*
14091525
tracemalloc_get_traceback(_PyTraceMalloc_domain_t domain, uintptr_t ptr)
14101526
{
1411-
trace_t trace;
1527+
trace_t *trace;
14121528
int found;
14131529

14141530
if (!tracemalloc_config.tracing)
@@ -1424,10 +1540,11 @@ tracemalloc_get_traceback(_PyTraceMalloc_domain_t domain, uintptr_t ptr)
14241540
}
14251541
TABLES_UNLOCK();
14261542

1427-
if (!found)
1543+
if (!found) {
14281544
return NULL;
1545+
}
14291546

1430-
return trace.traceback;
1547+
return trace->traceback;
14311548
}
14321549

14331550

@@ -1803,6 +1920,52 @@ _PyTraceMalloc_Untrack(_PyTraceMalloc_domain_t domain, uintptr_t ptr)
18031920
}
18041921

18051922

1923+
/* If the object memory block is already traced, update its trace
1924+
with the current Python traceback.
1925+
1926+
Do nothing if tracemalloc is not tracing memory allocations
1927+
or if the object memory block is not already traced. */
1928+
int
1929+
_PyTraceMalloc_NewReference(PyObject *op)
1930+
{
1931+
assert(PyGILState_Check());
1932+
1933+
if (!_Py_tracemalloc_config.tracing) {
1934+
/* tracemalloc is not tracing: do nothing */
1935+
return -1;
1936+
}
1937+
1938+
uintptr_t ptr;
1939+
PyTypeObject *type = Py_TYPE(op);
1940+
if (PyType_IS_GC(type)) {
1941+
ptr = (uintptr_t)((char *)op - sizeof(PyGC_Head));
1942+
}
1943+
else {
1944+
ptr = (uintptr_t)op;
1945+
}
1946+
1947+
int res = -1;
1948+
1949+
TABLES_LOCK();
1950+
_Py_hashtable_entry_t* entry;
1951+
entry = _Py_HASHTABLE_GET_ENTRY(tracemalloc_traces, ptr);
1952+
if (entry != NULL) {
1953+
/* update the traceback of the memory block */
1954+
traceback_t *traceback = traceback_new();
1955+
if (traceback != NULL) {
1956+
trace_t *trace;
1957+
_Py_HASHTABLE_ENTRY_READ_DATA(tracemalloc_traces, entry, trace);
1958+
trace->traceback = traceback;
1959+
res = 0;
1960+
}
1961+
}
1962+
/* else: cannot track the object, its memory block size is unknown */
1963+
TABLES_UNLOCK();
1964+
1965+
return res;
1966+
}
1967+
1968+
18061969
PyObject*
18071970
_PyTraceMalloc_GetTraceback(_PyTraceMalloc_domain_t domain, uintptr_t ptr)
18081971
{

Modules/hashtable.h

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,6 @@ typedef struct {
4040
sizeof(DATA)); \
4141
} while (0)
4242

43-
#define _Py_HASHTABLE_ENTRY_WRITE_DATA(TABLE, ENTRY, DATA) \
44-
do { \
45-
assert(sizeof(DATA) == (TABLE)->data_size); \
46-
memcpy((void *)_Py_HASHTABLE_ENTRY_PDATA(ENTRY), \
47-
&(DATA), sizeof(DATA)); \
48-
} while (0)
49-
5043

5144
/* _Py_hashtable: prototypes */
5245

@@ -113,9 +106,6 @@ PyAPI_FUNC(_Py_hashtable_t *) _Py_hashtable_new_full(
113106

114107
PyAPI_FUNC(void) _Py_hashtable_destroy(_Py_hashtable_t *ht);
115108

116-
/* Return a copy of the hash table */
117-
PyAPI_FUNC(_Py_hashtable_t *) _Py_hashtable_copy(_Py_hashtable_t *src);
118-
119109
PyAPI_FUNC(void) _Py_hashtable_clear(_Py_hashtable_t *ht);
120110

121111
typedef int (*_Py_hashtable_foreach_func) (_Py_hashtable_t *ht,

0 commit comments

Comments
 (0)