2626from zope .interface import implementer
2727
2828from twisted .application .internet import ClientService
29- from twisted .internet .defer import Deferred
29+ from twisted .internet .defer import CancelledError , Deferred
3030from twisted .internet .endpoints import (
3131 HostnameEndpoint ,
3232 TCP4ClientEndpoint ,
3333 TCP6ClientEndpoint ,
3434)
3535from twisted .internet .interfaces import IPushProducer , ITransport
3636from twisted .internet .protocol import Factory , Protocol
37+ from twisted .python .failure import Failure
3738
3839logger = logging .getLogger (__name__ )
3940
@@ -131,9 +132,11 @@ def __init__(
131132 factory = Factory .forProtocol (Protocol )
132133 self ._service = ClientService (endpoint , factory , clock = _reactor )
133134 self ._service .startService ()
135+ self ._stopping = False
134136 self ._connect ()
135137
136138 def close (self ):
139+ self ._stopping = True
137140 self ._service .stopService ()
138141
139142 def _connect (self ) -> None :
@@ -146,17 +149,21 @@ def _connect(self) -> None:
146149
147150 self ._connection_waiter = self ._service .whenConnected (failAfterFailures = 1 )
148151
149- @self ._connection_waiter .addErrback
150- def fail (r ):
151- r .printTraceback (file = sys .__stderr__ )
152+ def fail (failure : Failure ) -> None :
153+ # If the Deferred was cancelled (e.g. during shutdown) do not try to
154+ # reconnect (this will cause an infinite loop of errors).
155+ if failure .check (CancelledError ) and self ._stopping :
156+ return
157+
158+ # For a different error, print the traceback and re-connect.
159+ failure .printTraceback (file = sys .__stderr__ )
152160 self ._connection_waiter = None
153161 self ._connect ()
154162
155- @self ._connection_waiter .addCallback
156- def writer (r ):
163+ def writer (result : Protocol ) -> None :
157164 # We have a connection. If we already have a producer, and its
158165 # transport is the same, just trigger a resumeProducing.
159- if self ._producer and r .transport is self ._producer .transport :
166+ if self ._producer and result .transport is self ._producer .transport :
160167 self ._producer .resumeProducing ()
161168 self ._connection_waiter = None
162169 return
@@ -167,12 +174,14 @@ def writer(r):
167174
168175 # Make a new producer and start it.
169176 self ._producer = LogProducer (
170- buffer = self ._buffer , transport = r .transport , format = self .format ,
177+ buffer = self ._buffer , transport = result .transport , format = self .format ,
171178 )
172- r .transport .registerProducer (self ._producer , True )
179+ result .transport .registerProducer (self ._producer , True )
173180 self ._producer .resumeProducing ()
174181 self ._connection_waiter = None
175182
183+ self ._connection_waiter .addCallbacks (writer , fail )
184+
176185 def _handle_pressure (self ) -> None :
177186 """
178187 Handle backpressure by shedding records.
0 commit comments