bpo-33786: Fix asynchronous generators to handle GeneratorExit in athrow() (GH-7467) (GH-21878)
(cherry picked from commit 52698c7ad9
)
Co-authored-by: Yury Selivanov <yury@magic.io>
This commit is contained in:
parent
f3b6f3cd9a
commit
cf79cbf447
|
@ -186,7 +186,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:
|
||||
|
|
|
@ -108,6 +108,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:
|
||||
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Fix asynchronous generators to handle GeneratorExit in athrow() correctly
|
|
@ -1893,21 +1893,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;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue