summaryrefslogtreecommitdiffstats
path: root/Python
diff options
context:
space:
mode:
authorSam Gross <colesbury@gmail.com>2024-08-08 16:48:17 (GMT)
committerGitHub <noreply@github.com>2024-08-08 16:48:17 (GMT)
commit2d9d3a9f5319ce3f850341d116b63cc51869df3a (patch)
tree6d5250d1af82d114d310655fe164c932b955ea4b /Python
parent833eb106f5ebbac258f236d50177712d98a36715 (diff)
downloadcpython-2d9d3a9f5319ce3f850341d116b63cc51869df3a.zip
cpython-2d9d3a9f5319ce3f850341d116b63cc51869df3a.tar.gz
cpython-2d9d3a9f5319ce3f850341d116b63cc51869df3a.tar.bz2
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.
Diffstat (limited to 'Python')
-rw-r--r--Python/gc_free_threading.c12
-rw-r--r--Python/pylifecycle.c4
-rw-r--r--Python/pystate.c2
3 files changed, 17 insertions, 1 deletions
diff --git a/Python/gc_free_threading.c b/Python/gc_free_threading.c
index 1e02db0..543bee2 100644
--- a/Python/gc_free_threading.c
+++ b/Python/gc_free_threading.c
@@ -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);
diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c
index 6b641c0..3a41c64 100644
--- a/Python/pylifecycle.c
+++ b/Python/pylifecycle.c
@@ -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);
diff --git a/Python/pystate.c b/Python/pystate.c
index 8f4818c..bba88b7 100644
--- a/Python/pystate.c
+++ b/Python/pystate.c
@@ -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