diff --git a/Lib/test/test_coroutines.py b/Lib/test/test_coroutines.py index 6ad7e7994f32b0..a16544e7c436f9 100644 --- a/Lib/test/test_coroutines.py +++ b/Lib/test/test_coroutines.py @@ -2265,6 +2265,22 @@ 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): + def gen(): + yield 1 + next(gen()) + + 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