https://github.com/python/cpython/commit/858e69eab0949852cc41733e8465250fc80d0b66
commit: 858e69eab0949852cc41733e8465250fc80d0b66
branch: main
author: Gabriele N. Tornetta <[email protected]>
committer: markshannon <[email protected]>
date: 2026-04-22T09:08:23+01:00
summary:
gh-142186: Allow all PEP-669 events to be per-code object and disableable
(GH-146182)
* Make the `PY_UNWIND` monitoring event available as a code-local
event to allow trapping on function exit events when an exception
bubbles up. This complements the PY_RETURN event by allowing to
catch any function exit event.
* Allow `PY_UNWIND` to be `DISABLE`d; disabling it disables the event for the
whole code object.
* Do the above for `PY_THROW`, `RAISE`, `EXCEPTION_HANDLED`, and `RERAISE`
events.
files:
A
Misc/NEWS.d/next/Core_and_Builtins/2026-03-23-11-34-37.gh-issue-142186.v8Yp3W.rst
M Doc/library/sys.monitoring.rst
M Doc/whatsnew/3.15.rst
M Include/cpython/monitoring.h
M Include/internal/pycore_ceval.h
M Include/internal/pycore_instruments.h
M Lib/test/test_monitoring.py
M Objects/genobject.c
M Python/ceval.c
M Python/ceval.h
M Python/instrumentation.c
diff --git a/Doc/library/sys.monitoring.rst b/Doc/library/sys.monitoring.rst
index 16e6b1d6dc786b..7cca6f2bcdae91 100644
--- a/Doc/library/sys.monitoring.rst
+++ b/Doc/library/sys.monitoring.rst
@@ -180,8 +180,8 @@ Local events
''''''''''''
Local events are associated with normal execution of the program and happen
-at clearly defined locations. All local events can be disabled.
-The local events are:
+at clearly defined locations. All local events can be disabled
+per location. The local events are:
* :monitoring-event:`PY_START`
* :monitoring-event:`PY_RESUME`
@@ -205,6 +205,8 @@ Using :monitoring-event:`BRANCH_LEFT` and
:monitoring-event:`BRANCH_RIGHT`
events will give much better performance as they can be disabled
independently.
+.. _monitoring-ancillary-events:
+
Ancillary events
''''''''''''''''
@@ -226,7 +228,7 @@ Other events
''''''''''''
Other events are not necessarily tied to a specific location in the
-program and cannot be individually disabled via :data:`DISABLE`.
+program and cannot be individually disabled per location.
The other events that can be monitored are:
@@ -234,6 +236,12 @@ The other events that can be monitored are:
* :monitoring-event:`PY_UNWIND`
* :monitoring-event:`RAISE`
* :monitoring-event:`EXCEPTION_HANDLED`
+* :monitoring-event:`RERAISE`
+
+.. versionchanged:: 3.15
+ Other events can now be turned on and disabled on a per code object
+ basis. Returning :data:`DISABLE` from a callback disables the event
+ for the entire code object (for the current tool).
The STOP_ITERATION event
@@ -247,8 +255,7 @@ raise an exception unless it would be visible to other code.
To allow tools to monitor for real exceptions without slowing down generators
and coroutines, the :monitoring-event:`STOP_ITERATION` event is provided.
-:monitoring-event:`STOP_ITERATION` can be locally disabled, unlike
-:monitoring-event:`RAISE`.
+:monitoring-event:`STOP_ITERATION` can be locally disabled.
Note that the :monitoring-event:`STOP_ITERATION` event and the
:monitoring-event:`RAISE` event for a :exc:`StopIteration` exception are
@@ -314,15 +321,14 @@ location by returning :data:`sys.monitoring.DISABLE` from
a callback function.
This does not change which events are set, or any other code locations for the
same event.
-Disabling events for specific locations is very important for high
-performance monitoring. For example, a program can be run under a
-debugger with no overhead if the debugger disables all monitoring
-except for a few breakpoints.
+:ref:`Other events <monitoring-event-global>` can be disabled on a per code
+object basis by returning :data:`sys.monitoring.DISABLE` from a callback
+function. This disables the event for the entire code object (for the current
+tool).
-If :data:`DISABLE` is returned by a callback for a
-:ref:`global event <monitoring-event-global>`, :exc:`ValueError` will be raised
-by the interpreter in a non-specific location (that is, no traceback will be
-provided).
+Disabling events for specific locations is very important for high performance
+monitoring. For example, a program can be run under a debugger with no overhead
+if the debugger disables all monitoring except for a few breakpoints.
.. function:: restart_events() -> None
diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst
index c4dac339be66af..9630df9aad3021 100644
--- a/Doc/whatsnew/3.15.rst
+++ b/Doc/whatsnew/3.15.rst
@@ -1119,6 +1119,19 @@ sys
(Contributed by Klaus Zimmermann in :gh:`137476`.)
+sys.monitoring
+--------------
+
+* The :ref:`other events <monitoring-event-global>`
+ (:monitoring-event:`PY_THROW`, :monitoring-event:`PY_UNWIND`,
+ :monitoring-event:`RAISE`, :monitoring-event:`EXCEPTION_HANDLED`, and
+ :monitoring-event:`RERAISE`) can now be turned on and disabled on a per code
+ object basis. Returning :data:`~sys.monitoring.DISABLE` from a callback for
+ one of these events disables the event for the entire code object (for the
+ current tool), rather than raising :exc:`ValueError` as in prior versions.
+ (Contributed by Gabriele N. Tornetta in :gh:`146182`.)
+
+
tarfile
-------
diff --git a/Include/cpython/monitoring.h b/Include/cpython/monitoring.h
index 5094c8c23ae32b..fa6168d95cd210 100644
--- a/Include/cpython/monitoring.h
+++ b/Include/cpython/monitoring.h
@@ -24,16 +24,20 @@ extern "C" {
#define PY_MONITORING_EVENT_STOP_ITERATION 10
#define PY_MONITORING_IS_INSTRUMENTED_EVENT(ev) \
- ((ev) < _PY_MONITORING_LOCAL_EVENTS)
+((ev) <= PY_MONITORING_EVENT_STOP_ITERATION)
-/* Other events, mainly exceptions */
+/* Other events, mainly exceptions.
+ * These can now be turned on and disabled on a per code object basis. */
-#define PY_MONITORING_EVENT_RAISE 11
+#define PY_MONITORING_EVENT_PY_UNWIND 11
#define PY_MONITORING_EVENT_EXCEPTION_HANDLED 12
-#define PY_MONITORING_EVENT_PY_UNWIND 13
+#define PY_MONITORING_EVENT_RAISE 13
#define PY_MONITORING_EVENT_PY_THROW 14
#define PY_MONITORING_EVENT_RERAISE 15
+#define _PY_MONITORING_IS_UNGROUPED_EVENT(ev) \
+((ev) < _PY_MONITORING_UNGROUPED_EVENTS)
+
/* Ancillary events */
diff --git a/Include/internal/pycore_ceval.h b/Include/internal/pycore_ceval.h
index 94a1f687b7b150..ee8eb1095fe541 100644
--- a/Include/internal/pycore_ceval.h
+++ b/Include/internal/pycore_ceval.h
@@ -320,7 +320,7 @@ PyObject * _PyEval_ImportNameWithImport(
PyAPI_FUNC(PyObject *)_PyEval_MatchClass(PyThreadState *tstate, PyObject
*subject, PyObject *type, Py_ssize_t nargs, PyObject *kwargs);
PyAPI_FUNC(PyObject *)_PyEval_MatchKeys(PyThreadState *tstate, PyObject *map,
PyObject *keys);
PyAPI_FUNC(void) _PyEval_MonitorRaise(PyThreadState *tstate,
_PyInterpreterFrame *frame, _Py_CODEUNIT *instr);
-PyAPI_FUNC(bool) _PyEval_NoToolsForUnwind(PyThreadState *tstate);
+PyAPI_FUNC(bool) _PyEval_NoToolsForUnwind(PyThreadState *tstate,
_PyInterpreterFrame *frame);
PyAPI_FUNC(int) _PyEval_UnpackIterableStackRef(PyThreadState *tstate, PyObject
*v, int argcnt, int argcntafter, _PyStackRef *sp);
PyAPI_FUNC(void) _PyEval_FrameClearAndPop(PyThreadState *tstate,
_PyInterpreterFrame *frame);
PyAPI_FUNC(PyObject **) _PyObjectArray_FromStackRefArray(_PyStackRef *input,
Py_ssize_t nargs, PyObject **scratch);
diff --git a/Include/internal/pycore_instruments.h
b/Include/internal/pycore_instruments.h
index cb1f50e441c265..56b55e93a014cb 100644
--- a/Include/internal/pycore_instruments.h
+++ b/Include/internal/pycore_instruments.h
@@ -70,16 +70,15 @@ PyAPI_DATA(PyObject) _PyInstrumentation_DISABLE;
/* Total tool ids available */
#define PY_MONITORING_TOOL_IDS 8
-/* Count of all local monitoring events */
-#define _PY_MONITORING_LOCAL_EVENTS 11
-/* Count of all "real" monitoring events (not derived from other events) */
+/* Count of all "real" monitoring events (not derived from other events).
+ * "Other" events can now be turned on/disabled per code object. */
#define _PY_MONITORING_UNGROUPED_EVENTS 16
/* Count of all monitoring events */
#define _PY_MONITORING_EVENTS 19
/* Tables of which tools are active for each monitored event. */
typedef struct _Py_LocalMonitors {
- uint8_t tools[_PY_MONITORING_LOCAL_EVENTS];
+ uint8_t tools[_PY_MONITORING_UNGROUPED_EVENTS];
} _Py_LocalMonitors;
typedef struct _Py_GlobalMonitors {
diff --git a/Lib/test/test_monitoring.py b/Lib/test/test_monitoring.py
index bc7af6e15380a4..b8861d09e1564b 100644
--- a/Lib/test/test_monitoring.py
+++ b/Lib/test/test_monitoring.py
@@ -196,13 +196,10 @@ def test_c_return_count(self):
(E.BRANCH, "branch"),
]
-EXCEPT_EVENTS = [
+SIMPLE_EVENTS = INSTRUMENTED_EVENTS + [
(E.RAISE, "raise"),
- (E.PY_UNWIND, "unwind"),
(E.EXCEPTION_HANDLED, "exception_handled"),
-]
-
-SIMPLE_EVENTS = INSTRUMENTED_EVENTS + EXCEPT_EVENTS + [
+ (E.PY_UNWIND, "unwind"),
(E.C_RAISE, "c_raise"),
(E.C_RETURN, "c_return"),
]
@@ -738,18 +735,6 @@ def test_disable_legal_events(self):
sys.monitoring.register_callback(TEST_TOOL, event, None)
- def test_disable_illegal_events(self):
- for event, name in EXCEPT_EVENTS:
- try:
- counter = CounterWithDisable()
- counter.disable = True
- sys.monitoring.register_callback(TEST_TOOL, event, counter)
- sys.monitoring.set_events(TEST_TOOL, event)
- with self.assertRaises(ValueError):
- self.raise_handle_reraise()
- finally:
- sys.monitoring.set_events(TEST_TOOL, 0)
- sys.monitoring.register_callback(TEST_TOOL, event, None)
class ExceptionRecorder:
@@ -1481,8 +1466,334 @@ def func3():
('line', 'func3', 6)])
def test_set_non_local_event(self):
+ # C_RETURN/C_RAISE are ancillary (derived) events — not settable as
local
with self.assertRaises(ValueError):
- sys.monitoring.set_local_events(TEST_TOOL, just_call.__code__,
E.RAISE)
+ sys.monitoring.set_local_events(TEST_TOOL, just_call.__code__,
E.C_RETURN)
+
+ def test_local_reraise(self):
+ """RERAISE fires as a local event only for the instrumented code
object."""
+
+ def foo():
+ try:
+ raise RuntimeError("test")
+ except RuntimeError:
+ raise
+
+ def bar():
+ try:
+ raise RuntimeError("test")
+ except RuntimeError:
+ raise
+
+ events = set()
+
+ def callback(code, offset, exc):
+ events.add(code.co_name)
+
+ try:
+ sys.monitoring.register_callback(TEST_TOOL, E.RERAISE, callback)
+ sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, E.RERAISE)
+ try:
+ foo()
+ except RuntimeError:
+ pass
+ try:
+ bar() # should NOT trigger the callback
+ except RuntimeError:
+ pass
+ self.assertEqual(events, {'foo'})
+ finally:
+ sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, 0)
+ sys.monitoring.register_callback(TEST_TOOL, E.RERAISE, None)
+
+ def test_local_reraise_disable(self):
+ """Returning DISABLE from a RERAISE callback disables it for that code
object."""
+
+ call_count = 0
+
+ def foo():
+ try:
+ raise RuntimeError("test")
+ except RuntimeError:
+ raise
+
+ def callback(code, offset, exc):
+ nonlocal call_count
+ call_count += 1
+ return sys.monitoring.DISABLE
+
+ try:
+ sys.monitoring.register_callback(TEST_TOOL, E.RERAISE, callback)
+ sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, E.RERAISE)
+ try:
+ foo()
+ except RuntimeError:
+ pass
+ self.assertEqual(call_count, 1)
+ try:
+ foo()
+ except RuntimeError:
+ pass
+ self.assertEqual(call_count, 1) # not fired again — disabled
+ finally:
+ sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, 0)
+ sys.monitoring.register_callback(TEST_TOOL, E.RERAISE, None)
+
+ def test_local_py_throw(self):
+ """PY_THROW fires as a local event only for the instrumented code
object."""
+
+ def gen_foo():
+ yield 1
+ yield 2
+
+ def gen_bar():
+ yield 1
+ yield 2
+
+ events = []
+
+ def callback(code, offset, exc):
+ events.append(code.co_name)
+
+ try:
+ sys.monitoring.register_callback(TEST_TOOL, E.PY_THROW, callback)
+ sys.monitoring.set_local_events(TEST_TOOL, gen_foo.__code__,
E.PY_THROW)
+
+ g = gen_foo()
+ next(g)
+ try:
+ g.throw(RuntimeError("test"))
+ except RuntimeError:
+ pass
+
+ h = gen_bar()
+ next(h)
+ try:
+ h.throw(RuntimeError("test")) # should NOT trigger the
callback
+ except RuntimeError:
+ pass
+
+ self.assertEqual(events, ['gen_foo'])
+ finally:
+ sys.monitoring.set_local_events(TEST_TOOL, gen_foo.__code__, 0)
+ sys.monitoring.register_callback(TEST_TOOL, E.PY_THROW, None)
+
+ def test_local_py_throw_disable(self):
+ """Returning DISABLE from a PY_THROW callback disables it for that
code object."""
+
+ call_count = 0
+
+ def gen_foo():
+ yield 1
+ yield 2
+
+ def callback(code, offset, exc):
+ nonlocal call_count
+ call_count += 1
+ return sys.monitoring.DISABLE
+
+ try:
+ sys.monitoring.register_callback(TEST_TOOL, E.PY_THROW, callback)
+ sys.monitoring.set_local_events(TEST_TOOL, gen_foo.__code__,
E.PY_THROW)
+
+ g = gen_foo()
+ next(g)
+ try:
+ g.throw(RuntimeError("test"))
+ except RuntimeError:
+ pass
+ self.assertEqual(call_count, 1)
+
+ g2 = gen_foo()
+ next(g2)
+ try:
+ g2.throw(RuntimeError("test"))
+ except RuntimeError:
+ pass
+ self.assertEqual(call_count, 1) # not fired again — disabled
+ finally:
+ sys.monitoring.set_local_events(TEST_TOOL, gen_foo.__code__, 0)
+ sys.monitoring.register_callback(TEST_TOOL, E.PY_THROW, None)
+
+ def test_local_raise(self):
+ """RAISE fires as a local event only for the instrumented code
object."""
+
+ def foo():
+ try:
+ raise RuntimeError("test")
+ except RuntimeError:
+ pass
+
+ def bar():
+ try:
+ raise RuntimeError("test")
+ except RuntimeError:
+ pass
+
+ events = []
+
+ def callback(code, offset, exc):
+ events.append(code.co_name)
+
+ try:
+ sys.monitoring.register_callback(TEST_TOOL, E.RAISE, callback)
+ sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, E.RAISE)
+ foo()
+ bar() # should NOT trigger the callback
+ self.assertEqual(events, ['foo'])
+ finally:
+ sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, 0)
+ sys.monitoring.register_callback(TEST_TOOL, E.RAISE, None)
+
+ def test_local_raise_disable(self):
+ """Returning DISABLE from a RAISE callback disables it for that code
object."""
+
+ call_count = 0
+
+ def foo():
+ try:
+ raise RuntimeError("test")
+ except RuntimeError:
+ pass
+
+ def callback(code, offset, exc):
+ nonlocal call_count
+ call_count += 1
+ return sys.monitoring.DISABLE
+
+ try:
+ sys.monitoring.register_callback(TEST_TOOL, E.RAISE, callback)
+ sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, E.RAISE)
+ foo()
+ self.assertEqual(call_count, 1)
+ foo()
+ self.assertEqual(call_count, 1) # not fired again — disabled
+ finally:
+ sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, 0)
+ sys.monitoring.register_callback(TEST_TOOL, E.RAISE, None)
+
+ def test_local_exception_handled(self):
+ """EXCEPTION_HANDLED fires as a local event only for the instrumented
code object."""
+
+ def foo():
+ try:
+ raise RuntimeError("test")
+ except RuntimeError:
+ pass
+
+ def bar():
+ try:
+ raise RuntimeError("test")
+ except RuntimeError:
+ pass
+
+ events = []
+
+ def callback(code, offset, exc):
+ events.append(code.co_name)
+
+ try:
+ sys.monitoring.register_callback(TEST_TOOL, E.EXCEPTION_HANDLED,
callback)
+ sys.monitoring.set_local_events(TEST_TOOL, foo.__code__,
E.EXCEPTION_HANDLED)
+ foo()
+ bar() # should NOT trigger the callback
+ self.assertEqual(events, ['foo'])
+ finally:
+ sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, 0)
+ sys.monitoring.register_callback(TEST_TOOL, E.EXCEPTION_HANDLED,
None)
+
+ def test_local_exception_handled_disable(self):
+ """Returning DISABLE from an EXCEPTION_HANDLED callback disables it
for that code object."""
+
+ call_count = 0
+
+ def foo():
+ try:
+ raise RuntimeError("test")
+ except RuntimeError:
+ pass
+
+ def callback(code, offset, exc):
+ nonlocal call_count
+ call_count += 1
+ return sys.monitoring.DISABLE
+
+ try:
+ sys.monitoring.register_callback(TEST_TOOL, E.EXCEPTION_HANDLED,
callback)
+ sys.monitoring.set_local_events(TEST_TOOL, foo.__code__,
E.EXCEPTION_HANDLED)
+ foo()
+ self.assertEqual(call_count, 1)
+ foo()
+ self.assertEqual(call_count, 1) # not fired again — disabled
+ finally:
+ sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, 0)
+ sys.monitoring.register_callback(TEST_TOOL, E.EXCEPTION_HANDLED,
None)
+
+ def test_local_py_unwind(self):
+ """PY_UNWIND fires as a local event only for the instrumented code
object."""
+
+ def foo():
+ raise RuntimeError("test")
+
+ def bar():
+ raise RuntimeError("test")
+
+ events = []
+
+ def callback(code, offset, exc):
+ events.append(code.co_name)
+
+ try:
+ sys.monitoring.register_callback(TEST_TOOL, E.PY_UNWIND, callback)
+ sys.monitoring.set_local_events(TEST_TOOL, foo.__code__,
E.PY_UNWIND)
+
+ try:
+ foo()
+ except RuntimeError:
+ pass
+
+ try:
+ bar() # should NOT trigger the callback
+ except RuntimeError:
+ pass
+
+ self.assertEqual(events, ['foo'])
+ finally:
+ sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, 0)
+ sys.monitoring.register_callback(TEST_TOOL, E.PY_UNWIND, None)
+
+ def test_local_py_unwind_disable(self):
+ """Returning DISABLE from a PY_UNWIND callback disables it for that
code object."""
+
+ call_count = 0
+
+ def foo():
+ raise RuntimeError("test")
+
+ def callback(code, offset, exc):
+ nonlocal call_count
+ call_count += 1
+ return sys.monitoring.DISABLE
+
+ try:
+ sys.monitoring.register_callback(TEST_TOOL, E.PY_UNWIND, callback)
+ sys.monitoring.set_local_events(TEST_TOOL, foo.__code__,
E.PY_UNWIND)
+
+ try:
+ foo()
+ except RuntimeError:
+ pass
+ self.assertEqual(call_count, 1) # fired once
+
+ try:
+ foo()
+ except RuntimeError:
+ pass
+ self.assertEqual(call_count, 1) # not fired again — disabled by
DISABLE return
+
+ finally:
+ sys.monitoring.set_local_events(TEST_TOOL, foo.__code__, 0)
+ sys.monitoring.register_callback(TEST_TOOL, E.PY_UNWIND, None)
def line_from_offset(code, offset):
for start, end, line in code.co_lines():
diff --git
a/Misc/NEWS.d/next/Core_and_Builtins/2026-03-23-11-34-37.gh-issue-142186.v8Yp3W.rst
b/Misc/NEWS.d/next/Core_and_Builtins/2026-03-23-11-34-37.gh-issue-142186.v8Yp3W.rst
new file mode 100644
index 00000000000000..4a04658551c444
--- /dev/null
+++
b/Misc/NEWS.d/next/Core_and_Builtins/2026-03-23-11-34-37.gh-issue-142186.v8Yp3W.rst
@@ -0,0 +1,3 @@
+Global :mod:`sys.monitoring` events can now be turned on and disabled on a
+per code object basis. Returning ``DISABLE`` from a callback disables the
+event for the entire code object (for the current tool).
diff --git a/Objects/genobject.c b/Objects/genobject.c
index 2bbe79c253d3e6..d628889afc6dc8 100644
--- a/Objects/genobject.c
+++ b/Objects/genobject.c
@@ -496,7 +496,7 @@ gen_close(PyObject *self, PyObject *args)
}
if (is_resume(frame->instr_ptr)) {
- bool no_unwind_tools = _PyEval_NoToolsForUnwind(_PyThreadState_GET());
+ bool no_unwind_tools = _PyEval_NoToolsForUnwind(_PyThreadState_GET(),
frame);
/* We can safely ignore the outermost try block
* as it is automatically generated to handle
* StopIteration. */
diff --git a/Python/ceval.c b/Python/ceval.c
index 03bc5229565966..967d92f4ea6855 100644
--- a/Python/ceval.c
+++ b/Python/ceval.c
@@ -2406,15 +2406,16 @@ void
_PyEval_MonitorRaise(PyThreadState *tstate, _PyInterpreterFrame *frame,
_Py_CODEUNIT *instr)
{
- if (no_tools_for_global_event(tstate, PY_MONITORING_EVENT_RAISE)) {
+ if (no_tools_for_local_event(tstate, frame, PY_MONITORING_EVENT_RAISE)) {
return;
}
do_monitor_exc(tstate, frame, instr, PY_MONITORING_EVENT_RAISE);
}
bool
-_PyEval_NoToolsForUnwind(PyThreadState *tstate) {
- return no_tools_for_global_event(tstate, PY_MONITORING_EVENT_PY_UNWIND);
+_PyEval_NoToolsForUnwind(PyThreadState *tstate, _PyInterpreterFrame *frame)
+{
+ return no_tools_for_local_event(tstate, frame,
PY_MONITORING_EVENT_PY_UNWIND);
}
diff --git a/Python/ceval.h b/Python/ceval.h
index bb5f7ddb857246..0437ab85c5a668 100644
--- a/Python/ceval.h
+++ b/Python/ceval.h
@@ -367,7 +367,7 @@ no_tools_for_global_event(PyThreadState *tstate, int event)
static inline bool
no_tools_for_local_event(PyThreadState *tstate, _PyInterpreterFrame *frame,
int event)
{
- assert(event < _PY_MONITORING_LOCAL_EVENTS);
+ assert(event < _PY_MONITORING_UNGROUPED_EVENTS);
_PyCoMonitoringData *data = _PyFrame_GetCode(frame)->_co_monitoring;
if (data) {
return data->active_monitors.tools[event] == 0;
@@ -382,7 +382,7 @@ monitor_handled(PyThreadState *tstate,
_PyInterpreterFrame *frame,
_Py_CODEUNIT *instr, PyObject *exc)
{
- if (no_tools_for_global_event(tstate,
PY_MONITORING_EVENT_EXCEPTION_HANDLED)) {
+ if (no_tools_for_local_event(tstate, frame,
PY_MONITORING_EVENT_EXCEPTION_HANDLED)) {
return 0;
}
return _Py_call_instrumentation_arg(tstate,
PY_MONITORING_EVENT_EXCEPTION_HANDLED, frame, instr, exc);
@@ -393,7 +393,7 @@ monitor_throw(PyThreadState *tstate,
_PyInterpreterFrame *frame,
_Py_CODEUNIT *instr)
{
- if (no_tools_for_global_event(tstate, PY_MONITORING_EVENT_PY_THROW)) {
+ if (no_tools_for_local_event(tstate, frame, PY_MONITORING_EVENT_PY_THROW))
{
return;
}
do_monitor_exc(tstate, frame, instr, PY_MONITORING_EVENT_PY_THROW);
@@ -403,7 +403,7 @@ static void
monitor_reraise(PyThreadState *tstate, _PyInterpreterFrame *frame,
_Py_CODEUNIT *instr)
{
- if (no_tools_for_global_event(tstate, PY_MONITORING_EVENT_RERAISE)) {
+ if (no_tools_for_local_event(tstate, frame, PY_MONITORING_EVENT_RERAISE)) {
return;
}
do_monitor_exc(tstate, frame, instr, PY_MONITORING_EVENT_RERAISE);
@@ -431,7 +431,7 @@ monitor_unwind(PyThreadState *tstate,
_PyInterpreterFrame *frame,
_Py_CODEUNIT *instr)
{
- if (no_tools_for_global_event(tstate, PY_MONITORING_EVENT_PY_UNWIND)) {
+ if (no_tools_for_local_event(tstate, frame,
PY_MONITORING_EVENT_PY_UNWIND)) {
return;
}
do_monitor_exc(tstate, frame, instr, PY_MONITORING_EVENT_PY_UNWIND);
diff --git a/Python/instrumentation.c b/Python/instrumentation.c
index 4041aa0d8aeaaf..51bcbfdb3b6c55 100644
--- a/Python/instrumentation.c
+++ b/Python/instrumentation.c
@@ -203,7 +203,7 @@ is_instrumented(int opcode)
static inline bool
monitors_equals(_Py_LocalMonitors a, _Py_LocalMonitors b)
{
- for (int i = 0; i < _PY_MONITORING_LOCAL_EVENTS; i++) {
+ for (int i = 0; i < _PY_MONITORING_UNGROUPED_EVENTS; i++) {
if (a.tools[i] != b.tools[i]) {
return false;
}
@@ -216,7 +216,7 @@ static inline _Py_LocalMonitors
monitors_sub(_Py_LocalMonitors a, _Py_LocalMonitors b)
{
_Py_LocalMonitors res;
- for (int i = 0; i < _PY_MONITORING_LOCAL_EVENTS; i++) {
+ for (int i = 0; i < _PY_MONITORING_UNGROUPED_EVENTS; i++) {
res.tools[i] = a.tools[i] & ~b.tools[i];
}
return res;
@@ -227,7 +227,7 @@ static inline _Py_LocalMonitors
monitors_and(_Py_LocalMonitors a, _Py_LocalMonitors b)
{
_Py_LocalMonitors res;
- for (int i = 0; i < _PY_MONITORING_LOCAL_EVENTS; i++) {
+ for (int i = 0; i < _PY_MONITORING_UNGROUPED_EVENTS; i++) {
res.tools[i] = a.tools[i] & b.tools[i];
}
return res;
@@ -243,7 +243,7 @@ static inline _Py_LocalMonitors
local_union(_Py_GlobalMonitors a, _Py_LocalMonitors b)
{
_Py_LocalMonitors res;
- for (int i = 0; i < _PY_MONITORING_LOCAL_EVENTS; i++) {
+ for (int i = 0; i < _PY_MONITORING_UNGROUPED_EVENTS; i++) {
res.tools[i] = a.tools[i] | b.tools[i];
}
return res;
@@ -252,7 +252,7 @@ local_union(_Py_GlobalMonitors a, _Py_LocalMonitors b)
static inline bool
monitors_are_empty(_Py_LocalMonitors m)
{
- for (int i = 0; i < _PY_MONITORING_LOCAL_EVENTS; i++) {
+ for (int i = 0; i < _PY_MONITORING_UNGROUPED_EVENTS; i++) {
if (m.tools[i]) {
return false;
}
@@ -263,7 +263,7 @@ monitors_are_empty(_Py_LocalMonitors m)
static inline bool
multiple_tools(_Py_LocalMonitors *m)
{
- for (int i = 0; i < _PY_MONITORING_LOCAL_EVENTS; i++) {
+ for (int i = 0; i < _PY_MONITORING_UNGROUPED_EVENTS; i++) {
if (_Py_popcount32(m->tools[i]) > 1) {
return true;
}
@@ -275,7 +275,7 @@ static inline _PyMonitoringEventSet
get_local_events(_Py_LocalMonitors *m, int tool_id)
{
_PyMonitoringEventSet result = 0;
- for (int e = 0; e < _PY_MONITORING_LOCAL_EVENTS; e++) {
+ for (int e = 0; e < _PY_MONITORING_UNGROUPED_EVENTS; e++) {
if ((m->tools[e] >> tool_id) & 1) {
result |= (1 << e);
}
@@ -453,7 +453,7 @@ static void
dump_local_monitors(const char *prefix, _Py_LocalMonitors monitors, FILE*out)
{
fprintf(out, "%s monitors:\n", prefix);
- for (int event = 0; event < _PY_MONITORING_LOCAL_EVENTS; event++) {
+ for (int event = 0; event < _PY_MONITORING_UNGROUPED_EVENTS; event++) {
fprintf(out, " Event %d: Tools %x\n", event, monitors.tools[event]);
}
}
@@ -1102,8 +1102,10 @@ get_tools_for_instruction(PyCodeObject *code,
PyInterpreterState *interp, int i,
event == PY_MONITORING_EVENT_C_RETURN);
event = PY_MONITORING_EVENT_CALL;
}
+ assert(_PY_MONITORING_IS_UNGROUPED_EVENT(event));
+ CHECK(debug_check_sanity(interp, code));
if (PY_MONITORING_IS_INSTRUMENTED_EVENT(event)) {
- CHECK(debug_check_sanity(interp, code));
+ /* Instrumented events use per-instruction tool bitmaps. */
if (code->_co_monitoring->tools) {
tools = code->_co_monitoring->tools[i];
}
@@ -1112,7 +1114,9 @@ get_tools_for_instruction(PyCodeObject *code,
PyInterpreterState *interp, int i,
}
}
else {
- tools = interp->monitors.tools[event];
+ /* Other (non-instrumented) events are not tied to specific
instructions;
+ * use the code-object-level active_monitors bitmap instead. */
+ tools = code->_co_monitoring->active_monitors.tools[event];
}
return tools;
}
@@ -1139,6 +1143,25 @@ static const char *const event_names [] = {
[PY_MONITORING_EVENT_STOP_ITERATION] = "STOP_ITERATION",
};
+/* Disable an "other" (non-instrumented) event (e.g. PY_UNWIND) for a single
+ * tool on this code object. Must be called with the world stopped or the
+ * code lock held. */
+static void
+remove_local_tool(PyCodeObject *code, PyInterpreterState *interp,
+ int event, int tool)
+{
+ ASSERT_WORLD_STOPPED_OR_LOCKED(code);
+ assert(_PY_MONITORING_IS_UNGROUPED_EVENT(event));
+ assert(!PY_MONITORING_IS_INSTRUMENTED_EVENT(event));
+ assert(code->_co_monitoring);
+ code->_co_monitoring->local_monitors.tools[event] &= ~(1 << tool);
+ /* Recompute active_monitors for this event as the union of global and
+ * (now updated) local monitors. */
+ code->_co_monitoring->active_monitors.tools[event] =
+ interp->monitors.tools[event] |
+ code->_co_monitoring->local_monitors.tools[event];
+}
+
static int
call_instrumentation_vector(
_Py_CODEUNIT *instr, PyThreadState *tstate, int event,
@@ -1183,7 +1206,18 @@ call_instrumentation_vector(
}
else {
/* DISABLE */
- if (!PY_MONITORING_IS_INSTRUMENTED_EVENT(event)) {
+ if (PY_MONITORING_IS_INSTRUMENTED_EVENT(event)) {
+ _PyEval_StopTheWorld(interp);
+ remove_tools(code, offset, event, 1 << tool);
+ _PyEval_StartTheWorld(interp);
+ }
+ else if (_PY_MONITORING_IS_UNGROUPED_EVENT(event)) {
+ /* Other (non-instrumented) event: disable for this code
object. */
+ _PyEval_StopTheWorld(interp);
+ remove_local_tool(code, interp, event, tool);
+ _PyEval_StartTheWorld(interp);
+ }
+ else {
PyErr_Format(PyExc_ValueError,
"Cannot disable %s events. Callback removed.",
event_names[event]);
@@ -1192,12 +1226,6 @@ call_instrumentation_vector(
err = -1;
break;
}
- else {
- PyInterpreterState *interp = tstate->interp;
- _PyEval_StopTheWorld(interp);
- remove_tools(code, offset, event, 1 << tool);
- _PyEval_StartTheWorld(interp);
- }
}
}
Py_DECREF(arg2_obj);
@@ -1681,7 +1709,7 @@ update_instrumentation_data(PyCodeObject *code,
PyInterpreterState *interp)
_Py_LocalMonitors *local_monitors = &code->_co_monitoring->local_monitors;
for (int i = 0; i < PY_MONITORING_TOOL_IDS; i++) {
if (code->_co_monitoring->tool_versions[i] !=
interp->monitoring_tool_versions[i]) {
- for (int j = 0; j < _PY_MONITORING_LOCAL_EVENTS; j++) {
+ for (int j = 0; j < _PY_MONITORING_UNGROUPED_EVENTS; j++) {
local_monitors->tools[j] &= ~(1 << i);
}
}
@@ -1977,7 +2005,7 @@ static void
set_local_events(_Py_LocalMonitors *m, int tool_id, _PyMonitoringEventSet
events)
{
assert(0 <= tool_id && tool_id < PY_MONITORING_TOOL_IDS);
- for (int e = 0; e < _PY_MONITORING_LOCAL_EVENTS; e++) {
+ for (int e = 0; e < _PY_MONITORING_UNGROUPED_EVENTS; e++) {
uint8_t *tools = &m->tools[e];
int val = (events >> e) & 1;
*tools &= ~(1 << tool_id);
@@ -2037,7 +2065,7 @@ _PyMonitoring_SetLocalEvents(PyCodeObject *code, int
tool_id, _PyMonitoringEvent
assert(0 <= tool_id && tool_id < PY_MONITORING_TOOL_IDS);
PyInterpreterState *interp = _PyInterpreterState_GET();
- assert(events < (1 << _PY_MONITORING_LOCAL_EVENTS));
+ assert(events < (1 << _PY_MONITORING_UNGROUPED_EVENTS));
if (code->_co_firsttraceable >= Py_SIZE(code)) {
PyErr_Format(PyExc_SystemError, "cannot instrument shim code object
'%U'", code->co_name);
return -1;
@@ -2373,7 +2401,7 @@ monitoring_get_local_events_impl(PyObject *module, int
tool_id,
_PyMonitoringEventSet event_set = 0;
_PyCoMonitoringData *data = ((PyCodeObject *)code)->_co_monitoring;
if (data != NULL) {
- for (int e = 0; e < _PY_MONITORING_LOCAL_EVENTS; e++) {
+ for (int e = 0; e < _PY_MONITORING_UNGROUPED_EVENTS; e++) {
if ((data->local_monitors.tools[e] >> tool_id) & 1) {
event_set |= (1 << e);
}
@@ -2416,7 +2444,7 @@ monitoring_set_local_events_impl(PyObject *module, int
tool_id,
event_set &= ~(1 << PY_MONITORING_EVENT_BRANCH);
event_set |= (1 << PY_MONITORING_EVENT_BRANCH_RIGHT) | (1 <<
PY_MONITORING_EVENT_BRANCH_LEFT);
}
- if (event_set < 0 || event_set >= (1 << _PY_MONITORING_LOCAL_EVENTS)) {
+ if (event_set < 0 || event_set >= (1 << _PY_MONITORING_UNGROUPED_EVENTS)) {
PyErr_Format(PyExc_ValueError, "invalid local event set 0x%x",
event_set);
return NULL;
}
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]