因吞咽异常而停止扭曲

时间:2012-02-15 14:39:35

标签: python twisted

有没有办法阻止Twisted reactor自动吞咽异常(例如NameError)?我只是想让它停止执行,并在控制台中给我一个堆栈跟踪?

关于它,甚至还有一个常见问题question,但至少可以说,它不是很有帮助。

目前,在每个错误中我都这样做:

def errback(value):
    import traceback
    trace = traceback.format_exc()
    # rest of the errback...

但是这感觉很笨重,还有更好的方法吗?

更新

在回应Jean-Paul的回答时,我尝试运行以下代码(使用Twisted 11.1和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()

运行后,它只是挂在那里,所以我必须按Ctrl-C:

> 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.

2 个答案:

答案 0 :(得分:22)

让我们探索一下“吞咽”。 “吞下”异常是什么意思?

这是最直接的,我认为,忠实的解释:

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可能会有成功结果或失败结果。当它有任何结果以及更多的回调或错误时,它会尝试将结果传递给下一个回调或错误回复。然后Deferred的结果成为调用其中一个函数的结果。一旦Deferred经历了所有的回调和错误,它就会保留其结果,以防更多的回调或错误被添加到它。

如果Deferred最终导致失败结果并且没有更多的错误,那么它就会失败。如果它在处理失败的errback之前收集了垃圾,那么然后它将记录异常。这就是为什么你应该总是在你的Deferreds上有错误,至少这样你就可以及时记录意外的异常(而不是受垃圾收集器的影响)。

如果我们重新访问上一个示例并考虑当没有服务器监听localhost:22时的行为(或更改示例以连接到其他地址,没有服务器正在侦听),那么我们得到的只是一个Deferred,其失败结果并没有错误处理它。

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,其中的错误确实是致命的并且必须停止你的应用程序,那么你可以定义一个合适的错误并将其附加到Deferred

d = e.connect(f)
def fatalError(reason):
    err(reason, "Absolutely needed the foo, could not get it")
    reactor.stop()

d.addErrback(fatalError)

答案 1 :(得分:1)

您可以做的解决方法是注册日志监听器并在发现严重错误时停止反应堆!这是一种扭曲的(动词)方法,但幸运的是,所有“未处理的错误”都是通过LogLevel.critical引发的。

from twisted.logger._levels import LogLevel

def analyze(event):
    if event.get("log_level") == LogLevel.critical:
        print "Stopping for: ", event
        reactor.stop()

globalLogPublisher.addObserver(analyze)