]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-118692: Avoid creating unnecessary StopIteration instances for monitoring (#119216)
authorIrit Katriel <1055913+iritkatriel@users.noreply.github.com>
Tue, 21 May 2024 20:42:51 +0000 (16:42 -0400)
committerGitHub <noreply@github.com>
Tue, 21 May 2024 20:42:51 +0000 (20:42 +0000)
Doc/c-api/monitoring.rst
Include/cpython/monitoring.h
Include/internal/pycore_opcode_metadata.h
Lib/test/test_monitoring.py
Misc/NEWS.d/next/Core and Builtins/2024-05-20-14-57-39.gh-issue-118692.Qadm7F.rst [new file with mode: 0644]
Modules/_testcapi/monitoring.c
Python/bytecodes.c
Python/ceval.c
Python/generated_cases.c.h
Python/instrumentation.c

index 763ec8ef761e4ebac9236fbb97e9d05d6e2f57f7..ec743b98ba702492ea384b85d9f7db304fb97a97 100644 (file)
@@ -121,10 +121,10 @@ See :mod:`sys.monitoring` for descriptions of the events.
    :c:func:`PyErr_GetRaisedException`).
 
 
-.. c:function:: int PyMonitoring_FireStopIterationEvent(PyMonitoringState *state, PyObject *codelike, int32_t offset)
+.. c:function:: int PyMonitoring_FireStopIterationEvent(PyMonitoringState *state, PyObject *codelike, int32_t offset, PyObject *value)
 
-   Fire a ``STOP_ITERATION`` event with the current exception (as returned by
-   :c:func:`PyErr_GetRaisedException`).
+   Fire a ``STOP_ITERATION`` event. If ``value`` is an instance of :exc:`StopIteration`, it is used. Otherwise,
+   a new :exc:`StopIteration` instance is created with ``value`` as its argument.
 
 
 Managing the Monitoring State
index efb9ec0e587552c4800eb229e0879168a4976e53..797ba51246b1c63e5ee3a765c7742751e5acd002 100644 (file)
@@ -101,7 +101,7 @@ PyAPI_FUNC(int)
 _PyMonitoring_FirePyUnwindEvent(PyMonitoringState *state, PyObject *codelike, int32_t offset);
 
 PyAPI_FUNC(int)
-_PyMonitoring_FireStopIterationEvent(PyMonitoringState *state, PyObject *codelike, int32_t offset);
+_PyMonitoring_FireStopIterationEvent(PyMonitoringState *state, PyObject *codelike, int32_t offset, PyObject *value);
 
 
 #define _PYMONITORING_IF_ACTIVE(STATE, X)  \
@@ -240,11 +240,11 @@ PyMonitoring_FirePyUnwindEvent(PyMonitoringState *state, PyObject *codelike, int
 }
 
 static inline int
-PyMonitoring_FireStopIterationEvent(PyMonitoringState *state, PyObject *codelike, int32_t offset)
+PyMonitoring_FireStopIterationEvent(PyMonitoringState *state, PyObject *codelike, int32_t offset, PyObject *value)
 {
     _PYMONITORING_IF_ACTIVE(
         state,
-        _PyMonitoring_FireStopIterationEvent(state, codelike, offset));
+        _PyMonitoring_FireStopIterationEvent(state, codelike, offset, value));
 }
 
 #undef _PYMONITORING_IF_ACTIVE
index 2a237bc6dd8ee536d455da5626a3a6ed463cf521..665b8627dfcc5244112b712dabdd96e85989b7c8 100644 (file)
@@ -1060,8 +1060,8 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[268] = {
     [INSTRUMENTED_CALL] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
     [INSTRUMENTED_CALL_FUNCTION_EX] = { true, INSTR_FMT_IX, 0 },
     [INSTRUMENTED_CALL_KW] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
-    [INSTRUMENTED_END_FOR] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG },
-    [INSTRUMENTED_END_SEND] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG },
+    [INSTRUMENTED_END_FOR] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG },
+    [INSTRUMENTED_END_SEND] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG },
     [INSTRUMENTED_FOR_ITER] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG },
     [INSTRUMENTED_INSTRUCTION] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG },
     [INSTRUMENTED_JUMP_BACKWARD] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG },
index 6974bc5517ae5fb0fad9d690a02ae178b4399cd0..b7c6abed1016dc4a4b270420e94e8d02b1310513 100644 (file)
@@ -1938,19 +1938,28 @@ class TestCApiEventGeneration(MonitoringTestBase, unittest.TestCase):
             ( 1, E.RAISE, capi.fire_event_raise, ValueError(2)),
             ( 1, E.EXCEPTION_HANDLED, capi.fire_event_exception_handled, ValueError(5)),
             ( 1, E.PY_UNWIND, capi.fire_event_py_unwind, ValueError(6)),
-            ( 1, E.STOP_ITERATION, capi.fire_event_stop_iteration, ValueError(7)),
+            ( 1, E.STOP_ITERATION, capi.fire_event_stop_iteration, 7),
+            ( 1, E.STOP_ITERATION, capi.fire_event_stop_iteration, StopIteration(8)),
         ]
 
+        self.EXPECT_RAISED_EXCEPTION = [E.PY_THROW, E.RAISE, E.EXCEPTION_HANDLED, E.PY_UNWIND]
 
-    def check_event_count(self, event, func, args, expected):
+
+    def check_event_count(self, event, func, args, expected, callback_raises=None):
         class Counter:
-            def __init__(self):
+            def __init__(self, callback_raises):
+                self.callback_raises = callback_raises
                 self.count = 0
+
             def __call__(self, *args):
                 self.count += 1
+                if self.callback_raises:
+                    exc = self.callback_raises
+                    self.callback_raises = None
+                    raise exc
 
         try:
-            counter = Counter()
+            counter = Counter(callback_raises)
             sys.monitoring.register_callback(TEST_TOOL, event, counter)
             if event == E.C_RETURN or event == E.C_RAISE:
                 sys.monitoring.set_events(TEST_TOOL, E.CALL)
@@ -1987,8 +1996,9 @@ class TestCApiEventGeneration(MonitoringTestBase, unittest.TestCase):
 
     def test_missing_exception(self):
         for _, event, function, *args in self.cases:
-            if not (args and isinstance(args[-1], BaseException)):
+            if event not in self.EXPECT_RAISED_EXCEPTION:
                 continue
+            assert args and isinstance(args[-1], BaseException)
             offset = 0
             self.codelike = _testcapi.CodeLike(1)
             with self.subTest(function.__name__):
@@ -1997,6 +2007,17 @@ class TestCApiEventGeneration(MonitoringTestBase, unittest.TestCase):
                 expected = ValueError(f"Firing event {evt} with no exception set")
                 self.check_event_count(event, function, args_, expected)
 
+    def test_fire_event_failing_callback(self):
+        for expected, event, function, *args in self.cases:
+            offset = 0
+            self.codelike = _testcapi.CodeLike(1)
+            with self.subTest(function.__name__):
+                args_ = (self.codelike, offset) + tuple(args)
+                exc = OSError(42)
+                with self.assertRaises(type(exc)):
+                    self.check_event_count(event, function, args_, expected,
+                                           callback_raises=exc)
+
 
     CANNOT_DISABLE = { E.PY_THROW, E.RAISE, E.RERAISE,
                        E.EXCEPTION_HANDLED, E.PY_UNWIND }
diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-05-20-14-57-39.gh-issue-118692.Qadm7F.rst b/Misc/NEWS.d/next/Core and Builtins/2024-05-20-14-57-39.gh-issue-118692.Qadm7F.rst
new file mode 100644 (file)
index 0000000..11d1778
--- /dev/null
@@ -0,0 +1 @@
+Avoid creating unnecessary :exc:`StopIteration` instances for monitoring.
index aa90cfc06c1536fd679c654d9d834b921622665a..6fd4a405688f48da4798ecea457064ef20c73549 100644 (file)
@@ -416,16 +416,17 @@ fire_event_stop_iteration(PyObject *self, PyObject *args)
 {
     PyObject *codelike;
     int offset;
-    PyObject *exception;
-    if (!PyArg_ParseTuple(args, "OiO", &codelike, &offset, &exception)) {
+    PyObject *value;
+    if (!PyArg_ParseTuple(args, "OiO", &codelike, &offset, &value)) {
         return NULL;
     }
-    NULLABLE(exception);
+    NULLABLE(value);
+    PyObject *exception = NULL;
     PyMonitoringState *state = setup_fire(codelike, offset, exception);
     if (state == NULL) {
         return NULL;
     }
-    int res = PyMonitoring_FireStopIterationEvent(state, codelike, offset);
+    int res = PyMonitoring_FireStopIterationEvent(state, codelike, offset, value);
     RETURN_INT(teardown_fire(res, state, exception));
 }
 
index 55eda9711dea1feb38c83586be1ae2b735317540..434eb8042138109cbddb8e421501a0bcd20fa32f 100644 (file)
@@ -292,11 +292,9 @@ dummy_func(
             /* Need to create a fake StopIteration error here,
              * to conform to PEP 380 */
             if (PyGen_Check(receiver)) {
-                PyErr_SetObject(PyExc_StopIteration, value);
-                if (monitor_stop_iteration(tstate, frame, this_instr)) {
+                if (monitor_stop_iteration(tstate, frame, this_instr, value)) {
                     ERROR_NO_POP();
                 }
-                PyErr_SetRaisedException(NULL);
             }
             DECREF_INPUTS();
         }
@@ -307,11 +305,9 @@ dummy_func(
 
         tier1 inst(INSTRUMENTED_END_SEND, (receiver, value -- value)) {
             if (PyGen_Check(receiver) || PyCoro_CheckExact(receiver)) {
-                PyErr_SetObject(PyExc_StopIteration, value);
-                if (monitor_stop_iteration(tstate, frame, this_instr)) {
+                if (monitor_stop_iteration(tstate, frame, this_instr, value)) {
                     ERROR_NO_POP();
                 }
-                PyErr_SetRaisedException(NULL);
             }
             Py_DECREF(receiver);
         }
index 128e0417a9fd6348f68d7f0a2b489b93d9a606a3..324d062fe9bb43a37fff70dc356666d6fec22392 100644 (file)
@@ -231,7 +231,8 @@ static void monitor_reraise(PyThreadState *tstate,
                  _Py_CODEUNIT *instr);
 static int monitor_stop_iteration(PyThreadState *tstate,
                  _PyInterpreterFrame *frame,
-                 _Py_CODEUNIT *instr);
+                 _Py_CODEUNIT *instr,
+                 PyObject *value);
 static void monitor_unwind(PyThreadState *tstate,
                  _PyInterpreterFrame *frame,
                  _Py_CODEUNIT *instr);
@@ -2215,12 +2216,19 @@ monitor_reraise(PyThreadState *tstate, _PyInterpreterFrame *frame,
 
 static int
 monitor_stop_iteration(PyThreadState *tstate, _PyInterpreterFrame *frame,
-                       _Py_CODEUNIT *instr)
+                       _Py_CODEUNIT *instr, PyObject *value)
 {
     if (no_tools_for_local_event(tstate, frame, PY_MONITORING_EVENT_STOP_ITERATION)) {
         return 0;
     }
-    return do_monitor_exc(tstate, frame, instr, PY_MONITORING_EVENT_STOP_ITERATION);
+    assert(!PyErr_Occurred());
+    PyErr_SetObject(PyExc_StopIteration, value);
+    int res = do_monitor_exc(tstate, frame, instr, PY_MONITORING_EVENT_STOP_ITERATION);
+    if (res < 0) {
+        return res;
+    }
+    PyErr_SetRaisedException(NULL);
+    return 0;
 }
 
 static void
index 8b8112209cc78a999b6d56e466e5015630259ae4..96161c5a6586fd2a613c235d423fc51c1fe4817b 100644 (file)
             /* Need to create a fake StopIteration error here,
              * to conform to PEP 380 */
             if (PyGen_Check(receiver)) {
-                PyErr_SetObject(PyExc_StopIteration, value);
-                if (monitor_stop_iteration(tstate, frame, this_instr)) {
+                if (monitor_stop_iteration(tstate, frame, this_instr, value)) {
                     goto error;
                 }
-                PyErr_SetRaisedException(NULL);
             }
             Py_DECREF(value);
             stack_pointer += -1;
             value = stack_pointer[-1];
             receiver = stack_pointer[-2];
             if (PyGen_Check(receiver) || PyCoro_CheckExact(receiver)) {
-                PyErr_SetObject(PyExc_StopIteration, value);
-                if (monitor_stop_iteration(tstate, frame, this_instr)) {
+                if (monitor_stop_iteration(tstate, frame, this_instr, value)) {
                     goto error;
                 }
-                PyErr_SetRaisedException(NULL);
             }
             Py_DECREF(receiver);
             stack_pointer[-2] = value;
index 77d3489afcfc722ee768df07ab0bca73b841b36c..3d78214738e66b86a261d0136b494d08386ff4bd 100644 (file)
@@ -2622,7 +2622,7 @@ exception_event_teardown(int err, PyObject *exc) {
     }
     else {
         assert(PyErr_Occurred());
-        Py_DECREF(exc);
+        Py_XDECREF(exc);
     }
     return err;
 }
@@ -2712,15 +2712,17 @@ _PyMonitoring_FirePyUnwindEvent(PyMonitoringState *state, PyObject *codelike, in
 }
 
 int
-_PyMonitoring_FireStopIterationEvent(PyMonitoringState *state, PyObject *codelike, int32_t offset)
+_PyMonitoring_FireStopIterationEvent(PyMonitoringState *state, PyObject *codelike, int32_t offset, PyObject *value)
 {
     int event = PY_MONITORING_EVENT_STOP_ITERATION;
     assert(state->active);
+    assert(!PyErr_Occurred());
+    PyErr_SetObject(PyExc_StopIteration, value);
     PyObject *exc;
     if (exception_event_setup(&exc, event) < 0) {
         return -1;
     }
     PyObject *args[4] = { NULL, NULL, NULL, exc };
     int err = capi_call_instrumentation(state, codelike, offset, args, 3, event);
-    return exception_event_teardown(err, exc);
+    return exception_event_teardown(err, NULL);
 }