From afe006d70007edd7ae79ce77408e01daabed5f6d Mon Sep 17 00:00:00 2001 From: warner-tahoe Date: Tue, 7 Aug 2007 18:55:47 -0700 Subject: [PATCH] update foolscap to foolscap-0.1.5, the latest release --- src/foolscap/ChangeLog | 126 ++++++++++ src/foolscap/Makefile | 1 + src/foolscap/NEWS | 61 +++++ src/foolscap/doc/{newpb-jobs.txt => jobs.txt} | 0 src/foolscap/doc/{newpb-todo.txt => todo.txt} | 0 src/foolscap/foolscap/__init__.py | 2 +- src/foolscap/foolscap/broker.py | 42 ++-- src/foolscap/foolscap/call.py | 199 ++++++++------- src/foolscap/foolscap/ipb.py | 4 + src/foolscap/foolscap/pb.py | 52 +++- src/foolscap/foolscap/referenceable.py | 50 +++- src/foolscap/foolscap/slicer.py | 28 ++- src/foolscap/foolscap/slicers/dict.py | 5 +- src/foolscap/foolscap/slicers/list.py | 5 +- src/foolscap/foolscap/slicers/set.py | 3 +- src/foolscap/foolscap/slicers/tuple.py | 7 +- src/foolscap/foolscap/test/common.py | 3 + src/foolscap/foolscap/test/test_call.py | 14 ++ src/foolscap/foolscap/test/test_copyable.py | 11 +- src/foolscap/foolscap/test/test_gifts.py | 236 ++++++++++++++++-- src/foolscap/foolscap/test/test_interfaces.py | 2 +- src/foolscap/foolscap/test/test_pb.py | 6 +- src/foolscap/foolscap/test/test_reference.py | 71 ++++++ src/foolscap/foolscap/test/test_tub.py | 92 ++++++- src/foolscap/foolscap/test/test_util.py | 92 +++++++ src/foolscap/foolscap/util.py | 52 ++++ src/foolscap/misc/dapper/debian/changelog | 6 + src/foolscap/misc/edgy/debian/changelog | 6 + src/foolscap/misc/feisty/debian/changelog | 6 + src/foolscap/misc/sarge/debian/changelog | 6 + src/foolscap/misc/sid/debian/changelog | 6 + 31 files changed, 1034 insertions(+), 160 deletions(-) rename src/foolscap/doc/{newpb-jobs.txt => jobs.txt} (100%) rename src/foolscap/doc/{newpb-todo.txt => todo.txt} (100%) create mode 100644 src/foolscap/foolscap/test/test_reference.py create mode 100644 src/foolscap/foolscap/test/test_util.py create mode 100644 src/foolscap/foolscap/util.py diff --git a/src/foolscap/ChangeLog b/src/foolscap/ChangeLog index bade5f83..9c887ab9 100644 --- a/src/foolscap/ChangeLog +++ b/src/foolscap/ChangeLog @@ -1,3 +1,129 @@ +2007-08-07 Brian Warner + + * foolscap/__init__.py: release Foolscap-0.1.5 + * misc/{sid|sarge|dapper|edgy|feisty}/debian/changelog: same + +2007-08-07 Brian Warner + + * NEWS: update for the upcoming release + + * foolscap/pb.py (Tub.registerNameLookupHandler): new function to + augment Tub.registerReference(). This allows names to be looked up + at request time, rather than requiring all Referenceables be + pre-registered with registerReference(). The chief use of this + would be for FURLs which point at objects that live on disk in + some persistent state until they are needed. Closes #6. + (Tub.unregisterNameLookupHandler): allow handlers to be removed + (Tub.getReferenceForName): use the handler during lookup + * foolscap/test/test_tub.py (NameLookup): test it + +2007-07-27 Brian Warner + + * foolscap/referenceable.py (LocalReferenceable): implement an + adapter that allows code to do IRemoteReference(t).callRemote(...) + and have it work for both RemoteReferences and local + Referenceables. You might want to do this if you're getting back + introductions to a variety of remote Referenceables, some of which + might actually be on your local system, and you want to treat all + of the, the same way. Local Referenceables will be wrapped with a + class that implements callRemote() and makes it behave like an + actual remote callRemote() would. Closes ticket #1. + * foolscap/test/test_reference.py (LocalReference): test it + +2007-07-26 Brian Warner + + * foolscap/call.py (AnswerUnslicer.receiveChild): accept a + ready_deferred, to accomodate Gifts in return values. Closes #5. + (AnswerUnslicer.receiveClose): .. and don't fire the response + until any such Gifts resolve + * foolscap/test/test_gifts.py (Gifts.testReturn): test it + (Gifts.testReturnInContainer): same + (Bad.testReturn_swissnum): and test the failure case too + + * foolscap/test/test_pb.py (TestAnswer.testAccept1): fix a test + which wasn't calling start() properly and was broken by that change + (TestAnswer.testAccept2): same + + * foolscap/test/test_gifts.py (Bad.setUp): disable these tests when + we don't have crypto, since TubIDs are not mangleable in the same + way without crypto. + + * foolscap/slicer.py (BaseUnslicer.receiveChild): new convention: + Unslicers should accumulate their children's ready_deferreds into + an AsyncAND, and pass it to the parent. If something goes wrong, + the ready_deferred should errback, which will abandon the method + call that contains it. + * foolscap/slicers/dict.py (DictUnslicer.receiveClose): same + * foolscap/slicers/tuple.py (TupleUnslicer.receiveClose): same + (TupleUnslicer.complete): same + * foolscap/slicers/set.py (SetUnslicer.receiveClose): same + * foolscap/slicers/list.py (ListUnslicer.receiveClose): same + * foolscap/call.py (CallUnslicer.receiveClose): same + + * foolscap/referenceable.py (TheirReferenceUnslicer.receiveClose): + use our ready_deferred to signal whether the gift resolves + correctly or not. If it fails, errback ready_deferred (to prevent + the message from being delivered without the resolved gift), but + callback obj_deferred with a placeholder to avoid causing too much + distress to the container. + + * foolscap/broker.py (PBRootUnslicer.receiveChild): accept + ready_deferred in the InboundDelivery, stash both of them in the + broker. + (Broker.scheduleCall): rewrite inbound delivery handling: use a + self._call_is_running flag to prevent concurrent deliveries, and + wait for the ready_deferred before delivering the top-most + message. If the ready_deferred errbacks, that gets routed to + self.callFailed so the caller hears about the problem. This closes + ticket #2. + + * foolscap/call.py (InboundDelivery): remove whenRunnable, relying + upon the ready_deferred to let the Broker know when the message + can be delivered. + (ArgumentUnslicer): significant cleanup, using ready_deferred. + Remove isReady and whenReady. + + * foolscap/test/test_gifts.py (Base): factor setup code out + (Base.createCharacters): registerReference(tubname), for debugging + (Bad): add a bunch of tests to make sure that gifts which fail to + resolve (for various reasons) will inform the caller about the + problem, via an errback on the original callRemote()'s Deferred. + +2007-07-25 Brian Warner + + * foolscap/util.py (AsyncAND): new utility class, which is like + DeferredList but is specifically for control flow rather than data + flow. + * foolscap/test/test_util.py: test it + + * foolscap/call.py (CopiedFailure.setCopyableState): set .type to + a class that behaves (as least as far as reflect.qual() is + concerned) just like the original exception class. This improves + the behavior of derived Failure objects, as well as trial's + handling of CopiedFailures that get handed to log.err(). + CopiedFailures are now a bit more like actual Failures. See ticket + #4 (http://foolscap.lothar.com/trac/ticket/4) for more details. + (CopiedFailureSlicer): make sure that CopiedFailures can be + serialized, so that A-calls-B-calls-C can return a failure all + the way back. + * foolscap/test/test_call.py (TestCall.testCopiedFailure): test it + * foolscap/test/test_copyable.py: update to match, now we must + compare reflect.qual(f.type) against some extension classname, + rather than just f.type. + * foolscap/test/test_pb.py: same + * foolscap/test/common.py: same + +2007-07-15 Brian Warner + + * foolscap/test/test_interfaces.py (TestInterface.testStack): + don't look for a '/' in the stacktrace, since it won't be there + under windows. Thanks to 'strank'. Closes Twisted#2731. + +2007-06-29 Brian Warner + + * foolscap/__init__.py: bump revision to 0.1.4+ while between releases + * misc/{sid|sarge|dapper|edgy|feisty}/debian/changelog: same + 2007-05-14 Brian Warner * foolscap/__init__.py: release Foolscap-0.1.4 diff --git a/src/foolscap/Makefile b/src/foolscap/Makefile index d1c1612e..99496904 100644 --- a/src/foolscap/Makefile +++ b/src/foolscap/Makefile @@ -53,3 +53,4 @@ docs: lore -p --config template=$(DOC_TEMPLATE) --config ext=.html \ `find doc -name '*.xhtml'` + diff --git a/src/foolscap/NEWS b/src/foolscap/NEWS index f45ecb20..839c6de9 100644 --- a/src/foolscap/NEWS +++ b/src/foolscap/NEWS @@ -1,5 +1,66 @@ User visible changes in Foolscap (aka newpb/pb2). -*- outline -*- +* Release 0.1.5 (07 Aug 2007) + +** Compatibility + +This release is fully compatible with 0.1.4 and 0.1.3 . + +** CopiedFailure improvements + +When a remote method call fails, the calling side gets back a CopiedFailure +instance. These instances now behave slightly more like the (local) Failure +objects that they are intended to mirror, in that .type now behaves much like +the original class. This should allow trial tests which result in a +CopiedFailure to be logged without exploding. In addition, chained failures +(where A calls B, and B calls C, and C fails, so C's Failure is eventually +returned back to A) should work correctly now. + +** Gift improvements + +Gifts inside return values should properly stall the delivery of the response +until the gift is resolved. Gifts in all sorts of containers should work +properly now. Gifts which cannot be resolved successfully (either because the +hosting Tub cannot be reached, or because the name cannot be found) will now +cause a proper error rather than hanging forever. Unresolvable gifts in +method arguments will cause the message to not be delivered and an error to +be returned to the caller. Unresolvable gifts in method return values will +cause the caller to receive an error. + +** IRemoteReference() adapter + +The IRemoteReference() interface now has an adapter from Referenceable which +creates a wrapper that enables the use of callRemote() and other +IRemoteReference methods on a local object. + +The situation where this might be useful is when you have a central +introducer and a bunch of clients, and the clients are introducing themselves +to each other (to create a fully-connected mesh), and the introductions are +using live references (i.e. Gifts), then when a specific client learns about +itself from the introducer, that client will receive a local object instead +of a RemoteReference. Each client will wind up with n-1 RemoteReferences and +a single local object. + +This adapter allows the client to treat all these introductions as equal. A +client that wishes to send a message to everyone it's been introduced to +(including itself) can use: + + for i in introductions: + IRemoteReference(i).callRemote("hello", args) + +In the future, if we implement coercing Guards (instead of +compliance-asserting Constraints), then IRemoteReference will be useful as a +guard on methods that want to insure that they can do callRemote (and +notifyOnDisconnect, etc) on their argument. + +** Tub.registerNameLookupHandler + +This method allows a one-argument name-lookup callable to be attached to the +Tub. This augments the table maintained by Tub.registerReference, allowing +Referenceables to be created on the fly, or persisted/retrieved on disk +instead of requiring all of them to be generated and registered at startup. + + * Release 0.1.4 (14 May 2007) ** Compatibility diff --git a/src/foolscap/doc/newpb-jobs.txt b/src/foolscap/doc/jobs.txt similarity index 100% rename from src/foolscap/doc/newpb-jobs.txt rename to src/foolscap/doc/jobs.txt diff --git a/src/foolscap/doc/newpb-todo.txt b/src/foolscap/doc/todo.txt similarity index 100% rename from src/foolscap/doc/newpb-todo.txt rename to src/foolscap/doc/todo.txt diff --git a/src/foolscap/foolscap/__init__.py b/src/foolscap/foolscap/__init__.py index ab58d559..b3d41909 100644 --- a/src/foolscap/foolscap/__init__.py +++ b/src/foolscap/foolscap/__init__.py @@ -1,6 +1,6 @@ """Foolscap""" -__version__ = "0.1.4" +__version__ = "0.1.5" # here are the primary entry points from foolscap.pb import Tub, UnauthenticatedTub, getRemoteURL_TCP diff --git a/src/foolscap/foolscap/broker.py b/src/foolscap/foolscap/broker.py index 1977f4d6..4da9ebc3 100644 --- a/src/foolscap/foolscap/broker.py +++ b/src/foolscap/foolscap/broker.py @@ -111,8 +111,7 @@ class PBRootUnslicer(RootUnslicer): def receiveChild(self, token, ready_deferred): if isinstance(token, call.InboundDelivery): - assert ready_deferred is None - self.broker.scheduleCall(token) + self.broker.scheduleCall(token, ready_deferred) @@ -215,6 +214,7 @@ class Broker(banana.Banana, referenceable.Referenceable): self.disconnectWatchers = [] # receiving side uses these self.inboundDeliveryQueue = [] + self._call_is_running = False self.activeLocalCalls = {} # the other side wants an answer from us def setTub(self, tub): @@ -506,33 +506,31 @@ class Broker(banana.Banana, referenceable.Referenceable): return m return None - def scheduleCall(self, delivery): - self.inboundDeliveryQueue.append(delivery) + def scheduleCall(self, delivery, ready_deferred): + self.inboundDeliveryQueue.append( (delivery,ready_deferred) ) eventually(self.doNextCall) - def doNextCall(self, ignored=None): - if not self.inboundDeliveryQueue: + def doNextCall(self): + if self._call_is_running: return - nextCall = self.inboundDeliveryQueue[0] - if nextCall.isRunnable(): - # remove it and arrange to run again soon - self.inboundDeliveryQueue.pop(0) - delivery = nextCall - if self.inboundDeliveryQueue: - eventually(self.doNextCall) - - # now perform the actual delivery - d = defer.maybeDeferred(self._doCall, delivery) - d.addCallback(self._callFinished, delivery) - d.addErrback(self.callFailed, delivery.reqID, delivery) + if not self.inboundDeliveryQueue: return - # arrange to wake up when the next call becomes runnable - d = nextCall.whenRunnable() - d.addCallback(self.doNextCall) + delivery, ready_deferred = self.inboundDeliveryQueue.pop(0) + self._call_is_running = True + if not ready_deferred: + ready_deferred = defer.succeed(None) + d = ready_deferred + d.addCallback(lambda res: self._doCall(delivery)) + d.addCallback(self._callFinished, delivery) + d.addErrback(self.callFailed, delivery.reqID, delivery) + def _done(res): + self._call_is_running = False + eventually(self.doNextCall) + d.addBoth(_done) + return None def _doCall(self, delivery): obj = delivery.obj - assert delivery.allargs.isReady() args = delivery.allargs.args kwargs = delivery.allargs.kwargs for i in args + kwargs.values(): diff --git a/src/foolscap/foolscap/call.py b/src/foolscap/foolscap/call.py index 2f1ccd6a..e675a7b8 100644 --- a/src/foolscap/foolscap/call.py +++ b/src/foolscap/foolscap/call.py @@ -3,11 +3,11 @@ from twisted.python import failure, log, reflect from twisted.internet import defer from foolscap import copyable, slicer, tokens -from foolscap.eventual import eventually from foolscap.copyable import AttributeDictConstraint from foolscap.constraint import ByteStringConstraint from foolscap.slicers.list import ListConstraint from tokens import BananaError, Violation +from foolscap.util import AsyncAND class FailureConstraint(AttributeDictConstraint): @@ -162,21 +162,6 @@ class InboundDelivery: self.methodname = methodname self.methodSchema = methodSchema self.allargs = allargs - if allargs.isReady(): - self.runnable = True - self.runnable = False - - def isRunnable(self): - if self.allargs.isReady(): - return True - return False - - def whenRunnable(self): - if self.allargs.isReady(): - return defer.succeed(self) - d = self.allargs.whenReady() - d.addCallback(lambda res: self) - return d def logFailure(self, f): # called if tub.logLocalFailures is True @@ -211,7 +196,8 @@ class ArgumentUnslicer(slicer.ScopedUnslicer): self.argname = None self.argConstraint = None self.num_unreferenceable_children = 0 - self.num_unready_children = 0 + self._all_children_are_referenceable_d = None + self._ready_deferreds = [] self.closed = False def checkToken(self, typebyte, size): @@ -248,7 +234,7 @@ class ArgumentUnslicer(slicer.ScopedUnslicer): 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, + len(self._ready_deferreds), token, ready_deferred, self.args, self.kwargs)) if self.numargs is None: # this token is the number of positional arguments @@ -273,12 +259,10 @@ class ArgumentUnslicer(slicer.ScopedUnslicer): # 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) + self._ready_deferreds.append(ready_deferred) if len(self.args) < self.numargs: # more to come ms = self.methodSchema @@ -291,6 +275,7 @@ class ArgumentUnslicer(slicer.ScopedUnslicer): if self.argname is None: # this token is the name of a keyword argument + assert ready_deferred is None self.argname = token # if the argname is invalid, this may raise Violation ms = self.methodSchema @@ -308,12 +293,10 @@ class ArgumentUnslicer(slicer.ScopedUnslicer): if isinstance(argvalue, defer.Deferred): self.num_unreferenceable_children += 1 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._ready_deferreds.append(ready_deferred) self.argname = None return @@ -333,70 +316,31 @@ class ArgumentUnslicer(slicer.ScopedUnslicer): else: self.kwargs[which] = obj self.num_unreferenceable_children -= 1 - self.checkComplete() + if self.num_unreferenceable_children == 0: + if self._all_children_are_referenceable_d: + self._all_children_are_referenceable_d.callback(None) return obj - 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: - return - 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)) + len(self._ready_deferreds))) 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): - assert self.closed + dl = [] if self.num_unreferenceable_children: - return False - if self.num_unready_children: - return False - return True - - def whenReady(self): - assert self.closed - if self.isReady(): - return defer.succeed(self) - d = defer.Deferred() - self.watchers.append(d) - return d + d = self._all_children_are_referenceable_d = defer.Deferred() + dl.append(d) + dl.extend(self._ready_deferreds) + ready_deferred = None + if dl: + ready_deferred = AsyncAND(dl) + return self, ready_deferred def describe(self): s = " RemoteReferenceSchema cache could be built. + assert ready_deferred is None self.stage = 3 if self.objID < 0: @@ -548,6 +493,8 @@ class CallUnslicer(slicer.ScopedUnslicer): # queue the message. It will not be executed until all the # arguments are ready. The .args list and .kwargs dict may change # before then. + if ready_deferred: + self._ready_deferreds.append(ready_deferred) self.stage = 4 return @@ -559,7 +506,10 @@ class CallUnslicer(slicer.ScopedUnslicer): self.interface, self.methodname, self.methodSchema, self.allargs) - return delivery, None + ready_deferred = None + if self._ready_deferreds: + ready_deferred = AsyncAND(self._ready_deferreds) + return delivery, ready_deferred def describe(self): s = " Tue, 07 Aug 2007 17:47:53 -0700 + foolscap (0.1.4) unstable; urgency=low * new release diff --git a/src/foolscap/misc/edgy/debian/changelog b/src/foolscap/misc/edgy/debian/changelog index 9699a6df..d9b00653 100644 --- a/src/foolscap/misc/edgy/debian/changelog +++ b/src/foolscap/misc/edgy/debian/changelog @@ -1,3 +1,9 @@ +foolscap (0.1.5) unstable; urgency=low + + * new release + + -- Brian Warner Tue, 07 Aug 2007 17:47:53 -0700 + foolscap (0.1.4) unstable; urgency=low * new release diff --git a/src/foolscap/misc/feisty/debian/changelog b/src/foolscap/misc/feisty/debian/changelog index 9699a6df..d9b00653 100644 --- a/src/foolscap/misc/feisty/debian/changelog +++ b/src/foolscap/misc/feisty/debian/changelog @@ -1,3 +1,9 @@ +foolscap (0.1.5) unstable; urgency=low + + * new release + + -- Brian Warner Tue, 07 Aug 2007 17:47:53 -0700 + foolscap (0.1.4) unstable; urgency=low * new release diff --git a/src/foolscap/misc/sarge/debian/changelog b/src/foolscap/misc/sarge/debian/changelog index 9699a6df..d9b00653 100644 --- a/src/foolscap/misc/sarge/debian/changelog +++ b/src/foolscap/misc/sarge/debian/changelog @@ -1,3 +1,9 @@ +foolscap (0.1.5) unstable; urgency=low + + * new release + + -- Brian Warner Tue, 07 Aug 2007 17:47:53 -0700 + foolscap (0.1.4) unstable; urgency=low * new release diff --git a/src/foolscap/misc/sid/debian/changelog b/src/foolscap/misc/sid/debian/changelog index 9699a6df..d9b00653 100644 --- a/src/foolscap/misc/sid/debian/changelog +++ b/src/foolscap/misc/sid/debian/changelog @@ -1,3 +1,9 @@ +foolscap (0.1.5) unstable; urgency=low + + * new release + + -- Brian Warner Tue, 07 Aug 2007 17:47:53 -0700 + foolscap (0.1.4) unstable; urgency=low * new release -- 2.45.2