https://github.com/python/cpython/commit/43bb6300b3e1b477436592ecbbbebbe1534499cf
commit: 43bb6300b3e1b477436592ecbbbebbe1534499cf
branch: main
author: Sam Gross <[email protected]>
committer: colesbury <[email protected]>
date: 2026-01-20T17:51:55Z
summary:

gh-143939: Fix assignment to `_PyThreadStateImpl.generator_return_kind` 
(gh-143951)

The assignment to generator_return_kind has to be after any potentially
escaping calls to ensure that it's not overwritten.

files:
A 
Misc/NEWS.d/next/Core_and_Builtins/2026-01-16-23-19-38.gh-issue-143939.w9TWch.rst
M Lib/test/test_coroutines.py
M Objects/genobject.c
M Python/ceval.c

diff --git a/Lib/test/test_coroutines.py b/Lib/test/test_coroutines.py
index 6ad7e7994f32b0..93e9e7a8393cb1 100644
--- a/Lib/test/test_coroutines.py
+++ b/Lib/test/test_coroutines.py
@@ -2265,6 +2265,20 @@ def c():
         # 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
index 00000000000000..47423663e07864
--- /dev/null
+++ 
b/Misc/NEWS.d/next/Core_and_Builtins/2026-01-16-23-19-38.gh-issue-143939.w9TWch.rst
@@ -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.
diff --git a/Objects/genobject.c b/Objects/genobject.c
index 09407d60af62be..fcdb9017a35f5b 100644
--- a/Objects/genobject.c
+++ b/Objects/genobject.c
@@ -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;
     }
diff --git a/Python/ceval.c b/Python/ceval.c
index 87481ba6d0377f..bdf1e9bb742333 100644
--- a/Python/ceval.c
+++ b/Python/ceval.c
@@ -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

_______________________________________________
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