@@ -122,6 +122,7 @@ def __init__(self):
122122 self ._internal_fds = 0
123123 self ._running = False
124124 self ._clock_resolution = time .get_clock_info ('monotonic' ).resolution
125+ self ._exception_handler = None
125126
126127 def _make_socket_transport (self , sock , protocol , waiter = None , * ,
127128 extra = None , server = None ):
@@ -254,7 +255,7 @@ def call_at(self, when, callback, *args):
254255 """Like call_later(), but uses an absolute time."""
255256 if tasks .iscoroutinefunction (callback ):
256257 raise TypeError ("coroutines cannot be used with call_at()" )
257- timer = events .TimerHandle (when , callback , args )
258+ timer = events .TimerHandle (when , callback , args , self )
258259 heapq .heappush (self ._scheduled , timer )
259260 return timer
260261
@@ -270,7 +271,7 @@ def call_soon(self, callback, *args):
270271 """
271272 if tasks .iscoroutinefunction (callback ):
272273 raise TypeError ("coroutines cannot be used with call_soon()" )
273- handle = events .Handle (callback , args )
274+ handle = events .Handle (callback , args , self )
274275 self ._ready .append (handle )
275276 return handle
276277
@@ -625,6 +626,97 @@ def subprocess_exec(self, protocol_factory, program, *args, stdin=subprocess.PIP
625626 protocol , popen_args , False , stdin , stdout , stderr , bufsize , ** kwargs )
626627 return transport , protocol
627628
629+ def set_exception_handler (self , handler ):
630+ """Set handler as the new event loop exception handler.
631+
632+ If handler is None, the default exception handler will
633+ be set.
634+
635+ If handler is a callable object, it should have a
636+ matching signature to '(loop, context)', where 'loop'
637+ will be a reference to the active event loop, 'context'
638+ will be a dict object (see `call_exception_handler()`
639+ documentation for details about context).
640+ """
641+ if handler is not None and not callable (handler ):
642+ raise TypeError ('A callable object or None is expected, '
643+ 'got {!r}' .format (handler ))
644+ self ._exception_handler = handler
645+
646+ def default_exception_handler (self , context ):
647+ """Default exception handler.
648+
649+ This is called when an exception occurs and no exception
650+ handler is set, and can be called by a custom exception
651+ handler that wants to defer to the default behavior.
652+
653+ context parameter has the same meaning as in
654+ `call_exception_handler()`.
655+ """
656+ message = context .get ('message' )
657+ if not message :
658+ message = 'Unhandled exception in event loop'
659+
660+ exception = context .get ('exception' )
661+ if exception is not None :
662+ exc_info = (type (exception ), exception , exception .__traceback__ )
663+ else :
664+ exc_info = False
665+
666+ log_lines = [message ]
667+ for key in sorted (context ):
668+ if key in {'message' , 'exception' }:
669+ continue
670+ log_lines .append ('{}: {!r}' .format (key , context [key ]))
671+
672+ logger .error ('\n ' .join (log_lines ), exc_info = exc_info )
673+
674+ def call_exception_handler (self , context ):
675+ """Call the current event loop exception handler.
676+
677+ context is a dict object containing the following keys
678+ (new keys maybe introduced later):
679+ - 'message': Error message;
680+ - 'exception' (optional): Exception object;
681+ - 'future' (optional): Future instance;
682+ - 'handle' (optional): Handle instance;
683+ - 'protocol' (optional): Protocol instance;
684+ - 'transport' (optional): Transport instance;
685+ - 'socket' (optional): Socket instance.
686+
687+ Note: this method should not be overloaded in subclassed
688+ event loops. For any custom exception handling, use
689+ `set_exception_handler()` method.
690+ """
691+ if self ._exception_handler is None :
692+ try :
693+ self .default_exception_handler (context )
694+ except Exception :
695+ # Second protection layer for unexpected errors
696+ # in the default implementation, as well as for subclassed
697+ # event loops with overloaded "default_exception_handler".
698+ logger .error ('Exception in default exception handler' ,
699+ exc_info = True )
700+ else :
701+ try :
702+ self ._exception_handler (self , context )
703+ except Exception as exc :
704+ # Exception in the user set custom exception handler.
705+ try :
706+ # Let's try default handler.
707+ self .default_exception_handler ({
708+ 'message' : 'Unhandled error in exception handler' ,
709+ 'exception' : exc ,
710+ 'context' : context ,
711+ })
712+ except Exception :
713+ # Guard 'default_exception_handler' in case it's
714+ # overloaded.
715+ logger .error ('Exception in default exception handler '
716+ 'while handling an unexpected error '
717+ 'in custom exception handler' ,
718+ exc_info = True )
719+
628720 def _add_callback (self , handle ):
629721 """Add a Handle to ready or scheduled."""
630722 assert isinstance (handle , events .Handle ), 'A Handle is required here'
0 commit comments