8000 GH-117881: fix athrow().throw()/asend().throw() concurrent access (GH… · python/cpython@fc7e1aa · GitHub
[go: up one dir, main page]

Skip to content

Commit fc7e1aa

Browse files
authored
GH-117881: fix athrow().throw()/asend().throw() concurrent access (GH-117882)
1 parent 2520eed commit fc7e1aa

File tree

3 files changed

+235
-2
lines changed

3 files changed

+235
-2
lines changed

Lib/test/test_asyncgen.py

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

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

15751722
def test_async_gen_expression_01(self):
15761723
async def arange(n):
@@ -1624,6 +1771,7 @@ async def main():
16241771
asyncio.run(main())
16251772

16261773
self.assertEqual([], messages)
1774+
gc_collect()
16271775

16281776
def test_async_gen_await_same_anext_coro_twice(self):
16291777
async def async_iterate():
@@ -1809,9 +1957,56 @@ class MyException(Exception):
18091957
g = gen()
18101958
with self.assertRaises(MyException):
18111959
g.aclose().throw(MyException)
1812-
del g
1813-
gc_collect()
18141960

1961+
del g
1962+
gc_collect() # does not warn unawaited
1963+
1964+
def test_asend_send_already_running(self):
1965+
@types.coroutine
1966+
def _async_yield(v):
1967+
return (yield v)
1968+
1969+
async def agenfn():
1970+
while True:
1971+
await _async_yield(1)
1972+
return
1973+
yield
1974+
1975+
agen = agenfn()
1976+
gen = agen.asend(None)
1977+
gen.send(None)
1978+
gen2 = agen.asend(None)
1979+
1980+
with self.assertRaisesRegex(RuntimeError,
1981+
r'anext\(\): asynchronous generator is already running'):
1982+
gen2.send(None)
1983+
1984+
del gen2
1985+
gc_collect() # does not warn unawaited
1986+
1987+
1988+
def test_athrow_send_already_running(self):
1989+
@types.coroutine
1990+
def _async_yield(v):
1991+
return (yield v)
1992+
1993+
async def agenfn():
1994+
while True:
1995+
await _async_yield(1)
1996+
return
1997+
yield
1998+
1999+
agen = agenfn()
2000+
gen = agen.asend(None)
2001+
gen.send(None)
2002+
gen2 = agen.athrow(Exception)
2003+
2004+
with self.assertRaisesRegex(RuntimeError,
2005+
r'athrow\(\): asynchronous generator is already running'):
2006+
gen2.send(None)
2007+
2008+
del gen2
2009+
gc_collect() # does not warn unawaited
18152010

18162011
if __name__ == "__main__":
18172012
unittest.main()
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
@@ -1774,6 +1774,7 @@ async_gen_asend_send(PyAsyncGenASend *o, PyObject *arg)
17741774

17751775
if (o->ags_state == AWAITABLE_STATE_INIT) {
17761776
if (o->ags_gen->ag_running_async) {
1777+
o->ags_state = AWAITABLE_STATE_CLOSED;
17771778
PyErr_SetString(
17781779
PyExc_RuntimeError,
17791780
"anext(): asynchronous generator is already running");
@@ -1817,10 +1818,24 @@ async_gen_asend_throw(PyAsyncGenASend *o, PyObject *const *args, Py_ssize_t narg
18171818
return NULL;
18181819
}
18191820

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

18231837
if (result == NULL) {
1838+
o->ags_gen->ag_running_async = 0;
18241839
o->ags_state = AWAITABLE_STATE_CLOSED;
18251840
}
18261841

@@ -2209,10 +2224,31 @@ async_gen_athrow_throw(PyAsyncGenAThrow *o, PyObject *const *args, Py_ssize_t na
22092224
return NULL;
22102225
}
22112226

2227+
if (o->agt_state == AWAITABLE_STATE_INIT) {
2228+
if (o->agt_gen->ag_running_async) {
2229+
o->agt_state = AWAITABLE_STATE_CLOSED;
2230+
if (o->agt_args == NULL) {
2231+
PyErr_SetString(
2232+
PyExc_RuntimeError,
2233+
"aclose(): asynchronous generator is already running");
2234+
}
2235+
else {
2236+
PyErr_SetString(
2237+
PyExc_RuntimeError,
2238+
"athrow(): asynchronous generator is already running");
2239+
}
2240+
return NULL;
2241+
}
2242+
2243+
o->agt_state = AWAITABLE_STATE_ITER;
2244+
o->agt_gen->ag_running_async = 1;
2245+
}
2246+
22122247
retval = gen_throw((PyGenObject*)o->agt_gen, args, nargs);
22132248
if (o->agt_args) {
22142249
retval = async_gen_unwrap_value(o->agt_gen, retval);
22152250
if (retval == NULL) {
2251+
o->agt_gen->ag_running_async = 0;
22162252
o->agt_state = AWAITABLE_STATE_CLOSED;
22172253
}
22182254
return retval;
@@ -2226,6 +2262,7 @@ async_gen_athrow_throw(PyAsyncGenAThrow *o, PyObject *const *args, Py_ssize_t na
22262262
return NULL;
22272263
}
22282264
if (retval == NULL) {
2265+
o->agt_gen->ag_running_async = 0;
22292266
o->agt_state = AWAITABLE_STATE_CLOSED;
22302267
}
22312268
if (PyErr_ExceptionMatches(PyExc_StopAsyncIteration) ||

0 commit comments

Comments
 (0)
0