Skip to content

Commit 884302c

Browse files
[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 c215919) Co-authored-by: Nathan Goldbaum <nathan.goldbaum@gmail.com>
1 parent 02d4c3b commit 884302c

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
@@ -1591,6 +1591,25 @@ def test_indirect_calls_with_gc_disabled(self):
15911591
finally:
15921592
gc.enable()
15931593

1594+
def test_get_count_nonnegative(self):
1595+
xs = [[] for _ in range(1500)]
1596+
gc.collect()
1597+
del xs[:1000]
1598+
self.assertGreaterEqual(gc.get_count()[0], 0)
1599+
1600+
@gc_threshold(1000, 0, 0)
1601+
def test_get_count_does_not_prevent_collection(self):
1602+
junk = []
1603+
gc.collect()
1604+
detector = GC_Detector()
1605+
for _ in range(10000):
1606+
junk.append([])
1607+
gc.get_count()
1608+
if detector.gc_happened:
1609+
break
1610+
else:
1611+
self.fail("gc didn't happen after 10000 iterations")
1612+
15941613
# Ensure that setting *threshold0* to zero disables collection.
15951614
@gc_threshold(0)
15961615
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
@@ -222,9 +222,12 @@ gc_get_count_impl(PyObject *module)
222222
_PyThreadStateImpl *tstate = (_PyThreadStateImpl *)_PyThreadState_GET();
223223
struct _gc_thread_state *gc = &tstate->gc;
224224

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

230233
#ifndef Py_GIL_DISABLED
@@ -234,7 +237,7 @@ gc_get_count_impl(PyObject *module)
234237
gcstate->generations[2].count);
235238
#else
236239
return Py_BuildValue("(iii)",
237-
_Py_atomic_load_int_relaxed(&gcstate->young.count),
240+
young,
238241
gcstate->old[0].count,
239242
gcstate->old[1].count);
240243
#endif

0 commit comments

Comments
 (0)