Skip to content

Commit 3131a70

Browse files
committed
Merge branch 'fix-athrow-throw-asend-throw-concurrent' of github.com:graingert/cpython into gen-close-as-throw
2 parents ddf4c00 + 144ddbc commit 3131a70

File tree

3 files changed

+166
-0
lines changed

3 files changed

+166
-0
lines changed

Lib/test/test_asyncgen.py

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,131 @@ async def gen():
393393
r'anext\(\): asynchronous generator is already running'):
394394
an.__next__()
395395

396+
def test_async_gen_asend_throw_concurrent_with_send(self):
397+
import types
398+
399+
@types.coroutine
400+
def _async_yield(v):
401+
return (yield v)
402+
403+
class MyExc(Exception):
404+
pass
405+
406+
async def agenfn():
407+
while True:
408+
try:
409+
await _async_yield(None)
410+
except MyExc:
411+
pass
412+
return
413+
yield
414+
415+
416+
agen = agenfn()
417+
gen = agen.asend(None)
418+
gen.send(None)
419+
gen2 = agen.asend(None)
420+
421+
with self.assertRaisesRegex(RuntimeError,
422+
r'anext\(\): asynchronous generator is already running'):
423+
gen2.throw(MyExc)
424+
425+
def test_async_gen_athrow_throw_concurrent_with_send(self):
426+
import types
427+
428+
@types.coroutine
429+
def _async_yield(v):
430+
return (yield v)
431+
432+
class MyExc(Exception):
433+
pass
434+
435+
async def agenfn():
436+
while True:
437+
try:
438+
await _async_yield(None)
439+
except MyExc:
440+
pass
441+
return
442+
yield
443+
444+
445+
agen = agenfn()
446+
gen = agen.asend(None)
447+
gen.send(None)
448+
gen2 = agen.athrow(MyExc)
449+
450+
with self.assertRaisesRegex(RuntimeError,
451+
r'athrow\(\): asynchronous generator is already running'):
452+
gen2.throw(MyExc)
453+
454+
def test_async_gen_asend_throw_concurrent_with_throw(self):
455+
import types
456+
457+
@types.coroutine
458+
def _async_yield(v):
459+
return (yield v)
460+
461+
class MyExc(Exception):
462+
pass
463+
464+
async def agenfn():
465+
try:
466+
yield
467+
except MyExc:
468+
pass
469+
while True:
470+
try:
471+
await _async_yield(None)
472+
except MyExc:
473+
pass
474+
475+
476+
agen = agenfn()
477+
with self.assertRaises(StopIteration):
478+
agen.asend(None).send(None)
479+
480+
gen = agen.athrow(MyExc)
481+
gen.throw(MyExc)
482+
gen2 = agen.asend(MyExc)
483+
484+
with self.assertRaisesRegex(RuntimeError,
485+
r'anext\(\): asynchronous generator is already running'):
486+
gen2.throw(MyExc)
487+
488+
def test_async_gen_athrow_throw_concurrent_with_throw(self):
489+
import types
490+
491+
@types.coroutine
492+
def _async_yield(v):
493+
return (yield v)
494+
495+
class MyExc(Exception):
496+
pass
497+
498+
async def agenfn():
499+
try:
500+
yield
501+
except MyExc:
502+
pass
503+
while True:
504+
try:
505+
await _async_yield(None)
506+
except MyExc:
507+
pass
508+
509+
agen = agenfn()
510+
with self.assertRaises(StopIteration):
511+
agen.asend(None).send(None)
512+
513+
gen = agen.athrow(MyExc)
514+
gen.throw(MyExc)
515+
gen2 = agen.athrow(None)
516+
517+
with self.assertRaisesRegex(RuntimeError,
518+
r'athrow\(\): asynchronous generator is already running'):
519+
gen2.throw(MyExc)
520+
396521
def test_async_gen_3_arg_deprecation_warning(self):
397522
async def gen():
398523
yield 123
@@ -1569,6 +1694,8 @@ async def main():
15691694
self.assertIsInstance(message['exception'], ZeroDivisionError)
15701695
self.assertIn('unhandled exception during asyncio.run() shutdown',
15711696
message['message'])
1697+
del message, messages
1698+
gc_collect()
15721699

15731700
def test_async_gen_expression_01(self):
15741701
async def arange(n):
@@ -1622,6 +1749,7 @@ async def main():
16221749
asyncio.run(main())
16231750

16241751
self.assertEqual([], messages)
1752+
gc_collect()
16251753

16261754
def test_async_gen_await_same_anext_coro_twice(self):
16271755
async def async_iterate():
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
prevent concurrent access to an async generator via athrow().throw() or asend().throw()

Objects/genobject.c

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1771,6 +1771,7 @@ async_gen_asend_send(PyAsyncGenASend *o, PyObject *arg)
17711771

17721772
if (o->ags_state == AWAITABLE_STATE_INIT) {
17731773
if (o->ags_gen->ag_running_async) {
1774+
o->ags_state = AWAITABLE_STATE_CLOSED;
17741775
PyErr_SetString(
17751776
PyExc_RuntimeError,
17761777
"anext(): asynchronous generator is already running");
@@ -1814,10 +1815,24 @@ async_gen_asend_throw(PyAsyncGenASend *o, PyObject *const *args, Py_ssize_t narg
18141815
return NULL;
18151816
}
18161817

1818+
if (o->ags_state == AWAITABLE_STATE_INIT) {
1819+
if (o->ags_gen->ag_running_async) {
1820+
o->ags_state = AWAITABLE_STATE_CLOSED;
1821+
PyErr_SetString(
1822+
PyExc_RuntimeError,
1823+
"anext(): asynchronous generator is already running");
1824+
return NULL;
1825+
}
1826+
1827+
o->ags_state = AWAITABLE_STATE_ITER;
1828+
o->ags_gen->ag_running_async = 1;
1829+
}
1830+
18171831
result = gen_throw((PyGenObject*)o->ags_gen, args, nargs);
18181832
result = async_gen_unwrap_value(o->ags_gen, result);
18191833

18201834
if (result == NULL) {
1835+
o->ags_gen->ag_running_async = 0;
18211836
o->ags_state = AWAITABLE_STATE_CLOSED;
18221837
}
18231838

@@ -2206,11 +2221,32 @@ async_gen_athrow_throw(PyAsyncGenAThrow *o, PyObject *const *args, Py_ssize_t na
22062221
return NULL;
22072222
}
22082223

2224+
if (o->agt_state == AWAITABLE_STATE_INIT) {
2225+
if (o->agt_gen->ag_running_async) {
2226+
o->agt_state = AWAITABLE_STATE_CLOSED;
2227+
if (o->agt_args == NULL) {
2228+
PyErr_SetString(
2229+
PyExc_RuntimeError,
2230+
"aclose(): asynchronous generator is already running");
2231+
}
2232+
else {
2233+
PyErr_SetString(
2234+
PyExc_RuntimeError,
2235+
"athrow(): asynchronous generator is already running");
2236+
}
2237+
return NULL;
2238+
}
2239+
2240+
o->agt_state = AWAITABLE_STATE_ITER;
2241+
o->agt_gen->ag_running_async = 1;
2242+
}
2243+
22092244
retval = gen_throw((PyGenObject*)o->agt_gen, args, nargs);
22102245
if (o->agt_args) {
22112246
retval = async_gen_unwrap_value(o->agt_gen, retval);
22122247
if (retval == NULL) {
22132248
o->agt_state = AWAITABLE_STATE_CLOSED;
2249+
o->agt_gen->ag_running_async = 0;
22142250
}
22152251
return retval;
22162252
} else {
@@ -2224,6 +2260,7 @@ async_gen_athrow_throw(PyAsyncGenAThrow *o, PyObject *const *args, Py_ssize_t na
22242260
}
22252261
if (retval == NULL) {
22262262
o->agt_state = AWAITABLE_STATE_CLOSED;
2263+
o->agt_gen->ag_running_async = 0;
22272264
}
22282265
if (PyErr_ExceptionMatches(PyExc_StopAsyncIteration) ||
22292266
PyErr_ExceptionMatches(PyExc_GeneratorExit))

0 commit comments

Comments
 (0)