Skip to content

Commit 55169e0

Browse files
jimmojepler
authored andcommitted
extmod/uasyncio/task.py: Fix crash when non-awaited task is awaited.
A task that has been sent to the loop's exception handler due to being re-scheduled twice will then subsequently cause a `raise None` if it is subsequently awaited. In the C version of task.py, this causes a segfault. This makes the await succeed (via raising StopIteration instead). Signed-off-by: Jim Mussared <jim.mussared@gmail.com>
1 parent e590d27 commit 55169e0

File tree

4 files changed

+61
-4
lines changed

4 files changed

+61
-4
lines changed

extmod/moduasyncio.c

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -290,8 +290,13 @@ STATIC mp_obj_t task_getiter(mp_obj_t self_in, mp_obj_iter_buf_t *iter_buf) {
290290
STATIC mp_obj_t task_iternext(mp_obj_t self_in) {
291291
mp_obj_task_t *self = MP_OBJ_TO_PTR(self_in);
292292
if (TASK_IS_DONE(self)) {
293-
// Task finished, raise return value to caller so it can continue.
294-
nlr_raise(self->data);
293+
if (self->data == mp_const_none) {
294+
// Task finished but has already been sent to the loop's exception handler.
295+
mp_raise_StopIteration(MP_OBJ_NULL);
296+
} else {
297+
// Task finished, raise return value to caller so it can continue.
298+
nlr_raise(self->data);
299+
}
295300
} else {
296301
// Put calling task on waiting queue.
297302
mp_obj_t cur_task = mp_obj_dict_get(uasyncio_context, MP_OBJ_NEW_QSTR(MP_QSTR_cur_task));

extmod/uasyncio/task.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,8 +141,12 @@ def __await__(self):
141141

142142
def __next__(self):
143143
if not self.state:
144-
# Task finished, raise return value to caller so it can continue.
145-
raise self.data
144+
if self.data is None:
145+
# Task finished but has already been sent to the loop's exception handler.
146+
raise StopIteration
147+
else:
148+
# Task finished, raise return value to caller so it can continue.
149+
raise self.data
146150
else:
147151
# Put calling task on waiting queue.
148152
self.state.push_head(core.cur_task)
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# In MicroPython, a non-awaited task with a pending exception will raise to
2+
# the loop's exception handler the second time it is scheduled. This is
3+
# because without reference counting we have no way to know when the task is
4+
# truly "non awaited" -- i.e. we only know that it wasn't awaited in the time
5+
# it took to be re-scheduled.
6+
7+
# If the task _is_ subsequently awaited, then the await should succeed without
8+
# raising.
9+
10+
try:
11+
import uasyncio as asyncio
12+
except ImportError:
13+
try:
14+
import asyncio
15+
except ImportError:
16+
print("SKIP")
17+
raise SystemExit
18+
19+
20+
def custom_handler(loop, context):
21+
print("exception handler", type(context["exception"]).__name__)
22+
23+
24+
async def main():
25+
loop = asyncio.get_event_loop()
26+
loop.set_exception_handler(custom_handler)
27+
28+
async def task():
29+
print("raise")
30+
raise OSError
31+
32+
print("create")
33+
t = asyncio.create_task(task())
34+
print("sleep 1")
35+
await asyncio.sleep(0)
36+
print("sleep 2")
37+
await asyncio.sleep(0)
38+
print("await")
39+
await t # should not raise.
40+
41+
42+
asyncio.run(main())
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
create
2+
sleep 1
3+
raise
4+
sleep 2
5+
exception handler OSError
6+
await

0 commit comments

Comments
 (0)