Skip to content

Commit 2d9d3a9

Browse files
authored
gh-122697: Fix free-threading memory leaks at shutdown (#122703)
We were not properly accounting for interpreter memory leaks at shutdown and had two sources of leaks: * Objects that use deferred reference counting and were reachable via static types outlive the final GC. We now disable deferred reference counting on all objects if we are calling the GC due to interpreter shutdown. * `_PyMem_FreeDelayed` did not properly check for interpreter shutdown so we had some memory blocks that were enqueued to be freed, but never actually freed. * `_PyType_FinalizeIdPool` wasn't called at interpreter shutdown.
1 parent 833eb10 commit 2d9d3a9

File tree

5 files changed

+27
-4
lines changed

5 files changed

+27
-4
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fixed memory leaks at interpreter shutdown in the free-threaded build, and
2+
also reporting of leaked memory blocks via :option:`-X showrefcount <-X>`.

Objects/obmalloc.c

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1109,9 +1109,12 @@ free_delayed(uintptr_t ptr)
11091109
#ifndef Py_GIL_DISABLED
11101110
free_work_item(ptr);
11111111
#else
1112-
if (_PyRuntime.stoptheworld.world_stopped) {
1113-
// Free immediately if the world is stopped, including during
1114-
// interpreter shutdown.
1112+
PyInterpreterState *interp = _PyInterpreterState_GET();
1113+
if (_PyInterpreterState_GetFinalizing(interp) != NULL ||
1114+
interp->stoptheworld.world_stopped)
1115+
{
1116+
// Free immediately during interpreter shutdown or if the world is
1117+
// stopped.
11151118
free_work_item(ptr);
11161119
return;
11171120
}
@@ -1474,6 +1477,8 @@ _PyInterpreterState_FinalizeAllocatedBlocks(PyInterpreterState *interp)
14741477
{
14751478
#ifdef WITH_MIMALLOC
14761479
if (_PyMem_MimallocEnabled()) {
1480+
Py_ssize_t leaked = _PyInterpreterState_GetAllocatedBlocks(interp);
1481+
interp->runtime->obmalloc.interpreter_leaks += leaked;
14771482
return;
14781483
}
14791484
#endif

Python/gc_free_threading.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ struct collection_state {
5555
struct visitor_args base;
5656
PyInterpreterState *interp;
5757
GCState *gcstate;
58+
_PyGC_Reason reason;
5859
Py_ssize_t collected;
5960
Py_ssize_t uncollectable;
6061
Py_ssize_t long_lived_total;
@@ -572,6 +573,16 @@ scan_heap_visitor(const mi_heap_t *heap, const mi_heap_area_t *area,
572573
worklist_push(&state->unreachable, op);
573574
}
574575
}
576+
else if (state->reason == _Py_GC_REASON_SHUTDOWN &&
577+
_PyObject_HasDeferredRefcount(op))
578+
{
579+
// Disable deferred refcounting for reachable objects as well during
580+
// interpreter shutdown. This ensures that these objects are collected
581+
// immediately when their last reference is removed.
582+
disable_deferred_refcounting(op);
583+
merge_refcount(op, 0);
584+
state->long_lived_total++;
585+
}
575586
else {
576587
// object is reachable, restore `ob_tid`; we're done with these objects
577588
gc_restore_tid(op);
@@ -1228,6 +1239,7 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason)
12281239
struct collection_state state = {
12291240
.interp = interp,
12301241
.gcstate = gcstate,
1242+
.reason = reason,
12311243
};
12321244

12331245
gc_collect_internal(interp, &state, generation);

Python/pylifecycle.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include "pycore_sliceobject.h" // _PySlice_Fini()
2929
#include "pycore_sysmodule.h" // _PySys_ClearAuditHooks()
3030
#include "pycore_traceback.h" // _Py_DumpTracebackThreads()
31+
#include "pycore_typeid.h" // _PyType_FinalizeIdPool()
3132
#include "pycore_typeobject.h" // _PyTypes_InitTypes()
3233
#include "pycore_typevarobject.h" // _Py_clear_generic_types()
3334
#include "pycore_unicodeobject.h" // _PyUnicode_InitTypes()
@@ -1832,6 +1833,9 @@ finalize_interp_types(PyInterpreterState *interp)
18321833
_PyTypes_FiniTypes(interp);
18331834

18341835
_PyTypes_Fini(interp);
1836+
#ifdef Py_GIL_DISABLED
1837+
_PyType_FinalizeIdPool(interp);
1838+
#endif
18351839

18361840
_PyCode_Fini(interp);
18371841

Python/pystate.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
#include "pycore_runtime_init.h" // _PyRuntimeState_INIT
2121
#include "pycore_sysmodule.h" // _PySys_Audit()
2222
#include "pycore_obmalloc.h" // _PyMem_obmalloc_state_on_heap()
23-
#include "pycore_typeid.h" // _PyType_FinalizeIdPool
23+
#include "pycore_typeid.h" // _PyType_FinalizeThreadLocalRefcounts()
2424

2525
/* --------------------------------------------------------------------------
2626
CAUTION

0 commit comments

Comments
 (0)