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.
This commit is contained in:
parent
2d0a3d682f
commit
d95bd4214c
|
@ -45,13 +45,6 @@ typedef struct {
|
||||||
sizeof(DATA)); \
|
sizeof(DATA)); \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
#define _Py_HASHTABLE_ENTRY_WRITE_DATA(TABLE, ENTRY, DATA) \
|
|
||||||
do { \
|
|
||||||
assert(sizeof(DATA) == (TABLE)->data_size); \
|
|
||||||
memcpy((void *)_Py_HASHTABLE_ENTRY_PDATA(ENTRY), \
|
|
||||||
&(DATA), sizeof(DATA)); \
|
|
||||||
} while (0)
|
|
||||||
|
|
||||||
|
|
||||||
/* _Py_hashtable: prototypes */
|
/* _Py_hashtable: prototypes */
|
||||||
|
|
||||||
|
@ -118,9 +111,6 @@ PyAPI_FUNC(_Py_hashtable_t *) _Py_hashtable_new_full(
|
||||||
|
|
||||||
PyAPI_FUNC(void) _Py_hashtable_destroy(_Py_hashtable_t *ht);
|
PyAPI_FUNC(void) _Py_hashtable_destroy(_Py_hashtable_t *ht);
|
||||||
|
|
||||||
/* Return a copy of the hash table */
|
|
||||||
PyAPI_FUNC(_Py_hashtable_t *) _Py_hashtable_copy(_Py_hashtable_t *src);
|
|
||||||
|
|
||||||
PyAPI_FUNC(void) _Py_hashtable_clear(_Py_hashtable_t *ht);
|
PyAPI_FUNC(void) _Py_hashtable_clear(_Py_hashtable_t *ht);
|
||||||
|
|
||||||
typedef int (*_Py_hashtable_foreach_func) (_Py_hashtable_t *ht,
|
typedef int (*_Py_hashtable_foreach_func) (_Py_hashtable_t *ht,
|
||||||
|
|
|
@ -122,7 +122,7 @@ static traceback_t *tracemalloc_traceback = NULL;
|
||||||
Protected by the GIL */
|
Protected by the GIL */
|
||||||
static _Py_hashtable_t *tracemalloc_tracebacks = NULL;
|
static _Py_hashtable_t *tracemalloc_tracebacks = NULL;
|
||||||
|
|
||||||
/* pointer (void*) => trace (trace_t).
|
/* pointer (void*) => trace (trace_t*).
|
||||||
Protected by TABLES_LOCK(). */
|
Protected by TABLES_LOCK(). */
|
||||||
static _Py_hashtable_t *tracemalloc_traces = NULL;
|
static _Py_hashtable_t *tracemalloc_traces = NULL;
|
||||||
|
|
||||||
|
@ -467,13 +467,23 @@ traceback_new(void)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
tracemalloc_destroy_trace_cb(_Py_hashtable_t *traces,
|
||||||
|
_Py_hashtable_entry_t *entry)
|
||||||
|
{
|
||||||
|
trace_t *trace;
|
||||||
|
_Py_HASHTABLE_ENTRY_READ_DATA(traces, entry, trace);
|
||||||
|
raw_free(trace);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static _Py_hashtable_t*
|
static _Py_hashtable_t*
|
||||||
tracemalloc_create_traces_table(void)
|
tracemalloc_create_traces_table(void)
|
||||||
{
|
{
|
||||||
return hashtable_new(sizeof(trace_t),
|
return hashtable_new(sizeof(trace_t*),
|
||||||
_Py_hashtable_hash_ptr,
|
_Py_hashtable_hash_ptr,
|
||||||
_Py_hashtable_compare_direct,
|
_Py_hashtable_compare_direct,
|
||||||
NULL);
|
tracemalloc_destroy_trace_cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -528,12 +538,13 @@ tracemalloc_remove_trace(unsigned int domain, uintptr_t ptr)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
trace_t trace;
|
trace_t *trace;
|
||||||
if (!_Py_HASHTABLE_POP(traces, TO_PTR(ptr), trace)) {
|
if (!_Py_HASHTABLE_POP(traces, TO_PTR(ptr), trace)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
assert(tracemalloc_traced_memory >= trace.size);
|
assert(tracemalloc_traced_memory >= trace->size);
|
||||||
tracemalloc_traced_memory -= trace.size;
|
tracemalloc_traced_memory -= trace->size;
|
||||||
|
raw_free(trace);
|
||||||
}
|
}
|
||||||
|
|
||||||
#define REMOVE_TRACE(ptr) \
|
#define REMOVE_TRACE(ptr) \
|
||||||
|
@ -565,23 +576,27 @@ tracemalloc_add_trace(unsigned int domain, uintptr_t ptr,
|
||||||
}
|
}
|
||||||
|
|
||||||
_Py_hashtable_entry_t* entry = _Py_HASHTABLE_GET_ENTRY(traces, ptr);
|
_Py_hashtable_entry_t* entry = _Py_HASHTABLE_GET_ENTRY(traces, ptr);
|
||||||
trace_t trace;
|
|
||||||
if (entry != NULL) {
|
if (entry != NULL) {
|
||||||
/* the memory block is already tracked */
|
/* the memory block is already tracked */
|
||||||
|
trace_t *trace;
|
||||||
_Py_HASHTABLE_ENTRY_READ_DATA(traces, entry, trace);
|
_Py_HASHTABLE_ENTRY_READ_DATA(traces, entry, trace);
|
||||||
assert(tracemalloc_traced_memory >= trace.size);
|
assert(tracemalloc_traced_memory >= trace->size);
|
||||||
tracemalloc_traced_memory -= trace.size;
|
tracemalloc_traced_memory -= trace->size;
|
||||||
|
|
||||||
trace.size = size;
|
trace->size = size;
|
||||||
trace.traceback = traceback;
|
trace->traceback = traceback;
|
||||||
_Py_HASHTABLE_ENTRY_WRITE_DATA(traces, entry, trace);
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
trace.size = size;
|
trace_t *trace = raw_malloc(sizeof(trace_t));
|
||||||
trace.traceback = traceback;
|
if (trace == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
trace->size = size;
|
||||||
|
trace->traceback = traceback;
|
||||||
|
|
||||||
int res = _Py_HASHTABLE_SET(traces, TO_PTR(ptr), trace);
|
int res = _Py_HASHTABLE_SET(traces, TO_PTR(ptr), trace);
|
||||||
if (res != 0) {
|
if (res != 0) {
|
||||||
|
raw_free(trace);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1225,19 +1240,62 @@ typedef struct {
|
||||||
unsigned int domain;
|
unsigned int domain;
|
||||||
} get_traces_t;
|
} get_traces_t;
|
||||||
|
|
||||||
|
|
||||||
static int
|
static int
|
||||||
tracemalloc_get_traces_copy_domain(_Py_hashtable_t *domains,
|
tracemalloc_copy_trace(_Py_hashtable_t *traces,
|
||||||
_Py_hashtable_entry_t *entry,
|
_Py_hashtable_entry_t *entry,
|
||||||
void *user_data)
|
void *traces2_raw)
|
||||||
{
|
{
|
||||||
get_traces_t *get_traces = user_data;
|
_Py_hashtable_t *traces2 = (_Py_hashtable_t *)traces2_raw;
|
||||||
|
|
||||||
|
trace_t *trace;
|
||||||
|
_Py_HASHTABLE_ENTRY_READ_DATA(traces, entry, trace);
|
||||||
|
|
||||||
|
trace_t *trace2 = raw_malloc(sizeof(trace_t));
|
||||||
|
if (traces2 == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
*trace2 = *trace;
|
||||||
|
if (_Py_HASHTABLE_SET(traces2, entry->key, trace2) < 0) {
|
||||||
|
raw_free(trace2);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static _Py_hashtable_t*
|
||||||
|
tracemalloc_copy_traces(_Py_hashtable_t *traces)
|
||||||
|
{
|
||||||
|
_Py_hashtable_t *traces2 = tracemalloc_create_traces_table();
|
||||||
|
if (traces2 == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int err = _Py_hashtable_foreach(traces,
|
||||||
|
tracemalloc_copy_trace,
|
||||||
|
traces2);
|
||||||
|
if (err) {
|
||||||
|
_Py_hashtable_destroy(traces2);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return traces2;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int
|
||||||
|
tracemalloc_copy_domain(_Py_hashtable_t *domains,
|
||||||
|
_Py_hashtable_entry_t *entry,
|
||||||
|
void *domains2_raw)
|
||||||
|
{
|
||||||
|
_Py_hashtable_t *domains2 = (_Py_hashtable_t *)domains2_raw;
|
||||||
|
|
||||||
unsigned int domain = (unsigned int)FROM_PTR(entry->key);
|
unsigned int domain = (unsigned int)FROM_PTR(entry->key);
|
||||||
_Py_hashtable_t *traces;
|
_Py_hashtable_t *traces;
|
||||||
_Py_HASHTABLE_ENTRY_READ_DATA(domains, entry, traces);
|
_Py_HASHTABLE_ENTRY_READ_DATA(domains, entry, traces);
|
||||||
|
|
||||||
_Py_hashtable_t *traces2 = _Py_hashtable_copy(traces);
|
_Py_hashtable_t *traces2 = tracemalloc_copy_traces(traces);
|
||||||
if (_Py_HASHTABLE_SET(get_traces->domains, TO_PTR(domain), traces2) < 0) {
|
if (_Py_HASHTABLE_SET(domains2, TO_PTR(domain), traces2) < 0) {
|
||||||
_Py_hashtable_destroy(traces2);
|
_Py_hashtable_destroy(traces2);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
@ -1245,18 +1303,37 @@ tracemalloc_get_traces_copy_domain(_Py_hashtable_t *domains,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static _Py_hashtable_t*
|
||||||
|
tracemalloc_copy_domains(_Py_hashtable_t *domains)
|
||||||
|
{
|
||||||
|
_Py_hashtable_t *domains2 = tracemalloc_create_domains_table();
|
||||||
|
if (domains2 == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int err = _Py_hashtable_foreach(domains,
|
||||||
|
tracemalloc_copy_domain,
|
||||||
|
domains2);
|
||||||
|
if (err) {
|
||||||
|
_Py_hashtable_destroy(domains2);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return domains2;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static int
|
static int
|
||||||
tracemalloc_get_traces_fill(_Py_hashtable_t *traces, _Py_hashtable_entry_t *entry,
|
tracemalloc_get_traces_fill(_Py_hashtable_t *traces, _Py_hashtable_entry_t *entry,
|
||||||
void *user_data)
|
void *user_data)
|
||||||
{
|
{
|
||||||
get_traces_t *get_traces = user_data;
|
get_traces_t *get_traces = user_data;
|
||||||
trace_t trace;
|
trace_t *trace;
|
||||||
PyObject *tracemalloc_obj;
|
PyObject *tracemalloc_obj;
|
||||||
int res;
|
int res;
|
||||||
|
|
||||||
_Py_HASHTABLE_ENTRY_READ_DATA(traces, entry, trace);
|
_Py_HASHTABLE_ENTRY_READ_DATA(traces, entry, trace);
|
||||||
|
|
||||||
tracemalloc_obj = trace_to_pyobject(get_traces->domain, &trace, get_traces->tracebacks);
|
tracemalloc_obj = trace_to_pyobject(get_traces->domain, trace, get_traces->tracebacks);
|
||||||
if (tracemalloc_obj == NULL)
|
if (tracemalloc_obj == NULL)
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
|
@ -1335,37 +1412,34 @@ _tracemalloc__get_traces_impl(PyObject *module)
|
||||||
goto no_memory;
|
goto no_memory;
|
||||||
}
|
}
|
||||||
|
|
||||||
get_traces.domains = tracemalloc_create_domains_table();
|
|
||||||
if (get_traces.domains == NULL) {
|
|
||||||
goto no_memory;
|
|
||||||
}
|
|
||||||
|
|
||||||
int err;
|
|
||||||
|
|
||||||
// Copy all traces so tracemalloc_get_traces_fill() doesn't have to disable
|
// Copy all traces so tracemalloc_get_traces_fill() doesn't have to disable
|
||||||
// temporarily tracemalloc which would impact other threads and so would
|
// temporarily tracemalloc which would impact other threads and so would
|
||||||
// miss allocations while get_traces() is called.
|
// miss allocations while get_traces() is called.
|
||||||
TABLES_LOCK();
|
TABLES_LOCK();
|
||||||
get_traces.traces = _Py_hashtable_copy(tracemalloc_traces);
|
get_traces.traces = tracemalloc_copy_traces(tracemalloc_traces);
|
||||||
err = _Py_hashtable_foreach(tracemalloc_domains,
|
|
||||||
tracemalloc_get_traces_copy_domain,
|
|
||||||
&get_traces);
|
|
||||||
TABLES_UNLOCK();
|
TABLES_UNLOCK();
|
||||||
|
|
||||||
if (get_traces.traces == NULL) {
|
if (get_traces.traces == NULL) {
|
||||||
goto no_memory;
|
goto no_memory;
|
||||||
}
|
}
|
||||||
if (err) {
|
|
||||||
|
TABLES_LOCK();
|
||||||
|
get_traces.domains = tracemalloc_copy_domains(tracemalloc_domains);
|
||||||
|
TABLES_UNLOCK();
|
||||||
|
|
||||||
|
if (get_traces.domains == NULL) {
|
||||||
goto no_memory;
|
goto no_memory;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert traces to a list of tuples
|
// Convert traces to a list of tuples
|
||||||
set_reentrant(1);
|
set_reentrant(1);
|
||||||
err = _Py_hashtable_foreach(get_traces.traces,
|
int err = _Py_hashtable_foreach(get_traces.traces,
|
||||||
tracemalloc_get_traces_fill, &get_traces);
|
tracemalloc_get_traces_fill,
|
||||||
|
&get_traces);
|
||||||
if (!err) {
|
if (!err) {
|
||||||
err = _Py_hashtable_foreach(get_traces.domains,
|
err = _Py_hashtable_foreach(get_traces.domains,
|
||||||
tracemalloc_get_traces_domain, &get_traces);
|
tracemalloc_get_traces_domain,
|
||||||
|
&get_traces);
|
||||||
}
|
}
|
||||||
set_reentrant(0);
|
set_reentrant(0);
|
||||||
if (err) {
|
if (err) {
|
||||||
|
@ -1398,7 +1472,7 @@ finally:
|
||||||
static traceback_t*
|
static traceback_t*
|
||||||
tracemalloc_get_traceback(unsigned int domain, uintptr_t ptr)
|
tracemalloc_get_traceback(unsigned int domain, uintptr_t ptr)
|
||||||
{
|
{
|
||||||
trace_t trace;
|
trace_t *trace;
|
||||||
int found;
|
int found;
|
||||||
|
|
||||||
if (!_Py_tracemalloc_config.tracing)
|
if (!_Py_tracemalloc_config.tracing)
|
||||||
|
@ -1414,10 +1488,11 @@ tracemalloc_get_traceback(unsigned int domain, uintptr_t ptr)
|
||||||
}
|
}
|
||||||
TABLES_UNLOCK();
|
TABLES_UNLOCK();
|
||||||
|
|
||||||
if (!found)
|
if (!found) {
|
||||||
return NULL;
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
return trace.traceback;
|
return trace->traceback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1758,10 +1833,9 @@ _PyTraceMalloc_NewReference(PyObject *op)
|
||||||
/* update the traceback of the memory block */
|
/* update the traceback of the memory block */
|
||||||
traceback_t *traceback = traceback_new();
|
traceback_t *traceback = traceback_new();
|
||||||
if (traceback != NULL) {
|
if (traceback != NULL) {
|
||||||
trace_t trace;
|
trace_t *trace;
|
||||||
_Py_HASHTABLE_ENTRY_READ_DATA(tracemalloc_traces, entry, trace);
|
_Py_HASHTABLE_ENTRY_READ_DATA(tracemalloc_traces, entry, trace);
|
||||||
trace.traceback = traceback;
|
trace->traceback = traceback;
|
||||||
_Py_HASHTABLE_ENTRY_WRITE_DATA(tracemalloc_traces, entry, trace);
|
|
||||||
res = 0;
|
res = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -350,21 +350,6 @@ _Py_hashtable_pop(_Py_hashtable_t *ht, const void *key,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Code commented since the function is not needed in Python */
|
|
||||||
#if 0
|
|
||||||
void
|
|
||||||
_Py_hashtable_delete(_Py_hashtable_t *ht, size_t const void *key)
|
|
||||||
{
|
|
||||||
#ifndef NDEBUG
|
|
||||||
int found = _Py_hashtable_pop_entry(ht, key, NULL, 0);
|
|
||||||
assert(found);
|
|
||||||
#else
|
|
||||||
(void)_Py_hashtable_pop_entry(ht, key, NULL, 0);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
int
|
int
|
||||||
_Py_hashtable_foreach(_Py_hashtable_t *ht,
|
_Py_hashtable_foreach(_Py_hashtable_t *ht,
|
||||||
_Py_hashtable_foreach_func func,
|
_Py_hashtable_foreach_func func,
|
||||||
|
@ -538,37 +523,3 @@ _Py_hashtable_destroy(_Py_hashtable_t *ht)
|
||||||
ht->alloc.free(ht->buckets);
|
ht->alloc.free(ht->buckets);
|
||||||
ht->alloc.free(ht);
|
ht->alloc.free(ht);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
_Py_hashtable_t *
|
|
||||||
_Py_hashtable_copy(_Py_hashtable_t *src)
|
|
||||||
{
|
|
||||||
const size_t data_size = src->data_size;
|
|
||||||
_Py_hashtable_t *dst;
|
|
||||||
_Py_hashtable_entry_t *entry;
|
|
||||||
size_t bucket;
|
|
||||||
int err;
|
|
||||||
|
|
||||||
dst = _Py_hashtable_new_full(data_size, src->num_buckets,
|
|
||||||
src->hash_func,
|
|
||||||
src->compare_func,
|
|
||||||
src->key_destroy_func,
|
|
||||||
src->value_destroy_func,
|
|
||||||
&src->alloc);
|
|
||||||
if (dst == NULL)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
for (bucket=0; bucket < src->num_buckets; bucket++) {
|
|
||||||
entry = TABLE_HEAD(src, bucket);
|
|
||||||
for (; entry; entry = ENTRY_NEXT(entry)) {
|
|
||||||
const void *key = entry->key;
|
|
||||||
const void *pdata = _Py_HASHTABLE_ENTRY_PDATA(entry);
|
|
||||||
err = _Py_hashtable_set(dst, key, data_size, pdata);
|
|
||||||
if (err) {
|
|
||||||
_Py_hashtable_destroy(dst);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dst;
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue