From 77d9a4786915e71b92e5d6f961ec965092da5196 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Salgado Date: Fri, 23 Oct 2026 18:58:44 +0100 Subject: [PATCH] gh-159137: Skip unnecessary free-threaded GC finalizer and weakref scans --- ...026-10-23-19-45-02.gh-issue-159137.gcFlags.rst | 2 ++ Python/gc_free_threading.c | 15 +++++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-10-23-19-45-02.gh-issue-159137.gcFlags.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-10-23-19-45-02.gh-issue-159137.gcFlags.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-10-23-19-45-02.gh-issue-159137.gcFlags.rst new file mode 100644 index 00000000000000..b21b096068a128 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-10-23-19-45-02.gh-issue-159137.gcFlags.rst @@ -0,0 +1,2 @@ +Skip finalizer preparation and weakref cleanup in the free-threaded garbage +collector when unreachable objects require neither phase. diff --git a/Python/gc_free_threading.c b/Python/gc_free_threading.c index 6db010468456ea..6d3da0e07c84c5 100644 --- a/Python/gc_free_threading.c +++ b/Python/gc_free_threading.c @@ -80,6 +80,8 @@ struct collection_state { Py_ssize_t uncollectable; Py_ssize_t candidates; Py_ssize_t long_lived_total; + bool has_finalizer_work; + bool has_weakref_work; struct worklist unreachable; struct worklist legacy_finalizers; struct worklist wrcb_to_call; @@ -1215,6 +1217,12 @@ scan_heap_visitor(const mi_heap_t *heap, const mi_heap_area_t *area, worklist_push(&state->legacy_finalizers, op); } else { + if (Py_TYPE(op)->tp_finalize != NULL) { + state->has_finalizer_work = true; + } + if (PyWeakref_Check(op) || _PyType_SUPPORTS_WEAKREFS(Py_TYPE(op))) { + state->has_weakref_work = true; + } worklist_push(&state->unreachable, op); } // It is possible this object will be resurrected but @@ -2163,7 +2171,10 @@ gc_collect_internal(PyInterpreterState *interp, struct collection_state *state, } // Find weakref callbacks and pending finalizers (but do not call them). - bool has_finalizers = prepare_finalization(state, &needs_resurrection_check); + bool has_finalizers = false; + if (state->has_finalizer_work || state->has_weakref_work) { + has_finalizers = prepare_finalization(state, &needs_resurrection_check); + } if (needs_resurrection_check) { _PyEval_StartTheWorld(interp); @@ -2186,7 +2197,7 @@ gc_collect_internal(PyInterpreterState *interp, struct collection_state *state, // Otherwise, keep other threads paused so the unreachable set stays valid. // Clear free lists in all threads _PyGC_ClearAllFreeLists(interp); - if (err == 0) { + if (err == 0 && state->has_weakref_work) { clear_weakrefs(state); } // Record the number of live GC objects