]> granicus.if.org Git - python/commitdiff
bpo-33786: Fix asynchronous generators to handle GeneratorExit in athrow() (GH-7467...
authorYury Selivanov <yury@magic.io>
Fri, 8 Jun 2018 01:32:43 +0000 (21:32 -0400)
committerGitHub <noreply@github.com>
Fri, 8 Jun 2018 01:32:43 +0000 (21:32 -0400)
Lib/test/test_asyncgen.py
Misc/NEWS.d/next/Core and Builtins/2018-06-06-23-24-40.bpo-33786.lBvT8z.rst [new file with mode: 0644]
Objects/genobject.c

index 34ab8a04eee2dc34d03ca0298ffc2d5a51008b51..45cc2754a98a2a84e643344dfa3983c8f78b46cc 100644 (file)
@@ -111,6 +111,31 @@ class AsyncGenTest(unittest.TestCase):
                     res.append(str(type(ex)))
             return res
 
+        def async_iterate(g):
+            res = []
+            while True:
+                an = g.__anext__()
+                try:
+                    while True:
+                        try:
+                            an.__next__()
+                        except StopIteration as ex:
+                            if ex.args:
+                                res.append(ex.args[0])
+                                break
+                            else:
+                                res.append('EMPTY StopIteration')
+                                break
+                        except StopAsyncIteration:
+                            raise
+                        except Exception as ex:
+                            res.append(str(type(ex)))
+                            break
+                except StopAsyncIteration:
+                    res.append('STOP')
+                    break
+            return res
+
         def async_iterate(g):
             res = []
             while True:
@@ -300,6 +325,37 @@ class AsyncGenTest(unittest.TestCase):
                                     "non-None value .* async generator"):
             gen().__anext__().send(100)
 
+    def test_async_gen_exception_11(self):
+        def sync_gen():
+            yield 10
+            yield 20
+
+        def sync_gen_wrapper():
+            yield 1
+            sg = sync_gen()
+            sg.send(None)
+            try:
+                sg.throw(GeneratorExit())
+            except GeneratorExit:
+                yield 2
+            yield 3
+
+        async def async_gen():
+            yield 10
+            yield 20
+
+        async def async_gen_wrapper():
+            yield 1
+            asg = async_gen()
+            await asg.asend(None)
+            try:
+                await asg.athrow(GeneratorExit())
+            except GeneratorExit:
+                yield 2
+            yield 3
+
+        self.compare_generators(sync_gen_wrapper(), async_gen_wrapper())
+
     def test_async_gen_api_01(self):
         async def gen():
             yield 123
diff --git a/Misc/NEWS.d/next/Core and Builtins/2018-06-06-23-24-40.bpo-33786.lBvT8z.rst b/Misc/NEWS.d/next/Core and Builtins/2018-06-06-23-24-40.bpo-33786.lBvT8z.rst
new file mode 100644 (file)
index 0000000..57deefe
--- /dev/null
@@ -0,0 +1 @@
+Fix asynchronous generators to handle GeneratorExit in athrow() correctly
index 1c29e296afe2a8df848ad6c5787be9ec98d65360..f226dbebaf4abf8947d3b5cda91f66c68663ee34 100644 (file)
@@ -1939,21 +1939,20 @@ yield_close:
     return NULL;
 
 check_error:
-    if (PyErr_ExceptionMatches(PyExc_StopAsyncIteration)) {
+    if (PyErr_ExceptionMatches(PyExc_StopAsyncIteration) ||
+            PyErr_ExceptionMatches(PyExc_GeneratorExit))
+    {
         o->agt_state = AWAITABLE_STATE_CLOSED;
         if (o->agt_args == NULL) {
             /* when aclose() is called we don't want to propagate
-               StopAsyncIteration; just raise StopIteration, signalling
-               that 'aclose()' is done. */
+               StopAsyncIteration or GeneratorExit; just raise
+               StopIteration, signalling that this 'aclose()' await
+               is done.
+            */
             PyErr_Clear();
             PyErr_SetNone(PyExc_StopIteration);
         }
     }
-    else if (PyErr_ExceptionMatches(PyExc_GeneratorExit)) {
-        o->agt_state = AWAITABLE_STATE_CLOSED;
-        PyErr_Clear();          /* ignore these errors */
-        PyErr_SetNone(PyExc_StopIteration);
-    }
     return NULL;
 }