bpo-33786: Fix asynchronous generators to handle GeneratorExit in ath… · python/cpython@52698c7

GitHub

File tree

Lib

test

Misc/NEWS.d/next/Core and Builtins

Objects

Original file line numberDiff line numberDiff line change@@ -187,7 +187,7 @@ async def __aexit__(self, typ, value, traceback):

187187# in this implementation

188188try:

189189awaitself.gen.athrow(typ, value, traceback)

190-raiseRuntimeError("generator didn't stop after throw()")

190+raiseRuntimeError("generator didn't stop after athrow()")

191191exceptStopAsyncIterationasexc:

192192returnexcisnotvalue

193193exceptRuntimeErrorasexc:

Original file line numberDiff line numberDiff line change@@ -108,6 +108,31 @@ def sync_iterate(g):

108108res.append(str(type(ex)))

109109returnres

110110111+defasync_iterate(g):

112+res= []

113+whileTrue:

114+an=g.__anext__()

115+try:

116+whileTrue:

117+try:

118+an.__next__()

119+exceptStopIterationasex:

120+ifex.args:

121+res.append(ex.args[0])

122+break

123+else:

124+res.append('EMPTY StopIteration')

125+break

126+exceptStopAsyncIteration:

127+raise

128+exceptExceptionasex:

129+res.append(str(type(ex)))

130+break

131+exceptStopAsyncIteration:

132+res.append('STOP')

133+break

134+returnres

135+111136defasync_iterate(g):

112137res= []

113138whileTrue:

@@ -297,6 +322,37 @@ async def gen():

297322"non-None value .* async generator"):

298323gen().__anext__().send(100)

299324325+deftest_async_gen_exception_11(self):

326+defsync_gen():

327+yield10

328+yield20

329+330+defsync_gen_wrapper():

331+yield1

332+sg=sync_gen()

333+sg.send(None)

334+try:

335+sg.throw(GeneratorExit())

336+exceptGeneratorExit:

337+yield2

338+yield3

339+340+asyncdefasync_gen():

341+yield10

342+yield20

343+344+asyncdefasync_gen_wrapper():

345+yield1

346+asg=async_gen()

347+awaitasg.asend(None)

348+try:

349+awaitasg.athrow(GeneratorExit())

350+exceptGeneratorExit:

351+yield2

352+yield3

353+354+self.compare_generators(sync_gen_wrapper(), async_gen_wrapper())

355+300356deftest_async_gen_api_01(self):

301357asyncdefgen():

302358yield123

Original file line numberDiff line numberDiff line change@@ -36,6 +36,28 @@ async def __aexit__(self, *args):

3636asyncwithmanagerascontext:

3737self.assertIs(manager, context)

383839+@_async_test

40+asyncdeftest_async_gen_propagates_generator_exit(self):

41+# A regression test for https://bugs.python.org/issue33786.

42+43+@asynccontextmanager

44+asyncdefctx():

45+yield

46+47+asyncdefgen():

48+asyncwithctx():

49+yield11

50+51+ret= []

52+exc=ValueError(22)

53+withself.assertRaises(ValueError):

54+asyncwithctx():

55+asyncforvalingen():

56+ret.append(val)

57+raiseexc

58+59+self.assertEqual(ret, [11])

60+3961deftest_exit_is_abstract(self):

4062classMissingAexit(AbstractAsyncContextManager):

4163pass

Original file line numberDiff line numberDiff line change@@ -0,0 +1 @@

1+Fix asynchronous generators to handle GeneratorExit in athrow() correctly

Original file line numberDiff line numberDiff line change@@ -1876,21 +1876,20 @@ async_gen_athrow_send(PyAsyncGenAThrow *o, PyObject *arg)

18761876returnNULL;

1877187718781878check_error:

1879-if (PyErr_ExceptionMatches(PyExc_StopAsyncIteration)) {

1879+if (PyErr_ExceptionMatches(PyExc_StopAsyncIteration) ||

1880+PyErr_ExceptionMatches(PyExc_GeneratorExit))

1881+ {

18801882o->agt_state=AWAITABLE_STATE_CLOSED;

18811883if (o->agt_args==NULL) {

18821884/* when aclose() is called we don't want to propagate

1883- StopAsyncIteration; just raise StopIteration, signalling

1884- that 'aclose()' is done. */

1885+ StopAsyncIteration or GeneratorExit; just raise

1886+ StopIteration, signalling that this 'aclose()' await

1887+ is done.

1888+ */

18851889PyErr_Clear();

18861890PyErr_SetNone(PyExc_StopIteration);

18871891 }

18881892 }

1889-elseif (PyErr_ExceptionMatches(PyExc_GeneratorExit)) {

1890-o->agt_state=AWAITABLE_STATE_CLOSED;

1891-PyErr_Clear(); /* ignore these errors */

1892-PyErr_SetNone(PyExc_StopIteration);

1893- }

18941893returnNULL;

18951894}

18961895