From 1407cda1bb8eb3a2af42efd982aec38677c76566 Mon Sep 17 00:00:00 2001 From: edward_xu Date: Mon, 25 May 2026 23:41:49 +0800 Subject: [PATCH 1/2] fix `gc_generation.count` race --- Lib/test/test_free_threading/test_gc.py | 29 +++++++++++++++++++++++++ Modules/gcmodule.c | 6 ++--- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_free_threading/test_gc.py b/Lib/test/test_free_threading/test_gc.py index 8b45b6e2150c288..54cb8ccee395e3e 100644 --- a/Lib/test/test_free_threading/test_gc.py +++ b/Lib/test/test_free_threading/test_gc.py @@ -94,6 +94,35 @@ def evil(): thread.start() thread.join() + 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 = 200_000 + + 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/Modules/gcmodule.c b/Modules/gcmodule.c index 12f93ac0fdea14b..e13acc859cab5a8 100644 --- a/Modules/gcmodule.c +++ b/Modules/gcmodule.c @@ -230,9 +230,9 @@ gc_get_count_impl(PyObject *module) gcstate->generations[2].count); #else return Py_BuildValue("(iii)", - gcstate->young.count, - gcstate->old[0].count, - gcstate->old[1].count); + _Py_atomic_load_int_relaxed(&gcstate->young.count), + _Py_atomic_load_int_relaxed(&gcstate->old[0].count), + _Py_atomic_load_int_relaxed(&gcstate->old[1].count)); #endif } From 99086336c5304a1daea4e988438c813f650bf1fb Mon Sep 17 00:00:00 2001 From: edward_xu Date: Tue, 26 May 2026 00:06:37 +0800 Subject: [PATCH 2/2] add blurb --- .../2026-05-26-00-06-30.gh-issue-150411.u-d-_5.rst | 2 ++ 1 file changed, 2 insertions(+) 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/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.