Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-122697: Fix free-threading memory leaks at shutdown #122703

Merged
merged 10 commits into from
Aug 8, 2024
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

runtime shutdown would be better instead of interpreter shutdown?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, this should be interpreter shutdown. That's why the refleak buildbots had failures.

// 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
Loading