gh-148613: Fix race in gc_set_threshold and gc_get_threshold (#150356)

This commit is contained in:
Edward Xu 2026-06-03 19:28:26 +08:00 committed by GitHub
parent 494f2e3c92
commit 41eb8ee2bb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 35 additions and 0 deletions

View file

@ -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()

View file

@ -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.

View 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;
}