]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-143939: Fix assignment to `_PyThreadStateImpl.generator_return_kind` (gh-143951)
authorSam Gross <colesbury@gmail.com>
Tue, 20 Jan 2026 17:51:55 +0000 (12:51 -0500)
committerGitHub <noreply@github.com>
Tue, 20 Jan 2026 17:51:55 +0000 (17:51 +0000)
The assignment to generator_return_kind has to be after any potentially
escaping calls to ensure that it's not overwritten.

Lib/test/test_coroutines.py
Misc/NEWS.d/next/Core_and_Builtins/2026-01-16-23-19-38.gh-issue-143939.w9TWch.rst [new file with mode: 0644]
Objects/genobject.c
Python/ceval.c

index 6ad7e7994f32b020f6647b4fdf7fc4f0149736eb..93e9e7a8393cb1068b31f7b64ad92a3b37c3638c 100644 (file)
@@ -2265,6 +2265,20 @@ class CoroutineTest(unittest.TestCase):
         # before fixing, visible stack from throw would be shorter than from send.
         self.assertEqual(len_send, len_throw)
 
+    def test_call_generator_in_frame_clear(self):
+        # gh-143939: Running a generator while clearing the coroutine's frame
+        # should not be misinterpreted as a yield.
+        class CallGeneratorOnDealloc:
+            def __del__(self):
+                next(x for x in [1])
+
+        async def coro():
+            obj = CallGeneratorOnDealloc()
+            return 42
+
+        yielded, result = run_async(coro())
+        self.assertEqual(yielded, [])
+        self.assertEqual(result, 42)
 
 @unittest.skipIf(
     support.is_emscripten or support.is_wasi,
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-01-16-23-19-38.gh-issue-143939.w9TWch.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-01-16-23-19-38.gh-issue-143939.w9TWch.rst
new file mode 100644 (file)
index 0000000..4742366
--- /dev/null
@@ -0,0 +1,3 @@
+Fix erroneous "cannot reuse already awaited coroutine" error that could
+occur when a generator was run during the process of clearing a coroutine's
+frame.
index 09407d60af62bebb6f602d005dbeae8c1eb00433..fcdb9017a35f5b2be44b87c2762bb345c2f2fcd2 100644 (file)
@@ -280,6 +280,9 @@ gen_send_ex2(PyGenObject *gen, PyObject *arg, PyObject **presult, int exc)
 
     if (return_kind == GENERATOR_YIELD) {
         assert(result != NULL && !_PyErr_Occurred(tstate));
+#ifndef Py_GIL_DISABLED
+        assert(FRAME_STATE_SUSPENDED(gen->gi_frame_state));
+#endif
         *presult = result;
         return PYGEN_NEXT;
     }
index 87481ba6d0377f67799df4f14b46de2548184653..bdf1e9bb742333f2d19c42c9b63cd86e615963a5 100644 (file)
@@ -1914,7 +1914,6 @@ clear_gen_frame(PyThreadState *tstate, _PyInterpreterFrame * frame)
     assert(frame->owner == FRAME_OWNED_BY_GENERATOR);
     PyGenObject *gen = _PyGen_GetGeneratorFromFrame(frame);
     FT_ATOMIC_STORE_INT8_RELEASE(gen->gi_frame_state, FRAME_CLEARED);
-    ((_PyThreadStateImpl *)tstate)->generator_return_kind = GENERATOR_RETURN;
     assert(tstate->exc_info == &gen->gi_exc_state);
     tstate->exc_info = gen->gi_exc_state.previous_item;
     gen->gi_exc_state.previous_item = NULL;
@@ -1922,6 +1921,9 @@ clear_gen_frame(PyThreadState *tstate, _PyInterpreterFrame * frame)
     frame->previous = NULL;
     _PyFrame_ClearExceptCode(frame);
     _PyErr_ClearExcState(&gen->gi_exc_state);
+    // gh-143939: There must not be any escaping calls between setting
+    // the generator return kind and returning from _PyEval_EvalFrame.
+    ((_PyThreadStateImpl *)tstate)->generator_return_kind = GENERATOR_RETURN;
 }
 
 void