bpo-33786: Fix asynchronous generators to handle GeneratorExit in ath… · python/cpython@cf79cbf (original) (raw)

File tree

5 files changed

lines changed

5 files changed

lines changed

Original file line number Diff line number Diff line change
@@ -186,7 +186,7 @@ async def __aexit__(self, typ, value, traceback):
186 186 # in this implementation
187 187 try:
188 188 await self.gen.athrow(typ, value, traceback)
189 -raise RuntimeError("generator didn't stop after throw()")
189 +raise RuntimeError("generator didn't stop after athrow()")
190 190 except StopAsyncIteration as exc:
191 191 return exc is not value
192 192 except RuntimeError as exc:
Original file line number Diff line number Diff line change
@@ -108,6 +108,31 @@ def sync_iterate(g):
108 108 res.append(str(type(ex)))
109 109 return res
110 110
111 +def async_iterate(g):
112 +res = []
113 +while True:
114 +an = g.__anext__()
115 +try:
116 +while True:
117 +try:
118 +an.__next__()
119 +except StopIteration as ex:
120 +if ex.args:
121 +res.append(ex.args[0])
122 +break
123 +else:
124 +res.append('EMPTY StopIteration')
125 +break
126 +except StopAsyncIteration:
127 +raise
128 +except Exception as ex:
129 +res.append(str(type(ex)))
130 +break
131 +except StopAsyncIteration:
132 +res.append('STOP')
133 +break
134 +return res
135 +
111 136 def async_iterate(g):
112 137 res = []
113 138 while True:
@@ -297,6 +322,37 @@ async def gen():
297 322 "non-None value .* async generator"):
298 323 gen().__anext__().send(100)
299 324
325 +def test_async_gen_exception_11(self):
326 +def sync_gen():
327 +yield 10
328 +yield 20
329 +
330 +def sync_gen_wrapper():
331 +yield 1
332 +sg = sync_gen()
333 +sg.send(None)
334 +try:
335 +sg.throw(GeneratorExit())
336 +except GeneratorExit:
337 +yield 2
338 +yield 3
339 +
340 +async def async_gen():
341 +yield 10
342 +yield 20
343 +
344 +async def async_gen_wrapper():
345 +yield 1
346 +asg = async_gen()
347 +await asg.asend(None)
348 +try:
349 +await asg.athrow(GeneratorExit())
350 +except GeneratorExit:
351 +yield 2
352 +yield 3
353 +
354 +self.compare_generators(sync_gen_wrapper(), async_gen_wrapper())
355 +
300 356 def test_async_gen_api_01(self):
301 357 async def gen():
302 358 yield 123
Original file line number Diff line number Diff line change
@@ -36,6 +36,28 @@ async def __aexit__(self, *args):
36 36 async with manager as context:
37 37 self.assertIs(manager, context)
38 38
39 +@_async_test
40 +async def test_async_gen_propagates_generator_exit(self):
41 +# A regression test for https://bugs.python.org/issue33786.
42 +
43 +@asynccontextmanager
44 +async def ctx():
45 +yield
46 +
47 +async def gen():
48 +async with ctx():
49 +yield 11
50 +
51 +ret = []
52 +exc = ValueError(22)
53 +with self.assertRaises(ValueError):
54 +async with ctx():
55 +async for val in gen():
56 +ret.append(val)
57 +raise exc
58 +
59 +self.assertEqual(ret, [11])
60 +
39 61 def test_exit_is_abstract(self):
40 62 class MissingAexit(AbstractAsyncContextManager):
41 63 pass
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1 +Fix asynchronous generators to handle GeneratorExit in athrow() correctly
Original file line number Diff line number Diff line change
@@ -1893,21 +1893,20 @@ async_gen_athrow_send(PyAsyncGenAThrow *o, PyObject *arg)
1893 1893 return NULL;
1894 1894
1895 1895 check_error:
1896 -if (PyErr_ExceptionMatches(PyExc_StopAsyncIteration)) {
1896 +if (PyErr_ExceptionMatches(PyExc_StopAsyncIteration) |
1897 +PyErr_ExceptionMatches(PyExc_GeneratorExit))
1898 + {
1897 1899 o->agt_state = AWAITABLE_STATE_CLOSED;
1898 1900 if (o->agt_args == NULL) {
1899 1901 /* when aclose() is called we don't want to propagate
1900 - StopAsyncIteration; just raise StopIteration, signalling
1901 - that 'aclose()' is done. */
1902 + StopAsyncIteration or GeneratorExit; just raise
1903 + StopIteration, signalling that this 'aclose()' await
1904 + is done.
1905 + */
1902 1906 PyErr_Clear();
1903 1907 PyErr_SetNone(PyExc_StopIteration);
1904 1908 }
1905 1909 }
1906 -else if (PyErr_ExceptionMatches(PyExc_GeneratorExit)) {
1907 -o->agt_state = AWAITABLE_STATE_CLOSED;
1908 -PyErr_Clear(); /* ignore these errors */
1909 -PyErr_SetNone(PyExc_StopIteration);
1910 - }
1911 1910 return NULL;
1912 1911 }
1913 1912