Skip to content

Commit 5e5e4f1

Browse files
committed
gh-137400: Fix a crash when disabling profiling across all threads
The `PyEval_SetProfileAllThreads` function and other related functions had a race condition on `tstate->c_profilefunc` that could lead to a crash when disable profiling or tracing on all threads while another thread is starting to profile or trace a a call. There are still potential crashes when threads exit concurrently with profiling or tracing be enabled/disabled across all threads.
1 parent 525784a commit 5e5e4f1

File tree

3 files changed

+46
-2
lines changed

3 files changed

+46
-2
lines changed

Lib/test/test_free_threading/test_monitoring.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,40 @@ def during_threads(self):
194194
self.set = not self.set
195195

196196

197+
@threading_helper.requires_working_threading()
198+
class SetProfileAllMultiThreaded(TestCase):
199+
def test_profile_all_threads(self):
200+
done = threading.Event()
201+
202+
def func():
203+
pass
204+
205+
def bg_thread():
206+
while not done.is_set():
207+
func()
208+
func()
209+
func()
210+
func()
211+
func()
212+
213+
def my_profile(frame, event, arg):
214+
return None
215+
216+
bg_threads = []
217+
for i in range(10):
218+
t = threading.Thread(target=bg_thread)
219+
t.start()
220+
bg_threads.append(t)
221+
222+
for i in range(100):
223+
threading.setprofile_all_threads(my_profile)
224+
threading.setprofile_all_threads(None)
225+
226+
done.set()
227+
for t in bg_threads:
228+
t.join()
229+
230+
197231
class TraceBuf:
198232
def __init__(self):
199233
self.traces = []
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fix a crash in the :term:`free threading` build when disabling profiling or tracing
2+
across all threads with :c:func:`PyEval_SetProfileAllThreads` or
3+
:c:func:`PyEval_SetTraceAllThreads` or their Python equivalents
4+
:func:`threading.settrace_all_threads` and :func:`threading.setprofile_all_threads`.

Python/legacy_tracing.c

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -484,13 +484,16 @@ setup_profile(PyThreadState *tstate, Py_tracefunc func, PyObject *arg, PyObject
484484
}
485485
}
486486

487+
_PyEval_StopTheWorld(tstate->interp);
487488
int delta = (func != NULL) - (tstate->c_profilefunc != NULL);
488489
tstate->c_profilefunc = func;
489490
*old_profileobj = tstate->c_profileobj;
490491
tstate->c_profileobj = Py_XNewRef(arg);
491492
tstate->interp->sys_profiling_threads += delta;
492493
assert(tstate->interp->sys_profiling_threads >= 0);
493-
return tstate->interp->sys_profiling_threads;
494+
Py_ssize_t profiling_threads = tstate->interp->sys_profiling_threads;
495+
_PyEval_StartTheWorld(tstate->interp);
496+
return profiling_threads;
494497
}
495498

496499
int
@@ -581,13 +584,16 @@ setup_tracing(PyThreadState *tstate, Py_tracefunc func, PyObject *arg, PyObject
581584
}
582585
}
583586

587+
_PyEval_StopTheWorld(tstate->interp);
584588
int delta = (func != NULL) - (tstate->c_tracefunc != NULL);
585589
tstate->c_tracefunc = func;
586590
*old_traceobj = tstate->c_traceobj;
587591
tstate->c_traceobj = Py_XNewRef(arg);
588592
tstate->interp->sys_tracing_threads += delta;
589593
assert(tstate->interp->sys_tracing_threads >= 0);
590-
return tstate->interp->sys_tracing_threads;
594+
Py_ssize_t tracing_threads = tstate->interp->sys_tracing_threads;
595+
_PyEval_StartTheWorld(tstate->interp);
596+
return tracing_threads;
591597
}
592598

593599
int

0 commit comments

Comments
 (0)