Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions Lib/test/test_coroutines.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Comment on lines +2271 to +2279
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A little bit smaller in size:

Suggested change
class CallGeneratorOnDealloc:
def __del__(self):
def gen():
yield 1
next(gen())
async def coro():
obj = CallGeneratorOnDealloc()
return 42
class CallGeneratorOnDealloc:
def __del__(self):
next(x for x in [42])
async def coro():
obj = CallGeneratorOnDealloc()


yielded, result = run_async(coro())
self.assertEqual(yielded, [])
self.assertEqual(result, 42)

@unittest.skipIf(
support.is_emscripten or support.is_wasi,
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
3 changes: 3 additions & 0 deletions Objects/genobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
4 changes: 3 additions & 1 deletion Python/ceval.c
Original file line number Diff line number Diff line change
Expand Up @@ -1914,14 +1914,16 @@ 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;
assert(frame->frame_obj == NULL || frame->frame_obj->f_frame == 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
Expand Down
Loading