Is there a way to stop Twisted reactor from automatically swallowing exceptions (eg. NameError)? I just want it to stop execution, and give me a stack trace in console?
There’s even a FAQ question about it, but to say the least, it’s not very helpful.
Currently, in every errback I do this:
def errback(value):
import traceback
trace = traceback.format_exc()
# rest of the errback...
but that feels clunky, and there has to be a better way?
Update
In response to Jean-Paul’s answer, I’ve tried running the following code (with Twisted 11.1 and 12.0):
from twisted.internet.endpoints import TCP4ClientEndpoint
from twisted.internet import protocol, reactor
class Broken(protocol.Protocol):
def connectionMade(self):
buggy_user_code()
e = TCP4ClientEndpoint(reactor, "127.0.0.1", 22)
f = protocol.Factory()
f.protocol = Broken
e.connect(f)
reactor.run()
After running it, it just hangs there, so I have to Ctrl-C it:
> python2.7 tx-example.py
^CUnhandled error in Deferred:
Unhandled Error
Traceback (most recent call last):
Failure: twisted.internet.error.ConnectionRefusedError: Connection was refused by other side: 111: Connection refused.
Let’s explore “swallow” a little bit. What does it mean to “swallow” an exception?
Here’s the most direct and, I think, faithful interpretation:
Here any exceptions from the call to user code are caught and then discarded with no action taken. If you look through Twisted, I don’t think you’ll find this pattern anywhere. If you do, it’s a terrible mistake and a bug, and you would be helping out the project by filing a bug pointing it out.
What else might lead to “swallowing exceptions”? One possibility is that the exception is coming from application code that isn’t supposed to be raising exceptions at all. This is typically dealt with in Twisted by logging the exception and then moving on, perhaps after disconnecting the application code from whatever event source it was connected to. Consider this buggy application:
When run (if you have a server running on localhost:22, so the connection succeeds and
connectionMadeactually gets called), the output produced is:This error clearly isn’t swallowed. Even though the logging system hasn’t been initialized in any particular way by this application, the logged error still shows up. If the logging system had been initialized in a way that caused errors to go elsewhere – say some log file, or /dev/null – then the error might not be as apparent. You would have to go out of your way to cause this to happen though, and presumably if you direct your logging system at /dev/null then you won’t be surprised if you don’t see any errors logged.
In general there is no way to change this behavior in Twisted. Each exception handler is implemented separately, at the call site where application code is invoked, and each one is implemented separately to do the same thing – log the error.
One more case worth inspecting is how exceptions interact with the
Deferredclass. Since you mentioned errbacks I’m guessing this is the case that’s biting you.A
Deferredcan have a success result or a failure result. When it has any result at all and more callbacks or errbacks, it will try to pass the result to either the next callback or errback. The result of theDeferredthen becomes the result of the call to one of those functions. As soon as theDeferredhas gone though all of its callbacks and errbacks, it holds on to its result in case more callbacks or errbacks are added to it.If the
Deferredends up with a failure result and no more errbacks, then it just sits on that failure. If it gets garbage collected before an errback which handles that failure is added to it, then it will log the exception. This is why you should always have errbacks on your Deferreds, at least so that you can log unexpected exceptions in a timely manner (rather than being subject to the whims of the garbage collector).If we revisit the previous example and consider the behavior when there is no server listening on localhost:22 (or change the example to connect to a different address, where no server is listening), then what we get is exactly a
Deferredwith a failure result and no errback to handle it.This call returns a
Deferred, but the calling code just discards it. Hence, it has no callbacks or errbacks. When it gets its failure result, there’s no code to handle it. The error is only logged when theDeferredis garbage collected, which happens at an unpredictable time. Often, particularly for very simple examples, the garbage collection won’t happen until you try to shut down the program (eg via Control-C). The result is something like this:If you’ve accidentally written a large program and fallen into this trap somewhere, but you’re not exactly sure where, then
twisted.internet.defer.setDebuggingmight be helpful. If the example is changed to use it to enableDeferreddebugging:Then the output is somewhat more informative:
Notice near the top, where the
e.connect(f)line is given as the origin of thisDeferred– telling you a likely place where you should be adding an errback.However, the code should have been written to add an errback to this
Deferredin the first place, at least to log the error.There are shorter (and more correct) ways to display exceptions than the one you’ve given, though. For example, consider:
Or, even more succinctly:
This error handling behavior is somewhat fundamental to the idea of
Deferredand so also isn’t very likely to change.If you have a
Deferred, errors from which really are fatal and must stop your application, then you can define a suitable errback and attach it to thatDeferred: