summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDamien George <damien@micropython.org>2025-09-28 22:16:36 +1000
committerDamien George <damien@micropython.org>2025-09-30 17:39:15 +1000
commit094000d418e9f7393b3d34814c3e43e6386145a7 (patch)
tree95f26373168aff12f9efa36333ec5e3f184f611a
parent68ca22bbc882cda735d10f921769beff8aa60d77 (diff)
webassembly/objjsproxy: Fix logic that determines if asyncio is active.
`cur_task` can never be `None` in the webassembly port, so test it for the top-level task to see if an asyncio Task is active or not. This fixes a bug where await'ing on a JavaScript awaitable that ends up raising an error would not be caught on the Python side. The fix here makes sure it is caught by Python, as tested by the new test. Signed-off-by: Damien George <damien@micropython.org>
-rw-r--r--ports/webassembly/asyncio/core.py3
-rw-r--r--ports/webassembly/objjsproxy.c3
-rw-r--r--tests/ports/webassembly/asyncio_top_level_await.mjs32
-rw-r--r--tests/ports/webassembly/asyncio_top_level_await.mjs.exp6
4 files changed, 41 insertions, 3 deletions
diff --git a/ports/webassembly/asyncio/core.py b/ports/webassembly/asyncio/core.py
index 47846fc25..1d0124a6a 100644
--- a/ports/webassembly/asyncio/core.py
+++ b/ports/webassembly/asyncio/core.py
@@ -243,8 +243,7 @@ def get_event_loop():
def current_task():
- if cur_task is None:
- raise RuntimeError("no running event loop")
+ assert cur_task is not None
return cur_task
diff --git a/ports/webassembly/objjsproxy.c b/ports/webassembly/objjsproxy.c
index 2d46702ff..93c40d835 100644
--- a/ports/webassembly/objjsproxy.c
+++ b/ports/webassembly/objjsproxy.c
@@ -566,7 +566,8 @@ static mp_obj_t jsproxy_getiter(mp_obj_t self_in, mp_obj_iter_buf_t *iter_buf) {
// decouples the task from the thenable and allows cancelling the task.
if (mp_asyncio_context != MP_OBJ_NULL) {
mp_obj_t cur_task = mp_obj_dict_get(mp_asyncio_context, MP_OBJ_NEW_QSTR(MP_QSTR_cur_task));
- if (cur_task != mp_const_none) {
+ mp_obj_t top_level_task = mp_obj_dict_get(mp_asyncio_context, MP_OBJ_NEW_QSTR(MP_QSTR__top_level_task));
+ if (cur_task != top_level_task) {
mp_obj_t thenable_event_class = mp_obj_dict_get(mp_asyncio_context, MP_OBJ_NEW_QSTR(MP_QSTR_ThenableEvent));
mp_obj_t thenable_event = mp_call_function_1(thenable_event_class, self_in);
mp_obj_t dest[2];
diff --git a/tests/ports/webassembly/asyncio_top_level_await.mjs b/tests/ports/webassembly/asyncio_top_level_await.mjs
index 234b7a6ce..449985b49 100644
--- a/tests/ports/webassembly/asyncio_top_level_await.mjs
+++ b/tests/ports/webassembly/asyncio_top_level_await.mjs
@@ -88,3 +88,35 @@ print("top-level end")
`);
console.log("finished");
+
+/**********************************************************/
+// Top-level await's on a JavaScript function that throws.
+
+console.log("= TEST 4 ==========");
+
+globalThis.jsFail = async () => {
+ console.log("jsFail");
+ throw new Error("jsFail");
+};
+
+await mp.runPythonAsync(`
+import asyncio
+import js
+
+# Test top-level catching from a failed JS await.
+try:
+ await js.jsFail()
+except Exception as er:
+ print("caught exception:", type(er), type(er.args[0]), er.args[1:])
+
+async def main():
+ try:
+ await js.jsFail()
+ except Exception as er:
+ print("caught exception:", type(er), type(er.args[0]), er.args[1:])
+
+# Test top-level waiting on a coro that catches.
+await main()
+`);
+
+console.log("finished");
diff --git a/tests/ports/webassembly/asyncio_top_level_await.mjs.exp b/tests/ports/webassembly/asyncio_top_level_await.mjs.exp
index 66fefd2dc..90c817f3d 100644
--- a/tests/ports/webassembly/asyncio_top_level_await.mjs.exp
+++ b/tests/ports/webassembly/asyncio_top_level_await.mjs.exp
@@ -17,3 +17,9 @@ top-level wait task
task end
top-level end
finished
+= TEST 4 ==========
+jsFail
+caught exception: <class 'JsException'> <class 'JsProxy'> ('Error', 'jsFail')
+jsFail
+caught exception: <class 'JsException'> <class 'JsProxy'> ('Error', 'jsFail')
+finished