webassembly: Add JavaScript-based asyncio support.
This commit adds a significant portion of the existing MicroPython asyncio module to the webassembly port, using parts of the existing asyncio code and some custom JavaScript parts. The key difference to the standard asyncio is that this version uses the JavaScript runtime to do the actual scheduling and waiting on events, eg Promise fulfillment, timeouts, fetching URLs. This implementation does not include asyncio.run(). Instead one just uses asyncio.create_task(..) to start tasks and then returns to the JavaScript. Then JavaScript will run the tasks. The implementation here tries to reuse as much existing asyncio code as possible, and gets all the semantics correct for things like cancellation and asyncio.wait_for. An alternative approach would reimplement Task, Event, etc using JavaScript Promise's. That approach is very difficult to get right when trying to implement cancellation (because it's not possible to cancel a JavaScript Promise). Signed-off-by: Damien George <damien@micropython.org>
This commit is contained in:
44
tests/ports/webassembly/asyncio_create_task.mjs
Normal file
44
tests/ports/webassembly/asyncio_create_task.mjs
Normal file
@@ -0,0 +1,44 @@
|
||||
// Test asyncio.create_task(), and tasks waiting on a Promise.
|
||||
|
||||
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);
|
||||
}, 200);
|
||||
});
|
||||
|
||||
mp.runPython(`
|
||||
import js
|
||||
import asyncio
|
||||
|
||||
async def task(id, promise):
|
||||
print("task start", id)
|
||||
print("task await", id, await promise)
|
||||
print("task await", id, await promise)
|
||||
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")
|
||||
`);
|
||||
|
||||
// Wait for p1 to fulfill so t2 can continue.
|
||||
await globalThis.p1;
|
||||
|
||||
// Wait a little longer so t2 can complete.
|
||||
await new Promise((resolve, reject) => {
|
||||
setTimeout(resolve, 10);
|
||||
});
|
||||
|
||||
mp.runPython(`
|
||||
print("restart")
|
||||
print("t1", t1.done(), t2.done())
|
||||
`);
|
||||
14
tests/ports/webassembly/asyncio_create_task.mjs.exp
Normal file
14
tests/ports/webassembly/asyncio_create_task.mjs.exp
Normal file
@@ -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
|
||||
25
tests/ports/webassembly/asyncio_sleep.mjs
Normal file
25
tests/ports/webassembly/asyncio_sleep.mjs
Normal file
@@ -0,0 +1,25 @@
|
||||
// Test asyncio.sleep(), both at the top level and within a task.
|
||||
|
||||
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")
|
||||
`);
|
||||
6
tests/ports/webassembly/asyncio_sleep.mjs.exp
Normal file
6
tests/ports/webassembly/asyncio_sleep.mjs.exp
Normal file
@@ -0,0 +1,6 @@
|
||||
main start
|
||||
True
|
||||
main end
|
||||
task start
|
||||
True
|
||||
task end
|
||||
@@ -681,6 +681,17 @@ 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.add("extmod/asyncio_basic2.py")
|
||||
skip_tests.add("extmod/asyncio_cancel_self.py")
|
||||
skip_tests.add("extmod/asyncio_current_task.py")
|
||||
skip_tests.add("extmod/asyncio_exception.py")
|
||||
skip_tests.add("extmod/asyncio_gather_finished_early.py")
|
||||
skip_tests.add("extmod/asyncio_get_event_loop.py")
|
||||
skip_tests.add("extmod/asyncio_heaplock.py")
|
||||
skip_tests.add("extmod/asyncio_loop_stop.py")
|
||||
skip_tests.add("extmod/asyncio_new_event_loop.py")
|
||||
skip_tests.add("extmod/asyncio_threadsafeflag.py")
|
||||
skip_tests.add("extmod/asyncio_wait_for_fwd.py")
|
||||
skip_tests.add("extmod/binascii_a2b_base64.py")
|
||||
skip_tests.add("extmod/re_stack_overflow.py")
|
||||
skip_tests.add("extmod/time_res.py")
|
||||
|
||||
Reference in New Issue
Block a user