Давайте немного рассмотрим «ласточку». Что значит «проглотить» исключение?
Вот самое прямое и, я думаю, точное толкование:
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)