Package foolscap :: Module referenceable
[hide private]
[frames] | no frames]

Source Code for Module foolscap.referenceable

  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   
26 -class OnlyReferenceable(object):
27 implements(ipb.IReferenceable) 28
29 - def processUniqueID(self):
30 return id(self)
31
32 -class Referenceable(OnlyReferenceable):
33 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. 43
44 - def getInterface(self):
45 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._interface
52
53 - def getInterfaceName(self):
54 self.getInterface() 55 return self._interfaceName
56
57 - def doRemoteCall(self, methodname, args, kwargs):
58 meth = getattr(self, "remote_%s" % methodname) 59 res = meth(*args, **kwargs) 60 return res
61 62 constraintMap[Referenceable] = RemoteInterfaceConstraint(None) 63
64 -class ReferenceableTracker:
65 """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
79 - def __init__(self, tub, obj, puid, clid):
80 self.tub = tub 81 self.obj = obj 82 self.clid = clid 83 self.puid = puid 84 self.refcount = 0
85
86 - def send(self):
87 """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 True
93
94 - def getURL(self):
95 if self.tub: 96 return self.tub.getOrCreateURLForReference(self.obj) 97 return None
98
99 - def decref(self, count):
100 """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 False
109 110 # TODO: rather than subclassing Referenceable, ReferenceableSlicer should be 111 # registered to use for anything which provides any RemoteInterface 112
113 -class ReferenceableSlicer(slicer.BaseSlicer):
114 """I handle pb.Referenceable objects (things with remotely invokable 115 methods, which are copied by reference). 116 """ 117 opentype = ('my-reference',) 118
119 - def slice(self, streamable, protocol):
120 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()
154 155 156 registerAdapter(ReferenceableSlicer, Referenceable, ipb.ISlicer) 157
158 -class CallableSlicer(slicer.BaseSlicer):
159 """Bound methods are serialized as my-reference sequences with negative 160 clid values.""" 161 opentype = ('my-reference',) 162
163 - def sliceBody(self, streamable, protocol):
164 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 url
184
185 - def getSchema(self):
186 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)
195 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 202
203 -class ReferenceUnslicer(slicer.BaseUnslicer):
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) 212
213 - def checkToken(self, typebyte, size):
214 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")
223
224 - def receiveChild(self, obj, ready_deferred=None):
225 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")
242
243 - def receiveClose(self):
244 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(), None
250
251 - def describe(self):
252 if self.clid is None: 253 return "<ref-?>" 254 return "<ref-%s>" % self.clid
255 256 257
258 -class RemoteReferenceTracker:
259 """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 """ 273
274 - def __init__(self, parent, clid, url, interfaceName):
275 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 = None
291
292 - def __repr__(self):
293 s = "<RemoteReferenceTracker(clid=%d,url=%s)>" % (self.clid, self.