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

File tree

3 files changed

lines changed

3 files changed

lines changed

Original file line number Diff line number Diff line change
@@ -111,6 +111,31 @@ def sync_iterate(g):
111 111 res.append(str(type(ex)))
112 112 return res
113 113
114 +def async_iterate(g):
115 +res = []
116 +while True:
117 +an = g.__anext__()
118 +try:
119 +while True:
120 +try:
121 +an.__next__()
122 +except StopIteration as ex:
123 +if ex.args:
124 +res.append(ex.args[0])
125 +break
126 +else:
127 +res.append('EMPTY StopIteration')
128 +break
129 +except StopAsyncIteration:
130 +raise
131 +except Exception as ex:
132 +res.append(str(type(ex)))
133 +break
134 +except StopAsyncIteration:
135 +res.append('STOP')
136 +break
137 +return res
138 +
114 139 def async_iterate(g):
115 140 res = []
116 141 while True:
@@ -300,6 +325,37 @@ async def gen():
300 325 "non-None value .* async generator"):
301 326 gen().__anext__().send(100)
302 327
328 +def test_async_gen_exception_11(self):
329 +def sync_gen():
330 +yield 10
331 +yield 20
332 +
333 +def sync_gen_wrapper():
334 +yield 1
335 +sg = sync_gen()
336 +sg.send(None)
337 +try:
338 +sg.throw(GeneratorExit())
339 +except GeneratorExit:
340 +yield 2
341 +yield 3
342 +
343 +async def async_gen():
344 +yield 10
345 +yield 20
346 +
347 +async def async_gen_wrapper():
348 +yield 1
349 +asg = async_gen()
350 +await asg.asend(None)
351 +try:
352 +await asg.athrow(GeneratorExit())
353 +except GeneratorExit:
354 +yield 2
355 +yield 3
356 +
357 +self.compare_generators(sync_gen_wrapper(), async_gen_wrapper())
358 +
303 359 def test_async_gen_api_01(self):
304 360 async def gen():
305 361 yield 123
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
@@ -1939,21 +1939,20 @@ async_gen_athrow_send(PyAsyncGenAThrow *o, PyObject *arg)
1939 1939 return NULL;
1940 1940
1941 1941 check_error:
1942 -if (PyErr_ExceptionMatches(PyExc_StopAsyncIteration)) {
1942 +if (PyErr_ExceptionMatches(PyExc_StopAsyncIteration) |
1943 +PyErr_ExceptionMatches(PyExc_GeneratorExit))
1944 + {
1943 1945 o->agt_state = AWAITABLE_STATE_CLOSED;
1944 1946 if (o->agt_args == NULL) {
1945 1947 /* when aclose() is called we don't want to propagate
1946 - StopAsyncIteration; just raise StopIteration, signalling
1947 - that 'aclose()' is done. */
1948 + StopAsyncIteration or GeneratorExit; just raise
1949 + StopIteration, signalling that this 'aclose()' await
1950 + is done.
1951 + */
1948 1952 PyErr_Clear();
1949 1953 PyErr_SetNone(PyExc_StopIteration);
1950 1954 }
1951 1955 }
1952 -else if (PyErr_ExceptionMatches(PyExc_GeneratorExit)) {
1953 -o->agt_state = AWAITABLE_STATE_CLOSED;
1954 -PyErr_Clear(); /* ignore these errors */
1955 -PyErr_SetNone(PyExc_StopIteration);
1956 - }
1957 1956 return NULL;
1958 1957 }
1959 1958