From 63d3b167c925e6d449dce48615e43fb92ada4c1c Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Tue, 18 Jun 2024 12:29:30 +0100 Subject: [PATCH 1/5] Add (failing) test for issue 116090 --- Lib/test/test_monitoring.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_monitoring.py b/Lib/test/test_monitoring.py index a07be306986b43..4e06db4f2996c1 100644 --- a/Lib/test/test_monitoring.py +++ b/Lib/test/test_monitoring.py @@ -832,21 +832,26 @@ def func1(): self.check_events(func1, [("raise", KeyError)]) - # gh-116090: This test doesn't really require specialization, but running - # it without specialization exposes a monitoring bug. - @requires_specialization def test_implicit_stop_iteration(self): def gen(): yield 1 return 2 - def implicit_stop_iteration(): - for _ in gen(): + def implicit_stop_iteration(iterator=None): + if iterator is None: + iterator = gen() + for _ in iterator: pass self.check_events(implicit_stop_iteration, [("raise", StopIteration)], recorders=(StopiterationRecorder,)) + # Make specialization fail, so that we can test the unspecialized + # version of the loop. + implicit_stop_iteration(set(range(100))) + + self.check_events(implicit_stop_iteration, [("raise", StopIteration)], recorders=(StopiterationRecorder,)) + initial = [ ("raise", ZeroDivisionError), ("handled", ZeroDivisionError) From 992b27adb6ab29118ab49ff5480f54b83d57c259 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Tue, 18 Jun 2024 14:22:45 +0100 Subject: [PATCH 2/5] Fix test for issue 116090 --- Lib/test/test_monitoring.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_monitoring.py b/Lib/test/test_monitoring.py index 4e06db4f2996c1..b50cc3f3472074 100644 --- a/Lib/test/test_monitoring.py +++ b/Lib/test/test_monitoring.py @@ -833,6 +833,13 @@ def func1(): self.check_events(func1, [("raise", KeyError)]) def test_implicit_stop_iteration(self): + """Generators are documented as raising a StopIteration + when they terminate. + However, we don't do that if we can avoid it, for speed. + sys.monitoring handles that by injecting a STOP_ITERATION + event when we would otherwise have skip the RAISE event. + This test checks that both paths record an equivalent event. + """ def gen(): yield 1 @@ -844,13 +851,22 @@ def implicit_stop_iteration(iterator=None): for _ in iterator: pass - self.check_events(implicit_stop_iteration, [("raise", StopIteration)], recorders=(StopiterationRecorder,)) + recorders=(ExceptionRecorder, StopiterationRecorder,) + expected = [("raise", StopIteration)] # Make specialization fail, so that we can test the unspecialized - # version of the loop. + # version of the loop first. implicit_stop_iteration(set(range(100))) - self.check_events(implicit_stop_iteration, [("raise", StopIteration)], recorders=(StopiterationRecorder,)) + # This will record a RAISE event for the StopIteration. + self.check_events(implicit_stop_iteration, expected, recorders=recorders) + + # Now specialize, so that we see a STOP_ITERATION event. + for _ in range(100): + implicit_stop_iteration() + + # This will record a STOP_ITERATION event for the StopIteration. + self.check_events(implicit_stop_iteration, expected, recorders=recorders) initial = [ ("raise", ZeroDivisionError), From c736ead90b85e694bc0ceb89f572630d74c51112 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Tue, 18 Jun 2024 15:05:12 +0100 Subject: [PATCH 3/5] Document equivalence of STOP_ITERATION event and RAISE event with StopIteration --- Doc/library/sys.monitoring.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Doc/library/sys.monitoring.rst b/Doc/library/sys.monitoring.rst index 0fa06da522049f..2199293d742a78 100644 --- a/Doc/library/sys.monitoring.rst +++ b/Doc/library/sys.monitoring.rst @@ -226,6 +226,10 @@ 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`. +Note that the :monitoring-event:`STOP_ITERATION` event and the :monitoring-event:`RAISE` +event for a :exc:`StopIteration` exception are equivalent, and are treated as interchangeable +when generating events. Implementations will favor :monitoring-event:`STOP_ITERATION` for +performance reasons, but may generate :exc:`StopIteration` events. Turning events on and off ------------------------- From 5f4e8edf89f8f282a866d509ad21ab10c95a449e Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Tue, 18 Jun 2024 15:06:42 +0100 Subject: [PATCH 4/5] Fix typo --- Doc/library/sys.monitoring.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/sys.monitoring.rst b/Doc/library/sys.monitoring.rst index 2199293d742a78..56a05b5a1100fd 100644 --- a/Doc/library/sys.monitoring.rst +++ b/Doc/library/sys.monitoring.rst @@ -229,7 +229,7 @@ and coroutines, the :monitoring-event:`STOP_ITERATION` event is provided. Note that the :monitoring-event:`STOP_ITERATION` event and the :monitoring-event:`RAISE` event for a :exc:`StopIteration` exception are equivalent, and are treated as interchangeable when generating events. Implementations will favor :monitoring-event:`STOP_ITERATION` for -performance reasons, but may generate :exc:`StopIteration` events. +performance reasons, but may generate a RAISE event with a :exc:`StopIteration`. Turning events on and off ------------------------- From 810a4c65d651956e18571d10d2589acb552e993c Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Wed, 26 Jun 2024 11:20:14 +0100 Subject: [PATCH 5/5] Address review comments --- Doc/library/sys.monitoring.rst | 2 +- Lib/test/test_monitoring.py | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Doc/library/sys.monitoring.rst b/Doc/library/sys.monitoring.rst index 56a05b5a1100fd..3ead20815fa30e 100644 --- a/Doc/library/sys.monitoring.rst +++ b/Doc/library/sys.monitoring.rst @@ -229,7 +229,7 @@ and coroutines, the :monitoring-event:`STOP_ITERATION` event is provided. Note that the :monitoring-event:`STOP_ITERATION` event and the :monitoring-event:`RAISE` event for a :exc:`StopIteration` exception are equivalent, and are treated as interchangeable when generating events. Implementations will favor :monitoring-event:`STOP_ITERATION` for -performance reasons, but may generate a RAISE event with a :exc:`StopIteration`. +performance reasons, but may generate a :monitoring-event:`RAISE` event with a :exc:`StopIteration`. Turning events on and off ------------------------- diff --git a/Lib/test/test_monitoring.py b/Lib/test/test_monitoring.py index b50cc3f3472074..6d777c09553e1c 100644 --- a/Lib/test/test_monitoring.py +++ b/Lib/test/test_monitoring.py @@ -854,8 +854,10 @@ def implicit_stop_iteration(iterator=None): recorders=(ExceptionRecorder, StopiterationRecorder,) expected = [("raise", StopIteration)] - # Make specialization fail, so that we can test the unspecialized - # version of the loop first. + # Make sure that the loop is unspecialized, and that it will not + # re-specialize immediately, so that we can we can test the + # unspecialized version of the loop first. + # Note: this assumes that we don't specialize loops over sets. implicit_stop_iteration(set(range(100))) # This will record a RAISE event for the StopIteration.