mirror of
https://github.com/micropython/micropython.git
synced 2026-01-06 20:20:14 +01:00
extmod/uasyncio: Fix gather cancelling and handling of exceptions.
The following fixes are made: - cancelling a gather now cancels all sub-tasks of the gather (previously it would only cancel the first) - if any sub-task of a gather raises an exception then the gather finishes (previously it would only finish if the first sub-task raised) Fixes issues #5798, #7807, #7901. Signed-off-by: Damien George <damien@micropython.org>
This commit is contained in:
@@ -27,9 +27,22 @@ async def task(id):
|
||||
return id
|
||||
|
||||
|
||||
async def gather_task():
|
||||
async def task_loop(id):
|
||||
print("task_loop start", id)
|
||||
while True:
|
||||
await asyncio.sleep(0.02)
|
||||
print("task_loop loop", id)
|
||||
|
||||
|
||||
async def task_raise(id):
|
||||
print("task_raise start", id)
|
||||
await asyncio.sleep(0.02)
|
||||
raise ValueError(id)
|
||||
|
||||
|
||||
async def gather_task(t0, t1):
|
||||
print("gather_task")
|
||||
await asyncio.gather(task(1), task(2))
|
||||
await asyncio.gather(t0, t1)
|
||||
print("gather_task2")
|
||||
|
||||
|
||||
@@ -37,19 +50,49 @@ async def main():
|
||||
# Simple gather with return values
|
||||
print(await asyncio.gather(factorial("A", 2), factorial("B", 3), factorial("C", 4)))
|
||||
|
||||
print("====")
|
||||
|
||||
# Test return_exceptions, where one task is cancelled and the other finishes normally
|
||||
tasks = [asyncio.create_task(task(1)), asyncio.create_task(task(2))]
|
||||
tasks[0].cancel()
|
||||
print(await asyncio.gather(*tasks, return_exceptions=True))
|
||||
|
||||
# Cancel a multi gather
|
||||
# TODO doesn't work, Task should not forward cancellation from gather to sub-task
|
||||
# but rather CancelledError should cancel the gather directly, which will then cancel
|
||||
# all sub-tasks explicitly
|
||||
# t = asyncio.create_task(gather_task())
|
||||
# await asyncio.sleep(0.01)
|
||||
# t.cancel()
|
||||
# await asyncio.sleep(0.01)
|
||||
print("====")
|
||||
|
||||
# Test return_exceptions, where one task raises an exception and the other finishes normally.
|
||||
tasks = [asyncio.create_task(task(1)), asyncio.create_task(task_raise(2))]
|
||||
print(await asyncio.gather(*tasks, return_exceptions=True))
|
||||
|
||||
print("====")
|
||||
|
||||
# Test case where one task raises an exception and other task keeps running.
|
||||
tasks = [asyncio.create_task(task_loop(1)), asyncio.create_task(task_raise(2))]
|
||||
try:
|
||||
await asyncio.gather(*tasks)
|
||||
except ValueError as er:
|
||||
print(repr(er))
|
||||
print(tasks[0].done(), tasks[1].done())
|
||||
for t in tasks:
|
||||
t.cancel()
|
||||
await asyncio.sleep(0.04)
|
||||
|
||||
print("====")
|
||||
|
||||
# Test case where both tasks raise an exception.
|
||||
tasks = [asyncio.create_task(task_raise(1)), asyncio.create_task(task_raise(2))]
|
||||
try:
|
||||
await asyncio.gather(*tasks)
|
||||
except ValueError as er:
|
||||
print(repr(er))
|
||||
print(tasks[0].done(), tasks[1].done())
|
||||
|
||||
print("====")
|
||||
|
||||
# Cancel a multi gather.
|
||||
t = asyncio.create_task(gather_task(task(1), task(2)))
|
||||
await asyncio.sleep(0.01)
|
||||
t.cancel()
|
||||
await asyncio.sleep(0.04)
|
||||
|
||||
|
||||
asyncio.run(main())
|
||||
|
||||
@@ -8,6 +8,27 @@ Task B: factorial(3) = 6
|
||||
Task C: Compute factorial(4)...
|
||||
Task C: factorial(4) = 24
|
||||
[2, 6, 24]
|
||||
====
|
||||
start 2
|
||||
end 2
|
||||
[CancelledError(), 2]
|
||||
====
|
||||
start 1
|
||||
task_raise start 2
|
||||
end 1
|
||||
[1, ValueError(2,)]
|
||||
====
|
||||
task_loop start 1
|
||||
task_raise start 2
|
||||
task_loop loop 1
|
||||
ValueError(2,)
|
||||
False True
|
||||
====
|
||||
task_raise start 1
|
||||
task_raise start 2
|
||||
ValueError(1,)
|
||||
True True
|
||||
====
|
||||
gather_task
|
||||
start 1
|
||||
start 2
|
||||
|
||||
53
tests/extmod/uasyncio_gather_notimpl.py
Normal file
53
tests/extmod/uasyncio_gather_notimpl.py
Normal file
@@ -0,0 +1,53 @@
|
||||
# Test uasyncio.gather() function, features that are not implemented.
|
||||
|
||||
try:
|
||||
import uasyncio as asyncio
|
||||
except ImportError:
|
||||
try:
|
||||
import asyncio
|
||||
except ImportError:
|
||||
print("SKIP")
|
||||
raise SystemExit
|
||||
|
||||
|
||||
def custom_handler(loop, context):
|
||||
print(repr(context["exception"]))
|
||||
|
||||
|
||||
async def task(id):
|
||||
print("task start", id)
|
||||
await asyncio.sleep(0.01)
|
||||
print("task end", id)
|
||||
return id
|
||||
|
||||
|
||||
async def gather_task(t0, t1):
|
||||
print("gather_task start")
|
||||
await asyncio.gather(t0, t1)
|
||||
print("gather_task end")
|
||||
|
||||
|
||||
async def main():
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.set_exception_handler(custom_handler)
|
||||
|
||||
# Test case where can't wait on a task being gathered.
|
||||
tasks = [asyncio.create_task(task(1)), asyncio.create_task(task(2))]
|
||||
gt = asyncio.create_task(gather_task(tasks[0], tasks[1]))
|
||||
await asyncio.sleep(0) # let the gather start
|
||||
try:
|
||||
await tasks[0] # can't await because this task is part of the gather
|
||||
except RuntimeError as er:
|
||||
print(repr(er))
|
||||
await gt
|
||||
|
||||
print("====")
|
||||
|
||||
# Test case where can't gather on a task being waited.
|
||||
tasks = [asyncio.create_task(task(1)), asyncio.create_task(task(2))]
|
||||
asyncio.create_task(gather_task(tasks[0], tasks[1]))
|
||||
await tasks[0] # wait on this task before the gather starts
|
||||
await tasks[1]
|
||||
|
||||
|
||||
asyncio.run(main())
|
||||
14
tests/extmod/uasyncio_gather_notimpl.py.exp
Normal file
14
tests/extmod/uasyncio_gather_notimpl.py.exp
Normal file
@@ -0,0 +1,14 @@
|
||||
task start 1
|
||||
task start 2
|
||||
gather_task start
|
||||
RuntimeError("can't wait",)
|
||||
task end 1
|
||||
task end 2
|
||||
gather_task end
|
||||
====
|
||||
task start 1
|
||||
task start 2
|
||||
gather_task start
|
||||
RuntimeError("can't gather",)
|
||||
task end 1
|
||||
task end 2
|
||||
Reference in New Issue
Block a user