From 10d774648ee3c4eca9b0703d1c5f3acbb66a1d81 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Date: Mon, 6 Jun 2022 16:05:09 +0100 Subject: [PATCH 1/7] gh-93503: Add APIs to set profiling and tracing functions in all threads in the C-API --- Doc/c-api/init.rst | 18 +++++ Doc/data/refcounts.dat | 8 ++ Doc/library/threading.rst | 14 +++- Include/cpython/ceval.h | 2 + Lib/test/test_threading.py | 57 ++++++++++++++ Lib/threading.py | 14 +++- ...2-06-06-16-04-14.gh-issue-93503.MHJTu8.rst | 7 ++ Python/ceval.c | 27 +++++++ Python/clinic/sysmodule.c.h | 28 +++++++ Python/sysmodule.c | 76 +++++++++++++++++++ 10 files changed, 247 insertions(+), 4 deletions(-) create mode 100644 Misc/NEWS.d/next/C API/2022-06-06-16-04-14.gh-issue-93503.MHJTu8.rst diff --git a/Doc/c-api/init.rst b/Doc/c-api/init.rst index 038498f325ceb3..9cdd5a09bb7432 100644 --- a/Doc/c-api/init.rst +++ b/Doc/c-api/init.rst @@ -1774,6 +1774,15 @@ Python-level trace functions in previous versions. The caller must hold the :term:`GIL`. +.. c:function:: void PyEval_SetProfileAllThreads(Py_tracefunc func, PyObject *obj) + + Like :c:func:`PyEval_SetProfile` but sets the profile function in all running threads + instead of the setting it only on the current thread. + + The caller must hold the :term:`GIL`. + +.. versionadded:: 3.12 + .. c:function:: void PyEval_SetTrace(Py_tracefunc func, PyObject *obj) @@ -1788,6 +1797,15 @@ Python-level trace functions in previous versions. The caller must hold the :term:`GIL`. +.. c:function:: void PyEval_SetTraceAllThreads(Py_tracefunc func, PyObject *obj) + + Like :c:func:`PyEval_SetTrace` but sets the tracing function in all running threads + instead of the setting it only on the current thread. + + The caller must hold the :term:`GIL`. + +.. versionadded:: 3.12 + .. _advanced-debugging: diff --git a/Doc/data/refcounts.dat b/Doc/data/refcounts.dat index 1694cad6f43ba7..51ccacf13f9e3b 100644 --- a/Doc/data/refcounts.dat +++ b/Doc/data/refcounts.dat @@ -796,10 +796,18 @@ PyEval_SetProfile:void::: PyEval_SetProfile:Py_tracefunc:func:: PyEval_SetProfile:PyObject*:obj:+1: +PyEval_SetProfileAllThreads:void::: +PyEval_SetProfileAllThreads:Py_tracefunc:func:: +PyEval_SetProfileAllThreads:PyObject*:obj:+1: + PyEval_SetTrace:void::: PyEval_SetTrace:Py_tracefunc:func:: PyEval_SetTrace:PyObject*:obj:+1: +PyEval_SetTraceAllThreads:void::: +PyEval_SetTraceAllThreads:Py_tracefunc:func:: +PyEval_SetTraceAllThreads:PyObject*:obj:+1: + PyEval_EvalCode:PyObject*::+1: PyEval_EvalCode:PyObject*:co:0: PyEval_EvalCode:PyObject*:globals:0: diff --git a/Doc/library/threading.rst b/Doc/library/threading.rst index b22d4876622437..b6dcd290f2ab26 100644 --- a/Doc/library/threading.rst +++ b/Doc/library/threading.rst @@ -150,7 +150,7 @@ This module defines the following functions: .. versionadded:: 3.4 -.. function:: settrace(func) +.. function:: settrace(func, *, running_threads=False) .. index:: single: trace function @@ -158,6 +158,11 @@ This module defines the following functions: The *func* will be passed to :func:`sys.settrace` for each thread, before its :meth:`~Thread.run` method is called. + If *running_threads* is set, the tracing function will be installed in all running + threads. + + .. versionchanged:: 3.12 + The *running_threads* parameter was added. .. function:: gettrace() @@ -170,7 +175,7 @@ This module defines the following functions: .. versionadded:: 3.10 -.. function:: setprofile(func) +.. function:: setprofile(func, *, running_threads=False) .. index:: single: profile function @@ -178,6 +183,11 @@ This module defines the following functions: The *func* will be passed to :func:`sys.setprofile` for each thread, before its :meth:`~Thread.run` method is called. + If *running_threads* is set, the profile function will be installed in all running + threads. + + .. versionchanged:: 3.12 + The *running_threads* parameter was added. .. function:: getprofile() diff --git a/Include/cpython/ceval.h b/Include/cpython/ceval.h index 9d4eeafb427eb2..74665c9fa10580 100644 --- a/Include/cpython/ceval.h +++ b/Include/cpython/ceval.h @@ -3,8 +3,10 @@ #endif PyAPI_FUNC(void) PyEval_SetProfile(Py_tracefunc, PyObject *); +PyAPI_FUNC(void) PyEval_SetProfileAllThreads(Py_tracefunc, PyObject *); PyAPI_DATA(int) _PyEval_SetProfile(PyThreadState *tstate, Py_tracefunc func, PyObject *arg); PyAPI_FUNC(void) PyEval_SetTrace(Py_tracefunc, PyObject *); +PyAPI_FUNC(void) PyEval_SetTraceAllThreads(Py_tracefunc, PyObject *); PyAPI_FUNC(int) _PyEval_SetTrace(PyThreadState *tstate, Py_tracefunc func, PyObject *arg); /* Helper to look up a builtin object */ diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py index dcd27697bb4839..63851334fbedc1 100644 --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -866,6 +866,34 @@ def noop_trace(frame, event, arg): finally: threading.settrace(old_trace) + def test_gettrace_all_threads(self): + def fn(*args): pass + old_trace = threading.gettrace() + first_check = threading.Event() + second_check = threading.Event() + + trace_funcs = [] + def checker(): + trace_funcs.append(sys.gettrace()) + first_check.set() + second_check.wait() + trace_funcs.append(sys.gettrace()) + + try: + t = threading.Thread(target=checker) + t.start() + first_check.wait() + threading.settrace(fn, running_threads=True) + second_check.set() + t.join() + self.assertEqual(trace_funcs, [None, fn]) + self.assertEqual(threading.gettrace(), fn) + self.assertEqual(sys.gettrace(), fn) + finally: + threading.settrace(old_trace, running_threads=True) + self.assertEqual(threading.gettrace(), old_trace) + self.assertEqual(sys.gettrace(), old_trace) + def test_getprofile(self): def fn(*args): pass old_profile = threading.getprofile() @@ -875,6 +903,35 @@ def fn(*args): pass finally: threading.setprofile(old_profile) + def test_getprofile_all_threads(self): + def fn(*args): pass + old_profile = threading.getprofile() + first_check = threading.Event() + second_check = threading.Event() + + profile_funcs = [] + def checker(): + profile_funcs.append(sys.getprofile()) + first_check.set() + second_check.wait() + profile_funcs.append(sys.getprofile()) + + try: + t = threading.Thread(target=checker) + t.start() + first_check.wait() + threading.setprofile(fn, running_threads=True) + second_check.set() + t.join() + self.assertEqual(profile_funcs, [None, fn]) + self.assertEqual(threading.getprofile(), fn) + self.assertEqual(sys.getprofile(), fn) + finally: + threading.setprofile(old_profile, running_threads=True) + + self.assertEqual(threading.getprofile(), old_profile) + self.assertEqual(sys.getprofile(), old_profile) + @cpython_only def test_shutdown_locks(self): for daemon in (False, True): diff --git a/Lib/threading.py b/Lib/threading.py index e32ad1418d9dd7..f90f30e5075fa2 100644 --- a/Lib/threading.py +++ b/Lib/threading.py @@ -55,30 +55,40 @@ _profile_hook = None _trace_hook = None -def setprofile(func): +def setprofile(func, *, running_threads=False): """Set a profile function for all threads started from the threading module. The func will be passed to sys.setprofile() for each thread, before its run() method is called. + If running_threads is set, the profile function will be installed in all running + threads. """ global _profile_hook _profile_hook = func + if running_threads: + _sys._setprofileallthreads(func) + def getprofile(): """Get the profiler function as set by threading.setprofile().""" return _profile_hook -def settrace(func): +def settrace(func, *, running_threads=False): """Set a trace function for all threads started from the threading module. The func will be passed to sys.settrace() for each thread, before its run() method is called. + If running_threads is set, the trace function will be installed in all running + threads. """ global _trace_hook _trace_hook = func + if running_threads: + _sys._settraceallthreads(func) + def gettrace(): """Get the trace function as set by threading.settrace().""" return _trace_hook diff --git a/Misc/NEWS.d/next/C API/2022-06-06-16-04-14.gh-issue-93503.MHJTu8.rst b/Misc/NEWS.d/next/C API/2022-06-06-16-04-14.gh-issue-93503.MHJTu8.rst new file mode 100644 index 00000000000000..bab0b925919497 --- /dev/null +++ b/Misc/NEWS.d/next/C API/2022-06-06-16-04-14.gh-issue-93503.MHJTu8.rst @@ -0,0 +1,7 @@ +Add two new public functions to the public C-API, +:c:func:`PyEval_SetProfileAllThreads and +:c:func:`PyEval_SetTraceAllThreads`, that allow to set tracking and +profiling functions in all running threads in addition to the calling one. +Also, add a new *running_threads* parameter to :func:`threading.setprofile` +and :func:`threading.settrace` that allows to do the same from Python. Patch +by Pablo Galindo diff --git a/Python/ceval.c b/Python/ceval.c index 1ab104c18ed761..487074b16b379c 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -6455,6 +6455,20 @@ 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; + PyThreadState* ts = PyInterpreterState_ThreadHead(interp); + while (ts) { + if (_PyEval_SetProfile(ts, func, arg) < 0) { + _PyErr_WriteUnraisableMsg("in PyEval_SetProfileAllThreads", NULL); + } + ts = PyThreadState_Next(ts); + } +} + int _PyEval_SetTrace(PyThreadState *tstate, Py_tracefunc func, PyObject *arg) { @@ -6508,6 +6522,19 @@ 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; + PyThreadState* ts = PyInterpreterState_ThreadHead(interp); + while (ts) { + if (_PyEval_SetTrace(ts, func, arg) < 0) { + _PyErr_WriteUnraisableMsg("in PyEval_SetTraceAllThreads", NULL); + } + ts = PyThreadState_Next(ts); + } +} int _PyEval_SetCoroutineOriginTrackingDepth(int depth) diff --git a/Python/clinic/sysmodule.c.h b/Python/clinic/sysmodule.c.h index beaf21c85bcff2..a1ce3fc6c6748a 100644 --- a/Python/clinic/sysmodule.c.h +++ b/Python/clinic/sysmodule.c.h @@ -292,6 +292,18 @@ sys_intern(PyObject *module, PyObject *arg) return return_value; } +PyDoc_STRVAR(sys__settraceallthreads__doc__, +"_settraceallthreads($module, arg, /)\n" +"--\n" +"\n" +"Set the global debug tracing function in all running threads.\n" +"\n" +"It will be called on each function call. See the debugger chapter\n" +"in the library manual."); + +#define SYS__SETTRACEALLTHREADS_METHODDEF \ + {"_settraceallthreads", (PyCFunction)sys__settraceallthreads, METH_O, sys__settraceallthreads__doc__}, + PyDoc_STRVAR(sys_gettrace__doc__, "gettrace($module, /)\n" "--\n" @@ -312,6 +324,18 @@ sys_gettrace(PyObject *module, PyObject *Py_UNUSED(ignored)) return sys_gettrace_impl(module); } +PyDoc_STRVAR(sys__setprofileallthreads__doc__, +"_setprofileallthreads($module, arg, /)\n" +"--\n" +"\n" +"Set the profiling function in all running threads.\n" +"\n" +"It will be called on each function call and return. See the profiler chapter\n" +"in the library manual."); + +#define SYS__SETPROFILEALLTHREADS_METHODDEF \ + {"_setprofileallthreads", (PyCFunction)sys__setprofileallthreads, METH_O, sys__setprofileallthreads__doc__}, + PyDoc_STRVAR(sys_getprofile__doc__, "getprofile($module, /)\n" "--\n" @@ -1170,4 +1194,8 @@ sys_getandroidapilevel(PyObject *module, PyObject *Py_UNUSED(ignored)) #ifndef SYS_GETANDROIDAPILEVEL_METHODDEF #define SYS_GETANDROIDAPILEVEL_METHODDEF #endif /* !defined(SYS_GETANDROIDAPILEVEL_METHODDEF) */ +<<<<<<< HEAD /*[clinic end generated code: output=38446a4c76e2f3b6 input=a9049054013a1b77]*/ +======= +/*[clinic end generated code: output=541cc7ffee7d20f8 input=a9049054013a1b77]*/ +>>>>>>> d37f6a3263 (gh-93503: Add APIs to set profiling and tracing functions in all threads in the C-API) diff --git a/Python/sysmodule.c b/Python/sysmodule.c index b8009b2db45f7b..e75c56546de731 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -1021,6 +1021,43 @@ Set the global debug tracing function. It will be called on each\n\ function call. See the debugger chapter in the library manual." ); +/*[clinic input] +sys._settraceallthreads + + arg: object + / + +Set the global debug tracing function in all running threads. + +It will be called on each function call. See the debugger chapter +in the library manual. +[clinic start generated code]*/ + +static PyObject * +sys__settraceallthreads(PyObject *module, PyObject *arg) +/*[clinic end generated code: output=161cca30207bf3ca input=57c1ae02b1b5ee94]*/ +{ + PyObject* argument = NULL; + Py_tracefunc func = NULL; + + if (arg != Py_None) { + func = trace_trampoline; + argument = arg; + } + + PyThreadState *this_tstate = _PyThreadState_GET(); + PyInterpreterState* interp = this_tstate->interp; + PyThreadState* ts = PyInterpreterState_ThreadHead(interp); + while (ts) { + if (_PyEval_SetTrace(ts, func, argument) < 0) { + return NULL; + } + ts = PyThreadState_Next(ts); + } + + Py_RETURN_NONE; +} + /*[clinic input] sys.gettrace @@ -1066,6 +1103,43 @@ Set the profiling function. It will be called on each function call\n\ and return. See the profiler chapter in the library manual." ); +/*[clinic input] +sys._setprofileallthreads + + arg: object + / + +Set the profiling function in all running threads. + +It will be called on each function call and return. See the profiler chapter +in the library manual. +[clinic start generated code]*/ + +static PyObject * +sys__setprofileallthreads(PyObject *module, PyObject *arg) +/*[clinic end generated code: output=2d61319e27b309fe input=7daefd8797f0476f]*/ +{ + PyObject* argument = NULL; + Py_tracefunc func = NULL; + + if (arg != Py_None) { + func = profile_trampoline; + argument = arg; + } + + PyThreadState *this_tstate = _PyThreadState_GET(); + PyInterpreterState* interp = this_tstate->interp; + PyThreadState* ts = PyInterpreterState_ThreadHead(interp); + while (ts) { + if (_PyEval_SetProfile(ts, func, argument) < 0) { + return NULL; + } + ts = PyThreadState_Next(ts); + } + + Py_RETURN_NONE; +} + /*[clinic input] sys.getprofile @@ -2035,9 +2109,11 @@ static PyMethodDef sys_methods[] = { SYS_GETSWITCHINTERVAL_METHODDEF SYS_SETDLOPENFLAGS_METHODDEF {"setprofile", sys_setprofile, METH_O, setprofile_doc}, + SYS__SETPROFILEALLTHREADS_METHODDEF SYS_GETPROFILE_METHODDEF SYS_SETRECURSIONLIMIT_METHODDEF {"settrace", sys_settrace, METH_O, settrace_doc}, + SYS__SETTRACEALLTHREADS_METHODDEF SYS_GETTRACE_METHODDEF SYS_CALL_TRACING_METHODDEF SYS__DEBUGMALLOCSTATS_METHODDEF From b3a223f5a78337370c4ada4b6da574cab86bbc1c Mon Sep 17 00:00:00 2001 From: Pablo Galindo Date: Thu, 4 Aug 2022 15:11:37 +0100 Subject: [PATCH 2/7] Use a separate API --- Doc/library/threading.rst | 28 ++++++++++++++++++---------- Lib/test/test_threading.py | 10 ++++++---- Lib/threading.py | 35 ++++++++++++++++++++++------------- 3 files changed, 46 insertions(+), 27 deletions(-) diff --git a/Doc/library/threading.rst b/Doc/library/threading.rst index b6dcd290f2ab26..b352125551fa79 100644 --- a/Doc/library/threading.rst +++ b/Doc/library/threading.rst @@ -150,7 +150,7 @@ This module defines the following functions: .. versionadded:: 3.4 -.. function:: settrace(func, *, running_threads=False) +.. function:: settrace(func) .. index:: single: trace function @@ -158,11 +158,15 @@ This module defines the following functions: The *func* will be passed to :func:`sys.settrace` for each thread, before its :meth:`~Thread.run` method is called. - If *running_threads* is set, the tracing function will be installed in all running - threads. +.. function:: settrace_all_threads(func) - .. versionchanged:: 3.12 - The *running_threads* parameter was added. + Set a trace function for all threads started from the :mod:`threading` module + and all Python threads that are currently executing. + + The *func* will be passed to :func:`sys.settrace` for each thread, before its + :meth:`~Thread.run` method is called. + + .. versionadded:: 3.12 .. function:: gettrace() @@ -175,7 +179,7 @@ This module defines the following functions: .. versionadded:: 3.10 -.. function:: setprofile(func, *, running_threads=False) +.. function:: setprofile(func) .. index:: single: profile function @@ -183,11 +187,15 @@ This module defines the following functions: The *func* will be passed to :func:`sys.setprofile` for each thread, before its :meth:`~Thread.run` method is called. - If *running_threads* is set, the profile function will be installed in all running - threads. +.. function:: setprofile_all_threads(func) + + Set a profile function for all threads started from the :mod:`threading` module + and all Python threads that are currently executing. + + The *func* will be passed to :func:`sys.setprofile` for each thread, before its + :meth:`~Thread.run` method is called. - .. versionchanged:: 3.12 - The *running_threads* parameter was added. + .. versionadded:: 3.12 .. function:: getprofile() diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py index 63851334fbedc1..c6649962331464 100644 --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -853,6 +853,7 @@ def callback(): callback() finally: sys.settrace(old_trace) + threading.settrace(old_trace) def test_gettrace(self): def noop_trace(frame, event, arg): @@ -883,14 +884,15 @@ def checker(): t = threading.Thread(target=checker) t.start() first_check.wait() - threading.settrace(fn, running_threads=True) + threading.settrace_all_threads(fn) second_check.set() t.join() self.assertEqual(trace_funcs, [None, fn]) self.assertEqual(threading.gettrace(), fn) self.assertEqual(sys.gettrace(), fn) finally: - threading.settrace(old_trace, running_threads=True) + threading.settrace_all_threads(old_trace) + self.assertEqual(threading.gettrace(), old_trace) self.assertEqual(sys.gettrace(), old_trace) @@ -920,14 +922,14 @@ def checker(): t = threading.Thread(target=checker) t.start() first_check.wait() - threading.setprofile(fn, running_threads=True) + threading.setprofile_all_threads(fn) second_check.set() t.join() self.assertEqual(profile_funcs, [None, fn]) self.assertEqual(threading.getprofile(), fn) self.assertEqual(sys.getprofile(), fn) finally: - threading.setprofile(old_profile, running_threads=True) + threading.setprofile_all_threads(old_profile) self.assertEqual(threading.getprofile(), old_profile) self.assertEqual(sys.getprofile(), old_profile) diff --git a/Lib/threading.py b/Lib/threading.py index f90f30e5075fa2..f28597c993052a 100644 --- a/Lib/threading.py +++ b/Lib/threading.py @@ -28,7 +28,8 @@ 'Event', 'Lock', 'RLock', 'Semaphore', 'BoundedSemaphore', 'Thread', 'Barrier', 'BrokenBarrierError', 'Timer', 'ThreadError', 'setprofile', 'settrace', 'local', 'stack_size', - 'excepthook', 'ExceptHookArgs', 'gettrace', 'getprofile'] + 'excepthook', 'ExceptHookArgs', 'gettrace', 'getprofile', + 'setprofile_all_threads','settrace_all_threads'] # Rename some stuff so "from threading import *" is safe _start_new_thread = _thread.start_new_thread @@ -55,39 +56,47 @@ _profile_hook = None _trace_hook = None -def setprofile(func, *, running_threads=False): +def setprofile(func): """Set a profile function for all threads started from the threading module. The func will be passed to sys.setprofile() for each thread, before its run() method is called. - - If running_threads is set, the profile function will be installed in all running - threads. """ global _profile_hook _profile_hook = func - if running_threads: - _sys._setprofileallthreads(func) +def setprofile_all_threads(func): + """Set a profile function for all threads started from the threading module + and all Python threads that are currently executing. + + The func will be passed to sys.setprofile() for each thread, before its + run() method is called. + """ + setprofile(func) + _sys._setprofileallthreads(func) def getprofile(): """Get the profiler function as set by threading.setprofile().""" return _profile_hook -def settrace(func, *, running_threads=False): +def settrace(func): """Set a trace function for all threads started from the threading module. The func will be passed to sys.settrace() for each thread, before its run() method is called. - - If running_threads is set, the trace function will be installed in all running - threads. """ global _trace_hook _trace_hook = func - if running_threads: - _sys._settraceallthreads(func) +def settrace_all_threads(func): + """Set a trace function for all threads started from the threading module + and all Python threads that are currently executing. + + The func will be passed to sys.settrace() for each thread, before its run() + method is called. + """ + settrace(func) + _sys._settraceallthreads(func) def gettrace(): """Get the trace function as set by threading.settrace().""" From 438eae65158d1219cc7e90084a6d0d978a75c7de Mon Sep 17 00:00:00 2001 From: Pablo Galindo Date: Thu, 4 Aug 2022 15:20:55 +0100 Subject: [PATCH 3/7] Fix NEWS entry --- .../next/C API/2022-06-06-16-04-14.gh-issue-93503.MHJTu8.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/C API/2022-06-06-16-04-14.gh-issue-93503.MHJTu8.rst b/Misc/NEWS.d/next/C API/2022-06-06-16-04-14.gh-issue-93503.MHJTu8.rst index bab0b925919497..6df9f95fc9461d 100644 --- a/Misc/NEWS.d/next/C API/2022-06-06-16-04-14.gh-issue-93503.MHJTu8.rst +++ b/Misc/NEWS.d/next/C API/2022-06-06-16-04-14.gh-issue-93503.MHJTu8.rst @@ -1,5 +1,5 @@ Add two new public functions to the public C-API, -:c:func:`PyEval_SetProfileAllThreads and +:c:func:`PyEval_SetProfileAllThreads` and :c:func:`PyEval_SetTraceAllThreads`, that allow to set tracking and profiling functions in all running threads in addition to the calling one. Also, add a new *running_threads* parameter to :func:`threading.setprofile` From 5cbcaa133d051c912a25442d9e2b641378f597c2 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Date: Thu, 4 Aug 2022 16:29:23 +0100 Subject: [PATCH 4/7] Add locks around the loop --- Python/ceval.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Python/ceval.c b/Python/ceval.c index 487074b16b379c..ac77ab8e8692fe 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -96,6 +96,10 @@ #define _Py_atomic_load_relaxed_int32(ATOMIC_VAL) _Py_atomic_load_relaxed(ATOMIC_VAL) #endif +#define HEAD_LOCK(runtime) \ + PyThread_acquire_lock((runtime)->interpreters.mutex, WAIT_LOCK) +#define HEAD_UNLOCK(runtime) \ + PyThread_release_lock((runtime)->interpreters.mutex) /* Forward declarations */ static PyObject *trace_call_function( @@ -6460,12 +6464,19 @@ 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_WriteUnraisableMsg("in PyEval_SetProfileAllThreads", NULL); } + HEAD_LOCK(runtime); ts = PyThreadState_Next(ts); + HEAD_UNLOCK(runtime); } } @@ -6527,12 +6538,19 @@ 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_WriteUnraisableMsg("in PyEval_SetTraceAllThreads", NULL); } + HEAD_LOCK(runtime); ts = PyThreadState_Next(ts); + HEAD_UNLOCK(runtime); } } From b3333d879c3d3b10df254739335ac86b7db93037 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Date: Thu, 4 Aug 2022 16:37:55 +0100 Subject: [PATCH 5/7] Document ignoring exceptions --- Doc/c-api/init.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Doc/c-api/init.rst b/Doc/c-api/init.rst index 9cdd5a09bb7432..a04ab966643fc7 100644 --- a/Doc/c-api/init.rst +++ b/Doc/c-api/init.rst @@ -1781,6 +1781,9 @@ Python-level trace functions in previous versions. The caller must hold the :term:`GIL`. + As :c:func:`PyEval_SetProfile`, this function ignores any exceptions raised while + setting the profile functions in all threads. + .. versionadded:: 3.12 @@ -1804,6 +1807,9 @@ Python-level trace functions in previous versions. The caller must hold the :term:`GIL`. + As :c:func:`PyEval_SetTrace`, this function ignores any exceptions raised while + setting the trace functions in all threads. + .. versionadded:: 3.12 From d580e5d15aff3858504191eaeefefd1182b4ca03 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Date: Fri, 5 Aug 2022 15:36:04 +0100 Subject: [PATCH 6/7] Use the new APIs in the sys module --- Python/sysmodule.c | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/Python/sysmodule.c b/Python/sysmodule.c index e75c56546de731..00da20c8d0001f 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -1045,15 +1045,8 @@ sys__settraceallthreads(PyObject *module, PyObject *arg) argument = arg; } - PyThreadState *this_tstate = _PyThreadState_GET(); - PyInterpreterState* interp = this_tstate->interp; - PyThreadState* ts = PyInterpreterState_ThreadHead(interp); - while (ts) { - if (_PyEval_SetTrace(ts, func, argument) < 0) { - return NULL; - } - ts = PyThreadState_Next(ts); - } + + PyEval_SetTraceAllThreads(func, argument); Py_RETURN_NONE; } @@ -1127,15 +1120,7 @@ sys__setprofileallthreads(PyObject *module, PyObject *arg) argument = arg; } - PyThreadState *this_tstate = _PyThreadState_GET(); - PyInterpreterState* interp = this_tstate->interp; - PyThreadState* ts = PyInterpreterState_ThreadHead(interp); - while (ts) { - if (_PyEval_SetProfile(ts, func, argument) < 0) { - return NULL; - } - ts = PyThreadState_Next(ts); - } + PyEval_SetProfileAllThreads(func, argument); Py_RETURN_NONE; } From 05bf74c20c17f36e524575075201720ba74fb88e Mon Sep 17 00:00:00 2001 From: Pablo Galindo Date: Wed, 24 Aug 2022 21:36:40 +0100 Subject: [PATCH 7/7] Update docs --- Doc/c-api/init.rst | 4 ++-- Python/clinic/sysmodule.c.h | 10 +++------- Python/sysmodule.c | 8 ++++---- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/Doc/c-api/init.rst b/Doc/c-api/init.rst index a04ab966643fc7..2a9cf0ea702209 100644 --- a/Doc/c-api/init.rst +++ b/Doc/c-api/init.rst @@ -1777,7 +1777,7 @@ Python-level trace functions in previous versions. .. c:function:: void PyEval_SetProfileAllThreads(Py_tracefunc func, PyObject *obj) Like :c:func:`PyEval_SetProfile` but sets the profile function in all running threads - instead of the setting it only on the current thread. + belonging to the current interpreter instead of the setting it only on the current thread. The caller must hold the :term:`GIL`. @@ -1803,7 +1803,7 @@ Python-level trace functions in previous versions. .. c:function:: void PyEval_SetTraceAllThreads(Py_tracefunc func, PyObject *obj) Like :c:func:`PyEval_SetTrace` but sets the tracing function in all running threads - instead of the setting it only on the current thread. + belonging to the current interpreter instead of the setting it only on the current thread. The caller must hold the :term:`GIL`. diff --git a/Python/clinic/sysmodule.c.h b/Python/clinic/sysmodule.c.h index a1ce3fc6c6748a..0f9636690a40e5 100644 --- a/Python/clinic/sysmodule.c.h +++ b/Python/clinic/sysmodule.c.h @@ -296,7 +296,7 @@ PyDoc_STRVAR(sys__settraceallthreads__doc__, "_settraceallthreads($module, arg, /)\n" "--\n" "\n" -"Set the global debug tracing function in all running threads.\n" +"Set the global debug tracing function in all running threads belonging to the current interpreter.\n" "\n" "It will be called on each function call. See the debugger chapter\n" "in the library manual."); @@ -328,7 +328,7 @@ PyDoc_STRVAR(sys__setprofileallthreads__doc__, "_setprofileallthreads($module, arg, /)\n" "--\n" "\n" -"Set the profiling function in all running threads.\n" +"Set the profiling function in all running threads belonging to the current interpreter.\n" "\n" "It will be called on each function call and return. See the profiler chapter\n" "in the library manual."); @@ -1194,8 +1194,4 @@ sys_getandroidapilevel(PyObject *module, PyObject *Py_UNUSED(ignored)) #ifndef SYS_GETANDROIDAPILEVEL_METHODDEF #define SYS_GETANDROIDAPILEVEL_METHODDEF #endif /* !defined(SYS_GETANDROIDAPILEVEL_METHODDEF) */ -<<<<<<< HEAD -/*[clinic end generated code: output=38446a4c76e2f3b6 input=a9049054013a1b77]*/ -======= -/*[clinic end generated code: output=541cc7ffee7d20f8 input=a9049054013a1b77]*/ ->>>>>>> d37f6a3263 (gh-93503: Add APIs to set profiling and tracing functions in all threads in the C-API) +/*[clinic end generated code: output=322fb0409e376ad4 input=a9049054013a1b77]*/ diff --git a/Python/sysmodule.c b/Python/sysmodule.c index 00da20c8d0001f..c286438794164a 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -1027,7 +1027,7 @@ sys._settraceallthreads arg: object / -Set the global debug tracing function in all running threads. +Set the global debug tracing function in all running threads belonging to the current interpreter. It will be called on each function call. See the debugger chapter in the library manual. @@ -1035,7 +1035,7 @@ in the library manual. static PyObject * sys__settraceallthreads(PyObject *module, PyObject *arg) -/*[clinic end generated code: output=161cca30207bf3ca input=57c1ae02b1b5ee94]*/ +/*[clinic end generated code: output=161cca30207bf3ca input=5906aa1485a50289]*/ { PyObject* argument = NULL; Py_tracefunc func = NULL; @@ -1102,7 +1102,7 @@ sys._setprofileallthreads arg: object / -Set the profiling function in all running threads. +Set the profiling function in all running threads belonging to the current interpreter. It will be called on each function call and return. See the profiler chapter in the library manual. @@ -1110,7 +1110,7 @@ in the library manual. static PyObject * sys__setprofileallthreads(PyObject *module, PyObject *arg) -/*[clinic end generated code: output=2d61319e27b309fe input=7daefd8797f0476f]*/ +/*[clinic end generated code: output=2d61319e27b309fe input=d1a356d3f4f9060a]*/ { PyObject* argument = NULL; Py_tracefunc func = NULL; pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy