Остановка с витой стороны от глотательных исключений

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

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

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

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.

Ответ 1

Позвольте немного изучить "усвоить". Что значит "усвоить" исключение?

Здесь самая прямая и, я думаю, верная интерпретация:

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 ". Поскольку вы упомянули об ошибках, я предполагаю, что это тот случай, когда вы кусаете вас.

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

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

Если мы перейдем к предыдущему примеру и рассмотрим поведение, когда сервер не прослушивает 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)

Тогда выход несколько более информативен:

[email protected]:/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)

Ответ 2

То, что вы могли бы сделать в качестве обходного пути, - зарегистрировать регистратор журналов и остановить реактор всякий раз, когда вы видите критическую ошибку! Это закрученный (глагол) подход, но, к счастью, все "необработанные ошибки" возникают с помощью 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)