[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