Skip to content

Commit 43bb630

Browse files
authored
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.
1 parent a126893 commit 43bb630

File tree

4 files changed

+23
-1
lines changed

4 files changed

+23
-1
lines changed

Lib/test/test_coroutines.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2265,6 +2265,20 @@ def c():
22652265
# before fixing, visible stack from throw would be shorter than from send.
22662266
self.assertEqual(len_send, len_throw)
22672267

2268+
def test_call_generator_in_frame_clear(self):
2269+
# gh-143939: Running a generator while clearing the coroutine's frame
2270+
# should not be misinterpreted as a yield.
2271+
class CallGeneratorOnDealloc:
2272+
def __del__(self):
2273+
next(x for x in [1])
2274+
2275+
async def coro():
2276+
obj = CallGeneratorOnDealloc()
2277+
return 42
2278+
2279+
yielded, result = run_async(coro())
2280+
self.assertEqual(yielded, [])
2281+
self.assertEqual(result, 42)
22682282

22692283
@unittest.skipIf(
22702284
support.is_emscripten or support.is_wasi,
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix erroneous "cannot reuse already awaited coroutine" error that could
2+
occur when a generator was run during the process of clearing a coroutine's
3+
frame.

Objects/genobject.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,9 @@ gen_send_ex2(PyGenObject *gen, PyObject *arg, PyObject **presult, int exc)
280280

281281
if (return_kind == GENERATOR_YIELD) {
282282
assert(result != NULL && !_PyErr_Occurred(tstate));
283+
#ifndef Py_GIL_DISABLED
284+
assert(FRAME_STATE_SUSPENDED(gen->gi_frame_state));
285+
#endif
283286
*presult = result;
284287
return PYGEN_NEXT;
285288
}

Python/ceval.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1914,14 +1914,16 @@ clear_gen_frame(PyThreadState *tstate, _PyInterpreterFrame * frame)
19141914
assert(frame->owner == FRAME_OWNED_BY_GENERATOR);
19151915
PyGenObject *gen = _PyGen_GetGeneratorFromFrame(frame);
19161916
FT_ATOMIC_STORE_INT8_RELEASE(gen->gi_frame_state, FRAME_CLEARED);
1917-
((_PyThreadStateImpl *)tstate)->generator_return_kind = GENERATOR_RETURN;
19181917
assert(tstate->exc_info == &gen->gi_exc_state);
19191918
tstate->exc_info = gen->gi_exc_state.previous_item;
19201919
gen->gi_exc_state.previous_item = NULL;
19211920
assert(frame->frame_obj == NULL || frame->frame_obj->f_frame == frame);
19221921
frame->previous = NULL;
19231922
_PyFrame_ClearExceptCode(frame);
19241923
_PyErr_ClearExcState(&gen->gi_exc_state);
1924+
// gh-143939: There must not be any escaping calls between setting
1925+
// the generator return kind and returning from _PyEval_EvalFrame.
1926+
((_PyThreadStateImpl *)tstate)->generator_return_kind = GENERATOR_RETURN;
19251927
}
19261928

19271929
void

0 commit comments

Comments
 (0)