From 267a068fe4d45bffe5f38e93498b80066e837e8c Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Tue, 15 May 2007 17:33:52 -0700 Subject: [PATCH] update to foolscap-0.1.4 --- src/foolscap/ChangeLog | 109 +++++++++++++ src/foolscap/NEWS | 64 ++++++++ .../{using-pb.xhtml => using-foolscap.xhtml} | 74 +++++---- src/foolscap/foolscap/__init__.py | 2 +- src/foolscap/foolscap/call.py | 43 ++++- src/foolscap/foolscap/pb.py | 35 ++++- src/foolscap/foolscap/reconnector.py | 11 +- src/foolscap/foolscap/referenceable.py | 34 ++-- src/foolscap/foolscap/remoteinterface.py | 31 +++- src/foolscap/foolscap/slicers/allslicers.py | 4 +- src/foolscap/foolscap/slicers/dict.py | 12 +- src/foolscap/foolscap/slicers/list.py | 23 ++- src/foolscap/foolscap/slicers/set.py | 148 +++++++++++++++--- src/foolscap/foolscap/slicers/tuple.py | 31 +++- src/foolscap/foolscap/test/test_banana.py | 83 ++++++++-- src/foolscap/foolscap/test/test_call.py | 12 +- src/foolscap/foolscap/test/test_gifts.py | 136 +++++++++++++++- src/foolscap/foolscap/test/test_schema.py | 15 +- src/foolscap/foolscap/test/test_tub.py | 81 +++++++++- src/foolscap/misc/dapper/debian/changelog | 50 +----- src/foolscap/misc/dapper/debian/rules | 2 +- src/foolscap/misc/edgy/debian/changelog | 48 +----- src/foolscap/misc/edgy/debian/rules | 2 +- src/foolscap/misc/feisty/debian/changelog | 48 +----- src/foolscap/misc/feisty/debian/rules | 2 +- src/foolscap/misc/sarge/debian/changelog | 50 +----- src/foolscap/misc/sarge/debian/rules | 2 +- src/foolscap/misc/sid/debian/changelog | 48 +----- src/foolscap/misc/sid/debian/rules | 2 +- src/foolscap/misc/testutils/figleaf.py | 1 - 30 files changed, 845 insertions(+), 358 deletions(-) rename src/foolscap/doc/{using-pb.xhtml => using-foolscap.xhtml} (95%) diff --git a/src/foolscap/ChangeLog b/src/foolscap/ChangeLog index 06af6258..bade5f83 100644 --- a/src/foolscap/ChangeLog +++ b/src/foolscap/ChangeLog @@ -1,3 +1,112 @@ +2007-05-14 Brian Warner + + * foolscap/__init__.py: release Foolscap-0.1.4 + * misc/{sid|sarge|dapper|edgy|feisty}/debian/changelog: same, also + remove a bunch of old between-release version numbers + +2007-05-14 Brian Warner + + * NEWS: update for the upcoming release + + * doc/using-foolscap.xhtml: rename from doc/using-pb.xhtml + + * doc/using-pb.xhtml: replace all uses of 'PB URL' with 'FURL' + + * foolscap/pb.py (Tub.getReference): if getReference() is called + before Tub.startService(), queue the request until startup. + (Tub.connectTo): same for connectTo(). + (Tub.startService): launch pending getReference() and connectTo() + requests. There are all fired with eventual-sends. + * foolscap/reconnector.py (Reconnector): don't automatically start + the Reconnector in __init__, rather wait for the Tub to start it. + * foolscap/test/test_tub.py (QueuedStartup): test it + * doc/using-pb.xhtml: update docs to match + + * foolscap/test/test_call.py (TestCall.testCall1): replace an + arbitrary delay with a polling loop, to make the test more + reliable under load + + * foolscap/referenceable.py (SturdyRef.asLiveRef): remove a method + that was never used, didn't work, and is of dubious utility + anyways. + (_AsLiveRef): remove this too + + * misc/testutils/figleaf.py (CodeTracer.start): remove leftover + debug logging + + * foolscap/remoteinterface.py (RemoteInterfaceConstraint): accept + gifts too: allow sending of RemoteReferences on the outbound side, + and accept their-reference sequences on the inbound side. + * foolscap/test/test_gifts.py (Gifts.test_constraint): test it + * foolscap/test/test_schema.py (Interfaces.test_remotereference): + update test, since now we allow RemoteReferences to be sent on the + outbound side + + * foolscap/remoteinterface.py (getRemoteInterface): improve the + error message reported when a Referenceable class implements + multiple RemoteInterfaces + + * foolscap/remoteinterface.py (RemoteMethodSchema.initFromMethod): + properly handle methods like 'def foo(nodefault)' that are missing + *all* default values. Previously this resulted in an unhelpful + exception (since typeList==None), now it gives a sensible + InvalidRemoteInterface exception. + * foolscap/test/test_schema.py (Arguments.test_bad_arguments): + test it + +2007-05-11 Brian Warner + + * foolscap/slicers/set.py (FrozenSetSlicer): finally acknowledge + our dependence on python2.4 or newer, by using the built-in 'set' + and 'frozenset' types by default. We'll serialize the old sets.Set + and sets.ImmutableSet too, but they'll emerge as a set/frozenset. + This will cause problems for code that was written to be + compatible with python2.3 (by using sets.Set) and wasn't changed + when moved to 2.4, if it tries to mingle sets.Set with the data + coming out of Foolscap. Unfortunate, but doing it this way + preserves both sanity and good behavior for modern 2.4-or-later + apps. + (SetUnslicer): fix handling of children that were unreferenceable + during construction, fix handling of children that are not ready + for use (i.e. gifts). + (FrozenSetUnslicer): base this off of TupleUnslicer, since + previously the cycle-handling logic was completely broken. I'm not + entirely sure this is necessary, since I think the contents of + sets must be transitively immutable (or at least transitively + hashable), but it good to review and clean it up anyways. + * foolscap/slicers/allslicers.py: match name change + + * foolscap/slicers/tuple.py (TupleUnslicer.receiveClose): fix + handling of unready children (i.e. gifts), previously gifts inside + containers were completely broken. + * foolscap/slicers/list.py (ListUnslicer.receiveClose): same + * foolscap/slicers/dict.py (DictUnslicer.receiveClose): same + + * foolscap/call.py: add debug log messages (disabled) + + * foolscap/referenceable.py (TheirReferenceUnslicer.receiveClose): + gifts must declare themselves 'unready' until the RemoteReference + resolves, since we might be inside a container of some sort. + Without this fix, methods would be invoked too early, before the + RemoteReference was really available. + + * foolscap/test/test_banana.py (ThereAndBackAgain.test_set): match + new set/sets.Set behavior + (ThereAndBackAgain.test_cycles_1): test some of the cycles + (ThereAndBackAgain.test_cycles_3): add (disabled) test for + checking cycles that involve sets. I think these tests are + non-sensical, since sets can't really participate in the sorts of + cycles we worry about, but I left the (disabled) test code in + place in case it becomes useful again. + + * foolscap/test/test_gifts.py (Gifts.testContainers): validate + that gifts can appear in all sorts of containers successfully. + +2007-05-11 Brian Warner + + * foolscap/__init__.py: bump revision to 0.1.3+ while between releases + * misc/{sid|sarge|dapper|edgy|feisty}/debian/changelog: same + 2007-05-02 Brian Warner * foolscap/__init__.py: release Foolscap-0.1.3 diff --git a/src/foolscap/NEWS b/src/foolscap/NEWS index 8ef246fb..f45ecb20 100644 --- a/src/foolscap/NEWS +++ b/src/foolscap/NEWS @@ -1,5 +1,69 @@ User visible changes in Foolscap (aka newpb/pb2). -*- outline -*- +* Release 0.1.4 (14 May 2007) + +** Compatibility + +This release is fully compatible with 0.1.3 . + +** getReference/connectTo can be called before Tub.startService() + +The Tub.startService changes that were suggested in the 0.1.3 release notes +have been implemented. Calling getReference() or connectTo() before the Tub +has been started is now allowed, however no action will take place until the +Tub is running. Don't forget to start the Tub, or you'll be left wondering +why your Deferred or callback is never fired. (A log message is emitted when +these calls are made before the Tub is started, in the hopes of helping +developers find this mistake faster). + +** constraint improvements + +The RIFoo -style constraint now accepts gifts (third-party references). This +also means that using RIFoo on the outbound side will accept either a +Referenceable that implements the given RemoteInterface or a RemoteReference +that points to a Referenceable that implements the given RemoteInterface. +There is a situation (sending a RemoteReference back to its owner) that will +pass the outbound constraint but be rejected by the inbound constraint on the +other end. It remains to be seen how this will be fixed. + +** foolscap now deserializes into python2.4-native 'set' and 'frozenset' types + +Since Foolscap is dependent upon python2.4 or newer anyways, it now +unconditionally creates built-in 'set' and 'frozenset' instances when +deserializing 'set'/'immutable-set' banana sequences. The pre-python2.4 +'sets' module has non-built-in set classes named sets.Set and +sets.ImmutableSet, and these are serialized just like the built-in forms. + +Unfortunately this means that Set and ImmutableSet will not survive a +round-trip: they'll be turned into set and frozenset, respectively. Worse +yet, 'set' and 'sets.Set' are not entirely compatible. This may cause a +problem for older applications that were written to be compatible with both +python-2.3 and python-2.4 (by using sets.Set/sets.ImmutableSet), for which +the compatibility code is still in place (i.e. they are not using +set/frozenset). These applications may experience problems when set objects +that traverse the wire via Foolscap are brought into close proximity with set +objects that remained local. This is unfortunate, but it's the cleanest way +to support modern applications that use the native types exclusively. + +** bug fixes + +Gifts inside containers (lists, tuples, dicts, sets) were broken: the target +method was frequently invoked before the gift had properly resolved into a +RemoteReference. Constraints involving gifts inside containers were broken +too. The constraints may be too loose right now, but I don't think they +should cause false negatives. + +The unused SturdyRef.asLiveRef method was removed, since it didn't work +anyways. + +** terminology shift: FURL + +The preferred name for the sort of URL that you get back from +registerReference (and hand to getReference or connectTo) has changed from +"PB URL" to "FURL" (short for Foolscap URL). They still start with 'pb:', +however. Documentation is slowly being changed to use this term. + + * Release 0.1.3 (02 May 2007) ** Incompatibility Warning diff --git a/src/foolscap/doc/using-pb.xhtml b/src/foolscap/doc/using-foolscap.xhtml similarity index 95% rename from src/foolscap/doc/using-pb.xhtml rename to src/foolscap/doc/using-foolscap.xhtml index 97005d01..9ddc88ce 100644 --- a/src/foolscap/doc/using-pb.xhtml +++ b/src/foolscap/doc/using-foolscap.xhtml @@ -101,7 +101,8 @@ d.addCallbacks(gotAnswer, gotError)

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.

+we'll need to discuss the Tub, and the concept of a FURL +URL.

Tubs: The Foolscap Service

@@ -128,13 +129,13 @@ established since last startup. If you have no parent to attach it to, you can use 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.

Making your Tub remotely accessible

@@ -147,7 +148,7 @@ port is accessibly to the outside world.

creating an SSL private key certificate and hashing it into a suitably-long random-looking string. This is the primary identifier of the Tub: everything else is just a location hint that suggests how the Tub might be -reached. The fact that the TubID is tied to the private key allows PB URLs to +reached. The fact that the TubID is tied to the private key allows FURLs to be secure references (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.

+authenticated Tubs. Foolscap uses authenticated Tubs by default.

Having the Tub listen on a TCP port is as simple as calling listenOn with a url = tub.registerReference(myserver, "math-service") -

This returns the PB URL for your Referenceable. Remote +

This returns the 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.

+

Since 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

Here are two programs, one implementing the server side of our @@ -333,7 +353,7 @@ the answer is 3 -

PB URLs

+

FURLs

In Foolscap, each world-accessible Referenceable has one or more URLs which are secure, where we use the capability-security definition of @@ -350,9 +370,9 @@ the term, meaning those URLs have the following properties:

will be connected to will be the right one. -

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 tub.registerReference generate a random name for you, preserving the unguessability property.

@@ -367,9 +387,9 @@ you to mistakenly connect to the wrong target.

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:

@@ -402,7 +422,7 @@ URL:

the format 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:

@@ -470,13 +490,13 @@ therefore use 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 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/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