Skip to content

Commit e9c898c

Browse files
committed
webassembly/asyncio: Support top-level await of asyncio Task and Event.
This change allows doing a top-level await on an asyncio primitive like Task and Event. This feature enables a better interaction and synchronisation between JavaScript and Python, because `api.runPythonAsync` can now be used (called from JavaScript) to await on the completion of asyncio primitives. Signed-off-by: Damien George <damien@micropython.org>
1 parent a053e63 commit e9c898c

File tree

6 files changed

+83
-22
lines changed

6 files changed

+83
-22
lines changed

ports/webassembly/asyncio/core.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,6 @@ def __next__(self):
5050
# Pause task execution for the given time (integer in milliseconds, uPy extension)
5151
# Use a SingletonGenerator to do it without allocating on the heap
5252
def sleep_ms(t, sgen=SingletonGenerator()):
53-
if cur_task is None:
54-
# Support top-level asyncio.sleep, via a JavaScript Promise.
55-
return jsffi.async_timeout_ms(t)
5653
assert sgen.state is None
5754
sgen.state = ticks_add(ticks(), max(0, t))
5855
return sgen
@@ -69,6 +66,18 @@ def sleep(t):
6966
asyncio_timer = None
7067

7168

69+
class TopLevelCoro:
70+
@staticmethod
71+
def set(resolve, reject):
72+
TopLevelCoro.resolve = resolve
73+
TopLevelCoro.reject = reject
74+
_schedule_run_iter(0)
75+
76+
@staticmethod
77+
def send(value):
78+
TopLevelCoro.resolve()
79+
80+
7281
class ThenableEvent:
7382
def __init__(self, thenable):
7483
self.result = None # Result of the thenable
@@ -122,12 +131,12 @@ def _run_iter():
122131
dt = max(0, ticks_diff(t.ph_key, ticks()))
123132
else:
124133
# No tasks can be woken so finished running
125-
cur_task = None
134+
cur_task = _top_level_task
126135
return
127136

128137
if dt > 0:
129138
# schedule to call again later
130-
cur_task = None
139+
cur_task = _top_level_task
131140
_schedule_run_iter(dt)
132141
return
133142

@@ -198,11 +207,14 @@ def create_task(coro):
198207
return t
199208

200209

210+
# Task used to suspend and resume top-level await.
211+
_top_level_task = Task(TopLevelCoro, globals())
212+
201213
################################################################################
202214
# Event loop wrapper
203215

204216

205-
cur_task = None
217+
cur_task = _top_level_task
206218

207219

208220
class Loop:

ports/webassembly/modjsffi.c

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -62,20 +62,6 @@ static mp_obj_t mp_jsffi_to_js(mp_obj_t arg) {
6262
}
6363
static MP_DEFINE_CONST_FUN_OBJ_1(mp_jsffi_to_js_obj, mp_jsffi_to_js);
6464

65-
// *FORMAT-OFF*
66-
EM_JS(void, promise_with_timeout_ms, (double ms, uint32_t * out), {
67-
const ret = new Promise((resolve) => setTimeout(resolve, ms));
68-
proxy_convert_js_to_mp_obj_jsside(ret, out);
69-
});
70-
// *FORMAT-ON*
71-
72-
static mp_obj_t mp_jsffi_async_timeout_ms(mp_obj_t arg) {
73-
uint32_t out[PVN];
74-
promise_with_timeout_ms(mp_obj_get_float_to_d(arg), out);
75-
return proxy_convert_js_to_mp_obj_cside(out);
76-
}
77-
static MP_DEFINE_CONST_FUN_OBJ_1(mp_jsffi_async_timeout_ms_obj, mp_jsffi_async_timeout_ms);
78-
7965
// *FORMAT-OFF*
8066
EM_JS(void, js_get_proxy_js_ref_info, (uint32_t * out), {
8167
let used = 0;
@@ -121,7 +107,6 @@ static const mp_rom_map_elem_t mp_module_jsffi_globals_table[] = {
121107
{ MP_ROM_QSTR(MP_QSTR_JsException), MP_ROM_PTR(&mp_type_JsException) },
122108
{ MP_ROM_QSTR(MP_QSTR_create_proxy), MP_ROM_PTR(&mp_jsffi_create_proxy_obj) },
123109
{ MP_ROM_QSTR(MP_QSTR_to_js), MP_ROM_PTR(&mp_jsffi_to_js_obj) },
124-
{ MP_ROM_QSTR(MP_QSTR_async_timeout_ms), MP_ROM_PTR(&mp_jsffi_async_timeout_ms_obj) },
125110
{ MP_ROM_QSTR(MP_QSTR_mem_info), MP_ROM_PTR(&mp_jsffi_mem_info_obj) },
126111
};
127112
static MP_DEFINE_CONST_DICT(mp_module_jsffi_globals, mp_module_jsffi_globals_table);

ports/webassembly/proxy_c.c

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,12 @@ EM_JS(void, js_then_continue, (int jsref, uint32_t * py_resume, uint32_t * resol
470470
});
471471
// *FORMAT-ON*
472472

473+
EM_JS(void, create_promise, (uint32_t * out_set, uint32_t * out_promise), {
474+
const out_set_js = proxy_convert_mp_to_js_obj_jsside(out_set);
475+
const promise = new Promise(out_set_js);
476+
proxy_convert_js_to_mp_obj_jsside(promise, out_promise);
477+
});
478+
473479
static mp_obj_t proxy_resume_execute(mp_obj_t self_in, mp_obj_t send_value, mp_obj_t throw_value, mp_obj_t resolve, mp_obj_t reject) {
474480
if (throw_value != MP_OBJ_NULL && throw_value != mp_const_none) {
475481
if (send_value == mp_const_none) {
@@ -483,6 +489,9 @@ static mp_obj_t proxy_resume_execute(mp_obj_t self_in, mp_obj_t send_value, mp_o
483489
}
484490
} else {
485491
throw_value = MP_OBJ_NULL;
492+
if (send_value == mp_const_undefined) {
493+
send_value = mp_const_none;
494+
}
486495
}
487496

488497
mp_obj_t ret_value;
@@ -496,7 +505,29 @@ static mp_obj_t proxy_resume_execute(mp_obj_t self_in, mp_obj_t send_value, mp_o
496505
js_then_resolve(out_ret_value, out_resolve);
497506
return mp_const_none;
498507
} else if (ret_kind == MP_VM_RETURN_YIELD) {
499-
// ret_value should be a JS thenable
508+
// If ret_value is None then there has been a top-level await of an asyncio primitive.
509+
// Otherwise, ret_value should be a JS thenable.
510+
511+
if (ret_value == mp_const_none) {
512+
// Waiting on an asyncio primitive to complete, eg a Task or Event.
513+
//
514+
// Completion of this primitive will occur when the asyncio.core._top_level_task
515+
// Task is made runable and its coroutine's send() method is called. Need to
516+
// construct a Promise that resolves when that send() method is called, because
517+
// that will resume the top-level await from the JavaScript side.
518+
//
519+
// This is accomplished via the asyncio.core.TopLevelCoro class and its methods.
520+
mp_obj_t asyncio = mp_import_name(MP_QSTR_asyncio_dot_core, mp_const_none, MP_OBJ_NEW_SMALL_INT(0));
521+
mp_obj_t asyncio_core = mp_load_attr(asyncio, MP_QSTR_core);
522+
mp_obj_t top_level_coro = mp_load_attr(asyncio_core, MP_QSTR_TopLevelCoro);
523+
mp_obj_t top_level_coro_set = mp_load_attr(top_level_coro, MP_QSTR_set);
524+
uint32_t out_set[PVN];
525+
proxy_convert_mp_to_js_obj_cside(top_level_coro_set, out_set);
526+
uint32_t out_promise[PVN];
527+
create_promise(out_set, out_promise);
528+
ret_value = proxy_convert_js_to_mp_obj_cside(out_promise);
529+
}
530+
500531
mp_obj_t py_resume = mp_obj_new_bound_meth(MP_OBJ_FROM_PTR(&resume_obj), self_in);
501532
int ref = mp_obj_jsproxy_get_ref(ret_value);
502533
uint32_t out_py_resume[PVN];

ports/webassembly/qstrdefsport.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
// qstrs specific to this port
22
// *FORMAT-OFF*
33
Q(/lib)
4+
Q(asyncio.core)
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Test top-level await on asyncio primitives: Task, Event.
2+
3+
const mp = await (await import(process.argv[2])).loadMicroPython();
4+
5+
await mp.runPythonAsync(`
6+
import asyncio
7+
8+
async def task(event):
9+
print("task set event")
10+
event.set()
11+
print("task sleep")
12+
await asyncio.sleep(0.1)
13+
print("task end")
14+
15+
event = asyncio.Event()
16+
t = asyncio.create_task(task(event))
17+
18+
print("top-level wait event")
19+
await event.wait()
20+
print("top-level wait task")
21+
await t
22+
print("top-level end")
23+
`);
24+
25+
console.log("finished");
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
top-level wait event
2+
task set event
3+
task sleep
4+
top-level wait task
5+
task end
6+
top-level end
7+
finished

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy