]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-148613: Fix race in `gc_set_threshold` and `gc_get_threshold` (#150356)
authorEdward Xu <xuxiangad@gmail.com>
Wed, 3 Jun 2026 11:28:26 +0000 (19:28 +0800)
committerGitHub <noreply@github.com>
Wed, 3 Jun 2026 11:28:26 +0000 (16:58 +0530)
Lib/test/test_free_threading/test_gc.py
Misc/NEWS.d/next/Core_and_Builtins/2026-05-24-22-46-49.gh-issue-148613.PLpmyd.rst [new file with mode: 0644]
Modules/gcmodule.c

index 8b45b6e2150c288a17c4ead5ebb77aad060bd546..cc1888dae48bc0338d1e61b22932e2b67d24865a 100644 (file)
@@ -94,6 +94,36 @@ class TestGC(TestCase):
         thread.start()
         thread.join()
 
+    def test_set_threshold(self):
+        # GH-148613: Setting the GC threshold from another thread could cause a
+        # race between the `gc_should_collect` and `gc_set_threshold` functions.
+        NUM_THREADS = 8
+        NUM_ITERS = 100_000
+        barrier = threading.Barrier(NUM_THREADS)
+
+        class CyclicReference:
+            def __init__(self):
+                self.r = self
+
+        def allocator():
+            barrier.wait()
+            for _ in range(NUM_ITERS):
+                CyclicReference()
+
+        def setter():
+            barrier.wait()
+            for i in range(NUM_ITERS):
+                gc.set_threshold(100 + (i % 100), 10 + (i % 10), 10 + (i % 10))
+
+        current_threshold = gc.get_threshold()
+        try:
+            threads = [Thread(target=allocator) for _ in range(NUM_THREADS - 1)]
+            threads.append(Thread(target=setter))
+            with threading_helper.start_threads(threads):
+                pass
+        finally:
+            gc.set_threshold(*current_threshold)
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-05-24-22-46-49.gh-issue-148613.PLpmyd.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-05-24-22-46-49.gh-issue-148613.PLpmyd.rst
new file mode 100644 (file)
index 0000000..71a701b
--- /dev/null
@@ -0,0 +1,2 @@
+Fix a data race in the free-threaded build between :func:`gc.set_threshold`
+and garbage collection scheduling during object allocation.
index 12f93ac0fdea14b0f1e5b85f08235df4a6f6babf..8762e592b2581043af03c974a4aea32c6d2c49b6 100644 (file)
@@ -167,6 +167,8 @@ gc_set_threshold_impl(PyObject *module, int threshold0, int group_right_1,
         gcstate->generations[2].threshold = threshold2;
     }
 #else
+    PyInterpreterState *interp = _PyInterpreterState_GET();
+    _PyEval_StopTheWorld(interp);
     gcstate->young.threshold = threshold0;
     if (group_right_1) {
         gcstate->old[0].threshold = threshold1;
@@ -174,6 +176,7 @@ gc_set_threshold_impl(PyObject *module, int threshold0, int group_right_1,
     if (group_right_2) {
         gcstate->old[1].threshold = threshold2;
     }
+    _PyEval_StartTheWorld(interp);
 #endif
     Py_RETURN_NONE;
 }