circuitpython/tests/extmod/uasyncio_set_exception_handler.py
Damien George ca40eb0fda extmod/uasyncio: Delay calling Loop.call_exception_handler by 1 loop.
When a tasks raises an exception which is uncaught, and no other task
await's on that task, then an error message is printed (or a user function
called) via a call to Loop.call_exception_handler.  In CPython this call is
made when the Task object is freed (eg via reference counting) because it's
at that point that it is known that the exception that was raised will
never be handled.

MicroPython does not have reference counting and the current behaviour is
to deal with uncaught exceptions as early as possible, ie as soon as they
terminate the task.  But this can be undesirable because in certain cases
a task can start and raise an exception immediately (before any await is
executed in that task's coro) and before any other task gets a chance to
await on it to catch the exception.

This commit changes the behaviour so that tasks which end due to an
uncaught exception are scheduled one more time for execution, and if they
are not await'ed on by the next scheduling loop, then the exception handler
is called (eg the exception is printed out).

Signed-off-by: Damien George <damien@micropython.org>
2020-12-02 12:07:06 +11:00

57 lines
1.3 KiB
Python

# Test that tasks return their value correctly to the caller
try:
import uasyncio as asyncio
except ImportError:
try:
import asyncio
except ImportError:
print("SKIP")
raise SystemExit
def custom_handler(loop, context):
print("custom_handler", repr(context["exception"]))
async def task(i):
# Raise with 2 args so exception prints the same in uPy and CPython
raise ValueError(i, i + 1)
async def main():
loop = asyncio.get_event_loop()
# Check default exception handler, should be None
print(loop.get_exception_handler())
# Set exception handler and test it was set
loop.set_exception_handler(custom_handler)
print(loop.get_exception_handler() == custom_handler)
# Create a task that raises and uses the custom exception handler
asyncio.create_task(task(0))
print("sleep")
for _ in range(2):
await asyncio.sleep(0)
# Create 2 tasks to test order of printing exception
asyncio.create_task(task(1))
asyncio.create_task(task(2))
print("sleep")
for _ in range(2):
await asyncio.sleep(0)
# Create a task, let it run, then await it (no exception should be printed)
t = asyncio.create_task(task(3))
await asyncio.sleep(0)
try:
await t
except ValueError as er:
print(repr(er))
print("done")
asyncio.run(main())