]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.13] gh-137400: Fix a crash when disabling profiling across all threads (gh-137471...
authorSam Gross <colesbury@gmail.com>
Mon, 11 Aug 2025 16:07:21 +0000 (12:07 -0400)
committerGitHub <noreply@github.com>
Mon, 11 Aug 2025 16:07:21 +0000 (16:07 +0000)
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 362692852f13cdd1d33cc7ed35c0cbac7af1a785)

Lib/test/test_free_threading/test_monitoring.py
Misc/NEWS.d/next/Core and Builtins/2025-08-06-15-39-54.gh-issue-137400.xIw0zs.rst [new file with mode: 0644]
Python/legacy_tracing.c

index 8fec01715531cbe133d40badb923be2fd97a9a82..51860a7c94539f08c790d095fe641e78ad4516d2 100644 (file)
@@ -2,6 +2,7 @@
 environment to verify things are thread-safe in a free-threaded build"""
 
 import sys
+import threading
 import time
 import unittest
 import weakref
@@ -192,6 +193,40 @@ class SetProfileMultiThreaded(InstrumentationMultiThreadedMixin, TestCase):
         self.set = not self.set
 
 
+@threading_helper.requires_working_threading()
+class SetProfileAllMultiThreaded(TestCase):
+    def test_profile_all_threads(self):
+        done = threading.Event()
+
+        def func():
+            pass
+
+        def bg_thread():
+            while not done.is_set():
+                func()
+                func()
+                func()
+                func()
+                func()
+
+        def my_profile(frame, event, arg):
+            return None
+
+        bg_threads = []
+        for i in range(10):
+            t = threading.Thread(target=bg_thread)
+            t.start()
+            bg_threads.append(t)
+
+        for i in range(100):
+            threading.setprofile_all_threads(my_profile)
+            threading.setprofile_all_threads(None)
+
+        done.set()
+        for t in bg_threads:
+            t.join()
+
+
 @threading_helper.requires_working_threading()
 class MonitoringMisc(MonitoringTestMixin, TestCase):
     def register_callback(self):
diff --git a/Misc/NEWS.d/next/Core and Builtins/2025-08-06-15-39-54.gh-issue-137400.xIw0zs.rst b/Misc/NEWS.d/next/Core and Builtins/2025-08-06-15-39-54.gh-issue-137400.xIw0zs.rst
new file mode 100644 (file)
index 0000000..a464cf4
--- /dev/null
@@ -0,0 +1,4 @@
+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`.
index 8a9ad3601a379e9d755fdd45c380a6cdc2c63d3d..2f6d43658a9fa4f6e7b3bd3e2e42c3639724b37a 100644 (file)
@@ -479,13 +479,16 @@ setup_profile(PyThreadState *tstate, Py_tracefunc func, PyObject *arg, PyObject
         }
     }
 
+    _PyEval_StopTheWorld(tstate->interp);
     int delta = (func != NULL) - (tstate->c_profilefunc != NULL);
     tstate->c_profilefunc = func;
     *old_profileobj = tstate->c_profileobj;
     tstate->c_profileobj = Py_XNewRef(arg);
     tstate->interp->sys_profiling_threads += delta;
     assert(tstate->interp->sys_profiling_threads >= 0);
-    return tstate->interp->sys_profiling_threads;
+    Py_ssize_t profiling_threads = tstate->interp->sys_profiling_threads;
+    _PyEval_StartTheWorld(tstate->interp);
+    return profiling_threads;
 }
 
 int
@@ -574,13 +577,16 @@ setup_tracing(PyThreadState *tstate, Py_tracefunc func, PyObject *arg, PyObject
         }
     }
 
+    _PyEval_StopTheWorld(tstate->interp);
     int delta = (func != NULL) - (tstate->c_tracefunc != NULL);
     tstate->c_tracefunc = func;
     *old_traceobj = tstate->c_traceobj;
     tstate->c_traceobj = Py_XNewRef(arg);
     tstate->interp->sys_tracing_threads += delta;
     assert(tstate->interp->sys_tracing_threads >= 0);
-    return tstate->interp->sys_tracing_threads;
+    Py_ssize_t tracing_threads = tstate->interp->sys_tracing_threads;
+    _PyEval_StartTheWorld(tstate->interp);
+    return tracing_threads;
 }
 
 int