Skip to content
This repository was archived by the owner on Dec 9, 2025. It is now read-only.

Commit b0f5d5d

Browse files
habermanhonglooker
authored andcommitted
Added malloc_trim() calls to Python allocator so RSS will decrease when memory is freed
This partially fixes protocolbuffers/protobuf#10088. The test case from that bug significantly improves with this change. However we still have a global map that does not shrink, which can still create the appearance of leaking memory, as it will not be freed until the module is unloaded. PiperOrigin-RevId: 563124724
1 parent 2878aa7 commit b0f5d5d

File tree

1 file changed

+48
-2
lines changed

1 file changed

+48
-2
lines changed

python/protobuf.c

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636
#include "python/repeated.h"
3737
#include "python/unknown_fields.h"
3838

39+
static upb_Arena* PyUpb_NewArena(void);
40+
3941
static void PyUpb_ModuleDealloc(void* module) {
4042
PyUpb_ModuleState* s = PyModule_GetState(module);
4143
PyUpb_WeakMap_Free(s->obj_cache);
@@ -122,7 +124,7 @@ struct PyUpb_WeakMap {
122124
};
123125

124126
PyUpb_WeakMap* PyUpb_WeakMap_New(void) {
125-
upb_Arena* arena = upb_Arena_New();
127+
upb_Arena* arena = PyUpb_NewArena();
126128
PyUpb_WeakMap* map = upb_Arena_Malloc(arena, sizeof(*map));
127129
map->arena = arena;
128130
upb_inttable_init(&map->table, map->arena);
@@ -221,10 +223,54 @@ typedef struct {
221223
upb_Arena* arena;
222224
} PyUpb_Arena;
223225

226+
// begin:google_only
227+
// static upb_alloc* global_alloc = &upb_alloc_global;
228+
// end:google_only
229+
230+
// begin:github_only
231+
#ifdef __GLIBC__
232+
#include <malloc.h> // malloc_trim()
233+
#endif
234+
235+
// A special allocator that calls malloc_trim() periodically to release
236+
// memory to the OS. Without this call, we appear to leak memory, at least
237+
// as measured in RSS.
238+
//
239+
// We opt not to use this instead of PyMalloc (which would also solve the
240+
// problem) because the latter requires the GIL to be held. This would make
241+
// our messages unsafe to share with other languages that could free at
242+
// unpredictable
243+
// times.
244+
static void* upb_trim_allocfunc(upb_alloc* alloc, void* ptr, size_t oldsize,
245+
size_t size) {
246+
(void)alloc;
247+
(void)oldsize;
248+
if (size == 0) {
249+
free(ptr);
250+
#ifdef __GLIBC__
251+
static int count = 0;
252+
if (++count == 10000) {
253+
malloc_trim(0);
254+
count = 0;
255+
}
256+
#endif
257+
return NULL;
258+
} else {
259+
return realloc(ptr, size);
260+
}
261+
}
262+
static upb_alloc trim_alloc = {&upb_trim_allocfunc};
263+
static const upb_alloc* global_alloc = &trim_alloc;
264+
// end:github_only
265+
266+
static upb_Arena* PyUpb_NewArena(void) {
267+
return upb_Arena_Init(NULL, 0, global_alloc);
268+
}
269+
224270
PyObject* PyUpb_Arena_New(void) {
225271
PyUpb_ModuleState* state = PyUpb_ModuleState_Get();
226272
PyUpb_Arena* arena = (void*)PyType_GenericAlloc(state->arena_type, 0);
227-
arena->arena = upb_Arena_New();
273+
arena->arena = PyUpb_NewArena();
228274
return &arena->ob_base;
229275
}
230276

0 commit comments

Comments
 (0)