Skip to content
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

fix dynamic vat replay #1481

Merged
merged 3 commits into from
Aug 15, 2020
Merged

fix dynamic vat replay #1481

merged 3 commits into from
Aug 15, 2020

Conversation

warner
Copy link
Member

@warner warner commented Aug 15, 2020

Dynamic vats aren't getting reloaded properly: we aren't saving the source
bundle used to create them, nor are we loading them at startup time.

This adds test to exercise the problem, which fail ("unknown vatID") when we
start a new swingset from the old state vector. We'll fix the problem in the
next commit.

refs #1480

@warner warner added the SwingSet package: SwingSet label Aug 15, 2020
@warner warner self-assigned this Aug 15, 2020
Dynamic vats aren't getting reloaded properly: we aren't saving the source
bundle used to create them, nor are we loading them at startup time.

This adds test to exercise the problem, which fail ("unknown vatID") when we
start a new swingset from the old state vector. We'll fix the problem in the
next commit.

refs #1480
This saves the dynamic vat data (source bundle or bundleName, dynamicOptions)
to the kernel state DB when the vat is first created. Then, during replay, we
reload the dynamic vat from that data.

This refactors dynamic vat creation slightly, to merge create-by-bundle and
create-by-bundle-name into a single function that takes a `source` argument
which contains either `{ bundle }` or `{ bundleName }`. By passing `source`
to `createVatDynamically`, rather than first converting it into a full
bundle, we have enough information to store the much smaller bundleName in
the DB. Userspace code still sees the same API (`vatAdminSvc~.createVat(bundle)`
or `vatAdminSvc~.createVatByName(bundleName)`), but the kernel internals
and the vat-admin device endowments have changed.

We add a new `vat.dynamicIDs` entry to the kernelKeeper, which contains a
single JSON-encoded Array of dynamic vatIDs. This isn't great, but it's
expedient, and our DB doesn't really support enumerating anything more clever
anyways. We also add a pair of keys to the vatKeeper for each dynamic vat:
`v$NN.source` and `v$NN.options`, to record the bundle/bundleName and
dynamicOptions that were used the first time around, so we can use them on
subsequent calls too.

`createVatDynamically` normally sends a message (to the vatAdminVat) when the
new vat is ready, so it can inform the original caller and give them the new
root object. We suppress this message when reloading the kernel, because it
was already delivered the first time around. One side-effect is that failures
to reload the dynamic vat must panic the kernel, because there is nobody
ready to report the error to. This could happen if the new host application
failed to configure the same source bundles as the previous one did. At some
point we'll want to store all these bundles in the DB, which should remove
this failure mode.

closes #1480
@warner warner marked this pull request as ready for review August 15, 2020 09:02
@warner warner requested a review from FUDCo August 15, 2020 09:02
Copy link
Contributor

@FUDCo FUDCo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good aside from my issues below w.r.t the complexification of the API. I don't think those concerns are major enough to stop the train (hence I'm approving) but I'm a wee bit cranky about them. Also one or two of other totally non-blocking minor nits to address at your leisure.

packages/SwingSet/src/kernel/dynamicVat.js Show resolved Hide resolved
packages/SwingSet/src/kernel/dynamicVat.js Outdated Show resolved Hide resolved
* that can occur during any given crank. Stack frames are limited as well.
* The meter is refilled between cranks, but if the meter ever underflows,
* the vat is terminated. If 'false', the vat is unmetered.
* @param source an object which either has a `bundle` (JSON-serializable
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really not a fan of switching from two methods with simple APIs to one method with a complicated API.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hear you, but we can't simplify it to the point that dynamicVat.js only hears about full bundle objects, as it did before. If we did that, the create-from-name case would throw away the name before dynamicVat.js heard about it, which means it would have to store the full (unnamed) bundle into the DB for both cases, not just create-from-bundle. By retaining the name (when one is available), we can store just the name in the DB. And that feels like it's going to be important for storage-performance pretty quickly.

The fully-denormalized(?) alternative would probably be for vatKeeper to have two separate methods (one that returns a source bundle or undefined, a second which returns a source bundleName or undefined). The kernel would call both in succession to see which one has a value. Then dynamicVat.js must expose four methods (first-time-vs-replay times name-vs-bundle). That felt like a net complexity increase over the polymorphic source argument. We could have it expose two methods (name-vs-bundle) and make both take an optional vatID to select between first-time-vs-replay, but I was more uncomfortable using polymorphism over vatID than I was over source.

// vat.name.$NAME = $vatID = v$NN
// vat.nextID = $NN
// device.names = JSON([names..])
// device.name.$NAME = $deviceID = d$NN
// device.nextID = $NN

// dynamic vats have these too:
// v$NN.source = JSON({ bundle }) or JSON({ bundleName })
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suspect overloading this is the tail that wagged to dog w.r.t the API complexity I complained about above.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It did make it make the other pieces fall into that shape, yeah. My first pass had separate v$NN.sourceBundle and v$NN.sourceBundleName, with a comment that there should only ever be one, which is a sort of fussy invariant that I always want to avoid (better to have the rule be present-or-not than one-of-two).

@@ -498,6 +504,18 @@ export default function makeKernelKeeper(storage) {
return storage.get(k);
}

function addDynamicVatID(vatID) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm presuming that the plan is (eventually) for dead vats to be removed from vat.dynamicIDs? Also, might it make sense to put them in a separate identifier namespace (e.g., vd$NN) and allocate them here, instead of passing the ID in, as that might reduce the chances of a coordination failure between allocation and use?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, we'll remove them from that list when they're really dead. For our intermediate step, the dead vats will be remembered by a vat.dynamicIDs entry and a v$NN.dead flag (and no other v$NN.* keys), which will tell our startup code to create a dead-vat no-manager no-vatKeeper ephemeral.vats entry for it. The step after that, when we remove them completely (no object-table/etc references), will delete the two remaining DB keys, and the only trace that they ever existed will be the suspiciously-elevated nextVatID counter. And of course the vat will live on in the memories (messages/transcripts) of all those (surviving vats) who loved it.

@@ -31,12 +31,17 @@ const enableKernelPromiseGC = true;
// The schema is:
//
// vat.names = JSON([names..])
// vat.dynamicIDs = JSON([vatIDs..])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me know when you think the schema changes have stabilized, as I (or somebody, but probably me) will need to update the kerneldump utility to match.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's probably stable enough now. Unless it breaks something (which, if there were unit tests for kernelDump, I would have noticed 😉), I think we can ignore the new keys until your dead-vat changes land, and update kernelDump for both at the same time.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding to the database schema doesn't break kernelDump but if it knows about the new items it can display them more usefully.

Copy link
Member

@michaelfig michaelfig left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to add the genesis vats to the kernelDB as well, and treat their sources the same as the dynamic vats? I would like this, so that it's a little harder to accidentally modify the genesis vat source code without resetting state from scratch, and also so that exporting the kernel state is just a matter of doing a direct dump of the kernelDB and not having to rebundle sources at export time (which is sensitive to the bundler we use, and any changes to the sources). It would also mean we don't need to copy the vat directory to every created basedir.

If not now, but you think this is a good idea, I can create an issue for it.

@warner
Copy link
Member Author

warner commented Aug 15, 2020

Would it make sense to add the genesis vats to the kernelDB as well, and treat their sources the same as the dynamic vats? I would like this, so that it's a little harder to accidentally modify the genesis vat source code without resetting state from scratch, and also so that exporting the kernel state is just a matter of doing a direct dump of the kernelDB and not having to rebundle sources at export time (which is sensitive to the bundler we use, and any changes to the sources). It would also mean we don't need to copy the vat directory to every created basedir.

If not now, but you think this is a good idea, I can create an issue for it.

Yes, absolutely. I think we'll wind up going farther than that: the "payload"/deployment level of the stack will be defined by a set of additions to the "blob pool" (where bundles get stored), and an initial series of messages to the singular initial vat, which creates dynamic vats for zoe and whatever else the system should have from T=2 (where T=1 has the initial vat but nothing else). Genesis vats should be replaced by messages. I'm not sure how long the medium-term (genesis vat source code goes into the DB) will last, so I'm kinda deferring it a bit in the hopes we can jump directly to the messages form, but if we haven't made any progress on the latter within, say, a month, let's go ahead and do the genesis-code-in-DB thing.

Also address some review notes
@warner warner linked an issue Aug 15, 2020 that may be closed by this pull request
@warner warner merged commit a3bf3cd into master Aug 15, 2020
@warner warner deleted the 1480-dynamic-vat-replay branch August 15, 2020 23:27
@warner warner changed the title chore(swingset): add (failing) test for dynamic vat replay fix dynamic vat replay Aug 16, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
SwingSet package: SwingSet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

dynamic vats aren't replayed when kernel is restarted
3 participants