https://github.com/python/cpython/commit/41eb8ee2bb8cfd4129736e1f6068e702b8bdba6c
commit: 41eb8ee2bb8cfd4129736e1f6068e702b8bdba6c
branch: main
author: Edward Xu <[email protected]>
committer: kumaraditya303 <[email protected]>
date: 2026-06-03T16:58:26+05:30
summary:
gh-148613: Fix race in `gc_set_threshold` and `gc_get_threshold` (#150356)
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 12f93ac0fdea14b..8762e592b258104 100644
--- a/Modules/gcmodule.c
+++ b/Modules/gcmodule.c
@@ -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;
}
_______________________________________________
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]