Остановка Twisted от глотания исключений - PullRequest
24 голосов
/ 15 февраля 2012

Есть ли способ остановить 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 ]

22 голосов
/ 16 февраля 2012

Давайте немного рассмотрим «ласточку». Что значит «проглотить» исключение?

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

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 Я предполагаю, что это тот случай, который вас кусает.

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

Если 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)

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

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.

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

Однако код должен быть написан для того, чтобы сначала добавить ошибку к этому 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 голос
/ 21 мая 2016

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