Parando Twisted de tragar excepciones

¿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 stack en la consola?

Incluso hay una pregunta de preguntas 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 se 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 hace, es un error terrible y un error, y estaría 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 origen de evento al 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) ---  --- 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 evidente. 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 manera 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 se realizará hasta que intente cerrar el progtwig (por ejemplo, a través de Control-C). El resultado es algo como esto:

 $ python someprog.py ... wait ... ... wait ... ... wait ...  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 progtwig 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 ...  Unhandled error in Deferred: (debug: C: Deferred was created: C: File "proderr.py", line 15, in  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  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 debe 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) 

¡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)