| 1 |
-*- outline -*- |
|---|
| 2 |
|
|---|
| 3 |
non-independent things left to do on newpb. These require deeper magic or |
|---|
| 4 |
can not otherwise be done casually. Many of these involve fundamental |
|---|
| 5 |
protocol issues, and therefore need to be decided sooner rather than later. |
|---|
| 6 |
|
|---|
| 7 |
* summary |
|---|
| 8 |
** protocol issues |
|---|
| 9 |
*** negotiation |
|---|
| 10 |
*** VOCABADD/DEL/SET sequences |
|---|
| 11 |
*** remove 'copy' prefix from RemoteCopy type sequences? |
|---|
| 12 |
*** smaller scope for OPEN-counter reference numbers? |
|---|
| 13 |
** implementation issues |
|---|
| 14 |
*** cred |
|---|
| 15 |
*** oldbanana compatibility |
|---|
| 16 |
*** Copyable/RemoteCopy default to __getstate__ or self.__dict__ ? |
|---|
| 17 |
*** RIFoo['bar'] vs RIFoo.bar (should RemoteInterface inherit from Interface?) |
|---|
| 18 |
*** constrain ReferenceUnslicer |
|---|
| 19 |
*** serialize target.remote_foo usefully |
|---|
| 20 |
|
|---|
| 21 |
* decide whether to accept positional args in non-constrained methods |
|---|
| 22 |
|
|---|
| 23 |
DEFERRED until after 2.0 |
|---|
| 24 |
<glyph> warner: that would be awesome but let's do it _later_ |
|---|
| 25 |
|
|---|
| 26 |
This is really a backwards-source-compatibility issue. In newpb, the |
|---|
| 27 |
preferred way of invoking callRemote() is with kwargs exclusively: glyph's |
|---|
| 28 |
felt positional arguments are more fragile. If the client has a |
|---|
| 29 |
RemoteInterface, then they can convert any positional arguments into keyword |
|---|
| 30 |
arguments before sending the request. |
|---|
| 31 |
|
|---|
| 32 |
The question is what to do when the client is not using a RemoteInterface. |
|---|
| 33 |
Until recently, callRemote("bar") would try to find a matching RI. I changed |
|---|
| 34 |
that to have callRemote("bar") never use an RI, and instead you would use |
|---|
| 35 |
callRemote(RIFoo['bar']) to indicate that you want argument-checking. |
|---|
| 36 |
|
|---|
| 37 |
That makes positional arguments problematic in more situations than they were |
|---|
| 38 |
before. The decision to be made is if the OPEN(call) sequence should provide |
|---|
| 39 |
a way to convey positional args to the server (probably with numeric "names" |
|---|
| 40 |
in the (argname, argvalue) tuples). If we do this, the server (which always |
|---|
| 41 |
has the RemoteInterface) can do the positional-to-keyword mapping. But |
|---|
| 42 |
putting this in the protocol will oblige other implementations to handle them |
|---|
| 43 |
too. |
|---|
| 44 |
|
|---|
| 45 |
* change the method-call syntax to include an interfacename |
|---|
| 46 |
DONE |
|---|
| 47 |
|
|---|
| 48 |
Scope the method name to the interface. This implies (I think) one of two |
|---|
| 49 |
things: |
|---|
| 50 |
|
|---|
| 51 |
callRemote() must take a RemoteInterface argument |
|---|
| 52 |
|
|---|
| 53 |
each RemoteReference handles just a single Interface |
|---|
| 54 |
|
|---|
| 55 |
Probably the latter, maybe have the RR keep both default RI and a list of |
|---|
| 56 |
all implemented ones, then adapting the RR to a new RI can be a simple copy |
|---|
| 57 |
(and change of the default one) if the Referenceable knows about the RI. |
|---|
| 58 |
Otherwise something on the local side will need to adapt one RI to another. |
|---|
| 59 |
Need to handle reference-counting/DECREF properly for these shared RRs. |
|---|
| 60 |
|
|---|
| 61 |
From glyph: |
|---|
| 62 |
|
|---|
| 63 |
callRemote(methname, **args) # searches RIs |
|---|
| 64 |
callRemoteInterface(remoteinterface, methname, **args) # single RI |
|---|
| 65 |
|
|---|
| 66 |
getRemoteURL(url, *interfaces) |
|---|
| 67 |
|
|---|
| 68 |
URL-RRefs should turn into the original Referenceable (in args/results) |
|---|
| 69 |
(map through the factory's table upon receipt) |
|---|
| 70 |
|
|---|
| 71 |
URL-RRefs will not survive round trips. leave reference exchange for later. |
|---|
| 72 |
(like def remote_foo(): return GlobalReference(self) ) |
|---|
| 73 |
|
|---|
| 74 |
move method-invocation code into pb.Referenceable (or IReferenceable |
|---|
| 75 |
adapter). Continue using remote_ prefix for now, but make it a property of |
|---|
| 76 |
that code so it can change easily. |
|---|
| 77 |
<warner> ok, for today I'm just going to stick with remote_foo() as a |
|---|
| 78 |
low-budget decorator, so the current restrictions are 1: subclass |
|---|
| 79 |
pb.Referenceable, 2: implements() a RemoteInterface with method named "foo", |
|---|
| 80 |
3: implement a remote_foo method |
|---|
| 81 |
<warner> and #1 will probably go away within a week or two, to be replaced by |
|---|
| 82 |
#1a: subclass pb.Referenceable OR #1b: register an IReferenceable adapter |
|---|
| 83 |
|
|---|
| 84 |
try serializing with ISliceable first, then try IReferenceable. The |
|---|
| 85 |
IReferenceable adapter must implements() some RemoteInterfaces and gets |
|---|
| 86 |
serialized with a MyReferenceSlicer. |
|---|
| 87 |
|
|---|
| 88 |
http://svn.twistedmatrix.com/cvs/trunk/pynfo/admin.py?view=markup&rev=44&root=pynfo |
|---|
| 89 |
|
|---|
| 90 |
** use the methods of the RemoteInterface as the "method name" |
|---|
| 91 |
DONE (provisional), using RIFoo['add'] |
|---|
| 92 |
|
|---|
| 93 |
rr.callRemote(RIFoo.add, **args) |
|---|
| 94 |
|
|---|
| 95 |
Nice and concise. However, #twisted doesn't like it, adding/using arbitrary |
|---|
| 96 |
attributes of Interfaces is not clean (think about IFoo.implements colliding |
|---|
| 97 |
with RIFoo.something). |
|---|
| 98 |
|
|---|
| 99 |
rr.callRemote(RIFoo['add'], **args) |
|---|
| 100 |
RIFoo(rr).callRemote('add', **args) |
|---|
| 101 |
adaptation, or narrowing? |
|---|
| 102 |
|
|---|
| 103 |
<warner> glyph: I'm adding callRemote(RIFoo.bar, **args) to newpb right now |
|---|
| 104 |
<radix> wow. |
|---|
| 105 |
<warner> seemed like a simpler interface than callRemoteInterface("RIFoo", |
|---|
| 106 |
"bar", **args) |
|---|
| 107 |
<radix> warner: Does this mean that IPerspective can be parameterized now? |
|---|
| 108 |
<glyph> warner: bad idea |
|---|
| 109 |
<exarkun> warner: Zope hates you! |
|---|
| 110 |
<glyph> warner: zope interfaces don't support that syntax |
|---|
| 111 |
<slyphon> zi does support multi-adapter syntax |
|---|
| 112 |
<slyphon> but i don't really know what that is |
|---|
| 113 |
<exarkun> warner: callRemote(RIFoo.getDescriptionFor("bar"), *a, **k) |
|---|
| 114 |
<warner> glyph: yeah, I fake it. In RemoteInterfaceClass, I remove those |
|---|
| 115 |
attributes, call InterfaceClass, and then put them all back in |
|---|
| 116 |
<glyph> warner: don't add 'em as attributes |
|---|
| 117 |
<glyph> warner: just fix the result of __getitem__ to add a slot actually |
|---|
| 118 |
refer back to the interface |
|---|
| 119 |
<glyph> radix: the problem is that IFoo['bar'] doesn't point back to IFoo |
|---|
| 120 |
<glyph> warner: even better, make them callable :-) |
|---|
| 121 |
<exarkun> glyph: IFoo['bar'].interface == 'IFoo' |
|---|
| 122 |
<glyph> RIFoo['bar']('hello') |
|---|
| 123 |
<warner> glyph: I was thinking of doing that in a later version of |
|---|
| 124 |
RemoteInterface |
|---|
| 125 |
<glyph> exarkun: >>> type(IFoo['bar'].interface) |
|---|
| 126 |
<glyph> <type 'str'> |
|---|
| 127 |
<exarkun> right |
|---|
| 128 |
<exarkun> 'IFoo' |
|---|
| 129 |
<exarkun> Just look through all the defined interfaces for ones with matching |
|---|
| 130 |
names |
|---|
| 131 |
<glyph> exarkun: ... e.g. *NOT* __main__.IFoo |
|---|
| 132 |
<glyph> exarkun: AAAA you die |
|---|
| 133 |
<radix> hee hee |
|---|
| 134 |
* warner struggles to keep up with his thoughts and those of people around him |
|---|
| 135 |
* glyph realizes he has been given the power to whine |
|---|
| 136 |
<warner> glyph: ok, so with RemoteInterface.__getitem__, you could still do |
|---|
| 137 |
rr.callRemote(RIFoo.bar, **kw), right? |
|---|
| 138 |
<warner> was your objection to the interface or to the implementation? |
|---|
| 139 |
<itamar> I really don't think you should add attributes to the interface |
|---|
| 140 |
<warner> ok |
|---|
| 141 |
<warner> I need to stash a table of method schemas somewhere |
|---|
| 142 |
<itamar> just make __getitem__ return better type of object |
|---|
| 143 |
<itamar> and ideally if this is generic we can get it into upstream |
|---|
| 144 |
<exarkun> Is there a reason Method.interface isn't a fully qualified name? |
|---|
| 145 |
<itamar> not necessarily |
|---|
| 146 |
<itamar> I have commit access to zope.interface |
|---|
| 147 |
<itamar> if you have any features you want added, post to |
|---|
| 148 |
interface-dev@zope.org mailing list |
|---|
| 149 |
<itamar> and if Jim Fulton is ok with them I can add them for you |
|---|
| 150 |
<warner> hmm |
|---|
| 151 |
<warner> does using RIFoo.bar to designate a remote method seem reasonable? |
|---|
| 152 |
<warner> I could always adapt it to something inside callRemote |
|---|
| 153 |
<warner> something PB-specific, that is |
|---|
| 154 |
<warner> but that adapter would have to be able to pull a few attributes off |
|---|
| 155 |
the method (name, schema, reference to the enclosing RemoteInterface) |
|---|
| 156 |
<warner> and we're really talking about __getattr__ here, not __getitem__, |
|---|
| 157 |
right? |
|---|
| 158 |
<exarkun> for x.y yes |
|---|
| 159 |
<itamar> no, I don't think that's a good idea |
|---|
| 160 |
<itamar> interfaces have all kinds od methods on them already, for |
|---|
| 161 |
introspection purposes |
|---|
| 162 |
<itamar> namespace clashes are the suck |
|---|
| 163 |
<itamar> unless RIFoo isn't really an Interface |
|---|
| 164 |
<itamar> hm |
|---|
| 165 |
<itamar> how about if it were a wrapper around a regular Interface? |
|---|
| 166 |
<warner> yeah, RemoteInterfaces are kind of a special case |
|---|
| 167 |
<itamar> RIFoo(IFoo, publishedMethods=['doThis', 'doThat']) |
|---|
| 168 |
<itamar> s/RIFoo/RIFoo = RemoteInterface(/ |
|---|
| 169 |
<exarkun> I'm confused. Why should you have to specify which methods are |
|---|
| 170 |
published? |
|---|
| 171 |
<itamar> SECURITY! |
|---|
| 172 |
<itamar> not actually necessary though, no |
|---|
| 173 |
<itamar> and may be overkill |
|---|
| 174 |
<warner> the only reason I have it derive from Interface is so that we can do |
|---|
| 175 |
neat adapter tricks in the future |
|---|
| 176 |
<itamar> that's not contradictory |
|---|
| 177 |
<itamar> RIFoo(x) would still be able to do magic |
|---|
| 178 |
<itamar> you wouldn't be able to check if an object provides RIFoo, though |
|---|
| 179 |
<itamar> which kinda sucks |
|---|
| 180 |
<itamar> but in any case I am against RIFoo.bar |
|---|
| 181 |
<warner> pity, it makes the callRemote syntax very clean |
|---|
| 182 |
<radix> hm |
|---|
| 183 |
<radix> So how come it's a RemoteInterface and not an Interface, anyway? |
|---|
| 184 |
<radix> I mean, how come that needs to be done explicitly. Can't you just |
|---|
| 185 |
write a serializer for Interface itself? |
|---|
| 186 |
|
|---|
| 187 |
* warner goes to figure out where the RemoteInterface discussion went after he |
|---|
| 188 |
got distracted |
|---|
| 189 |
<warner> maybe I should make RemoteInterface a totally separate class and just |
|---|
| 190 |
implement a couple of Interface-like methods |
|---|
| 191 |
<warner> cause rr.callRemote(IFoo.bar, a=1) just feels so clean |
|---|
| 192 |
<Jerub> warner: why not IFoo(rr).bar(a=1) ? |
|---|
| 193 |
<warner> hmm, also a possibility |
|---|
| 194 |
<radix> well |
|---|
| 195 |
<radix> IFoo(rr).callRemote('bar') |
|---|
| 196 |
<radix> or RIFoo, or whatever |
|---|
| 197 |
<Jerub> hold on, what does rr inherit from? |
|---|
| 198 |
<warner> RemoteReference |
|---|
| 199 |
<radix> it's a RemoteReference |
|---|
| 200 |
<Jerub> then why not IFoo(rr) / |
|---|
| 201 |
<warner> I'm keeping a strong distinction between local interfaces and remote |
|---|
| 202 |
ones |
|---|
| 203 |
<Jerub> ah, oka.y |
|---|
| 204 |
<radix> warner: right, you can still do RIFoo |
|---|
| 205 |
<warner> ILocal(a).meth(args) is an immediate function call |
|---|
| 206 |
<Jerub> in that case, I prefer rr.callRemote(IFoo.bar, a=1) |
|---|
| 207 |
<radix> .meth( is definitely bad, we need callRemote |
|---|
| 208 |
<warner> rr.callRemote("meth", args) returns a deferred |
|---|
| 209 |
<Jerub> radix: I don't like from foo import IFoo, RIFoo |
|---|
| 210 |
<warner> you probably wouldn't have both an IFoo and an RIFoo |
|---|
| 211 |
<radix> warner: well, look at it this way: IFoo(rr).callRemote('foo') still |
|---|
| 212 |
makes it obvious that IFoo isn't local |
|---|
| 213 |
<radix> warner: you could implement RemoteReferen.__conform__ to implement it |
|---|
| 214 |
<warner> radix: I'm thinking of providing some kind of other class that would |
|---|
| 215 |
allow .meth() to work (without the callRemote), but it wouldn't be the default |
|---|
| 216 |
<radix> plus, IFoo(rr) is how you use interfaces normally, and callRemote is |
|---|
| 217 |
how you make remote calls normally, so it seems that's the best way to do |
|---|
| 218 |
interfaces + PB |
|---|
| 219 |
<warner> hmm |
|---|
| 220 |
<warner> in that case the object returned by IFoo(rr) is just rr with a tag |
|---|
| 221 |
that sets the "default interface name" |
|---|
| 222 |
<radix> right |
|---|
| 223 |
<warner> and callRemote(methname) looks in that default interface before |
|---|
| 224 |
looking anywhere else |
|---|
| 225 |
<warner> for some reason I want to get rid of the stringyness of the method |
|---|
| 226 |
name |
|---|
| 227 |
<warner> and the original syntax (callRemoteInterface('RIFoo', 'methname', |
|---|
| 228 |
args)) felt too verbose |
|---|
| 229 |
<radix> warner: well, isn't that what your optional .meth thing is for? |
|---|
| 230 |
<radix> yes, I don't like that either |
|---|
| 231 |
<warner> using callRemote(RIFoo.bar, args) means I can just switch on the |
|---|
| 232 |
_name= argument being either a string or a (whatever) that's contained in a |
|---|
| 233 |
RemoteInterface |
|---|
| 234 |
<warner> a lot of it comes down to how adapters would be most useful when |
|---|
| 235 |
dealing with remote objects |
|---|
| 236 |
<warner> and to what extent remote interfaces should be interchangeable with |
|---|
| 237 |
local ones |
|---|
| 238 |
<radix> good point. I have never had a use case where I wanted to adapt a |
|---|
| 239 |
remote object, I don't think |
|---|
| 240 |
<radix> however, I have had use cases to send interfaces across the wire |
|---|
| 241 |
<radix> e.g. having a parameterized portal.login() interface |
|---|
| 242 |
<warner> that'll be different, just callRemote('foo', RIFoo) |
|---|
| 243 |
<radix> yeah. |
|---|
| 244 |
<warner> the current issue is whether to pass them by reference or by value |
|---|
| 245 |
<radix> eugh |
|---|
| 246 |
<radix> Can you explain it without using those words? :) |
|---|
| 247 |
<warner> hmm |
|---|
| 248 |
<radix> Do you mean, Referenceable style vs Copyable style? |
|---|
| 249 |
<warner> at the moment, when you send a Referenceable across the wire, the |
|---|
| 250 |
id-number is accompanied with a list of strings that designate which |
|---|
| 251 |
RemoteInterfaces the original claims to provide |
|---|
| 252 |
<warner> the receiving end looks up each string in a local table, and |
|---|
| 253 |
populates the RemoteReference with a list of RemoteInterface classes |
|---|
| 254 |
<warner> the table is populated by metaclass magic that runs when a 'class |
|---|
| 255 |
RIFoo(RemoteInterface)' definition is complete |
|---|
| 256 |
<radix> ok |
|---|
| 257 |
<radix> so a RemoteInterface is simply serialized as its qual(), right? |
|---|
| 258 |
<warner> so as long as both sides include the same RIFoo definition, they'll |
|---|
| 259 |
wind up with compatible remote interfaces, defining the same method names, |
|---|
| 260 |
same method schemas, etc |
|---|
| 261 |
<warner> effectively |
|---|
| 262 |
<warner> you can't just send a RemoteInterface across the wire right now, but |
|---|
| 263 |
it would be easy to add |
|---|
| 264 |
<warner> the places where they are used (sending a Referenceable across the |
|---|
| 265 |
wire) all special case them |
|---|
| 266 |
<radix> ok, and you're considering actually writing a serializer for them that |
|---|
| 267 |
sends all the information to totally reconstruct it on the other side without |
|---|
| 268 |
having the definiton |
|---|
| 269 |
<warner> yes |
|---|
| 270 |
<warner> or having some kind of debug method which give you that |
|---|
| 271 |
<radix> I'd say, do it the way you're doing it now until someone comes up with |
|---|
| 272 |
a use case for actually sending it... |
|---|
| 273 |
<warner> right |
|---|
| 274 |
<warner> the only case I can come up with is some sort of generic object |
|---|
| 275 |
browser debug tool |
|---|
| 276 |
<warner> everything else turns into a form of version negotiation which is |
|---|
| 277 |
better handled elsewhere |
|---|
| 278 |
<warner> hmm |
|---|
| 279 |
<warner> so RIFoo(rr).callRemote('bar', **kw) |
|---|
| 280 |
<warner> I guess that's not too ugly |
|---|
| 281 |
<radix> That's my vote. :) |
|---|
| 282 |
<warner> one thing it lacks is the ability to cleanly state that if 'bar' |
|---|
| 283 |
doesn't exist in RIFoo then it should signal an error |
|---|
| 284 |
<warner> whereas callRemote(RIFoo.bar, **kw) would give you an AttributeError |
|---|
| 285 |
before callRemote ever got called |
|---|
| 286 |
<warner> i.e. "make it impossible to express the incorrect usage" |
|---|
| 287 |
<radix> mmmh |
|---|
| 288 |
<radix> warner: but you _can_ check it immediately when it's called |
|---|
| 289 |
<warner> in the direction I was heading, callRemote(str) would just send the |
|---|
| 290 |
method request and let the far end deal with it, no schema-checking involved |
|---|
| 291 |
<radix> warner: which, 99% of the time, is effectively the same time as |
|---|
| 292 |
IFoo.bar would happen |
|---|
| 293 |
<warner> whereas callRemote(RIFoo.bar) would indicate that you want schema |
|---|
| 294 |
checking |
|---|
| 295 |
<warner> yeah, true |
|---|
| 296 |
<radix> hm. |
|---|
| 297 |
<warner> (that last feature is what allowed callRemote and callRemoteInterface |
|---|
| 298 |
to be merged) |
|---|
| 299 |
<warner> or, I could say that the normal RemoteReference is "untyped" and does |
|---|
| 300 |
not do schema checking |
|---|
| 301 |
<warner> but adapting one to a RemoteInterface results in a |
|---|
| 302 |
TypedRemoteReference which does do schema checking |
|---|
| 303 |
<warner> and which refuses to be invoked with method names that are not in the |
|---|
| 304 |
schema |
|---|
| 305 |
<radix> warner: we-ell |
|---|
| 306 |
<radix> warner: doing method existence checking is cool |
|---|
| 307 |
<radix> warner: but I think tying any further "schema checking" to adaptation |
|---|
| 308 |
is a bad idea |
|---|
| 309 |
<warner> yeah, that's my hunch too |
|---|
| 310 |
<warner> which is why I'd rather not use adapters to express the scope of the |
|---|
| 311 |
method name (which RemoteInterface it is supposed to be a part of) |
|---|
| 312 |
<radix> warner: well, I don't think tying it to callRemote(RIFoo.methName) |
|---|
| 313 |
would be a good idea just the same |
|---|
| 314 |
<warner> hm |
|---|
| 315 |
<warner> so that leaves rr.callRemote(RIFoo['add']) and |
|---|
| 316 |
rr.callRemoteInterface(RIFoo, 'add') |
|---|
| 317 |
<radix> OTOH, I'm inclined to think schema checking should happen by default |
|---|
| 318 |
<radix> It's just a the matter of where it's parameterized |
|---|
| 319 |
<warner> yeah, it's just that the "default" case (rr.callRemote('name')) needs |
|---|
| 320 |
to work when there aren't any RemoteInterfaces declared |
|---|
| 321 |
<radix> warner: oh |
|---|
| 322 |
<warner> but if we want to encourage people to use the schemas, then we need |
|---|
| 323 |
to make that case simple and concise |
|---|
| 324 |
* radix goes over the issue in his head again |
|---|
| 325 |
<radix> Yes, I think I still have the same position. |
|---|
| 326 |
<warner> which one? :) |
|---|
| 327 |
<radix> IFoo(rr).callRemote("foo"); which would do schema checking because |
|---|
| 328 |
schema checking is on by default when it's possible |
|---|
| 329 |
<warner> using an adaptation-like construct to declare a scope of the method |
|---|
| 330 |
name that comes later |
|---|
| 331 |
<radix> well, it _is_ adaptation, I think. |
|---|
| 332 |
<radix> Adaptation always has plugged in behavior, we're just adding a bit |
|---|
| 333 |
more :) |
|---|
| 334 |
<warner> heh |
|---|
| 335 |
<warner> it is a narrowing of capability |
|---|
| 336 |
<radix> hmm, how do you mean? |
|---|
| 337 |
<warner> rr.callRemote("foo") will do the same thing |
|---|
| 338 |
<warner> but rr.callRemote("foo") can be used without the remote interfaces |
|---|
| 339 |
<radix> I think I lost you. |
|---|
| 340 |
<warner> if rr has any RIs defined, it will try to use them (and therefore |
|---|
| 341 |
complain if "foo" does not exist in any of them, or if the schema is violated) |
|---|
| 342 |
<radix> Oh. That's strange. |
|---|
| 343 |
<radix> So it's really quite different from how interfaces regularly work... |
|---|
| 344 |
<warner> yeah |
|---|
| 345 |
<warner> except that if you were feeling clever you could use them the normal |
|---|
| 346 |
way |
|---|
| 347 |
<radix> Well, my inclination is to make them work as similarly as possible. |
|---|
| 348 |
<warner> "I have a remote reference to something that implements RIFoo, but I |
|---|
| 349 |
want to use it in some other way" |
|---|
| 350 |
<radix> s/possible/practical/ |
|---|
| 351 |
<warner> then IBar(rr) or RIBar(rr) would wrap rr in something that knows how |
|---|
| 352 |
to translate Bar methods into RIFoo remote methods |
|---|
| 353 |
<radix> Maybe it's not practical to make them very similar. |
|---|
| 354 |
<radix> I see. |
|---|
| 355 |
|
|---|
| 356 |
rr.callRemote(RIFoo.add, **kw) |
|---|
| 357 |
rr.callRemote(RIFoo['add'], **kw) |
|---|
| 358 |
RIFoo(rr).callRemote('add', **kw) |
|---|
| 359 |
|
|---|
| 360 |
I like the second one. Normal Interfaces behave like a dict, so IFoo['add'] |
|---|
| 361 |
gets you the method-describing object (z.i.i.Method). My RemoteInterfaces |
|---|
| 362 |
don't do that right now (because I remove the attributes before handing the |
|---|
| 363 |
RI to z.i), but I could probably fix that. I could either add attributes to |
|---|
| 364 |
the Method or hook __getitem__ to return something other than a Method |
|---|
| 365 |
(maybe a RemoteMethodSchema). |
|---|
| 366 |
|
|---|
| 367 |
Those Method objects have a .getSignatureInfo() which provides almost |
|---|
| 368 |
everything I need to construct the RemoteMethodSchema. Perhaps I should |
|---|
| 369 |
post-process Methods rather than pre-process the RemoteInterface. I can't |
|---|
| 370 |
tell how to use the return value trick, and it looks like the function may |
|---|
| 371 |
be discarded entirely once the Method is created, so this approach may not |
|---|
| 372 |
work. |
|---|
| 373 |
|
|---|
| 374 |
On the server side (Referenceable), subclassing Interface is nice because it |
|---|
| 375 |
provides adapters and implements() queries. |
|---|
| 376 |
|
|---|
| 377 |
On the client side (RemoteReference), subclassing Interface is a hassle: I |
|---|
| 378 |
don't think adapters are as useful, but getting at a method (as an attribute |
|---|
| 379 |
of the RI) is important. We have to bypass most of Interface to parse the |
|---|
| 380 |
method definitions differently. |
|---|
| 381 |
|
|---|
| 382 |
* create UnslicerRegistry, registerUnslicer |
|---|
| 383 |
DONE (PROVISIONAL), flat registry (therefore problematic for len(opentype)>1) |
|---|
| 384 |
|
|---|
| 385 |
consider adopting the existing collection API (getChild, putChild) for this, |
|---|
| 386 |
or maybe allow registerUnslicer() to take a callable which behaves kind of |
|---|
| 387 |
like a twisted.web isLeaf=1 resource (stop walking the tree, give all index |
|---|
| 388 |
tokens to the isLeaf=1 node) |
|---|
| 389 |
|
|---|
| 390 |
also some APIs to get a list of everything in the registry |
|---|
| 391 |
|
|---|
| 392 |
* use metaclass to auto-register RemoteCopy classes |
|---|
| 393 |
DONE |
|---|
| 394 |
|
|---|
| 395 |
** use metaclass to auto-register Unslicer classes |
|---|
| 396 |
DONE |
|---|
| 397 |
|
|---|
| 398 |
** and maybe Slicer classes too |
|---|
| 399 |
DONE with name 'slices', perhaps change to 'slicerForClasses'? |
|---|
| 400 |
|
|---|
| 401 |
class FailureSlicer(slicer.BaseSlicer): |
|---|
| 402 |
classname = "twisted.python.failure.Failure" |
|---|
| 403 |
slicerForClasses = (failure.Failure,) # triggers auto-register |
|---|
| 404 |
|
|---|
| 405 |
** various registry approaches |
|---|
| 406 |
DONE |
|---|
| 407 |
|
|---|
| 408 |
There are currently three kinds of registries used in banana/newpb: |
|---|
| 409 |
|
|---|
| 410 |
RemoteInterface <-> interface name |
|---|
| 411 |
class/type -> Slicer (-> opentype) -> Unslicer (-> class/type) |
|---|
| 412 |
Copyable subclass -> copyable-opentype -> RemoteCopy subclass |
|---|
| 413 |
|
|---|
| 414 |
There are two basic approaches to representing the mappings that these |
|---|
| 415 |
registries implement. The first is implicit, where the local objects are |
|---|
| 416 |
subclassed from Sliceable or Copyable or RemoteInterface and have attributes |
|---|
| 417 |
to define the wire-side strings that represent them. On the receiving side, |
|---|
| 418 |
we make extensive use of metaclasses to perform automatic registration |
|---|
| 419 |
(taking names from class attributes and mapping them to the factory or |
|---|
| 420 |
RemoteInterface used to create the remote version). |
|---|
| 421 |
|
|---|
| 422 |
The second approach is explicit, where pb.registerRemoteInterface, |
|---|
| 423 |
pb.registerRemoteCopy, and pb.registerUnslicer are used to establish the |
|---|
| 424 |
receiving-side mapping. There isn't a clean way to do it explicitly on the |
|---|
| 425 |
sending side, since we already have instances whose classes can give us |
|---|
| 426 |
whatever information we want. |
|---|
| 427 |
|
|---|
| 428 |
The advantage of implicit is simplicity: no more questions about why my |
|---|
| 429 |
pb.RemoteCopy is giving "not unserializable" errors. The mere act of |
|---|
| 430 |
importing a module is enough to let PB create instances of its classes. |
|---|
| 431 |
|
|---|
| 432 |
The advantage of doing it explicitly is to remind the user about the |
|---|
| 433 |
existence of those maps, because the factory classes in the receiving map is |
|---|
| 434 |
precisely equal to the user's exposure (from a security point of view). See |
|---|
| 435 |
the E paper on secure-serialization for some useful concepts. |
|---|
| 436 |
|
|---|
| 437 |
A disadvantage of implicit is that you can't quite be sure what, exactly, |
|---|
| 438 |
you're exposed to: the registrations take place all over the place. |
|---|
| 439 |
|
|---|
| 440 |
To make explicit not so painful, we can use quotient's .wsv files |
|---|
| 441 |
(whitespace-separated values) which map from class to string and back again. |
|---|
| 442 |
The file could list fully-qualified classname, wire-side string, and |
|---|
| 443 |
receiving factory class on each line. The Broker (or rather the RootSlicer |
|---|
| 444 |
and RootUnslicer) would be given a set of .wsv files to define their |
|---|
| 445 |
mapping. It would get all the registrations at once (instead of having them |
|---|
| 446 |
scattered about). They could also demand-load the receive-side factory |
|---|
| 447 |
classes. |
|---|
| 448 |
|
|---|
| 449 |
For now, go implicit. Put off the decision until we have some more |
|---|
| 450 |
experience with using newpb. |
|---|
| 451 |
|
|---|
| 452 |
* move from VocabSlicer sequence to ADDVOCAB/DELVOCAB tokens |
|---|
| 453 |
|
|---|
| 454 |
Requires a .wantVocabString flag in the parser, which is kind of icky but |
|---|
| 455 |
fixes the annoying asymmetry between set (vocab sequence) and get (VOCAB |
|---|
| 456 |
token). Might want a CLEARVOCAB token too. |
|---|
| 457 |
|
|---|
| 458 |
On second thought, this won't work. There isn't room for both a vocab number |
|---|
| 459 |
and a variable-length string in a single token. It must be an open sequence. |
|---|
| 460 |
However, it could be an add/del/set-vocab sequence, allowing the vocab to be |
|---|
| 461 |
modified incrementally. |
|---|
| 462 |
|
|---|
| 463 |
** VOCABize interface/method names |
|---|
| 464 |
|
|---|
| 465 |
One possibility is to make a list of all strings used by all known |
|---|
| 466 |
RemoteInterfaces and all their methods, then send it at broker connection |
|---|
| 467 |
time as the initial vocab map. A better one (maybe) is to somehow track what |
|---|
| 468 |
we send and add a word to the vocab once we've sent it more than three |
|---|
| 469 |
times. |
|---|
| 470 |
|
|---|
| 471 |
Maybe vocabize the pairs, as "ri/name1","ri/name2", etc, or maybe do them |
|---|
| 472 |
separately. Should do some handwaving math to figure out which is better. |
|---|
| 473 |
|
|---|
| 474 |
* nail down some useful schema syntaxes |
|---|
| 475 |
|
|---|
| 476 |
This has two parts: parsing something like a __schema__ class attribute (see |
|---|
| 477 |
the sketches in schema.xhtml) into a tree of FooConstraint objects, and |
|---|
| 478 |
deciding how to retrieve schemas at runtime from things like the object being |
|---|
| 479 |
serialized or the object being called from afar. To be most useful, the |
|---|
| 480 |
syntax needs to mesh nicely (read "is identical to") things like formless and |
|---|
| 481 |
(maybe?) atop or whatever has replaced the high-density highly-structured |
|---|
| 482 |
save-to-disk scheme that twisted.world used to do. |
|---|
| 483 |
|
|---|
| 484 |
Some lingering questions in this area: |
|---|
| 485 |
|
|---|
| 486 |
When an object has a remotely-invokable method, where does the appropriate |
|---|
| 487 |
MethodConstraint come from? Some possibilities: |
|---|
| 488 |
|
|---|
| 489 |
an attribute of the method itself: obj.method.__schema__ |
|---|
| 490 |
|
|---|
| 491 |
from inside a __schema__ attribute of the object's class |
|---|
| 492 |
|
|---|
| 493 |
from inside a __schema__ attribute of an Interface (which?) that the object |
|---|
| 494 |
implements |
|---|
| 495 |
|
|---|
| 496 |
Likewise, when a caller holding a RemoteReference invokes a method on it, it |
|---|
| 497 |
would be nice to enforce a schema on the arguments they are sending to the |
|---|
| 498 |
far end ("be conservative in what you send"). Where should this schema come |
|---|
| 499 |
from? It is likely that the sender only knows an Interface for their |
|---|
| 500 |
RemoteReference. |
|---|
| 501 |
|
|---|
| 502 |
When PB determines that an object wants to be copied by value instead of by |
|---|
| 503 |
reference (pb.Copyable subclass, Copyable(obj), schema says so), where |
|---|
| 504 |
should it find a schema to define what exactly gets copied over? A class |
|---|
| 505 |
attribute of the object's class would make sense: most objects would do |
|---|
| 506 |
this, some could override jellyFor to get more control, and others could |
|---|
| 507 |
override something else to push a new Slicer on the stack and do streaming |
|---|
| 508 |
serialization. Whatever the approach, it needs to be paralleled by the |
|---|
| 509 |
receiving side's unjellyableRegistry. |
|---|
| 510 |
|
|---|
| 511 |
* RemoteInterface instances should have an "RI-" prefix instead of "I-" |
|---|
| 512 |
|
|---|
| 513 |
DONE |
|---|
| 514 |
|
|---|
| 515 |
* merge my RemoteInterface syntax with zope.interface's |
|---|
| 516 |
|
|---|
| 517 |
I hacked up a syntax for how method definitions are parsed in |
|---|
| 518 |
RemoteInterface objects. That syntax isn't compatible with the one |
|---|
| 519 |
zope.interface uses for local methods, so I just delete them from the |
|---|
| 520 |
attribute dictionary to avoid causing z.i indigestion. It would be nice if |
|---|
| 521 |
they were compatible so I didn't have to do that. This basically translates |
|---|
| 522 |
into identifying the nifty extra flags (like priority classes, no-response) |
|---|
| 523 |
that we want on these methods and finding a z.i-compatible way to implement |
|---|
| 524 |
them. It also means thinking of SOAP/XML-RPC schemas and having a syntax |
|---|
| 525 |
that can represent everything at once. |
|---|
| 526 |
|
|---|
| 527 |
|
|---|
| 528 |
* use adapters to enable pass-by-reference or pass-by-value |
|---|
| 529 |
|
|---|
| 530 |
It should be possible to pass a reference with variable forms: |
|---|
| 531 |
|
|---|
| 532 |
rr.callRemote("foo", 1, Reference(obj)) |
|---|
| 533 |
rr.callRemote("bar", 2, Copy(obj)) |
|---|
| 534 |
|
|---|
| 535 |
This should probably adapt the object to IReferenceable or ICopyable, which |
|---|
| 536 |
are like ISliceable except they can pass the object by reference or by |
|---|
| 537 |
value. The slicing process should be: |
|---|
| 538 |
|
|---|
| 539 |
look up the type() in a table: this handles all basic types |
|---|
| 540 |
else adapt the object to ISliceable, use the result |
|---|
| 541 |
else raise an Unsliceable exception |
|---|
| 542 |
(and point the user to the docs on how to fix it) |
|---|
| 543 |
|
|---|
| 544 |
The adapter returned by IReferenceable or ICopyable should implement |
|---|
| 545 |
ISliceable, so no further adaptation will be done. |
|---|
| 546 |
|
|---|
| 547 |
* remove 'copy' prefix from remotecopy banana type names? |
|---|
| 548 |
|
|---|
| 549 |
<glyph> warner: did we ever finish our conversation on the usefulness of the |
|---|
| 550 |
(copy foo blah) namespace rather than just (foo blah)? |
|---|
| 551 |
<warner> glyph: no, I don't think we did |
|---|
| 552 |
<glyph> warner: do you still have (copy foo blah)? |
|---|
| 553 |
<warner> glyph: yup |
|---|
| 554 |
<warner> so far, it seems to make some things easier |
|---|
| 555 |
<warner> glyph: the sender can subclass pb.Copyable and not write any new |
|---|
| 556 |
code, while the receiver can write an Unslicer and do a registerRemoteCopy |
|---|
| 557 |
<warner> glyph: instead of the sender writing a whole slicer and the receiver |
|---|
| 558 |
registering at the top-level |
|---|
| 559 |
<glyph> warner: aah |
|---|
| 560 |
<warner> glyph: although the fact that it's easier that way may be an artifact |
|---|
| 561 |
of my sucky registration scheme |
|---|
| 562 |
<glyph> warner: so the advantage is in avoiding registration of each new |
|---|
| 563 |
unslicer token? |
|---|
| 564 |
<glyph> warner: yes. I'm thinking that a metaclass will handily remove the |
|---|
| 565 |
need for extra junk in the protocol ;) |
|---|
| 566 |
<warner> well, the real reason is my phobia about namespace purity, of course |
|---|
| 567 |
<glyph> warner: That's what the dots are for |
|---|
| 568 |
<warner> but ease of dispatch is also important |
|---|
| 569 |
<glyph> warner: I'm concerned about it because I consider my use of the same |
|---|
| 570 |
idiom in the first version of PB to be a serious wart |
|---|
| 571 |
* warner nods |
|---|
| 572 |
<warner> I will put together a list of my reasoning |
|---|
| 573 |
<glyph> warner: I think it's likely that PB implementors in other languages |
|---|
| 574 |
are going to want to introduce new standard "builtin" types; our "builtins" |
|---|
| 575 |
shouldn't be limited to python's provided data structures |
|---|
| 576 |
<moshez> glyph: wait |
|---|
| 577 |
<warner> ok |
|---|
| 578 |
<moshez> glyph: are you talking of banana types |
|---|
| 579 |
<moshez> glyph: or really PB |
|---|
| 580 |
<warner> in which case (copy blah blah) is a non-builtin type, while |
|---|
| 581 |
(type-foo) is a builtin type |
|---|
| 582 |
<glyph> warner: plus, our namespaces are already quite well separated, I can |
|---|
| 583 |
tell you I will never be declaring new types outside of quotient.* and |
|---|
| 584 |
twisted.* :) |
|---|
| 585 |
<warner> moshez: this is mostly banana (or what used to be jelly, really) |
|---|
| 586 |
<glyph> warner: my inclination is to standardize by convention |
|---|
| 587 |
<glyph> warner: *.* is a non-builtin type, [~.] is a builtin |
|---|
| 588 |
<moshez> glyph: ? |
|---|
| 589 |
<glyph> sorry [^.]* |
|---|
| 590 |
<glyph> my regular expressions and shell globs are totally confused but you |
|---|
| 591 |
know what I mean |
|---|
| 592 |
<glyph> moshez: yes |
|---|
| 593 |
<moshez> glyph: hrm |
|---|
| 594 |
<saph_w> glyph: you're making crazy anime faces |
|---|
| 595 |
<moshez> glyph: why do we need any non-Python builtin types |
|---|
| 596 |
<glyph> moshez: because I want to destroy SOAP, and doing that means working |
|---|
| 597 |
with people I don't like |
|---|
| 598 |
<glyph> moshez: outside of python |
|---|
| 599 |
<moshez> glyph: I meant, "what specific types" |
|---|
| 600 |
<moshez> I'd appreciate a blog on that |
|---|
| 601 |
|
|---|
| 602 |
* have Copyable/RemoteCopy default to __getstate__/__setstate__? |
|---|
| 603 |
|
|---|
| 604 |
At the moment, the default implementations of getStateToCopy() and |
|---|
| 605 |
setCopyableState() get and set __dict__ directly. Should the default instead |
|---|
| 606 |
be to call __getstate__() or __setstate__()? |
|---|
| 607 |
|
|---|
| 608 |
* make slicer/unslicers for pb.RemoteInterfaces |
|---|
| 609 |
|
|---|
| 610 |
exarkun's use case requires these Interfaces to be passable by reference |
|---|
| 611 |
(i.e. by name). It would also be interesting to let them be passed (and |
|---|
| 612 |
requested!) by value, so you can ask a remote peer exactly what their |
|---|
| 613 |
objects will respond to (the method names, the argument values, the return |
|---|
| 614 |
value). This also requires that constraints be serializable. |
|---|
| 615 |
|
|---|
| 616 |
do this, should be referenceable (round-trip should return the same object), |
|---|
| 617 |
should use the same registration lookup that RemoteReference(interfacelist) |
|---|
| 618 |
uses |
|---|
| 619 |
|
|---|
| 620 |
* investigate decref/Referenceable race |
|---|
| 621 |
|
|---|
| 622 |
Any object that includes some state when it is first sent across the wire |
|---|
| 623 |
needs more thought. The far end could drop the last reference (at time t=1) |
|---|
| 624 |
while a method is still pending that wants to send back the same object. If |
|---|
| 625 |
the method finishes at time t=2 but the decref isn't received until t=3, the |
|---|
| 626 |
object will be sent across the wire without the state, and the far end will |
|---|
| 627 |
receive it for the "first" time without that associated state. |
|---|
| 628 |
|
|---|
| 629 |
This kind of conserve-bandwidth optimization may be a bad idea. Or there |
|---|
| 630 |
might be a reasonable way to deal with it (maybe request the state if it |
|---|
| 631 |
wasn't sent and the recipient needs it, and delay delivery of the object |
|---|
| 632 |
until the state arrives). |
|---|
| 633 |
|
|---|
| 634 |
DONE, the RemoteReference is held until the decref has been acked. As long as |
|---|
| 635 |
the methods are executed in-order, this will prevent the race. TODO: |
|---|
| 636 |
third-party references (and other things that can cause out-of-order |
|---|
| 637 |
execution) could mess this up. |
|---|
| 638 |
|
|---|
| 639 |
* sketch out how to implement glyph's crazy non-compressed sexpr encoding |
|---|
| 640 |
|
|---|
| 641 |
* consider a smaller scope for OPEN-counter reference numbers |
|---|
| 642 |
|
|---|
| 643 |
For newpb, we moved to implicit reference numbers (counting OPEN tags |
|---|
| 644 |
instead of putting a number in the OPEN tag) because we didn't want to burn |
|---|
| 645 |
so much bandwidth: it isn't feasible to predict whether your object will |
|---|
| 646 |
need to be referenced in the future, so you always have to be prepared to |
|---|
| 647 |
reference it, so we always burn the memory to keep track of them (generally |
|---|
| 648 |
in a ScopedSlicer subclass). If we used explicit refids then we'd have to |
|---|
| 649 |
burn the bandwidth too. |
|---|
| 650 |
|
|---|
| 651 |
The sorta-problem is that these numbers will grow without bound as long as |
|---|
| 652 |
the connection remains open. After a few hours of sending 100-byte objects |
|---|
| 653 |
over a 100MB connection, you'll hit 1G-references and will have to start |
|---|
| 654 |
sending them as LONGINT tokens, which is annoying and slightly verbose (say |
|---|
| 655 |
3 or 4 bytes of number instead of 1 or 2). You never keep track of that many |
|---|
| 656 |
actual objects, because the references do not outlive their parent |
|---|
| 657 |
ScopedSlicer. |
|---|
| 658 |
|
|---|
| 659 |
The fact that the references themselves are scoped to the ScopedSlicer |
|---|
| 660 |
suggests that the reference numbers could be too. Each ScopedSlicer would |
|---|
| 661 |
track the number of OPEN tokens emitted (actually the number of |
|---|
| 662 |
slicerForObject calls made, except you'd want to use a different method to |
|---|
| 663 |
make sure that children who return a Slicer themselves don't corrupt the |
|---|
| 664 |
OPEN count). |
|---|
| 665 |
|
|---|
| 666 |
This requires careful synchronization between the ScopedSlicers on one end |
|---|
| 667 |
and the ScopedUnslicers on the other. I suspect it would be slightly |
|---|
| 668 |
fragile. |
|---|
| 669 |
|
|---|
| 670 |
One sorta-benefit would be that a somewhat human-readable sexpr-based |
|---|
| 671 |
encoding would be even more human readable if the reference numbers stayed |
|---|
| 672 |
small (you could visually correlate objects and references more easily). The |
|---|
| 673 |
ScopedSlicer's open-parenthesis could be represented with a curly brace or |
|---|
| 674 |
something, then the refNN number would refer to the NN'th left-paren from |
|---|
| 675 |
the last left-brace. It would also make it clear that the recipient will not |
|---|
| 676 |
care about objects outside that scope. |
|---|
| 677 |
|
|---|
| 678 |
* implement the FDSlicer |
|---|
| 679 |
|
|---|
| 680 |
Over a unix socket, you can pass fds. exarkun had a presentation at PyCon04 |
|---|
| 681 |
describing the use of this to implement live application upgrade. I think |
|---|
| 682 |
that we could make a simple FDSlicer to hide the complexity of the |
|---|
| 683 |
out-of-band part of the communication. |
|---|
| 684 |
|
|---|
| 685 |
class Server(unix.Server): |
|---|
| 686 |
def sendFileDescriptors(self, fileno, data="Filler"): |
|---|
| 687 |
""" |
|---|
| 688 |
@param fileno: An iterable of the file descriptors to pass. |
|---|
| 689 |
""" |
|---|
| 690 |
payload = struct.pack("%di" % len(fileno), *fileno) |
|---|
| 691 |
r = sendmsg(self.fileno(), data, 0, (socket.SOL_SOCKET, SCM_RIGHTS, payload)) |
|---|
| 692 |
return r |
|---|
| 693 |
|
|---|
| 694 |
class Client(unix.Client): |
|---|
| 695 |
def doRead(self): |
|---|
| 696 |
if not self.connected: |
|---|
| 697 |
return |
|---|
| 698 |
try: |
|---|
| 699 |
msg, flags, ancillary = recvmsg(self.fileno()) |
|---|
| 700 |
except: |
|---|
| 701 |
log.msg('recvmsg():') |
|---|
| 702 |
log.err() |
|---|
| 703 |
else: |
|---|
| 704 |
buf = ancillary[0][2] |
|---|
| 705 |
fds = [] |
|---|
| 706 |
while buf: |
|---|
| 707 |
fd, buf = buf[:4], buf[4:] |
|---|
| 708 |
fds.append(struct.unpack("i", fd)[0]) |
|---|
| 709 |
try: |
|---|
| 710 |
self.protocol.fileDescriptorsReceived(fds) |
|---|
| 711 |
except: |
|---|
| 712 |
log.msg('protocol.fileDescriptorsReceived') |
|---|
| 713 |
log.err() |
|---|
| 714 |
return unix.Client.doRead(self) |
|---|
| 715 |
|
|---|
| 716 |
* implement AsyncDeferred returns |
|---|
| 717 |
|
|---|
| 718 |
dash wanted to implement a TransferrableReference object with a scheme that |
|---|
| 719 |
would require creating a new connection (to a third-party Broker) during |
|---|
| 720 |
ReferenceUnslicer.receiveClose . This would cause the object deserialization |
|---|
| 721 |
to be asynchronous. |
|---|
| 722 |
|
|---|
| 723 |
At the moment, Unslicers can return a Deferred from their receiveClose |
|---|
| 724 |
method. This is used by immutable containers (like tuples) to indicate that |
|---|
| 725 |
their object cannot be created yet. Other containers know to watch for these |
|---|
| 726 |
Deferreds and add a callback which will update their own entries |
|---|
| 727 |
appropriately. The implicit requirement is that all these Deferreds fire |
|---|
| 728 |
before the top-level parent object (usually a CallUnslicer) finishes. This |
|---|
| 729 |
allows for circular references involving immutable containers to be resolved |
|---|
| 730 |
into the final object graph before the target method is invoked. |
|---|
| 731 |
|
|---|
| 732 |
To accomodate Deferreds which will fire at arbitrary points in the future, |
|---|
| 733 |
it would be useful to create a marker subclass named AsyncDeferred. If an |
|---|
| 734 |
unslicer returns such an object, the container parent starts by treating it |
|---|
| 735 |
like a regular Deferred, but it also knows that its object is not |
|---|
| 736 |
"complete", and therefore returns an AsyncDeferred of its own. When the |
|---|
| 737 |
child completes, the parent can complete, etc. The difference between the |
|---|
| 738 |
two types: Deferred means that the object will be complete before the |
|---|
| 739 |
top-level parent is finished, AsyncDeferred makes claims about when the |
|---|
| 740 |
object will be finished. |
|---|
| 741 |
|
|---|
| 742 |
CallUnslicer would know that if any of its arguments are Deferreds or |
|---|
| 743 |
AsyncDeferreds then it need to hold off on the broker.doCall until all those |
|---|
| 744 |
Deferreds have fired. Top-level objects are not required to differentiate |
|---|
| 745 |
between the two types, because they do not return an object to an enclosing |
|---|
| 746 |
parent (the CallUnslicer is a child of the RootUnslicer, but it always |
|---|
| 747 |
returns None). |
|---|
| 748 |
|
|---|
| 749 |
Other issues: we'll need a schema to let you say whether you'll accept these |
|---|
| 750 |
late-bound objects or not (because if you do accept them, you won't be able |
|---|
| 751 |
to impose the same sorts of type-checks as you would on immediate objects). |
|---|
| 752 |
Also this will impact the in-order-invocation promises of PB method calls, |
|---|
| 753 |
so we may need to implement the "it is ok to run this asynchronously" flag |
|---|
| 754 |
first, then require that TransferrableReference objects are only passed to |
|---|
| 755 |
methods with the flag set. |
|---|
| 756 |
|
|---|
| 757 |
Also, it may not be necessary to have a marker subclass of Deferred: perhaps |
|---|
| 758 |
_any_ Deferred which arrives from a child is an indication that the object |
|---|
| 759 |
will not be available until an unknown time in the future, and obligates the |
|---|
| 760 |
parent to return another Deferred upwards (even though their object could be |
|---|
| 761 |
created synchronously). Or, it might be better to implement this some other |
|---|
| 762 |
way, perhaps separating "here is my object" from "here is a Deferred that |
|---|
| 763 |
will fire when my object is complete", like a call to |
|---|
| 764 |
parent.addDependency(self.deferred) or something. |
|---|
| 765 |
|
|---|
| 766 |
DONE, needs testing |
|---|
| 767 |
|
|---|
| 768 |
* TransferrableReference |
|---|
| 769 |
|
|---|
| 770 |
class MyThing(pb.Referenceable): pass |
|---|
| 771 |
r1 = MyThing() |
|---|
| 772 |
r2 = Facet(r1) |
|---|
| 773 |
g1 = Global(r1) |
|---|
| 774 |
class MyGlobalThing(pb.GloballyReferenceable): pass |
|---|
| 775 |
g2 = MyGlobalThing() |
|---|
| 776 |
g3 = Facet(g2) |
|---|
| 777 |
|
|---|
| 778 |
broker.setLocation("pb://hostname.com:8044") |
|---|
| 779 |
|
|---|
| 780 |
rem.callRemote("m1", r1) # limited to just this connection |
|---|
| 781 |
rem.callRemote("m2", Global(r1)) # can be published |
|---|
| 782 |
g3 = Global(r1) |
|---|
| 783 |
rem.callRemote("m3", g1) # can also be published.. |
|---|
| 784 |
g1.revoke() # but since we remember it, it can be revoked too |
|---|
| 785 |
g1.restrict() # and, as a Facet, we can revoke some functionality but not all |
|---|
| 786 |
|
|---|
| 787 |
rem.callRemote("m1", g2) # can be published |
|---|
| 788 |
|
|---|
| 789 |
E tarball: jsrc/net/captp/tables/NearGiftTable |
|---|
| 790 |
|
|---|
| 791 |
issues: |
|---|
| 792 |
1: when A sends a reference on B to C, C's messages to the object |
|---|
| 793 |
referenced must arrive after any messages A sent before the reference forks |
|---|
| 794 |
|
|---|
| 795 |
in particular, if A does: |
|---|
| 796 |
B.callRemote("1", hugestring) |
|---|
| 797 |
B.callRemote("2_makeYourSelfSecure", args) |
|---|
| 798 |
C.callRemote("3_transfer", B) |
|---|
| 799 |
|
|---|
| 800 |
and C does B.callRemote("4_breakIntoYou") as soon as it gets the reference, |
|---|
| 801 |
then the A->B queue looks like (1, 2), and the A->C queue looks like (3). |
|---|
| 802 |
The transfer message can be fast, and the resulting 4 message could be |
|---|
| 803 |
delivered to B before the A->B queue manages to deliver 2. |
|---|
| 804 |
|
|---|
| 805 |
2: an object which get passed through multiple external brokers and |
|---|
| 806 |
eventually comes home must be recognized as a local object |
|---|
| 807 |
|
|---|
| 808 |
3: Copyables that contain RemoteReferences must be passable between hosts |
|---|
| 809 |
|
|---|
| 810 |
E cannot do all three of these at once |
|---|
| 811 |
http://www.erights.org/elib/distrib/captp/WormholeOp.html |
|---|
| 812 |
|
|---|
| 813 |
I think that it's ok to tell people who want this guarantee to explicitly |
|---|
| 814 |
serialize it like this: |
|---|
| 815 |
|
|---|
| 816 |
B.callRemote("1", hugestring) |
|---|
| 817 |
d = B.callRemote("2_makeYourSelfSecure", args) |
|---|
| 818 |
d.addCallback(lambda res: C.callRemote("3_transfer", B)) |
|---|
| 819 |
|
|---|
| 820 |
Note that E might not require that method calls even have a return value, so |
|---|
| 821 |
they might not have had a convenient way to express this enforced |
|---|
| 822 |
serialization. |
|---|
| 823 |
|
|---|
| 824 |
** more thoughts |
|---|
| 825 |
|
|---|
| 826 |
To enforce the partial-ordering, you could do the equivalent of: |
|---|
| 827 |
A: |
|---|
| 828 |
B.callRemote("1", hugestring) |
|---|
| 829 |
B.callRemote("2_makeYourSelfSecure", args) |
|---|
| 830 |
nonce = makeNonce() |
|---|
| 831 |
B.callRemote("makeYourSelfAvailableAs", nonce) |
|---|
| 832 |
C.callRemote("3_transfer", (nonce, B.name)) |
|---|
| 833 |
C: |
|---|
| 834 |
B.callRemote("4_breakIntoYou") |
|---|
| 835 |
|
|---|
| 836 |
C uses the nonce when it connects to B. It knows the name of the reference, |
|---|
| 837 |
so it can compare it against some other reference to the same thing, but it |
|---|
| 838 |
can't actually use that name alone to get access. |
|---|
| 839 |
|
|---|
| 840 |
When the connection request arrives at B, it sees B.name (which is also |
|---|
| 841 |
unguessable), so that gives it reason to believe that it should queue C's |
|---|
| 842 |
request (that it isn't just a DoS attack). It queues it until it sees A's |
|---|
| 843 |
request to makeYourSelfAvailableAs with the matching nonce. Once that |
|---|
| 844 |
happens, it can provide the reference back to C. |
|---|
| 845 |
|
|---|
| 846 |
This implies that C won't be able to send *any* messages to B until that |
|---|
| 847 |
handshake has completed. It might be desireable to avoid the extra round-trip |
|---|
| 848 |
this would require. |
|---|
| 849 |
|
|---|
| 850 |
** more thoughts |
|---|
| 851 |
|
|---|
| 852 |
url = PBServerFactory.registerReference(ref, name=None) |
|---|
| 853 |
creates human-readable URLs or random identifiers |
|---|
| 854 |
|
|---|
| 855 |
the factory keeps a bidirectional mapping of names and Referenceables |
|---|
| 856 |
|
|---|
| 857 |
when a Referenceable gets serialized, if the factory's table doesn't have a |
|---|
| 858 |
name for it, the factory creates a random one. This entry in the table is |
|---|
| 859 |
kept alive by two things: |
|---|
| 860 |
|
|---|
| 861 |
a live reference by one of the factory's Brokers |
|---|
| 862 |
an entry in a Broker's "gift table" |
|---|
| 863 |
|
|---|
| 864 |
When a RemoteReference gets serialized (and it doesn't point back to the |
|---|
| 865 |
receiving Broker, and thus get turned into a your-reference sequence), |
|---|
| 866 |
|
|---|
| 867 |
<warner> A->C: "I'm going to send somebody a reference to you, incref your |
|---|
| 868 |
gift table", C->A: roger that, here's a gift nonce |
|---|
| 869 |
<warner> A->B: "here's Carol's reference: URL plus nonce" |
|---|
| 870 |
<warner> B->C: "I want a liveref to your 'Carol' object, here's my ticket |
|---|
| 871 |
(nonce)", C->B: "ok, ticket redeemed, here's your liveref" |
|---|
| 872 |
|
|---|
| 873 |
once more, without nonces: |
|---|
| 874 |
A->C: "I'm going to send somebody a reference to you, incref your |
|---|
| 875 |
gift table", C->A: roger that |
|---|
| 876 |
A->B: "here's Carol's reference: URL" |
|---|
| 877 |
B->C: "I want a liveref to your 'Carol' object", C->B: "ok, here's your |
|---|
| 878 |
liveref" |
|---|
| 879 |
|
|---|
| 880 |
really: |
|---|
| 881 |
on A: c.vat.callRemote("giftYourReference", c).addCallback(step2) |
|---|
| 882 |
c is serialized as (your-reference, clid) |
|---|
| 883 |
on C: vat.remote_giftYourReference(which): self.table[which] += 1; return |
|---|
| 884 |
on A: step2: b.introduce(c) |
|---|
| 885 |
c is serialized as (their-reference, url) |
|---|
| 886 |
on B: deserialization sees their-reference |
|---|
| 887 |
newvat = makeConnection(URL) |
|---|
| 888 |
newvat.callRemote("redeemGift", URL).addCallback(step3) |
|---|
| 889 |
on C: vat.remote_redeemGift(URL): |
|---|
| 890 |
ref = self.urls[URL]; self.table[ref] -= 1; return ref |
|---|
| 891 |
ref is serialized as (my-reference, clid) |
|---|
| 892 |
on B: step3(c): b.remote_introduce(c) |
|---|
| 893 |
|
|---|
| 894 |
problem: if alice sends a thousand copies, that means these 5 messages are |
|---|
| 895 |
each send a thousand times. The makeConnection is cached, but the rest are |
|---|
| 896 |
not. We don't rememeber that we've already made this gift before, that the |
|---|
| 897 |
other end probably still has it. Hm, but we also don't know that they didn't |
|---|
| 898 |
lose it already. |
|---|
| 899 |
|
|---|
| 900 |
** ok, a plan: |
|---|
| 901 |
|
|---|
| 902 |
concern 1: objects must be kept alive as long as there is a RemoteReference |
|---|
| 903 |
to them. |
|---|
| 904 |
|
|---|
| 905 |
concern 2: we should be able to tell when an object is being sent for the |
|---|
| 906 |
first time, to add metadata (interface list, public URL) that would be |
|---|
| 907 |
expensive to add to every occurrence. |
|---|
| 908 |
|
|---|
| 909 |
each (my-reference) sent over the wire increases the broker's refcount on |
|---|
| 910 |
both ends. |
|---|
| 911 |
|
|---|
| 912 |
the receiving Broker retains a weakref to the RemoteReference, and retains a |
|---|
| 913 |
copy of the metadata necessary to create it in the clid table (basically the |
|---|
| 914 |
entire contents of the RemoteReference). When the weakref expires, it marks |
|---|
| 915 |
the clid entry as "pending-free", and sends a decref(clid,N) to the other |
|---|
| 916 |
Broker. The decref is actually sent with broker.callRemote("decref", clid, |
|---|
| 917 |
N), so it can be acked. |
|---|
| 918 |
|
|---|
| 919 |
the sending broker gets the decref and reduces its count by N. If another |
|---|
| 920 |
reference was sent recently, this count may not drop all the way to zero, |
|---|
| 921 |
indicating there is a reference "in flight" and the far end should be ready |
|---|
| 922 |
to deal with it (by making a new RemoteReference with the same properties as |
|---|
| 923 |
the old one). If N!=0, it returns False to indicate that this was not the |
|---|
| 924 |
last decref message for the clid. If N==0, it returns True, since it is the |
|---|
| 925 |
last decref, and removes the entry from its table. Once remote_decref |
|---|
| 926 |
returns True, the clid is retired. |
|---|
| 927 |
|
|---|
| 928 |
the receiving broker receives the ack from the decref. If the ack says |
|---|
| 929 |
last==True, the clid table entry is freed. If it says last==False, then |
|---|
| 930 |
there should have been another (my-reference) received before the ack, so |
|---|
| 931 |
the refcount should be non-zero. |
|---|
| 932 |
|
|---|
| 933 |
message sequence: |
|---|
| 934 |
|
|---|
| 935 |
A-> : (my-reference clid metadata) [A.myrefs[clid].refcount++ = 1] |
|---|
| 936 |
A-> : (my-reference clid) [A.myrefs[clid].refcount++ = 2] |
|---|
| 937 |
->B: receives my-ref, creates RR, B.yourrefs[clid].refcount++ = 1 |
|---|
| 938 |
->B: receives my-ref, B.yourrefs[clid].refcount++ = 2 |
|---|
| 939 |
: time passes, B sees the reference go away |
|---|
| 940 |
<-B: d=brokerA.callRemote("decref", clid, B.yourrefs[clid].refcount) |
|---|
| 941 |
B.yourrefs[clid].refcount = 0; d.addCallback(B.checkref, clid) |
|---|
| 942 |
A-> : (my-reference clid) [A.myrefs[clid].refcount++ = 3] |
|---|
| 943 |
A<- : receives decref, A.myrefs[clid].refcount -= 2, now =1, returns False |
|---|
| 944 |
->B: receives my-ref, re-creates RR, B.yourrefs[clid].refcount++ = 1 |
|---|
| 945 |
->B: receives ack(False), B.checkref asserts refcount != 0 |
|---|
| 946 |
: time passes, B sees the reference go away again |
|---|
| 947 |
<-B: d=brokerA.callRemote("decref", clid, B.yourrefs[clid].refcount) |
|---|
| 948 |
B.yourrefs[clid].refcount = 0; d.addCallback(B.checkref, clid) |
|---|
| 949 |
A<- : receives decref, A.myrefs[clid].refcount -= 1, now =0, returns True |
|---|
| 950 |
del A.myrefs[clid] |
|---|
| 951 |
->B: receives ack(True), B.checkref asserts refcount==0 |
|---|
| 952 |
del B.yourrefs[clid] |
|---|
| 953 |
|
|---|
| 954 |
B retains the RemoteReference data until it receives confirmation from A. |
|---|
| 955 |
Therefore whenever A sends a reference that doesn't already exist in the clid |
|---|
| 956 |
table, it is sending it to a B that doesn't know about that reference, so it |
|---|
| 957 |
needs to send the metadata. |
|---|
| 958 |
|
|---|
| 959 |
concern 3: in the three-party exchange, Carol must be kept alive until Bob |
|---|
| 960 |
has established a reference to her, even if Alice drops her carol-reference |
|---|
| 961 |
immediately after sending the introduction to Bob. |
|---|
| 962 |
|
|---|
| 963 |
(my-reference, clid, [interfaces, public URL]) |
|---|
| 964 |
(your-reference, clid) |
|---|
| 965 |
(their-reference, URL) |
|---|
| 966 |
|
|---|
| 967 |
Serializing a their-reference causes an entry to be placed in the Broker's |
|---|
| 968 |
.theirrefs[URL] table. Each time a their-reference is sent, the entry's |
|---|
| 969 |
refcount is incremented. |
|---|
| 970 |
|
|---|
| 971 |
Receiving a their-reference may initiate a PB connection to the target, |
|---|
| 972 |
followed by a getNamedReference request. When this completes (or if the |
|---|
| 973 |
reference was already available), the recipient sends a decgift message to |
|---|
| 974 |
the sender. This message includes a count, so multiple instances of the same |
|---|
| 975 |
gift can be acked as a group. |
|---|
| 976 |
|
|---|
| 977 |
The .theirrefs entry retains a reference to the sender's RemoteReference, so |
|---|
| 978 |
it cannot go away until the gift is acked. |
|---|
| 979 |
|
|---|
| 980 |
DONE, gifts are implemented, we punted on partial-ordering |
|---|
| 981 |
|
|---|
| 982 |
*** security, DoS |
|---|
| 983 |
|
|---|
| 984 |
Bob can force Alice to hold on to a reference to Carol, as long as both |
|---|
| 985 |
connections are open, by never acknowledging the gift. |
|---|
| 986 |
|
|---|
| 987 |
Alice can cause Bob to open up TCP connections to arbitrary hosts and ports, |
|---|
| 988 |
by sending third-party references to him, although the only protocol those |
|---|
| 989 |
connections will speak is PB. |
|---|
| 990 |
|
|---|
| 991 |
Using yURLs and StartTLS should be enough to secure and authenticate the |
|---|
| 992 |
connections. |
|---|
| 993 |
|
|---|
| 994 |
*** partial-ordering |
|---|
| 995 |
|
|---|
| 996 |
If we need it, the gift (their-reference message) can include a nonce, Alice |
|---|
| 997 |
sends a makeYourSelfAvailableAs message to Carol with the nonce, and Bob must |
|---|
| 998 |
do a new getReference with the nonce. |
|---|
| 999 |
|
|---|
| 1000 |
Kragen came up with a good use-case for partial-ordering: |
|---|
| 1001 |
A: |
|---|
| 1002 |
B.callRemote("updateDocument", bigDocument) |
|---|
| 1003 |
C.callRemote("pleaseReviewLatest", B) |
|---|
| 1004 |
C: |
|---|
| 1005 |
B.callRemote("getLatestDocument") |
|---|
| 1006 |
|
|---|
| 1007 |
|
|---|
| 1008 |
* PBService / Tub |
|---|
| 1009 |
|
|---|
| 1010 |
Really, PB wants to be a Service, since third-party references mean it will |
|---|
| 1011 |
need to make connections to arbitrary targets, and it may want to re-use |
|---|
| 1012 |
those connections. |
|---|
| 1013 |
|
|---|
| 1014 |
s = pb.PBService() |
|---|
| 1015 |
s.listenOn(strport) # provides URL base |
|---|
| 1016 |
swissURL = s.registerReference(ref) # creates unguessable name |
|---|
| 1017 |
publicURL = s.registerReference(ref, "name") # human-readable name |
|---|
| 1018 |
s.unregister(URL) # also revokes all clids |
|---|
| 1019 |
s.unregisterReference(ref) |
|---|
| 1020 |
d = s.getReference(URL) # Deferred which fires with the RemoteReference |
|---|
| 1021 |
d = s.shutdown() # close all servers and client connections |
|---|
| 1022 |
|
|---|
| 1023 |
DONE, this makes things quite clean |
|---|
| 1024 |
|
|---|
| 1025 |
* promise pipelining |
|---|
| 1026 |
|
|---|
| 1027 |
Even without third-party references, we can do E-style promise pipelining. |
|---|
| 1028 |
|
|---|
| 1029 |
<warner> hmm. subclass of Deferred that represents a Promise, can be |
|---|
| 1030 |
serialized if it's being sent to the same broker as the RemoteReference it was |
|---|
| 1031 |
generated for |
|---|
| 1032 |
<dash> warner: hmmm. how's that help us? |
|---|
| 1033 |
<dash> oh, pipelining? |
|---|
| 1034 |
<warner> maybe a flag on the callRemote to say that "yeah, I want a |
|---|
| 1035 |
DeferredPromise out of you, but I'm only going to include it as an argument to |
|---|
| 1036 |
another method call I'm sending you, so don't bother sending *me* the result" |
|---|
| 1037 |
<dash> aah |
|---|
| 1038 |
<dash> yeah |
|---|
| 1039 |
<dash> that sounds like a reasonable approach |
|---|
| 1040 |
<warner> that would actually work |
|---|
| 1041 |
<warner> dash: do you know if E makes any attempt to handle >2 vats in their |
|---|
| 1042 |
pipelining implementation? seems to me it could turn into a large network |
|---|
| 1043 |
optimization problem pretty quickly |
|---|
| 1044 |
<dash> warner: Mmm |
|---|
| 1045 |
<warner> hmm |
|---|
| 1046 |
<dash> I do not think you have to |
|---|
| 1047 |
<warner> so you have: t1=a.callRemote("foo",args1); |
|---|
| 1048 |
t2=t1.callRemote("bar",args2), where callRemote returns a Promise, which is a |
|---|
| 1049 |
special kind of Deferred that remembers the Broker its answer will eventually |
|---|
| 1050 |
come from. If args2 consists of entirely immediate things (no Promises) or |
|---|
| 1051 |
Promises that are coming from the same broker as t1 uses, then the "bar" call |
|---|
| 1052 |
is eligible for pipelining and gets sent to the remote broker |
|---|
| 1053 |
<warner> in the resulting newpb banana sequence, the clid of the target method |
|---|
| 1054 |
is replaced by another kind of clid, which means "the answer you're going to |
|---|
| 1055 |
send to method call #N", where N comes from t1 |
|---|
| 1056 |
<dash> mmm yep |
|---|
| 1057 |
<warner> using that new I-can't-unserialize-this-yet hook we added, the second |
|---|
| 1058 |
call sequence doesn't finish unserializing until the first call finishes and |
|---|
| 1059 |
sends the answer. Sending answer #N fires the hook's deferred. |
|---|
| 1060 |
<warner> that triggers the invocation of the second method |
|---|
| 1061 |
<dash> yay |
|---|
| 1062 |
<warner> hm, of course that totally blows away the idea of using a Constraint |
|---|
| 1063 |
on the arguments to the second method |
|---|
| 1064 |
<warner> because you don't even know what the object is until after the |
|---|
| 1065 |
arguments have arrived |
|---|
| 1066 |
<warner> but |
|---|
| 1067 |
<dash> well |
|---|
| 1068 |
<warner> the first method has a schema, which includes a return constraint |
|---|
| 1069 |
<dash> okay you can't fail synchronously |
|---|
| 1070 |
<warner> so you *can* assert that, whatever the object will be, it obeys that |
|---|
| 1071 |
constraint |
|---|
| 1072 |
<dash> but you can return a failure like everybody else |
|---|
| 1073 |
<warner> and since the constraint specifies an Interface, then the Interface |
|---|
| 1074 |
plus mehtod name is enough to come up with an argument constraint |
|---|
| 1075 |
<warner> so you can still enforce one |
|---|
| 1076 |
<warner> this is kind of cool |
|---|
| 1077 |
<dash> the big advantage of pipelining is that you can have a lot of |
|---|
| 1078 |
composable primitives on your remote interfaces rather than having to smush |
|---|
| 1079 |
them together into things that are efficient to call remotely |
|---|
| 1080 |
<warner> hm, yeah, as long as all the arguments are either immediate or |
|---|
| 1081 |
reference something on the recipient |
|---|
| 1082 |
<warner> as soon as a third party enters the equation, you have to decide |
|---|
| 1083 |
whether to wait for the arguments to resolve locally or if it might be faster |
|---|
| 1084 |
to throw them at someone else |
|---|
| 1085 |
<warner> that's where the network-optimization thing I mentioned before comes |
|---|
| 1086 |
into play |
|---|
| 1087 |
<dash> mmm |
|---|
| 1088 |
<warner> you send messages to A and to B, once you get both results you want |
|---|
| 1089 |
to send the pair to C to do something with them |
|---|
| 1090 |
<dash> spin me an example scenario |
|---|
| 1091 |
<dash> Hmm |
|---|
| 1092 |
<warner> if all three are close to each other, and you're far from all of |
|---|
| 1093 |
them, it makes more sense to tell C about A and B |
|---|
| 1094 |
<dash> how _does_ E handle that |
|---|
| 1095 |
<warner> or maybe tell A and B about C, tell them "when you get done, send |
|---|
| 1096 |
your results to C, who will be waiting for them" |
|---|
| 1097 |
<dash> warner: yeah, i think that the right thing to do is to wait for them to |
|---|
| 1098 |
resolve locally |
|---|
| 1099 |
<Tv> assuming that C can talk to A and B is bad |
|---|
| 1100 |
<dash> no it isn't |
|---|
| 1101 |
<Tv> well, depends on whether you live in this world or not :) |
|---|
| 1102 |
<dash> warner: if you want other behaviour then you should have to set it up |
|---|
| 1103 |
explicitly, i think |
|---|
| 1104 |
<warner> I'm not even sure how you would describe that sort of thing. It'd be |
|---|
| 1105 |
like routing protocols, you assign a cost to each link and hope some magical |
|---|
| 1106 |
omniscient entity can pick an optimal solution |
|---|
| 1107 |
|
|---|
| 1108 |
** revealing intentions |
|---|
| 1109 |
|
|---|
| 1110 |
<zooko> Now suppose I say "B.your_fired(C.revoke_his_rights())", or such. |
|---|
| 1111 |
<warner> A->C: sell all my stock. A->B: declare bankruptcy |
|---|
| 1112 |
|
|---|
| 1113 |
If B has access to C, and the promises are pipelined, then B has a window |
|---|
| 1114 |
during which they know something's about to happen, and they still have full |
|---|
| 1115 |
access to C, so they can do evil. |
|---|
| 1116 |
|
|---|
| 1117 |
Zooko tried to explain the concern to MarkM years ago, but didn't have a |
|---|
| 1118 |
clear example of the problem. The thing is, B can do evil all the time, |
|---|
| 1119 |
you're just trying to revoke their capability *before* they get wind of your |
|---|
| 1120 |
intentions. Keeping intentions secret is hard, much harder than limiting |
|---|
| 1121 |
someone's capabilities. It's kind of the trailing edge of the capability, as |
|---|
| 1122 |
opposed to the leading edge. |
|---|
| 1123 |
|
|---|
| 1124 |
Zooko feels the language needs clear support for expressing how the |
|---|
| 1125 |
synchronization needs to take place, and which domain it needs to happen in. |
|---|
| 1126 |
|
|---|
| 1127 |
* web-calculus integration |
|---|
| 1128 |
|
|---|
| 1129 |
Tyler pointed out that it is vital for a node to be able to grant limited |
|---|
| 1130 |
access to some held object. Specifically, Alice may want to give Bob a |
|---|
| 1131 |
reference not to Carol as a whole, but to just a specific Carol.remote_foo |
|---|
| 1132 |
method (and not to any other methods that Alice might be allowed to invoke). |
|---|
| 1133 |
I had been thinking of using RemoteInterfaces to indicate method subsets, |
|---|
| 1134 |
something like this: |
|---|
| 1135 |
|
|---|
|
|---|