# 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,
--- /dev/null
+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.
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;
}
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;
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