Changeset 209:87a1dce6b4ce

Show
Ignore:
Timestamp:
07/26/07 16:40:56 (3 years ago)
Author:
"Brian Warner <warner@lothar.com>"
branch:
default
Message:

make sure errors during Gift resolution are reported to the original caller. Closes #2.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • ChangeLog

    r208 r209  
     12007-07-26  Brian Warner  <warner@lothar.com> 
     2 
     3        * foolscap/slicer.py (BaseUnslicer.receiveChild): new convention: 
     4        Unslicers should accumulate their children's ready_deferreds into 
     5        an AsyncAND, and pass it to the parent. If something goes wrong, 
     6        the ready_deferred should errback, which will abandon the method 
     7        call that contains it. 
     8        * foolscap/slicers/dict.py (DictUnslicer.receiveClose): same 
     9        * foolscap/slicers/tuple.py (TupleUnslicer.receiveClose): same 
     10        (TupleUnslicer.complete): same 
     11        * foolscap/slicers/set.py (SetUnslicer.receiveClose): same 
     12        * foolscap/slicers/list.py (ListUnslicer.receiveClose): same 
     13        * foolscap/call.py (CallUnslicer.receiveClose): same 
     14 
     15        * foolscap/referenceable.py (TheirReferenceUnslicer.receiveClose): 
     16        use our ready_deferred to signal whether the gift resolves 
     17        correctly or not. If it fails, errback ready_deferred (to prevent 
     18        the message from being delivered without the resolved gift), but 
     19        callback obj_deferred with a placeholder to avoid causing too much 
     20        distress to the container. 
     21 
     22        * foolscap/broker.py (PBRootUnslicer.receiveChild): accept 
     23        ready_deferred in the InboundDelivery, stash both of them in the 
     24        broker. 
     25        (Broker.scheduleCall): rewrite inbound delivery handling: use a 
     26        self._call_is_running flag to prevent concurrent deliveries, and 
     27        wait for the ready_deferred before delivering the top-most 
     28        message. If the ready_deferred errbacks, that gets routed to 
     29        self.callFailed so the caller hears about the problem. This closes 
     30        ticket #2. 
     31 
     32        * foolscap/call.py (InboundDelivery): remove whenRunnable, relying 
     33        upon the ready_deferred to let the Broker know when the message 
     34        can be delivered. 
     35        (ArgumentUnslicer): significant cleanup, using ready_deferred. 
     36        Remove isReady and whenReady. 
     37 
     38        * foolscap/test/test_gifts.py (Base): factor setup code out 
     39        (Base.createCharacters): registerReference(tubname), for debugging 
     40        (Bad): add a bunch of tests to make sure that gifts which fail to 
     41        resolve (for various reasons) will inform the caller about the 
     42        problem, via an errback on the original callRemote()'s Deferred. 
     43 
    1442007-07-25  Brian Warner  <warner@lothar.com> 
    245 
  • foolscap/broker.py

    r170 r209  
    112112    def receiveChild(self, token, ready_deferred): 
    113113        if isinstance(token, call.InboundDelivery): 
    114             assert ready_deferred is None 
    115             self.broker.scheduleCall(token) 
     114            self.broker.scheduleCall(token, ready_deferred) 
    116115 
    117116 
     
    216215        # receiving side uses these 
    217216        self.inboundDeliveryQueue = [] 
     217        self._call_is_running = False 
    218218        self.activeLocalCalls = {} # the other side wants an answer from us 
    219219 
     
    507507        return None 
    508508 
    509     def scheduleCall(self, delivery): 
    510         self.inboundDeliveryQueue.append(delivery
     509    def scheduleCall(self, delivery, ready_deferred): 
     510        self.inboundDeliveryQueue.append( (delivery,ready_deferred)
    511511        eventually(self.doNextCall) 
    512512 
    513     def doNextCall(self, ignored=None): 
     513    def doNextCall(self): 
     514        if self._call_is_running: 
     515            return 
    514516        if not self.inboundDeliveryQueue: 
    515517            return 
    516         nextCall = self.inboundDeliveryQueue[0] 
    517         if nextCall.isRunnable(): 
    518             # remove it and arrange to run again soon 
    519             self.inboundDeliveryQueue.pop(0) 
    520             delivery = nextCall 
    521             if self.inboundDeliveryQueue: 
    522                 eventually(self.doNextCall) 
    523  
    524             # now perform the actual delivery 
    525             d = defer.maybeDeferred(self._doCall, delivery) 
    526             d.addCallback(self._callFinished, delivery) 
    527             d.addErrback(self.callFailed, delivery.reqID, delivery) 
    528             return 
    529         # arrange to wake up when the next call becomes runnable 
    530         d = nextCall.whenRunnable() 
    531         d.addCallback(self.doNextCall) 
     518        delivery, ready_deferred = self.inboundDeliveryQueue.pop(0) 
     519        self._call_is_running = True 
     520        if not ready_deferred: 
     521            ready_deferred = defer.succeed(None) 
     522        d = ready_deferred 
     523        d.addCallback(lambda res: self._doCall(delivery)) 
     524        d.addCallback(self._callFinished, delivery) 
     525        d.addErrback(self.callFailed, delivery.reqID, delivery) 
     526        def _done(res): 
     527            self._call_is_running = False 
     528            eventually(self.doNextCall) 
     529        d.addBoth(_done) 
     530        return None 
    532531 
    533532    def _doCall(self, delivery): 
    534533        obj = delivery.obj 
    535         assert delivery.allargs.isReady() 
    536534        args = delivery.allargs.args 
    537535        kwargs = delivery.allargs.kwargs 
  • foolscap/call.py

    r207 r209  
    44 
    55from foolscap import copyable, slicer, tokens 
    6 from foolscap.eventual import eventually 
    76from foolscap.copyable import AttributeDictConstraint 
    87from foolscap.constraint import ByteStringConstraint 
    98from foolscap.slicers.list import ListConstraint 
    109from tokens import BananaError, Violation 
     10from foolscap.util import AsyncAND 
    1111 
    1212 
     
    163163        self.methodSchema = methodSchema 
    164164        self.allargs = allargs 
    165         if allargs.isReady(): 
    166             self.runnable = True 
    167         self.runnable = False 
    168  
    169     def isRunnable(self): 
    170         if self.allargs.isReady(): 
    171             return True 
    172         return False 
    173  
    174     def whenRunnable(self): 
    175         if self.allargs.isReady(): 
    176             return defer.succeed(self) 
    177         d = self.allargs.whenReady() 
    178         d.addCallback(lambda res: self) 
    179         return d 
    180165 
    181166    def logFailure(self, f): 
     
    212197        self.argConstraint = None 
    213198        self.num_unreferenceable_children = 0 
    214         self.num_unready_children = 0 
     199        self._all_children_are_referenceable_d = None 
     200        self._ready_deferreds = [] 
    215201        self.closed = False 
    216202 
     
    249235            log.msg("%s.receiveChild: %s %s %s %s %s args=%s kwargs=%s" % 
    250236                    (self, self.closed, self.num_unreferenceable_children, 
    251                      self.num_unready_children, token, ready_deferred, 
     237                     len(self._ready_deferreds), token, ready_deferred, 
    252238                     self.args, self.kwargs)) 
    253239        if self.numargs is None: 
     
    274260                self.num_unreferenceable_children += 1 
    275261                argvalue.addCallback(self.updateChild, argpos) 
    276                 argvalue.addErrback(self.explode) 
    277262            if ready_deferred: 
    278263                if self.debug: 
    279264                    log.msg("%s.receiveChild got an unready posarg" % self) 
    280                 self.num_unready_children += 1 
    281                 ready_deferred.addCallback(self.childReady) 
     265                self._ready_deferreds.append(ready_deferred) 
    282266            if len(self.args) < self.numargs: 
    283267                # more to come 
     
    292276        if self.argname is None: 
    293277            # this token is the name of a keyword argument 
     278            assert ready_deferred is None 
    294279            self.argname = token 
    295280            # if the argname is invalid, this may raise Violation 
     
    309294            self.num_unreferenceable_children += 1 
    310295            argvalue.addCallback(self.updateChild, self.argname) 
    311             argvalue.addErrback(self.explode) 
    312296        if ready_deferred: 
    313297            if self.debug: 
    314298                log.msg("%s.receiveChild got an unready kwarg" % self) 
    315             self.num_unready_children += 1 
    316             ready_deferred.addCallback(self.childReady) 
     299            self._ready_deferreds.append(ready_deferred) 
    317300        self.argname = None 
    318301        return 
     
    334317            self.kwargs[which] = obj 
    335318        self.num_unreferenceable_children -= 1 
    336         self.checkComplete() 
     319        if self.num_unreferenceable_children == 0: 
     320            if self._all_children_are_referenceable_d: 
     321                self._all_children_are_referenceable_d.callback(None) 
    337322        return obj 
    338323 
    339     def childReady(self, obj): 
    340         self.num_unready_children -= 1 
    341         if self.debug: 
    342             log.msg("%s.childReady, now %d left" % 
    343                     (self, self.num_unready_children)) 
    344             log.msg(" obj=%s, args=%s, kwargs=%s" % 
    345                     (obj, self.args, self.kwargs)) 
    346         self.checkComplete() 
    347         return obj 
    348  
    349     def checkComplete(self): 
    350         # this is called each time one of our children gets updated or 
    351         # becomes ready (like when a Gift is finally resolved) 
    352         if self.debug: 
    353             log.msg("%s.checkComplete: %s %s %s args=%s kwargs=%s" % 
    354                     (self, self.closed, self.num_unreferenceable_children, 
    355                      self.num_unready_children, self.args, self.kwargs)) 
    356  
    357         if not self.closed: 
    358             return 
    359         if self.num_unreferenceable_children: 
    360             return 
    361         if self.num_unready_children: 
    362             return 
    363         # yup, we're done. Notify anyone who is still waiting 
    364         if self.debug: 
    365             log.msg(" we are ready") 
    366         for d in self.watchers: 
    367             eventually(d.callback, self) 
    368         del self.watchers 
    369324 
    370325    def receiveClose(self): 
     
    372327            log.msg("%s.receiveClose: %s %s %s" % 
    373328                    (self, self.closed, self.num_unreferenceable_children, 
    374                      self.num_unready_children)) 
     329                     len(self._ready_deferreds))) 
    375330        if (self.numargs is None or 
    376331            len(self.args) < self.numargs or 
     
    378333            raise BananaError("'arguments' sequence ended too early") 
    379334        self.closed = True 
    380         self.watchers = [] 
    381         # we don't return a ready_deferred. Instead, the InboundDelivery 
    382         # object queries our isReady() method directly. 
    383         return self, None 
    384  
    385     def isReady(self): 
    386         assert self.closed 
     335        dl = [] 
    387336        if self.num_unreferenceable_children: 
    388             return False 
    389         if self.num_unready_children: 
    390             return False 
    391         return True 
    392  
    393     def whenReady(self): 
    394         assert self.closed 
    395         if self.isReady(): 
    396             return defer.succeed(self) 
    397         d = defer.Deferred() 
    398         self.watchers.append(d) 
    399         return d 
     337            d = self._all_children_are_referenceable_d = defer.Deferred() 
     338            dl.append(d) 
     339        dl.extend(self._ready_deferreds) 
     340        ready_deferred = None 
     341        if dl: 
     342            ready_deferred = AsyncAND(dl) 
     343        return self, ready_deferred 
    400344 
    401345    def describe(self): 
     
    410354                    s += " arg[?]" 
    411355        if self.closed: 
    412             if self.isReady(): 
    413                 # waiting to be delivered 
    414                 s += " ready" 
    415             else: 
    416                 s += " waiting" 
     356            s += " closed" 
     357            # TODO: it would be nice to indicate if we still have unready 
     358            # children 
    417359        s += ">" 
    418360        return s 
     
    431373        self.methodname = None 
    432374        self.methodSchema = None # will be a MethodArgumentsConstraint 
     375        self._ready_deferreds = [] 
    433376 
    434377    def checkToken(self, typebyte, size): 
     
    473416    def receiveChild(self, token, ready_deferred=None): 
    474417        assert not isinstance(token, defer.Deferred) 
    475         assert ready_deferred is None 
    476418        if self.debug: 
    477419            log.msg("%s.receiveChild [s%d]: %s" % 
     
    480422        if self.stage == 0: # reqID 
    481423            # we don't yet know which reqID to send any failure to 
     424            assert ready_deferred is None 
    482425            self.reqID = token 
    483426            self.stage = 1 
     
    489432        if self.stage == 1: # objID 
    490433            # this might raise an exception if objID is invalid 
     434            assert ready_deferred is None 
    491435            self.objID = token 
    492436            self.obj = self.broker.getMyReferenceByCLID(token) 
     
    518462            # obj.__class__ -> RemoteReferenceSchema cache could be built. 
    519463 
     464            assert ready_deferred is None 
    520465            self.stage = 3 
    521466 
     
    549494            # arguments are ready. The .args list and .kwargs dict may change 
    550495            # before then. 
     496            if ready_deferred: 
     497                self._ready_deferreds.append(ready_deferred) 
    551498            self.stage = 4 
    552499            return 
     
    560507                                   self.methodSchema, 
    561508                                   self.allargs) 
    562         return delivery, None 
     509        ready_deferred = None 
     510        if self._ready_deferreds: 
     511            ready_deferred = AsyncAND(self._ready_deferreds) 
     512        return delivery, ready_deferred 
    563513 
    564514    def describe(self): 
  • foolscap/referenceable.py

    r191 r209  
    1212Interface = interface.Interface 
    1313from twisted.internet import defer 
    14 from twisted.python import failure 
     14from twisted.python import failure, log 
    1515 
    1616from foolscap import ipb, slicer, tokens, call 
     
    636636        # complete. See to it that we fire the object deferred before we fire 
    637637        # the ready_deferred. 
    638         obj_deferred, ready_deferred = defer.Deferred(), defer.Deferred() 
     638 
     639        obj_deferred = defer.Deferred() 
     640        ready_deferred = defer.Deferred() 
     641 
    639642        def _ready(rref): 
    640643            obj_deferred.callback(rref) 
    641644            ready_deferred.callback(rref) 
    642         d.addCallback(_ready) 
     645        def _failed(f): 
     646            # if an error in getReference() occurs, log it locally (with 
     647            # priority UNUSUAL), because this end might need to diagnose some 
     648            # connection or networking problems. 
     649            log.msg("gift (%s) failed to resolve: %s" % (self.url, f)) 
     650            # deliver a placeholder object to the container, but signal the 
     651            # ready_deferred that we've failed. This will bubble up to the 
     652            # enclosing InboundDelivery, and when it gets to the top of the 
     653            # queue, it will be flunked. 
     654            obj_deferred.callback("Place holder for a Gift which failed to " 
     655                                  "resolve: %s" % f) 
     656            ready_deferred.errback(f) 
     657        d.addCallbacks(_ready, _failed) 
    643658 
    644659        return obj_deferred, ready_deferred 
  • foolscap/slicer.py

    r88 r209  
    22 
    33from twisted.python.components import registerAdapter 
     4from twisted.python import log 
    45from zope.interface import implements 
    56from twisted.internet.defer import Deferred 
     
    191192 
    192193    def receiveChild(self, obj, ready_deferred=None): 
     194        """Unslicers for containers should accumulate their children's 
     195        ready_deferreds, then combine them in an AsyncAND when receiveClose() 
     196        happens, and return the AsyncAND as the ready_deferreds half of the 
     197        receiveClose() return value. 
     198        """ 
    193199        pass 
    194200 
     
    222228 
    223229    def explode(self, failure): 
    224         """If something goes wrong in a Deferred callback, it may be too 
    225         late to reject the token and to normal error handling. I haven't 
    226         figured out how to do sensible error-handling in this situation. 
    227         This method exists to make sure that the exception shows up 
    228         *somewhere*. If this is called, it is also likely that a placeholder 
    229         (probably a Deferred) will be left in the unserialized object about 
    230         to be handed to the RootUnslicer. 
    231         """ 
    232         print "KABOOM" 
    233         print failure 
     230        """If something goes wrong in a Deferred callback, it may be too late 
     231        to reject the token and to normal error handling. I haven't figured 
     232        out how to do sensible error-handling in this situation. This method 
     233        exists to make sure that the exception shows up *somewhere*. If this 
     234        is called, it is also likely that a placeholder (probably a Deferred) 
     235        will be left in the unserialized object graph about to be handed to 
     236        the RootUnslicer. 
     237        """ 
     238 
     239        # RootUnslicer pays attention to this .exploded attribute and refuses 
     240        # to deliver anything if it is set. But PBRootUnslicer ignores it. 
     241        # TODO: clean this up, and write some unit tests to trigger it (by 
     242        # violating schemas?) 
     243        log.msg("BaseUnslicer.explode: %s" % failure) 
    234244        self.protocol.exploded = failure 
    235245 
  • foolscap/slicers/dict.py

    r187 r209  
    22 
    33from twisted.python import log 
    4 from twisted.internet.defer import Deferred, DeferredList 
     4from twisted.internet.defer import Deferred 
    55from foolscap.tokens import Violation, BananaError 
    66from foolscap.slicer import BaseSlicer, BaseUnslicer 
    77from foolscap.constraint import OpenerConstraint, Any, UnboundedSchema, IConstraint 
     8from foolscap.util import AsyncAND 
    89 
    910class DictSlicer(BaseSlicer): 
     
    106107        ready_deferred = None 
    107108        if self._ready_deferreds: 
    108             ready_deferred = DeferredList(self._ready_deferreds) 
     109            ready_deferred = AsyncAND(self._ready_deferreds) 
    109110        return self.d, ready_deferred 
    110111 
  • foolscap/slicers/list.py

    r187 r209  
    22 
    33from twisted.python import log 
    4 from twisted.internet.defer import Deferred, DeferredList 
     4from twisted.internet.defer import Deferred 
    55from foolscap.tokens import Violation 
    66from foolscap.slicer import BaseSlicer, BaseUnslicer 
    77from foolscap.constraint import OpenerConstraint, Any, UnboundedSchema, IConstraint 
     8from foolscap.util import AsyncAND 
    89 
    910 
     
    106107        ready_deferred = None 
    107108        if self._ready_deferreds: 
    108             ready_deferred = DeferredList(self._ready_deferreds) 
     109            ready_deferred = AsyncAND(self._ready_deferreds) 
    109110        return self.list, ready_deferred 
    110111 
  • foolscap/slicers/set.py

    r187 r209  
    1010from foolscap.constraint import OpenerConstraint, UnboundedSchema, Any, \ 
    1111     IConstraint 
     12from foolscap.util import AsyncAND 
    1213 
    1314class SetSlicer(ListSlicer): 
     
    137138        ready_deferred = None 
    138139        if self._ready_deferreds: 
    139             ready_deferred = defer.DeferredList(self._ready_deferreds) 
     140            ready_deferred = AsyncAND(self._ready_deferreds) 
    140141        return self.set, ready_deferred 
    141142 
  • foolscap/slicers/tuple.py

    r187 r209  
    11# -*- test-case-name: foolscap.test.test_banana -*- 
    22 
    3 from twisted.internet.defer import Deferred, DeferredList 
     3from twisted.internet.defer import Deferred 
    44from foolscap.tokens import Violation 
    55from foolscap.slicer import BaseUnslicer 
    66from foolscap.slicers.list import ListSlicer 
    77from foolscap.constraint import OpenerConstraint, Any, UnboundedSchema, IConstraint 
     8from foolscap.util import AsyncAND 
    89 
    910 
     
    9293        ready_deferred = None 
    9394        if self._ready_deferreds: 
    94             ready_deferred = DeferredList(self._ready_deferreds) 
     95            ready_deferred = AsyncAND(self._ready_deferreds) 
    9596 
    9697        t = tuple(self.list) 
     
    112113            ready_deferred = None 
    113114            if self._ready_deferreds: 
    114                 ready_deferred = DeferredList(self._ready_deferreds) 
     115                ready_deferred = AsyncAND(self._ready_deferreds) 
    115116            return self.deferred, ready_deferred 
    116117 
  • foolscap/test/test_gifts.py

    r189 r209  
    22from zope.interface import implements 
    33from twisted.trial import unittest 
    4 from twisted.internet import defer 
    5 from twisted.internet.error import ConnectionDone, ConnectionLost 
     4from twisted.internet import defer, protocol, reactor 
     5from twisted.internet.error import ConnectionDone, ConnectionLost, \ 
     6     ConnectionRefusedError 
     7from twisted.python import failure 
    68from foolscap import Tub, UnauthenticatedTub, RemoteInterface, Referenceable 
    7 from foolscap.referenceable import RemoteReference 
     9from foolscap.referenceable import RemoteReference, SturdyRef 
    810from foolscap.test.common import HelperTarget, RIHelper 
    911from foolscap.eventual import flushEventualQueue 
     12from foolscap.tokens import BananaError, NegotiationError 
    1013 
    1114crypto_available = False 
     
    3942        self.obj = obj 
    4043 
    41 class Gifts(unittest.TestCase): 
    42     # Here we test the three-party introduction process as depicted in the 
    43     # classic Granovetter diagram. Alice has a reference to Bob and another 
    44     # one to Carol. Alice wants to give her Carol-reference to Bob, by 
    45     # including it as the argument to a method she invokes on her 
    46     # Bob-reference. 
     44class Base: 
    4745 
    4846    debug = False 
    4947 
    5048    def setUp(self): 
    51         self.services = [GoodEnoughTub(), GoodEnoughTub(), GoodEnoughTub()] 
    52         self.tubA, self.tubB, self.tubC = self.services 
     49        self.services = [GoodEnoughTub() for i in range(4)] 
     50        self.tubA, self.tubB, self.tubC, self.tubD = self.services 
    5351        for s in self.services: 
    5452            s.startService() 
     
    6462        self.alice = HelperTarget("alice") 
    6563        self.bob = HelperTarget("bob") 
    66         self.bob_url = self.tubB.registerReference(self.bob
     64        self.bob_url = self.tubB.registerReference(self.bob, "bob"
    6765        self.carol = HelperTarget("carol") 
    68         self.carol_url = self.tubC.registerReference(self.carol
     66        self.carol_url = self.tubC.registerReference(self.carol, "carol"
    6967        # cindy is Carol's little sister. She doesn't have a phone, but 
    7068        # Carol might talk about her anyway. 
     
    7674        self.colette = HelperTarget("colette") 
    7775        self.courtney = HelperTarget("courtney") 
     76        self.dave = HelperTarget("dave") 
     77        self.dave_url = self.tubD.registerReference(self.dave, "dave") 
    7878 
    7979    def createInitialReferences(self): 
     
    9191            if self.debug: print "Alice got carol" 
    9292            self.acarol = acarol # Alice's reference to Carol 
     93            d = self.tubB.getReference(self.dave_url) 
     94            return d 
    9395        d.addCallback(_aliceGotCarol) 
     96        def _bobGotDave(bdave): 
     97            self.bdave = bdave 
     98        d.addCallback(_bobGotDave) 
    9499        return d 
    95100 
     
    98103        dl = [] 
    99104 
    100         url = self.tubC.registerReference(self.charlene
     105        url = self.tubC.registerReference(self.charlene, "charlene"
    101106        d = self.tubA.getReference(url) 
    102107        def _got_charlene(rref): 
     
    105110        dl.append(d) 
    106111 
    107         url = self.tubC.registerReference(self.christine
     112        url = self.tubC.registerReference(self.christine, "christine"
    108113        d = self.tubA.getReference(url) 
    109114        def _got_christine(rref): 
     
    112117        dl.append(d) 
    113118 
    114         url = self.tubC.registerReference(self.clarisse
     119        url = self.tubC.registerReference(self.clarisse, "clarisse"
    115120        d = self.tubA.getReference(url) 
    116121        def _got_clarisse(rref): 
     
    119124        dl.append(d) 
    120125 
    121         url = self.tubC.registerReference(self.colette
     126        url = self.tubC.registerReference(self.colette, "colette"
    122127        d = self.tubA.getReference(url) 
    123128        def _got_colette(rref): 
     
    126131        dl.append(d) 
    127132 
    128         url = self.tubC.registerReference(self.courtney
     133        url = self.tubC.registerReference(self.courtney, "courtney"
    129134        d = self.tubA.getReference(url) 
    130135        def _got_courtney(rref): 
     
    132137        d.addCallback(_got_courtney) 
    133138        dl.append(d) 
     139 
    134140        return defer.DeferredList(dl) 
     141 
     142    def shouldFail(self, res, expected_failure, which, substring=None): 
     143        # attach this with: 
     144        #  d = something() 
     145        #  d.addBoth(self.shouldFail, IndexError, "something") 
     146        # the 'which' string helps to identify which call to shouldFail was 
     147        # triggered, since certain versions of Twisted don't display this 
     148        # very well. 
     149 
     150        if isinstance(res, failure.Failure): 
     151            res.trap(expected_failure) 
     152            if substring: 
     153                self.failUnless(substring in str(res), 
     154                                "substring '%s' not in '%s'" 
     155                                % (substring, str(res))) 
     156        else: 
     157            self.fail("%s was supposed to raise %s, not get '%s'" % 
     158                      (which, expected_failure, res)) 
     159 
     160class Gifts(Base, unittest.TestCase): 
     161    # Here we test the three-party introduction process as depicted in the 
     162    # classic Granovetter diagram. Alice has a reference to Bob and another 
     163    # one to Carol. Alice wants to give her Carol-reference to Bob, by 
     164    # including it as the argument to a method she invokes on her 
     165    # Bob-reference. 
    135166 
    136167    def testGift(self): 
     
    164195        d.addCallback(_carolCalled) 
    165196        return d 
    166  
    167197 
    168198    def testImplicitGift(self): 
     
    227257        return d 
    228258 
    229  
    230259    def testOrdering(self): 
    231260        self.createCharacters() 
     
    304333        self.alice = HelperTarget("alice") 
    305334        self.bob = ConstrainedHelper("bob") 
    306         self.bob_url = self.tubB.registerReference(self.bob
     335        self.bob_url = self.tubB.registerReference(self.bob, "bob"
    307336        self.carol = HelperTarget("carol") 
    308         self.carol_url = self.tubC.registerReference(self.carol) 
     337        self.carol_url = self.tubC.registerReference(self.carol, "carol") 
     338        self.dave = HelperTarget("dave") 
     339        self.dave_url = self.tubD.registerReference(self.dave, "dave") 
    309340 
    310341    def test_constraint(self): 
     
    319350        d.addCallback(_checkBob) 
    320351        return d 
     352 
     353 
    321354 
    322355    # this was used to alice's reference to carol (self.acarol) appeared in 
     
    360393        return d 
    361394 
     395 
     396class Bad(Base, unittest.TestCase): 
     397 
     398    # if the recipient cannot claim their gift, the caller should see an 
     399    # errback. 
     400 
     401    def test_swissnum(self): 
     402        self.createCharacters() 
     403        d = self.createInitialReferences() 
     404        d.addCallback(lambda res: self.tubA.getReference(self.dave_url)) 
     405        def _introduce(adave): 
     406            # now break the gift to insure that Bob is unable to claim it. 
     407            # The first way to do this is to simple mangle the swissnum, 
     408            # which will result in a failure in remote_getReferenceByName. 
     409            # NOTE: this will have to change when we modify the way gifts are 
     410            # referenced, since tracker.url is scheduled to go away. 
     411            r = SturdyRef(adave.tracker.url) 
     412            r.name += ".MANGLED" 
     413            adave.tracker.url = r.getURL() 
     414            return self.acarol.callRemote("set", adave) 
     415        d.addCallback(_introduce) 
     416        d.addBoth(self.shouldFail, KeyError, "Bad.test_swissnum") 
     417        # make sure we can still talk to Carol, though 
     418        d.addCallback(lambda res: self.acarol.callRemote("set", 14)) 
     419        d.addCallback(lambda res: self.failUnlessEqual(self.carol.obj, 14)) 
     420        return d 
     421    test_swissnum.timeout = 10 
     422 
     423    def test_tubid(self): 
     424        self.createCharacters() 
     425        d = self.createInitialReferences() 
     426        d.addCallback(lambda res: self.tubA.getReference(self.dave_url)) 
     427        def _introduce(adave): 
     428            # The second way is to mangle the tubid, which will result in a 
     429            # failure during negotiation. NOTE: this will have to change when 
     430            # we modify the way gifts are referenced, since tracker.url is 
     431            # scheduled to go away. 
     432            r = SturdyRef(adave.tracker.url) 
     433            r.tubID += ".MANGLED" 
     434            adave.tracker.url = r.getURL() 
     435            return self.acarol.callRemote("set", adave) 
     436        d.addCallback(_introduce) 
     437        d.addBoth(self.shouldFail, BananaError, "Bad.test_tubid", 
     438                  "unknown TubID") 
     439        return d 
     440    test_tubid.timeout = 10 
     441 
     442    def test_location(self): 
     443        self.createCharacters() 
     444        d = self.createInitialReferences() 
     445        d.addCallback(lambda res: self.tubA.getReference(self.dave_url)) 
     446        def _introduce(adave): 
     447            # The third way is to mangle the location hints, which will 
     448            # result in a failure during negotiation as it attempts to 
     449            # establish a TCP connection. 
     450            r = SturdyRef(adave.tracker.url) 
     451            # highly unlikely that there's anything listening on this port 
     452            r.locationHints = ["127.0.0.47:1"] 
     453            adave.tracker.url = r.getURL() 
     454            return self.acarol.callRemote("set", adave) 
     455        d.addCallback(_introduce) 
     456        d.addBoth(self.shouldFail, ConnectionRefusedError, "Bad.test_location") 
     457        return d 
     458    test_location.timeout = 10 
     459 
     460    def test_hang(self): 
     461        f = protocol.Factory() 
     462        f.protocol = protocol.Protocol # ignores all input 
     463        p = reactor.listenTCP(0, f, interface="127.0.0.1") 
     464        self.createCharacters() 
     465        d = self.createInitialReferences() 
     466        d.addCallback(lambda res: self.tubA.getReference(self.dave_url)) 
     467        def _introduce(adave): 
     468            # The next form of mangling is to connect to a port which never 
     469            # responds, which could happen if a firewall were silently 
     470            # dropping the TCP packets. We can't accurately simulate this 
     471            # case, but we can connect to a port which accepts the connection 
     472            # and then stays silent. This should trigger the overall 
     473            # connection timeout. 
     474            r = SturdyRef(adave.tracker.url) 
     475            r.locationHints = ["127.0.0.1:%d" % p.getHost().port] 
     476            adave.tracker.url = r.getURL() 
     477            self.tubD.options['connect_timeout'] = 2 
     478            return self.acarol.callRemote("set", adave) 
     479        d.addCallback(_introduce) 
     480        d.addBoth(self.shouldFail, NegotiationError, "Bad.test_hang", 
     481                  "no connection established within client timeout") 
     482        def _stop_listening(res): 
     483            d1 = p.stopListening() 
     484            def _done_listening(x): 
     485                return res 
     486            d1.addCallback(_done_listening) 
     487            return d1 
     488        d.addBoth(_stop_listening) 
     489        return d 
     490    test_hang.timeout = 10 
     491     
     492