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]

Reply via email to