From 15f41c2dbf669e1a15ce2afb59eb29f5625d9973 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20K=C3=B6ck?= Date: Mon, 30 Mar 2020 08:41:42 +0200 Subject: [PATCH] extmod/uasyncio: Add global exception handling methods. This commit adds support for global exception handling in uasyncio according to the CPython error handling: https://docs.python.org/3/library/asyncio-eventloop.html#error-handling-api This allows a program to receive exceptions from detached tasks and log them to an appropriate location, instead of them being printed to the REPL. The implementation preallocates a context dictionary so in case of an exception there shouldn't be any RAM allocation. The approach here is compatible with CPython except that in CPython the exception handler is called once the task that threw an uncaught exception is freed, whereas in MicroPython the exception handler is called immediately when the exception is thrown. --- extmod/uasyncio/core.py | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/extmod/uasyncio/core.py b/extmod/uasyncio/core.py index dd0229ee45..e2f64119c5 100644 --- a/extmod/uasyncio/core.py +++ b/extmod/uasyncio/core.py @@ -23,6 +23,10 @@ class TimeoutError(Exception): pass +# Used when calling Loop.call_exception_handler +_exc_context = {"message": "Task exception wasn't retrieved", "exception": None, "future": None} + + ################################################################################ # Sleep functions @@ -199,8 +203,9 @@ def run_until_complete(main_task=None): t.waiting = None # Free waiting queue head # Print out exception for detached tasks if not waiting and not isinstance(er, excs_stop): - print("task raised exception:", t.coro) - sys.print_exception(er) + _exc_context["exception"] = er + _exc_context["future"] = t + Loop.call_exception_handler(_exc_context) # Indicate task is done t.coro = None @@ -222,6 +227,8 @@ _stop_task = None class Loop: + _exc_handler = None + def create_task(coro): return create_task(coro) @@ -244,6 +251,20 @@ class Loop: def close(): pass + def set_exception_handler(handler): + Loop._exc_handler = handler + + def get_exception_handler(): + return Loop._exc_handler + + def default_exception_handler(loop, context): + print(context["message"]) + print("future:", context["future"], "coro=", context["future"].coro) + sys.print_exception(context["exception"]) + + def call_exception_handler(context): + (Loop._exc_handler or Loop.default_exception_handler)(Loop, context) + # The runq_len and waitq_len arguments are for legacy uasyncio compatibility def get_event_loop(runq_len=0, waitq_len=0):