summaryrefslogtreecommitdiff
path: root/Lib/asyncio/base_events.py
diff options
context:
space:
mode:
authorYury Selivanov <yselivanov@sprymix.com>2014-02-18 18:02:19 -0500
committerYury Selivanov <yselivanov@sprymix.com>2014-02-18 18:02:19 -0500
commit569efa2e4bf985f27a9f85393e29d3ad8ac73344 (patch)
tree892c07e1a7b02669d13331bd991f3a72078ba03d /Lib/asyncio/base_events.py
parent6acc5e1330239cd721205b310dfddec1eb6425c1 (diff)
downloadcpython-git-569efa2e4bf985f27a9f85393e29d3ad8ac73344.tar.gz
asyncio: New error handling API. Issue #20681.
Diffstat (limited to 'Lib/asyncio/base_events.py')
-rw-r--r--Lib/asyncio/base_events.py96
1 files changed, 94 insertions, 2 deletions
diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py
index b74e936941..cb2499d26c 100644
--- a/Lib/asyncio/base_events.py
+++ b/Lib/asyncio/base_events.py
@@ -122,6 +122,7 @@ class BaseEventLoop(events.AbstractEventLoop):
self._internal_fds = 0
self._running = False
self._clock_resolution = time.get_clock_info('monotonic').resolution
+ self._exception_handler = None
def _make_socket_transport(self, sock, protocol, waiter=None, *,
extra=None, server=None):
@@ -254,7 +255,7 @@ class BaseEventLoop(events.AbstractEventLoop):
"""Like call_later(), but uses an absolute time."""
if tasks.iscoroutinefunction(callback):
raise TypeError("coroutines cannot be used with call_at()")
- timer = events.TimerHandle(when, callback, args)
+ timer = events.TimerHandle(when, callback, args, self)
heapq.heappush(self._scheduled, timer)
return timer
@@ -270,7 +271,7 @@ class BaseEventLoop(events.AbstractEventLoop):
"""
if tasks.iscoroutinefunction(callback):
raise TypeError("coroutines cannot be used with call_soon()")
- handle = events.Handle(callback, args)
+ handle = events.Handle(callback, args, self)
self._ready.append(handle)
return handle
@@ -625,6 +626,97 @@ class BaseEventLoop(events.AbstractEventLoop):
protocol, popen_args, False, stdin, stdout, stderr, bufsize, **kwargs)
return transport, protocol
+ def set_exception_handler(self, handler):
+ """Set handler as the new event loop exception handler.
+
+ If handler is None, the default exception handler will
+ be set.
+
+ If handler is a callable object, it should have a
+ matching signature to '(loop, context)', where 'loop'
+ will be a reference to the active event loop, 'context'
+ will be a dict object (see `call_exception_handler()`
+ documentation for details about context).
+ """
+ if handler is not None and not callable(handler):
+ raise TypeError('A callable object or None is expected, '
+ 'got {!r}'.format(handler))
+ self._exception_handler = handler
+
+ def default_exception_handler(self, context):
+ """Default exception handler.
+
+ This is called when an exception occurs and no exception
+ handler is set, and can be called by a custom exception
+ handler that wants to defer to the default behavior.
+
+ context parameter has the same meaning as in
+ `call_exception_handler()`.
+ """
+ message = context.get('message')
+ if not message:
+ message = 'Unhandled exception in event loop'
+
+ exception = context.get('exception')
+ if exception is not None:
+ exc_info = (type(exception), exception, exception.__traceback__)
+ else:
+ exc_info = False
+
+ log_lines = [message]
+ for key in sorted(context):
+ if key in {'message', 'exception'}:
+ continue
+ log_lines.append('{}: {!r}'.format(key, context[key]))
+
+ logger.error('\n'.join(log_lines), exc_info=exc_info)
+
+ def call_exception_handler(self, context):
+ """Call the current event loop exception handler.
+
+ context is a dict object containing the following keys
+ (new keys maybe introduced later):
+ - 'message': Error message;
+ - 'exception' (optional): Exception object;
+ - 'future' (optional): Future instance;
+ - 'handle' (optional): Handle instance;
+ - 'protocol' (optional): Protocol instance;
+ - 'transport' (optional): Transport instance;
+ - 'socket' (optional): Socket instance.
+
+ Note: this method should not be overloaded in subclassed
+ event loops. For any custom exception handling, use
+ `set_exception_handler()` method.
+ """
+ if self._exception_handler is None:
+ try:
+ self.default_exception_handler(context)
+ except Exception:
+ # Second protection layer for unexpected errors
+ # in the default implementation, as well as for subclassed
+ # event loops with overloaded "default_exception_handler".
+ logger.error('Exception in default exception handler',
+ exc_info=True)
+ else:
+ try:
+ self._exception_handler(self, context)
+ except Exception as exc:
+ # Exception in the user set custom exception handler.
+ try:
+ # Let's try default handler.
+ self.default_exception_handler({
+ 'message': 'Unhandled error in exception handler',
+ 'exception': exc,
+ 'context': context,
+ })
+ except Exception:
+ # Guard 'default_exception_handler' in case it's
+ # overloaded.
+ logger.error('Exception in default exception handler '
+ 'while handling an unexpected error '
+ 'in custom exception handler',
+ exc_info=True)
+
def _add_callback(self, handle):
"""Add a Handle to ready or scheduled."""
assert isinstance(handle, events.Handle), 'A Handle is required here'