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 { ...@@ -11,6 +11,7 @@ typedef struct {
PyObject *ag_finalizer; PyObject *ag_finalizer;
int ag_hooks_inited; int ag_hooks_inited;
int ag_closed; int ag_closed;
int ag_running_async;
} __pyx_PyAsyncGenObject; } __pyx_PyAsyncGenObject;
static PyTypeObject *__pyx__PyAsyncGenWrappedValueType = 0; static PyTypeObject *__pyx__PyAsyncGenWrappedValueType = 0;
...@@ -42,6 +43,7 @@ static __pyx_CoroutineObject *__Pyx_AsyncGen_New( ...@@ -42,6 +43,7 @@ static __pyx_CoroutineObject *__Pyx_AsyncGen_New(
gen->ag_finalizer = NULL; gen->ag_finalizer = NULL;
gen->ag_closed = 0; gen->ag_closed = 0;
gen->ag_hooks_inited = 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); 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 * ...@@ -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_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_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 { typedef enum {
__PYX_AWAITABLE_STATE_INIT, /* new awaitable, has not yet been iterated */ __PYX_AWAITABLE_STATE_INIT, /* new awaitable, has not yet been iterated */
...@@ -253,7 +257,7 @@ static PyObject * ...@@ -253,7 +257,7 @@ static PyObject *
__Pyx_async_gen_anext(PyObject *g) __Pyx_async_gen_anext(PyObject *g)
{ {
__pyx_PyAsyncGenObject *o = (__pyx_PyAsyncGenObject*) 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 NULL;
} }
return __Pyx_async_gen_asend_new(o, NULL); return __Pyx_async_gen_asend_new(o, NULL);
...@@ -268,7 +272,7 @@ __Pyx_async_gen_anext_method(PyObject *g, CYTHON_UNUSED PyObject *arg) { ...@@ -268,7 +272,7 @@ __Pyx_async_gen_anext_method(PyObject *g, CYTHON_UNUSED PyObject *arg) {
static PyObject * static PyObject *
__Pyx_async_gen_asend(__pyx_PyAsyncGenObject *o, PyObject *arg) __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 NULL;
} }
return __Pyx_async_gen_asend_new(o, arg); return __Pyx_async_gen_asend_new(o, arg);
...@@ -278,7 +282,7 @@ __Pyx_async_gen_asend(__pyx_PyAsyncGenObject *o, PyObject *arg) ...@@ -278,7 +282,7 @@ __Pyx_async_gen_asend(__pyx_PyAsyncGenObject *o, PyObject *arg)
static PyObject * static PyObject *
__Pyx_async_gen_aclose(__pyx_PyAsyncGenObject *o, CYTHON_UNUSED PyObject *arg) __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 NULL;
} }
return __Pyx_async_gen_athrow_new(o, NULL); return __Pyx_async_gen_athrow_new(o, NULL);
...@@ -288,7 +292,7 @@ __Pyx_async_gen_aclose(__pyx_PyAsyncGenObject *o, CYTHON_UNUSED PyObject *arg) ...@@ -288,7 +292,7 @@ __Pyx_async_gen_aclose(__pyx_PyAsyncGenObject *o, CYTHON_UNUSED PyObject *arg)
static PyObject * static PyObject *
__Pyx_async_gen_athrow(__pyx_PyAsyncGenObject *o, PyObject *args) __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 NULL;
} }
return __Pyx_async_gen_athrow_new(o, args); return __Pyx_async_gen_athrow_new(o, args);
...@@ -313,7 +317,7 @@ static PyGetSetDef __Pyx_async_gen_getsetlist[] = { ...@@ -313,7 +317,7 @@ static PyGetSetDef __Pyx_async_gen_getsetlist[] = {
static PyMemberDef __Pyx_async_gen_memberlist[] = { static PyMemberDef __Pyx_async_gen_memberlist[] = {
//REMOVED: {(char*) "ag_frame", T_OBJECT, offsetof(__pyx_PyAsyncGenObject, ag_frame), READONLY}, //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}, //REMOVED: {(char*) "ag_code", T_OBJECT, offsetof(__pyx_PyAsyncGenObject, ag_code), READONLY},
//ADDED: "ag_await" //ADDED: "ag_await"
{(char*) "ag_await", T_OBJECT, offsetof(__pyx_CoroutineObject, yieldfrom), READONLY, {(char*) "ag_await", T_OBJECT, offsetof(__pyx_CoroutineObject, yieldfrom), READONLY,
...@@ -360,7 +364,7 @@ static PyTypeObject __pyx_AsyncGenType_type = { ...@@ -360,7 +364,7 @@ static PyTypeObject __pyx_AsyncGenType_type = {
sizeof(__pyx_PyAsyncGenObject), /* tp_basicsize */ sizeof(__pyx_PyAsyncGenObject), /* tp_basicsize */
0, /* tp_itemsize */ 0, /* tp_itemsize */
(destructor)__Pyx_Coroutine_dealloc, /* tp_dealloc */ (destructor)__Pyx_Coroutine_dealloc, /* tp_dealloc */
0, /* tp_print */ 0, /* tp_vectorcall_offset */
0, /* tp_getattr */ 0, /* tp_getattr */
0, /* tp_setattr */ 0, /* tp_setattr */
#if CYTHON_USE_ASYNC_SLOTS #if CYTHON_USE_ASYNC_SLOTS
...@@ -425,7 +429,7 @@ static PyTypeObject __pyx_AsyncGenType_type = { ...@@ -425,7 +429,7 @@ static PyTypeObject __pyx_AsyncGenType_type = {
0, /*tp_vectorcall*/ 0, /*tp_vectorcall*/
#endif #endif
#if PY_VERSION_HEX >= 0x030800b4 && PY_VERSION_HEX < 0x03090000 #if PY_VERSION_HEX >= 0x030800b4 && PY_VERSION_HEX < 0x03090000
0, /*tp_print*/ 0, /*tp_vectorcall_offset*/
#endif #endif
}; };
...@@ -471,6 +475,7 @@ __Pyx_async_gen_unwrap_value(__pyx_PyAsyncGenObject *gen, PyObject *result) ...@@ -471,6 +475,7 @@ __Pyx_async_gen_unwrap_value(__pyx_PyAsyncGenObject *gen, PyObject *result)
gen->ag_closed = 1; gen->ag_closed = 1;
} }
gen->ag_running_async = 0;
return NULL; return NULL;
} }
...@@ -478,6 +483,7 @@ __Pyx_async_gen_unwrap_value(__pyx_PyAsyncGenObject *gen, PyObject *result) ...@@ -478,6 +483,7 @@ __Pyx_async_gen_unwrap_value(__pyx_PyAsyncGenObject *gen, PyObject *result)
/* async yield */ /* async yield */
__Pyx_ReturnWithStopIteration(((__pyx__PyAsyncGenWrappedValue*)result)->agw_val); __Pyx_ReturnWithStopIteration(((__pyx__PyAsyncGenWrappedValue*)result)->agw_val);
Py_DECREF(result); Py_DECREF(result);
gen->ag_running_async = 0;
return NULL; return NULL;
} }
...@@ -494,7 +500,7 @@ __Pyx_async_gen_asend_dealloc(__pyx_PyAsyncGenASend *o) ...@@ -494,7 +500,7 @@ __Pyx_async_gen_asend_dealloc(__pyx_PyAsyncGenASend *o)
PyObject_GC_UnTrack((PyObject *)o); PyObject_GC_UnTrack((PyObject *)o);
Py_CLEAR(o->ags_gen); Py_CLEAR(o->ags_gen);
Py_CLEAR(o->ags_sendval); 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)); assert(__pyx_PyAsyncGenASend_CheckExact(o));
__Pyx_ag_asend_freelist[__Pyx_ag_asend_freelist_free++] = o; __Pyx_ag_asend_freelist[__Pyx_ag_asend_freelist_free++] = o;
} else { } else {
...@@ -518,17 +524,25 @@ __Pyx_async_gen_asend_send(PyObject *g, PyObject *arg) ...@@ -518,17 +524,25 @@ __Pyx_async_gen_asend_send(PyObject *g, PyObject *arg)
PyObject *result; PyObject *result;
if (unlikely(o->ags_state == __PYX_AWAITABLE_STATE_CLOSED)) { 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; return NULL;
} }
if (o->ags_state == __PYX_AWAITABLE_STATE_INIT) { 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) { if (arg == NULL || arg == Py_None) {
arg = o->ags_sendval ? o->ags_sendval : Py_None; arg = o->ags_sendval ? o->ags_sendval : Py_None;
} }
o->ags_state = __PYX_AWAITABLE_STATE_ITER; 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_Coroutine_Send((PyObject*)o->ags_gen, arg);
result = __Pyx_async_gen_unwrap_value(o->ags_gen, result); result = __Pyx_async_gen_unwrap_value(o->ags_gen, result);
...@@ -553,7 +567,7 @@ __Pyx_async_gen_asend_throw(__pyx_PyAsyncGenASend *o, PyObject *args) ...@@ -553,7 +567,7 @@ __Pyx_async_gen_asend_throw(__pyx_PyAsyncGenASend *o, PyObject *args)
PyObject *result; PyObject *result;
if (unlikely(o->ags_state == __PYX_AWAITABLE_STATE_CLOSED)) { 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; return NULL;
} }
...@@ -602,7 +616,7 @@ static PyTypeObject __pyx__PyAsyncGenASendType_type = { ...@@ -602,7 +616,7 @@ static PyTypeObject __pyx__PyAsyncGenASendType_type = {
0, /* tp_itemsize */ 0, /* tp_itemsize */
/* methods */ /* methods */
(destructor)__Pyx_async_gen_asend_dealloc, /* tp_dealloc */ (destructor)__Pyx_async_gen_asend_dealloc, /* tp_dealloc */
0, /* tp_print */ 0, /* tp_vectorcall_offset */
0, /* tp_getattr */ 0, /* tp_getattr */
0, /* tp_setattr */ 0, /* tp_setattr */
#if CYTHON_USE_ASYNC_SLOTS #if CYTHON_USE_ASYNC_SLOTS
...@@ -660,7 +674,7 @@ static PyTypeObject __pyx__PyAsyncGenASendType_type = { ...@@ -660,7 +674,7 @@ static PyTypeObject __pyx__PyAsyncGenASendType_type = {
0, /*tp_vectorcall*/ 0, /*tp_vectorcall*/
#endif #endif
#if PY_VERSION_HEX >= 0x030800b4 && PY_VERSION_HEX < 0x03090000 #if PY_VERSION_HEX >= 0x030800b4 && PY_VERSION_HEX < 0x03090000
0, /*tp_print*/ 0, /*tp_vectorcall_offset*/
#endif #endif
}; };
...@@ -669,13 +683,13 @@ static PyObject * ...@@ -669,13 +683,13 @@ static PyObject *
__Pyx_async_gen_asend_new(__pyx_PyAsyncGenObject *gen, PyObject *sendval) __Pyx_async_gen_asend_new(__pyx_PyAsyncGenObject *gen, PyObject *sendval)
{ {
__pyx_PyAsyncGenASend *o; __pyx_PyAsyncGenASend *o;
if (__Pyx_ag_asend_freelist_free) { if (likely(__Pyx_ag_asend_freelist_free)) {
__Pyx_ag_asend_freelist_free--; __Pyx_ag_asend_freelist_free--;
o = __Pyx_ag_asend_freelist[__Pyx_ag_asend_freelist_free]; o = __Pyx_ag_asend_freelist[__Pyx_ag_asend_freelist_free];
_Py_NewReference((PyObject *)o); _Py_NewReference((PyObject *)o);
} else { } else {
o = PyObject_GC_New(__pyx_PyAsyncGenASend, __pyx__PyAsyncGenASendType); o = PyObject_GC_New(__pyx_PyAsyncGenASend, __pyx__PyAsyncGenASendType);
if (o == NULL) { if (unlikely(o == NULL)) {
return NULL; return NULL;
} }
} }
...@@ -701,7 +715,7 @@ __Pyx_async_gen_wrapped_val_dealloc(__pyx__PyAsyncGenWrappedValue *o) ...@@ -701,7 +715,7 @@ __Pyx_async_gen_wrapped_val_dealloc(__pyx__PyAsyncGenWrappedValue *o)
{ {
PyObject_GC_UnTrack((PyObject *)o); PyObject_GC_UnTrack((PyObject *)o);
Py_CLEAR(o->agw_val); 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)); assert(__pyx__PyAsyncGenWrappedValue_CheckExact(o));
__Pyx_ag_value_freelist[__Pyx_ag_value_freelist_free++] = o; __Pyx_ag_value_freelist[__Pyx_ag_value_freelist_free++] = o;
} else { } else {
...@@ -726,7 +740,7 @@ static PyTypeObject __pyx__PyAsyncGenWrappedValueType_type = { ...@@ -726,7 +740,7 @@ static PyTypeObject __pyx__PyAsyncGenWrappedValueType_type = {
0, /* tp_itemsize */ 0, /* tp_itemsize */
/* methods */ /* methods */
(destructor)__Pyx_async_gen_wrapped_val_dealloc, /* tp_dealloc */ (destructor)__Pyx_async_gen_wrapped_val_dealloc, /* tp_dealloc */
0, /* tp_print */ 0, /* tp_vectorcall_offset */
0, /* tp_getattr */ 0, /* tp_getattr */
0, /* tp_setattr */ 0, /* tp_setattr */
0, /* tp_as_async */ 0, /* tp_as_async */
...@@ -775,7 +789,7 @@ static PyTypeObject __pyx__PyAsyncGenWrappedValueType_type = { ...@@ -775,7 +789,7 @@ static PyTypeObject __pyx__PyAsyncGenWrappedValueType_type = {
0, /*tp_vectorcall*/ 0, /*tp_vectorcall*/
#endif #endif
#if PY_VERSION_HEX >= 0x030800b4 && PY_VERSION_HEX < 0x03090000 #if PY_VERSION_HEX >= 0x030800b4 && PY_VERSION_HEX < 0x03090000
0, /*tp_print*/ 0, /*tp_vectorcall_offset*/
#endif #endif
}; };
...@@ -787,7 +801,7 @@ __Pyx__PyAsyncGenValueWrapperNew(PyObject *val) ...@@ -787,7 +801,7 @@ __Pyx__PyAsyncGenValueWrapperNew(PyObject *val)
__pyx__PyAsyncGenWrappedValue *o; __pyx__PyAsyncGenWrappedValue *o;
assert(val); assert(val);
if (__Pyx_ag_value_freelist_free) { if (likely(__Pyx_ag_value_freelist_free)) {
__Pyx_ag_value_freelist_free--; __Pyx_ag_value_freelist_free--;
o = __Pyx_ag_value_freelist[__Pyx_ag_value_freelist_free]; o = __Pyx_ag_value_freelist[__Pyx_ag_value_freelist_free];
assert(__pyx__PyAsyncGenWrappedValue_CheckExact(o)); assert(__pyx__PyAsyncGenWrappedValue_CheckExact(o));
...@@ -832,34 +846,56 @@ static PyObject * ...@@ -832,34 +846,56 @@ static PyObject *
__Pyx_async_gen_athrow_send(__pyx_PyAsyncGenAThrow *o, PyObject *arg) __Pyx_async_gen_athrow_send(__pyx_PyAsyncGenAThrow *o, PyObject *arg)
{ {
__pyx_CoroutineObject *gen = (__pyx_CoroutineObject*)o->agt_gen; __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); PyErr_SetNone(PyExc_StopIteration);
return NULL; return NULL;
} }
if (o->agt_state == __PYX_AWAITABLE_STATE_INIT) { if (o->agt_state == __PYX_AWAITABLE_STATE_INIT) {
if (o->agt_gen->ag_closed) { if (unlikely(o->agt_gen->ag_running_async)) {
PyErr_SetNone(PyExc_StopIteration); 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; return NULL;
} }
if (arg != Py_None) { if (unlikely(arg != Py_None)) {
PyErr_SetString(PyExc_RuntimeError, __Pyx_NON_INIT_CORO_MSG); PyErr_SetString(PyExc_RuntimeError, __Pyx_NON_INIT_CORO_MSG);
return NULL; return NULL;
} }
o->agt_state = __PYX_AWAITABLE_STATE_ITER; o->agt_state = __PYX_AWAITABLE_STATE_ITER;
o->agt_gen->ag_running_async = 1;
if (o->agt_args == NULL) { if (o->agt_args == NULL) {
/* aclose() mode */ /* aclose() mode */
o->agt_gen->ag_closed = 1; o->agt_gen->ag_closed = 1;
retval = __Pyx__Coroutine_Throw((PyObject*)gen, retval = __Pyx__Coroutine_Throw((PyObject*)gen,
/* Do not close generator when /* Do not close generator when PyExc_GeneratorExit is passed */
PyExc_GeneratorExit is passed */ PyExc_GeneratorExit, NULL, NULL, NULL, 0);
PyExc_GeneratorExit, NULL, NULL, NULL, 0);
if (retval && __pyx__PyAsyncGenWrappedValue_CheckExact(retval)) { if (retval && __pyx__PyAsyncGenWrappedValue_CheckExact(retval)) {
Py_DECREF(retval); Py_DECREF(retval);
...@@ -870,14 +906,13 @@ __Pyx_async_gen_athrow_send(__pyx_PyAsyncGenAThrow *o, PyObject *arg) ...@@ -870,14 +906,13 @@ __Pyx_async_gen_athrow_send(__pyx_PyAsyncGenAThrow *o, PyObject *arg)
PyObject *tb = NULL; PyObject *tb = NULL;
PyObject *val = NULL; PyObject *val = NULL;
if (!PyArg_UnpackTuple(o->agt_args, "athrow", 1, 3, if (unlikely(!PyArg_UnpackTuple(o->agt_args, "athrow", 1, 3, &typ, &val, &tb))) {
&typ, &val, &tb)) {
return NULL; return NULL;
} }
retval = __Pyx__Coroutine_Throw((PyObject*)gen, retval = __Pyx__Coroutine_Throw((PyObject*)gen,
/* Do not close generator when PyExc_GeneratorExit is passed */ /* Do not close generator when PyExc_GeneratorExit is passed */
typ, val, tb, o->agt_args, 0); typ, val, tb, o->agt_args, 0);
retval = __Pyx_async_gen_unwrap_value(o->agt_gen, retval); retval = __Pyx_async_gen_unwrap_value(o->agt_gen, retval);
} }
if (retval == NULL) { if (retval == NULL) {
...@@ -908,26 +943,26 @@ __Pyx_async_gen_athrow_send(__pyx_PyAsyncGenAThrow *o, PyObject *arg) ...@@ -908,26 +943,26 @@ __Pyx_async_gen_athrow_send(__pyx_PyAsyncGenAThrow *o, PyObject *arg)
} }
yield_close: yield_close:
o->agt_gen->ag_running_async = 0;
o->agt_state = __PYX_AWAITABLE_STATE_CLOSED;
PyErr_SetString( PyErr_SetString(
PyExc_RuntimeError, __Pyx_ASYNC_GEN_IGNORED_EXIT_MSG); PyExc_RuntimeError, __Pyx_ASYNC_GEN_IGNORED_EXIT_MSG);
return NULL; return NULL;
check_error: check_error:
if (PyErr_ExceptionMatches(__Pyx_PyExc_StopAsyncIteration)) { o->agt_gen->ag_running_async = 0;
o->agt_state = __PYX_AWAITABLE_STATE_CLOSED; 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) { if (o->agt_args == NULL) {
// when aclose() is called we don't want to propagate // when aclose() is called we don't want to propagate
// StopAsyncIteration; just raise StopIteration, signalling // StopAsyncIteration or GeneratorExit; just raise
// that 'aclose()' is done. // StopIteration, signalling that this 'aclose()' await
// is done.
PyErr_Clear(); PyErr_Clear();
PyErr_SetNone(PyExc_StopIteration); 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; return NULL;
} }
...@@ -937,13 +972,8 @@ __Pyx_async_gen_athrow_throw(__pyx_PyAsyncGenAThrow *o, PyObject *args) ...@@ -937,13 +972,8 @@ __Pyx_async_gen_athrow_throw(__pyx_PyAsyncGenAThrow *o, PyObject *args)
{ {
PyObject *retval; PyObject *retval;
if (o->agt_state == __PYX_AWAITABLE_STATE_INIT) { if (unlikely(o->agt_state == __PYX_AWAITABLE_STATE_CLOSED)) {
PyErr_SetString(PyExc_RuntimeError, __Pyx_NON_INIT_CORO_MSG); PyErr_SetString(PyExc_RuntimeError, __Pyx_ASYNC_GEN_CANNOT_REUSE_CLOSE_MSG);
return NULL;
}
if (o->agt_state == __PYX_AWAITABLE_STATE_CLOSED) {
PyErr_SetNone(PyExc_StopIteration);
return NULL; return NULL;
} }
...@@ -951,12 +981,24 @@ __Pyx_async_gen_athrow_throw(__pyx_PyAsyncGenAThrow *o, PyObject *args) ...@@ -951,12 +981,24 @@ __Pyx_async_gen_athrow_throw(__pyx_PyAsyncGenAThrow *o, PyObject *args)
if (o->agt_args) { if (o->agt_args) {
return __Pyx_async_gen_unwrap_value(o->agt_gen, retval); return __Pyx_async_gen_unwrap_value(o->agt_gen, retval);
} else { } else {
/* aclose() mode */ // aclose() mode
PyObject *exc_type;
if (retval && __pyx__PyAsyncGenWrappedValue_CheckExact(retval)) { if (retval && __pyx__PyAsyncGenWrappedValue_CheckExact(retval)) {
o->agt_gen->ag_running_async = 0;
o->agt_state = __PYX_AWAITABLE_STATE_CLOSED;
Py_DECREF(retval); Py_DECREF(retval);
PyErr_SetString(PyExc_RuntimeError, __Pyx_ASYNC_GEN_IGNORED_EXIT_MSG); PyErr_SetString(PyExc_RuntimeError, __Pyx_ASYNC_GEN_IGNORED_EXIT_MSG);
return NULL; 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; return retval;
} }
} }
...@@ -1002,7 +1044,7 @@ static PyTypeObject __pyx__PyAsyncGenAThrowType_type = { ...@@ -1002,7 +1044,7 @@ static PyTypeObject __pyx__PyAsyncGenAThrowType_type = {
sizeof(__pyx_PyAsyncGenAThrow), /* tp_basicsize */ sizeof(__pyx_PyAsyncGenAThrow), /* tp_basicsize */
0, /* tp_itemsize */ 0, /* tp_itemsize */
(destructor)__Pyx_async_gen_athrow_dealloc, /* tp_dealloc */ (destructor)__Pyx_async_gen_athrow_dealloc, /* tp_dealloc */
0, /* tp_print */ 0, /* tp_vectorcall_offset */
0, /* tp_getattr */ 0, /* tp_getattr */
0, /* tp_setattr */ 0, /* tp_setattr */
#if CYTHON_USE_ASYNC_SLOTS #if CYTHON_USE_ASYNC_SLOTS
...@@ -1060,7 +1102,7 @@ static PyTypeObject __pyx__PyAsyncGenAThrowType_type = { ...@@ -1060,7 +1102,7 @@ static PyTypeObject __pyx__PyAsyncGenAThrowType_type = {
0, /*tp_vectorcall*/ 0, /*tp_vectorcall*/
#endif #endif
#if PY_VERSION_HEX >= 0x030800b4 && PY_VERSION_HEX < 0x03090000 #if PY_VERSION_HEX >= 0x030800b4 && PY_VERSION_HEX < 0x03090000
0, /*tp_print*/ 0, /*tp_vectorcall_offset*/
#endif #endif
}; };
...@@ -1070,7 +1112,7 @@ __Pyx_async_gen_athrow_new(__pyx_PyAsyncGenObject *gen, PyObject *args) ...@@ -1070,7 +1112,7 @@ __Pyx_async_gen_athrow_new(__pyx_PyAsyncGenObject *gen, PyObject *args)
{ {
__pyx_PyAsyncGenAThrow *o; __pyx_PyAsyncGenAThrow *o;
o = PyObject_GC_New(__pyx_PyAsyncGenAThrow, __pyx__PyAsyncGenAThrowType); o = PyObject_GC_New(__pyx_PyAsyncGenAThrow, __pyx__PyAsyncGenAThrowType);
if (o == NULL) { if (unlikely(o == NULL)) {
return NULL; return NULL;
} }
o->agt_gen = gen; o->agt_gen = gen;
......
...@@ -263,19 +263,26 @@ class AsyncGenTest(unittest.TestCase): ...@@ -263,19 +263,26 @@ class AsyncGenTest(unittest.TestCase):
def async_iterate(g): def async_iterate(g):
res = [] res = []
while True: while True:
an = g.__anext__()
try: 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: except StopAsyncIteration:
res.append('STOP') res.append('STOP')
break 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 return res
sync_gen_result = sync_iterate(sync_gen) sync_gen_result = sync_iterate(sync_gen)
...@@ -303,19 +310,22 @@ class AsyncGenTest(unittest.TestCase): ...@@ -303,19 +310,22 @@ class AsyncGenTest(unittest.TestCase):
g = gen() g = gen()
ai = g.__aiter__() ai = g.__aiter__()
self.assertEqual(next(ai.__anext__()), ('result',))
an = ai.__anext__()
self.assertEqual(next(an), ('result',))
try: try:
next(ai.__anext__()) next(an)
except StopIteration as ex: except StopIteration as ex:
self.assertEqual(ex.args[0], 123) self.assertEqual(ex.args[0], 123)
else: else:
self.fail('StopIteration was not raised') self.fail('StopIteration was not raised')
self.assertEqual(next(ai.__anext__()), ('result',)) an = ai.__anext__()
self.assertEqual(next(an), ('result',))
try: try:
next(ai.__anext__()) next(an)
except StopAsyncIteration as ex: except StopAsyncIteration as ex:
self.assertFalse(ex.args) self.assertFalse(ex.args)
else: else:
...@@ -339,10 +349,12 @@ class AsyncGenTest(unittest.TestCase): ...@@ -339,10 +349,12 @@ class AsyncGenTest(unittest.TestCase):
g = gen() g = gen()
ai = g.__aiter__() ai = g.__aiter__()
self.assertEqual(next(ai.__anext__()), ('result',))
an = ai.__anext__()
self.assertEqual(next(an), ('result',))
try: try:
next(ai.__anext__()) next(an)
except StopIteration as ex: except StopIteration as ex:
self.assertEqual(ex.args[0], 123) self.assertEqual(ex.args[0], 123)
else: else:
...@@ -449,6 +461,37 @@ class AsyncGenTest(unittest.TestCase): ...@@ -449,6 +461,37 @@ class AsyncGenTest(unittest.TestCase):
"non-None value .* async generator"): "non-None value .* async generator"):
gen().__anext__().send(100) 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): def test_async_gen_api_01(self):
async def gen(): async def gen():
yield 123 yield 123
...@@ -742,17 +785,13 @@ class AsyncGenAsyncioTest(unittest.TestCase): ...@@ -742,17 +785,13 @@ class AsyncGenAsyncioTest(unittest.TestCase):
gen = foo() gen = foo()
it = gen.__aiter__() it = gen.__aiter__()
self.assertEqual(await it.__anext__(), 1) self.assertEqual(await it.__anext__(), 1)
t = self.loop.create_task(it.__anext__())
await asyncio.sleep(0.01, loop=self.loop)
await gen.aclose() await gen.aclose()
return t
t = self.loop.run_until_complete(run()) self.loop.run_until_complete(run())
self.assertEqual(DONE, 1) self.assertEqual(DONE, 1)
# Silence ResourceWarnings # Silence ResourceWarnings
fut.cancel() fut.cancel()
t.cancel()
self.loop.run_until_complete(asyncio.sleep(0.01, loop=self.loop)) self.loop.run_until_complete(asyncio.sleep(0.01, loop=self.loop))
@needs_py36_asyncio @needs_py36_asyncio
...@@ -850,6 +889,33 @@ class AsyncGenAsyncioTest(unittest.TestCase): ...@@ -850,6 +889,33 @@ class AsyncGenAsyncioTest(unittest.TestCase):
self.loop.run_until_complete(run()) self.loop.run_until_complete(run())
self.assertEqual(DONE, 10) 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): def test_async_gen_asyncio_asend_01(self):
DONE = 0 DONE = 0
...@@ -1152,47 +1218,156 @@ class AsyncGenAsyncioTest(unittest.TestCase): ...@@ -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(asyncio.sleep(0.1, loop=self.loop))
self.loop.run_until_complete(self.loop.shutdown_asyncgens())
self.assertEqual(finalized, 2)
# Silence warnings # Silence warnings
t1.cancel() t1.cancel()
t2.cancel() t2.cancel()
self.loop.run_until_complete(asyncio.sleep(0.1, loop=self.loop))
@needs_py36_asyncio with self.assertRaises(asyncio.CancelledError):
def test_async_gen_asyncio_shutdown_02(self): self.loop.run_until_complete(t1)
logged = 0 with self.assertRaises(asyncio.CancelledError):
self.loop.run_until_complete(t2)
def logger(loop, context): self.loop.run_until_complete(self.loop.shutdown_asyncgens())
nonlocal logged
self.assertIn('asyncgen', context)
expected = 'an error occurred during closing of asynchronous'
if expected in context['message']:
logged += 1
async def waiter(timeout): self.assertEqual(finalized, 2)
try:
await asyncio.sleep(timeout, loop=self.loop)
yield 1
finally:
1 / ZERO
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 pass
await it.aclose()
t = self.loop.create_task(wait()) self.loop.run_until_complete(run())
self.loop.run_until_complete(asyncio.sleep(0.1, loop=self.loop))
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__": if __name__ == "__main__":
unittest.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