https://github.com/python/cpython/commit/884302c9e6591b263e1fea2d4e003bd8054c6899 commit: 884302c9e6591b263e1fea2d4e003bd8054c6899 branch: 3.14 author: Miss Islington (bot) <[email protected]> committer: ngoldbaum <[email protected]> date: 2026-09-14T17:22:57Z summary:
[3.14] gh-157377: Don't flush the thread-local allocation count in gc.get_count (GH-157381) (#157506) gh-157377: Don't flush the thread-local allocation count in gc.get_count (GH-157381) * gh-157377: Don't flush the thread-local allocation count in gc.get_count() * gh-157377: clamp gc.get_count() return value at zero (cherry picked from commit c2159199c5761f662f084a52c6d8ee704e7efdc7) Co-authored-by: Nathan Goldbaum <[email protected]> files: A Misc/NEWS.d/next/Core_and_Builtins/2026-09-12-22-29-51.gh-issue-157377.O6WYlM.rst M Lib/test/test_gc.py M Modules/gcmodule.c diff --git a/Lib/test/test_gc.py b/Lib/test/test_gc.py index 0fe63332d15c9c..5573edfcb17bef 100644 --- a/Lib/test/test_gc.py +++ b/Lib/test/test_gc.py @@ -1591,6 +1591,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 7bb1dcc076ee49..303c6a08c0d577 100644 --- a/Modules/gcmodule.c +++ b/Modules/gcmodule.c @@ -222,9 +222,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 @@ -234,7 +237,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 _______________________________________________ Python-checkins mailing list -- [email protected] To unsubscribe send an email to [email protected] https://mail.python.org/mailman3//lists/python-checkins.python.org Member address: [email protected]
