Skip to content

Commit 41eb8ee

Browse files
authored
gh-148613: Fix race in gc_set_threshold and gc_get_threshold (#150356)
1 parent 494f2e3 commit 41eb8ee

3 files changed

Lines changed: 35 additions & 0 deletions

File tree

Lib/test/test_free_threading/test_gc.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,36 @@ def evil():
9494
thread.start()
9595
thread.join()
9696

97+
def test_set_threshold(self):
98+
# GH-148613: Setting the GC threshold from another thread could cause a
99+
# race between the `gc_should_collect` and `gc_set_threshold` functions.
100+
NUM_THREADS = 8
101+
NUM_ITERS = 100_000
102+
barrier = threading.Barrier(NUM_THREADS)
103+
104+
class CyclicReference:
105+
def __init__(self):
106+
self.r = self
107+
108+
def allocator():
109+
barrier.wait()
110+
for _ in range(NUM_ITERS):
111+
CyclicReference()
112+
113+
def setter():
114+
barrier.wait()
115+
for i in range(NUM_ITERS):
116+
gc.set_threshold(100 + (i % 100), 10 + (i % 10), 10 + (i % 10))
117+
118+
current_threshold = gc.get_threshold()
119+
try:
120+
threads = [Thread(target=allocator) for _ in range(NUM_THREADS - 1)]
121+
threads.append(Thread(target=setter))
122+
with threading_helper.start_threads(threads):
123+
pass
124+
finally:
125+
gc.set_threshold(*current_threshold)
126+
97127

98128
if __name__ == "__main__":
99129
unittest.main()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix a data race in the free-threaded build between :func:`gc.set_threshold`
2+
and garbage collection scheduling during object allocation.

Modules/gcmodule.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,13 +167,16 @@ gc_set_threshold_impl(PyObject *module, int threshold0, int group_right_1,
167167
gcstate->generations[2].threshold = threshold2;
168168
}
169169
#else
170+
PyInterpreterState *interp = _PyInterpreterState_GET();
171+
_PyEval_StopTheWorld(interp);
170172
gcstate->young.threshold = threshold0;
171173
if (group_right_1) {
172174
gcstate->old[0].threshold = threshold1;
173175
}
174176
if (group_right_2) {
175177
gcstate->old[1].threshold = threshold2;
176178
}
179+
_PyEval_StartTheWorld(interp);
177180
#endif
178181
Py_RETURN_NONE;
179182
}

0 commit comments

Comments
 (0)