Skip to content

Commit 6e9863d

Browse files
authored
gh-118692: Avoid creating unnecessary StopIteration instances for monitoring (#119216)
1 parent b641825 commit 6e9863d

File tree

10 files changed

+60
-35
lines changed

10 files changed

+60
-35
lines changed

Diff for: Doc/c-api/monitoring.rst

+3-3
Original file line numberDiff line numberDiff line change
@@ -121,10 +121,10 @@ See :mod:`sys.monitoring` for descriptions of the events.
121121
:c:func:`PyErr_GetRaisedException`).
122122
123123
124-
.. c:function:: int PyMonitoring_FireStopIterationEvent(PyMonitoringState *state, PyObject *codelike, int32_t offset)
124+
.. c:function:: int PyMonitoring_FireStopIterationEvent(PyMonitoringState *state, PyObject *codelike, int32_t offset, PyObject *value)
125125
126-
Fire a ``STOP_ITERATION`` event with the current exception (as returned by
127-
:c:func:`PyErr_GetRaisedException`).
126+
Fire a ``STOP_ITERATION`` event. If ``value`` is an instance of :exc:`StopIteration`, it is used. Otherwise,
127+
a new :exc:`StopIteration` instance is created with ``value`` as its argument.
128128
129129
130130
Managing the Monitoring State

Diff for: Include/cpython/monitoring.h

+3-3
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ PyAPI_FUNC(int)
101101
_PyMonitoring_FirePyUnwindEvent(PyMonitoringState *state, PyObject *codelike, int32_t offset);
102102

103103
PyAPI_FUNC(int)
104-
_PyMonitoring_FireStopIterationEvent(PyMonitoringState *state, PyObject *codelike, int32_t offset);
104+
_PyMonitoring_FireStopIterationEvent(PyMonitoringState *state, PyObject *codelike, int32_t offset, PyObject *value);
105105

106106

107107
#define _PYMONITORING_IF_ACTIVE(STATE, X) \
@@ -240,11 +240,11 @@ PyMonitoring_FirePyUnwindEvent(PyMonitoringState *state, PyObject *codelike, int
240240
}
241241

242242
static inline int
243-
PyMonitoring_FireStopIterationEvent(PyMonitoringState *state, PyObject *codelike, int32_t offset)
243+
PyMonitoring_FireStopIterationEvent(PyMonitoringState *state, PyObject *codelike, int32_t offset, PyObject *value)
244244
{
245245
_PYMONITORING_IF_ACTIVE(
246246
state,
247-
_PyMonitoring_FireStopIterationEvent(state, codelike, offset));
247+
_PyMonitoring_FireStopIterationEvent(state, codelike, offset, value));
248248
}
249249

250250
#undef _PYMONITORING_IF_ACTIVE

Diff for: Include/internal/pycore_opcode_metadata.h

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: Lib/test/test_monitoring.py

+26-5
Original file line numberDiff line numberDiff line change
@@ -1938,19 +1938,28 @@ def setUp(self):
19381938
( 1, E.RAISE, capi.fire_event_raise, ValueError(2)),
19391939
( 1, E.EXCEPTION_HANDLED, capi.fire_event_exception_handled, ValueError(5)),
19401940
( 1, E.PY_UNWIND, capi.fire_event_py_unwind, ValueError(6)),
1941-
( 1, E.STOP_ITERATION, capi.fire_event_stop_iteration, ValueError(7)),
1941+
( 1, E.STOP_ITERATION, capi.fire_event_stop_iteration, 7),
1942+
( 1, E.STOP_ITERATION, capi.fire_event_stop_iteration, StopIteration(8)),
19421943
]
19431944

1945+
self.EXPECT_RAISED_EXCEPTION = [E.PY_THROW, E.RAISE, E.EXCEPTION_HANDLED, E.PY_UNWIND]
19441946

1945-
def check_event_count(self, event, func, args, expected):
1947+
1948+
def check_event_count(self, event, func, args, expected, callback_raises=None):
19461949
class Counter:
1947-
def __init__(self):
1950+
def __init__(self, callback_raises):
1951+
self.callback_raises = callback_raises
19481952
self.count = 0
1953+
19491954
def __call__(self, *args):
19501955
self.count += 1
1956+
if self.callback_raises:
1957+
exc = self.callback_raises
1958+
self.callback_raises = None
1959+
raise exc
19511960

19521961
try:
1953-
counter = Counter()
1962+
counter = Counter(callback_raises)
19541963
sys.monitoring.register_callback(TEST_TOOL, event, counter)
19551964
if event == E.C_RETURN or event == E.C_RAISE:
19561965
sys.monitoring.set_events(TEST_TOOL, E.CALL)
@@ -1987,8 +1996,9 @@ def test_fire_event(self):
19871996

19881997
def test_missing_exception(self):
19891998
for _, event, function, *args in self.cases:
1990-
if not (args and isinstance(args[-1], BaseException)):
1999+
if event not in self.EXPECT_RAISED_EXCEPTION:
19912000
continue
2001+
assert args and isinstance(args[-1], BaseException)
19922002
offset = 0
19932003
self.codelike = _testcapi.CodeLike(1)
19942004
with self.subTest(function.__name__):
@@ -1997,6 +2007,17 @@ def test_missing_exception(self):
19972007
expected = ValueError(f"Firing event {evt} with no exception set")
19982008
self.check_event_count(event, function, args_, expected)
19992009

2010+
def test_fire_event_failing_callback(self):
2011+
for expected, event, function, *args in self.cases:
2012+
offset = 0
2013+
self.codelike = _testcapi.CodeLike(1)
2014+
with self.subTest(function.__name__):
2015+
args_ = (self.codelike, offset) + tuple(args)
2016+
exc = OSError(42)
2017+
with self.assertRaises(type(exc)):
2018+
self.check_event_count(event, function, args_, expected,
2019+
callback_raises=exc)
2020+
20002021

20012022
CANNOT_DISABLE = { E.PY_THROW, E.RAISE, E.RERAISE,
20022023
E.EXCEPTION_HANDLED, E.PY_UNWIND }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Avoid creating unnecessary :exc:`StopIteration` instances for monitoring.

Diff for: Modules/_testcapi/monitoring.c

+5-4
Original file line numberDiff line numberDiff line change
@@ -416,16 +416,17 @@ fire_event_stop_iteration(PyObject *self, PyObject *args)
416416
{
417417
PyObject *codelike;
418418
int offset;
419-
PyObject *exception;
420-
if (!PyArg_ParseTuple(args, "OiO", &codelike, &offset, &exception)) {
419+
PyObject *value;
420+
if (!PyArg_ParseTuple(args, "OiO", &codelike, &offset, &value)) {
421421
return NULL;
422422
}
423-
NULLABLE(exception);
423+
NULLABLE(value);
424+
PyObject *exception = NULL;
424425
PyMonitoringState *state = setup_fire(codelike, offset, exception);
425426
if (state == NULL) {
426427
return NULL;
427428
}
428-
int res = PyMonitoring_FireStopIterationEvent(state, codelike, offset);
429+
int res = PyMonitoring_FireStopIterationEvent(state, codelike, offset, value);
429430
RETURN_INT(teardown_fire(res, state, exception));
430431
}
431432

Diff for: Python/bytecodes.c

+2-6
Original file line numberDiff line numberDiff line change
@@ -292,11 +292,9 @@ dummy_func(
292292
/* Need to create a fake StopIteration error here,
293293
* to conform to PEP 380 */
294294
if (PyGen_Check(receiver)) {
295-
PyErr_SetObject(PyExc_StopIteration, value);
296-
if (monitor_stop_iteration(tstate, frame, this_instr)) {
295+
if (monitor_stop_iteration(tstate, frame, this_instr, value)) {
297296
ERROR_NO_POP();
298297
}
299-
PyErr_SetRaisedException(NULL);
300298
}
301299
DECREF_INPUTS();
302300
}
@@ -307,11 +305,9 @@ dummy_func(
307305

308306
tier1 inst(INSTRUMENTED_END_SEND, (receiver, value -- value)) {
309307
if (PyGen_Check(receiver) || PyCoro_CheckExact(receiver)) {
310-
PyErr_SetObject(PyExc_StopIteration, value);
311-
if (monitor_stop_iteration(tstate, frame, this_instr)) {
308+
if (monitor_stop_iteration(tstate, frame, this_instr, value)) {
312309
ERROR_NO_POP();
313310
}
314-
PyErr_SetRaisedException(NULL);
315311
}
316312
Py_DECREF(receiver);
317313
}

Diff for: Python/ceval.c

+11-3
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,8 @@ static void monitor_reraise(PyThreadState *tstate,
231231
_Py_CODEUNIT *instr);
232232
static int monitor_stop_iteration(PyThreadState *tstate,
233233
_PyInterpreterFrame *frame,
234-
_Py_CODEUNIT *instr);
234+
_Py_CODEUNIT *instr,
235+
PyObject *value);
235236
static void monitor_unwind(PyThreadState *tstate,
236237
_PyInterpreterFrame *frame,
237238
_Py_CODEUNIT *instr);
@@ -2215,12 +2216,19 @@ monitor_reraise(PyThreadState *tstate, _PyInterpreterFrame *frame,
22152216

22162217
static int
22172218
monitor_stop_iteration(PyThreadState *tstate, _PyInterpreterFrame *frame,
2218-
_Py_CODEUNIT *instr)
2219+
_Py_CODEUNIT *instr, PyObject *value)
22192220
{
22202221
if (no_tools_for_local_event(tstate, frame, PY_MONITORING_EVENT_STOP_ITERATION)) {
22212222
return 0;
22222223
}
2223-
return do_monitor_exc(tstate, frame, instr, PY_MONITORING_EVENT_STOP_ITERATION);
2224+
assert(!PyErr_Occurred());
2225+
PyErr_SetObject(PyExc_StopIteration, value);
2226+
int res = do_monitor_exc(tstate, frame, instr, PY_MONITORING_EVENT_STOP_ITERATION);
2227+
if (res < 0) {
2228+
return res;
2229+
}
2230+
PyErr_SetRaisedException(NULL);
2231+
return 0;
22242232
}
22252233

22262234
static void

Diff for: Python/generated_cases.c.h

+2-6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: Python/instrumentation.c

+5-3
Original file line numberDiff line numberDiff line change
@@ -2622,7 +2622,7 @@ exception_event_teardown(int err, PyObject *exc) {
26222622
}
26232623
else {
26242624
assert(PyErr_Occurred());
2625-
Py_DECREF(exc);
2625+
Py_XDECREF(exc);
26262626
}
26272627
return err;
26282628
}
@@ -2712,15 +2712,17 @@ _PyMonitoring_FirePyUnwindEvent(PyMonitoringState *state, PyObject *codelike, in
27122712
}
27132713

27142714
int
2715-
_PyMonitoring_FireStopIterationEvent(PyMonitoringState *state, PyObject *codelike, int32_t offset)
2715+
_PyMonitoring_FireStopIterationEvent(PyMonitoringState *state, PyObject *codelike, int32_t offset, PyObject *value)
27162716
{
27172717
int event = PY_MONITORING_EVENT_STOP_ITERATION;
27182718
assert(state->active);
2719+
assert(!PyErr_Occurred());
2720+
PyErr_SetObject(PyExc_StopIteration, value);
27192721
PyObject *exc;
27202722
if (exception_event_setup(&exc, event) < 0) {
27212723
return -1;
27222724
}
27232725
PyObject *args[4] = { NULL, NULL, NULL, exc };
27242726
int err = capi_call_instrumentation(state, codelike, offset, args, 3, event);
2725-
return exception_event_teardown(err, exc);
2727+
return exception_event_teardown(err, NULL);
27262728
}

0 commit comments

Comments
 (0)