summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNathaniel J. Smith <njs@pobox.com>2020-02-13 08:15:38 (GMT)
committerGitHub <noreply@github.com>2020-02-13 08:15:38 (GMT)
commit925dc7fb1d0db85dc137afa4cd14211bf0d67414 (patch)
treec03ac2612f81cd6135dc9f084a491fb8663b260f
parent7514f4f6254f4a2d13ea8e5632a8e5f22b637e0b (diff)
downloadcpython-925dc7fb1d0db85dc137afa4cd14211bf0d67414.zip
cpython-925dc7fb1d0db85dc137afa4cd14211bf0d67414.tar.gz
cpython-925dc7fb1d0db85dc137afa4cd14211bf0d67414.tar.bz2
bpo-39606: allow closing async generators that are already closed (GH-18475)
The fix for [bpo-39386](https://bugs.python.org/issue39386) attempted to make it so you couldn't reuse a agen.aclose() coroutine object. It accidentally also prevented you from calling aclose() at all on an async generator that was already closed or exhausted. This commit fixes it so we're only blocking the actually illegal cases, while allowing the legal cases. The new tests failed before this patch. Also confirmed that this fixes the test failures we were seeing in Trio with Python dev builds: https://github.com/python-trio/trio/pull/1396 https://bugs.python.org/issue39606
-rw-r--r--Lib/test/test_asyncgen.py30
-rw-r--r--Misc/NEWS.d/next/Core and Builtins/2020-02-11-23-59-07.bpo-39606.a72Sxc.rst2
-rw-r--r--Objects/genobject.c15
3 files changed, 41 insertions, 6 deletions
diff --git a/Lib/test/test_asyncgen.py b/Lib/test/test_asyncgen.py
index 24b20be..fb6321d 100644
--- a/Lib/test/test_asyncgen.py
+++ b/Lib/test/test_asyncgen.py
@@ -1128,7 +1128,7 @@ class AsyncGenAsyncioTest(unittest.TestCase):
self.assertEqual([], messages)
- def test_async_gen_await_anext_twice(self):
+ def test_async_gen_await_same_anext_coro_twice(self):
async def async_iterate():
yield 1
yield 2
@@ -1147,7 +1147,7 @@ class AsyncGenAsyncioTest(unittest.TestCase):
self.loop.run_until_complete(run())
- def test_async_gen_await_aclose_twice(self):
+ def test_async_gen_await_same_aclose_coro_twice(self):
async def async_iterate():
yield 1
yield 2
@@ -1164,6 +1164,32 @@ class AsyncGenAsyncioTest(unittest.TestCase):
self.loop.run_until_complete(run())
+ def test_async_gen_aclose_twice_with_different_coros(self):
+ # Regression test for https://bugs.python.org/issue39606
+ async def async_iterate():
+ yield 1
+ yield 2
+
+ async def run():
+ it = async_iterate()
+ await it.aclose()
+ await it.aclose()
+
+ self.loop.run_until_complete(run())
+
+ def test_async_gen_aclose_after_exhaustion(self):
+ # Regression test for https://bugs.python.org/issue39606
+ async def async_iterate():
+ yield 1
+ yield 2
+
+ async def run():
+ it = async_iterate()
+ async for _ in it:
+ pass
+ await it.aclose()
+
+ self.loop.run_until_complete(run())
if __name__ == "__main__":
unittest.main()
diff --git a/Misc/NEWS.d/next/Core and Builtins/2020-02-11-23-59-07.bpo-39606.a72Sxc.rst b/Misc/NEWS.d/next/Core and Builtins/2020-02-11-23-59-07.bpo-39606.a72Sxc.rst
new file mode 100644
index 0000000..b7cbe4e
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2020-02-11-23-59-07.bpo-39606.a72Sxc.rst
@@ -0,0 +1,2 @@
+Fix regression caused by fix for bpo-39386, that prevented calling
+``aclose`` on an async generator that had already been closed or exhausted.
diff --git a/Objects/genobject.c b/Objects/genobject.c
index 576d685..0efd57d 100644
--- a/Objects/genobject.c
+++ b/Objects/genobject.c
@@ -1797,16 +1797,22 @@ async_gen_athrow_send(PyAsyncGenAThrow *o, PyObject *arg)
PyFrameObject *f = gen->gi_frame;
PyObject *retval;
- if (f == NULL || f->f_stacktop == NULL ||
- o->agt_state == AWAITABLE_STATE_CLOSED) {
+ if (o->agt_state == AWAITABLE_STATE_CLOSED) {
PyErr_SetString(
PyExc_RuntimeError,
"cannot reuse already awaited aclose()/athrow()");
return NULL;
}
+ if (f == NULL || f->f_stacktop == NULL) {
+ o->agt_state = AWAITABLE_STATE_CLOSED;
+ PyErr_SetNone(PyExc_StopIteration);
+ return NULL;
+ }
+
if (o->agt_state == AWAITABLE_STATE_INIT) {
if (o->agt_gen->ag_running_async) {
+ o->agt_state = AWAITABLE_STATE_CLOSED;
if (o->agt_args == NULL) {
PyErr_SetString(
PyExc_RuntimeError,
@@ -1878,7 +1884,6 @@ async_gen_athrow_send(PyAsyncGenAThrow *o, PyObject *arg)
/* aclose() mode */
if (retval) {
if (_PyAsyncGenWrappedValue_CheckExact(retval)) {
- o->agt_gen->ag_running_async = 0;
Py_DECREF(retval);
goto yield_close;
}
@@ -1893,16 +1898,17 @@ async_gen_athrow_send(PyAsyncGenAThrow *o, PyObject *arg)
yield_close:
o->agt_gen->ag_running_async = 0;
+ o->agt_state = AWAITABLE_STATE_CLOSED;
PyErr_SetString(
PyExc_RuntimeError, ASYNC_GEN_IGNORED_EXIT_MSG);
return NULL;
check_error:
o->agt_gen->ag_running_async = 0;
+ o->agt_state = AWAITABLE_STATE_CLOSED;
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 or GeneratorExit; just raise
@@ -1936,6 +1942,7 @@ async_gen_athrow_throw(PyAsyncGenAThrow *o, PyObject *args)
/* aclose() mode */
if (retval && _PyAsyncGenWrappedValue_CheckExact(retval)) {
o->agt_gen->ag_running_async = 0;
+ o->agt_state = AWAITABLE_STATE_CLOSED;
Py_DECREF(retval);
PyErr_SetString(PyExc_RuntimeError, ASYNC_GEN_IGNORED_EXIT_MSG);
return NULL;