From 0d0c5a95aa11f496c4fac1992f1e99ac160d087e Mon Sep 17 00:00:00 2001 From: Neil Schemenauer Date: Sat, 16 Dec 2023 13:27:43 -0800 Subject: [PATCH 1/2] Fix memory leak of obmalloc state. Free the obmalloc arenas, radix tree nodes and allarenas array when the interpreter state is freed. --- Objects/obmalloc.c | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/Objects/obmalloc.c b/Objects/obmalloc.c index 99c95d90658b08..a845e9a83618d3 100644 --- a/Objects/obmalloc.c +++ b/Objects/obmalloc.c @@ -1052,6 +1052,8 @@ _PyInterpreterState_GetAllocatedBlocks(PyInterpreterState *interp) return n; } +static void free_arenas(PyInterpreterState *interp); + void _PyInterpreterState_FinalizeAllocatedBlocks(PyInterpreterState *interp) { @@ -1065,6 +1067,7 @@ _PyInterpreterState_FinalizeAllocatedBlocks(PyInterpreterState *interp) assert(has_own_state(interp) || leaked == 0); interp->runtime->obmalloc.interpreter_leaks += leaked; } + free_arenas(interp); } static Py_ssize_t get_num_global_allocated_blocks(_PyRuntimeState *); @@ -2612,6 +2615,37 @@ _PyObject_DebugDumpAddress(const void *p) _PyMem_DumpTraceback(fileno(stderr), p); } +static void +free_arenas(PyInterpreterState *interp) +{ + OMState *state = &interp->obmalloc; + for (uint i = 0; i < maxarenas; ++i) { + // free each obmalloc memory arena + struct arena_object *ao = &allarenas[i]; + _PyObject_Arena.free(_PyObject_Arena.ctx, + (void *)ao->address, ARENA_SIZE); + } + // free the array containing pointers to all arenas + PyMem_RawFree(allarenas); +#if WITH_PYMALLOC_RADIX_TREE + // Free the middle and bottom nodes of the radix tree. These are allocated + // by arena_map_mark_used() but not freed when arenas are freed. + for (int i1 = 0; i1 < MAP_TOP_LENGTH; i1++) { + arena_map_mid_t *mid = arena_map_root.ptrs[i1]; + if (mid == NULL) { + continue; + } + for (int i2 = 0; i2 < MAP_MID_LENGTH; i2++) { + arena_map_bot_t *bot = arena_map_root.ptrs[i1]->ptrs[i2]; + if (bot == NULL) { + continue; + } + PyMem_RawFree(bot); + } + PyMem_RawFree(mid); + } +#endif +} static size_t printone(FILE *out, const char* msg, size_t value) From 4471ac7adeaf85f9a0c99993ab44e5d8293a58a9 Mon Sep 17 00:00:00 2001 From: Neil Schemenauer Date: Sat, 16 Dec 2023 23:18:04 -0800 Subject: [PATCH 2/2] Fix for various config options. Make work for --without-pymalloc and for WITH_PYMALLOC_RADIX_TREE=0. --- Objects/obmalloc.c | 66 ++++++++++++++++++++++++---------------------- 1 file changed, 34 insertions(+), 32 deletions(-) diff --git a/Objects/obmalloc.c b/Objects/obmalloc.c index a845e9a83618d3..f40dc8991efaed 100644 --- a/Objects/obmalloc.c +++ b/Objects/obmalloc.c @@ -2615,38 +2615,6 @@ _PyObject_DebugDumpAddress(const void *p) _PyMem_DumpTraceback(fileno(stderr), p); } -static void -free_arenas(PyInterpreterState *interp) -{ - OMState *state = &interp->obmalloc; - for (uint i = 0; i < maxarenas; ++i) { - // free each obmalloc memory arena - struct arena_object *ao = &allarenas[i]; - _PyObject_Arena.free(_PyObject_Arena.ctx, - (void *)ao->address, ARENA_SIZE); - } - // free the array containing pointers to all arenas - PyMem_RawFree(allarenas); -#if WITH_PYMALLOC_RADIX_TREE - // Free the middle and bottom nodes of the radix tree. These are allocated - // by arena_map_mark_used() but not freed when arenas are freed. - for (int i1 = 0; i1 < MAP_TOP_LENGTH; i1++) { - arena_map_mid_t *mid = arena_map_root.ptrs[i1]; - if (mid == NULL) { - continue; - } - for (int i2 = 0; i2 < MAP_MID_LENGTH; i2++) { - arena_map_bot_t *bot = arena_map_root.ptrs[i1]->ptrs[i2]; - if (bot == NULL) { - continue; - } - PyMem_RawFree(bot); - } - PyMem_RawFree(mid); - } -#endif -} - static size_t printone(FILE *out, const char* msg, size_t value) { @@ -2700,6 +2668,40 @@ _PyDebugAllocatorStats(FILE *out, #ifdef WITH_PYMALLOC +static void +free_arenas(PyInterpreterState *interp) +{ + OMState *state = &interp->obmalloc; + for (uint i = 0; i < maxarenas; ++i) { + // free each obmalloc memory arena + struct arena_object *ao = &allarenas[i]; + _PyObject_Arena.free(_PyObject_Arena.ctx, + (void *)ao->address, ARENA_SIZE); + } + // free the array containing pointers to all arenas + PyMem_RawFree(allarenas); +#if WITH_PYMALLOC_RADIX_TREE +#ifdef USE_INTERIOR_NODES + // Free the middle and bottom nodes of the radix tree. These are allocated + // by arena_map_mark_used() but not freed when arenas are freed. + for (int i1 = 0; i1 < MAP_TOP_LENGTH; i1++) { + arena_map_mid_t *mid = arena_map_root.ptrs[i1]; + if (mid == NULL) { + continue; + } + for (int i2 = 0; i2 < MAP_MID_LENGTH; i2++) { + arena_map_bot_t *bot = arena_map_root.ptrs[i1]->ptrs[i2]; + if (bot == NULL) { + continue; + } + PyMem_RawFree(bot); + } + PyMem_RawFree(mid); + } +#endif +#endif +} + #ifdef Py_DEBUG /* Is target in the list? The list is traversed via the nextpool pointers. * The list may be NULL-terminated, or circular. Return 1 if target is in