Остановка Twisted от глотания исключений

есть ли способ остановить скрученный реактор от автоматического проглатывания исключений (например. NameError)? Я просто хочу, чтобы он прекратил выполнение и дал мне трассировку стека в консоли?

есть даже FAQ вопрос об этом, но мягко говоря, это не очень полезно.

В настоящее время в каждом errback я делаю это:

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

но это кажется неуклюжим, и должен быть лучший способ?

обновление

В ответ на Ответ Жан-Поля, я попытался запустить следующий код (с 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 ответов


давайте рассмотрим "Ласточка" немного. Что значит "проглотить" исключение?

вот самое прямое и, я думаю, верное толкование:

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

эта ошибка явно не проглотит. Несмотря на то, что система ведения журнала не была инициализирована каким-либо определенным образом этим приложением, регистрируемая ошибка все еще отображается. Если система регистрации had был инициализирован таким образом, что вызвал ошибки в другом месте - скажем, какой - то файл журнала или /dev/null-тогда ошибка может быть не такой очевидной. Вам придется выйти из однако ваш способ заставить это произойти, и, предположительно, если вы направите свою систему регистрации в /dev/null, вы не удивитесь, если не увидите никаких ошибок.

в общем нет никакого способа изменить это поведение в Twisted. Каждый обработчик исключений реализуется отдельно, на сайте вызова, где вызывается код приложения, и каждый из них реализуется отдельно, чтобы сделать то же самое - зарегистрировать ошибку.

еще один случай стоит проверить, как исключения взаимодействие с Deferred класса. Раз уж вы упомянули ... --44-->errbacks я предполагаю, что это тот случай, который кусает вас.

A Deferred может иметь результат успеха или результат неудачи. Когда у него есть какой-либо результат вообще и больше обратных вызовов или ошибок, он попытается передать результат следующему обратному вызову или errback. Результат становится результатом вызова одной из этих функций. Как только Deferred пошел, хотя все его вызовы и errbacks, она держит на ее результат в случае, если больше обратных errbacks добавляются к нему.

если Deferred завершается с неудачным результатом и не более errbacks, потом он просто садится на провал. Если он получает мусор, собранный до errback, который обрабатывает этот сбой, добавляется к нему, затем он зарегистрирует исключение. Вот почему у вас всегда должны быть ошибки в ваших отсрочках, по крайней мере, чтобы вы могли своевременно регистрировать неожиданные исключения (скорее чем подчиняться прихотям мусорщика).

если мы вернемся к предыдущему примеру и рассмотрим поведение, когда есть нет прослушивание сервера на localhost: 22 (или измените пример для подключения к другому адресу, где сервер не прослушивает), тогда то, что мы получаем, точно Deferred С результатом сбоя и без ошибок для его обработки.

e.connect(f)

этот вызов возвращает Deferred, но вызывающий код просто отбрасывает его. Следовательно, нет обратных вызовов или errbacks. Когда он получает результат сбоя, нет кода для его обработки. Ошибка регистрируется только при 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)

что вы можете сделать в качестве обходного пути, это зарегистрировать прослушиватель журнала и остановить реактор всякий раз, когда вы видите критическую ошибку! Это скрученный (глагольный) подход, но, к счастью, все "необработанные ошибки" возникают с LogLevel.критический.

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)