[Pkg-privacy-commits] [txtorcon] 13/49: Fix attacher handling (sync vs async)

Ximin Luo infinity0 at debian.org
Mon Oct 19 13:49:50 UTC 2015


This is an automated email from the git hooks/post-receive script.

infinity0 pushed a commit to branch master
in repository txtorcon.

commit 6f2ded338c7ab5f1c2a73a6cfa7b3ca57bd56574
Author: meejah <meejah at meejah.ca>
Date:   Sun May 31 14:36:29 2015 -0600

    Fix attacher handling (sync vs async)
    
    Unifies the sync versus async (Deferred) handling of the IStreamAttacher.attach
    callback, identifies non-Circuit returns and fixes the unit-tests and documentation
    to match.
---
 test/test_torstate.py |  16 ++++++--
 txtorcon/interface.py |   4 +-
 txtorcon/torstate.py  | 105 ++++++++++++++++++++++++++++----------------------
 3 files changed, 73 insertions(+), 52 deletions(-)

diff --git a/test/test_torstate.py b/test/test_torstate.py
index 5b35f3b..d526826 100644
--- a/test/test_torstate.py
+++ b/test/test_torstate.py
@@ -134,7 +134,7 @@ class FakeReactor:
         raise RuntimeError('connectUNIX: ' + str(args))
 
 
-class FakeCircuit:
+class FakeCircuit(Circuit):
 
     def __init__(self, id=-999):
         self.streams = []
@@ -555,6 +555,7 @@ class StateTests(unittest.TestCase):
                 return defer.succeed(self.answer)
 
         self.state.circuits[1] = FakeCircuit(1)
+        self.state.circuits[1].state = 'BUILT'
         attacher = MyAttacher(self.state.circuits[1])
         self.state.set_attacher(attacher, FakeReactor(self))
 
@@ -573,6 +574,7 @@ class StateTests(unittest.TestCase):
         self.assertEqual(len(self.protocol.commands), 1)
         self.assertEqual(self.protocol.commands[0][1], 'ATTACHSTREAM 1 1')
 
+    @defer.inlineCallbacks
     def test_attacher_errors(self):
         class MyAttacher(object):
             implements(IStreamAttacher)
@@ -592,7 +594,7 @@ class StateTests(unittest.TestCase):
         stream.id = 3
         msg = ''
         try:
-            self.state._maybe_attach(stream)
+            yield self.state._maybe_attach(stream)
         except Exception, e:
             msg = str(e)
         self.assertTrue('circuit unknown' in msg)
@@ -600,11 +602,19 @@ class StateTests(unittest.TestCase):
         attacher.answer = self.state.circuits[1]
         msg = ''
         try:
-            self.state._maybe_attach(stream)
+            yield self.state._maybe_attach(stream)
         except Exception, e:
             msg = str(e)
         self.assertTrue('only attach to BUILT' in msg)
 
+        attacher.answer = 'not a Circuit instance'
+        msg = ''
+        try:
+            yield self.state._maybe_attach(stream)
+        except Exception, e:
+            msg = str(e)
+        self.assertTrue('Circuit instance' in msg)
+
     def test_attacher_no_attach(self):
         class MyAttacher(object):
             implements(IStreamAttacher)
diff --git a/txtorcon/interface.py b/txtorcon/interface.py
index 8ccfded..7d4172e 100644
--- a/txtorcon/interface.py
+++ b/txtorcon/interface.py
@@ -106,9 +106,7 @@ class IStreamAttacher(Interface):
         also return a Deferred which will callback with the desired
         circuit. In this case, you will probably need to be aware that
         the callback from :meth:`txtorcon.TorState.build_circuit` does
-        NOT call back with a Circuit (just Tor's response of 'EXTEND
-        1234') and any circuit you do return must be in the BUILT
-        state anyway (which the above will not).
+        not wait for the circuit to be in BUILT state.
 
         See :ref:`attach_streams_by_country.py` for a complete
         example of using a Deferred in an IStreamAttacher.
diff --git a/txtorcon/torstate.py b/txtorcon/torstate.py
index c8679e4..8b0bb05 100644
--- a/txtorcon/torstate.py
+++ b/txtorcon/torstate.py
@@ -435,16 +435,16 @@ class TorState(object):
             self.attacher = None
 
         if self.attacher is None:
-            self.undo_attacher()
+            d = self.undo_attacher()
             if self.cleanup:
                 react.removeSystemEventTrigger(self.cleanup)
                 self.cleanup = None
 
         else:
-            self.protocol.set_conf("__LeaveStreamsUnattached", "1")
+            d = self.protocol.set_conf("__LeaveStreamsUnattached", "1")
             self.cleanup = react.addSystemEventTrigger('before', 'shutdown',
                                                        self.undo_attacher)
-        return None
+        return d
 
     stream_close_reasons = {
         'REASON_MISC': 1,               # (catch-all for unlisted reasons)
@@ -597,55 +597,68 @@ class TorState(object):
         (neither attaching it, nor telling Tor to attach it).
         """
 
-        if self.attacher:
-            if stream.target_host is not None \
-               and '.exit' in stream.target_host:
-                # we want to totally ignore .exit URIs as these are
-                # used to specify a particular exit node, and trying
-                # to do STREAMATTACH on them will fail with an error
-                # from Tor anyway.
-                txtorlog.msg("ignore attacher:", stream)
-                return
+        if self.attacher is None:
+            return None
+
+        if stream.target_host is not None \
+           and '.exit' in stream.target_host:
+            # we want to totally ignore .exit URIs as these are
+            # used to specify a particular exit node, and trying
+            # to do STREAMATTACH on them will fail with an error
+            # from Tor anyway.
+            txtorlog.msg("ignore attacher:", stream)
+            return
 
-            circ = self.attacher.attach_stream(stream, self.circuits)
-            if circ is self.DO_NOT_ATTACH:
-                return
+        # handle async or sync .attach() the same
+        circ_d = defer.maybeDeferred(
+            self.attacher.attach_stream,
+            stream, self.circuits,
+        )
 
+        # actually do the attachment logic; .attach() can return 3 things:
+        #    1. None: let Tor do whatever it wants
+        #    2. DO_NOT_ATTACH: don't attach the stream at all
+        #    3. Circuit instance: attach to the provided circuit
+        def issue_stream_attach(circ):
             if circ is None:
-                self.protocol.queue_command("ATTACHSTREAM %d 0" % stream.id)
+                # tell Tor to do what it likes
+                return self.protocol.queue_command("ATTACHSTREAM %d 0" % stream.id)
 
-            else:
-                if isinstance(circ, defer.Deferred):
-                    class IssueStreamAttach:
-                        def __init__(self, state, streamid):
-                            self.stream_id = streamid
-                            self.state = state
-
-                        def __call__(self, arg):
-                            if arg is None:
-                                return self.state.protocol.queue_command("ATTACHSTREAM %d 0" % stream.id)
-                            else:
-                                circid = arg.id
-                                return self.state.protocol.queue_command(
-                                    "ATTACHSTREAM %d %d" % (self.stream_id, circid)
-                                )
-
-                    circ.addCallback(IssueStreamAttach(self, stream.id))
-                    circ.addErrback(log.err)
+            elif circ is self.DO_NOT_ATTACH:
+                # do nothing; don't attach the stream
+                return
 
-                else:
-                    if circ.id not in self.circuits:
-                        raise RuntimeError(
-                            "Attacher returned a circuit unknown to me."
-                        )
-                    if circ.state != 'BUILT':
-                        raise RuntimeError(
-                            "Can only attach to BUILT circuits; %d is in %s." %
-                            (circ.id, circ.state)
-                        )
-                    self.protocol.queue_command(
-                        "ATTACHSTREAM %d %d" % (stream.id, circ.id)
+            else:
+                # should get a Circuit instance; check it for suitability
+                if not isinstance(circ, Circuit):
+                    raise RuntimeError(
+                        "IStreamAttacher.attach() must return a Circuit instance "
+                        "(or None or DO_NOT_ATTACH): %s"
+                    )
+                if circ.id not in self.circuits:
+                    raise RuntimeError(
+                        "Attacher returned a circuit unknown to me."
                     )
+                if circ.state != 'BUILT':
+                    raise RuntimeError(
+                        "Can only attach to BUILT circuits; %d is in %s." %
+                        (circ.id, circ.state)
+                    )
+                # we've got a valid Circuit instance; issue the command
+                return self.protocol.queue_command(
+                    "ATTACHSTREAM %d %d" % (stream.id, circ.id)
+                )
+
+        # not ideal, but there's not really a good way to let the
+        # caller handler errors :/ since we ultimately call this due
+        # to an async request from Tor. Mostly these errors will be
+        # logic or syntax errors in the caller's code anyway.
+        def _error(f):
+            print "Failure while attaching stream:", f
+            return f
+        circ_d.addCallback(issue_stream_attach)
+        circ_d.addErrback(_error)
+        return circ_d
 
     def _circuit_status(self, data):
         """Used internally as a callback for updating Circuit information"""

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-privacy/packages/txtorcon.git



More information about the Pkg-privacy-commits mailing list