As discussed with Guido in issue #80
(https://code.google.com/p/tulip/issues/detail?id=80),
I'm proposing a strawman design for having a user definable event loop
handler for unhandled exceptions.
Two new API points:
- "loop.set_exception_handler(callback)"
Sets 'callback' as a new unhandled error handler for the event loop
'loop'. The signature of callback should be '(loop, exception, context)'
where: 'loop' parameter is the current event loop running; 'exception'
is the unhandled exception object; 'context' is a string (str),
that will be the basic context information, like
"unhandled exception in add_reader callback" (the kind of information
that usually goes to the log title).
It's OK to call 'loop.stop()' or 'loop.call_*' methods from the handler.
If a user set handler raises an exception, that exception will be
handled by the default event loop exception handler (that will
simply log it)
- "loop.restore_default_exception_handler()"
Restores exception handler to the default one.
Default exception handler just logs the error with
'loop.logger.exception()'
Agree. Need to make it clear, though, that 'message' is not
On 2/6/2014, 1:24 PM, Guido van Rossum wrote:
On Thu, Feb 6, 2014 at 8:53 AM, Yury Selivanov <yseliv...@gmail.com>wrote:
As discussed with Guido in issue #80I'd rename the context arg to 'message', then you don't have to explain
(https://code.google.com/p/tulip/issues/detail?id=80),
I'm proposing a strawman design for having a user definable event loop
handler for unhandled exceptions.
Two new API points:
- "loop.set_exception_handler(callback)"
Sets 'callback' as a new unhandled error handler for the event loop
'loop'. The signature of callback should be '(loop, exception,
context)'
where: 'loop' parameter is the current event loop running; 'exception'
is the unhandled exception object; 'context' is a string (str),
that will be the basic context information, like
"unhandled exception in add_reader callback" (the kind of information
that usually goes to the log title).
what it is. :-)
necessarily the same as 'exception.args[0]'
Good. Since we always need to have at least default exception
Also I'd steer clear from calling the handler 'callback' -- maybe just name
it 'exception_handler'?
It's OK to call 'loop.stop()' or 'loop.call_*' methods from theCool.
handler.
If a user set handler raises an exception, that exception will beNice.
handled by the default event loop exception handler (that will
simply log it)
- "loop.restore_default_exception_handler()"Maybe instead of having a separate API for this rarely-used action, this
Restores exception handler to the default one.
Default exception handler just logs the error with
'loop.logger.exception()'
could be spelled as loop.set_exception_handler(None)?
handler, using None in this context seems very logical.I thought about this, and my reasoning for not proposing
A few questions:
- Do we need an API to ask the loop for its current exception handler?
'loop.get_exception_handler()' was to keep the API simple.
Besides, let's pretend we have this API in place, and you
can get the current handler. The getter will return the handler
to the caller, and that handler, or callback object will make
sense only to the application/code that set it in the first
place.
And I think in this case, it's better to force the client
code to carefully use and track the calls to
'set_exception_handler()'.
If we make the default handler a public API, then we can returnIf
so, what should it return if no handler is set explicitly?
a bound method. If we decide to hide it -- then 'None'.The API will be more complex then, as we'll need functions to
- Should we allow multiple exception handlers, or just one? (I'd prefer
just one.)
remove exception handlers, at least. Applications can always
set one handler that will call other functions (as many as they
need).
I think it's a good idea to let users trigger the default handler
- Should we have a way to invoke the default handler explicitly? E.g. if
the user's handler doesn't want to do anything special, they could pass it
to the default handler instead of having to figure out how the default
handler logs things.
somehow.
One option is to add it to the public API. But it may cause some
confusion about when you should use it. Basically, the only place
it should be used in, is a custom exception handler.
Another option would be to let the custom handler simply re-raise
the exception, but this way we'll mix exceptions originated *in*
the handler with other exceptions, so I'd be -1 on this approach.
And another option is to establish a protocol: if a custom handler
returns an exception object, that object is passed to the default
handler.
- Should we have a way to invoke the current handler explicitly? E.g. if
there's user code that has encountered an exception and it wants that
exception to be treated the same as the loop handles other exceptions.
-1 on this. Non-sophisticated code should just stick to the logging
module. The kind of code that uses its custom error handlers, can
develop its own facilities for error reporting.
So let me reiterate on the proposed design:
loop.set_exception_handler(handler)
Sets 'handler' as a new unhandled exceptions handler.
If 'handler' is a callable object, then it should
have the following signature (or compatible one):
"(loop, exception, message)".
If 'callback' is None, default event loop exceptions
handler will be set.
loop.default_exception_handler(exception, message)
Triggers default exceptions handler of the event loop.
loop.call_exception_handler(exception, message)
Triggers the exceptions handler set with
'set_exception_handler'.
If there was no custom exceptions handler set, the
default one is triggered.
Install an handler for unhandled exceptions is not something new: there is already sys.displayhook and sys.excepthook.
I'm not sure that it's useful to get the "default" handler. Just give access to the current handler. So when you setup a new handler, just call the previous if you don't know what to do with it. It is the design chosen for the new malloc API, PEP 445. So you just need two function: get and set.
For sys.displayhook/excepthook, the default is stored in sys.__displayhook/excepthook__.
Victor
> However, I'm not sure how passing the future/handle to the
> handler may help. You have the name of the failed callback
> in the traceback of the exception. And, in many places all
> you have is just the failed callback function, and no
> related future or task.
I'm talking about Future destructor which logs the unhandled exception. The future is just self.
Do you know the new function tracemalloc.get_object_traceback()?
Victor
A question to you and Guido: should the callback receive
three arguments: loop, exception, and context, where
context is a namedtuple (so we can add new fields to it
in future releases). For now, 'context' can have two fields:
'message' and 'callback'. The former is the log message, and
the latter is the failed callback function (or None).
Another option is to have a loop method to get the current/last
executed callback.
On 2/6/2014, 11:02 PM, Guido van Rossum wrote:
Sure.
On 2/7/2014, 3:52 PM, Guido van Rossum wrote:
Can't you add a reference to the loop to the tb logger object? The loop
should outlive any futures anyway (since the future has a reference to the
loop) and it shouldn't be a ref cycle.
Another question: "logger.exception" is also used in:
- selector_events.py: in _accept_connection, in case of errors in pause_writing/resume_writing and _fatal_error
- proactor_events.py: in case of failed accept, _fatal_error and errors in pause/resume writing
- unix_events.py: In pipe transport's _fatal_error, in case of exception in SIGCHLD handler
- windows_events.py: pipe accept failed
All of the above sites are logging exceptions (typically OSErrors). Should we use the loop exception API there, or you want to keep using loggers directly?
And one more, aesthetic question: the currently agreed on signature of exception handlers is '(loop, context)'. I have a method "BaseEventLoop.default_exception_handler(self, context)", but when the method is bound, its signature is just '(context)', hence, this bound method cannot be passed to 'set_exception_handler'. Should the signature be "BaseEventLoop.default_exception_handler(self, loop, context)", or can I just make it a staticmethod with '(loop, context)'?
On 2/7/2014, 4:47 PM, Guido van Rossum wrote: