From 7c3d4248f80c7d4f4538f778354609082aa9d9e7 Mon Sep 17 00:00:00 2001 From: Edward Xu Date: Sun, 7 Jun 2026 01:03:04 +0800 Subject: [PATCH] gh-150411: fix `gc_generation.count` race in free-threading (GH-150413) (cherry picked from commit 12af26d17e4343c1f4ea3a1019bcc34ee76671c3) Co-authored-by: Edward Xu --- Lib/test/test_free_threading/test_gc.py | 29 +++++++++++++++++++ ...-05-26-00-06-30.gh-issue-150411.u-d-_5.rst | 2 ++ Modules/gcmodule.c | 2 +- 3 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-05-26-00-06-30.gh-issue-150411.u-d-_5.rst diff --git a/Lib/test/test_free_threading/test_gc.py b/Lib/test/test_free_threading/test_gc.py index cc1888dae48bc03..399010234509408 100644 --- a/Lib/test/test_free_threading/test_gc.py +++ b/Lib/test/test_free_threading/test_gc.py @@ -124,6 +124,35 @@ def setter(): finally: gc.set_threshold(*current_threshold) + def test_get_count(self): + class CyclicReference: + def __init__(self): + self.ref = self + + NUM_ALLOCATORS = 7 + NUM_READERS = 1 + NUM_THREADS = NUM_ALLOCATORS + NUM_READERS + NUM_ITERS = 1000 + + barrier = threading.Barrier(NUM_THREADS) + + def allocator(): + barrier.wait() + for _ in range(NUM_ITERS): + CyclicReference() + + + def reader(): + barrier.wait() + for _ in range(NUM_ITERS): + gc.get_count() + + threads = [Thread(target=allocator) for _ in range(NUM_ALLOCATORS)] + threads.extend(Thread(target=reader) for _ in range(NUM_READERS)) + + with threading_helper.start_threads(threads): + pass + if __name__ == "__main__": unittest.main() diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-05-26-00-06-30.gh-issue-150411.u-d-_5.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-05-26-00-06-30.gh-issue-150411.u-d-_5.rst new file mode 100644 index 000000000000000..5b19a4fff5ddc73 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-05-26-00-06-30.gh-issue-150411.u-d-_5.rst @@ -0,0 +1,2 @@ +Fix a data race in the free-threaded build when :func:`gc.get_count` reads +the young generation allocation count while another thread updates it. diff --git a/Modules/gcmodule.c b/Modules/gcmodule.c index fe1802c73dab315..7bb1dcc076ee495 100644 --- a/Modules/gcmodule.c +++ b/Modules/gcmodule.c @@ -234,7 +234,7 @@ gc_get_count_impl(PyObject *module) gcstate->generations[2].count); #else return Py_BuildValue("(iii)", - gcstate->young.count, + _Py_atomic_load_int_relaxed(&gcstate->young.count), gcstate->old[0].count, gcstate->old[1].count); #endif