diff --git a/Lib/test/test_gc.py b/Lib/test/test_gc.py index 10f2a5dfb505e3..919100f2d5ce04 100644 --- a/Lib/test/test_gc.py +++ b/Lib/test/test_gc.py @@ -1658,6 +1658,25 @@ def test_indirect_calls_with_gc_disabled(self): finally: gc.enable() + def test_get_count_nonnegative(self): + xs = [[] for _ in range(1500)] + gc.collect() + del xs[:1000] + self.assertGreaterEqual(gc.get_count()[0], 0) + + @gc_threshold(1000, 0, 0) + def test_get_count_does_not_prevent_collection(self): + junk = [] + gc.collect() + detector = GC_Detector() + for _ in range(10000): + junk.append([]) + gc.get_count() + if detector.gc_happened: + break + else: + self.fail("gc didn't happen after 10000 iterations") + # Ensure that setting *threshold0* to zero disables collection. @gc_threshold(0) def test_threshold_zero(self): diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-09-12-22-29-51.gh-issue-157377.O6WYlM.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-09-12-22-29-51.gh-issue-157377.O6WYlM.rst new file mode 100644 index 00000000000000..0e8c5584a316a5 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-09-12-22-29-51.gh-issue-157377.O6WYlM.rst @@ -0,0 +1,4 @@ +Fix :func:`gc.get_count` on the free-threaded build resetting the thread-local +allocation counter that schedules automatic garbage collection. A thread that +called it while allocating could prevent cyclic garbage from ever being +collected. diff --git a/Modules/gcmodule.c b/Modules/gcmodule.c index e2df31556f3c37..fea3897ceb4c8c 100644 --- a/Modules/gcmodule.c +++ b/Modules/gcmodule.c @@ -221,9 +221,12 @@ gc_get_count_impl(PyObject *module) _PyThreadStateImpl *tstate = (_PyThreadStateImpl *)_PyThreadState_GET(); struct _gc_thread_state *gc = &tstate->gc; - // Flush the local allocation count to the global count - _Py_atomic_add_int(&gcstate->young.count, (int)gc->alloc_count); - gc->alloc_count = 0; + // Don't flush: record_allocation() checks the threshold only when it fills. + int young = _Py_atomic_load_int_relaxed(&gcstate->young.count); + young += (int)gc->alloc_count; + if (young < 0) { + young = 0; + } #endif #ifndef Py_GIL_DISABLED @@ -233,7 +236,7 @@ gc_get_count_impl(PyObject *module) gcstate->generations[2].count); #else return Py_BuildValue("(iii)", - _Py_atomic_load_int_relaxed(&gcstate->young.count), + young, gcstate->old[0].count, gcstate->old[1].count); #endif