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.
This commit is contained in:
Sam Gross 2024-08-08 12:48:17 -04:00 committed by GitHub
parent 833eb106f5
commit 2d9d3a9f53
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 27 additions and 4 deletions

View file

@ -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;
@ -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);
@ -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);

View file

@ -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()
@ -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);

View file

@ -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