From bfccb77ec1afe9d0634f6868e36ffad8b5914181 Mon Sep 17 00:00:00 2001 From: Dan Halbert Date: Fri, 18 Aug 2023 13:16:16 -0400 Subject: [PATCH] asyncio test fixes and asyncio library updates --- extmod/moduselect.c | 2 +- frozen/Adafruit_CircuitPython_asyncio | 2 +- ports/unix/mpconfigport.h | 1 + py/emitglue.c | 7 +- py/obj.h | 4 + py/objgenerator.c | 103 +++++++++++++------- py/runtime.c | 3 +- tests/extmod/uasyncio_threadsafeflag.py | 79 +++++++++++++++ tests/extmod/uasyncio_threadsafeflag.py.exp | 21 ++++ tests/extmod/websocket_basic.py.exp | 14 --- 10 files changed, 183 insertions(+), 53 deletions(-) create mode 100644 tests/extmod/uasyncio_threadsafeflag.py create mode 100644 tests/extmod/uasyncio_threadsafeflag.py.exp delete mode 100644 tests/extmod/websocket_basic.py.exp diff --git a/extmod/moduselect.c b/extmod/moduselect.c index b68d97bbe5..e41adbc791 100644 --- a/extmod/moduselect.c +++ b/extmod/moduselect.c @@ -383,6 +383,6 @@ const mp_obj_module_t mp_module_uselect = { .globals = (mp_obj_dict_t *)&mp_module_select_globals, }; -MP_REGISTER_MODULE(MP_QSTR_uselect, mp_module_uselect); +MP_REGISTER_MODULE(MP_QSTR_select, mp_module_uselect); #endif // MICROPY_PY_USELECT diff --git a/frozen/Adafruit_CircuitPython_asyncio b/frozen/Adafruit_CircuitPython_asyncio index 596cc896e5..510d4a3bb9 160000 --- a/frozen/Adafruit_CircuitPython_asyncio +++ b/frozen/Adafruit_CircuitPython_asyncio @@ -1 +1 @@ -Subproject commit 596cc896e5c8815caa2a6f405560833193848149 +Subproject commit 510d4a3bb9326a31105622fe1905301a4f543a2c diff --git a/ports/unix/mpconfigport.h b/ports/unix/mpconfigport.h index bcf5ffe5a0..a9e6cf9885 100644 --- a/ports/unix/mpconfigport.h +++ b/ports/unix/mpconfigport.h @@ -36,6 +36,7 @@ // CIRCUITPY #define CIRCUITPY_MICROPYTHON_ADVANCED (1) +#define MICROPY_PY_ASYNC_AWAIT (1) // If the variant did not set a feature level then configure a set of features. #ifndef MICROPY_CONFIG_ROM_LEVEL diff --git a/py/emitglue.c b/py/emitglue.c index 95be7f661a..ed30ea35f2 100644 --- a/py/emitglue.c +++ b/py/emitglue.c @@ -191,6 +191,7 @@ mp_obj_t mp_make_function_from_raw_code(const mp_raw_code_t *rc, const mp_module if ((rc->scope_flags & MP_SCOPE_FLAG_GENERATOR) != 0) { ((mp_obj_base_t *)MP_OBJ_TO_PTR(fun))->type = &mp_type_native_gen_wrap; } + // CIRCUITPY: no support for mp_type_native_coro_wrap, native coroutine objects (yet). break; #endif #if MICROPY_EMIT_INLINE_ASM @@ -203,7 +204,11 @@ mp_obj_t mp_make_function_from_raw_code(const mp_raw_code_t *rc, const mp_module assert(rc->kind == MP_CODE_BYTECODE); fun = mp_obj_new_fun_bc(def_args, rc->fun_data, context, rc->children); // check for generator functions and if so change the type of the object - if ((rc->scope_flags & MP_SCOPE_FLAG_GENERATOR) != 0) { + // A generator is MP_SCOPE_FLAG_ASYNC | MP_SCOPE_FLAG_GENERATOR, + // so check for ASYNC first. + if ((rc->scope_flags & MP_SCOPE_FLAG_ASYNC) != 0) { + ((mp_obj_base_t *)MP_OBJ_TO_PTR(fun))->type = &mp_type_coro_wrap; + } else if ((rc->scope_flags & MP_SCOPE_FLAG_GENERATOR) != 0) { ((mp_obj_base_t *)MP_OBJ_TO_PTR(fun))->type = &mp_type_gen_wrap; } diff --git a/py/obj.h b/py/obj.h index 81b0eab06c..f28563dfe4 100644 --- a/py/obj.h +++ b/py/obj.h @@ -738,6 +738,10 @@ extern const mp_obj_type_t mp_type_super; extern const mp_obj_type_t mp_type_gen_wrap; extern const mp_obj_type_t mp_type_native_gen_wrap; extern const mp_obj_type_t mp_type_gen_instance; +// CIRCUITPY +extern const mp_obj_type_t mp_type_coro_wrap; +// CIRCUITPY +extern const mp_obj_type_t mp_type_coro_instance; extern const mp_obj_type_t mp_type_fun_builtin_0; extern const mp_obj_type_t mp_type_fun_builtin_1; extern const mp_obj_type_t mp_type_fun_builtin_2; diff --git a/py/objgenerator.c b/py/objgenerator.c index 32f4af43c7..4040cd291d 100644 --- a/py/objgenerator.c +++ b/py/objgenerator.c @@ -55,23 +55,23 @@ typedef struct _mp_obj_gen_instance_t { // mp_const_none: Not-running, no exception. // MP_OBJ_NULL: Running, no exception. // other: Not running, pending exception. - bool coroutine_generator; mp_obj_t pend_exc; mp_code_state_t code_state; } mp_obj_gen_instance_t; STATIC mp_obj_t gen_wrap_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { - // A generating function is just a bytecode function with type mp_type_gen_wrap + // A generating or coroutine function is just a bytecode function + // with type mp_type_gen_wrap or mp_type_coro_wrap. mp_obj_fun_bc_t *self_fun = MP_OBJ_TO_PTR(self_in); // bytecode prelude: get state size and exception stack size const uint8_t *ip = self_fun->bytecode; MP_BC_PRELUDE_SIG_DECODE(ip); - // allocate the generator object, with room for local stack and exception stack + // allocate the generator or coroutine object, with room for local stack and exception stack mp_obj_gen_instance_t *o = mp_obj_malloc_var(mp_obj_gen_instance_t, byte, n_state * sizeof(mp_obj_t) + n_exc_stack * sizeof(mp_exc_stack_t), - &mp_type_gen_instance); + self_fun->base.type == &mp_type_gen_wrap ? &mp_type_gen_instance : &mp_type_coro_instance); o->pend_exc = mp_const_none; o->code_state.fun_bc = self_fun; @@ -93,6 +93,19 @@ const mp_obj_type_t mp_type_gen_wrap = { ), }; +const mp_obj_type_t mp_type_coro_wrap = { + { &mp_type_type }, + .flags = MP_TYPE_FLAG_BINDS_SELF | MP_TYPE_FLAG_EXTENDED, + .name = MP_QSTR_coroutine, + #if MICROPY_PY_FUNCTION_ATTRS + .attr = mp_obj_fun_bc_attr, + #endif + MP_TYPE_EXTENDED_FIELDS( + .call = gen_wrap_call, + .unary_op = mp_generic_unary_op, + ), +}; + /******************************************************************************/ // native generator wrapper @@ -167,20 +180,26 @@ const mp_obj_type_t mp_type_native_gen_wrap = { STATIC void gen_instance_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { (void)kind; mp_obj_gen_instance_t *self = MP_OBJ_TO_PTR(self_in); - #if MICROPY_PY_ASYNC_AWAIT - if (self->coroutine_generator) { - mp_printf(print, "<%q object '%q' at %p>", MP_QSTR_coroutine, mp_obj_fun_get_name(MP_OBJ_FROM_PTR(self->code_state.fun_bc)), self); - } else { - mp_printf(print, "<%q object '%q' at %p>", MP_QSTR_generator, mp_obj_fun_get_name(MP_OBJ_FROM_PTR(self->code_state.fun_bc)), self); - } - #else mp_printf(print, "", mp_obj_fun_get_name(MP_OBJ_FROM_PTR(self->code_state.fun_bc)), self); - #endif } +// CIRCUITPY +#if MICROPY_PY_ASYNC_AWAIT +STATIC void coro_instance_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + (void)kind; + mp_obj_gen_instance_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "", mp_obj_fun_get_name(MP_OBJ_FROM_PTR(self->code_state.fun_bc)), self); +} +#endif + mp_vm_return_kind_t mp_obj_gen_resume(mp_obj_t self_in, mp_obj_t send_value, mp_obj_t throw_value, mp_obj_t *ret_val) { MP_STACK_CHECK(); - mp_check_self(mp_obj_is_type(self_in, &mp_type_gen_instance)); + // CIRCUITPY + // note that self may have as its type either gen or coro, + // both of which are stored as an mp_obj_gen_instance_t . + mp_check_self( + mp_obj_is_type(self_in, &mp_type_gen_instance) || + mp_obj_is_type(self_in, &mp_type_coro_instance)); mp_obj_gen_instance_t *self = MP_OBJ_TO_PTR(self_in); if (self->code_state.ip == 0) { // Trying to resume an already stopped generator. @@ -188,7 +207,6 @@ mp_vm_return_kind_t mp_obj_gen_resume(mp_obj_t self_in, mp_obj_t send_value, mp_ *ret_val = mp_const_none; return MP_VM_RETURN_NORMAL; } - // Ensure the generator cannot be reentered during execution if (self->pend_exc == MP_OBJ_NULL) { mp_raise_ValueError(MP_ERROR_TEXT("generator already executing")); @@ -285,6 +303,7 @@ mp_vm_return_kind_t mp_obj_gen_resume(mp_obj_t self_in, mp_obj_t send_value, mp_ STATIC mp_obj_t gen_resume_and_raise(mp_obj_t self_in, mp_obj_t send_value, mp_obj_t throw_value, bool raise_stop_iteration) { mp_obj_t ret; switch (mp_obj_gen_resume(self_in, send_value, throw_value, &ret)) { + case MP_VM_RETURN_NORMAL: default: // A normal return is a StopIteration, either raise it or return @@ -307,12 +326,6 @@ STATIC mp_obj_t gen_resume_and_raise(mp_obj_t self_in, mp_obj_t send_value, mp_o } STATIC mp_obj_t gen_instance_iternext(mp_obj_t self_in) { - #if MICROPY_PY_ASYNC_AWAIT - mp_obj_gen_instance_t *self = MP_OBJ_TO_PTR(self_in); - if (self->coroutine_generator) { - mp_raise_TypeError(MP_ERROR_TEXT("'coroutine' object is not an iterator")); - } - #endif return gen_resume_and_raise(self_in, mp_const_none, MP_OBJ_NULL, false); } @@ -322,21 +335,12 @@ STATIC mp_obj_t gen_instance_send(mp_obj_t self_in, mp_obj_t send_value) { STATIC MP_DEFINE_CONST_FUN_OBJ_2(gen_instance_send_obj, gen_instance_send); #if MICROPY_PY_ASYNC_AWAIT -STATIC mp_obj_t gen_instance_await(mp_obj_t self_in) { - mp_obj_gen_instance_t *self = MP_OBJ_TO_PTR(self_in); - if (!self->coroutine_generator) { - // Pretend like a generator does not have this coroutine behavior. - // Pay no attention to the dir() behind the curtain - mp_raise_msg_varg(&mp_type_AttributeError, MP_ERROR_TEXT("type object '%q' has no attribute '%q'"), - MP_QSTR_generator, MP_QSTR___await__); - } - // You can directly call send on a coroutine generator or you can __await__ then send on the return of that. - return self; +STATIC mp_obj_t coro_instance_await(mp_obj_t self_in) { + return self_in; } -STATIC MP_DEFINE_CONST_FUN_OBJ_1(gen_instance_await_obj, gen_instance_await); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(coro_instance_await_obj, coro_instance_await); #endif -STATIC mp_obj_t gen_instance_close(mp_obj_t self_in); STATIC mp_obj_t gen_instance_throw(size_t n_args, const mp_obj_t *args) { // The signature of this function is: throw(type[, value[, traceback]]) // CPython will pass all given arguments through the call chain and process them @@ -408,9 +412,6 @@ STATIC const mp_rom_map_elem_t gen_instance_locals_dict_table[] = { #if MICROPY_PY_GENERATOR_PEND_THROW { MP_ROM_QSTR(MP_QSTR_pend_throw), MP_ROM_PTR(&gen_instance_pend_throw_obj) }, #endif - #if MICROPY_PY_ASYNC_AWAIT - { MP_ROM_QSTR(MP_QSTR___await__), MP_ROM_PTR(&gen_instance_await_obj) }, - #endif }; STATIC MP_DEFINE_CONST_DICT(gen_instance_locals_dict, gen_instance_locals_dict_table); @@ -427,3 +428,35 @@ const mp_obj_type_t mp_type_gen_instance = { .iternext = gen_instance_iternext, ), }; + +#if MICROPY_PY_ASYNC_AWAIT +// CIRCUITPY +// coroutine instance locals dict and type +// same as generator, but with addition of __await()__. +STATIC const mp_rom_map_elem_t coro_instance_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_close), MP_ROM_PTR(&gen_instance_close_obj) }, + { MP_ROM_QSTR(MP_QSTR_send), MP_ROM_PTR(&gen_instance_send_obj) }, + { MP_ROM_QSTR(MP_QSTR_throw), MP_ROM_PTR(&gen_instance_throw_obj) }, + #if MICROPY_PY_GENERATOR_PEND_THROW + { MP_ROM_QSTR(MP_QSTR_pend_throw), MP_ROM_PTR(&gen_instance_pend_throw_obj) }, + #endif + #if MICROPY_PY_ASYNC_AWAIT + { MP_ROM_QSTR(MP_QSTR___await__), MP_ROM_PTR(&coro_instance_await_obj) }, + #endif +}; + +STATIC MP_DEFINE_CONST_DICT(coro_instance_locals_dict, coro_instance_locals_dict_table); + +const mp_obj_type_t mp_type_coro_instance = { + { &mp_type_type }, + .flags = MP_TYPE_FLAG_EXTENDED, + .name = MP_QSTR_coroutine, + .print = coro_instance_print, + .locals_dict = (mp_obj_dict_t *)&coro_instance_locals_dict, + MP_TYPE_EXTENDED_FIELDS( + .unary_op = mp_generic_unary_op, + .getiter = mp_identity_getiter, + .iternext = gen_instance_iternext, + ), +}; +#endif diff --git a/py/runtime.c b/py/runtime.c index da7343d0c6..aeb9a7954b 100644 --- a/py/runtime.c +++ b/py/runtime.c @@ -1415,7 +1415,8 @@ mp_vm_return_kind_t mp_resume(mp_obj_t self_in, mp_obj_t send_value, mp_obj_t th assert((send_value != MP_OBJ_NULL) ^ (throw_value != MP_OBJ_NULL)); const mp_obj_type_t *type = mp_obj_get_type(self_in); - if (type == &mp_type_gen_instance) { + // CIRCUITPY distinguishes generators and coroutines. + if (type == &mp_type_gen_instance || type == &mp_type_coro_instance) { return mp_obj_gen_resume(self_in, send_value, throw_value, ret_val); } diff --git a/tests/extmod/uasyncio_threadsafeflag.py b/tests/extmod/uasyncio_threadsafeflag.py new file mode 100644 index 0000000000..4e002a3d2a --- /dev/null +++ b/tests/extmod/uasyncio_threadsafeflag.py @@ -0,0 +1,79 @@ +# Test Event class + +try: + import uasyncio as asyncio +except ImportError: + print("SKIP") + raise SystemExit + + +import micropython + +try: + micropython.schedule +except AttributeError: + print("SKIP") + raise SystemExit + + +try: + # Unix port can't select/poll on user-defined types. + import uselect as select + + poller = select.poll() + poller.register(asyncio.ThreadSafeFlag()) +except TypeError: + print("SKIP") + raise SystemExit + + +async def task(id, flag): + print("task", id) + await flag.wait() + print("task", id, "done") + + +def set_from_schedule(flag): + print("schedule") + flag.set() + print("schedule done") + + +async def main(): + flag = asyncio.ThreadSafeFlag() + + # Set the flag from within the loop. + t = asyncio.create_task(task(1, flag)) + print("yield") + await asyncio.sleep(0) + print("set event") + flag.set() + print("yield") + await asyncio.sleep(0) + print("wait task") + await t + + # Set the flag from scheduler context. + print("----") + t = asyncio.create_task(task(2, flag)) + print("yield") + await asyncio.sleep(0) + print("set event") + micropython.schedule(set_from_schedule, flag) + print("yield") + await asyncio.sleep(0) + print("wait task") + await t + + # Flag already set. + print("----") + print("set event") + flag.set() + t = asyncio.create_task(task(3, flag)) + print("yield") + await asyncio.sleep(0) + print("wait task") + await t + + +asyncio.run(main()) diff --git a/tests/extmod/uasyncio_threadsafeflag.py.exp b/tests/extmod/uasyncio_threadsafeflag.py.exp new file mode 100644 index 0000000000..aef4e479ba --- /dev/null +++ b/tests/extmod/uasyncio_threadsafeflag.py.exp @@ -0,0 +1,21 @@ +yield +task 1 +set event +yield +wait task +task 1 done +---- +yield +task 2 +set event +yield +schedule +schedule done +wait task +task 2 done +---- +set event +yield +task 3 +task 3 done +wait task diff --git a/tests/extmod/websocket_basic.py.exp b/tests/extmod/websocket_basic.py.exp deleted file mode 100644 index 2d7657b535..0000000000 --- a/tests/extmod/websocket_basic.py.exp +++ /dev/null @@ -1,14 +0,0 @@ -b'ping' -b'ping' -b'\x81\x04pong' -b'pingpingpingpingpingpingpingpingpingpingpingpingpingpingpingpingpingpingpingpingpingpingpingpingpingpingpingpingpingpingpingping' -b'\x81~\x00\x80pongpongpongpongpongpongpongpongpongpongpongpongpongpongpongpongpongpongpongpongpongpongpongpongpongpongpongpongpongpongpongpong' -b'\x00\x00\x00\x00' -b'' -b'\x81\x02\x88\x00' -b'ping' -b'pong' -0 -1 -2 -ioctl: EINVAL: True