From 267a068fe4d45bffe5f38e93498b80066e837e8c Mon Sep 17 00:00:00 2001
From: Brian Warner Ok, now how do you acquire that RemoteReference
? How do you
make the Referenceable
available to the outside world? For this,
-we'll need to discuss the Tub
, and the concept of a PB URL
.Tub
, and the concept of a FURL
+URL
.
startService
and stopService
on the Tub
directly.
-Note that you must start the Tub before calling getReference
-or connectTo
, since both of these trigger network activity, and
-Tubs are supposed to be silent until they are started. In a future release
-this requirement may be relaxed, but the principle of "no network activity
-until the Tub is started" will be maintained, probably by queueing the
-getReference
calls and handling them after the Tub been
-started.
Note that no network activity will occur until the Tub's
+startService
method has been called. This means that any
+getReference
or connectTo
requests that occur
+before the Tub is started will be deferred until startup. If the program
+forgets to start the Tub, these requests will never be serviced. A message to
+this effect is added to the twistd.log file to help developers discover this
+kind of problem.
securereferences (meaning that no third party can cause you to connect to the wrong reference). You can also create a Tub with a pre-existing certificate, which is how Tubs can retain a persistent identity @@ -156,10 +157,10 @@ over multiple executions.
You can also create an UnauthenticatedTub
, which has an empty
TubID. Hosting and connecting to unauthenticated Tubs do not require the
pyOpenSSL library, but do not provide privacy, authentication, connection
-redirection, or shared listening ports. The PB-URLs that point to
+redirection, or shared listening ports. The FURLs that point to
unauthenticated Tubs have a distinct form (starting with pbu:
instead of pb:
) to make sure they are not mistaken for
-authenticated Tubs. PB uses authenticated Tubs by default.
Having the Tub listen on a TCP port is as simple as calling This returns the This returns the Since Here are two programs, one implementing the server side of our
@@ -333,7 +353,7 @@ the answer is 3
- In Foolscap, each world-accessible Referenceable has one or more URLs
which are To accomplish the first goal, PB URLs must be unguessable. You can
-register the reference with a human-readable name if your intention is to
-make it available to the world, but in general you will let
+ To accomplish the first goal, FURLs must be unguessable. You can register
+the reference with a human-readable name if your intention is to make it
+available to the world, but in general you will let
listenOn
with a
url = tub.registerReference(myserver, "math-service")
-
PB URL
for your Referenceable
. Remote
+FURL
for your Referenceable
. Remote
systems will use this URL to access your newly-published object. The
registration just maps a per-Tub name to the Referenceable
:
technically the same Referenceable
could be published multiple
@@ -279,6 +280,25 @@ d.addCallbacks(gotReference, gotError)
connection, if one is available, and it will return an existing
RemoteReference
, it one has already been acquired.getReference
requests are queued until the Tub starts,
+the following will work too. But don't forget to call
+tub.startService()
eventually, otherwise your program will hang
+forever.
+from foolscap import Tub
+
+tub = Tub()
+d = tub.getReference("pb://ABCD@myhost.example.com:12345/math-service")
+def gotReference(remote):
+ print "Got the RemoteReference:", remote
+def gotError(err):
+ print "error:", err
+d.addCallbacks(gotReference, gotError)
+tub.startService()
+
+
+
Complete example
PB URLs
+FURLs
secure
, where we use the capability-security definition of
@@ -350,9 +370,9 @@ the term, meaning those URLs have the following properties:tub.registerReference
generate a random name for you, preserving
the unguessability property.
Obviously this second property only holds if you use SSL. If you choose to use unauthenticated Tubs, all security properties are lost.
-The format of a PB URL, like +
The format of a FURL, like
pb://abcd123@example.com:5901,backup.example.com:8800/math-server
,
-is as followsnote that the PB URL uses the same format
+is as followsnote that the FURL uses the same format
as an HTTPSY
URL:
pb://ABCD@unix/path/to/socket/NAME
, but this needs
some work)
-PB URLs for unauthenticated Tubs, like +
FURLs for unauthenticated Tubs, like
pbu://example.com:8700/math-server
, are formatted as
follows:
twistd
to launch the program. The User side is
written with the same reactor.run()
style as the earlier
example.
-The server registers the Calculator instance and prints the PBURL at which -it is listening. You need to pass this PBURL to the client program so it -knows how to contact the servre. If you have a modern version of Twisted (2.5 -or later) and the right encryption libraries installed, you'll get an -authenticated Tub (for which the PBURL will start with "pb:" and will be +
The server registers the Calculator instance and prints the FURL at which +it is listening. You need to pass this FURL to the client program so it knows +how to contact the servre. If you have a modern version of Twisted (2.5 or +later) and the right encryption libraries installed, you'll get an +authenticated Tub (for which the FURL will start with "pb:" and will be fairly long). If you don't, you'll get an unauthenticated Tub (with a -relatively short PBURL that starts with "pbu:").
+relatively short FURL that starts with "pbu:"). pb3calculator.py @@ -766,7 +786,7 @@ suitably unique string (like a URI). from foolscap import RemoteInterface, schema class RIMath(RemoteInterface): - __remote_name__ = "RIMath.using-pb.docs.foolscap.twistedmatrix.com" + __remote_name__ = "RIMath.using-foolscap.docs.foolscap.twistedmatrix.com" def add(a=int, b=int): return int # declare it with an attribute instead of a function definition @@ -883,7 +903,7 @@ method is invoked), he will discover that he's holding a fully-functional class="footnote">and if everyone involved is using authenticated Tubs, then Foolscap offers a guarantee, in the cryptographic sense, that Bob will wind up with a reference to the same object that Alice intended. The authenticated -PBURLs prevent DNS-spoofing and man-in-the-middle attacks.. He can +FURLs prevent DNS-spoofing and man-in-the-middle attacks.. He can start using this RemoteReference right away:diff --git a/src/foolscap/foolscap/__init__.py b/src/foolscap/foolscap/__init__.py index e2243f42..ab58d559 100644 --- a/src/foolscap/foolscap/__init__.py +++ b/src/foolscap/foolscap/__init__.py @@ -1,6 +1,6 @@ """Foolscap""" -__version__ = "0.1.3" +__version__ = "0.1.4" # here are the primary entry points from foolscap.pb import Tub, UnauthenticatedTub, getRemoteURL_TCP diff --git a/src/foolscap/foolscap/call.py b/src/foolscap/foolscap/call.py index 8ed3af4b..2f1ccd6a 100644 --- a/src/foolscap/foolscap/call.py +++ b/src/foolscap/foolscap/call.py @@ -197,11 +197,14 @@ class InboundDelivery: class ArgumentUnslicer(slicer.ScopedUnslicer): methodSchema = None + debug = False def setConstraint(self, methodSchema): self.methodSchema = methodSchema def start(self, count): + if self.debug: + log.msg("%s.start: %s" % (self, count)) self.numargs = None self.args = [] self.kwargs = {} @@ -242,6 +245,11 @@ class ArgumentUnslicer(slicer.ScopedUnslicer): return unslicer def receiveChild(self, token, ready_deferred=None): + if self.debug: + log.msg("%s.receiveChild: %s %s %s %s %s args=%s kwargs=%s" % + (self, self.closed, self.num_unreferenceable_children, + self.num_unready_children, token, ready_deferred, + self.args, self.kwargs)) if self.numargs is None: # this token is the number of positional arguments assert isinstance(token, int) @@ -261,10 +269,14 @@ class ArgumentUnslicer(slicer.ScopedUnslicer): argpos = len(self.args) self.args.append(argvalue) if isinstance(argvalue, defer.Deferred): + # this may occur if the child is a gift which has not + # resolved yet. self.num_unreferenceable_children += 1 argvalue.addCallback(self.updateChild, argpos) argvalue.addErrback(self.explode) if ready_deferred: + if self.debug: + log.msg("%s.receiveChild got an unready posarg" % self) self.num_unready_children += 1 ready_deferred.addCallback(self.childReady) if len(self.args) < self.numargs: @@ -298,11 +310,13 @@ class ArgumentUnslicer(slicer.ScopedUnslicer): argvalue.addCallback(self.updateChild, self.argname) argvalue.addErrback(self.explode) if ready_deferred: + if self.debug: + log.msg("%s.receiveChild got an unready kwarg" % self) self.num_unready_children += 1 ready_deferred.addCallback(self.childReady) self.argname = None return - + def updateChild(self, obj, which): # one of our arguments has just now become referenceable. Normal # types can't trigger this (since the arguments to a method form a @@ -311,6 +325,9 @@ class ArgumentUnslicer(slicer.ScopedUnslicer): # RemoteReference, but for now all we get is a Deferred as a # placeholder. + if self.debug: + log.msg("%s.updateChild, [%s] became referenceable: %s" % + (self, which, obj)) if isinstance(which, int): self.args[which] = obj else: @@ -321,12 +338,22 @@ class ArgumentUnslicer(slicer.ScopedUnslicer): def childReady(self, obj): self.num_unready_children -= 1 + if self.debug: + log.msg("%s.childReady, now %d left" % + (self, self.num_unready_children)) + log.msg(" obj=%s, args=%s, kwargs=%s" % + (obj, self.args, self.kwargs)) self.checkComplete() return obj def checkComplete(self): # this is called each time one of our children gets updated or # becomes ready (like when a Gift is finally resolved) + if self.debug: + log.msg("%s.checkComplete: %s %s %s args=%s kwargs=%s" % + (self, self.closed, self.num_unreferenceable_children, + self.num_unready_children, self.args, self.kwargs)) + if not self.closed: return if self.num_unreferenceable_children: @@ -334,17 +361,25 @@ class ArgumentUnslicer(slicer.ScopedUnslicer): if self.num_unready_children: return # yup, we're done. Notify anyone who is still waiting + if self.debug: + log.msg(" we are ready") for d in self.watchers: eventually(d.callback, self) del self.watchers def receiveClose(self): + if self.debug: + log.msg("%s.receiveClose: %s %s %s" % + (self, self.closed, self.num_unreferenceable_children, + self.num_unready_children)) if (self.numargs is None or len(self.args) < self.numargs or self.argname is not None): raise BananaError("'arguments' sequence ended too early") self.closed = True self.watchers = [] + # we don't return a ready_deferred. Instead, the InboundDelivery + # object queries our isReady() method directly. return self, None def isReady(self): @@ -385,6 +420,8 @@ class ArgumentUnslicer(slicer.ScopedUnslicer): class CallUnslicer(slicer.ScopedUnslicer): + debug = False + def start(self, count): # start=0:reqID, 1:objID, 2:methodname, 3: arguments self.stage = 0 @@ -436,7 +473,9 @@ class CallUnslicer(slicer.ScopedUnslicer): def receiveChild(self, token, ready_deferred=None): assert not isinstance(token, defer.Deferred) assert ready_deferred is None - #print "CallUnslicer.receiveChild [s%d]" % self.stage, repr(token) + if self.debug: + log.msg("%s.receiveChild [s%d]: %s" % + (self, self.stage, repr(token))) if self.stage == 0: # reqID # we don't yet know which reqID to send any failure to diff --git a/src/foolscap/foolscap/pb.py b/src/foolscap/foolscap/pb.py index 9f7cfb9d..735f1dbc 100644 --- a/src/foolscap/foolscap/pb.py +++ b/src/foolscap/foolscap/pb.py @@ -6,7 +6,7 @@ from twisted.internet import defer, protocol from twisted.application import service, strports from twisted.python import log -from foolscap import ipb, base32, negotiate, broker, observer +from foolscap import ipb, base32, negotiate, broker, observer, eventual from foolscap.referenceable import SturdyRef from foolscap.tokens import PBError, BananaError from foolscap.reconnector import Reconnector @@ -242,6 +242,8 @@ class Tub(service.MultiService): self._activeConnectors = [] self._allConnectorsAreFinished = observer.OneShotObserverList() + self._pending_getReferences = [] # list of (d, furl) pairs + def setOption(self, name, value): if name == "logLocalFailures": # log (with log.err) any exceptions that occur during the @@ -376,6 +378,15 @@ class Tub(service.MultiService): if not self.running and not self._activeConnectors: self._allConnectorsAreFinished.fire(self) + def startService(self): + service.MultiService.startService(self) + for d,sturdy in self._pending_getReferences: + d1 = eventual.fireEventually(sturdy) + d1.addCallback(self.getReference) + d1.addBoth(lambda res, d=d: d.callback(res)) + del self._pending_getReferences + for rc in self.reconnectors: + eventual.eventually(rc.startConnecting, self) def _tubsAreNotRestartable(self): raise RuntimeError("Sorry, but Tubs cannot be restarted.") @@ -386,6 +397,7 @@ class Tub(service.MultiService): # note that once you stopService a Tub, I cannot be restarted. (at # least this code is not designed to make that possible.. it might be # doable in the future). + assert self.running self.startService = self._tubsAreNotRestartable self.getReference = self._tubHasBeenShutDown self.connectTo = self._tubHasBeenShutDown @@ -523,8 +535,6 @@ class Tub(service.MultiService): @return: a Deferred that fires with the RemoteReference """ - assert self.running - if isinstance(sturdyOrURL, SturdyRef): sturdy = sturdyOrURL else: @@ -538,12 +548,21 @@ class Tub(service.MultiService): "we cannot handle encrypted PB-URLs like %s" % sturdy.getURL()) return defer.fail(e) + + if not self.running: + # queue their request for service once the Tub actually starts + log.msg("Tub.getReference(%s) queued until Tub.startService called" + % sturdy) + d = defer.Deferred() + self._pending_getReferences.append((d, sturdy)) + return d + name = sturdy.name d = self.getBrokerForTubRef(sturdy.getTubRef()) d.addCallback(lambda b: b.getYourReferenceByName(name)) return d - def connectTo(self, sturdyOrURL, cb, *args, **kwargs): + def connectTo(self, _sturdyOrURL, _cb, *args, **kwargs): """Establish (and maintain) a connection to a given PBURL. I establish a connection to the PBURL and run a callback to inform @@ -582,8 +601,12 @@ class Tub(service.MultiService): rc.stopConnecting() # later """ - assert self.running - rc = Reconnector(self, sturdyOrURL, cb, *args, **kwargs) + rc = Reconnector(_sturdyOrURL, _cb, args, kwargs) + if self.running: + rc.startConnecting(self) + else: + log.msg("Tub.connectTo(%s) queued until Tub.startService called" + % _sturdyOrURL) self.reconnectors.append(rc) return rc diff --git a/src/foolscap/foolscap/reconnector.py b/src/foolscap/foolscap/reconnector.py index d22ab3e8..eace104e 100644 --- a/src/foolscap/foolscap/reconnector.py +++ b/src/foolscap/foolscap/reconnector.py @@ -42,17 +42,17 @@ class Reconnector: jitter = 0.11962656492 # molar Planck constant times c, Joule meter/mole verbose = False - def __init__(self, tub, url, cb, *args, **kwargs): - self._tub = tub + def __init__(self, url, cb, args, kwargs): self._url = url self._active = False self._observer = (cb, args, kwargs) self._delay = self.initialDelay self._retries = 0 self._timer = None - self.startConnecting() + self._tub = None - def startConnecting(self): + def startConnecting(self, tub): + self._tub = tub if self.verbose: log.msg("Reconnector starting for %s" % self._url) self._active = True @@ -65,7 +65,8 @@ class Reconnector: if self._timer: self._timer.cancel() self._timer = False - self._tub._removeReconnector(self) + if self._tub: + self._tub._removeReconnector(self) def _connect(self): d = self._tub.getReference(self._url) diff --git a/src/foolscap/foolscap/referenceable.py b/src/foolscap/foolscap/referenceable.py index 283705a7..d940429f 100644 --- a/src/foolscap/foolscap/referenceable.py +++ b/src/foolscap/foolscap/referenceable.py @@ -630,8 +630,18 @@ class TheirReferenceUnslicer(slicer.LeafUnslicer): d.addBoth(self.ackGift) # we return a Deferred that will fire with the RemoteReference when # it becomes available. The RemoteReference is not even referenceable - # until then. - return d,None + # until then. In addition, we provide a ready_deferred, since any + # mutable container which holds the gift will be referenceable early + # but the message delivery must still wait for the getReference to + # complete. See to it that we fire the object deferred before we fire + # the ready_deferred. + obj_deferred, ready_deferred = defer.Deferred(), defer.Deferred() + def _ready(rref): + obj_deferred.callback(rref) + ready_deferred.callback(rref) + d.addCallback(_ready) + + return obj_deferred, ready_deferred def ackGift(self, rref): rb = self.broker.remote_broker @@ -727,26 +737,6 @@ class SturdyRef(Copyable, RemoteCopy): cmp(self.__class__, them.__class__) or cmp(self._distinguishers(), them._distinguishers())) - def asLiveRef(self): - """Return an object that can be sent over the wire and unserialized - as a live RemoteReference on the far end. Use this when you have a - SturdyRef and want to give someone a reference to its target, but - when you haven't bothered to acquire your own live reference to it.""" - - return _AsLiveRef(self) - -class _AsLiveRef: - implements(ipb.ISlicer) - - def __init__(self, sturdy): - self.target = sturdy - - def slice(self, streamable, banana): - yield 'their-reference' - yield giftID - yield self.target.getURL() - yield [] # interfacenames - class TubRef: """This is a little helper class which provides a comparable identifier diff --git a/src/foolscap/foolscap/remoteinterface.py b/src/foolscap/foolscap/remoteinterface.py index 46df3616..f3fbdc09 100644 --- a/src/foolscap/foolscap/remoteinterface.py +++ b/src/foolscap/foolscap/remoteinterface.py @@ -105,7 +105,8 @@ def getRemoteInterface(obj): if isinstance(i, RemoteInterfaceClass): if i not in ilist: ilist.append(i) - assert len(ilist) <= 1, "don't use multiple RemoteInterfaces! %s" % (obj,) + assert len(ilist) <= 1, ("don't use multiple RemoteInterfaces! %s uses %s" + % (obj, ilist)) if ilist: return ilist[0] return None @@ -199,7 +200,8 @@ class RemoteMethodSchema: raise InvalidRemoteInterface(why) if not names: typeList = [] - if len(names) != len(typeList): + # 'def foo(oops)' results in typeList==None + if typeList is None or len(names) != len(typeList): # TODO: relax this, use schema=Any for the args that don't have # default values. This would make: # def foo(a, b=int): return None @@ -361,9 +363,18 @@ class RemoteInterfaceConstraint(OpenerConstraint): associated with a remote Referenceable that implements the given RemoteInterface. If 'interface' is None, just assert that it is a RemoteReference at all. + + On the inbound side, this will only accept a suitably-implementing + RemoteReference, or a gift that resolves to such a RemoteReference. On + the outbound side, this will accept either a Referenceable or a + RemoteReference (which might be a your-reference or a their-reference). + + Sending your-references will result in the recipient getting a local + Referenceable, which will not pass the constraint. TODO: think about if + we want this behavior or not. """ - opentypes = [("my-reference",)] - # TODO: accept their-references too + + opentypes = [("my-reference",), ("their-reference",)] name = "RemoteInterfaceConstraint" def __init__(self, interface): @@ -387,7 +398,17 @@ class RemoteInterfaceConstraint(OpenerConstraint): % (obj, self.interface)) else: # this ought to be a Referenceable which implements the desired - # interface + # interface. Or, it might be a RemoteReference which points to + # one. + if ipb.IRemoteReference.providedBy(obj): + # it's a RemoteReference + if not self.interface: + return + iface = obj.tracker.interface + if not iface or iface != self.interface: + raise Violation("'%s' does not provide RemoteInterface %s" + % (obj, self.interface)) + return if not ipb.IReferenceable.providedBy(obj): # TODO: maybe distinguish between OnlyReferenceable and # Referenceable? which is more useful here? diff --git a/src/foolscap/foolscap/slicers/allslicers.py b/src/foolscap/foolscap/slicers/allslicers.py index aa05f177..afb3b470 100644 --- a/src/foolscap/foolscap/slicers/allslicers.py +++ b/src/foolscap/foolscap/slicers/allslicers.py @@ -10,7 +10,7 @@ from foolscap.slicers.unicode import UnicodeSlicer, UnicodeUnslicer from foolscap.slicers.list import ListSlicer, ListUnslicer from foolscap.slicers.tuple import TupleSlicer, TupleUnslicer from foolscap.slicers.set import SetSlicer, SetUnslicer -from foolscap.slicers.set import ImmutableSetSlicer, ImmutableSetUnslicer +from foolscap.slicers.set import FrozenSetSlicer, FrozenSetUnslicer #from foolscap.slicers.set import BuiltinSetSlicer from foolscap.slicers.dict import DictSlicer, DictUnslicer, OrderedDictSlicer from foolscap.slicers.vocab import ReplaceVocabSlicer, ReplaceVocabUnslicer @@ -26,7 +26,7 @@ unused = [ ListSlicer, ListUnslicer, TupleSlicer, TupleUnslicer, SetSlicer, SetUnslicer, - ImmutableSetSlicer, ImmutableSetUnslicer, + FrozenSetSlicer, FrozenSetUnslicer, #from foolscap.slicers.set import BuiltinSetSlicer DictSlicer, DictUnslicer, OrderedDictSlicer, ReplaceVocabSlicer, ReplaceVocabUnslicer, diff --git a/src/foolscap/foolscap/slicers/dict.py b/src/foolscap/foolscap/slicers/dict.py index 6cd30c29..23a69ca3 100644 --- a/src/foolscap/foolscap/slicers/dict.py +++ b/src/foolscap/foolscap/slicers/dict.py @@ -1,7 +1,7 @@ # -*- test-case-name: foolscap.test.test_banana -*- from twisted.python import log -from twisted.internet.defer import Deferred +from twisted.internet.defer import Deferred, DeferredList from foolscap.tokens import Violation, BananaError from foolscap.slicer import BaseSlicer, BaseUnslicer from foolscap.constraint import OpenerConstraint, Any, UnboundedSchema, IConstraint @@ -35,6 +35,7 @@ class DictUnslicer(BaseUnslicer): self.d = {} self.protocol.setObject(count, self.d) self.key = None + self._ready_deferreds = [] def checkToken(self, typebyte, size): if self.maxKeys != None: @@ -72,8 +73,8 @@ class DictUnslicer(BaseUnslicer): self.d[key] = value def receiveChild(self, obj, ready_deferred=None): - assert not isinstance(obj, Deferred) - assert ready_deferred is None + if ready_deferred: + self._ready_deferreds.append(ready_deferred) if self.gettingKey: self.receiveKey(obj) else: @@ -102,7 +103,10 @@ class DictUnslicer(BaseUnslicer): self.d[self.key] = value # placeholder def receiveClose(self): - return self.d, None + ready_deferred = None + if self._ready_deferreds: + ready_deferred = DeferredList(self._ready_deferreds) + return self.d, ready_deferred def describe(self): if self.gettingKey: diff --git a/src/foolscap/foolscap/slicers/list.py b/src/foolscap/foolscap/slicers/list.py index 4e8a0db2..70ad55f8 100644 --- a/src/foolscap/foolscap/slicers/list.py +++ b/src/foolscap/foolscap/slicers/list.py @@ -1,7 +1,7 @@ # -*- test-case-name: foolscap.test.test_banana -*- from twisted.python import log -from twisted.internet.defer import Deferred +from twisted.internet.defer import Deferred, DeferredList from foolscap.tokens import Violation from foolscap.slicer import BaseSlicer, BaseUnslicer from foolscap.constraint import OpenerConstraint, Any, UnboundedSchema, IConstraint @@ -35,8 +35,9 @@ class ListUnslicer(BaseUnslicer): self.list = [] self.count = count if self.debug: - print "%s[%d].start with %s" % (self, self.count, self.list) + log.msg("%s[%d].start with %s" % (self, self.count, self.list)) self.protocol.setObject(count, self.list) + self._ready_deferreds = [] def checkToken(self, typebyte, size): if self.maxLength != None and len(self.list) >= self.maxLength: @@ -65,15 +66,16 @@ class ListUnslicer(BaseUnslicer): def update(self, obj, index): # obj has already passed typechecking if self.debug: - print "%s[%d].update: [%d]=%s" % (self, self.count, index, obj) + log.msg("%s[%d].update: [%d]=%s" % (self, self.count, index, obj)) assert isinstance(index, int) self.list[index] = obj return obj def receiveChild(self, obj, ready_deferred=None): - assert ready_deferred is None + if ready_deferred: + self._ready_deferreds.append(ready_deferred) if self.debug: - print "%s[%d].receiveChild(%s)" % (self, self.count, obj) + log.msg("%s[%d].receiveChild(%s)" % (self, self.count, obj)) # obj could be a primitive type, a Deferred, or a complex type like # those returned from an InstanceUnslicer. However, the individual # object has already been through the schema validation process. The @@ -86,10 +88,12 @@ class ListUnslicer(BaseUnslicer): raise Violation("the list is full") if isinstance(obj, Deferred): if self.debug: - print " adding my update[%d] to %s" % (len(self.list), obj) + log.msg(" adding my update[%d] to %s" % (len(self.list), obj)) obj.addCallback(self.update, len(self.list)) obj.addErrback(self.printErr) - self.list.append("placeholder") + placeholder = "list placeholder for arg[%d], rd=%s" % \ + (len(self.list), ready_deferred) + self.list.append(placeholder) else: self.list.append(obj) @@ -99,7 +103,10 @@ class ListUnslicer(BaseUnslicer): log.err(why) def receiveClose(self): - return self.list, None + ready_deferred = None + if self._ready_deferreds: + ready_deferred = DeferredList(self._ready_deferreds) + return self.list, ready_deferred def describe(self): return "[%d]" % len(self.list) diff --git a/src/foolscap/foolscap/slicers/set.py b/src/foolscap/foolscap/slicers/set.py index 206507f2..f2afa09c 100644 --- a/src/foolscap/foolscap/slicers/set.py +++ b/src/foolscap/foolscap/slicers/set.py @@ -1,7 +1,11 @@ # -*- test-case-name: foolscap.test.test_banana -*- import sets -from foolscap.slicers.list import ListSlicer, ListUnslicer +from twisted.internet import defer +from twisted.python import log +from foolscap.slicers.list import ListSlicer +from foolscap.slicers.tuple import TupleUnslicer +from foolscap.slicer import BaseUnslicer from foolscap.tokens import Violation from foolscap.constraint import OpenerConstraint, UnboundedSchema, Any, \ IConstraint @@ -9,34 +13,41 @@ from foolscap.constraint import OpenerConstraint, UnboundedSchema, Any, \ class SetSlicer(ListSlicer): opentype = ("set",) trackReferences = True - slices = sets.Set + slices = set def sliceBody(self, streamable, banana): for i in self.obj: yield i -class ImmutableSetSlicer(SetSlicer): +class FrozenSetSlicer(SetSlicer): opentype = ("immutable-set",) trackReferences = False + slices = frozenset + +# python2.4 has a builtin 'set' type, which is mutable, and we require +# python2.4 or newer. Code which was written to be compatible with python2.3, +# however, may use the 'sets' module. We will serialize old sets.Set and +# sets.ImmutableSet the same as we serialize new set and frozenset. +# Unfortunately this means that these objects will be deserialized as modern +# 'set' and 'frozenset' objects, which are not entirely compatible. Therefore +# code that is compatible with python2.3 might not work with foolscap. + +class OldSetSlicer(SetSlicer): + slices = sets.Set +class OldImmutableSetSlicer(FrozenSetSlicer): slices = sets.ImmutableSet -have_builtin_set = False -try: - set - # python2.4 has a builtin 'set' type, which is mutable - have_builtin_set = True - class BuiltinSetSlicer(SetSlicer): - slices = set - class BuiltinFrozenSetSlicer(ImmutableSetSlicer): - slices = frozenset -except NameError: - # oh well, I guess we don't have 'set' +class _Placeholder: pass -class SetUnslicer(ListUnslicer): +class SetUnslicer(BaseUnslicer): + # this is a lot like a list, but sufficiently different to make it not + # worth subclassing opentype = ("set",) - def receiveClose(self): - return sets.Set(self.list), None + + debug = False + maxLength = None + itemConstraint = None def setConstraint(self, constraint): if isinstance(constraint, Any): @@ -45,10 +56,101 @@ class SetUnslicer(ListUnslicer): self.maxLength = constraint.maxLength self.itemConstraint = constraint.constraint -class ImmutableSetUnslicer(SetUnslicer): + def start(self, count): + #self.opener = foo # could replace it if we wanted to + self.set = set() + self.count = count + if self.debug: + log.msg("%s[%d].start with %s" % (self, self.count, self.set)) + self.protocol.setObject(count, self.set) + self._ready_deferreds = [] + + def checkToken(self, typebyte, size): + if self.maxLength != None and len(self.set) >= self.maxLength: + # list is full, no more tokens accepted + # this is hit if the max+1 item is a primitive type + raise Violation("the set is full") + if self.itemConstraint: + self.itemConstraint.checkToken(typebyte, size) + + def doOpen(self, opentype): + # decide whether the given object type is acceptable here. Raise a + # Violation exception if not, otherwise give it to our opener (which + # will normally be the RootUnslicer). Apply a constraint to the new + # unslicer. + if self.maxLength != None and len(self.set) >= self.maxLength: + # this is hit if the max+1 item is a non-primitive type + raise Violation("the set is full") + if self.itemConstraint: + self.itemConstraint.checkOpentype(opentype) + unslicer = self.open(opentype) + if unslicer: + if self.itemConstraint: + unslicer.setConstraint(self.itemConstraint) + return unslicer + + def update(self, obj, placeholder): + # obj has already passed typechecking + if self.debug: + log.msg("%s[%d].update: [%s]=%s" % (self, self.count, + placeholder, obj)) + self.set.remove(placeholder) + self.set.add(obj) + return obj + + def receiveChild(self, obj, ready_deferred=None): + if ready_deferred: + self._ready_deferreds.append(ready_deferred) + if self.debug: + log.msg("%s[%d].receiveChild(%s)" % (self, self.count, obj)) + # obj could be a primitive type, a Deferred, or a complex type like + # those returned from an InstanceUnslicer. However, the individual + # object has already been through the schema validation process. The + # only remaining question is whether the larger schema will accept + # it. + if self.maxLength != None and len(self.set) >= self.maxLength: + # this is redundant + # (if it were a non-primitive one, it would be caught in doOpen) + # (if it were a primitive one, it would be caught in checkToken) + raise Violation("the set is full") + if isinstance(obj, defer.Deferred): + if self.debug: + log.msg(" adding my update[%d] to %s" % (len(self.set), obj)) + # note: the placeholder isn't strictly necessary, but it will + # help debugging to see a _Placeholder sitting in the set when it + # shouldn't rather than seeing a set that is smaller than it + # ought to be. If a remote method ever sees a _Placeholder, then + # something inside Foolscap has broken. + placeholder = _Placeholder() + obj.addCallback(self.update, placeholder) + obj.addErrback(self.printErr) + self.set.add(placeholder) + else: + self.set.add(obj) + + def printErr(self, why): + print "ERR!" + print why.getBriefTraceback() + log.err(why) + + def receiveClose(self): + ready_deferred = None + if self._ready_deferreds: + ready_deferred = defer.DeferredList(self._ready_deferreds) + return self.set, ready_deferred + +class FrozenSetUnslicer(TupleUnslicer): opentype = ("immutable-set",) + def receiveClose(self): - return sets.ImmutableSet(self.list), None + obj_or_deferred, ready_deferred = TupleUnslicer.receiveClose(self) + if isinstance(obj_or_deferred, defer.Deferred): + def _convert(the_tuple): + return frozenset(the_tuple) + obj_or_deferred.addCallback(_convert) + else: + obj_or_deferred = frozenset(obj_or_deferred) + return obj_or_deferred, ready_deferred class SetConstraint(OpenerConstraint): @@ -64,12 +166,8 @@ class SetConstraint(OpenerConstraint): opentypes = [("set",), ("immutable-set",)] name = "SetConstraint" - if have_builtin_set: - mutable_set_types = (set, sets.Set) - immutable_set_types = (frozenset, sets.ImmutableSet) - else: - mutable_set_types = (sets.Set,) - immutable_set_types = (sets.ImmutableSet,) + mutable_set_types = (set, sets.Set) + immutable_set_types = (frozenset, sets.ImmutableSet) all_set_types = mutable_set_types + immutable_set_types def __init__(self, constraint, maxLength=30, mutable=None): diff --git a/src/foolscap/foolscap/slicers/tuple.py b/src/foolscap/foolscap/slicers/tuple.py index f7ea5657..6469f822 100644 --- a/src/foolscap/foolscap/slicers/tuple.py +++ b/src/foolscap/foolscap/slicers/tuple.py @@ -1,6 +1,6 @@ # -*- test-case-name: foolscap.test.test_banana -*- -from twisted.internet.defer import Deferred +from twisted.internet.defer import Deferred, DeferredList from foolscap.tokens import Violation from foolscap.slicer import BaseUnslicer from foolscap.slicers.list import ListSlicer @@ -34,6 +34,7 @@ class TupleUnslicer(BaseUnslicer): self.finished = False self.deferred = Deferred() self.protocol.setObject(count, self.deferred) + self._ready_deferreds = [] def checkToken(self, typebyte, size): if self.constraints == None: @@ -64,7 +65,8 @@ class TupleUnslicer(BaseUnslicer): return obj def receiveChild(self, obj, ready_deferred=None): - assert ready_deferred is None + if ready_deferred: + self._ready_deferreds.append(ready_deferred) if isinstance(obj, Deferred): obj.addCallback(self.update, len(self.list)) obj.addErrback(self.explode) @@ -81,20 +83,39 @@ class TupleUnslicer(BaseUnslicer): # not finished yet, we'll fire our Deferred when we are if self.debug: print " not finished yet" - return self.deferred, None + return + # list is now complete. We can finish. + return self.complete() + + def complete(self): + ready_deferred = None + if self._ready_deferreds: + ready_deferred = DeferredList(self._ready_deferreds) + t = tuple(self.list) if self.debug: print " finished! tuple:%s{%s}" % (t, id(t)) self.protocol.setObject(self.count, t) self.deferred.callback(t) - return t, None + return t, ready_deferred def receiveClose(self): if self.debug: print "%s[%d].receiveClose" % (self, self.count) self.finished = 1 - return self.checkComplete() + + if self.num_unreferenceable_children: + # not finished yet, we'll fire our Deferred when we are + if self.debug: + print " not finished yet" + ready_deferred = None + if self._ready_deferreds: + ready_deferred = DeferredList(self._ready_deferreds) + return self.deferred, ready_deferred + + # the list is already complete + return self.complete() def describe(self): return "[%d]" % len(self.list) diff --git a/src/foolscap/foolscap/test/test_banana.py b/src/foolscap/foolscap/test/test_banana.py index 5951cafe..2c95c3f7 100644 --- a/src/foolscap/foolscap/test/test_banana.py +++ b/src/foolscap/foolscap/test/test_banana.py @@ -1597,18 +1597,13 @@ class ThereAndBackAgain(TestBananaMixin, unittest.TestCase): def test_tuple(self): return self.looptest((1,2)) def test_set(self): - d = self.looptest(sets.Set([1,2])) - d.addCallback(lambda res: self.looptest(sets.ImmutableSet([1,2]))) - # verify the python2.4 builtin 'set' type, which is mutable - try: - set - have_set = True - except NameError: - have_set = False - if have_set: - # we serialize builtin 'set' as a regular mutable sets.Set - d.addCallback(lambda res: - self.looptest(set([1,2]), sets.Set([1,2]))) + d = self.looptest(set([1,2])) + d.addCallback(lambda res: self.looptest(frozenset([1,2]))) + # and verify that old sets turn into modern ones, which is + # unfortunate but at least consistent + d.addCallback(lambda res: self.looptest(sets.Set([1,2]), set([1,2]))) + d.addCallback(lambda res: self.looptest(sets.ImmutableSet([1,2]), + frozenset([1,2]))) return d def test_bool(self): @@ -1709,15 +1704,71 @@ class ThereAndBackAgain(TestBananaMixin, unittest.TestCase): self.assertIdentical(z[0]['list'], z[1]) self.assertIdentical(z[0]['list'][0], z) - def testMoreReferences(self): + def test_cycles_1(self): + # a list that contains a tuple that can't be referenced yet a = [] - t = (a,) - t2 = (t,) + t1 = (a,) + t2 = (t1,) a.append(t2) - d = self.loop(t) + d = self.loop(t1) d.addCallback(lambda z: self.assertIdentical(z[0][0][0], z)) return d + def test_cycles_2(self): + # a dict that contains a tuple that can't be referenced yet. + a = {} + t1 = (a,) + t2 = (t1,) + a['foo'] = t2 + d = self.loop(t1) + d.addCallback(lambda z: self.assertIdentical(z[0]['foo'][0], z)) + return d + + def test_cycles_3(self): + # sets seem to be transitively immutable: any mutable contents would + # be unhashable, and sets can only contain hashable objects. + # Therefore sets cannot participate in cycles the way that tuples + # can. + + # a set that contains a tuple that can't be referenced yet. You can't + # actually create this in python, because you can only create a set + # out of hashable objects, and sets aren't hashable, and a tuple that + # contains a set is not hashable. + a = set() + t1 = (a,) + t2 = (t1,) + a.add(t2) + d = self.loop(t1) + d.addCallback(lambda z: self.assertIdentical(list(z[0])[0][0], z)) + + # a list that contains a frozenset that can't be referenced yet + a = [] + t1 = frozenset([a]) + t2 = frozenset([t1]) + a.append(t2) + d = self.loop(t1) + d.addCallback(lambda z: + self.assertIdentical(list(list(z)[0][0])[0], z)) + + # a dict that contains a frozenset that can't be referenced yet. + a = {} + t1 = frozenset([a]) + t2 = frozenset([t1]) + a['foo'] = t2 + d = self.loop(t1) + d.addCallback(lambda z: + self.assertIdentical(list(list(z)[0]['foo'])[0], z)) + + # a set that contains a frozenset that can't be referenced yet. + a = set() + t1 = frozenset([a]) + t2 = frozenset([t1]) + a.add(t2) + d = self.loop(t1) + d.addCallback(lambda z: self.assertIdentical(list(list(list(z)[0])[0])[0], z)) + return d + del test_cycles_3 + class VocabTest1(unittest.TestCase): diff --git a/src/foolscap/foolscap/test/test_call.py b/src/foolscap/foolscap/test/test_call.py index 04dc4abc..79e19beb 100644 --- a/src/foolscap/foolscap/test/test_call.py +++ b/src/foolscap/foolscap/test/test_call.py @@ -9,7 +9,6 @@ if False: log.startLogging(sys.stderr) from twisted.python import failure, log -from twisted.internet import reactor, defer from twisted.trial import unittest from twisted.internet.main import CONNECTION_LOST @@ -36,7 +35,6 @@ class TestCall(TargetMixin, unittest.TestCase): d.addCallback(lambda res: self.failUnlessEqual(target.calls, [(1,2)])) d.addCallback(self._testCall1_1, rr) return d - testCall1.timeout = 3 def _testCall1_1(self, res, rr): # the caller still holds the RemoteReference self.failUnless(self.callingBroker.yourReferenceByCLID.has_key(1)) @@ -46,10 +44,14 @@ class TestCall(TargetMixin, unittest.TestCase): # the targetBroker so *they* can forget about it. del rr # this fires a DecRef gc.collect() # make sure + # we need to give it a moment to deliver the DecRef message and act - # on it - d = defer.Deferred() - reactor.callLater(0.1, d.callback, None) + # on it. Poll until the caller has received it. + def _check(): + if self.callingBroker.yourReferenceByCLID.has_key(1): + return False + return True + d = self.poll(_check) d.addCallback(self._testCall1_2) return d def _testCall1_2(self, res): diff --git a/src/foolscap/foolscap/test/test_gifts.py b/src/foolscap/foolscap/test/test_gifts.py index 3775e657..db6dfd78 100644 --- a/src/foolscap/foolscap/test/test_gifts.py +++ b/src/foolscap/foolscap/test/test_gifts.py @@ -1,10 +1,11 @@ +from zope.interface import implements from twisted.trial import unittest from twisted.internet import defer from twisted.internet.error import ConnectionDone, ConnectionLost -from foolscap import Tub, UnauthenticatedTub +from foolscap import Tub, UnauthenticatedTub, RemoteInterface, Referenceable from foolscap.referenceable import RemoteReference -from foolscap.test.common import HelperTarget +from foolscap.test.common import HelperTarget, RIHelper from foolscap.eventual import flushEventualQueue crypto_available = False @@ -24,6 +25,19 @@ def ignoreConnectionDone(f): f.trap(ConnectionDone, ConnectionLost) return None +class RIConstrainedHelper(RemoteInterface): + def set(obj=RIHelper): return None + + +class ConstrainedHelper(Referenceable): + implements(RIConstrainedHelper) + + def __init__(self, name="unnamed"): + self.name = name + + def remote_set(self, obj): + self.obj = obj + class Gifts(unittest.TestCase): # Here we test the three-party introduction process as depicted in the # classic Granovetter diagram. Alice has a reference to Bob and another @@ -52,9 +66,15 @@ class Gifts(unittest.TestCase): self.bob_url = self.tubB.registerReference(self.bob) self.carol = HelperTarget("carol") self.carol_url = self.tubC.registerReference(self.carol) - self.cindy = HelperTarget("cindy") # cindy is Carol's little sister. She doesn't have a phone, but # Carol might talk about her anyway. + self.cindy = HelperTarget("cindy") + # more sisters. Alice knows them, and she introduces Bob to them. + self.charlene = HelperTarget("charlene") + self.christine = HelperTarget("christine") + self.clarisse = HelperTarget("clarisse") + self.colette = HelperTarget("colette") + self.courtney = HelperTarget("courtney") def createInitialReferences(self): # we must start by giving Alice a reference to both Bob and Carol. @@ -73,6 +93,46 @@ class Gifts(unittest.TestCase): d.addCallback(_aliceGotCarol) return d + def createMoreReferences(self): + # give Alice references to Carol's sisters + dl = [] + + url = self.tubC.registerReference(self.charlene) + d = self.tubA.getReference(url) + def _got_charlene(rref): + self.acharlene = rref + d.addCallback(_got_charlene) + dl.append(d) + + url = self.tubC.registerReference(self.christine) + d = self.tubA.getReference(url) + def _got_christine(rref): + self.achristine = rref + d.addCallback(_got_christine) + dl.append(d) + + url = self.tubC.registerReference(self.clarisse) + d = self.tubA.getReference(url) + def _got_clarisse(rref): + self.aclarisse = rref + d.addCallback(_got_clarisse) + dl.append(d) + + url = self.tubC.registerReference(self.colette) + d = self.tubA.getReference(url) + def _got_colette(rref): + self.acolette = rref + d.addCallback(_got_colette) + dl.append(d) + + url = self.tubC.registerReference(self.courtney) + d = self.tubA.getReference(url) + def _got_courtney(rref): + self.acourtney = rref + d.addCallback(_got_courtney) + dl.append(d) + return defer.DeferredList(dl) + def testGift(self): #defer.setDebugging(True) self.createCharacters() @@ -189,6 +249,76 @@ class Gifts(unittest.TestCase): d.addCallback(_checkBob) return d + def testContainers(self): + self.createCharacters() + self.bob.calls = [] + d = self.createInitialReferences() + d.addCallback(lambda res: self.createMoreReferences()) + def _introduce(res): + # we send several messages to Bob, each of which has a container + # with a gift inside it. This exercises the ready_deferred + # handling inside containers. + dl = [] + cr = self.abob.callRemote + dl.append(cr("append", set([self.acharlene]))) + dl.append(cr("append", frozenset([self.achristine]))) + dl.append(cr("append", [self.aclarisse])) + dl.append(cr("append", obj=(self.acolette,))) + dl.append(cr("append", {'a': self.acourtney})) + # TODO: pass a gift as an attribute of a Copyable + return defer.DeferredList(dl) + d.addCallback(_introduce) + def _checkBob(res): + # this runs after all three messages have been acked by Bob + self.failUnlessEqual(len(self.bob.calls), 5) + + bcharlene = self.bob.calls.pop(0) + self.failUnless(isinstance(bcharlene, set)) + self.failUnlessEqual(len(bcharlene), 1) + self.failUnless(isinstance(list(bcharlene)[0], RemoteReference)) + + bchristine = self.bob.calls.pop(0) + self.failUnless(isinstance(bchristine, frozenset)) + self.failUnlessEqual(len(bchristine), 1) + self.failUnless(isinstance(list(bchristine)[0], RemoteReference)) + + bclarisse = self.bob.calls.pop(0) + self.failUnless(isinstance(bclarisse, list)) + self.failUnlessEqual(len(bclarisse), 1) + self.failUnless(isinstance(bclarisse[0], RemoteReference)) + + bcolette = self.bob.calls.pop(0) + self.failUnless(isinstance(bcolette, tuple)) + self.failUnlessEqual(len(bcolette), 1) + self.failUnless(isinstance(bcolette[0], RemoteReference)) + + bcourtney = self.bob.calls.pop(0) + self.failUnless(isinstance(bcourtney, dict)) + self.failUnlessEqual(len(bcourtney), 1) + self.failUnless(isinstance(bcourtney['a'], RemoteReference)) + + d.addCallback(_checkBob) + return d + + def create_constrained_characters(self): + self.alice = HelperTarget("alice") + self.bob = ConstrainedHelper("bob") + self.bob_url = self.tubB.registerReference(self.bob) + self.carol = HelperTarget("carol") + self.carol_url = self.tubC.registerReference(self.carol) + + def test_constraint(self): + self.create_constrained_characters() + self.bob.calls = [] + d = self.createInitialReferences() + def _introduce(res): + return self.abob.callRemote("set", self.acarol) + d.addCallback(_introduce) + def _checkBob(res): + self.failUnless(isinstance(self.bob.obj, RemoteReference)) + d.addCallback(_checkBob) + return d + # this was used to alice's reference to carol (self.acarol) appeared in # alice's gift table at the right time, to make sure that the # RemoteReference is kept alive while the gift is in transit. The whole diff --git a/src/foolscap/foolscap/test/test_schema.py b/src/foolscap/foolscap/test/test_schema.py index 671b7a73..0c052e78 100644 --- a/src/foolscap/foolscap/test/test_schema.py +++ b/src/foolscap/foolscap/test/test_schema.py @@ -2,7 +2,7 @@ import sets, re from twisted.trial import unittest from foolscap import schema, copyable -from foolscap.tokens import Violation +from foolscap.tokens import Violation, InvalidRemoteInterface from foolscap.constraint import IConstraint from foolscap.remoteinterface import RemoteMethodSchema, \ RemoteInterfaceConstraint, LocalInterfaceConstraint @@ -474,6 +474,13 @@ class Arguments(unittest.TestCase): self.failUnlessRaises(schema.Violation, r.checkResults, 12, False) + def test_bad_arguments(self): + def foo(nodefault): return str + self.failUnlessRaises(InvalidRemoteInterface, + RemoteMethodSchema, method=foo) + def bar(nodefault, a=int): return str + self.failUnlessRaises(InvalidRemoteInterface, + RemoteMethodSchema, method=bar) class Interfaces(unittest.TestCase): @@ -520,9 +527,11 @@ class Interfaces(unittest.TestCase): interfaceName = common.RIHelper.__remote_name__ tracker = RemoteReferenceTracker(parent, clid, url, interfaceName) rr = RemoteReference(tracker) + c1 = RemoteInterfaceConstraint(common.RIHelper) - c2 = RemoteInterfaceConstraint(common.RIMyTarget) self.check_inbound(rr, c1) - self.violates_outbound(rr, c1) + self.check_outbound(rr, c1) # gift + + c2 = RemoteInterfaceConstraint(common.RIMyTarget) self.violates_inbound(rr, c2) self.violates_outbound(rr, c2) diff --git a/src/foolscap/foolscap/test/test_tub.py b/src/foolscap/foolscap/test/test_tub.py index 4b0d31b1..8f36a524 100644 --- a/src/foolscap/foolscap/test/test_tub.py +++ b/src/foolscap/foolscap/test/test_tub.py @@ -2,6 +2,7 @@ import os.path from twisted.trial import unittest +from twisted.internet import defer crypto_available = False try: @@ -10,7 +11,16 @@ try: except ImportError: pass -from foolscap import Tub +from foolscap import Tub, UnauthenticatedTub +from foolscap.referenceable import RemoteReference +from foolscap.eventual import eventually, flushEventualQueue +from foolscap.test.common import HelperTarget, TargetMixin + +# we use authenticated tubs if possible. If crypto is not available, fall +# back to unauthenticated ones +GoodEnoughTub = UnauthenticatedTub +if crypto_available: + GoodEnoughTub = Tub class TestCertFile(unittest.TestCase): def test_generate(self): @@ -38,3 +48,72 @@ class TestCertFile(unittest.TestCase): if not crypto_available: del TestCertFile + +class QueuedStartup(TargetMixin, unittest.TestCase): + # calling getReference and connectTo before the Tub has started should + # put off network activity until the Tub is started. + + def setUp(self): + TargetMixin.setUp(self) + self.tubB = GoodEnoughTub() + self.services = [self.tubB] + for s in self.services: + s.startService() + l = s.listenOn("tcp:0:interface=127.0.0.1") + s.setLocation("127.0.0.1:%d" % l.getPortnum()) + + self.barry = HelperTarget("barry") + self.barry_url = self.tubB.registerReference(self.barry) + + self.bill = HelperTarget("bill") + self.bill_url = self.tubB.registerReference(self.bill) + + self.bob = HelperTarget("bob") + self.bob_url = self.tubB.registerReference(self.bob) + + def tearDown(self): + d = TargetMixin.tearDown(self) + def _more(res): + return defer.DeferredList([s.stopService() for s in self.services]) + d.addCallback(_more) + d.addCallback(flushEventualQueue) + return d + + def test_queued_getref(self): + t1 = GoodEnoughTub() + d1 = t1.getReference(self.barry_url) + d2 = t1.getReference(self.bill_url) + def _check(res): + ((barry_success, barry_rref), + (bill_success, bill_rref)) = res + self.failUnless(barry_success) + self.failUnless(bill_success) + self.failUnless(isinstance(barry_rref, RemoteReference)) + self.failUnless(isinstance(bill_rref, RemoteReference)) + self.failIf(barry_rref == bill_success) + dl = defer.DeferredList([d1, d2]) + dl.addCallback(_check) + self.services.append(t1) + eventually(t1.startService) + return dl + + def test_queued_reconnector(self): + t1 = GoodEnoughTub() + bill_connections = [] + barry_connections = [] + t1.connectTo(self.bill_url, bill_connections.append) + t1.connectTo(self.barry_url, barry_connections.append) + def _check(): + if len(bill_connections) >= 1 and len(barry_connections) >= 1: + return True + return False + d = self.poll(_check) + def _validate(res): + self.failUnless(isinstance(bill_connections[0], RemoteReference)) + self.failUnless(isinstance(barry_connections[0], RemoteReference)) + self.failIf(bill_connections[0] == barry_connections[0]) + d.addCallback(_validate) + self.services.append(t1) + eventually(t1.startService) + return d + diff --git a/src/foolscap/misc/dapper/debian/changelog b/src/foolscap/misc/dapper/debian/changelog index f3331be3..9699a6df 100644 --- a/src/foolscap/misc/dapper/debian/changelog +++ b/src/foolscap/misc/dapper/debian/changelog @@ -1,14 +1,14 @@ -foolscap (0.1.3) unstable; urgency=low +foolscap (0.1.4) unstable; urgency=low * new release - -- Brian WarnerWed, 2 May 2007 14:55:49 -0700 + -- Brian Warner Mon, 14 May 2007 22:37:04 -0700 -foolscap (0.1.2+) unstable; urgency=low +foolscap (0.1.3) unstable; urgency=low - * bump revision while between releases + * new release - -- Brian Warner Fri, 13 Apr 2007 00:22:10 -0700 + -- Brian Warner Wed, 2 May 2007 14:55:49 -0700 foolscap (0.1.2) unstable; urgency=low @@ -16,79 +16,43 @@ foolscap (0.1.2) unstable; urgency=low -- Brian Warner Wed, 4 Apr 2007 12:32:46 -0700 -foolscap (0.1.1+) unstable; urgency=low - - * bump revision while between releases - - -- Brian Warner Wed, 4 Apr 2007 10:32:22 -0700 - foolscap (0.1.1) unstable; urgency=low * new release -- Brian Warner Tue, 3 Apr 2007 20:48:07 -0700 -foolscap (0.1.0+) unstable; urgency=low - - * bump revision while between releases - - -- Brian Warner Mon, 19 Mar 2007 23:11:35 -0700 - foolscap (0.1.0) unstable; urgency=low * new release -- Brian Warner Thu, 15 Mar 2007 16:56:16 -0700 -foolscap (0.0.7+) unstable; urgency=low - - * bump revision while between releases - - -- Brian Warner Mon, 22 Jan 2007 12:41:18 -0800 - foolscap (0.0.7) unstable; urgency=low * new release -- Brian Warner Tue, 16 Jan 2007 12:03:00 -0800 -foolscap (0.0.6+) unstable; urgency=low - - * bump revision while between releases - - -- Brian Warner Thu, 4 Jan 2007 18:45:04 -0500 - foolscap (0.0.6) unstable; urgency=low * new release -- Brian Warner Mon, 18 Dec 2006 12:10:51 -0800 -foolscap (0.0.5+) unstable; urgency=low - - * bump revision while between releases - - -- Brian Warner Tue, 14 Nov 2006 21:24:17 -0800 - foolscap (0.0.5) unstable; urgency=low * new release -- Brian Warner Sat, 4 Nov 2006 23:20:46 -0800 -foolscap (0.0.4+) unstable; urgency=low - - * bump revision while between releases - - -- Brian Warner Tue, 31 Oct 2006 23:38:34 -0800 - foolscap (0.0.4) unstable; urgency=low * new release - -- Brian Warner Thu, 26 Oct 2006 00:46:11 -0700 + -- Brian Warner Thu, 26 Oct 2006 00:46:30 -0700 -foolscap (0.0.3+) unstable; urgency=low +foolscap (0.0.3) unstable; urgency=low * new upstream release, put debian packaging in the tree diff --git a/src/foolscap/misc/dapper/debian/rules b/src/foolscap/misc/dapper/debian/rules index eb2284d2..8ee04543 100644 --- a/src/foolscap/misc/dapper/debian/rules +++ b/src/foolscap/misc/dapper/debian/rules @@ -41,7 +41,7 @@ binary-indep: build install dh_testdir dh_testroot dh_installdocs -i -A NEWS README - dh_installdocs ChangeLog doc/newpb-jobs.txt doc/newpb-todo.txt doc/use-cases.txt doc/using-pb.xhtml doc/copyable.xhtml doc/listings doc/specifications + dh_installdocs ChangeLog doc/newpb-jobs.txt doc/newpb-todo.txt doc/use-cases.txt doc/using-foolscap.xhtml doc/copyable.xhtml doc/listings doc/specifications dh_installchangelogs -i dh_compress -i -X.py dh_fixperms diff --git a/src/foolscap/misc/edgy/debian/changelog b/src/foolscap/misc/edgy/debian/changelog index 93eb7f3a..9699a6df 100644 --- a/src/foolscap/misc/edgy/debian/changelog +++ b/src/foolscap/misc/edgy/debian/changelog @@ -1,14 +1,14 @@ -foolscap (0.1.3) unstable; urgency=low +foolscap (0.1.4) unstable; urgency=low * new release - -- Brian Warner Wed, 2 May 2007 14:55:49 -0700 + -- Brian Warner Mon, 14 May 2007 22:37:04 -0700 -foolscap (0.1.2+) unstable; urgency=low +foolscap (0.1.3) unstable; urgency=low - * bump revision while between releases + * new release - -- Brian Warner Fri, 13 Apr 2007 00:22:10 -0700 + -- Brian Warner Wed, 2 May 2007 14:55:49 -0700 foolscap (0.1.2) unstable; urgency=low @@ -16,79 +16,43 @@ foolscap (0.1.2) unstable; urgency=low -- Brian Warner Wed, 4 Apr 2007 12:32:46 -0700 -foolscap (0.1.1+) unstable; urgency=low - - * bump revision while between releases - - -- Brian Warner Wed, 4 Apr 2007 10:32:22 -0700 - foolscap (0.1.1) unstable; urgency=low * new release -- Brian Warner Tue, 3 Apr 2007 20:48:07 -0700 -foolscap (0.1.0+) unstable; urgency=low - - * bump revision while between releases - - -- Brian Warner Mon, 19 Mar 2007 23:11:35 -0700 - foolscap (0.1.0) unstable; urgency=low * new release -- Brian Warner Thu, 15 Mar 2007 16:56:16 -0700 -foolscap (0.0.7+) unstable; urgency=low - - * bump revision while between releases - - -- Brian Warner Mon, 22 Jan 2007 12:41:18 -0800 - foolscap (0.0.7) unstable; urgency=low * new release -- Brian Warner Tue, 16 Jan 2007 12:03:00 -0800 -foolscap (0.0.6+) unstable; urgency=low - - * bump revision while between releases - - -- Brian Warner Thu, 4 Jan 2007 18:45:04 -0500 - foolscap (0.0.6) unstable; urgency=low * new release -- Brian Warner Mon, 18 Dec 2006 12:10:51 -0800 -foolscap (0.0.5+) unstable; urgency=low - - * bump revision while between releases - - -- Brian Warner Tue, 14 Nov 2006 21:24:17 -0800 - foolscap (0.0.5) unstable; urgency=low * new release -- Brian Warner Sat, 4 Nov 2006 23:20:46 -0800 -foolscap (0.0.4+) unstable; urgency=low - - * bump revision while between releases - - -- Brian Warner Tue, 31 Oct 2006 23:38:34 -0800 - foolscap (0.0.4) unstable; urgency=low * new release -- Brian Warner Thu, 26 Oct 2006 00:46:30 -0700 -foolscap (0.0.3+) unstable; urgency=low +foolscap (0.0.3) unstable; urgency=low * new upstream release, put debian packaging in the tree diff --git a/src/foolscap/misc/edgy/debian/rules b/src/foolscap/misc/edgy/debian/rules index 3ea18835..252672cc 100644 --- a/src/foolscap/misc/edgy/debian/rules +++ b/src/foolscap/misc/edgy/debian/rules @@ -9,7 +9,7 @@ include /usr/share/cdbs/1/class/python-distutils.mk install/python-foolscap:: - dh_installdocs doc/newpb-jobs.txt doc/newpb-todo.txt doc/use-cases.txt doc/using-pb.xhtml doc/copyable.xhtml doc/listings doc/specifications + dh_installdocs doc/newpb-jobs.txt doc/newpb-todo.txt doc/use-cases.txt doc/using-foolscap.xhtml doc/copyable.xhtml doc/listings doc/specifications clean:: -rm -rf build diff --git a/src/foolscap/misc/feisty/debian/changelog b/src/foolscap/misc/feisty/debian/changelog index 93eb7f3a..9699a6df 100644 --- a/src/foolscap/misc/feisty/debian/changelog +++ b/src/foolscap/misc/feisty/debian/changelog @@ -1,14 +1,14 @@ -foolscap (0.1.3) unstable; urgency=low +foolscap (0.1.4) unstable; urgency=low * new release - -- Brian Warner Wed, 2 May 2007 14:55:49 -0700 + -- Brian Warner Mon, 14 May 2007 22:37:04 -0700 -foolscap (0.1.2+) unstable; urgency=low +foolscap (0.1.3) unstable; urgency=low - * bump revision while between releases + * new release - -- Brian Warner Fri, 13 Apr 2007 00:22:10 -0700 + -- Brian Warner Wed, 2 May 2007 14:55:49 -0700 foolscap (0.1.2) unstable; urgency=low @@ -16,79 +16,43 @@ foolscap (0.1.2) unstable; urgency=low -- Brian Warner Wed, 4 Apr 2007 12:32:46 -0700 -foolscap (0.1.1+) unstable; urgency=low - - * bump revision while between releases - - -- Brian Warner Wed, 4 Apr 2007 10:32:22 -0700 - foolscap (0.1.1) unstable; urgency=low * new release -- Brian Warner Tue, 3 Apr 2007 20:48:07 -0700 -foolscap (0.1.0+) unstable; urgency=low - - * bump revision while between releases - - -- Brian Warner Mon, 19 Mar 2007 23:11:35 -0700 - foolscap (0.1.0) unstable; urgency=low * new release -- Brian Warner Thu, 15 Mar 2007 16:56:16 -0700 -foolscap (0.0.7+) unstable; urgency=low - - * bump revision while between releases - - -- Brian Warner Mon, 22 Jan 2007 12:41:18 -0800 - foolscap (0.0.7) unstable; urgency=low * new release -- Brian Warner Tue, 16 Jan 2007 12:03:00 -0800 -foolscap (0.0.6+) unstable; urgency=low - - * bump revision while between releases - - -- Brian Warner Thu, 4 Jan 2007 18:45:04 -0500 - foolscap (0.0.6) unstable; urgency=low * new release -- Brian Warner Mon, 18 Dec 2006 12:10:51 -0800 -foolscap (0.0.5+) unstable; urgency=low - - * bump revision while between releases - - -- Brian Warner Tue, 14 Nov 2006 21:24:17 -0800 - foolscap (0.0.5) unstable; urgency=low * new release -- Brian Warner Sat, 4 Nov 2006 23:20:46 -0800 -foolscap (0.0.4+) unstable; urgency=low - - * bump revision while between releases - - -- Brian Warner Tue, 31 Oct 2006 23:38:34 -0800 - foolscap (0.0.4) unstable; urgency=low * new release -- Brian Warner Thu, 26 Oct 2006 00:46:30 -0700 -foolscap (0.0.3+) unstable; urgency=low +foolscap (0.0.3) unstable; urgency=low * new upstream release, put debian packaging in the tree diff --git a/src/foolscap/misc/feisty/debian/rules b/src/foolscap/misc/feisty/debian/rules index 3ea18835..252672cc 100644 --- a/src/foolscap/misc/feisty/debian/rules +++ b/src/foolscap/misc/feisty/debian/rules @@ -9,7 +9,7 @@ include /usr/share/cdbs/1/class/python-distutils.mk install/python-foolscap:: - dh_installdocs doc/newpb-jobs.txt doc/newpb-todo.txt doc/use-cases.txt doc/using-pb.xhtml doc/copyable.xhtml doc/listings doc/specifications + dh_installdocs doc/newpb-jobs.txt doc/newpb-todo.txt doc/use-cases.txt doc/using-foolscap.xhtml doc/copyable.xhtml doc/listings doc/specifications clean:: -rm -rf build diff --git a/src/foolscap/misc/sarge/debian/changelog b/src/foolscap/misc/sarge/debian/changelog index f3331be3..9699a6df 100644 --- a/src/foolscap/misc/sarge/debian/changelog +++ b/src/foolscap/misc/sarge/debian/changelog @@ -1,14 +1,14 @@ -foolscap (0.1.3) unstable; urgency=low +foolscap (0.1.4) unstable; urgency=low * new release - -- Brian Warner Wed, 2 May 2007 14:55:49 -0700 + -- Brian Warner Mon, 14 May 2007 22:37:04 -0700 -foolscap (0.1.2+) unstable; urgency=low +foolscap (0.1.3) unstable; urgency=low - * bump revision while between releases + * new release - -- Brian Warner Fri, 13 Apr 2007 00:22:10 -0700 + -- Brian Warner Wed, 2 May 2007 14:55:49 -0700 foolscap (0.1.2) unstable; urgency=low @@ -16,79 +16,43 @@ foolscap (0.1.2) unstable; urgency=low -- Brian Warner Wed, 4 Apr 2007 12:32:46 -0700 -foolscap (0.1.1+) unstable; urgency=low - - * bump revision while between releases - - -- Brian Warner Wed, 4 Apr 2007 10:32:22 -0700 - foolscap (0.1.1) unstable; urgency=low * new release -- Brian Warner Tue, 3 Apr 2007 20:48:07 -0700 -foolscap (0.1.0+) unstable; urgency=low - - * bump revision while between releases - - -- Brian Warner Mon, 19 Mar 2007 23:11:35 -0700 - foolscap (0.1.0) unstable; urgency=low * new release -- Brian Warner Thu, 15 Mar 2007 16:56:16 -0700 -foolscap (0.0.7+) unstable; urgency=low - - * bump revision while between releases - - -- Brian Warner Mon, 22 Jan 2007 12:41:18 -0800 - foolscap (0.0.7) unstable; urgency=low * new release -- Brian Warner Tue, 16 Jan 2007 12:03:00 -0800 -foolscap (0.0.6+) unstable; urgency=low - - * bump revision while between releases - - -- Brian Warner Thu, 4 Jan 2007 18:45:04 -0500 - foolscap (0.0.6) unstable; urgency=low * new release -- Brian Warner Mon, 18 Dec 2006 12:10:51 -0800 -foolscap (0.0.5+) unstable; urgency=low - - * bump revision while between releases - - -- Brian Warner Tue, 14 Nov 2006 21:24:17 -0800 - foolscap (0.0.5) unstable; urgency=low * new release -- Brian Warner Sat, 4 Nov 2006 23:20:46 -0800 -foolscap (0.0.4+) unstable; urgency=low - - * bump revision while between releases - - -- Brian Warner Tue, 31 Oct 2006 23:38:34 -0800 - foolscap (0.0.4) unstable; urgency=low * new release - -- Brian Warner Thu, 26 Oct 2006 00:46:11 -0700 + -- Brian Warner Thu, 26 Oct 2006 00:46:30 -0700 -foolscap (0.0.3+) unstable; urgency=low +foolscap (0.0.3) unstable; urgency=low * new upstream release, put debian packaging in the tree diff --git a/src/foolscap/misc/sarge/debian/rules b/src/foolscap/misc/sarge/debian/rules index eb2284d2..8ee04543 100644 --- a/src/foolscap/misc/sarge/debian/rules +++ b/src/foolscap/misc/sarge/debian/rules @@ -41,7 +41,7 @@ binary-indep: build install dh_testdir dh_testroot dh_installdocs -i -A NEWS README - dh_installdocs ChangeLog doc/newpb-jobs.txt doc/newpb-todo.txt doc/use-cases.txt doc/using-pb.xhtml doc/copyable.xhtml doc/listings doc/specifications + dh_installdocs ChangeLog doc/newpb-jobs.txt doc/newpb-todo.txt doc/use-cases.txt doc/using-foolscap.xhtml doc/copyable.xhtml doc/listings doc/specifications dh_installchangelogs -i dh_compress -i -X.py dh_fixperms diff --git a/src/foolscap/misc/sid/debian/changelog b/src/foolscap/misc/sid/debian/changelog index 93eb7f3a..9699a6df 100644 --- a/src/foolscap/misc/sid/debian/changelog +++ b/src/foolscap/misc/sid/debian/changelog @@ -1,14 +1,14 @@ -foolscap (0.1.3) unstable; urgency=low +foolscap (0.1.4) unstable; urgency=low * new release - -- Brian Warner Wed, 2 May 2007 14:55:49 -0700 + -- Brian Warner Mon, 14 May 2007 22:37:04 -0700 -foolscap (0.1.2+) unstable; urgency=low +foolscap (0.1.3) unstable; urgency=low - * bump revision while between releases + * new release - -- Brian Warner Fri, 13 Apr 2007 00:22:10 -0700 + -- Brian Warner Wed, 2 May 2007 14:55:49 -0700 foolscap (0.1.2) unstable; urgency=low @@ -16,79 +16,43 @@ foolscap (0.1.2) unstable; urgency=low -- Brian Warner Wed, 4 Apr 2007 12:32:46 -0700 -foolscap (0.1.1+) unstable; urgency=low - - * bump revision while between releases - - -- Brian Warner Wed, 4 Apr 2007 10:32:22 -0700 - foolscap (0.1.1) unstable; urgency=low * new release -- Brian Warner Tue, 3 Apr 2007 20:48:07 -0700 -foolscap (0.1.0+) unstable; urgency=low - - * bump revision while between releases - - -- Brian Warner Mon, 19 Mar 2007 23:11:35 -0700 - foolscap (0.1.0) unstable; urgency=low * new release -- Brian Warner Thu, 15 Mar 2007 16:56:16 -0700 -foolscap (0.0.7+) unstable; urgency=low - - * bump revision while between releases - - -- Brian Warner Mon, 22 Jan 2007 12:41:18 -0800 - foolscap (0.0.7) unstable; urgency=low * new release -- Brian Warner Tue, 16 Jan 2007 12:03:00 -0800 -foolscap (0.0.6+) unstable; urgency=low - - * bump revision while between releases - - -- Brian Warner Thu, 4 Jan 2007 18:45:04 -0500 - foolscap (0.0.6) unstable; urgency=low * new release -- Brian Warner Mon, 18 Dec 2006 12:10:51 -0800 -foolscap (0.0.5+) unstable; urgency=low - - * bump revision while between releases - - -- Brian Warner Tue, 14 Nov 2006 21:24:17 -0800 - foolscap (0.0.5) unstable; urgency=low * new release -- Brian Warner Sat, 4 Nov 2006 23:20:46 -0800 -foolscap (0.0.4+) unstable; urgency=low - - * bump revision while between releases - - -- Brian Warner Tue, 31 Oct 2006 23:38:34 -0800 - foolscap (0.0.4) unstable; urgency=low * new release -- Brian Warner Thu, 26 Oct 2006 00:46:30 -0700 -foolscap (0.0.3+) unstable; urgency=low +foolscap (0.0.3) unstable; urgency=low * new upstream release, put debian packaging in the tree diff --git a/src/foolscap/misc/sid/debian/rules b/src/foolscap/misc/sid/debian/rules index 3ea18835..252672cc 100644 --- a/src/foolscap/misc/sid/debian/rules +++ b/src/foolscap/misc/sid/debian/rules @@ -9,7 +9,7 @@ include /usr/share/cdbs/1/class/python-distutils.mk install/python-foolscap:: - dh_installdocs doc/newpb-jobs.txt doc/newpb-todo.txt doc/use-cases.txt doc/using-pb.xhtml doc/copyable.xhtml doc/listings doc/specifications + dh_installdocs doc/newpb-jobs.txt doc/newpb-todo.txt doc/use-cases.txt doc/using-foolscap.xhtml doc/copyable.xhtml doc/listings doc/specifications clean:: -rm -rf build diff --git a/src/foolscap/misc/testutils/figleaf.py b/src/foolscap/misc/testutils/figleaf.py index a24d4fab..03d8c08d 100644 --- a/src/foolscap/misc/testutils/figleaf.py +++ b/src/foolscap/misc/testutils/figleaf.py @@ -168,7 +168,6 @@ class CodeTracer: Start recording. """ if not self.started: - self.LOG = open("/tmp/flog.out", "w") self.started = True sys.settrace(self.g) -- 2.45.2