python - traduccion - Parando Twisted de tragar excepciones
twisted serie (2)
¡Lo que podría hacer como solución alternativa es registrar un detector de registro y detener el reactor cada vez que vea un error crítico! Este es un enfoque retorcido (verbo) pero, afortunadamente, todos los "errores no controlados" se generan con 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)
¿Hay una manera de evitar que Twisted reactor se trague automáticamente las excepciones (por ejemplo, NameError)? ¿Solo quiero que detenga la ejecución y me dé un seguimiento de pila en la consola?
Incluso hay una pregunta de question frecuentes al respecto, pero por decir lo menos, no es muy útil.
Actualmente, en cada errback hago esto:
def errback(value):
import traceback
trace = traceback.format_exc()
# rest of the errback...
¿Pero eso se siente torpe, y tiene que haber una mejor manera?
Actualizar
En respuesta a la respuesta de Jean-Paul, he intentado ejecutar el siguiente código (con Twisted 11.1 y 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()
Después de ejecutarlo, simplemente cuelga allí, así que tengo que presionar 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.
Vamos a explorar "tragar" un poco. ¿Qué significa "tragar" una excepción?
Aquí está la interpretación más directa y, creo, fiel:
try:
user_code()
except:
pass
Aquí, cualquier excepción de la llamada al código de usuario se captura y luego se descarta sin tomar ninguna acción. Si miras a través de Twisted, no creo que encuentres este patrón en ninguna parte. Si lo haces, es un error terrible y un error, y estarías ayudando en el proyecto presentando un error que lo señale.
¿Qué más podría llevar a "tragar excepciones"? Una posibilidad es que la excepción proviene del código de la aplicación que se supone que no debe generar excepciones en absoluto. Normalmente, esto se trata en Twisted registrando la excepción y luego continuando, tal vez después de desconectar el código de la aplicación de cualquier fuente de evento a la que esté conectado. Considera esta aplicación de buggy:
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()
Cuando se ejecuta (si tiene un servidor ejecutándose en localhost: 22, por lo que la conexión se realiza correctamente y se llama a connectionMade
), la salida producida es:
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
Este error claramente no es tragado. A pesar de que el sistema de registro no se ha inicializado de ninguna manera en particular por esta aplicación, el error registrado todavía aparece. Si el sistema de registro se había inicializado de una manera que causó que los errores fueran a otra parte (por ejemplo, algún archivo de registro o / dev / null), el error podría no ser tan aparente. Sin embargo, tendría que hacer todo lo posible para que esto suceda, y presumiblemente, si dirige su sistema de registro a / dev / null, no se sorprenderá si no ve ningún error registrado.
En general no hay forma de cambiar este comportamiento en Twisted. Cada controlador de excepciones se implementa por separado, en el sitio de la llamada donde se invoca el código de la aplicación, y cada uno se implementa por separado para hacer lo mismo: registrar el error.
Un caso más que vale la pena inspeccionar es cómo interactúan las excepciones con la clase Deferred
. Desde que mencionaste errores , supongo que este es el caso que te está mordiendo.
Un Deferred
puede tener un resultado exitoso o un resultado de falla. Cuando tenga algún resultado y más devoluciones de llamada o errbacks, intentará pasar el resultado a la siguiente retrollamada o errback. El resultado del Deferred
se convierte en el resultado de la llamada a una de esas funciones. Tan pronto como el Deferred
ha pasado por todas sus devoluciones de llamada y errbacks, retiene su resultado en caso de que se le agreguen más callbacks o errbacks.
Si el Deferred
termina con un resultado de falla y no más errores, entonces simplemente se sienta en esa falla. Si se recolecta la basura antes de que se agregue un error que controla la falla, entonces registrará la excepción. Esta es la razón por la que siempre debe tener errores en sus Aplazados, al menos para poder registrar excepciones inesperadas de manera oportuna (en lugar de estar sujeto a los caprichos del recolector de basura).
Si revisamos el ejemplo anterior y consideramos el comportamiento cuando no hay servidor escuchando en localhost: 22 (o cambiamos el ejemplo para conectarse a una dirección diferente, donde no hay servidor escuchando), entonces lo que obtenemos es exactamente un Deferred
con un error Resultado y no hay error para manejarlo.
e.connect(f)
Esta llamada devuelve un Deferred
, pero el código de llamada simplemente lo descarta. Por lo tanto, no tiene devoluciones de llamada o errbacks. Cuando obtiene su resultado de falla, no hay código para manejarlo. El error solo se registra cuando el Deferred
se recolecta como basura, lo que ocurre en un momento impredecible. A menudo, particularmente para ejemplos muy simples, la recolección de basura no ocurrirá hasta que intente cerrar el programa (por ejemplo, a través de Control-C). El resultado es algo como esto:
$ 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.
Si accidentalmente ha escrito un programa grande y ha caído en esta trampa en algún lugar, pero no está exactamente seguro de dónde, entonces twisted.internet.defer.setDebugging
puede ser útil. Si se cambia el ejemplo para usarlo para habilitar la depuración Deferred
:
from twisted.internet.defer import setDebugging
setDebugging(True)
Entonces la salida es algo más informativa:
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.
Observe cerca de la parte superior, donde se e.connect(f)
la línea e.connect(f)
como el origen de este Deferred
, indicándole un lugar probable donde debería agregar un error.
Sin embargo, el código debería haberse escrito para agregar un error al presente Aplazado en primer lugar, al menos para registrar el error.
Sin embargo, hay formas más cortas (y más correctas) de mostrar excepciones que la que le dio. Por ejemplo, considere:
d = e.connect(f)
def errback(reason):
reason.printTraceback()
d.addErrback(errback)
O, incluso más sucintamente:
from twisted.python.log import err
d = e.connect(f)
d.addErrback(err, "Problem fetching the foo from the bar")
Este comportamiento de manejo de errores es algo fundamental para la idea de Deferred
y, por lo tanto, tampoco es muy probable que cambie.
Si tiene un Deferred
, los errores que realmente son fatales y deben detener su aplicación, entonces puede definir un errback adecuado y adjuntarlo a ese Deferred
:
d = e.connect(f)
def fatalError(reason):
err(reason, "Absolutely needed the foo, could not get it")
reactor.stop()
d.addErrback(fatalError)