Skip to content

Commit

Permalink
pythongh-122697: Fix free-threading memory leaks at shutdown (python#…
Browse files Browse the repository at this point in the history
…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.
  • Loading branch information
colesbury authored Aug 8, 2024
1 parent 833eb10 commit 2d9d3a9
Show file tree
Hide file tree
Showing 5 changed files with 27 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fixed memory leaks at interpreter shutdown in the free-threaded build, and
also reporting of leaked memory blocks via :option:`-X showrefcount <-X>`.
11 changes: 8 additions & 3 deletions Objects/obmalloc.c
Original file line number Diff line number Diff line change
Expand Up @@ -1109,9 +1109,12 @@ free_delayed(uintptr_t ptr)
#ifndef Py_GIL_DISABLED
free_work_item(ptr);
#else
if (_PyRuntime.stoptheworld.world_stopped) {
// Free immediately if the world is stopped, including during
// interpreter shutdown.
PyInterpreterState *interp = _PyInterpreterState_GET();
if (_PyInterpreterState_GetFinalizing(interp) != NULL ||
interp->stoptheworld.world_stopped)
{
// Free immediately during interpreter shutdown or if the world is
// stopped.
free_work_item(ptr);
return;
}
Expand Down Expand Up @@ -1474,6 +1477,8 @@ _PyInterpreterState_FinalizeAllocatedBlocks(PyInterpreterState *interp)
{
#ifdef WITH_MIMALLOC
if (_PyMem_MimallocEnabled()) {
Py_ssize_t leaked = _PyInterpreterState_GetAllocatedBlocks(interp);
interp->runtime->obmalloc.interpreter_leaks += leaked;
return;
}
#endif
Expand Down
12 changes: 12 additions & 0 deletions Python/gc_free_threading.c
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ struct collection_state {
struct visitor_args base;
PyInterpreterState *interp;
GCState *gcstate;
_PyGC_Reason reason;
Py_ssize_t collected;
Py_ssize_t uncollectable;
Py_ssize_t long_lived_total;
Expand Down Expand Up @@ -572,6 +573,16 @@ scan_heap_visitor(const mi_heap_t *heap, const mi_heap_area_t *area,
worklist_push(&state->unreachable, op);
}
}
else if (state->reason == _Py_GC_REASON_SHUTDOWN &&
_PyObject_HasDeferredRefcount(op))
{
// Disable deferred refcounting for reachable objects as well during
// interpreter shutdown. This ensures that these objects are collected
// immediately when their last reference is removed.
disable_deferred_refcounting(op);
merge_refcount(op, 0);
state->long_lived_total++;
}
else {
// object is reachable, restore `ob_tid`; we're done with these objects
gc_restore_tid(op);
Expand Down Expand Up @@ -1228,6 +1239,7 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason)
struct collection_state state = {
.interp = interp,
.gcstate = gcstate,
.reason = reason,
};

gc_collect_internal(interp, &state, generation);
Expand Down
4 changes: 4 additions & 0 deletions Python/pylifecycle.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include "pycore_sliceobject.h" // _PySlice_Fini()
#include "pycore_sysmodule.h" // _PySys_ClearAuditHooks()
#include "pycore_traceback.h" // _Py_DumpTracebackThreads()
#include "pycore_typeid.h" // _PyType_FinalizeIdPool()
#include "pycore_typeobject.h" // _PyTypes_InitTypes()
#include "pycore_typevarobject.h" // _Py_clear_generic_types()
#include "pycore_unicodeobject.h" // _PyUnicode_InitTypes()
Expand Down Expand Up @@ -1832,6 +1833,9 @@ finalize_interp_types(PyInterpreterState *interp)
_PyTypes_FiniTypes(interp);

_PyTypes_Fini(interp);
#ifdef Py_GIL_DISABLED
_PyType_FinalizeIdPool(interp);
#endif

_PyCode_Fini(interp);

Expand Down
2 changes: 1 addition & 1 deletion Python/pystate.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
#include "pycore_runtime_init.h" // _PyRuntimeState_INIT
#include "pycore_sysmodule.h" // _PySys_Audit()
#include "pycore_obmalloc.h" // _PyMem_obmalloc_state_on_heap()
#include "pycore_typeid.h" // _PyType_FinalizeIdPool
#include "pycore_typeid.h" // _PyType_FinalizeThreadLocalRefcounts()

/* --------------------------------------------------------------------------
CAUTION
Expand Down

0 comments on commit 2d9d3a9

Please sign in to comment.