https://github.com/python/cpython/commit/52731c4e62f6dcc15dffc0d98796c43763b7bcc2
commit: 52731c4e62f6dcc15dffc0d98796c43763b7bcc2
branch: 3.14
author: Miss Islington (bot) <[email protected]>
committer: hugovk <[email protected]>
date: 2025-11-30T14:35:14+02:00
summary:

[3.14] gh-140373: Correctly emit `PY_UNWIND` event when generator is closed 
(GH-140767) (#140816)

Co-authored-by: Mikhail Efimov <[email protected]>

files:
A 
Misc/NEWS.d/next/Core_and_Builtins/2025-10-29-20-59-10.gh-issue-140373.-uoaPP.rst
M Include/internal/pycore_ceval.h
M Lib/test/test_monitoring.py
M Lib/test/test_sys_setprofile.py
M Objects/genobject.c
M Python/ceval.c

diff --git a/Include/internal/pycore_ceval.h b/Include/internal/pycore_ceval.h
index cf5ad528217eab..89504f6bfbbfb7 100644
--- a/Include/internal/pycore_ceval.h
+++ b/Include/internal/pycore_ceval.h
@@ -296,6 +296,7 @@ PyAPI_FUNC(PyObject *) _PyEval_ImportName(PyThreadState *, 
_PyInterpreterFrame *
 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(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/Lib/test/test_monitoring.py b/Lib/test/test_monitoring.py
index 98276a8ccaa610..de658c597a197d 100644
--- a/Lib/test/test_monitoring.py
+++ b/Lib/test/test_monitoring.py
@@ -1079,6 +1079,25 @@ def f():
 
         self.assertEqual(events, expected)
 
+    # gh-140373
+    def test_gen_unwind(self):
+        def gen():
+            yield 1
+
+        def f():
+            g = gen()
+            next(g)
+            g.close()
+
+        recorders = (
+            UnwindRecorder,
+        )
+        events = self.get_events(f, TEST_TOOL, recorders)
+        expected = [
+            ("unwind", GeneratorExit, "gen"),
+        ]
+        self.assertEqual(events, expected)
+
 class LineRecorder:
 
     event_type = E.LINE
diff --git a/Lib/test/test_sys_setprofile.py b/Lib/test/test_sys_setprofile.py
index 345c022bd2374c..a22b728b569419 100644
--- a/Lib/test/test_sys_setprofile.py
+++ b/Lib/test/test_sys_setprofile.py
@@ -272,6 +272,8 @@ def g(p):
         self.check_events(g, [(1, 'call', g_ident, None),
                               (2, 'call', f_ident, None),
                               (2, 'return', f_ident, 0),
+                              (2, 'call', f_ident, None),
+                              (2, 'return', f_ident, None),
                               (1, 'return', g_ident, None),
                               ], check_args=True)
 
diff --git 
a/Misc/NEWS.d/next/Core_and_Builtins/2025-10-29-20-59-10.gh-issue-140373.-uoaPP.rst
 
b/Misc/NEWS.d/next/Core_and_Builtins/2025-10-29-20-59-10.gh-issue-140373.-uoaPP.rst
new file mode 100644
index 00000000000000..c9a97037920fda
--- /dev/null
+++ 
b/Misc/NEWS.d/next/Core_and_Builtins/2025-10-29-20-59-10.gh-issue-140373.-uoaPP.rst
@@ -0,0 +1,2 @@
+Correctly emit ``PY_UNWIND`` event when generator object is closed. Patch by
+Mikhail Efimov.
diff --git a/Objects/genobject.c b/Objects/genobject.c
index f429bc47678fed..575752fc84002a 100644
--- a/Objects/genobject.c
+++ b/Objects/genobject.c
@@ -407,11 +407,12 @@ gen_close(PyObject *self, PyObject *args)
     }
     _PyInterpreterFrame *frame = &gen->gi_iframe;
     if (is_resume(frame->instr_ptr)) {
+        bool no_unwind_tools = _PyEval_NoToolsForUnwind(_PyThreadState_GET());
         /* We can safely ignore the outermost try block
          * as it is automatically generated to handle
          * StopIteration. */
         int oparg = frame->instr_ptr->op.arg;
-        if (oparg & RESUME_OPARG_DEPTH1_MASK) {
+        if (oparg & RESUME_OPARG_DEPTH1_MASK && no_unwind_tools) {
             // RESUME after YIELD_VALUE and exception depth is 1
             assert((oparg & RESUME_OPARG_LOCATION_MASK) != 
RESUME_AT_FUNC_START);
             gen->gi_frame_state = FRAME_COMPLETED;
diff --git a/Python/ceval.c b/Python/ceval.c
index 4b1e7abe66dfad..faca6fe81b977a 100644
--- a/Python/ceval.c
+++ b/Python/ceval.c
@@ -2545,6 +2545,10 @@ monitor_unwind(PyThreadState *tstate,
     do_monitor_exc(tstate, frame, instr, PY_MONITORING_EVENT_PY_UNWIND);
 }
 
+bool
+_PyEval_NoToolsForUnwind(PyThreadState *tstate) {
+    return no_tools_for_global_event(tstate, PY_MONITORING_EVENT_PY_UNWIND);
+}
 
 static int
 monitor_handled(PyThreadState *tstate,

_______________________________________________
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]

Reply via email to