| Home | Trees | Indices | Help |
|
|---|
|
|
1 # -*- test-case-name: foolscap.test.test_sturdyref -*- 2 3 # this module is responsible for sending and receiving OnlyReferenceable and 4 # Referenceable (callable) objects. All details of actually invoking methods 5 # live in call.py 6 7 import weakref, re 8 9 from zope.interface import interface 10 from zope.interface import implements 11 from twisted.python.components import registerAdapter 12 Interface = interface.Interface 13 from twisted.internet import defer 14 from twisted.python import failure, log 15 16 from foolscap import ipb, slicer, tokens, call, base32 17 BananaError = tokens.BananaError 18 Violation = tokens.Violation 19 from foolscap.constraint import IConstraint, ByteStringConstraint 20 from foolscap.remoteinterface import getRemoteInterface, \ 21 getRemoteInterfaceByName, RemoteInterfaceConstraint 22 from foolscap.schema import constraintMap 23 from foolscap.copyable import Copyable, RemoteCopy 24 from foolscap.eventual import eventually, fireEventually 25 3133 implements(ipb.IReferenceable, ipb.IRemotelyCallable) 34 _interface = None 35 _interfaceName = None 36 37 # TODO: this code wants to be in an adapter, not a base class. Also, it 38 # would be nice to cache this across the class: if every instance has the 39 # same interfaces, they will have the same values of _interface and 40 # _interfaceName, and it feels silly to store this data separately for 41 # each instance. Perhaps we could compare the instance's interface list 42 # with that of the class and only recompute this stuff if they differ. 4361 62 constraintMap[Referenceable] = RemoteInterfaceConstraint(None) 6345 if not self._interface: 46 self._interface = getRemoteInterface(self) 47 if self._interface: 48 self._interfaceName = self._interface.__remote_name__ 49 else: 50 self._interfaceName = None 51 return self._interface52 5665 """I hold the data which tracks a local Referenceable that is in used by 66 a remote Broker. 67 68 @ivar obj: the actual object 69 @ivar refcount: the number of times this reference has been sent to the 70 remote end, minus the number of DECREF messages which it 71 has sent back. When it goes to zero, the remote end has 72 forgotten the RemoteReference, and is prepared to forget 73 the RemoteReferenceData as soon as the DECREF message is 74 acknowledged. 75 @ivar clid: the connection-local ID used to represent this object on the 76 wire. 77 """ 78 85109 110 # TODO: rather than subclassing Referenceable, ReferenceableSlicer should be 111 # registered to use for anything which provides any RemoteInterface 11287 """Increment the refcount. 88 @return: True if this is the first transmission of the reference. 89 """ 90 self.refcount += 1 91 if self.refcount == 1: 92 return True93 98100 """Call this in response to a DECREF message from the other end. 101 @return: True if the refcount went to zero, meaning this clid should 102 be retired. 103 """ 104 assert self.refcount >= count, "decref(%d) but refcount was %d" % (count, self.refcount) 105 self.refcount -= count 106 if self.refcount == 0: 107 return True 108 return False114 """I handle pb.Referenceable objects (things with remotely invokable 115 methods, which are copied by reference). 116 """ 117 opentype = ('my-reference',) 118154 155 156 registerAdapter(ReferenceableSlicer, Referenceable, ipb.ISlicer) 157120 broker = self.requireBroker(protocol) 121 puid = ipb.IReferenceable(self.obj).processUniqueID() 122 tracker = broker.getTrackerForMyReference(puid, self.obj) 123 if broker.remote_broker: 124 # emit a my-reference sequence 125 yield 'my-reference' 126 yield tracker.clid 127 firstTime = tracker.send() 128 if firstTime: 129 # this is the first time the Referenceable has crossed this 130 # wire. In addition to the clid, send the interface name (if 131 # any), and any URL this reference might be known by 132 iname = ipb.IRemotelyCallable(self.obj).getInterfaceName() 133 if iname: 134 yield iname 135 else: 136 yield "" 137 url = tracker.getURL() 138 if url: 139 yield url 140 else: 141 # when we're serializing to data, rather than to a live 142 # connection, all of my Referenceables are turned into 143 # their-reference sequences, to prompt the eventual recipient to 144 # create a new connection for this object. 145 146 # a big note on object lifetimes: obviously, the data cannot keep 147 # the Referenceable alive. Use tub.registerReference() on any 148 # Referenceable that you want to include in the serialized data, 149 # and take steps to make sure that later incarnations of this Tub 150 # will do the same. 151 yield 'their-reference' 152 yield 0 # giftID==0 tells the recipient to not try to ack it 153 yield tracker.getURL()159 """Bound methods are serialized as my-reference sequences with negative 160 clid values.""" 161 opentype = ('my-reference',) 162195 196 197 # The CallableSlicer is activated through PBRootSlicer.slicerTable, because a 198 # StorageBanana might want to stick with the old MethodSlicer/FunctionSlicer 199 # for these types 200 #registerAdapter(CallableSlicer, types.MethodType, ipb.ISlicer) 201 202164 broker = self.requireBroker(protocol) 165 # TODO: consider this requirement, maybe based upon a Tub flag 166 # assert ipb.ISlicer(self.obj.im_self) 167 # or maybe even isinstance(self.obj.im_self, Referenceable) 168 puid = id(self.obj) 169 tracker = broker.getTrackerForMyCall(puid, self.obj) 170 yield tracker.clid 171 firstTime = tracker.send() 172 if firstTime: 173 # this is the first time the Call has crossed this wire. In 174 # addition to the clid, send the schema name and any URL this 175 # reference might be known by 176 schema = self.getSchema() 177 if schema: 178 yield schema 179 else: 180 yield "" 181 url = tracker.getURL() 182 if url: 183 yield url184186 return None # TODO: not quite ready yet 187 # callables which are actually bound methods of a pb.Referenceable 188 # can use the schema from that 189 s = ipb.IReferenceable(self.obj.im_self, None) 190 if s: 191 return s.getSchemaForMethodNamed(self.obj.im_func.__name__) 192 # both bound methods and raw callables can also use a .schema 193 # attribute 194 return getattr(self.obj, "schema", None)204 """I turn an incoming 'my-reference' sequence into a RemoteReference or a 205 RemoteMethodReference.""" 206 state = 0 207 clid = None 208 interfaceName = None 209 url = None 210 inameConstraint = ByteStringConstraint(200) # TODO: only known RI names? 211 urlConstraint = ByteStringConstraint(200) 212255 256 257214 if self.state == 0: 215 if typebyte not in (tokens.INT, tokens.NEG): 216 raise BananaError("reference ID must be an INT or NEG") 217 elif self.state == 1: 218 self.inameConstraint.checkToken(typebyte, size) 219 elif self.state == 2: 220 self.urlConstraint.checkToken(typebyte, size) 221 else: 222 raise Violation("too many parameters in my-reference")223225 assert not isinstance(obj, defer.Deferred) 226 assert ready_deferred is None 227 if self.state == 0: 228 self.clid = obj 229 self.state = 1 230 elif self.state == 1: 231 # must be the interface name 232 self.interfaceName = obj 233 if obj == "": 234 self.interfaceName = None 235 self.state = 2 236 elif self.state == 2: 237 # URL 238 self.url = obj 239 self.state = 3 240 else: 241 raise BananaError("Too many my-reference parameters")242244 if self.clid is None: 245 raise BananaError("sequence ended too early") 246 tracker = self.broker.getTrackerForYourReference(self.clid, 247 self.interfaceName, 248 self.url) 249 return tracker.getRef(), None250259 """I hold the data necessary to locate (or create) a RemoteReference. 260 261 @ivar url: the target Referenceable's global URL 262 @ivar broker: the Broker which holds this RemoteReference 263 @ivar clid: for that Broker, the your-reference CLID for the 264 RemoteReference 265 @ivar interfaceName: the name of a RemoteInterface object that the 266 RemoteReference claims to implement 267 @ivar interface: our version of a RemoteInterface object that corresponds 268 to interfaceName 269 @ivar received_count: the number of times the remote end has send us this 270 object. We must send back decref() calls to match. 271 @ivar ref: a weakref to the RemoteReference itself 272 """ 273275 self.broker = parent 276 self.clid = clid 277 # TODO: the remote end sends us a global URL, when really it should 278 # probably send us a per-Tub name, which can can then concatenate to 279 # their TubID if/when we pass it on to others. By accepting a full 280 # URL, we give them the ability to sort-of spoof others. We could 281 # check that url.startswith(broker.remoteTub.baseURL), but the Right 282 # Way is to just not have them send the base part in the first place. 283 # I haven't yet made this change because I'm not yet positive it 284 # would work.. how exactly does the base url get sent, anyway? What 285 # about Tubs visible through multiple names? 286 self.url = url 287 self.interfaceName = interfaceName 288 self.interface = getRemoteInterfaceByName(interfaceName) 289 self.received_count = 0 290 self.ref = None291