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]