webassembly/asyncio: Add simple asyncio module with create_task, sleep.

Signed-off-by: Damien George <damien@micropython.org>
pull/14193/head
Damien George 2024-03-27 14:40:21 +11:00
rodzic 5114f2c1ea
commit 7ebf3afd3e
8 zmienionych plików z 148 dodań i 0 usunięć

Wyświetl plik

@ -0,0 +1,24 @@
# Minimal asyncio interface that uses JavaScript for scheduling.
# Only implements a very small subset of the asyncio API.
import jsffi
class Task:
def __init__(self, coro):
self._coro = coro
def done(self):
return self._coro is None
async def sleep(s):
await jsffi.async_timeout(s)
def create_task(coro):
if not hasattr(coro, "send"):
raise TypeError("coroutine expected")
t = Task(coro)
jsffi.create_task(t)
return t

Wyświetl plik

@ -61,12 +61,47 @@ static mp_obj_t mp_jsffi_to_js(mp_obj_t arg) {
}
static MP_DEFINE_CONST_FUN_OBJ_1(mp_jsffi_to_js_obj, mp_jsffi_to_js);
// *FORMAT-OFF*
EM_JS(void, create_task, (uint32_t * out), {
const task = proxy_convert_mp_to_js_obj_jsside(out);
queueMicrotask(() =>
Promise.resolve(task._coro).then(() => {
task._coro = null;
})
);
});
// *FORMAT-ON*
static mp_obj_t mp_jsffi_create_task(mp_obj_t arg) {
uint32_t out[3];
proxy_convert_mp_to_js_obj_cside(arg, out);
create_task(out);
return mp_const_none;
}
static MP_DEFINE_CONST_FUN_OBJ_1(mp_jsffi_create_task_obj, mp_jsffi_create_task);
// *FORMAT-OFF*
EM_JS(void, promise_with_timeout_ms, (double ms, uint32_t * out), {
const ret = new Promise((resolve) => setTimeout(resolve, ms));
proxy_convert_js_to_mp_obj_jsside(ret, out);
});
// *FORMAT-ON*
static mp_obj_t mp_jsffi_async_timeout(mp_obj_t arg) {
uint32_t out[3];
promise_with_timeout_ms(mp_obj_get_float_to_d(arg) * 1000.0, out);
return proxy_convert_js_to_mp_obj_cside(out);
}
static MP_DEFINE_CONST_FUN_OBJ_1(mp_jsffi_async_timeout_obj, mp_jsffi_async_timeout);
static const mp_rom_map_elem_t mp_module_jsffi_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_jsffi) },
{ MP_ROM_QSTR(MP_QSTR_JsProxy), MP_ROM_PTR(&mp_type_jsproxy) },
{ MP_ROM_QSTR(MP_QSTR_create_proxy), MP_ROM_PTR(&mp_jsffi_create_proxy_obj) },
{ MP_ROM_QSTR(MP_QSTR_to_js), MP_ROM_PTR(&mp_jsffi_to_js_obj) },
{ MP_ROM_QSTR(MP_QSTR_create_task), MP_ROM_PTR(&mp_jsffi_create_task_obj) },
{ MP_ROM_QSTR(MP_QSTR_async_timeout), MP_ROM_PTR(&mp_jsffi_async_timeout_obj) },
};
static MP_DEFINE_CONST_DICT(mp_module_jsffi_globals, mp_module_jsffi_globals_table);

Wyświetl plik

@ -1,3 +1,5 @@
module("asyncio.py", base_path="$(PORT_DIR)")
require("abc")
require("base64")
require("collections")

Wyświetl plik

@ -0,0 +1,41 @@
// Test asyncio.create_task().
const mp = await (await import(process.argv[2])).loadMicroPython();
globalThis.p0 = new Promise((resolve, reject) => {
resolve(123);
});
globalThis.p1 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log("setTimeout resolved");
resolve(456);
}, 500);
});
mp.runPython(`
import js
import asyncio
async def task(id, pp):
print("task start", id)
print("task await", id, await pp)
print("task await", id, await pp)
print("task end", id)
print("start")
t1 = asyncio.create_task(task(1, js.p0))
t2 = asyncio.create_task(task(2, js.p1))
print("t1", t1.done(), t2.done())
print("end")
`);
await globalThis.p1;
await globalThis.p1;
await globalThis.p1;
await globalThis.p1;
mp.runPython(`
print("restart")
print("t1", t1.done(), t2.done())
`);

Wyświetl plik

@ -0,0 +1,14 @@
start
t1 False False
end
task start 1
task start 2
task await 1 123
task await 1 123
task end 1
setTimeout resolved
task await 2 456
task await 2 456
task end 2
restart
t1 True True

Wyświetl plik

@ -0,0 +1,25 @@
// Test asyncio.sleep().
const mp = await (await import(process.argv[2])).loadMicroPython();
await mp.runPythonAsync(`
import time
import asyncio
print("main start")
t0 = time.time()
await asyncio.sleep(0.25)
dt = time.time() - t0
print(0.2 <= dt <= 0.3)
async def task():
print("task start")
t0 = time.time()
await asyncio.sleep(0.25)
dt = time.time() - t0
print(0.2 <= dt <= 0.3)
print("task end")
asyncio.create_task(task())
print("main end")
`);

Wyświetl plik

@ -0,0 +1,6 @@
main start
True
main end
task start
True
task end

Wyświetl plik

@ -681,6 +681,7 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1):
elif args.target == "webassembly":
skip_tests.add("basics/string_format_modulo.py") # can't print nulls to stdout
skip_tests.add("basics/string_strip.py") # can't print nulls to stdout
skip_tests.update(t for t in tests if t.startswith("extmod/asyncio_"))
skip_tests.add("extmod/binascii_a2b_base64.py")
skip_tests.add("extmod/re_stack_overflow.py")
skip_tests.add("extmod/time_res.py")