-
Notifications
You must be signed in to change notification settings - Fork 233
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
support virtual objects that manage ephemeral state #5759
Comments
For initialization, one option would be a breaking change to the API, in which the Another is adding |
If we're going for API breaking changes, I'd much rather we go for what I proposed in #5170 in which case we'd simply have something like |
There is an argument to be made about having a separate |
IBIS for the design options. Please edit this comment to add your points. Let's not bikeshed on the names yet and stick "ephemera" for any object that holds ephemera. String can change once the semantics are figured out. Requirements:
Cases to handle:
? How should the ephemera object be created?
|
factoryPowers: ({ state, self }) => provide(factoryPowersWM, self, () => makeFactoryPowers(state)); |
@FUDCo and I walked through the options this afternoon. We found problems with most of the proposals above, and came up with two new ones that we think might work. The main constraint is that the It also takes out the portion of proposal 1 that passes Proposal 5 is kinda flipped around, I was there when we came up with it but I can't parse it well enough to consider. Proposal 6 is close to the "open coded" approach that Chip and I were using as a jumping-off point, which I'll continue here. The following pseudo-code is what a userspace author might do on their own, if the VOM didn't provide any better tooling: function createVaultDirector(VDstuff) {
const ephemeraWM = new WeakMap();
function provideEphemera(vm, state) {
if (!ephemeraWM.has(vm)) {
const ephemera = createEphemera(VDstuff, vm, state);
ephemeraWM.set(vm, ephemera);
}
return ephemeraWM.get(vm);
}
const init = (args) => initialState;
const behavior = {
doFoo({ self, state }, ...fooArgs) {
const ephemera = provideEphemera(self, state);
doStuffWithEphemera();
},
doBar({ self, state }, ...barArgs) {
doStuffWithoutEphemera();
},
};
const options = {};
const makeVaultManager = defineDurableKind(handle, init, behavior, options);
function createVaultManager(VMstuff) {
dostuff();
const vm = makeVaultManager(args);
return vm;
}
return { createVaultManager };
} In that example, the parent code (VaultDirector) must create a WeakMap and a Then, inside every method that wants to use this ephemeral data, it must call Then, after upgrade, a new WeakMap is created, initially empty. The first time someone talks to one of the old (durable, We think this pattern would work, and could be implemented without VOM changes. But it's not particularly ergonomic. The biggest pain points are:
Keeping in mind the requirement that "Kinds which don't want function createVaultDirector(VDstuff) {
function createEphemera(state) {
// now use VDstuff and VaultManager's state to create
// ephemeral stuff for each vaultManager
// this ephemera can be anything, it doesn't even have to be an
// object, and will not be hardened
const ephemera = anything;
return ephemera;
}
const init = (args) => initialState;
const behavior = {
foo({ self, state, ephemera }, ...fooArgs) {
},
bar({ self, state }, ...barArgs) {
},
};
const options = { createEphemera };
const makeVaultManager = defineDurableKind(handle, init, behavior, options);
function createVaultManager(VMstuff) {
dostuff();
const vm = makeVaultManager(args);
return vm;
}
return { createVaultManager };
} The new By passing it in
In version-1, the user's Because This is probably the biggest limitation of this approach, but it is the price paid for removing the boilerplate and receiving Within the VOM, the (It would be slightly easier/safer to build I'll describe the second proposal we came up in a separate comment. |
Our second proposal is a bit more radical. We realized that we're currently providing three-ish tools, with various values of two orthogonal properties:
The fourth corner wants a tool for data that is durable but of low-cardinality (so we can afford to spend RAM on each instance). A lot of the singleton Kinds we're building for contract upgrade (ZCF, the contract instance) fall into this category, but some of the friction is because our only durable tool is made for high-cardinality data. We sketched out a fourth tool, with a strawman name of function makeBehavior(state) { // called once per version, during first unserialize
// could mutate 'state' here
let ephemera;
return {
doFoo({ self }, ...fooArgs) {
// can read/write ephemera. 'self' has an identity.
},
doBar({ self }, ...barArgs) {
},
doBarMulti({ facets }) {
},
};
let maker = defineExpensiveDurableKind(handle, init, makeBehavior, options); This approach would call By calling a user-provided function once per instance, we could create Note that Also note that The |
Argh, nope, both We disable it because deserialization might encounter vrefs which refer to virtual/durable objects, whose Representatives may or may not already be in memory (they are tracked with a WeakRef). If we don't currently have a Representative, we must build one, which costs more meter usage than if we skipped it. That would give a GC sensor to anyone watching the meter. To avoid that, we sandwich the The number of times The open-coded approach doesn't suffer from this because |
Why not make
This getter would roughly be equivalent to a Regarding the |
Oh, that's clever.. yeah I think making it a getter would address the problems I raised.
Yeah, if the method is defined as
Hm,
Yeah, needing the And the "closure over state" model is exactly what it's trying to salvage, for the use case where we're closing over shared (ephemeral) state, and only use The |
Yes, and there is also the option to explicitly create the ephemera before calling the behavior method the first time (not during the getter), and prevent calling any facet methods during the ephemera creation. This would be a more heavy handed "guard" to prevent potential footguns (conditionals ephemera init), but might prevent some legitimate use cases (internal behavior facets used for ephemera init).
Oh I thought we explicitly disallowed that.
It's not, I just wanted to get ephemera experience before moving it into the platform, see if that approach actually solves problems. Basically have userland implement
I still very much would like to "disable" the |
What is the Problem Being Solved?
Changing requirements should be efficient
When developing contracts, requirements may evolve and it helps developers to minimize the effort necessary to implement changes. One such requirement is the durability of data. It can be:
In the Virtual Object Manager (VOM) now transitioning a value from V→D or D→V is pretty declarative. Change a function name or add an option flag. But changing →H or H→ is laborious. See #5736 commits example.
The resulting code should also be clean.
The movements above resulted in the simple
agoric-sdk/packages/run-protocol/src/vaultFactory/vaultManager.js
Line 529 in 39fe092
turning into
agoric-sdk/packages/run-protocol/src/vaultFactory/vaultManager.js
Lines 557 to 561 in e4c837a
Representatives cannot reveal GC
#5758 tried to solve the above problems by letting some heap data onto the
state
object. The problem with this is if that userspace could tell when GC happens because one Representative will have those properties, while later Representatives will not.Description of the Design
A design that seems to satisfy the above requirements is to continue to use the WeakMap pattern as in
agoric-sdk/packages/run-protocol/src/vaultFactory/vaultManager.js
Lines 140 to 147 in 39fe092
But instead of making the module responsible for the map and each method responsible for pulling the
ephemera
object out of it, have the VOM provideephemera
in the context next tostate
. So the ugly refactor above could be back to one line,TBD how the
ephemera
object gets initialized. Some requirements:-
finished
, like other context consumers needsephemera
.state
so can't be beforeinitState
(example wrapping a durable store in ephemeral behavior)…so maybe
initEphemera(state, ...args)
Security Considerations
Test Plan
The text was updated successfully, but these errors were encountered: