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(swingset): stop using a persistent kernel bundle #5706

Merged
merged 1 commit into from
Jul 1, 2022

Conversation

warner
Copy link
Member

@warner warner commented Jun 30, 2022

Previously, the kernel sources were bundled during
initializeSwingset(), and stored in the kvStore portion of the
swingstore kernelDB. It was read from that DB each time
makeSwingsetController() launched the application process.

That ensured the kernel behavior would remain consistent (and
deterministic) even if the user's source tree changed. Using a
persistent kernel bundle also speeds up application launch: we save
about 1.8s by not re-running bundleSource on each launch.

Once upon a time I thought this consistency was important/useful, but
now I see that it causes more problems than it's worth. In particular,
upgrading a chain requires an awkward "rekernelize" action to replace
the kernel bundle in the database. And we believe we can reclaim about
1.0s per launch by moving to a not-yet-written importLocation() Endo
tool that loads module graphs from disk without serializing the
archive in the middle.

This patch changes swingset to stop using a persistent kernel
bundle. Each application launch (i.e. makeSwingsetController() call)
re-bundles the kernel sources just before importing that bundle, so
that updated source trees are automatically picked up without
additional API calls or rekernelize steps. The bundle is no longer
stored in the kvStore.

The other bundles (vats, devices, xsnap helpers) are still created
during initializeSwingset and stored in the DB, since these become
part of the deterministic history of each vat (#4376 and #5703 will
inform changes to how those bundles are managed). Only the kernel
bundle has become non-persistent.

Bundling is slow enough that many unit tests pre-bundle both the
kernel and the sources of built-in vats, devices, and xsnap helper
bundles. This provides a considerable speedup for tests that build (or
launch) multiple kernels within a single test file. These
kernelBundles are passed into initializationOptions, which then
bypassed the swingset-bundles-it-for-you code. This saves a minute or
two when running the full swingset test suite.

Unit tests can still use initializationOptions to amortize bundling
costs for the non-kernel bundles. runtimeOptions now accepts a
kernelBundle member to amortize bundling the kernel itself. Tests
which use this trick and also use makeSwingsetController() need to
be updated (else they'll run 1.8s slower). All such tests were inside
packages/SwingSet/test/ and have been updated, which would otherwise
cause the test suite to run about 50-60s slower.

The other agoric-sdk packages that do swingset-style tests are mostly
using the deprecated buildVatController(), whose options continue to
accept both kinds of bundles, and do not need changes.

closes #5679

@warner warner added the SwingSet package: SwingSet label Jun 30, 2022
@warner warner added this to the Mainnet 1 milestone Jun 30, 2022
@warner warner requested a review from mhofman June 30, 2022 23:23
@warner warner self-assigned this Jun 30, 2022
@warner warner force-pushed the 5679-unpersist-kernel-bundle branch from ad0d304 to f7bf9fa Compare June 30, 2022 23:24
Copy link
Member

@mhofman mhofman left a comment

Choose a reason for hiding this comment

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

LGTM, mostly nits on option naming.

I'd like to see the slog-to-otel file updated to avoid the console err spam (arguably minimal here)

@@ -238,6 +239,10 @@ export async function makeSwingsetController(
// see https://github.com/Agoric/SES-shim/issues/292 for details
harden(console);

writeSlogObject({ type: 'bundle-kernel-start' });
Copy link
Member

Choose a reason for hiding this comment

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

Adding slog events should also result in adding a case in slog-to-otel. I'm open to suggestions on how to test this (either through types or unit tests).

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, until/unless we have slog-to-otel tests (was that sketch I wrote at all useful?), I guess types are good enough. Adding a single span with name bundle-kernel or swingset-bundle-kernel that gets recorded when the bundle-kernel-finish appears might be nice, although really I don't know how helpful it'll be to get telemetry on this, it's always going to be about the same amount of time, and happens rarely.

@@ -542,6 +545,7 @@ export async function buildVatController(
slogCallbacks,
warehousePolicy,
slogFile,
kernelBundle: kernelBundles?.kernelBundle,
};
const initializationOptions = { verbose, kernelBundles };
Copy link
Member

Choose a reason for hiding this comment

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

It seems that kernelBundle (formerly kernel) is not used by initializeSwingset ? Should kernelBundles be destructured first to split the pieces? And maybe the initializationOptions property renamed to be explicit it no longer looks at the kernel option?

Aka:

  const {
    hostStorage = provideHostStorage(),
    env,
    verbose,
    kernelBundles: { kernel: kernelBundle, ...vatAndDeviceBundles } = {},
    debugPrefix,
    slogCallbacks,
    warehousePolicy,
    slogFile,
  } = runtimeOptions;

Copy link
Member Author

Choose a reason for hiding this comment

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

Right, kernelBundle is no longer used by initializeSwingset. It accepts a kernelBundles (plural) that has the vat/device/lockdown/supervisor bundles (aka "bundles that the kernel needs") but not kernelBundle (aka "the bundle that contains the actual kernel"). But I figured it wasn't worth the work to remove it: initializeSwingset will just ignore the spurious property.

This buildVatController is a deprecated API that's mostly used by external unit tests, so I didn't want to change the signature significantly. And by having buildVatController accept an option object that includes both kernelBundle and the other bundles, none of those tests needs to change, and they all continue to benefit from the amortized bundling costs. But the consequence is that the name kernelBundles as used in the third argument of buildVatController (and also the return value of the exported buildKernelBundles() function) is a slightly different/larger type than the "not the kernel too" kernelBundles accepted by the third argument of initializeSwingset.

I guess your proposal would look like:

  const {
    hostStorage = provideHostStorage(),
    env,
    verbose,
    kernelBundles: { kernelBundle, ...otherBundles } = {},
    debugPrefix,
    slogCallbacks,
    warehousePolicy,
    slogFile,
  } = runtimeOptions;
  const actualRuntimeOptions = {
    env,
    verbose,
    debugPrefix,
    slogCallbacks,
    warehousePolicy,
    slogFile,
    kernelBundle,
  };
  const initializationOptions = { verbose, kernelBundles: otherBundles };

right? I'd be ok with that if it reads better to you.

And the wart is that initializationOptions.kernelBundles is a different type than actualRuntimeOptions.kernelBundles. I could change makeSwingsetController to take an option with a different name, but 1: that's a lot of churn and 2: kernelBundles is pretty good and 3: vatAndDeviceAndSupervisorAndLockdownBundles is pretty bad :).

Copy link
Member Author

Choose a reason for hiding this comment

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

nope, kernelBundles: { kernelBundle, ...otherBundles } = {} means that otherBundles is set to an empty object, even if the caller didn't provide kernelBundles, which makes initializeSwingset() skips the default-value call to await buildVatAndDeviceBundles():

  const {
    kernelBundles = await buildVatAndDeviceBundles(),
    verbose,
    addVatAdmin = true,
    addComms = true,
    addVattp = true,
    addTimer = true,
  } = initializationOptions;

so they wind up empty.

I think I'd have to do something like:

  const {
    hostStorage = provideHostStorage(),
    env,
    verbose,
    kernelBundles: kernelAndOtherBundles = {},
    debugPrefix,
    slogCallbacks,
    warehousePolicy,
    slogFile,
  } = runtimeOptions;
  const { kernel: kernelBundle, ...otherBundles } = kernelAndOtherBundles;
  const kernelBundles = runtimeOptions.kernelBundles ? otherBundles : undefined;

Copy link
Member

@mhofman mhofman Jul 1, 2022

Choose a reason for hiding this comment

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

Oh yeah that'd be a problem.
Starting to be not worth it!

@@ -542,6 +545,7 @@ export async function buildVatController(
slogCallbacks,
warehousePolicy,
slogFile,
kernelBundle: kernelBundles?.kernelBundle,
Copy link
Member

Choose a reason for hiding this comment

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

Note to self: it previously was kernelBundles.kernel, renamed in buildKernelBundles

const { bundleFormat = undefined } = options;
export async function buildKernelBundle() {
// this takes about 1.0s on my computer
const src = rel => bundleSource(new URL(rel, import.meta.url).pathname);
Copy link
Member

Choose a reason for hiding this comment

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

Is there a reason not to extract this src helper out of this function and buildVatAndDeviceBundles and into top level?

Copy link
Member Author

Choose a reason for hiding this comment

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

sure

Comment on lines 84 to 85
const [vdBundles, kernelBundle] = await Promise.all([bp, kp]);
return harden({ kernelBundle, ...vdBundles });
Copy link
Member

Choose a reason for hiding this comment

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

Any reason to rename the previously used kernel to kernelBundle ? I'm always warry of such renames with our typing info so sparse.

Copy link
Member Author

Choose a reason for hiding this comment

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

When the value is passed into runtimeOptions, I wanted it to be named kernelBundle and not kernel (since at that point it's no longer living inside a container with bundles in the name). I want to keep using buildKernelBundles and buildVatController(.. initializationOptions.kernelBundles) names to avoid rewriting a bunch of other tests.

I guess one option would be to have buildKernelBundles and buildVatController create/accept .kernel, and then rename it within buildVatController to pass runtimeOptions.kernelBundle into makeSwingsetController. Lemme see how that looks.

@warner warner requested a review from mhofman July 1, 2022 01:19
@warner
Copy link
Member Author

warner commented Jul 1, 2022

@mhofman New version pushed, see what you think about the changes

Copy link
Member

@mhofman mhofman left a comment

Choose a reason for hiding this comment

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

Thanks for addressing those nits. I think you were right thought, and excluding the kernelBundle from the rest may not be worth it.

I'll address the otel stuff in my upcoming PR since we already have spam from the unknown snapshot slot events anyway.

debugPrefix,
slogCallbacks,
warehousePolicy,
slogFile,
} = runtimeOptions;
const { kernel: kernelBundle, ...otherBundles } = kernelAndOtherBundles;
const kernelBundles = runtimeOptions.kernelBundles ? otherBundles : undefined;
Copy link
Member

Choose a reason for hiding this comment

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

Yeah sorry that's unfortunately not as clean as I originally thought!

@warner
Copy link
Member Author

warner commented Jul 1, 2022

Oh, sorry, I missed the fact that slog-to-otel errors everything it doesn't recognize.. I wonder if it'd be better to ignore those events, it's not like the operator can do anything about it, and it's not fatal or damaging.

@warner
Copy link
Member Author

warner commented Jul 1, 2022

Eh, it works now, and I fixed the type problems, so I guess I'll go with the current version.

Previously, the kernel sources were bundled during
`initializeSwingset()`, and stored in the kvStore portion of the
swingstore kernelDB. It was read from that DB each time
makeSwingsetController() launched the application process.

That ensured the kernel behavior would remain consistent (and
deterministic) even if the user's source tree changed. Using a
persistent kernel bundle also speeds up application launch: we save
about 1.8s by not re-running `bundleSource` on each launch.

Once upon a time I thought this consistency was important/useful, but
now I see that it causes more problems than it's worth. In particular,
upgrading a chain requires an awkward "rekernelize" action to replace
the kernel bundle in the database. And we believe we can reclaim about
1.0s per launch by moving to a not-yet-written `importLocation()` Endo
tool that loads module graphs from disk without serializing the
archive in the middle.

This patch changes swingset to stop using a persistent kernel
bundle. Each application launch (i.e. `makeSwingsetController()` call)
re-bundles the kernel sources just before importing that bundle, so
that updated source trees are automatically picked up without
additional API calls or rekernelize steps. The bundle is no longer
stored in the kvStore.

The other bundles (vats, devices, xsnap helpers) are still created
during `initializeSwingset` and stored in the DB, since these become
part of the deterministic history of each vat (#4376 and #5703 will
inform changes to how those bundles are managed). Only the kernel
bundle has become non-persistent.

Bundling is slow enough that many unit tests pre-bundle both the
kernel and the sources of built-in vats, devices, and xsnap helper
bundles. This provides a considerable speedup for tests that build (or
launch) multiple kernels within a single test file. These
`kernelBundles` are passed into `initializationOptions`, which then
bypassed the swingset-bundles-it-for-you code. This saves a minute or
two when running the full swingset test suite.

Unit tests can still use `initializationOptions` to amortize bundling
costs for the non-kernel bundles. `runtimeOptions` now accepts a
`kernelBundle` member to amortize bundling the kernel itself. Tests
which use this trick and also use `makeSwingsetController()` need to
be updated (else they'll run 1.8s slower). All such tests were inside
`packages/SwingSet/test/` and have been updated, which would otherwise
cause the test suite to run about 50-60s slower.

The other agoric-sdk packages that do swingset-style tests are mostly
using the deprecated `buildVatController()`, whose options continue to
accept both kinds of bundles, and do not need changes.

closes #5679
@warner warner force-pushed the 5679-unpersist-kernel-bundle branch from cfbbf33 to ec2c30d Compare July 1, 2022 04:17
@warner warner added the automerge:rebase Automatically rebase updates, then merge label Jul 1, 2022
@mergify mergify bot merged commit f2cb70e into master Jul 1, 2022
@mergify mergify bot deleted the 5679-unpersist-kernel-bundle branch July 1, 2022 06:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
automerge:rebase Automatically rebase updates, then merge SwingSet package: SwingSet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

unpersist the kernel bundle (was: API to trigger kernel rebundling)
2 participants