diff --git a/Lib/test/test_asyncgen.py b/Lib/test/test_asyncgen.py index 39605dca388..a1e9e1b89c6 100644 --- a/Lib/test/test_asyncgen.py +++ b/Lib/test/test_asyncgen.py @@ -399,9 +399,8 @@ class AsyncGenTest(unittest.TestCase): with self.assertWarns(DeprecationWarning): x = gen().athrow(GeneratorExit, GeneratorExit(), None) - with self.assertWarnsRegex(RuntimeWarning, - f"coroutine method 'athrow' of '{gen.__qualname__}' " - f"was never awaited"): + with self.assertRaises(GeneratorExit): + x.send(None) del x gc_collect() @@ -1572,11 +1571,6 @@ class AsyncGenAsyncioTest(unittest.TestCase): self.assertIsInstance(message['exception'], ZeroDivisionError) self.assertIn('unhandled exception during asyncio.run() shutdown', message['message']) - with self.assertWarnsRegex(RuntimeWarning, - f"coroutine method 'aclose' of '{async_iterate.__qualname__}' " - f"was never awaited"): - del message, messages - gc_collect() def test_async_gen_expression_01(self): async def arange(n): @@ -1630,10 +1624,6 @@ class AsyncGenAsyncioTest(unittest.TestCase): asyncio.run(main()) self.assertEqual([], messages) - with self.assertWarnsRegex(RuntimeWarning, - f"coroutine method 'aclose' of '{async_iterate.__qualname__}' " - f"was never awaited"): - gc_collect() def test_async_gen_await_same_anext_coro_twice(self): async def async_iterate(): @@ -1671,6 +1661,62 @@ class AsyncGenAsyncioTest(unittest.TestCase): self.loop.run_until_complete(run()) + def test_async_gen_throw_same_aclose_coro_twice(self): + async def async_iterate(): + yield 1 + yield 2 + + it = async_iterate() + nxt = it.aclose() + with self.assertRaises(StopIteration): + nxt.throw(GeneratorExit) + + with self.assertRaisesRegex( + RuntimeError, + r"cannot reuse already awaited aclose\(\)/athrow\(\)" + ): + nxt.throw(GeneratorExit) + + def test_async_gen_throw_custom_same_aclose_coro_twice(self): + async def async_iterate(): + yield 1 + yield 2 + + it = async_iterate() + + class MyException(Exception): + pass + + nxt = it.aclose() + with self.assertRaises(MyException): + nxt.throw(MyException) + + with self.assertRaisesRegex( + RuntimeError, + r"cannot reuse already awaited aclose\(\)/athrow\(\)" + ): + nxt.throw(MyException) + + def test_async_gen_throw_custom_same_athrow_coro_twice(self): + async def async_iterate(): + yield 1 + yield 2 + + it = async_iterate() + + class MyException(Exception): + pass + + nxt = it.athrow(MyException) + with self.assertRaises(MyException): + nxt.throw(MyException) + + with self.assertRaisesRegex( + RuntimeError, + r"cannot reuse already awaited aclose\(\)/athrow\(\)" + ): + nxt.throw(MyException) + def test_async_gen_aclose_twice_with_different_coros(self): # Regression test for https://bugs.python.org/issue39606 async def async_iterate(): @@ -1752,6 +1798,19 @@ class TestUnawaitedWarnings(unittest.TestCase): g.aclose() gc_collect() + def test_aclose_throw(self): + async def gen(): + return + yield + + class MyException(Exception): + pass + + g = gen() + with self.assertRaises(MyException): + g.aclose().throw(MyException) + del g + gc_collect() if __name__ == "__main__": diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-04-13-16-55-53.gh-issue-117536.xkVbfv.rst b/Misc/NEWS.d/next/Core and Builtins/2024-04-13-16-55-53.gh-issue-117536.xkVbfv.rst new file mode 100644 index 00000000000..2492fd163cb --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-04-13-16-55-53.gh-issue-117536.xkVbfv.rst @@ -0,0 +1 @@ +Fix a :exc:`RuntimeWarning` when calling ``agen.aclose().throw(Exception)``. diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-04-15-13-53-59.gh-issue-117894.8LpZ6m.rst b/Misc/NEWS.d/next/Core and Builtins/2024-04-15-13-53-59.gh-issue-117894.8LpZ6m.rst new file mode 100644 index 00000000000..bd32500a54e --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-04-15-13-53-59.gh-issue-117894.8LpZ6m.rst @@ -0,0 +1 @@ +Prevent ``agen.aclose()`` objects being re-used after ``.throw()``. diff --git a/Objects/genobject.c b/Objects/genobject.c index 8d1dbb72ba9..5d7da49cfca 100644 --- a/Objects/genobject.c +++ b/Objects/genobject.c @@ -2208,7 +2208,11 @@ async_gen_athrow_throw(PyAsyncGenAThrow *o, PyObject *const *args, Py_ssize_t na retval = gen_throw((PyGenObject*)o->agt_gen, args, nargs); if (o->agt_args) { - return async_gen_unwrap_value(o->agt_gen, retval); + retval = async_gen_unwrap_value(o->agt_gen, retval); + if (retval == NULL) { + o->agt_state = AWAITABLE_STATE_CLOSED; + } + return retval; } else { /* aclose() mode */ if (retval && _PyAsyncGenWrappedValue_CheckExact(retval)) { @@ -2218,6 +2222,9 @@ async_gen_athrow_throw(PyAsyncGenAThrow *o, PyObject *const *args, Py_ssize_t na PyErr_SetString(PyExc_RuntimeError, ASYNC_GEN_IGNORED_EXIT_MSG); return NULL; } + if (retval == NULL) { + o->agt_state = AWAITABLE_STATE_CLOSED; + } if (PyErr_ExceptionMatches(PyExc_StopAsyncIteration) || PyErr_ExceptionMatches(PyExc_GeneratorExit)) {