summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorYury Selivanov <yury@magic.io>2018-06-08 00:31:26 (GMT)
committerGitHub <noreply@github.com>2018-06-08 00:31:26 (GMT)
commit52698c7ad9eae9feb35839fde17a7d1da8036a9b (patch)
tree1db0d329f430f5eda34575454d35b0b7e5570249
parent378c53cc3187dba57c7560ccc2557f516c8a7bc8 (diff)
downloadcpython-52698c7ad9eae9feb35839fde17a7d1da8036a9b.zip
cpython-52698c7ad9eae9feb35839fde17a7d1da8036a9b.tar.gz
cpython-52698c7ad9eae9feb35839fde17a7d1da8036a9b.tar.bz2
bpo-33786: Fix asynchronous generators to handle GeneratorExit in athrow() (GH-7467)
-rw-r--r--Lib/contextlib.py2
-rw-r--r--Lib/test/test_asyncgen.py56
-rw-r--r--Lib/test/test_contextlib_async.py22
-rw-r--r--Misc/NEWS.d/next/Core and Builtins/2018-06-06-23-24-40.bpo-33786.lBvT8z.rst1
-rw-r--r--Objects/genobject.c15
5 files changed, 87 insertions, 9 deletions
diff --git a/Lib/contextlib.py b/Lib/contextlib.py
index 1a58b50..c06ec73 100644
--- a/Lib/contextlib.py
+++ b/Lib/contextlib.py
@@ -187,7 +187,7 @@ class _AsyncGeneratorContextManager(_GeneratorContextManagerBase,
# in this implementation
try:
await self.gen.athrow(typ, value, traceback)
- raise RuntimeError("generator didn't stop after throw()")
+ raise RuntimeError("generator didn't stop after athrow()")
except StopAsyncIteration as exc:
return exc is not value
except RuntimeError as exc:
diff --git a/Lib/test/test_asyncgen.py b/Lib/test/test_asyncgen.py
index 8dc76ce..9d60cb0 100644
--- a/Lib/test/test_asyncgen.py
+++ b/Lib/test/test_asyncgen.py
@@ -111,6 +111,31 @@ class AsyncGenTest(unittest.TestCase):
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:
try:
g.__anext__().__next__()
except StopAsyncIteration:
@@ -297,6 +322,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/Lib/test/test_contextlib_async.py b/Lib/test/test_contextlib_async.py
index 355955f..39dcd9b 100644
--- a/Lib/test/test_contextlib_async.py
+++ b/Lib/test/test_contextlib_async.py
@@ -36,6 +36,28 @@ class TestAbstractAsyncContextManager(unittest.TestCase):
async with manager as context:
self.assertIs(manager, context)
+ @_async_test
+ async def test_async_gen_propagates_generator_exit(self):
+ # A regression test for https://bugs.python.org/issue33786.
+
+ @asynccontextmanager
+ async def ctx():
+ yield
+
+ async def gen():
+ async with ctx():
+ yield 11
+
+ ret = []
+ exc = ValueError(22)
+ with self.assertRaises(ValueError):
+ async with ctx():
+ async for val in gen():
+ ret.append(val)
+ raise exc
+
+ self.assertEqual(ret, [11])
+
def test_exit_is_abstract(self):
class MissingAexit(AbstractAsyncContextManager):
pass
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
index 0000000..57deefe
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2018-06-06-23-24-40.bpo-33786.lBvT8z.rst
@@ -0,0 +1 @@
+Fix asynchronous generators to handle GeneratorExit in athrow() correctly
diff --git a/Objects/genobject.c b/Objects/genobject.c
index 9f59338..e55cfd2 100644
--- a/Objects/genobject.c
+++ b/Objects/genobject.c
@@ -1876,21 +1876,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;
}