https://github.com/python/cpython/commit/c2159199c5761f662f084a52c6d8ee704e7efdc7
commit: c2159199c5761f662f084a52c6d8ee704e7efdc7
branch: main
author: Nathan Goldbaum <[email protected]>
committer: ngoldbaum <[email protected]>
date: 2026-09-14T10:56:53-06:00
summary:
gh-157377: Don't flush the thread-local allocation count in gc.get_count
(#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
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 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
_______________________________________________
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]