Skip to content

Commit 0ed896f

Browse files
[3.15] gh-157377: Don't flush the thread-local allocation count in gc.get_count (GH-157381) (#157505)
Co-authored-by: Nathan Goldbaum <nathan.goldbaum@gmail.com>
1 parent 3628396 commit 0ed896f

3 files changed

Lines changed: 30 additions & 4 deletions

File tree

Lib/test/test_gc.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1662,6 +1662,25 @@ def test_indirect_calls_with_gc_disabled(self):
16621662
finally:
16631663
gc.enable()
16641664

1665+
def test_get_count_nonnegative(self):
1666+
xs = [[] for _ in range(1500)]
1667+
gc.collect()
1668+
del xs[:1000]
1669+
self.assertGreaterEqual(gc.get_count()[0], 0)
1670+
1671+
@gc_threshold(1000, 0, 0)
1672+
def test_get_count_does_not_prevent_collection(self):
1673+
junk = []
1674+
gc.collect()
1675+
detector = GC_Detector()
1676+
for _ in range(10000):
1677+
junk.append([])
1678+
gc.get_count()
1679+
if detector.gc_happened:
1680+
break
1681+
else:
1682+
self.fail("gc didn't happen after 10000 iterations")
1683+
16651684
# Ensure that setting *threshold0* to zero disables collection.
16661685
@gc_threshold(0)
16671686
def test_threshold_zero(self):
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fix :func:`gc.get_count` on the free-threaded build resetting the thread-local
2+
allocation counter that schedules automatic garbage collection. A thread that
3+
called it while allocating could prevent cyclic garbage from ever being
4+
collected.

Modules/gcmodule.c

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -221,9 +221,12 @@ gc_get_count_impl(PyObject *module)
221221
_PyThreadStateImpl *tstate = (_PyThreadStateImpl *)_PyThreadState_GET();
222222
struct _gc_thread_state *gc = &tstate->gc;
223223

224-
// Flush the local allocation count to the global count
225-
_Py_atomic_add_int(&gcstate->young.count, (int)gc->alloc_count);
226-
gc->alloc_count = 0;
224+
// Don't flush: record_allocation() checks the threshold only when it fills.
225+
int young = _Py_atomic_load_int_relaxed(&gcstate->young.count);
226+
young += (int)gc->alloc_count;
227+
if (young < 0) {
228+
young = 0;
229+
}
227230
#endif
228231

229232
#ifndef Py_GIL_DISABLED
@@ -233,7 +236,7 @@ gc_get_count_impl(PyObject *module)
233236
gcstate->generations[2].count);
234237
#else
235238
return Py_BuildValue("(iii)",
236-
_Py_atomic_load_int_relaxed(&gcstate->young.count),
239+
young,
237240
gcstate->old[0].count,
238241
gcstate->old[1].count);
239242
#endif

0 commit comments

Comments
 (0)