bpo-33786: Fix asynchronous generators to handle GeneratorExit in athrow() (GH-7467)
This commit is contained in:
parent
378c53cc31
commit
52698c7ad9
|
@ -187,7 +187,7 @@ class _AsyncGeneratorContextManager(_GeneratorContextManagerBase,
|
||||||
# in this implementation
|
# in this implementation
|
||||||
try:
|
try:
|
||||||
await self.gen.athrow(typ, value, traceback)
|
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:
|
except StopAsyncIteration as exc:
|
||||||
return exc is not value
|
return exc is not value
|
||||||
except RuntimeError as exc:
|
except RuntimeError as exc:
|
||||||
|
|
|
@ -108,6 +108,31 @@ class AsyncGenTest(unittest.TestCase):
|
||||||
res.append(str(type(ex)))
|
res.append(str(type(ex)))
|
||||||
return res
|
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):
|
def async_iterate(g):
|
||||||
res = []
|
res = []
|
||||||
while True:
|
while True:
|
||||||
|
@ -297,6 +322,37 @@ class AsyncGenTest(unittest.TestCase):
|
||||||
"non-None value .* async generator"):
|
"non-None value .* async generator"):
|
||||||
gen().__anext__().send(100)
|
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):
|
def test_async_gen_api_01(self):
|
||||||
async def gen():
|
async def gen():
|
||||||
yield 123
|
yield 123
|
||||||
|
|
|
@ -36,6 +36,28 @@ class TestAbstractAsyncContextManager(unittest.TestCase):
|
||||||
async with manager as context:
|
async with manager as context:
|
||||||
self.assertIs(manager, 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):
|
def test_exit_is_abstract(self):
|
||||||
class MissingAexit(AbstractAsyncContextManager):
|
class MissingAexit(AbstractAsyncContextManager):
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Fix asynchronous generators to handle GeneratorExit in athrow() correctly
|
|
@ -1876,21 +1876,20 @@ yield_close:
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
check_error:
|
check_error:
|
||||||
if (PyErr_ExceptionMatches(PyExc_StopAsyncIteration)) {
|
if (PyErr_ExceptionMatches(PyExc_StopAsyncIteration) ||
|
||||||
|
PyErr_ExceptionMatches(PyExc_GeneratorExit))
|
||||||
|
{
|
||||||
o->agt_state = AWAITABLE_STATE_CLOSED;
|
o->agt_state = AWAITABLE_STATE_CLOSED;
|
||||||
if (o->agt_args == NULL) {
|
if (o->agt_args == NULL) {
|
||||||
/* when aclose() is called we don't want to propagate
|
/* when aclose() is called we don't want to propagate
|
||||||
StopAsyncIteration; just raise StopIteration, signalling
|
StopAsyncIteration or GeneratorExit; just raise
|
||||||
that 'aclose()' is done. */
|
StopIteration, signalling that this 'aclose()' await
|
||||||
|
is done.
|
||||||
|
*/
|
||||||
PyErr_Clear();
|
PyErr_Clear();
|
||||||
PyErr_SetNone(PyExc_StopIteration);
|
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;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue