https://github.com/python/cpython/commit/6a02a8848fece567d97810b170b7e5e8ce36813a
commit: 6a02a8848fece567d97810b170b7e5e8ce36813a
branch: 3.14
author: Miss Islington (bot) <[email protected]>
committer: kumaraditya303 <[email protected]>
date: 2026-06-03T11:54:42Z
summary:

[3.14] gh-148613: Fix race in `gc_set_threshold` and `gc_get_threshold` 
(GH-150356) (#150842)

gh-148613: Fix race in `gc_set_threshold` and `gc_get_threshold` (GH-150356)
(cherry picked from commit 41eb8ee2bb8cfd4129736e1f6068e702b8bdba6c)

Co-authored-by: Edward Xu <[email protected]>

files:
A 
Misc/NEWS.d/next/Core_and_Builtins/2026-05-24-22-46-49.gh-issue-148613.PLpmyd.rst
M Lib/test/test_free_threading/test_gc.py
M Modules/gcmodule.c

diff --git a/Lib/test/test_free_threading/test_gc.py 
b/Lib/test/test_free_threading/test_gc.py
index 8b45b6e2150c288..cc1888dae48bc03 100644
--- a/Lib/test/test_free_threading/test_gc.py
+++ b/Lib/test/test_free_threading/test_gc.py
@@ -94,6 +94,36 @@ def evil():
         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
index 000000000000000..71a701bf3eb3551
--- /dev/null
+++ 
b/Misc/NEWS.d/next/Core_and_Builtins/2026-05-24-22-46-49.gh-issue-148613.PLpmyd.rst
@@ -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.
diff --git a/Modules/gcmodule.c b/Modules/gcmodule.c
index 2a2ce72f82c78eb..fe1802c73dab315 100644
--- a/Modules/gcmodule.c
+++ b/Modules/gcmodule.c
@@ -168,6 +168,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;
@@ -175,6 +177,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;
 }

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to