bpo-33786: Fix asynchronous generators to handle GeneratorExit in ath… · python/cpython@8de73d5 (original) (raw)
File tree
3 files changed
lines changed
- Misc/NEWS.d/next/Core and Builtins
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 |