r46779 - fix the bug

glyph-TA+aISz0psMTMxyoc4vAAJOcrHinNvQL0E9HWUfgJXw@public.gmane.org
Newsgroups gmane.comp.python.twisted.commits
Message-ID <[email protected]>
Author: glyph
Date: Fri Feb 12 04:12:42 2016
New Revision: 46779

Modified:
   branches/hostname-endpoint-8014/twisted/internet/endpoints.py

Log:
fix the bug

also refactor somewhat for comprehensibility

Modified: branches/hostname-endpoint-8014/twisted/internet/endpoints.py
==============================================================================
--- branches/hostname-endpoint-8014/twisted/internet/endpoints.py	(original)
+++ branches/hostname-endpoint-8014/twisted/internet/endpoints.py	Fri Feb 12 04:12:42 2016
@@ -669,35 +669,14 @@
         connection which is established first.
         """
         wf = protocolFactory
-        pending = []
-
-        def _canceller(d):
-            """
-            The outgoing connection attempt was cancelled.  Fail that L{Deferred}
-            with an L{error.ConnectingCancelledError}.
-
-            @param d: The L{Deferred <defer.Deferred>} that was cancelled
-            @type d: L{Deferred <defer.Deferred>}
-
-            @return: C{None}
-            """
-            d.errback(error.ConnectingCancelledError(
-                HostnameAddress(self._host, self._port)))
-            for p in pending[:]:
-                p.cancel()
-
-        def errbackForGai(failure):
-            """
-            Errback for when L{_nameResolution} returns a Deferred that fires
-            with failure.
-            """
-            return defer.fail(error.DNSLookupError(
-                "Couldn't find the hostname '%s'" % (self._host,)))
-
-        def _endpoints(gaiResult):
+        d = self._nameResolution(self._host, self._port)
+        d.addErrback(lambda ignored: defer.fail(error.DNSLookupError(
+            "Couldn't find the hostname '%s'" % (self._host,))))
+        @d.addCallback
+        def gaiResultToEndpoints(gaiResult):
             """
             This method matches the host address family with an endpoint for
-            every address returned by GAI.
+            every address returned by C{getaddrinfo}.
 
             @param gaiResult: A list of 5-tuples as returned by GAI.
             @type gaiResult: list
@@ -709,72 +688,79 @@
                 elif family in [AF_INET]:
                     yield TCP4ClientEndpoint(self._reactor, sockaddr[0],
                             sockaddr[1], self._timeout, self._bindAddress)
-                        # Yields an endpoint for every address returned by GAI
+                    # Yields an endpoint for every address returned by GAI
 
+        def _canceller(d):
+            # This canceller must remain defined outside of
+            # `attemptConnection`, because Defereds should not participate in
+            # cycles with their cancellers; that would create a potentially
+            # problematic circular reference and possibly gc.garbage.
+            d.errback(error.ConnectingCancelledError(
+                HostnameAddress(self._host, self._port)))
+
+        @d.addCallback
         def attemptConnection(endpoints):
             """
-            When L{endpoints} yields an endpoint, this method attempts to connect it.
+            When L{gaiResultToEndpoints} yields an endpoint, this function
+            attempts to connect it.  The trial attempts for each endpoints, the
+            recording of successful and failed attempts, and the algorithm to
+            pick the winner endpoint goes here.
+
+            @return: a Deferred that fires with the result of the
+                C{endpoint.connect} method that completes the fastest, or fails
+                with the first connection error it encountered if none of them
+                succeed.
             """
-            # The trial attempts for each endpoints, the recording of
-            # successful and failed attempts, and the algorithm to pick the
-            # winner endpoint goes here.
-            # Return a Deferred that fires with the endpoint that wins,
-            # or `failures` if none succeed.
-
-            endpointsListExhausted = []
-            successful = []
+            pending = []
             failures = []
             winner = defer.Deferred(canceller=_canceller)
 
-            def usedEndpointRemoval(connResult, connAttempt):
-                pending.remove(connAttempt)
-                return connResult
-
-            def afterConnectionAttempt(connResult):
-                if lc.running:
-                    lc.stop()
-
-                successful.append(True)
-                for p in pending[:]:
-                    p.cancel()
-                winner.callback(connResult)
-                return None
-
             def checkDone():
-                if endpointsListExhausted and not pending and not successful:
-                    winner.errback(failures.pop())
-
-            def connectFailed(reason):
-                failures.append(reason)
-                checkDone()
-                return None
+                if pending or checkDone.completed or checkDone.endpointsLeft:
+                    return
+                winner.errback(failures.pop())
+            checkDone.completed = False
+            checkDone.endpointsLeft = True
 
+            @LoopingCall
             def iterateEndpoint():
-                try:
-                    endpoint = next(endpoints)
-                except StopIteration:
+                endpoint = next(endpoints, None)
+                if endpoint is None:
                     # The list of endpoints ends.
-                    endpointsListExhausted.append(True)
-                    lc.stop()
+                    checkDone.endpointsLeft = False
+                    iterateEndpoint.stop()
+                    checkDone()
+                    return
+
+                eachAttempt = endpoint.connect(wf)
+                pending.append(eachAttempt)
+                @eachAttempt.addBoth
+                def noLongerPending(result):
+                    pending.remove(eachAttempt)
+                    return result
+                @eachAttempt.addCallback
+                def succeeded(result):
+                    if iterateEndpoint.running:
+                        iterateEndpoint.stop()
+                    winner.callback(result)
+                @eachAttempt.addErrback
+                def failed(reason):
+                    failures.append(reason)
                     checkDone()
-                else:
-                    dconn = endpoint.connect(wf)
-                    pending.append(dconn)
-                    dconn.addBoth(usedEndpointRemoval, dconn)
-                    dconn.addCallback(afterConnectionAttempt)
-                    dconn.addErrback(connectFailed)
-
-            lc = LoopingCall(iterateEndpoint)
-            lc.clock = self._reactor
-            lc.start(0.3)
+
+            iterateEndpoint.clock = self._reactor
+            iterateEndpoint.start(0.3)
+            @winner.addBoth
+            def cancelRemainingPending(result):
+                checkDone.completed = True
+                for remaining in pending[:]:
+                    remaining.cancel()
+                return result
             return winner
 
-        d = self._nameResolution(self._host, self._port)
-        d.addErrback(errbackForGai)
-        d.addCallback(_endpoints)
-        d.addCallback(attemptConnection)
         return d
 
+
     def _nameResolution(self, host, port):
         """
         Resolve the hostname string into a tuple containig the host
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.