【问题标题】:Stopping Twisted from swallowing exceptions阻止 Twisted 吞下异常
【发布时间】:2012-03-06 21:52:19
【问题描述】:

有没有办法阻止 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.

【问题讨论】:

    标签: python twisted


    【解决方案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)
    

    【讨论】:

      【解决方案2】:

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

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

      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)
      

      【讨论】:

      • 感谢您的努力和详细的回答!我已经更新了我的问题,因为在运行您的代码示例时我没有得到相同的输出。
      • 您需要一个在端口 22 上运行的服务器才能获得我示例中的行为。我将更新答案以讨论没有服务器在该端口上侦听时的行为。
      • 是的,连接到 na 开放端口使您的示例工作!但是,在我自己的代码(基于 twisted.web 构建的 HTTP 客户端)中,仍有一些异常被默默忽略,我不知道为什么会发生这种情况。我将尝试将我的代码提炼成一个简单的示例,并可能发布另一个问题。谢谢!
      • Python 有一个已知的未捕获异常行为。他们结束这个过程。任何改变这种行为的库都绝对是例外。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-08-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-01
      • 2018-08-25
      • 2018-05-26
      相关资源
      最近更新 更多