Остановка 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)