Commit 23a60938 authored by Stefan Behnel's avatar Stefan Behnel

Bring async generator implementation en par with current CPython 3.9 alpha.

* allow closing async generators that are already closed
https://bugs.python.org/issue39606

* Prevent double awaiting of async iterator
https://bugs.python.org/issue39386

* Fix ag_running; prohibit running athrow/asend/aclose in parallel
https://bugs.python.org/issue30773

* Fix asynchronous generators to handle GeneratorExit in athrow()
https://bugs.python.org/issue33786

* Ignore GeneratorExit in async_gen_athrow_throw
https://bugs.python.org/issue35409

* make async_generator_athrow object tolerant to throwing exceptions
https://bugs.python.org/issue38013
parent a3b1cf6f
......@@ -11,6 +11,7 @@ typedef struct {
PyObject *ag_finalizer;
int ag_hooks_inited;
int ag_closed;
int ag_running_async;
} __pyx_PyAsyncGenObject;
static PyTypeObject *__pyx__PyAsyncGenWrappedValueType = 0;
......@@ -42,6 +43,7 @@ static __pyx_CoroutineObject *__Pyx_AsyncGen_New(
gen->ag_finalizer = NULL;
gen->ag_closed = 0;
gen->ag_hooks_inited = 0;
gen->ag_running_async = 0;
return __Pyx__Coroutine_NewInit((__pyx_CoroutineObject*)gen, body, code, closure, name, qualname, module_name);
}
......@@ -127,6 +129,8 @@ static PyObject *__Pyx_async_gen_athrow_new(__pyx_PyAsyncGenObject *, PyObject *
static const char *__Pyx_NON_INIT_CORO_MSG = "can't send non-None value to a just-started coroutine";
static const char *__Pyx_ASYNC_GEN_IGNORED_EXIT_MSG = "async generator ignored GeneratorExit";
static const char *__Pyx_ASYNC_GEN_CANNOT_REUSE_SEND_MSG = "cannot reuse already awaited __anext__()/asend()";
static const char *__Pyx_ASYNC_GEN_CANNOT_REUSE_CLOSE_MSG = "cannot reuse already awaited aclose()/athrow()";
typedef enum {
__PYX_AWAITABLE_STATE_INIT, /* new awaitable, has not yet been iterated */
......@@ -253,7 +257,7 @@ static PyObject *
__Pyx_async_gen_anext(PyObject *g)
{
__pyx_PyAsyncGenObject *o = (__pyx_PyAsyncGenObject*) g;
if (__Pyx_async_gen_init_hooks(o)) {
if (unlikely(__Pyx_async_gen_init_hooks(o))) {
return NULL;
}
return __Pyx_async_gen_asend_new(o, NULL);
......@@ -268,7 +272,7 @@ __Pyx_async_gen_anext_method(PyObject *g, CYTHON_UNUSED PyObject *arg) {
static PyObject *
__Pyx_async_gen_asend(__pyx_PyAsyncGenObject *o, PyObject *arg)
{
if (__Pyx_async_gen_init_hooks(o)) {
if (unlikely(__Pyx_async_gen_init_hooks(o))) {
return NULL;
}
return __Pyx_async_gen_asend_new(o, arg);
......@@ -278,7 +282,7 @@ __Pyx_async_gen_asend(__pyx_PyAsyncGenObject *o, PyObject *arg)
static PyObject *
__Pyx_async_gen_aclose(__pyx_PyAsyncGenObject *o, CYTHON_UNUSED PyObject *arg)
{
if (__Pyx_async_gen_init_hooks(o)) {
if (unlikely(__Pyx_async_gen_init_hooks(o))) {
return NULL;
}
return __Pyx_async_gen_athrow_new(o, NULL);
......@@ -288,7 +292,7 @@ __Pyx_async_gen_aclose(__pyx_PyAsyncGenObject *o, CYTHON_UNUSED PyObject *arg)
static PyObject *
__Pyx_async_gen_athrow(__pyx_PyAsyncGenObject *o, PyObject *args)
{
if (__Pyx_async_gen_init_hooks(o)) {
if (unlikely(__Pyx_async_gen_init_hooks(o))) {
return NULL;
}
return __Pyx_async_gen_athrow_new(o, args);
......@@ -313,7 +317,7 @@ static PyGetSetDef __Pyx_async_gen_getsetlist[] = {
static PyMemberDef __Pyx_async_gen_memberlist[] = {
//REMOVED: {(char*) "ag_frame", T_OBJECT, offsetof(__pyx_PyAsyncGenObject, ag_frame), READONLY},
{(char*) "ag_running", T_BOOL, offsetof(__pyx_CoroutineObject, is_running), READONLY, NULL},
{(char*) "ag_running", T_BOOL, offsetof(__pyx_PyAsyncGenObject, ag_running_async), READONLY, NULL},
//REMOVED: {(char*) "ag_code", T_OBJECT, offsetof(__pyx_PyAsyncGenObject, ag_code), READONLY},
//ADDED: "ag_await"
{(char*) "ag_await", T_OBJECT, offsetof(__pyx_CoroutineObject, yieldfrom), READONLY,
......@@ -360,7 +364,7 @@ static PyTypeObject __pyx_AsyncGenType_type = {
sizeof(__pyx_PyAsyncGenObject), /* tp_basicsize */
0, /* tp_itemsize */
(destructor)__Pyx_Coroutine_dealloc, /* tp_dealloc */
0, /* tp_print */
0, /* tp_vectorcall_offset */
0, /* tp_getattr */
0, /* tp_setattr */
#if CYTHON_USE_ASYNC_SLOTS
......@@ -425,7 +429,7 @@ static PyTypeObject __pyx_AsyncGenType_type = {
0, /*tp_vectorcall*/
#endif
#if PY_VERSION_HEX >= 0x030800b4 && PY_VERSION_HEX < 0x03090000
0, /*tp_print*/
0, /*tp_vectorcall_offset*/
#endif
};
......@@ -471,6 +475,7 @@ __Pyx_async_gen_unwrap_value(__pyx_PyAsyncGenObject *gen, PyObject *result)
gen->ag_closed = 1;
}
gen->ag_running_async = 0;
return NULL;
}
......@@ -478,6 +483,7 @@ __Pyx_async_gen_unwrap_value(__pyx_PyAsyncGenObject *gen, PyObject *result)
/* async yield */
__Pyx_ReturnWithStopIteration(((__pyx__PyAsyncGenWrappedValue*)result)->agw_val);
Py_DECREF(result);
gen->ag_running_async = 0;
return NULL;
}
......@@ -494,7 +500,7 @@ __Pyx_async_gen_asend_dealloc(__pyx_PyAsyncGenASend *o)
PyObject_GC_UnTrack((PyObject *)o);
Py_CLEAR(o->ags_gen);
Py_CLEAR(o->ags_sendval);
if (__Pyx_ag_asend_freelist_free < _PyAsyncGen_MAXFREELIST) {
if (likely(__Pyx_ag_asend_freelist_free < _PyAsyncGen_MAXFREELIST)) {
assert(__pyx_PyAsyncGenASend_CheckExact(o));
__Pyx_ag_asend_freelist[__Pyx_ag_asend_freelist_free++] = o;
} else {
......@@ -518,17 +524,25 @@ __Pyx_async_gen_asend_send(PyObject *g, PyObject *arg)
PyObject *result;
if (unlikely(o->ags_state == __PYX_AWAITABLE_STATE_CLOSED)) {
PyErr_SetNone(PyExc_StopIteration);
PyErr_SetString(PyExc_RuntimeError, __Pyx_ASYNC_GEN_CANNOT_REUSE_SEND_MSG);
return NULL;
}
if (o->ags_state == __PYX_AWAITABLE_STATE_INIT) {
if (unlikely(o->ags_gen->ag_running_async)) {
PyErr_SetString(
PyExc_RuntimeError,
"anext(): asynchronous generator is already running");
return NULL;
}
if (arg == NULL || arg == Py_None) {
arg = o->ags_sendval ? o->ags_sendval : Py_None;
}
o->ags_state = __PYX_AWAITABLE_STATE_ITER;
}
o->ags_gen->ag_running_async = 1;
result = __Pyx_Coroutine_Send((PyObject*)o->ags_gen, arg);
result = __Pyx_async_gen_unwrap_value(o->ags_gen, result);
......@@ -553,7 +567,7 @@ __Pyx_async_gen_asend_throw(__pyx_PyAsyncGenASend *o, PyObject *args)
PyObject *result;
if (unlikely(o->ags_state == __PYX_AWAITABLE_STATE_CLOSED)) {
PyErr_SetNone(PyExc_StopIteration);
PyErr_SetString(PyExc_RuntimeError, __Pyx_ASYNC_GEN_CANNOT_REUSE_SEND_MSG);
return NULL;
}
......@@ -602,7 +616,7 @@ static PyTypeObject __pyx__PyAsyncGenASendType_type = {
0, /* tp_itemsize */
/* methods */
(destructor)__Pyx_async_gen_asend_dealloc, /* tp_dealloc */
0, /* tp_print */
0, /* tp_vectorcall_offset */
0, /* tp_getattr */
0, /* tp_setattr */
#if CYTHON_USE_ASYNC_SLOTS
......@@ -660,7 +674,7 @@ static PyTypeObject __pyx__PyAsyncGenASendType_type = {
0, /*tp_vectorcall*/
#endif
#if PY_VERSION_HEX >= 0x030800b4 && PY_VERSION_HEX < 0x03090000
0, /*tp_print*/
0, /*tp_vectorcall_offset*/
#endif
};
......@@ -669,13 +683,13 @@ static PyObject *
__Pyx_async_gen_asend_new(__pyx_PyAsyncGenObject *gen, PyObject *sendval)
{
__pyx_PyAsyncGenASend *o;
if (__Pyx_ag_asend_freelist_free) {
if (likely(__Pyx_ag_asend_freelist_free)) {
__Pyx_ag_asend_freelist_free--;
o = __Pyx_ag_asend_freelist[__Pyx_ag_asend_freelist_free];
_Py_NewReference((PyObject *)o);
} else {
o = PyObject_GC_New(__pyx_PyAsyncGenASend, __pyx__PyAsyncGenASendType);
if (o == NULL) {
if (unlikely(o == NULL)) {
return NULL;
}
}
......@@ -701,7 +715,7 @@ __Pyx_async_gen_wrapped_val_dealloc(__pyx__PyAsyncGenWrappedValue *o)
{
PyObject_GC_UnTrack((PyObject *)o);
Py_CLEAR(o->agw_val);
if (__Pyx_ag_value_freelist_free < _PyAsyncGen_MAXFREELIST) {
if (likely(__Pyx_ag_value_freelist_free < _PyAsyncGen_MAXFREELIST)) {
assert(__pyx__PyAsyncGenWrappedValue_CheckExact(o));
__Pyx_ag_value_freelist[__Pyx_ag_value_freelist_free++] = o;
} else {
......@@ -726,7 +740,7 @@ static PyTypeObject __pyx__PyAsyncGenWrappedValueType_type = {
0, /* tp_itemsize */
/* methods */
(destructor)__Pyx_async_gen_wrapped_val_dealloc, /* tp_dealloc */
0, /* tp_print */
0, /* tp_vectorcall_offset */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_as_async */
......@@ -775,7 +789,7 @@ static PyTypeObject __pyx__PyAsyncGenWrappedValueType_type = {
0, /*tp_vectorcall*/
#endif
#if PY_VERSION_HEX >= 0x030800b4 && PY_VERSION_HEX < 0x03090000
0, /*tp_print*/
0, /*tp_vectorcall_offset*/
#endif
};
......@@ -787,7 +801,7 @@ __Pyx__PyAsyncGenValueWrapperNew(PyObject *val)
__pyx__PyAsyncGenWrappedValue *o;
assert(val);
if (__Pyx_ag_value_freelist_free) {
if (likely(__Pyx_ag_value_freelist_free)) {
__Pyx_ag_value_freelist_free--;
o = __Pyx_ag_value_freelist[__Pyx_ag_value_freelist_free];
assert(__pyx__PyAsyncGenWrappedValue_CheckExact(o));
......@@ -832,34 +846,56 @@ static PyObject *
__Pyx_async_gen_athrow_send(__pyx_PyAsyncGenAThrow *o, PyObject *arg)
{
__pyx_CoroutineObject *gen = (__pyx_CoroutineObject*)o->agt_gen;
PyObject *retval;
PyObject *retval, *exc_type;
if (unlikely(o->agt_state == __PYX_AWAITABLE_STATE_CLOSED)) {
PyErr_SetString(PyExc_RuntimeError, __Pyx_ASYNC_GEN_CANNOT_REUSE_CLOSE_MSG);
return NULL;
}
if (o->agt_state == __PYX_AWAITABLE_STATE_CLOSED) {
if (unlikely(gen->resume_label == -1)) {
// already run past the end
o->agt_state = __PYX_AWAITABLE_STATE_CLOSED;
PyErr_SetNone(PyExc_StopIteration);
return NULL;
}
if (o->agt_state == __PYX_AWAITABLE_STATE_INIT) {
if (o->agt_gen->ag_closed) {
PyErr_SetNone(PyExc_StopIteration);
if (unlikely(o->agt_gen->ag_running_async)) {
o->agt_state = __PYX_AWAITABLE_STATE_CLOSED;
if (o->agt_args == NULL) {
PyErr_SetString(
PyExc_RuntimeError,
"aclose(): asynchronous generator is already running");
} else {
PyErr_SetString(
PyExc_RuntimeError,
"athrow(): asynchronous generator is already running");
}
return NULL;
}
if (unlikely(o->agt_gen->ag_closed)) {
o->agt_state = __PYX_AWAITABLE_STATE_CLOSED;
PyErr_SetNone(__Pyx_PyExc_StopAsyncIteration);
return NULL;
}
if (arg != Py_None) {
if (unlikely(arg != Py_None)) {
PyErr_SetString(PyExc_RuntimeError, __Pyx_NON_INIT_CORO_MSG);
return NULL;
}
o->agt_state = __PYX_AWAITABLE_STATE_ITER;
o->agt_gen->ag_running_async = 1;
if (o->agt_args == NULL) {
/* aclose() mode */
o->agt_gen->ag_closed = 1;
retval = __Pyx__Coroutine_Throw((PyObject*)gen,
/* Do not close generator when
PyExc_GeneratorExit is passed */
PyExc_GeneratorExit, NULL, NULL, NULL, 0);
/* Do not close generator when PyExc_GeneratorExit is passed */
PyExc_GeneratorExit, NULL, NULL, NULL, 0);
if (retval && __pyx__PyAsyncGenWrappedValue_CheckExact(retval)) {
Py_DECREF(retval);
......@@ -870,14 +906,13 @@ __Pyx_async_gen_athrow_send(__pyx_PyAsyncGenAThrow *o, PyObject *arg)
PyObject *tb = NULL;
PyObject *val = NULL;
if (!PyArg_UnpackTuple(o->agt_args, "athrow", 1, 3,
&typ, &val, &tb)) {
if (unlikely(!PyArg_UnpackTuple(o->agt_args, "athrow", 1, 3, &typ, &val, &tb))) {
return NULL;
}
retval = __Pyx__Coroutine_Throw((PyObject*)gen,
/* Do not close generator when PyExc_GeneratorExit is passed */
typ, val, tb, o->agt_args, 0);
/* Do not close generator when PyExc_GeneratorExit is passed */
typ, val, tb, o->agt_args, 0);
retval = __Pyx_async_gen_unwrap_value(o->agt_gen, retval);
}
if (retval == NULL) {
......@@ -908,26 +943,26 @@ __Pyx_async_gen_athrow_send(__pyx_PyAsyncGenAThrow *o, PyObject *arg)
}
yield_close:
o->agt_gen->ag_running_async = 0;
o->agt_state = __PYX_AWAITABLE_STATE_CLOSED;
PyErr_SetString(
PyExc_RuntimeError, __Pyx_ASYNC_GEN_IGNORED_EXIT_MSG);
return NULL;
check_error:
if (PyErr_ExceptionMatches(__Pyx_PyExc_StopAsyncIteration)) {
o->agt_state = __PYX_AWAITABLE_STATE_CLOSED;
o->agt_gen->ag_running_async = 0;
o->agt_state = __PYX_AWAITABLE_STATE_CLOSED;
exc_type = PyErr_Occurred();
if (__Pyx_PyErr_GivenExceptionMatches2(exc_type, __Pyx_PyExc_StopAsyncIteration, PyExc_GeneratorExit)) {
if (o->agt_args == NULL) {
// when aclose() is called we don't want to propagate
// StopAsyncIteration; just raise StopIteration, signalling
// that 'aclose()' is done.
// StopAsyncIteration or GeneratorExit; just raise
// StopIteration, signalling that this 'aclose()' await
// is done.
PyErr_Clear();
PyErr_SetNone(PyExc_StopIteration);
}
}
else if (PyErr_ExceptionMatches(PyExc_GeneratorExit)) {
o->agt_state = __PYX_AWAITABLE_STATE_CLOSED;
PyErr_Clear(); /* ignore these errors */
PyErr_SetNone(PyExc_StopIteration);
}
return NULL;
}
......@@ -937,13 +972,8 @@ __Pyx_async_gen_athrow_throw(__pyx_PyAsyncGenAThrow *o, PyObject *args)
{
PyObject *retval;
if (o->agt_state == __PYX_AWAITABLE_STATE_INIT) {
PyErr_SetString(PyExc_RuntimeError, __Pyx_NON_INIT_CORO_MSG);
return NULL;
}
if (o->agt_state == __PYX_AWAITABLE_STATE_CLOSED) {
PyErr_SetNone(PyExc_StopIteration);
if (unlikely(o->agt_state == __PYX_AWAITABLE_STATE_CLOSED)) {
PyErr_SetString(PyExc_RuntimeError, __Pyx_ASYNC_GEN_CANNOT_REUSE_CLOSE_MSG);
return NULL;
}
......@@ -951,12 +981,24 @@ __Pyx_async_gen_athrow_throw(__pyx_PyAsyncGenAThrow *o, PyObject *args)
if (o->agt_args) {
return __Pyx_async_gen_unwrap_value(o->agt_gen, retval);
} else {
/* aclose() mode */
// aclose() mode
PyObject *exc_type;
if (retval && __pyx__PyAsyncGenWrappedValue_CheckExact(retval)) {
o->agt_gen->ag_running_async = 0;
o->agt_state = __PYX_AWAITABLE_STATE_CLOSED;
Py_DECREF(retval);
PyErr_SetString(PyExc_RuntimeError, __Pyx_ASYNC_GEN_IGNORED_EXIT_MSG);
return NULL;
}
exc_type = PyErr_Occurred();
if (__Pyx_PyErr_GivenExceptionMatches2(exc_type, __Pyx_PyExc_StopAsyncIteration, PyExc_GeneratorExit)) {
// when aclose() is called we don't want to propagate
// StopAsyncIteration or GeneratorExit; just raise
// StopIteration, signalling that this 'aclose()' await
// is done.
PyErr_Clear();
PyErr_SetNone(PyExc_StopIteration);
}
return retval;
}
}
......@@ -1002,7 +1044,7 @@ static PyTypeObject __pyx__PyAsyncGenAThrowType_type = {
sizeof(__pyx_PyAsyncGenAThrow), /* tp_basicsize */
0, /* tp_itemsize */
(destructor)__Pyx_async_gen_athrow_dealloc, /* tp_dealloc */
0, /* tp_print */
0, /* tp_vectorcall_offset */
0, /* tp_getattr */
0, /* tp_setattr */
#if CYTHON_USE_ASYNC_SLOTS
......@@ -1060,7 +1102,7 @@ static PyTypeObject __pyx__PyAsyncGenAThrowType_type = {
0, /*tp_vectorcall*/
#endif
#if PY_VERSION_HEX >= 0x030800b4 && PY_VERSION_HEX < 0x03090000
0, /*tp_print*/
0, /*tp_vectorcall_offset*/
#endif
};
......@@ -1070,7 +1112,7 @@ __Pyx_async_gen_athrow_new(__pyx_PyAsyncGenObject *gen, PyObject *args)
{
__pyx_PyAsyncGenAThrow *o;
o = PyObject_GC_New(__pyx_PyAsyncGenAThrow, __pyx__PyAsyncGenAThrowType);
if (o == NULL) {
if (unlikely(o == NULL)) {
return NULL;
}
o->agt_gen = gen;
......
......@@ -263,19 +263,26 @@ class AsyncGenTest(unittest.TestCase):
def async_iterate(g):
res = []
while True:
an = g.__anext__()
try:
next(g.__anext__())
while True:
try:
next(an)
except StopIteration as ex:
if ex.args:
res.append(ex.args[0])
break
else:
res.append('EMPTY StopIteration')
break
except StopAsyncIteration:
raise
except Exception as ex:
res.append(str(type(ex)))
break
except StopAsyncIteration:
res.append('STOP')
break
except StopIteration as ex:
if ex.args:
res.append(ex.args[0])
else:
res.append('EMPTY StopIteration')
break
except Exception as ex:
res.append(str(type(ex)))
return res
sync_gen_result = sync_iterate(sync_gen)
......@@ -303,19 +310,22 @@ class AsyncGenTest(unittest.TestCase):
g = gen()
ai = g.__aiter__()
self.assertEqual(next(ai.__anext__()), ('result',))
an = ai.__anext__()
self.assertEqual(next(an), ('result',))
try:
next(ai.__anext__())
next(an)
except StopIteration as ex:
self.assertEqual(ex.args[0], 123)
else:
self.fail('StopIteration was not raised')
self.assertEqual(next(ai.__anext__()), ('result',))
an = ai.__anext__()
self.assertEqual(next(an), ('result',))
try:
next(ai.__anext__())
next(an)
except StopAsyncIteration as ex:
self.assertFalse(ex.args)
else:
......@@ -339,10 +349,12 @@ class AsyncGenTest(unittest.TestCase):
g = gen()
ai = g.__aiter__()
self.assertEqual(next(ai.__anext__()), ('result',))
an = ai.__anext__()
self.assertEqual(next(an), ('result',))
try:
next(ai.__anext__())
next(an)
except StopIteration as ex:
self.assertEqual(ex.args[0], 123)
else:
......@@ -449,6 +461,37 @@ class AsyncGenTest(unittest.TestCase):
"non-None value .* async generator"):
gen().__anext__().send(100)
def test_async_gen_exception_11(self):
def sync_gen():
yield 10
yield 20
def sync_gen_wrapper():
yield 1
sg = sync_gen()
sg.send(None)
try:
sg.throw(GeneratorExit())
except GeneratorExit:
yield 2
yield 3
async def async_gen():
yield 10
yield 20
async def async_gen_wrapper():
yield 1
asg = async_gen()
await asg.asend(None)
try:
await asg.athrow(GeneratorExit())
except GeneratorExit:
yield 2
yield 3
self.compare_generators(sync_gen_wrapper(), async_gen_wrapper())
def test_async_gen_api_01(self):
async def gen():
yield 123
......@@ -742,17 +785,13 @@ class AsyncGenAsyncioTest(unittest.TestCase):
gen = foo()
it = gen.__aiter__()
self.assertEqual(await it.__anext__(), 1)
t = self.loop.create_task(it.__anext__())
await asyncio.sleep(0.01, loop=self.loop)
await gen.aclose()
return t
t = self.loop.run_until_complete(run())
self.loop.run_until_complete(run())
self.assertEqual(DONE, 1)
# Silence ResourceWarnings
fut.cancel()
t.cancel()
self.loop.run_until_complete(asyncio.sleep(0.01, loop=self.loop))
@needs_py36_asyncio
......@@ -850,6 +889,33 @@ class AsyncGenAsyncioTest(unittest.TestCase):
self.loop.run_until_complete(run())
self.assertEqual(DONE, 10)
def test_async_gen_asyncio_aclose_12(self):
DONE = 0
async def target():
await asyncio.sleep(0.01)
1 / 0
async def foo():
nonlocal DONE
task = self.loop.create_task(target())
try:
yield 1
finally:
try:
await task
except ZeroDivisionError:
DONE = 1
async def run():
gen = foo()
it = gen.__aiter__()
await it.__anext__()
await gen.aclose()
self.loop.run_until_complete(run())
self.assertEqual(DONE, 1)
def test_async_gen_asyncio_asend_01(self):
DONE = 0
......@@ -1152,47 +1218,156 @@ class AsyncGenAsyncioTest(unittest.TestCase):
self.loop.run_until_complete(asyncio.sleep(0.1, loop=self.loop))
self.loop.run_until_complete(self.loop.shutdown_asyncgens())
self.assertEqual(finalized, 2)
# Silence warnings
t1.cancel()
t2.cancel()
self.loop.run_until_complete(asyncio.sleep(0.1, loop=self.loop))
@needs_py36_asyncio
def test_async_gen_asyncio_shutdown_02(self):
logged = 0
with self.assertRaises(asyncio.CancelledError):
self.loop.run_until_complete(t1)
with self.assertRaises(asyncio.CancelledError):
self.loop.run_until_complete(t2)
def logger(loop, context):
nonlocal logged
self.assertIn('asyncgen', context)
expected = 'an error occurred during closing of asynchronous'
if expected in context['message']:
logged += 1
self.loop.run_until_complete(self.loop.shutdown_asyncgens())
async def waiter(timeout):
try:
await asyncio.sleep(timeout, loop=self.loop)
yield 1
finally:
1 / ZERO
self.assertEqual(finalized, 2)
async def wait():
async for _ in waiter(1):
"""
def test_async_gen_expression_01(self):
async def arange(n):
for i in range(n):
await asyncio.sleep(0.01)
yield i
def make_arange(n):
# This syntax is legal starting with Python 3.7
return (i * 2 async for i in arange(n))
async def run():
return [i async for i in make_arange(10)]
res = self.loop.run_until_complete(run())
self.assertEqual(res, [i * 2 for i in range(10)])
def test_async_gen_expression_02(self):
async def wrap(n):
await asyncio.sleep(0.01)
return n
def make_arange(n):
# This syntax is legal starting with Python 3.7
return (i * 2 for i in range(n) if await wrap(i))
async def run():
return [i async for i in make_arange(10)]
res = self.loop.run_until_complete(run())
self.assertEqual(res, [i * 2 for i in range(1, 10)])
"""
def test_asyncgen_nonstarted_hooks_are_cancellable(self):
# See https://bugs.python.org/issue38013
messages = []
def exception_handler(loop, context):
messages.append(context)
async def async_iterate():
yield 1
yield 2
async def main():
# loop = asyncio.get_running_loop()
loop = self.loop
loop.set_exception_handler(exception_handler)
async for i in async_iterate():
break
# asyncio.run(main())
self.loop.run_until_complete(main())
self.assertEqual([], messages)
def test_async_gen_await_same_anext_coro_twice(self):
async def async_iterate():
yield 1
yield 2
async def run():
it = async_iterate()
nxt = it.__anext__()
await nxt
with self.assertRaisesRegex(
RuntimeError,
r"cannot reuse already awaited __anext__\(\)/asend\(\)"
):
await nxt
await it.aclose() # prevent unfinished iterator warning
self.loop.run_until_complete(run())
def test_async_gen_await_same_aclose_coro_twice(self):
async def async_iterate():
yield 1
yield 2
async def run():
it = async_iterate()
nxt = it.aclose()
await nxt
with self.assertRaisesRegex(
RuntimeError,
r"cannot reuse already awaited aclose\(\)/athrow\(\)"
):
await nxt
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()
t = self.loop.create_task(wait())
self.loop.run_until_complete(asyncio.sleep(0.1, loop=self.loop))
self.loop.run_until_complete(run())
self.loop.set_exception_handler(logger)
self.loop.run_until_complete(self.loop.shutdown_asyncgens())
"""
def test_async_gen_aclose_compatible_with_get_stack(self):
async def async_generator():
yield object()
self.assertEqual(logged, 1)
async def run():
ag = async_generator()
self.loop.create_task(ag.aclose())
tasks = asyncio.all_tasks()
for task in tasks:
# No AttributeError raised
task.get_stack()
self.loop.run_until_complete(run())
"""
# Silence warnings
t.cancel()
self.loop.run_until_complete(asyncio.sleep(0.1, loop=self.loop))
if __name__ == "__main__":
unittest.main()
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment