[e-lang] Newbie questions about persistence
Kevin Reid
kpreid at mac.com
Fri Aug 28 19:15:16 EDT 2009
On Aug 28, 2009, at 10:27, Thomas Leonard wrote:
> There are a number of things which are probably obvious if you know
> the system, but took me a while to realise. I'll summarise them here
> for other beginners, and so people can correct the bits I get wrong.
...
> Sturdy refs
>
> My first mistake was assuming that, since a client can invoke methods
> on a live ref and can pass the live ref to other clients, it must know
> the remote object's Swiss number. I therefore assumed that the client
> should call "makeSturdyRef(farRef)" to be able to reconnect or persist
> the reference.
The second part is true: to be persistent across CapTP connections a
reference must be a SturdyRef.
> The CapTP documentation says this about what is sent to the client (in
> DeliverOnlyOp):
>
> "The NewFarDesc encoding must have all the information needed to
> create such a new Far reference, which is the position the Far
> reference should be assigned in the Imports table (the same as the
> position at which Carol is Exported), and the SwissNumber that,
> together with VatA's VatID, represents Carol's sameness identity.
>
> However, the JavaDoc shows that a NewFarDesc actually contains only
> the Swiss hash (i.e. the identity of the object), not the Swiss number
> (the ability to call it):
MarkM, could you comment on why this is so?
> * When a live ref is sent to the client, it is assigned a small
> integer ID (like a Unix file descriptor), which is only valid in the
> context of that TCP connection. There is a special mechanism for
> passing these live refs between clients, via the server.
There is no client or server in CapTP; it is a symmetric protocol. 3-
party introductions happen in the same way always.
> * The client cannot get a Swiss number from a live ref.
Yes.
> * The server (hosting vat) must always call makeSturdyRef on an object
> before passing it to another vat if it wants the remote end to be
> able to persist it or reconnect after the TCP connection is closed.
Yes.
> * Calling makeSturdyRef creates a new Swiss base and, from that, a new
> Swiss number. It adds a mapping from this to the object, in a map it
> shares with the time machine. It then registers the object with the
> IdentityMgr (so that clients can connect to it) and creates the
> actual SturdyRef object with the Swiss number in it.
Yes.
> * Calling makeSturdyRef.temp is similar, except that it doesn't add
> anything to the map shared with the time machine.
Yes.
> * The time machine works by saving the object graph starting from the
> swiss-number-to-object mapping.
Yes.
> * Since makeSturdyRef(obj) makes a sturdy ref that lasts forever and
> is persisted, and throws away the SwissRetainer used to cancel it,
> refs made this way will accumulate forever.
Not forever, but for the lifetime of the vat. So if the system is such
that each Foo has a particular vat it, and only it runs in, it's
perfectly fine to do makeSturdyRef(theFoo) after setting up the vat.
> Many things cannot be serialised by default, including:
>
> - facets (obj.method)
This is a bug, IMO, and probably due to the lack of availability of
the persistence sealer as discussed below.
> - caretakers
This is a little tricky because it would be in general possible to
serialize the caretaker but not the component which decides to revoke
it. However, there is an (probably outdated) version of this called
makeFancyRevoker in the Den <http://wiki.erights.org/wiki/Den> source
code.
> - promises
In general, this is necessary. A promise becomes something else on
some unspecified future event; that event is running code/messages in
flight and cannot be serialized.
If you have some activity that *can* be serialized (e.g. a promise for
the result of a lengthy computation, which can be saved/restarted)
then the proxy interface <http://wiki.erights.org/wiki/Proxy> can be
used to create a promise which is also serializable (by
handleOptSealedDispatch).
> - near problems (bug in minimalUncaller?)
Do you mean broken references? Could you provide details on this bug?
> Persistence of sturdy refs
>
> A sturdy ref can only be serialised if fully resolved (is this a
> bug?),
> e.g.
>
> ? introducer.onTheAir()
> ? def surgeon := <elib:serial.makeSurgeon>.withSrcKit("de:
> ").diverge()
> ? surgeon.addLoader(introducer, "cap__uriGetter")
> ? def a
> ? def b := makeSturdyRef.temp(3)
> ? bind a := b
>
> ? surgeon.serialize(a)
> # problem: Can't uneval <SturdyRef to 3>
>
> ? a == b
> # value: true
>
> ? surgeon.serialize(b)
> # value: "de: <cap://*qvcmrxvqy66rest6gfzhhxwrjni6nrdq@192.9.206.110:59905/jo6sk4nehnrewzo7bvntfz7rl32f6q2g
> >"
This is a bug; I have committed a fix.
If a == b then nothing whatsoever (except for unsafe imports) should
behave differently on a and b. Ref.resolution/1 should always be a no-
op from the E user's perspective. The bug was that
Introducer.optUncall performed an instanceof check without
Ref.resolution first.
> Persistence of cycles
>
> Some cyclic data structures can be persisted, while others can't (is
> this a bug?). I haven't worked out what the rule is, but I do have
> some examples:
...
> Serializing [[<***CYCLE***>], [<***CYCLE***>]]...
> Serialized as de: [def t__0 := [t__0], t__0]
> # problem: <IndexOutOfBoundsException: not found: t__0>
>
> This is quite annoying, because a SwissRetainer includes a pointer to
> its object, in addition to the pointer in the table for which the
> retainer is a key. Therefore, an object containing a single cycle ends
> up with the broken double-cycle structure when saved.
This is a bug. Please send versions, test cases, patches, etc.
The relevant code would be in esrc/org/erights/e/elib/serial/,
deSubgraphKit, deASTKit, and deSrcKit. Since it mentions "t__0" as
opposed to 0, probably deSrcKit.
> Persistence of identity
>
> Transparent objects can implement __optUncall to allow them to be
> persisted. This method returns all of the object's authority, proving
> to the surgeon that it is permitted to be revived with it. However,
> this also allows anyone else to get an object's authority by calling
> __optUncall themselves.
Yes.
> Therefore, most objects should use __optSealedDispatch to seal the
> result. There don't seem to be many examples of this (e.g. FileGetter
> uses "obj instanceof File" instead). I'm having trouble seeing how to
> use this. Should I add a new loader/uncaller?
No, you should use the vat-wide persistence sealer, whose unsealer is
closely held by the vat persistence system, in your
__optSealedDispatch. If you seal your portrayal (what you would return
from __optUncall) then you will get the behavior you want without any
global definitions.
Unfortunately, IIRC, the persistence sealer is also closely held, but
there is no good reason for this. (MarkM, could you confirm this?)
> How is identity handled?
> e.g if I have a one-shot object, how can I ensure that an object
> holding it will revive with only one copy of the one-shot object?
> Similarly for a caretaker.
If your object uncalls only by request of the vat persistence sealer,
then you're guaranteed that its reincarnations will only be along with
the rest of the vat state. So your one-shot might be rolled back to
the not-yet-fired state, but everything else will be rolled back too
and in sync.
> Persistence of functions
>
> The result of uncalling an object is usually of the form [makeFoo,
> "run", [...]], where makeFoo is a top-level function in some module.
> Is there any way to get the time machine to handle these
> automatically, without having to add them all as exists manually?
The proper way for this to happen is that makeFoo is DeepFrozen. If
this happens, then the <import> loader will automatically serve as a
loader for your maker.
Unfortunately, the guard-based auditing system has not yet been
implemented in E-on-Java, so the DeepFrozen auditor to verify your
maker as DeepFrozen cannot be used with E-on-Java, and only built-in
objects can be DeepFrozen in E-on-Java.
If you want to experiment with DeepFrozen you can use my own E-on-CL <http://wiki.erights.org/wiki/E-on-CL
> which contains the DeepFrozen auditor.
The workaround way, which I have used in Den, is to give the maker an
__optUncall:
--- begin makeFoo.emaker ---
def makeFoo {
to __optUncall() { return [<import>, "get", ["my.fqn.goes.here"]] }
to run() {
def foo {
to __optUncall() { return [makeFoo, "run", []] }
}
return foo
}
}
--- end makeFoo.emaker ---
The fully-generic way to write that optUncall is:
to __optUncall() {
return [<import>, "get", [meta.context().getFQNPrefix().split("$")
[0]]]
}
> My current solution is that every module looks like this:
>
> def makeFooInternal(state) { ... }
>
> def makeFoo() { ... }
>
> [ =>makeFoo, =>makeFooInternal ]
A few comments:
* This will not work if you use persistence in the conventional
fashion: uncalls should typically correspond to calls, but here you
have a map, which will uncall using the map constructor, rather than
<import:makeFoo>.
* Why are you using two objects instead of two methods on one object?
* Particularly, "internal" is a bad concept: I can guess that you are
using this for persistence of the state of an object, which you never
expect to be constructed explicitly, but it is important to remember
that the way in which you portray the objects for serialization is
part of *THE PUBLIC INTERFACE* to your code, because other people's
serialized data will contain it even when you publish a new version of
your library/module/appplication.
--
Kevin Reid <http://switchb.org/kpreid/>
More information about the e-lang
mailing list