Skip to content

Commit d95bd42

Browse files
authored
bpo-40609: _tracemalloc allocates traces (GH-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 2d0a3d6 commit d95bd42

File tree

3 files changed

+117
-102
lines changed

3 files changed

+117
-102
lines changed

Include/internal/pycore_hashtable.h

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,6 @@ typedef struct {
4545
sizeof(DATA)); \
4646
} while (0)
4747

48-
#define _Py_HASHTABLE_ENTRY_WRITE_DATA(TABLE, ENTRY, DATA) \
49-
do { \
50-
assert(sizeof(DATA) == (TABLE)->data_size); \
51-
memcpy((void *)_Py_HASHTABLE_ENTRY_PDATA(ENTRY), \
52-
&(DATA), sizeof(DATA)); \
53-
} while (0)
54-
5548

5649
/* _Py_hashtable: prototypes */
5750

@@ -118,9 +111,6 @@ PyAPI_FUNC(_Py_hashtable_t *) _Py_hashtable_new_full(
118111

119112
PyAPI_FUNC(void) _Py_hashtable_destroy(_Py_hashtable_t *ht);
120113

121-
/* Return a copy of the hash table */
122-
PyAPI_FUNC(_Py_hashtable_t *) _Py_hashtable_copy(_Py_hashtable_t *src);
123-
124114
PyAPI_FUNC(void) _Py_hashtable_clear(_Py_hashtable_t *ht);
125115

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

Modules/_tracemalloc.c

Lines changed: 117 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ static traceback_t *tracemalloc_traceback = NULL;
122122
Protected by the GIL */
123123
static _Py_hashtable_t *tracemalloc_tracebacks = NULL;
124124

125-
/* pointer (void*) => trace (trace_t).
125+
/* pointer (void*) => trace (trace_t*).
126126
Protected by TABLES_LOCK(). */
127127
static _Py_hashtable_t *tracemalloc_traces = NULL;
128128

@@ -467,13 +467,23 @@ traceback_new(void)
467467
}
468468

469469

470+
static void
471+
tracemalloc_destroy_trace_cb(_Py_hashtable_t *traces,
472+
_Py_hashtable_entry_t *entry)
473+
{
474+
trace_t *trace;
475+
_Py_HASHTABLE_ENTRY_READ_DATA(traces, entry, trace);
476+
raw_free(trace);
477+
}
478+
479+
470480
static _Py_hashtable_t*
471481
tracemalloc_create_traces_table(void)
472482
{
473-
return hashtable_new(sizeof(trace_t),
483+
return hashtable_new(sizeof(trace_t*),
474484
_Py_hashtable_hash_ptr,
475485
_Py_hashtable_compare_direct,
476-
NULL);
486+
tracemalloc_destroy_trace_cb);
477487
}
478488

479489

@@ -528,12 +538,13 @@ tracemalloc_remove_trace(unsigned int domain, uintptr_t ptr)
528538
return;
529539
}
530540

531-
trace_t trace;
541+
trace_t *trace;
532542
if (!_Py_HASHTABLE_POP(traces, TO_PTR(ptr), trace)) {
533543
return;
534544
}
535-
assert(tracemalloc_traced_memory >= trace.size);
536-
tracemalloc_traced_memory -= trace.size;
545+
assert(tracemalloc_traced_memory >= trace->size);
546+
tracemalloc_traced_memory -= trace->size;
547+
raw_free(trace);
537548
}
538549

539550
#define REMOVE_TRACE(ptr) \
@@ -565,23 +576,27 @@ tracemalloc_add_trace(unsigned int domain, uintptr_t ptr,
565576
}
566577

567578
_Py_hashtable_entry_t* entry = _Py_HASHTABLE_GET_ENTRY(traces, ptr);
568-
trace_t trace;
569579
if (entry != NULL) {
570580
/* the memory block is already tracked */
581+
trace_t *trace;
571582
_Py_HASHTABLE_ENTRY_READ_DATA(traces, entry, trace);
572-
assert(tracemalloc_traced_memory >= trace.size);
573-
tracemalloc_traced_memory -= trace.size;
583+
assert(tracemalloc_traced_memory >= trace->size);
584+
tracemalloc_traced_memory -= trace->size;
574585

575-
trace.size = size;
576-
trace.traceback = traceback;
577-
_Py_HASHTABLE_ENTRY_WRITE_DATA(traces, entry, trace);
586+
trace->size = size;
587+
trace->traceback = traceback;
578588
}
579589
else {
580-
trace.size = size;
581-
trace.traceback = traceback;
590+
trace_t *trace = raw_malloc(sizeof(trace_t));
591+
if (trace == NULL) {
592+
return -1;
593+
}
594+
trace->size = size;
595+
trace->traceback = traceback;
582596

583597
int res = _Py_HASHTABLE_SET(traces, TO_PTR(ptr), trace);
584598
if (res != 0) {
599+
raw_free(trace);
585600
return res;
586601
}
587602
}
@@ -1225,38 +1240,100 @@ typedef struct {
12251240
unsigned int domain;
12261241
} get_traces_t;
12271242

1243+
12281244
static int
1229-
tracemalloc_get_traces_copy_domain(_Py_hashtable_t *domains,
1230-
_Py_hashtable_entry_t *entry,
1231-
void *user_data)
1245+
tracemalloc_copy_trace(_Py_hashtable_t *traces,
1246+
_Py_hashtable_entry_t *entry,
1247+
void *traces2_raw)
12321248
{
1233-
get_traces_t *get_traces = user_data;
1249+
_Py_hashtable_t *traces2 = (_Py_hashtable_t *)traces2_raw;
1250+
1251+
trace_t *trace;
1252+
_Py_HASHTABLE_ENTRY_READ_DATA(traces, entry, trace);
1253+
1254+
trace_t *trace2 = raw_malloc(sizeof(trace_t));
1255+
if (traces2 == NULL) {
1256+
return -1;
1257+
}
1258+
*trace2 = *trace;
1259+
if (_Py_HASHTABLE_SET(traces2, entry->key, trace2) < 0) {
1260+
raw_free(trace2);
1261+
return -1;
1262+
}
1263+
return 0;
1264+
}
1265+
1266+
1267+
static _Py_hashtable_t*
1268+
tracemalloc_copy_traces(_Py_hashtable_t *traces)
1269+
{
1270+
_Py_hashtable_t *traces2 = tracemalloc_create_traces_table();
1271+
if (traces2 == NULL) {
1272+
return NULL;
1273+
}
1274+
1275+
int err = _Py_hashtable_foreach(traces,
1276+
tracemalloc_copy_trace,
1277+
traces2);
1278+
if (err) {
1279+
_Py_hashtable_destroy(traces2);
1280+
return NULL;
1281+
}
1282+
return traces2;
1283+
}
1284+
1285+
1286+
static int
1287+
tracemalloc_copy_domain(_Py_hashtable_t *domains,
1288+
_Py_hashtable_entry_t *entry,
1289+
void *domains2_raw)
1290+
{
1291+
_Py_hashtable_t *domains2 = (_Py_hashtable_t *)domains2_raw;
12341292

12351293
unsigned int domain = (unsigned int)FROM_PTR(entry->key);
12361294
_Py_hashtable_t *traces;
12371295
_Py_HASHTABLE_ENTRY_READ_DATA(domains, entry, traces);
12381296

1239-
_Py_hashtable_t *traces2 = _Py_hashtable_copy(traces);
1240-
if (_Py_HASHTABLE_SET(get_traces->domains, TO_PTR(domain), traces2) < 0) {
1297+
_Py_hashtable_t *traces2 = tracemalloc_copy_traces(traces);
1298+
if (_Py_HASHTABLE_SET(domains2, TO_PTR(domain), traces2) < 0) {
12411299
_Py_hashtable_destroy(traces2);
12421300
return -1;
12431301
}
12441302
return 0;
12451303
}
12461304

12471305

1306+
static _Py_hashtable_t*
1307+
tracemalloc_copy_domains(_Py_hashtable_t *domains)
1308+
{
1309+
_Py_hashtable_t *domains2 = tracemalloc_create_domains_table();
1310+
if (domains2 == NULL) {
1311+
return NULL;
1312+
}
1313+
1314+
int err = _Py_hashtable_foreach(domains,
1315+
tracemalloc_copy_domain,
1316+
domains2);
1317+
if (err) {
1318+
_Py_hashtable_destroy(domains2);
1319+
return NULL;
1320+
}
1321+
return domains2;
1322+
}
1323+
1324+
12481325
static int
12491326
tracemalloc_get_traces_fill(_Py_hashtable_t *traces, _Py_hashtable_entry_t *entry,
12501327
void *user_data)
12511328
{
12521329
get_traces_t *get_traces = user_data;
1253-
trace_t trace;
1330+
trace_t *trace;
12541331
PyObject *tracemalloc_obj;
12551332
int res;
12561333

12571334
_Py_HASHTABLE_ENTRY_READ_DATA(traces, entry, trace);
12581335

1259-
tracemalloc_obj = trace_to_pyobject(get_traces->domain, &trace, get_traces->tracebacks);
1336+
tracemalloc_obj = trace_to_pyobject(get_traces->domain, trace, get_traces->tracebacks);
12601337
if (tracemalloc_obj == NULL)
12611338
return 1;
12621339

@@ -1335,37 +1412,34 @@ _tracemalloc__get_traces_impl(PyObject *module)
13351412
goto no_memory;
13361413
}
13371414

1338-
get_traces.domains = tracemalloc_create_domains_table();
1339-
if (get_traces.domains == NULL) {
1340-
goto no_memory;
1341-
}
1342-
1343-
int err;
1344-
13451415
// Copy all traces so tracemalloc_get_traces_fill() doesn't have to disable
13461416
// temporarily tracemalloc which would impact other threads and so would
13471417
// miss allocations while get_traces() is called.
13481418
TABLES_LOCK();
1349-
get_traces.traces = _Py_hashtable_copy(tracemalloc_traces);
1350-
err = _Py_hashtable_foreach(tracemalloc_domains,
1351-
tracemalloc_get_traces_copy_domain,
1352-
&get_traces);
1419+
get_traces.traces = tracemalloc_copy_traces(tracemalloc_traces);
13531420
TABLES_UNLOCK();
13541421

13551422
if (get_traces.traces == NULL) {
13561423
goto no_memory;
13571424
}
1358-
if (err) {
1425+
1426+
TABLES_LOCK();
1427+
get_traces.domains = tracemalloc_copy_domains(tracemalloc_domains);
1428+
TABLES_UNLOCK();
1429+
1430+
if (get_traces.domains == NULL) {
13591431
goto no_memory;
13601432
}
13611433

13621434
// Convert traces to a list of tuples
13631435
set_reentrant(1);
1364-
err = _Py_hashtable_foreach(get_traces.traces,
1365-
tracemalloc_get_traces_fill, &get_traces);
1436+
int err = _Py_hashtable_foreach(get_traces.traces,
1437+
tracemalloc_get_traces_fill,
1438+
&get_traces);
13661439
if (!err) {
13671440
err = _Py_hashtable_foreach(get_traces.domains,
1368-
tracemalloc_get_traces_domain, &get_traces);
1441+
tracemalloc_get_traces_domain,
1442+
&get_traces);
13691443
}
13701444
set_reentrant(0);
13711445
if (err) {
@@ -1398,7 +1472,7 @@ _tracemalloc__get_traces_impl(PyObject *module)
13981472
static traceback_t*
13991473
tracemalloc_get_traceback(unsigned int domain, uintptr_t ptr)
14001474
{
1401-
trace_t trace;
1475+
trace_t *trace;
14021476
int found;
14031477

14041478
if (!_Py_tracemalloc_config.tracing)
@@ -1414,10 +1488,11 @@ tracemalloc_get_traceback(unsigned int domain, uintptr_t ptr)
14141488
}
14151489
TABLES_UNLOCK();
14161490

1417-
if (!found)
1491+
if (!found) {
14181492
return NULL;
1493+
}
14191494

1420-
return trace.traceback;
1495+
return trace->traceback;
14211496
}
14221497

14231498

@@ -1758,10 +1833,9 @@ _PyTraceMalloc_NewReference(PyObject *op)
17581833
/* update the traceback of the memory block */
17591834
traceback_t *traceback = traceback_new();
17601835
if (traceback != NULL) {
1761-
trace_t trace;
1836+
trace_t *trace;
17621837
_Py_HASHTABLE_ENTRY_READ_DATA(tracemalloc_traces, entry, trace);
1763-
trace.traceback = traceback;
1764-
_Py_HASHTABLE_ENTRY_WRITE_DATA(tracemalloc_traces, entry, trace);
1838+
trace->traceback = traceback;
17651839
res = 0;
17661840
}
17671841
}

Python/hashtable.c

Lines changed: 0 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -350,21 +350,6 @@ _Py_hashtable_pop(_Py_hashtable_t *ht, const void *key,
350350
}
351351

352352

353-
/* Code commented since the function is not needed in Python */
354-
#if 0
355-
void
356-
_Py_hashtable_delete(_Py_hashtable_t *ht, size_t const void *key)
357-
{
358-
#ifndef NDEBUG
359-
int found = _Py_hashtable_pop_entry(ht, key, NULL, 0);
360-
assert(found);
361-
#else
362-
(void)_Py_hashtable_pop_entry(ht, key, NULL, 0);
363-
#endif
364-
}
365-
#endif
366-
367-
368353
int
369354
_Py_hashtable_foreach(_Py_hashtable_t *ht,
370355
_Py_hashtable_foreach_func func,
@@ -538,37 +523,3 @@ _Py_hashtable_destroy(_Py_hashtable_t *ht)
538523
ht->alloc.free(ht->buckets);
539524
ht->alloc.free(ht);
540525
}
541-
542-
543-
_Py_hashtable_t *
544-
_Py_hashtable_copy(_Py_hashtable_t *src)
545-
{
546-
const size_t data_size = src->data_size;
547-
_Py_hashtable_t *dst;
548-
_Py_hashtable_entry_t *entry;
549-
size_t bucket;
550-
int err;
551-
552-
dst = _Py_hashtable_new_full(data_size, src->num_buckets,
553-
src->hash_func,
554-
src->compare_func,
555-
src->key_destroy_func,
556-
src->value_destroy_func,
557-
&src->alloc);
558-
if (dst == NULL)
559-
return NULL;
560-
561-
for (bucket=0; bucket < src->num_buckets; bucket++) {
562-
entry = TABLE_HEAD(src, bucket);
563-
for (; entry; entry = ENTRY_NEXT(entry)) {
564-
const void *key = entry->key;
565-
const void *pdata = _Py_HASHTABLE_ENTRY_PDATA(entry);
566-
err = _Py_hashtable_set(dst, key, data_size, pdata);
567-
if (err) {
568-
_Py_hashtable_destroy(dst);
569-
return NULL;
570-
}
571-
}
572-
}
573-
return dst;
574-
}

0 commit comments

Comments
 (0)