9

When the twisted reactor is running and an exception occurs within a deferred that isn't caught, "Unhandled Error" is printed to the terminal along with a traceback and the exception. Is it possible to handle/intercept these exceptions (e.g., set a callback or override a method)?

EDIT: I'm aware that I can catch a failure by adding an errback to a deferrerd. What I want to know is if there is a way to intercept an unhandled failure/exception that has traversed its way up the chain to the reactor.

EDIT: Essentially, I'm wondering if the twisted reactor has a global error handler or something that can be accessed. I wonder because it prints the traceback and error from the failure.

Example:

Unhandled Error
Traceback (most recent call last):
  File "/var/projects/python/server.py", line 359, in run_server
    return server.run()
  File "/var/projects/python/server.py", line 881, in run
    reactor.run()
  File "/usr/local/lib/python2.6/dist-packages/Twisted-11.0.0-py2.6-linux-x86_64.egg/twisted/internet/base.py", line 1162, in run
    self.mainLoop()
  File "/usr/local/lib/python2.6/dist-packages/Twisted-11.0.0-py2.6-linux-x86_64.egg/twisted/internet/base.py", line 1171, in mainLoop
    self.runUntilCurrent()
--- <exception caught here> ---
  File "/usr/local/lib/python2.6/dist-packages/Twisted-11.0.0-py2.6-linux-x86_64.egg/twisted/internet/base.py", line 793, in runUntilCurrent
    call.func(*call.args, **call.kw)
  File "/var/projects/python/server.py", line 524, in monitor
    elapsed = time.time() - info.last
exceptions.NameError: global name 'info' is not defined
Uyghur Lives Matter
  • 18,820
  • 42
  • 108
  • 144

3 Answers3

6

Because these tracebacks are written using a call to twisted.python.log.deferr() (in Twisted 10.2 anyway), it is possible to redirect them using a log observer. This is the most common thing to do with these stack traces. I can't find any base class for log observers (surprisingly) but there are a couple built in:

twisted.python.log.PythonLoggingObserver - Anything logged goes to the standard Python logging module. (I use this in my application.)

twisted.python.log.FileLogObserver - Anything logged goes to a file.

Both of these will catch stack traces reported by the reactor. All you have to do is construct the log observer (no arguments) and then call the object's start() method.

(Side note: there's also a StdioOnnaStick class that you can construct and assign to sys.stdout or sys.stderr if you want. Then anything you print goes to the Twisted log.)

To really, truly intercept these calls, so the stack traces never get logged at all, you could either:

  • Subclass twisted.internet.SelectReactor and override its runUntilCurrent() method. That is what logs the stack traces. You would need to study the source of twisted.internet.base.ReactorBase before doing this.
  • After you have done all twisted.* imports, set twisted.python.log.deferr to a function of your choosing, that is compatible with the prototype def err(_stuff=None, _why=None, **kw).
wberry
  • 18,519
  • 8
  • 53
  • 85
  • All good stuff, except you should *really* never recommend subclassing a reactor. Writing your own reactor cuts you off from a huge amount of Twisted's functionality; anything involving a GUI or an optimized multiplexor for your platform (epoll, kqueue, IOCP), and it's rarely necessary. Also, monkeypatching `deferr` is too drastic. The right thing to do is to take your suggestion about the log observer. You can just write your own and wrap around an existing observer, you don't need to subclass one of the named classes. – Glyph Aug 05 '11 at 23:10
  • Yeah, it sort of depends on what he meant by "intercept". If he meant "completely silence", then hopefully the prospect of having to mess with the Twisted internals to achieve that will disincentivize it. I too recommend using one of the log observer classes. – wberry Aug 05 '11 at 23:42
  • Thanks. The logging observer should work. Subclassing the reactor may be overkill but nice to know. – Uyghur Lives Matter Aug 08 '11 at 14:09
3

You can add an errback to the deferred; unhandled exceptions are automatically converted to twisted.python.failure.Failure.

GaretJax
  • 7,462
  • 1
  • 38
  • 47
  • "failures" meaning instances of twisted.python.failure.Failure – wberry Aug 05 '11 at 21:34
  • 1
    I'm aware that I can catch a failure by adding an errback to a deferrerd. What I want to know is if there is a way to intercept an unhandled failure/exception that has traversed its way up the chain to the reactor. – Uyghur Lives Matter Aug 05 '11 at 21:37
  • @C.P.Burns: As the exception is transformed to a failure, if you add an errback to the deferred, you can intercept it. Am I missing something? – GaretJax Aug 05 '11 at 21:41
  • @GaretJax: C.P. seems to want to know how to do this for *all* logged exceptions ever (whether they're coming from a Deferred or not), without knowing about which Deferred they might be coming from. – Glyph Aug 05 '11 at 23:12
  • @Glyph: to me it seems that it is exclusively deferred related: *"an exception occurs within a deferred"*; anyway, I wrote this answer before the C.P. commented and edited his question. ;-) – GaretJax Aug 06 '11 at 00:44
1

Answering to your comment:

Essentially, I'm wondering if the twisted reactor has a global error handler or something that can be accessed. I wonder because it prints the traceback and error from the failure.

The response is "not in a proper way".

First, the reactor has nothing to do with deferreds, actually, the whole deferred module should be placed in the twisted.python package, but this cannot be done yet because of some dependencies. Back to your question...

Digging into the twisted code (more precisel, the twisted.internet.defer module) you can outline the following event flow:

  1. When the callback method is called with a result, the deferred instance begins to run its callbacks through the _runCallbacks method;
  2. If one of the callbacks throws an exception, it is wrapped into Failure (line 542);
  3. If the callback chain is exhausted and the last result was a failure, the current result is assigned to the failResult property of a DebugInfo instance (line 575);
  4. If the deferred instance, and thus its DebugInfo instance, are garbage collected ad there still is an active failure as a result, the DebugInfo.__del__ method is called and the traceback printed out.

Given these premises, one of the simplest solutions would be to monkey patch the DebugInfo class:

from twisted.internet.defer import DebugInfo
del DebugInfo.__del__  # Hides all errors
GaretJax
  • 7,462
  • 1
  • 38
  • 47
  • This is wrong - there are several ways to intercept errors by adding log observers that will capture `Failure` objects and deal with them in various ways. Other answers discuss some of these. Not going to downvote because it's an interesting exploration of the code, but please don't take this suggestion. – Glyph Aug 05 '11 at 23:11
  • Will not argue with you... would not have a chance. ;-) But what if you have several log observers and you want to hide it from all of them? They are called iteratively if I remember well, are they? – GaretJax Aug 05 '11 at 23:17
  • That's not a very useful thing to want. Don't emit the error if you don't want the observers to see it (which equates to adding the right errback in this particular case). – Jean-Paul Calderone Aug 07 '11 at 14:48
  • I agree with you (that's why I replied with the other answer in the first place), but maybe, instead of hiding the errors you would like to trigger some other action (but not log them)… the monkey patching code in the answer was only one of the possible examples. Note that I'm not arguing that mine is the correct answer, I was only curious about that. – GaretJax Aug 07 '11 at 20:26
  • Thanks for the clarification between the reactor and deferreds. – Uyghur Lives Matter Aug 08 '11 at 13:58