From 9d8fa7e5b43adb3e864921e141a173d283ee5460 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Tue, 8 Sep 2026 17:44:03 +0500 Subject: [PATCH 1/3] Test that GC frame is not cleared --- Lib/test/test_gc.py | 18 ++++++++++++++++++ Modules/_testinternalcapi.c | 14 ++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/Lib/test/test_gc.py b/Lib/test/test_gc.py index 10f2a5dfb505e31..36168efafc4ffe5 100644 --- a/Lib/test/test_gc.py +++ b/Lib/test/test_gc.py @@ -1289,6 +1289,24 @@ def test_heap_size(self): del l self.assertEqual(count, _testinternalcapi.get_tracked_heap_size()) + @unittest.skipIf(_testinternalcapi is None, "requires _testinternalcapi") + def test_clear_frame_on_early_return(self): + # __del__ methods can trigger collection, make this to happen + thresholds = gc.get_threshold() + gc.enable() + gc.set_threshold(1) + + class A: + def __del__(self): + dir(self) + + x = [A() for _ in range(10)] + del x + self.assertTrue(_testinternalcapi.is_gc_frame_clear()) + + gc.disable() + gc.set_threshold(*thresholds) + class GCCallbackTests(unittest.TestCase): def setUp(self): diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index 38e56ae70420985..6de03fba43ba29a 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -3206,6 +3206,19 @@ test_thread_state_ensure_from_view_interp_switch(PyObject *self, PyObject *unuse Py_RETURN_NONE; } +static PyObject * +is_gc_frame_clear(PyObject *self, PyObject *unused) +{ + PyInterpreterState *interp = _PyInterpreterState_GET(); + assert(interp != NULL); + + if (!interp->gc.frame) { + Py_RETURN_TRUE; + } + + Py_RETURN_FALSE; +} + /* Self interrupting context manager */ typedef struct { @@ -3393,6 +3406,7 @@ static PyMethodDef module_functions[] = { {"test_interp_guard_countdown", test_interp_guard_countdown, METH_NOARGS}, {"test_interp_view_countdown", test_interp_view_countdown, METH_NOARGS}, {"test_thread_state_ensure_from_view_interp_switch", test_thread_state_ensure_from_view_interp_switch, METH_NOARGS}, + {"is_gc_frame_clear", is_gc_frame_clear, METH_NOARGS}, {NULL, NULL} /* sentinel */ }; From a4b6f3da67e75cc1ae0ac66c7d2649318f5e2178 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Tue, 8 Sep 2026 17:44:16 +0500 Subject: [PATCH 2/3] Clear GC frame on early return --- Python/gc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Python/gc.c b/Python/gc.c index 201c621bcc3cb9b..8b1bdeb56556a2c 100644 --- a/Python/gc.c +++ b/Python/gc.c @@ -1447,6 +1447,7 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason) // objects from that generation and all generations younger than it. generation = gc_select_generation(gcstate); if (generation < 0) { + gcstate->frame = NULL; // No generation needs to be collected. _Py_atomic_store_int(&gcstate->collecting, 0); return 0; From f15dc69346c693e42eda1449d0895c108cd03492 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Tue, 8 Sep 2026 17:54:44 +0500 Subject: [PATCH 3/3] Add news entry --- .../2026-09-08-17-54-34.gh-issue-156425.-Rn7Tw.rst | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-09-08-17-54-34.gh-issue-156425.-Rn7Tw.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-09-08-17-54-34.gh-issue-156425.-Rn7Tw.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-09-08-17-54-34.gh-issue-156425.-Rn7Tw.rst new file mode 100644 index 000000000000000..bee2f80335df95b --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-09-08-17-54-34.gh-issue-156425.-Rn7Tw.rst @@ -0,0 +1,3 @@ +Fix a bug in the garbage collector where frames could be kept alive longer +than necessary, potentially distorting profiler statistics. Patch by Sergey +Miryanov.