Skip to content

Commit e236568

Browse files
authored
[3.13] gh-137400: Fix a crash when disabling profiling across all threads (gh-137471) (gh-137649)
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. (cherry picked from commit 3626928)
1 parent 63a9494 commit e236568

File tree

3 files changed

+47
-2
lines changed

3 files changed

+47
-2
lines changed

Lib/test/test_free_threading/test_monitoring.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
environment to verify things are thread-safe in a free-threaded build"""
33

44
import sys
5+
import threading
56
import time
67
import unittest
78
import weakref
@@ -192,6 +193,40 @@ def during_threads(self):
192193
self.set = not self.set
193194

194195

196+
@threading_helper.requires_working_threading()
197+
class SetProfileAllMultiThreaded(TestCase):
198+
def test_profile_all_threads(self):
199+
done = threading.Event()
200+
201+
def func():
202+
pass
203+
204+
def bg_thread():
205+
while not done.is_set():
206+
func()
207+
func()
208+
func()
209+
func()
210+
func()
211+
212+
def my_profile(frame, event, arg):
213+
return None
214+
215+
bg_threads = []
216+
for i in range(10):
217+
t = threading.Thread(target=bg_thread)
218+
t.start()
219+
bg_threads.append(t)
220+
221+
for i in range(100):
222+
threading.setprofile_all_threads(my_profile)
223+
threading.setprofile_all_threads(None)
224+
225+
done.set()
226+
for t in bg_threads:
227+
t.join()
228+
229+
195230
@threading_helper.requires_working_threading()
196231
class MonitoringMisc(MonitoringTestMixin, TestCase):
197232
def register_callback(self):
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
@@ -479,13 +479,16 @@ setup_profile(PyThreadState *tstate, Py_tracefunc func, PyObject *arg, PyObject
479479
}
480480
}
481481

482+
_PyEval_StopTheWorld(tstate->interp);
482483
int delta = (func != NULL) - (tstate->c_profilefunc != NULL);
483484
tstate->c_profilefunc = func;
484485
*old_profileobj = tstate->c_profileobj;
485486
tstate->c_profileobj = Py_XNewRef(arg);
486487
tstate->interp->sys_profiling_threads += delta;
487488
assert(tstate->interp->sys_profiling_threads >= 0);
488-
return tstate->interp->sys_profiling_threads;
489+
Py_ssize_t profiling_threads = tstate->interp->sys_profiling_threads;
490+
_PyEval_StartTheWorld(tstate->interp);
491+
return profiling_threads;
489492
}
490493

491494
int
@@ -574,13 +577,16 @@ setup_tracing(PyThreadState *tstate, Py_tracefunc func, PyObject *arg, PyObject
574577
}
575578
}
576579

580+
_PyEval_StopTheWorld(tstate->interp);
577581
int delta = (func != NULL) - (tstate->c_tracefunc != NULL);
578582
tstate->c_tracefunc = func;
579583
*old_traceobj = tstate->c_traceobj;
580584
tstate->c_traceobj = Py_XNewRef(arg);
581585
tstate->interp->sys_tracing_threads += delta;
582586
assert(tstate->interp->sys_tracing_threads >= 0);
583-
return tstate->interp->sys_tracing_threads;
587+
Py_ssize_t tracing_threads = tstate->interp->sys_tracing_threads;
588+
_PyEval_StartTheWorld(tstate->interp);
589+
return tracing_threads;
584590
}
585591

586592
int

0 commit comments

Comments
 (0)