让我们稍微探索一下“吞”。 “吞下”异常是什么意思?
这是我认为最直接、最忠实的解释:
try:
user_code()
except:
pass
在这里,调用用户代码的任何异常都会被捕获,然后在不采取任何措施的情况下被丢弃。如果您浏览 Twisted,我认为您不会在任何地方找到这种模式。如果你这样做了,这是一个可怕的错误和错误,你会通过提交一个错误指出来帮助项目。
还有什么可能导致“吞咽异常”?一种可能性是异常来自根本不应该引发异常的应用程序代码。这通常在 Twisted 中通过记录异常然后继续进行处理,可能在断开应用程序代码与其连接的任何事件源之后。考虑一下这个有缺陷的应用程序:
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()
运行时(如果你有一个在 localhost:22 上运行的服务器,那么连接成功并且connectionMade 实际被调用),产生的输出是:
Unhandled Error
Traceback (most recent call last):
File "/usr/lib/python2.7/dist-packages/twisted/python/log.py", line 84, in callWithLogger
return callWithContext({"system": lp}, func, *args, **kw)
File "/usr/lib/python2.7/dist-packages/twisted/python/log.py", line 69, in callWithContext
return context.call({ILogContext: newCtx}, func, *args, **kw)
File "/usr/lib/python2.7/dist-packages/twisted/python/context.py", line 118, in callWithContext
return self.currentContext().callWithContext(ctx, func, *args, **kw)
File "/usr/lib/python2.7/dist-packages/twisted/python/context.py", line 81, in callWithContext
return func(*args,**kw)
--- <exception caught here> ---
File "/usr/lib/python2.7/dist-packages/twisted/internet/selectreactor.py", line 146, in _doReadOrWrite
why = getattr(selectable, method)()
File "/usr/lib/python2.7/dist-packages/twisted/internet/tcp.py", line 674, in doConnect
self._connectDone()
File "/usr/lib/python2.7/dist-packages/twisted/internet/tcp.py", line 681, in _connectDone
self.protocol.makeConnection(self)
File "/usr/lib/python2.7/dist-packages/twisted/internet/protocol.py", line 461, in makeConnection
self.connectionMade()
File "/usr/lib/python2.7/dist-packages/twisted/internet/endpoints.py", line 64, in connectionMade
self._wrappedProtocol.makeConnection(self.transport)
File "/usr/lib/python2.7/dist-packages/twisted/internet/protocol.py", line 461, in makeConnection
self.connectionMade()
File "proderr.py", line 6, in connectionMade
buggy_user_code()
exceptions.NameError: global name 'buggy_user_code' is not defined
这个错误显然没有被吞下。即使此应用程序没有以任何特定方式初始化日志系统,记录的错误仍然会出现。如果日志系统已经以一种导致错误转移到其他地方的方式进行初始化——比如一些日志文件,或者 /dev/null——那么错误可能不会那么明显。但是,您必须不遗余力地导致这种情况发生,并且大概如果您将日志系统定向到 /dev/null ,那么如果您没有看到任何记录的错误,您将不会感到惊讶。
一般来说,在 Twisted 中无法更改此行为。每个异常处理程序都是在调用应用程序代码的调用站点上单独实现的,并且每个异常处理程序都单独实现以执行相同的操作 - 记录错误。
另一个值得研究的案例是异常如何与Deferred 类交互。既然你提到了errbacks,我猜这就是让你难受的情况。
Deferred 可以有成功结果或失败结果。当它有任何结果以及更多回调或 errback 时,它会尝试将结果传递给下一个回调或 errback。 Deferred 的结果然后成为调用这些函数之一的结果。一旦Deferred 完成了所有回调和错误返回,它就会保留其结果,以防添加更多回调或错误返回。
如果Deferred 以失败结果结束并且没有更多的errbacks,那么它只是坐在那个失败上。如果它在处理该故障的 errback 添加到它之前被垃圾收集,然后它将记录异常。这就是为什么你应该总是在你的 Deferreds 上有 errbacks,至少这样你可以及时记录意外的异常(而不是受制于垃圾收集器的突发奇想)。
如果我们重新访问前面的示例,并考虑在 localhost:22 上没有 no 服务器侦听时的行为(或更改示例以连接到没有服务器在侦听的其他地址),那么我们得到的正是一个Deferred,它有一个失败的结果并且没有errback 来处理它。
e.connect(f)
此调用返回Deferred,但调用代码只是将其丢弃。因此,它没有回调或错误返回。当它得到失败结果时,没有代码可以处理它。仅当Deferred 被垃圾回收时才会记录该错误,这发生在不可预测的时间。通常,特别是对于非常简单的示例,在您尝试关闭程序(例如,通过 Control-C)之前不会发生垃圾收集。结果是这样的:
$ python someprog.py
... wait ...
... wait ...
... wait ...
<Control C>
Unhandled error in Deferred:
Unhandled Error
Traceback (most recent call last):
Failure: twisted.internet.error.ConnectionRefusedError: Connection was refused by other side: 111: Connection refused.
如果您不小心编写了一个大型程序并在某个地方陷入了这个陷阱,但您不确定在哪里,那么twisted.internet.defer.setDebugging 可能会有所帮助。如果示例改成使用它来启用Deferred调试:
from twisted.internet.defer import setDebugging
setDebugging(True)
然后输出的信息量更大:
exarkun@top:/tmp$ python proderr.py
... wait ...
... wait ...
... wait ...
<Control C>
Unhandled error in Deferred:
(debug: C: Deferred was created:
C: File "proderr.py", line 15, in <module>
C: e.connect(f)
C: File "/usr/lib/python2.7/dist-packages/twisted/internet/endpoints.py", line 240, in connect
C: wf = _WrappingFactory(protocolFactory, _canceller)
C: File "/usr/lib/python2.7/dist-packages/twisted/internet/endpoints.py", line 121, in __init__
C: self._onConnection = defer.Deferred(canceller=canceller)
I: First Invoker was:
I: File "proderr.py", line 16, in <module>
I: reactor.run()
I: File "/usr/lib/python2.7/dist-packages/twisted/internet/base.py", line 1162, in run
I: self.mainLoop()
I: File "/usr/lib/python2.7/dist-packages/twisted/internet/base.py", line 1174, in mainLoop
I: self.doIteration(t)
I: File "/usr/lib/python2.7/dist-packages/twisted/internet/selectreactor.py", line 140, in doSelect
I: _logrun(selectable, _drdw, selectable, method, dict)
I: File "/usr/lib/python2.7/dist-packages/twisted/python/log.py", line 84, in callWithLogger
I: return callWithContext({"system": lp}, func, *args, **kw)
I: File "/usr/lib/python2.7/dist-packages/twisted/python/log.py", line 69, in callWithContext
I: return context.call({ILogContext: newCtx}, func, *args, **kw)
I: File "/usr/lib/python2.7/dist-packages/twisted/python/context.py", line 118, in callWithContext
I: return self.currentContext().callWithContext(ctx, func, *args, **kw)
I: File "/usr/lib/python2.7/dist-packages/twisted/python/context.py", line 81, in callWithContext
I: return func(*args,**kw)
I: File "/usr/lib/python2.7/dist-packages/twisted/internet/selectreactor.py", line 146, in _doReadOrWrite
I: why = getattr(selectable, method)()
I: File "/usr/lib/python2.7/dist-packages/twisted/internet/tcp.py", line 638, in doConnect
I: self.failIfNotConnected(error.getConnectError((err, strerror(err))))
I: File "/usr/lib/python2.7/dist-packages/twisted/internet/tcp.py", line 592, in failIfNotConnected
I: self.connector.connectionFailed(failure.Failure(err))
I: File "/usr/lib/python2.7/dist-packages/twisted/internet/base.py", line 1048, in connectionFailed
I: self.factory.clientConnectionFailed(self, reason)
I: File "/usr/lib/python2.7/dist-packages/twisted/internet/endpoints.py", line 144, in clientConnectionFailed
I: self._onConnection.errback(reason)
)
Unhandled Error
Traceback (most recent call last):
Failure: twisted.internet.error.ConnectionRefusedError: Connection was refused by other side: 111: Connection refused.
注意顶部附近,e.connect(f) 行作为此 Deferred 的起源 - 告诉您应该添加错误返回的可能位置。
但是,首先应该编写代码以向此Deferred 添加错误返回,至少要记录错误。
不过,显示异常的方法比您给出的方法更短(且更正确)。例如,考虑:
d = e.connect(f)
def errback(reason):
reason.printTraceback()
d.addErrback(errback)
或者,更简洁:
from twisted.python.log import err
d = e.connect(f)
d.addErrback(err, "Problem fetching the foo from the bar")
这种错误处理行为在某种程度上是Deferred 的基本理念,因此也不太可能改变。
如果您有一个 Deferred,其中的错误确实是致命的并且必须停止您的应用程序,那么您可以定义一个合适的 errback 并将其附加到该 Deferred:
d = e.connect(f)
def fatalError(reason):
err(reason, "Absolutely needed the foo, could not get it")
reactor.stop()
d.addErrback(fatalError)