kopia lustrzana https://github.com/peterhinch/micropython-samples
add global exception handling
Support global exception handling to uasyncio according to CPython error handling: https://docs.python.org/3/library/asyncio-eventloop.html#error-handling-api Adds the Loop methods (also changing all loop methods to be staticmethos or classmethods since there is only one loop) and a default exception handler. This is especially interesting since this new version of uasyncio doesn't throw exceptions to the caller of "loop.run_forever()" and therefore exceptions in other tasks are only printed to repl, where the user might never see it since the device will be deployed without logging the repl output. With a global exception handling a user can catch those exceptions and send them by mqtt or log them to a file on the device. The implementation preallocates a context dictionary so in case of an exception there shouldn't be any RAM allocations. The used approach is compatible to CPython except for 2 problems: 1) There is no way to log the Exception traceback because "sys.print_exception" only prints the traceback to the repl. So there is no way to actually log the traceback, which would be very helpful. Hopefully this can be implemented. I understand that this might cause RAM allocation but a user might decide to use it anyway in a custom exception handler because it makes debugging a lot easier if you know in what file and line the error occured. 2) In CPython the exception handler is called once the task is finished which created the task that threw an uncaught exception, whereas in UPy the exception handler is called immediately when the exception is thrown. This makes a difference in the following testcase but is generally just a minor difference that shouldn't cause any abnormal behaviour. ``` async def test_catch_uncaught_exception(): # can't work with a local return value because the exception handler runs after # the current coroutine is finished in CPython. Works in UPy though. res = False async def fail(): raise TypeError("uncaught exception") def handle_exception(loop, context): # context["message"] will always be there; but context["exception"] may not print(context) print(context["message"]) print(context["exception"]) msg = context.get("exception", context["message"]) if mp: print("Caught: {}{}".format(type(context["exception"]), msg)) else: print("Caught: {}".format({msg})) nonlocal res print("res is", res) res = True print("done") t = asyncio.create_task(fail()) loop = asyncio.get_event_loop() loop.set_exception_handler(handle_exception) await asyncio.sleep(0.1) await asyncio.sleep(0.1) print("coro done") return res ``` I'd be glad to discuss this PR.pull/13/head
rodzic
4d2a52d109
commit
b4395cbe27
|
@ -7,6 +7,7 @@ from time import ticks_ms as ticks, ticks_diff, ticks_add
|
|||
import sys, select
|
||||
|
||||
type_genf = type((lambda: (yield))) # Type of a generator function upy iss #3241
|
||||
_exc_message = 'Task exception was never retrieved'
|
||||
|
||||
################################################################################
|
||||
# Primitive class embodies methods common to most synchronisation primitives
|
||||
|
@ -390,6 +391,20 @@ async def start_server(cb, host, port, backlog=5):
|
|||
################################################################################
|
||||
# Main run loop
|
||||
|
||||
_context = {"message": _exc_message,
|
||||
"exception": None,
|
||||
"future": None}
|
||||
|
||||
|
||||
def _exc_handler(loop, context):
|
||||
print(context["message"])
|
||||
print("future:",context["future"],"coro=",context["future"].coro)
|
||||
# missing traceback
|
||||
sys.print_exception(context["exception"])
|
||||
|
||||
# set default exception handler
|
||||
exc_handler = _exc_handler
|
||||
|
||||
# Queue of Task instances
|
||||
_queue = TQueue()
|
||||
|
||||
|
@ -443,11 +458,12 @@ def run_until_complete(main_task=None):
|
|||
waiting = True
|
||||
t.waiting = None # Free waiting queue head
|
||||
_io_queue.remove(t) # Remove task from the IO queue (if it's on it)
|
||||
t.coro = None # Indicate task is done
|
||||
# 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)
|
||||
_context["exception"] = er
|
||||
_context["future"] = t
|
||||
Loop.call_exception_handler(_context)
|
||||
t.coro = None # Indicate task is done
|
||||
|
||||
StreamReader = Stream
|
||||
StreamWriter = Stream # CPython 3.8 compatibility
|
||||
|
@ -469,17 +485,34 @@ Stream.awrite = stream_awrite
|
|||
Stream.awritestr = stream_awrite # TODO explicitly convert to bytes?
|
||||
|
||||
class Loop:
|
||||
def create_task(self, coro):
|
||||
@staticmethod
|
||||
def create_task(coro):
|
||||
return create_task(coro)
|
||||
def run_forever(self):
|
||||
@staticmethod
|
||||
def run_forever():
|
||||
run_until_complete()
|
||||
# TODO should keep running until .stop() is called, even if there're no tasks left
|
||||
def run_until_complete(self, aw):
|
||||
@staticmethod
|
||||
def run_until_complete(aw):
|
||||
return run_until_complete(_promote_to_task(aw))
|
||||
@staticmethod
|
||||
def close(self):
|
||||
pass
|
||||
@staticmethod
|
||||
def set_exception_handler(handler):
|
||||
global exc_handler
|
||||
exc_handler = handler
|
||||
@staticmethod
|
||||
def get_exception_handler():
|
||||
return exc_handler
|
||||
@classmethod
|
||||
def default_exception_handler(cls,context):
|
||||
exc_handler(cls,context)
|
||||
@classmethod
|
||||
def call_exception_handler(cls, context):
|
||||
exc_handler(cls,context)
|
||||
|
||||
def get_event_loop(runq_len=0, waitq_len=0):
|
||||
return Loop()
|
||||
|
||||
version = (3, 0, 1)
|
||||
version = (3, 0, 2)
|
||||
|
|
Ładowanie…
Reference in New Issue