Skip to content

Commit f464edf

Browse files
authored
bpo-39606: allow closing async generators that are already closed (GH-18475) (GH-18502)
The fix for [bpo-39386](https://bugs.python.org/issue39386) attempted to make it so you couldn't reuse a agen.aclose() coroutine object. It accidentally also prevented you from calling aclose() at all on an async generator that was already closed or exhausted. This commit fixes it so we're only blocking the actually illegal cases, while allowing the legal cases. The new tests failed before this patch. Also confirmed that this fixes the test failures we were seeing in Trio with Python dev builds: python-trio/trio#1396 https://bugs.python.org/issue39606 (cherry picked from commit 925dc7f)
1 parent ca133e5 commit f464edf

File tree

3 files changed

+39
-4
lines changed

3 files changed

+39
-4
lines changed

Lib/test/test_asyncgen.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1117,7 +1117,7 @@ async def main():
11171117

11181118
self.assertEqual([], messages)
11191119

1120-
def test_async_gen_await_anext_twice(self):
1120+
def test_async_gen_await_same_anext_coro_twice(self):
11211121
async def async_iterate():
11221122
yield 1
11231123
yield 2
@@ -1136,7 +1136,7 @@ async def run():
11361136

11371137
self.loop.run_until_complete(run())
11381138

1139-
def test_async_gen_await_aclose_twice(self):
1139+
def test_async_gen_await_same_aclose_coro_twice(self):
11401140
async def async_iterate():
11411141
yield 1
11421142
yield 2
@@ -1153,6 +1153,32 @@ async def run():
11531153

11541154
self.loop.run_until_complete(run())
11551155

1156+
def test_async_gen_aclose_twice_with_different_coros(self):
1157+
# Regression test for https://bugs.python.org/issue39606
1158+
async def async_iterate():
1159+
yield 1
1160+
yield 2
1161+
1162+
async def run():
1163+
it = async_iterate()
1164+
await it.aclose()
1165+
await it.aclose()
1166+
1167+
self.loop.run_until_complete(run())
1168+
1169+
def test_async_gen_aclose_after_exhaustion(self):
1170+
# Regression test for https://bugs.python.org/issue39606
1171+
async def async_iterate():
1172+
yield 1
1173+
yield 2
1174+
1175+
async def run():
1176+
it = async_iterate()
1177+
async for _ in it:
1178+
pass
1179+
await it.aclose()
1180+
1181+
self.loop.run_until_complete(run())
11561182

11571183
if __name__ == "__main__":
11581184
unittest.main()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix regression caused by fix for bpo-39386, that prevented calling
2+
``aclose`` on an async generator that had already been closed or exhausted.

Objects/genobject.c

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1804,14 +1804,19 @@ async_gen_athrow_send(PyAsyncGenAThrow *o, PyObject *arg)
18041804
PyFrameObject *f = gen->gi_frame;
18051805
PyObject *retval;
18061806

1807-
if (f == NULL || f->f_stacktop == NULL ||
1808-
o->agt_state == AWAITABLE_STATE_CLOSED) {
1807+
if (o->agt_state == AWAITABLE_STATE_CLOSED) {
18091808
PyErr_SetString(
18101809
PyExc_RuntimeError,
18111810
"cannot reuse already awaited aclose()/athrow()");
18121811
return NULL;
18131812
}
18141813

1814+
if (f == NULL || f->f_stacktop == NULL) {
1815+
o->agt_state = AWAITABLE_STATE_CLOSED;
1816+
PyErr_SetNone(PyExc_StopIteration);
1817+
return NULL;
1818+
}
1819+
18151820
if (o->agt_state == AWAITABLE_STATE_INIT) {
18161821
if (o->agt_gen->ag_closed) {
18171822
PyErr_SetNone(PyExc_StopIteration);
@@ -1882,6 +1887,7 @@ async_gen_athrow_send(PyAsyncGenAThrow *o, PyObject *arg)
18821887
}
18831888

18841889
yield_close:
1890+
o->agt_state = AWAITABLE_STATE_CLOSED;
18851891
PyErr_SetString(
18861892
PyExc_RuntimeError, ASYNC_GEN_IGNORED_EXIT_MSG);
18871893
return NULL;
@@ -1924,6 +1930,7 @@ async_gen_athrow_throw(PyAsyncGenAThrow *o, PyObject *args)
19241930
} else {
19251931
/* aclose() mode */
19261932
if (retval && _PyAsyncGenWrappedValue_CheckExact(retval)) {
1933+
o->agt_state = AWAITABLE_STATE_CLOSED;
19271934
Py_DECREF(retval);
19281935
PyErr_SetString(PyExc_RuntimeError, ASYNC_GEN_IGNORED_EXIT_MSG);
19291936
return NULL;

0 commit comments

Comments
 (0)