https://github.com/python/cpython/commit/a10152f8fd0f4b291e53d646cffe22fbeec73e1e
commit: a10152f8fd0f4b291e53d646cffe22fbeec73e1e
branch: main
author: Sam Gross <colesb...@gmail.com>
committer: colesbury <colesb...@gmail.com>
date: 2025-08-13T14:15:12-04:00
summary:

gh-137400: Fix thread-safety issues when profiling all threads (gh-137518)

There were a few thread-safety issues when profiling or tracing all
threads via PyEval_SetProfileAllThreads or PyEval_SetTraceAllThreads:

* The loop over thread states could crash if a thread exits concurrently
  (in both the free threading and default build)
* The modification of `c_profilefunc` and `c_tracefunc` wasn't
  thread-safe on the free threading build.

files:
A 
Misc/NEWS.d/next/Core_and_Builtins/2025-08-07-09-52-19.gh-issue-137400.AK1dy-.rst
M Include/internal/pycore_ceval.h
M Include/internal/pycore_interp_structs.h
M Lib/test/test_free_threading/test_monitoring.py
M Python/bytecodes.c
M Python/ceval.c
M Python/generated_cases.c.h
M Python/instrumentation.c
M Python/legacy_tracing.c
M Python/pystate.c
M Python/sysmodule.c

diff --git a/Include/internal/pycore_ceval.h b/Include/internal/pycore_ceval.h
index cc2defbdf77821..86eb405da659cc 100644
--- a/Include/internal/pycore_ceval.h
+++ b/Include/internal/pycore_ceval.h
@@ -22,8 +22,10 @@ struct _ceval_runtime_state;
 
 // Export for '_lsprof' shared extension
 PyAPI_FUNC(int) _PyEval_SetProfile(PyThreadState *tstate, Py_tracefunc func, 
PyObject *arg);
+extern int _PyEval_SetProfileAllThreads(PyInterpreterState *interp, 
Py_tracefunc func, PyObject *arg);
 
 extern int _PyEval_SetTrace(PyThreadState *tstate, Py_tracefunc func, PyObject 
*arg);
+extern int _PyEval_SetTraceAllThreads(PyInterpreterState *interp, Py_tracefunc 
func, PyObject *arg);
 
 extern int _PyEval_SetOpcodeTrace(PyFrameObject *f, bool enable);
 
diff --git a/Include/internal/pycore_interp_structs.h 
b/Include/internal/pycore_interp_structs.h
index 7cb5bce546ac74..e300732e9e58c3 100644
--- a/Include/internal/pycore_interp_structs.h
+++ b/Include/internal/pycore_interp_structs.h
@@ -99,7 +99,6 @@ struct _ceval_runtime_state {
     // For example, we use a preallocated array
     // for the list of pending calls.
     struct _pending_calls pending_mainthread;
-    PyMutex sys_trace_profile_mutex;
 };
 
 
@@ -951,8 +950,8 @@ struct _is {
     PyDict_WatchCallback builtins_dict_watcher;
 
     _Py_GlobalMonitors monitors;
-    bool sys_profile_initialized;
-    bool sys_trace_initialized;
+    _PyOnceFlag sys_profile_once_flag;
+    _PyOnceFlag sys_trace_once_flag;
     Py_ssize_t sys_profiling_threads; /* Count of threads with c_profilefunc 
set */
     Py_ssize_t sys_tracing_threads; /* Count of threads with c_tracefunc set */
     PyObject 
*monitoring_callables[PY_MONITORING_TOOL_IDS][_PY_MONITORING_EVENTS];
diff --git a/Lib/test/test_free_threading/test_monitoring.py 
b/Lib/test/test_free_threading/test_monitoring.py
index 9b9fc19364391f..407bf7cbdee917 100644
--- a/Lib/test/test_free_threading/test_monitoring.py
+++ b/Lib/test/test_free_threading/test_monitoring.py
@@ -195,6 +195,31 @@ def during_threads(self):
 
 
 @threading_helper.requires_working_threading()
+class SetProfileAllThreadsMultiThreaded(InstrumentationMultiThreadedMixin, 
TestCase):
+    """Uses threading.setprofile_all_threads and repeatedly toggles 
instrumentation on and off"""
+
+    def setUp(self):
+        self.set = False
+        self.called = False
+
+    def after_test(self):
+        self.assertTrue(self.called)
+
+    def tearDown(self):
+        threading.setprofile_all_threads(None)
+
+    def trace_func(self, frame, event, arg):
+        self.called = True
+        return self.trace_func
+
+    def during_threads(self):
+        if self.set:
+            threading.setprofile_all_threads(self.trace_func)
+        else:
+            threading.setprofile_all_threads(None)
+        self.set = not self.set
+
+
 class SetProfileAllMultiThreaded(TestCase):
     def test_profile_all_threads(self):
         done = threading.Event()
@@ -421,6 +446,38 @@ def noop():
 
         self.observe_threads(noop, buf)
 
+    def test_trace_concurrent(self):
+        # Test calling a function concurrently from a tracing and a non-tracing
+        # thread
+        b = threading.Barrier(2)
+
+        def func():
+            for _ in range(100):
+                pass
+
+        def noop():
+            pass
+
+        def bg_thread():
+            b.wait()
+            func()  # this may instrument `func`
+
+        def tracefunc(frame, event, arg):
+            # These calls run under tracing can race with the background thread
+            for _ in range(10):
+                func()
+            return tracefunc
+
+        t = Thread(target=bg_thread)
+        t.start()
+        try:
+            sys.settrace(tracefunc)
+            b.wait()
+            noop()
+        finally:
+            sys.settrace(None)
+        t.join()
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git 
a/Misc/NEWS.d/next/Core_and_Builtins/2025-08-07-09-52-19.gh-issue-137400.AK1dy-.rst
 
b/Misc/NEWS.d/next/Core_and_Builtins/2025-08-07-09-52-19.gh-issue-137400.AK1dy-.rst
new file mode 100644
index 00000000000000..406d6528840ba5
--- /dev/null
+++ 
b/Misc/NEWS.d/next/Core_and_Builtins/2025-08-07-09-52-19.gh-issue-137400.AK1dy-.rst
@@ -0,0 +1,5 @@
+Fix a crash in the :term:`free threading` build when disabling profiling or
+tracing across all threads with :c:func:`PyEval_SetProfileAllThreads` or
+:c:func:`PyEval_SetTraceAllThreads` or their Python equivalents
+:func:`threading.settrace_all_threads` and
+:func:`threading.setprofile_all_threads`.
diff --git a/Python/bytecodes.c b/Python/bytecodes.c
index 4d6dbe5116626d..8a60e48cd465b5 100644
--- a/Python/bytecodes.c
+++ b/Python/bytecodes.c
@@ -178,7 +178,15 @@ dummy_func(
         }
 
         tier1 op(_MAYBE_INSTRUMENT, (--)) {
-            if (tstate->tracing == 0) {
+            #ifdef Py_GIL_DISABLED
+            // For thread-safety, we need to check instrumentation version
+            // even when tracing. Otherwise, another thread may concurrently
+            // re-write the bytecode while we are executing this function.
+            int check_instrumentation = 1;
+            #else
+            int check_instrumentation = (tstate->tracing == 0);
+            #endif
+            if (check_instrumentation) {
                 uintptr_t global_version = 
_Py_atomic_load_uintptr_relaxed(&tstate->eval_breaker) & ~_PY_EVAL_EVENTS_MASK;
                 uintptr_t code_version = 
FT_ATOMIC_LOAD_UINTPTR_ACQUIRE(_PyFrame_GetCode(frame)->_co_instrumentation_version);
                 if (code_version != global_version) {
diff --git a/Python/ceval.c b/Python/ceval.c
index 94c24187f6b25f..b8c1dd3e3bf74b 100644
--- a/Python/ceval.c
+++ b/Python/ceval.c
@@ -2510,21 +2510,10 @@ PyEval_SetProfile(Py_tracefunc func, PyObject *arg)
 void
 PyEval_SetProfileAllThreads(Py_tracefunc func, PyObject *arg)
 {
-    PyThreadState *this_tstate = _PyThreadState_GET();
-    PyInterpreterState* interp = this_tstate->interp;
-
-    _PyRuntimeState *runtime = &_PyRuntime;
-    HEAD_LOCK(runtime);
-    PyThreadState* ts = PyInterpreterState_ThreadHead(interp);
-    HEAD_UNLOCK(runtime);
-
-    while (ts) {
-        if (_PyEval_SetProfile(ts, func, arg) < 0) {
-            PyErr_FormatUnraisable("Exception ignored in 
PyEval_SetProfileAllThreads");
-        }
-        HEAD_LOCK(runtime);
-        ts = PyThreadState_Next(ts);
-        HEAD_UNLOCK(runtime);
+    PyInterpreterState *interp = _PyInterpreterState_GET();
+    if (_PyEval_SetProfileAllThreads(interp, func, arg) < 0) {
+        /* Log _PySys_Audit() error */
+        PyErr_FormatUnraisable("Exception ignored in 
PyEval_SetProfileAllThreads");
     }
 }
 
@@ -2541,21 +2530,10 @@ PyEval_SetTrace(Py_tracefunc func, PyObject *arg)
 void
 PyEval_SetTraceAllThreads(Py_tracefunc func, PyObject *arg)
 {
-    PyThreadState *this_tstate = _PyThreadState_GET();
-    PyInterpreterState* interp = this_tstate->interp;
-
-    _PyRuntimeState *runtime = &_PyRuntime;
-    HEAD_LOCK(runtime);
-    PyThreadState* ts = PyInterpreterState_ThreadHead(interp);
-    HEAD_UNLOCK(runtime);
-
-    while (ts) {
-        if (_PyEval_SetTrace(ts, func, arg) < 0) {
-            PyErr_FormatUnraisable("Exception ignored in 
PyEval_SetTraceAllThreads");
-        }
-        HEAD_LOCK(runtime);
-        ts = PyThreadState_Next(ts);
-        HEAD_UNLOCK(runtime);
+    PyInterpreterState *interp = _PyInterpreterState_GET();
+    if (_PyEval_SetTraceAllThreads(interp, func, arg) < 0) {
+        /* Log _PySys_Audit() error */
+        PyErr_FormatUnraisable("Exception ignored in 
PyEval_SetTraceAllThreads");
     }
 }
 
diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h
index d683582761295a..d63225bb0cd3cd 100644
--- a/Python/generated_cases.c.h
+++ b/Python/generated_cases.c.h
@@ -7334,7 +7334,13 @@
             }
             // _MAYBE_INSTRUMENT
             {
-                if (tstate->tracing == 0) {
+                #ifdef Py_GIL_DISABLED
+
+                int check_instrumentation = 1;
+                #else
+                int check_instrumentation = (tstate->tracing == 0);
+                #endif
+                if (check_instrumentation) {
                     uintptr_t global_version = 
_Py_atomic_load_uintptr_relaxed(&tstate->eval_breaker) & ~_PY_EVAL_EVENTS_MASK;
                     uintptr_t code_version = 
FT_ATOMIC_LOAD_UINTPTR_ACQUIRE(_PyFrame_GetCode(frame)->_co_instrumentation_version);
                     if (code_version != global_version) {
@@ -10245,7 +10251,13 @@
             }
             // _MAYBE_INSTRUMENT
             {
-                if (tstate->tracing == 0) {
+                #ifdef Py_GIL_DISABLED
+
+                int check_instrumentation = 1;
+                #else
+                int check_instrumentation = (tstate->tracing == 0);
+                #endif
+                if (check_instrumentation) {
                     uintptr_t global_version = 
_Py_atomic_load_uintptr_relaxed(&tstate->eval_breaker) & ~_PY_EVAL_EVENTS_MASK;
                     uintptr_t code_version = 
FT_ATOMIC_LOAD_UINTPTR_ACQUIRE(_PyFrame_GetCode(frame)->_co_instrumentation_version);
                     if (code_version != global_version) {
diff --git a/Python/instrumentation.c b/Python/instrumentation.c
index f9913cff402ed1..b4b2bc5dc69f9d 100644
--- a/Python/instrumentation.c
+++ b/Python/instrumentation.c
@@ -1040,6 +1040,8 @@ set_version_raw(uintptr_t *ptr, uint32_t version)
 static void
 set_global_version(PyThreadState *tstate, uint32_t version)
 {
+    ASSERT_WORLD_STOPPED();
+
     assert((version & _PY_EVAL_EVENTS_MASK) == 0);
     PyInterpreterState *interp = tstate->interp;
     set_version_raw(&interp->ceval.instrumentation_version, version);
@@ -1939,28 +1941,26 @@ _Py_Instrument(PyCodeObject *code, PyInterpreterState 
*interp)
 
 
 static int
-instrument_all_executing_code_objects(PyInterpreterState *interp) {
+instrument_all_executing_code_objects(PyInterpreterState *interp)
+{
     ASSERT_WORLD_STOPPED();
 
-    _PyRuntimeState *runtime = &_PyRuntime;
-    HEAD_LOCK(runtime);
-    PyThreadState* ts = PyInterpreterState_ThreadHead(interp);
-    HEAD_UNLOCK(runtime);
-    while (ts) {
+    int err = 0;
+    _Py_FOR_EACH_TSTATE_BEGIN(interp, ts) {
         _PyInterpreterFrame *frame = ts->current_frame;
         while (frame) {
             if (frame->owner < FRAME_OWNED_BY_INTERPRETER) {
-                if (instrument_lock_held(_PyFrame_GetCode(frame), interp)) {
-                    return -1;
+                err = instrument_lock_held(_PyFrame_GetCode(frame), interp);
+                if (err) {
+                    goto done;
                 }
             }
             frame = frame->previous;
         }
-        HEAD_LOCK(runtime);
-        ts = PyThreadState_Next(ts);
-        HEAD_UNLOCK(runtime);
     }
-    return 0;
+done:
+    _Py_FOR_EACH_TSTATE_END(interp);
+    return err;
 }
 
 static void
@@ -2006,6 +2006,7 @@ check_tool(PyInterpreterState *interp, int tool_id)
 int
 _PyMonitoring_SetEvents(int tool_id, _PyMonitoringEventSet events)
 {
+    ASSERT_WORLD_STOPPED();
     assert(0 <= tool_id && tool_id < PY_MONITORING_TOOL_IDS);
     PyThreadState *tstate = _PyThreadState_GET();
     PyInterpreterState *interp = tstate->interp;
@@ -2014,33 +2015,28 @@ _PyMonitoring_SetEvents(int tool_id, 
_PyMonitoringEventSet events)
         return -1;
     }
 
-    int res;
-    _PyEval_StopTheWorld(interp);
     uint32_t existing_events = get_events(&interp->monitors, tool_id);
     if (existing_events == events) {
-        res = 0;
-        goto done;
+        return 0;
     }
     set_events(&interp->monitors, tool_id, events);
     uint32_t new_version = global_version(interp) + 
MONITORING_VERSION_INCREMENT;
     if (new_version == 0) {
         PyErr_Format(PyExc_OverflowError, "events set too many times");
-        res = -1;
-        goto done;
+        return -1;
     }
     set_global_version(tstate, new_version);
 #ifdef _Py_TIER2
     _Py_Executors_InvalidateAll(interp, 1);
 #endif
-    res = instrument_all_executing_code_objects(interp);
-done:
-    _PyEval_StartTheWorld(interp);
-    return res;
+    return instrument_all_executing_code_objects(interp);
 }
 
 int
 _PyMonitoring_SetLocalEvents(PyCodeObject *code, int tool_id, 
_PyMonitoringEventSet events)
 {
+    ASSERT_WORLD_STOPPED();
+
     assert(0 <= tool_id && tool_id < PY_MONITORING_TOOL_IDS);
     PyInterpreterState *interp = _PyInterpreterState_GET();
     assert(events < (1 << _PY_MONITORING_LOCAL_EVENTS));
@@ -2052,11 +2048,8 @@ _PyMonitoring_SetLocalEvents(PyCodeObject *code, int 
tool_id, _PyMonitoringEvent
         return -1;
     }
 
-    int res;
-    _PyEval_StopTheWorld(interp);
     if (allocate_instrumentation_data(code)) {
-        res = -1;
-        goto done;
+        return -1;
     }
 
     code->_co_monitoring->tool_versions[tool_id] = 
interp->monitoring_tool_versions[tool_id];
@@ -2064,16 +2057,11 @@ _PyMonitoring_SetLocalEvents(PyCodeObject *code, int 
tool_id, _PyMonitoringEvent
     _Py_LocalMonitors *local = &code->_co_monitoring->local_monitors;
     uint32_t existing_events = get_local_events(local, tool_id);
     if (existing_events == events) {
-        res = 0;
-        goto done;
+        return 0;
     }
     set_local_events(local, tool_id, events);
 
-    res = force_instrument_lock_held(code, interp);
-
-done:
-    _PyEval_StartTheWorld(interp);
-    return res;
+    return force_instrument_lock_held(code, interp);
 }
 
 int
@@ -2105,11 +2093,12 @@ int _PyMonitoring_ClearToolId(int tool_id)
         }
     }
 
+    _PyEval_StopTheWorld(interp);
     if (_PyMonitoring_SetEvents(tool_id, 0) < 0) {
+        _PyEval_StartTheWorld(interp);
         return -1;
     }
 
-    _PyEval_StopTheWorld(interp);
     uint32_t version = global_version(interp) + MONITORING_VERSION_INCREMENT;
     if (version == 0) {
         PyErr_Format(PyExc_OverflowError, "events set too many times");
@@ -2346,7 +2335,11 @@ monitoring_set_events_impl(PyObject *module, int 
tool_id, int event_set)
         event_set &= ~(1 << PY_MONITORING_EVENT_BRANCH);
         event_set |= (1 << PY_MONITORING_EVENT_BRANCH_RIGHT) | (1 << 
PY_MONITORING_EVENT_BRANCH_LEFT);
     }
-    if (_PyMonitoring_SetEvents(tool_id, event_set)) {
+    PyInterpreterState *interp = _PyInterpreterState_GET();
+    _PyEval_StopTheWorld(interp);
+    int err = _PyMonitoring_SetEvents(tool_id, event_set);
+    _PyEval_StartTheWorld(interp);
+    if (err) {
         return NULL;
     }
     Py_RETURN_NONE;
@@ -2427,7 +2420,11 @@ monitoring_set_local_events_impl(PyObject *module, int 
tool_id,
         return NULL;
     }
 
-    if (_PyMonitoring_SetLocalEvents((PyCodeObject*)code, tool_id, event_set)) 
{
+    PyInterpreterState *interp = _PyInterpreterState_GET();
+    _PyEval_StopTheWorld(interp);
+    int err = _PyMonitoring_SetLocalEvents((PyCodeObject*)code, tool_id, 
event_set);
+    _PyEval_StartTheWorld(interp);
+    if (err) {
         return NULL;
     }
     Py_RETURN_NONE;
diff --git a/Python/legacy_tracing.c b/Python/legacy_tracing.c
index d14a1f21879627..594d5c5ead5021 100644
--- a/Python/legacy_tracing.c
+++ b/Python/legacy_tracing.c
@@ -126,16 +126,10 @@ sys_profile_call_or_return(
     Py_RETURN_NONE;
 }
 
-int
-_PyEval_SetOpcodeTrace(
-    PyFrameObject *frame,
-    bool enable
-) {
-    assert(frame != NULL);
-
-    PyCodeObject *code = _PyFrame_GetCode(frame->f_frame);
+static int
+set_opcode_trace_world_stopped(PyCodeObject *code, bool enable)
+{
     _PyMonitoringEventSet events = 0;
-
     if (_PyMonitoring_GetLocalEvents(code, PY_MONITORING_SYS_TRACE_ID, 
&events) < 0) {
         return -1;
     }
@@ -154,6 +148,32 @@ _PyEval_SetOpcodeTrace(
     return _PyMonitoring_SetLocalEvents(code, PY_MONITORING_SYS_TRACE_ID, 
events);
 }
 
+int
+_PyEval_SetOpcodeTrace(PyFrameObject *frame, bool enable)
+{
+    assert(frame != NULL);
+
+    PyCodeObject *code = _PyFrame_GetCode(frame->f_frame);
+
+#ifdef Py_GIL_DISABLED
+    // First check if a change is necessary outside of the stop-the-world pause
+    _PyMonitoringEventSet events = 0;
+    if (_PyMonitoring_GetLocalEvents(code, PY_MONITORING_SYS_TRACE_ID, 
&events) < 0) {
+        return -1;
+    }
+    int is_enabled = (events & (1 << PY_MONITORING_EVENT_INSTRUCTION)) != 0;
+    if (is_enabled == enable) {
+        return 0;  // No change needed
+    }
+#endif
+
+    PyInterpreterState *interp = _PyInterpreterState_GET();
+    _PyEval_StopTheWorld(interp);
+    int res = set_opcode_trace_world_stopped(code, enable);
+    _PyEval_StartTheWorld(interp);
+    return res;
+}
+
 static PyObject *
 call_trace_func(_PyLegacyEventHandler *self, PyObject *arg)
 {
@@ -431,62 +451,74 @@ is_tstate_valid(PyThreadState *tstate)
 }
 #endif
 
-static Py_ssize_t
-setup_profile(PyThreadState *tstate, Py_tracefunc func, PyObject *arg, 
PyObject **old_profileobj)
+static int
+setup_profile_callbacks(void *Py_UNUSED(arg))
 {
-    *old_profileobj = NULL;
     /* Setup PEP 669 monitoring callbacks and events. */
-    if (!tstate->interp->sys_profile_initialized) {
-        tstate->interp->sys_profile_initialized = true;
-        if (set_callbacks(PY_MONITORING_SYS_PROFILE_ID,
-                          sys_profile_start, PyTrace_CALL,
-                          PY_MONITORING_EVENT_PY_START,
-                          PY_MONITORING_EVENT_PY_RESUME)) {
-            return -1;
-        }
-        if (set_callbacks(PY_MONITORING_SYS_PROFILE_ID,
-                          sys_profile_throw, PyTrace_CALL,
-                          PY_MONITORING_EVENT_PY_THROW, -1)) {
-            return -1;
-        }
-        if (set_callbacks(PY_MONITORING_SYS_PROFILE_ID,
-                          sys_profile_return, PyTrace_RETURN,
-                          PY_MONITORING_EVENT_PY_RETURN,
-                          PY_MONITORING_EVENT_PY_YIELD)) {
-            return -1;
-        }
-        if (set_callbacks(PY_MONITORING_SYS_PROFILE_ID,
-                          sys_profile_unwind, PyTrace_RETURN,
-                          PY_MONITORING_EVENT_PY_UNWIND, -1)) {
-            return -1;
-        }
-        if (set_callbacks(PY_MONITORING_SYS_PROFILE_ID,
-                          sys_profile_call_or_return, PyTrace_C_CALL,
-                          PY_MONITORING_EVENT_CALL, -1)) {
-            return -1;
-        }
-        if (set_callbacks(PY_MONITORING_SYS_PROFILE_ID,
-                          sys_profile_call_or_return, PyTrace_C_RETURN,
-                          PY_MONITORING_EVENT_C_RETURN, -1)) {
-            return -1;
-        }
-        if (set_callbacks(PY_MONITORING_SYS_PROFILE_ID,
-                          sys_profile_call_or_return, PyTrace_C_EXCEPTION,
-                          PY_MONITORING_EVENT_C_RAISE, -1)) {
-            return -1;
-        }
+    if (set_callbacks(PY_MONITORING_SYS_PROFILE_ID,
+                      sys_profile_start, PyTrace_CALL,
+                      PY_MONITORING_EVENT_PY_START,
+                      PY_MONITORING_EVENT_PY_RESUME)) {
+        return -1;
+    }
+    if (set_callbacks(PY_MONITORING_SYS_PROFILE_ID,
+                      sys_profile_throw, PyTrace_CALL,
+                      PY_MONITORING_EVENT_PY_THROW, -1)) {
+        return -1;
+    }
+    if (set_callbacks(PY_MONITORING_SYS_PROFILE_ID,
+                      sys_profile_return, PyTrace_RETURN,
+                      PY_MONITORING_EVENT_PY_RETURN,
+                      PY_MONITORING_EVENT_PY_YIELD)) {
+        return -1;
     }
+    if (set_callbacks(PY_MONITORING_SYS_PROFILE_ID,
+                      sys_profile_unwind, PyTrace_RETURN,
+                      PY_MONITORING_EVENT_PY_UNWIND, -1)) {
+        return -1;
+    }
+    if (set_callbacks(PY_MONITORING_SYS_PROFILE_ID,
+                      sys_profile_call_or_return, PyTrace_C_CALL,
+                      PY_MONITORING_EVENT_CALL, -1)) {
+        return -1;
+    }
+    if (set_callbacks(PY_MONITORING_SYS_PROFILE_ID,
+                      sys_profile_call_or_return, PyTrace_C_RETURN,
+                      PY_MONITORING_EVENT_C_RETURN, -1)) {
+        return -1;
+    }
+    if (set_callbacks(PY_MONITORING_SYS_PROFILE_ID,
+                      sys_profile_call_or_return, PyTrace_C_EXCEPTION,
+                      PY_MONITORING_EVENT_C_RAISE, -1)) {
+        return -1;
+    }
+    return 0;
+}
 
-    _PyEval_StopTheWorld(tstate->interp);
+static PyObject *
+swap_profile_func_arg(PyThreadState *tstate, Py_tracefunc func, PyObject *arg)
+{
     int delta = (func != NULL) - (tstate->c_profilefunc != NULL);
     tstate->c_profilefunc = func;
-    *old_profileobj = tstate->c_profileobj;
+    PyObject *old_profileobj = tstate->c_profileobj;
     tstate->c_profileobj = Py_XNewRef(arg);
     tstate->interp->sys_profiling_threads += delta;
     assert(tstate->interp->sys_profiling_threads >= 0);
-    Py_ssize_t profiling_threads = tstate->interp->sys_profiling_threads;
-    _PyEval_StartTheWorld(tstate->interp);
-    return profiling_threads;
+    return old_profileobj;
+}
+
+static int
+set_monitoring_profile_events(PyInterpreterState *interp)
+{
+    uint32_t events = 0;
+    if (interp->sys_profiling_threads) {
+        events =
+            (1 << PY_MONITORING_EVENT_PY_START) | (1 << 
PY_MONITORING_EVENT_PY_RESUME) |
+            (1 << PY_MONITORING_EVENT_PY_RETURN) | (1 << 
PY_MONITORING_EVENT_PY_YIELD) |
+            (1 << PY_MONITORING_EVENT_CALL) | (1 << 
PY_MONITORING_EVENT_PY_UNWIND) |
+            (1 << PY_MONITORING_EVENT_PY_THROW);
+    }
+    return _PyMonitoring_SetEvents(PY_MONITORING_SYS_PROFILE_ID, events);
 }
 
 int
@@ -503,90 +535,155 @@ _PyEval_SetProfile(PyThreadState *tstate, Py_tracefunc 
func, PyObject *arg)
         return -1;
     }
 
-    // needs to be decref'd outside of the lock
-    PyObject *old_profileobj;
-    FT_MUTEX_LOCK(&_PyRuntime.ceval.sys_trace_profile_mutex);
-    Py_ssize_t profiling_threads = setup_profile(tstate, func, arg, 
&old_profileobj);
-    FT_MUTEX_UNLOCK(&_PyRuntime.ceval.sys_trace_profile_mutex);
-    Py_XDECREF(old_profileobj);
+    PyInterpreterState *interp = tstate->interp;
+    if (_PyOnceFlag_CallOnce(&interp->sys_profile_once_flag,
+                             setup_profile_callbacks, NULL) < 0) {
+        return -1;
+    }
 
-    uint32_t events = 0;
-    if (profiling_threads) {
-        events =
-            (1 << PY_MONITORING_EVENT_PY_START) | (1 << 
PY_MONITORING_EVENT_PY_RESUME) |
-            (1 << PY_MONITORING_EVENT_PY_RETURN) | (1 << 
PY_MONITORING_EVENT_PY_YIELD) |
-            (1 << PY_MONITORING_EVENT_CALL) | (1 << 
PY_MONITORING_EVENT_PY_UNWIND) |
-            (1 << PY_MONITORING_EVENT_PY_THROW);
+    _PyEval_StopTheWorld(interp);
+    PyObject *old_profileobj = swap_profile_func_arg(tstate, func, arg);
+    int ret = set_monitoring_profile_events(interp);
+    _PyEval_StartTheWorld(interp);
+    Py_XDECREF(old_profileobj);  // needs to be decref'd outside of 
stop-the-world
+    return ret;
+}
+
+int
+_PyEval_SetProfileAllThreads(PyInterpreterState *interp, Py_tracefunc func, 
PyObject *arg)
+{
+    PyThreadState *current_tstate = _PyThreadState_GET();
+    assert(is_tstate_valid(current_tstate));
+    assert(current_tstate->interp == interp);
+
+    if (_PySys_Audit(current_tstate, "sys.setprofile", NULL) < 0) {
+        return -1;
     }
-    return _PyMonitoring_SetEvents(PY_MONITORING_SYS_PROFILE_ID, events);
+
+    if (_PyOnceFlag_CallOnce(&interp->sys_profile_once_flag,
+                             setup_profile_callbacks, NULL) < 0) {
+        return -1;
+    }
+
+    PyObject *old_profileobjs = NULL;
+    _PyEval_StopTheWorld(interp);
+    HEAD_LOCK(&_PyRuntime);
+    Py_ssize_t num_thread_states = 0;
+    _Py_FOR_EACH_TSTATE_UNLOCKED(interp, p) {
+        num_thread_states++;
+    }
+    old_profileobjs = PyTuple_New(num_thread_states);
+    if (old_profileobjs == NULL) {
+        HEAD_UNLOCK(&_PyRuntime);
+        _PyEval_StartTheWorld(interp);
+        return -1;
+    }
+    _Py_FOR_EACH_TSTATE_UNLOCKED(interp, tstate) {
+        PyObject *old = swap_profile_func_arg(tstate, func, arg);
+        PyTuple_SET_ITEM(old_profileobjs, --num_thread_states, old);
+    }
+    HEAD_UNLOCK(&_PyRuntime);
+    int ret = set_monitoring_profile_events(interp);
+    _PyEval_StartTheWorld(interp);
+    Py_XDECREF(old_profileobjs);  // needs to be decref'd outside of 
stop-the-world
+    return ret;
 }
 
-static Py_ssize_t
-setup_tracing(PyThreadState *tstate, Py_tracefunc func, PyObject *arg, 
PyObject **old_traceobj)
+static int
+setup_trace_callbacks(void *Py_UNUSED(arg))
 {
-    *old_traceobj = NULL;
     /* Setup PEP 669 monitoring callbacks and events. */
-    if (!tstate->interp->sys_trace_initialized) {
-        tstate->interp->sys_trace_initialized = true;
-        if (set_callbacks(PY_MONITORING_SYS_TRACE_ID,
-                          sys_trace_start, PyTrace_CALL,
-                          PY_MONITORING_EVENT_PY_START,
-                          PY_MONITORING_EVENT_PY_RESUME)) {
-            return -1;
-        }
-        if (set_callbacks(PY_MONITORING_SYS_TRACE_ID,
-                          sys_trace_throw, PyTrace_CALL,
-                          PY_MONITORING_EVENT_PY_THROW, -1)) {
-            return -1;
-        }
-        if (set_callbacks(PY_MONITORING_SYS_TRACE_ID,
-                          sys_trace_return, PyTrace_RETURN,
-                          PY_MONITORING_EVENT_PY_RETURN, -1)) {
-            return -1;
-        }
-        if (set_callbacks(PY_MONITORING_SYS_TRACE_ID,
-                          sys_trace_yield, PyTrace_RETURN,
-                          PY_MONITORING_EVENT_PY_YIELD, -1)) {
-            return -1;
-        }
-        if (set_callbacks(PY_MONITORING_SYS_TRACE_ID,
-                          sys_trace_exception_func, PyTrace_EXCEPTION,
-                          PY_MONITORING_EVENT_RAISE,
-                          PY_MONITORING_EVENT_STOP_ITERATION)) {
-            return -1;
-        }
-        if (set_callbacks(PY_MONITORING_SYS_TRACE_ID,
-                          sys_trace_line_func, PyTrace_LINE,
-                          PY_MONITORING_EVENT_LINE, -1)) {
-            return -1;
-        }
-        if (set_callbacks(PY_MONITORING_SYS_TRACE_ID,
-                          sys_trace_unwind, PyTrace_RETURN,
-                          PY_MONITORING_EVENT_PY_UNWIND, -1)) {
-            return -1;
-        }
-        if (set_callbacks(PY_MONITORING_SYS_TRACE_ID,
-                          sys_trace_jump_func, PyTrace_LINE,
-                          PY_MONITORING_EVENT_JUMP, -1)) {
-            return -1;
-        }
-        if (set_callbacks(PY_MONITORING_SYS_TRACE_ID,
-                          sys_trace_instruction_func, PyTrace_OPCODE,
-                          PY_MONITORING_EVENT_INSTRUCTION, -1)) {
-            return -1;
-        }
+    if (set_callbacks(PY_MONITORING_SYS_TRACE_ID,
+                      sys_trace_start, PyTrace_CALL,
+                      PY_MONITORING_EVENT_PY_START,
+                      PY_MONITORING_EVENT_PY_RESUME)) {
+        return -1;
+    }
+    if (set_callbacks(PY_MONITORING_SYS_TRACE_ID,
+                      sys_trace_throw, PyTrace_CALL,
+                      PY_MONITORING_EVENT_PY_THROW, -1)) {
+        return -1;
+    }
+    if (set_callbacks(PY_MONITORING_SYS_TRACE_ID,
+                      sys_trace_return, PyTrace_RETURN,
+                      PY_MONITORING_EVENT_PY_RETURN, -1)) {
+        return -1;
+    }
+    if (set_callbacks(PY_MONITORING_SYS_TRACE_ID,
+                      sys_trace_yield, PyTrace_RETURN,
+                      PY_MONITORING_EVENT_PY_YIELD, -1)) {
+        return -1;
+    }
+    if (set_callbacks(PY_MONITORING_SYS_TRACE_ID,
+                      sys_trace_exception_func, PyTrace_EXCEPTION,
+                      PY_MONITORING_EVENT_RAISE,
+                      PY_MONITORING_EVENT_STOP_ITERATION)) {
+        return -1;
+    }
+    if (set_callbacks(PY_MONITORING_SYS_TRACE_ID,
+                      sys_trace_line_func, PyTrace_LINE,
+                      PY_MONITORING_EVENT_LINE, -1)) {
+        return -1;
     }
+    if (set_callbacks(PY_MONITORING_SYS_TRACE_ID,
+                      sys_trace_unwind, PyTrace_RETURN,
+                      PY_MONITORING_EVENT_PY_UNWIND, -1)) {
+        return -1;
+    }
+    if (set_callbacks(PY_MONITORING_SYS_TRACE_ID,
+                      sys_trace_jump_func, PyTrace_LINE,
+                      PY_MONITORING_EVENT_JUMP, -1)) {
+        return -1;
+    }
+    if (set_callbacks(PY_MONITORING_SYS_TRACE_ID,
+                      sys_trace_instruction_func, PyTrace_OPCODE,
+                      PY_MONITORING_EVENT_INSTRUCTION, -1)) {
+        return -1;
+    }
+    return 0;
+}
 
-    _PyEval_StopTheWorld(tstate->interp);
+static PyObject *
+swap_trace_func_arg(PyThreadState *tstate, Py_tracefunc func, PyObject *arg)
+{
     int delta = (func != NULL) - (tstate->c_tracefunc != NULL);
     tstate->c_tracefunc = func;
-    *old_traceobj = tstate->c_traceobj;
+    PyObject *old_traceobj = tstate->c_traceobj;
     tstate->c_traceobj = Py_XNewRef(arg);
     tstate->interp->sys_tracing_threads += delta;
     assert(tstate->interp->sys_tracing_threads >= 0);
-    Py_ssize_t tracing_threads = tstate->interp->sys_tracing_threads;
-    _PyEval_StartTheWorld(tstate->interp);
-    return tracing_threads;
+    return old_traceobj;
+}
+
+static int
+set_monitoring_trace_events(PyInterpreterState *interp)
+{
+    uint32_t events = 0;
+    if (interp->sys_tracing_threads) {
+        events =
+            (1 << PY_MONITORING_EVENT_PY_START) | (1 << 
PY_MONITORING_EVENT_PY_RESUME) |
+            (1 << PY_MONITORING_EVENT_PY_RETURN) | (1 << 
PY_MONITORING_EVENT_PY_YIELD) |
+            (1 << PY_MONITORING_EVENT_RAISE) | (1 << PY_MONITORING_EVENT_LINE) 
|
+            (1 << PY_MONITORING_EVENT_JUMP) |
+            (1 << PY_MONITORING_EVENT_PY_UNWIND) | (1 << 
PY_MONITORING_EVENT_PY_THROW) |
+            (1 << PY_MONITORING_EVENT_STOP_ITERATION);
+    }
+    return _PyMonitoring_SetEvents(PY_MONITORING_SYS_TRACE_ID, events);
+}
+
+// Enable opcode tracing for the thread's current frame if needed.
+static int
+maybe_set_opcode_trace(PyThreadState *tstate)
+{
+    _PyInterpreterFrame *iframe = tstate->current_frame;
+    if (iframe == NULL) {
+        return 0;
+    }
+    PyFrameObject *frame = iframe->frame_obj;
+    if (frame == NULL || !frame->f_trace_opcodes) {
+        return 0;
+    }
+    return set_opcode_trace_world_stopped(_PyFrame_GetCode(iframe), true);
 }
 
 int
@@ -602,35 +699,76 @@ _PyEval_SetTrace(PyThreadState *tstate, Py_tracefunc 
func, PyObject *arg)
     if (_PySys_Audit(current_tstate, "sys.settrace", NULL) < 0) {
         return -1;
     }
-    // needs to be decref'd outside of the lock
-    PyObject *old_traceobj;
-    FT_MUTEX_LOCK(&_PyRuntime.ceval.sys_trace_profile_mutex);
-    assert(tstate->interp->sys_tracing_threads >= 0);
-    Py_ssize_t tracing_threads = setup_tracing(tstate, func, arg, 
&old_traceobj);
-    FT_MUTEX_UNLOCK(&_PyRuntime.ceval.sys_trace_profile_mutex);
-    Py_XDECREF(old_traceobj);
-    if (tracing_threads < 0) {
+
+    PyInterpreterState *interp = tstate->interp;
+    if (_PyOnceFlag_CallOnce(&interp->sys_trace_once_flag,
+                             setup_trace_callbacks, NULL) < 0) {
         return -1;
     }
 
-    uint32_t events = 0;
-    if (tracing_threads) {
-        events =
-            (1 << PY_MONITORING_EVENT_PY_START) | (1 << 
PY_MONITORING_EVENT_PY_RESUME) |
-            (1 << PY_MONITORING_EVENT_PY_RETURN) | (1 << 
PY_MONITORING_EVENT_PY_YIELD) |
-            (1 << PY_MONITORING_EVENT_RAISE) | (1 << PY_MONITORING_EVENT_LINE) 
|
-            (1 << PY_MONITORING_EVENT_JUMP) |
-            (1 << PY_MONITORING_EVENT_PY_UNWIND) | (1 << 
PY_MONITORING_EVENT_PY_THROW) |
-            (1 << PY_MONITORING_EVENT_STOP_ITERATION);
+    int err = 0;
+    _PyEval_StopTheWorld(interp);
+    PyObject *old_traceobj = swap_trace_func_arg(tstate, func, arg);
+    err = set_monitoring_trace_events(interp);
+    if (err != 0) {
+        goto done;
+    }
+    if (interp->sys_tracing_threads) {
+        err = maybe_set_opcode_trace(tstate);
+    }
+done:
+    _PyEval_StartTheWorld(interp);
+    Py_XDECREF(old_traceobj);  // needs to be decref'd outside stop-the-world
+    return err;
+}
+
+int
+_PyEval_SetTraceAllThreads(PyInterpreterState *interp, Py_tracefunc func, 
PyObject *arg)
+{
+    PyThreadState *current_tstate = _PyThreadState_GET();
+    assert(is_tstate_valid(current_tstate));
+    assert(current_tstate->interp == interp);
+
+    if (_PySys_Audit(current_tstate, "sys.settrace", NULL) < 0) {
+        return -1;
+    }
+
+    if (_PyOnceFlag_CallOnce(&interp->sys_trace_once_flag,
+                             setup_trace_callbacks, NULL) < 0) {
+        return -1;
+    }
 
-        PyFrameObject* frame = PyEval_GetFrame();
-        if (frame && frame->f_trace_opcodes) {
-            int ret = _PyEval_SetOpcodeTrace(frame, true);
-            if (ret != 0) {
-                return ret;
+    PyObject *old_trace_objs = NULL;
+    _PyEval_StopTheWorld(interp);
+    HEAD_LOCK(&_PyRuntime);
+    Py_ssize_t num_thread_states = 0;
+    _Py_FOR_EACH_TSTATE_UNLOCKED(interp, p) {
+        num_thread_states++;
+    }
+    old_trace_objs = PyTuple_New(num_thread_states);
+    if (old_trace_objs == NULL) {
+        HEAD_UNLOCK(&_PyRuntime);
+        _PyEval_StartTheWorld(interp);
+        return -1;
+    }
+    _Py_FOR_EACH_TSTATE_UNLOCKED(interp, tstate) {
+        PyObject *old = swap_trace_func_arg(tstate, func, arg);
+        PyTuple_SET_ITEM(old_trace_objs, --num_thread_states, old);
+    }
+    if (interp->sys_tracing_threads) {
+        _Py_FOR_EACH_TSTATE_UNLOCKED(interp, tstate) {
+            int err = maybe_set_opcode_trace(tstate);
+            if (err != 0) {
+                HEAD_UNLOCK(&_PyRuntime);
+                _PyEval_StartTheWorld(interp);
+                Py_XDECREF(old_trace_objs);
+                return -1;
             }
         }
     }
-
-    return _PyMonitoring_SetEvents(PY_MONITORING_SYS_TRACE_ID, events);
+    HEAD_UNLOCK(&_PyRuntime);
+    int err = set_monitoring_trace_events(interp);
+    _PyEval_StartTheWorld(interp);
+    Py_XDECREF(old_trace_objs);  // needs to be decref'd outside of 
stop-the-world
+    return err;
 }
diff --git a/Python/pystate.c b/Python/pystate.c
index cd62bf86837f83..a2914b3718eea2 100644
--- a/Python/pystate.c
+++ b/Python/pystate.c
@@ -324,7 +324,6 @@ _Py_COMP_DIAG_POP
         &(runtime)->unicode_state.ids.mutex, \
         &(runtime)->imports.extensions.mutex, \
         &(runtime)->ceval.pending_mainthread.mutex, \
-        &(runtime)->ceval.sys_trace_profile_mutex, \
         &(runtime)->atexit.mutex, \
         &(runtime)->audit_hooks.mutex, \
         &(runtime)->allocators.mutex, \
@@ -565,8 +564,6 @@ init_interpreter(PyInterpreterState *interp,
         }
         interp->monitoring_tool_versions[t] = 0;
     }
-    interp->sys_profile_initialized = false;
-    interp->sys_trace_initialized = false;
     interp->_code_object_generation = 0;
     interp->jit = false;
     interp->executor_list_head = NULL;
@@ -773,8 +770,6 @@ interpreter_clear(PyInterpreterState *interp, PyThreadState 
*tstate)
             Py_CLEAR(interp->monitoring_callables[t][e]);
         }
     }
-    interp->sys_profile_initialized = false;
-    interp->sys_trace_initialized = false;
     for (int t = 0; t < PY_MONITORING_TOOL_IDS; t++) {
         Py_CLEAR(interp->monitoring_tool_names[t]);
     }
@@ -1689,19 +1684,15 @@ PyThreadState_Clear(PyThreadState *tstate)
           "PyThreadState_Clear: warning: thread still has a generator\n");
     }
 
-    FT_MUTEX_LOCK(&_PyRuntime.ceval.sys_trace_profile_mutex);
-
     if (tstate->c_profilefunc != NULL) {
-        tstate->interp->sys_profiling_threads--;
+        FT_ATOMIC_ADD_SSIZE(tstate->interp->sys_profiling_threads, -1);
         tstate->c_profilefunc = NULL;
     }
     if (tstate->c_tracefunc != NULL) {
-        tstate->interp->sys_tracing_threads--;
+        FT_ATOMIC_ADD_SSIZE(tstate->interp->sys_tracing_threads, -1);
         tstate->c_tracefunc = NULL;
     }
 
-    FT_MUTEX_UNLOCK(&_PyRuntime.ceval.sys_trace_profile_mutex);
-
     Py_CLEAR(tstate->c_profileobj);
     Py_CLEAR(tstate->c_traceobj);
 
diff --git a/Python/sysmodule.c b/Python/sysmodule.c
index e4bc27d2ce624c..19912b4a4c6198 100644
--- a/Python/sysmodule.c
+++ b/Python/sysmodule.c
@@ -1183,9 +1183,10 @@ sys__settraceallthreads(PyObject *module, PyObject *arg)
         argument = arg;
     }
 
-
-    PyEval_SetTraceAllThreads(func, argument);
-
+    PyInterpreterState *interp = _PyInterpreterState_GET();
+    if (_PyEval_SetTraceAllThreads(interp, func, argument) < 0) {
+        return NULL;
+    }
     Py_RETURN_NONE;
 }
 
@@ -1263,8 +1264,10 @@ sys__setprofileallthreads(PyObject *module, PyObject 
*arg)
         argument = arg;
     }
 
-    PyEval_SetProfileAllThreads(func, argument);
-
+    PyInterpreterState *interp = _PyInterpreterState_GET();
+    if (_PyEval_SetProfileAllThreads(interp, func, argument) < 0) {
+        return NULL;
+    }
     Py_RETURN_NONE;
 }
 

_______________________________________________
Python-checkins mailing list -- python-checkins@python.org
To unsubscribe send an email to python-checkins-le...@python.org
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: arch...@mail-archive.com

Reply via email to