-
Notifications
You must be signed in to change notification settings - Fork 214
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): use "dirt" to schedule vat reap/bringOutYourDead #9169
Conversation
62aa511
to
0fe9f39
Compare
Deploying agoric-sdk with Cloudflare Pages
|
0fe9f39
to
967e458
Compare
967e458
to
402811a
Compare
I am somewhat concerned that the cost of engine gc might mean 20 is too low. How do we validate we won't spend too much time in BOYD? Do we have an idea of what portion of the time spent in BOYD is related to the engine doing a gc mark and sweep, and how much of it is liveslots reacting to the found garbage? I assume liveslots usually has vatStore calls to make when garbage was found?
This does not sound scalable for adding new dirt thresholds. Why not add a
I'll need to look at this. It sounds like a sort of schema migration.
Edit: I totally misunderstood |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is mostly looking good.
I think we're missing a clearReapDirt
for the BOYD injected when creating a snapshot.
Wondering if the schema migration should be handled more explicitly when starting the kernel instead of optimistically running it before each crank.
I'll need to do another pass and review the tests.
// the caller is responsible for ensuring that any changes made here | ||
// get committed at a useful point |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some worry that a rollback could revert the kernel state but would leave the upgraded
flag in place, but it looks like this is always called outside savepoints. Why not run this at the beginning of every startCrank()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, there's one upgraded
flag per KernelKeeper
object, and each kernel has exactly one KernelKeeper
(created at the start of buildKernel()
), but we also create a KernelKeeper
when we initialize/populate the DB, and we should not be doing an upgrade at that time, which is why maybeUpgradeKernelState()
is a method and not an automatic consequence of makeKernelKeeper()
.
It's currently called just before each call to startCrank()
.. I wanted to make it more explicit, because (as you noted) it's important to call it from outside a savepoint. I was worried that adding it to startCrank()
would obscure the upgrade point and make it fragile under maintenance.
But.. in looking more carefully at how/when startCrank
is called (it also show up in changeKernelOptions
and installBundle
, to which I did not add a maybeUpgrade
call), I think my PR would have a small bug. kernelKeeper.startCrank()
calls kernelStorage.startCrank()
in swing-store, which sets an inCrank = true
flag, and calls resetCrankHash()
. By not calling startCrank
before maybeUpgrade
(or not calling it at all, if we start with changeKernelOptions
or installBundle
, which sounds pretty plausible TBH), we don't include any upgrade-related kvStore changes in the crankhash. That's not the intent: upgrades are in-consensus, all nodes of a consensus kernel should upgrade at the same time, and their changes should be included in the crankhash.
So I think I need to change this as you suggest: call maybeUpgradeKernelState()
from inside kernelKeeper.startCrank()
, but do it specifically after that function calls kernelStorage.startCrank()
. I'm not excited to conceal the fact that upgrades are happening from call sites like step()
and run()
, but it's more important to have these upgrade changes be captured in the hash reliably.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we also create a
KernelKeeper
when we initialize/populate the DB, and we should not be doing an upgrade at that time, which is whymaybeUpgradeKernelState()
is a method and not an automatic consequence ofmakeKernelKeeper()
.
That had totally skipped my mind. Can we document somewhere these constraints?
return reapDirt; | ||
} | ||
|
||
function clearReapDirt() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to also call this when injecting the BOYD message in maybeSaveSnapshot
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, I'll see if there's a way to factor out BOYD delivery to have just one place to call it from (we actually have three code paths: processBringOutYourDead
, processUpgradeVat
as the last thing sent to the old incarnation, and maybeSaveSnapshot
). Ideally all three would call processBringOutYourDead
but that wasn't trivial. I'll see what I can do, worst case I'll just add another clearReapDirt()
call like you suggested.
// this default "reap interval" is low for the benefit of tests: | ||
// applications should set it to something higher (perhaps 200) based | ||
// on their expected usage | ||
|
||
export const DEFAULT_DELIVERIES_PER_BOYD = 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am somewhat concerned with a default value that is not "production ready", but this seems to be the existing behavior already, so out of scope. Is the builder for a test kernel the same as the production one?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The (weak) philosophy here was that we don't know what a good value would be, so make the default a predictable and hyper-safe one, at the cost of performance. Making it convenient for tests is another consideration, although of course it's always a lazy and inadvisable choice to prioritize tests over reality.
We might even make a hand-wavy argument that this matches XS: minimizing RAM usage (ie GCing frequently) at the cost of performance.
Is the builder for a test kernel the same as the production one?
Not sure what you mean, our most important host application (cosmic-swingset) overrides this value, and in general we expect all host applictions to do so.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was remembering that all tests used a single function to build the kernel, in which case we could have a value more appropriate for production as a default here. Anyway, as stated this not really in scope for this PR.
We certainly need to experiment on real data and tune this. The " My notes/predictions are in #8928 (comment) . This might actually be too aggressive, but if so we can reduce the cleanup rate instead of reducing the My worry about increasing Also, your question means I should measure the rate of non-cleanup-related
I started out doing it that way, but it triggered a lot of test churn, and made it hard to distinguish between "I'm setting a threshold and I don't care about property X, use your default", vs "I'm setting a threshold and I want property X to never trigger BOYD", mostly because I stuck with a type of I'll take another look and see if there's a way to get a cleaner API, but I have to admit it's not my highest priority.
It is, for sure. I thought briefly about adding a
import { makeSwingsetController, maybeUpgradeKernelState } from '@agoric/swingset-vat';
const { kernelStorage, hostStorage } = openSwingStore(..);
maybeUpgradeKernelState(kernelStorage);
hostStorage.commit();
const controller = makeSwingsetController(kernelStorage, ..);
.. We've had similar discussions in #8089, and I think you convinced me to make it explicit, where the host app must call a function (at the right time), and not have the library perform an automatic upgrade, but then export-data considerations led us both to give up on using an explicit call, and instead having upgrade be implicit during the I think we've got similar issues here: the reapDirt upgrades will cause kvStore changes, and export-data/IAVL changes, that need to be committed. The situation is better, though, because we already have the I'll try to think about how we could make this an explicit call.
Right, we'd have |
I went to move the upgrade code into I thought about our plans for swing-store schema migration (concluding by this comment), and the constraints therein, and how it applies to this upgrade step. We don't have the same "where do the export-data changes show up?" problem, since Each kernel reboot uses some particular version of the In a consensus system, each "block" (or whatever they call the coordinated units of work) is executed with some particular version of the kernel/swingstore. The host-app's responsbility is to ensure that each block is executed with the same version, across all workers. You must upgrade your kernel at the same time as everybody else, even though you're allowed to make non-upgrading reboots more frequently than that. So, I think I see three options:
I prefer being very explicit about when DB changes might happen, so I don't like C. And it feels odd to have So I'm going to pick A, where we have a separate I'm also going to move to the explicit kvStore key that says what version the schema is, and graduate from our "ad-hoc" phase . I need something for And then I'll need to look at how cosmic-swingset calls this and make sure I'm not creating any problems. We'll already need to handle mutation (and thus export-data callbacks being invoked) with the #8089 changes, because the constraints there obligate us to perform upgrades as part of Note that this means the upgrade changes won't be included in any crank, and they won't be included in a crankhash. (To be precise, they'll get hashed into the |
That sounds right. cosmic-swingset only builds the kernel as part of an init message, which is always done while processing a block at the cosmos level, so calls to the bridge are safe during that time.
This part is a little unfortunate, but I guess acceptable. If we ever change how we export the swing-store kv data, we'll likely still have data to validate it in IAVL. The unfortunate part is that right now we would have no way to detect this difference using the slog only, and exporting and comparing swing-store and iavl trees is difficult compared to comparing slog entries (e.g. from a flight recorder). Can we maybe add a new slog entry for upgrades, which maybe captures the crankHasher state at the end even if it doesn't end up chained like a normal crank? |
Let's see, if I call On one hand, that's good, it's a more explicit way for consensus to fail if one node diverges from the rest during On the other hand, it makes the act of calling I guess I could split I'm trying to think through how this interacts with the mutations caused by device inputs (eg pushing messages onto the run-queue). Those don't happen inside cranks, they happen before a |
I'm going to leave the crankhash/activityhash alone for now, I'm worried about accidentally introducing "how many times did we call |
402811a
to
3200c52
Compare
One lingering concern I've got.. we have a kernel-wide default value, and vat-creation -time options that can override it, and we combine those two values at the moment we create the vat, and store the resulting per-vat threshold in I'm wondering if there's a decent way to record "the creator of this vat didn't override anything" as a missing per-vat threshold property, and have the kernel merge in the kernel-wide default later, either as the That would change the upgrade process to look for differences between Adding new kinds of dirt would only require a change to the kernel-wide @mhofman thoughts? |
changed PR to draft for an hour while I try to implement that new thing |
3200c52
to
a698f7b
Compare
@mhofman ok I updated the PR to minimize the per-vat recordkeeping: it only puts something in |
a698f7b
to
2e3641a
Compare
just updated it to slog the cleanup work (and when it's complete), and to rebase onto current trunk |
fdecbe5
to
796a2d3
Compare
c05be76
to
70300ac
Compare
Upgrade ConsiderationsNOTE: deployed kernels require explicit state upgrade, with: import { upgradeSwingset } from '@agoric/swingset-vat';
..
upgradeSwingset(kernelStorage); This must be called after upgrading to the new kernel code/release, (we still need an integration PR which adds cosmic-swingset support; until that lands, pre-existing kernels, like mainnet, will crash at startup. I don't think we have any CI which exercises upgrade from old kernel state) |
70300ac
to
e79c29c
Compare
e79c29c
to
691b43a
Compare
I've updated the PR to include the necessary |
ede07c7
to
a1b9efe
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was going to ask about the interaction of various dirt-driven BOYDs, but I think this approach of allowing each metric to independently prompt them makes sense.
No blockers, but a large handful of æsthetic suggestions. Also, thanks for the thorough testing.
const CURRENT_VERSION = 1; | ||
kernelStorage.kvStore.set('version', `${CURRENT_VERSION}`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It feels wrong to modify kvStore contents before asserting that kernelKeeper.getInitialized()
returns false, and for that matter it seems like there's some redundancy between "initialized" and "version" data. I can think of a few possible refactorings, but the one that appeals to me most is:
- Remove
kernelStorage.kvStore.set('version', `${CURRENT_VERSION}`)
here. - Replace
kernelKeeper.setInitialized()
below with something likekernelKeeper.setInitialized(CURRENT_VERSION)
. - In kernel.js, update
setInitialized
accordingly (to take a version argument and write that to the kvStore, preferably at key "version" rather than "initialized").- If at key "version", then update
upgradeSwingset
accordingly (to migrate data from "initialized" to "version").
- If at key "version", then update
- Also in kernel.js, move the version check from
makeKernelKeeper
togetInitialized
:function getInitialized() { const version = kvStore.get('version'); if (version !== `${EXPECTED_VERSION}`) { throw Error( `kernelStorage is too old (have version ${version}, need ${EXPECTED_VERSION}), please upgradeSwingset()`, ); } return true; }
- Optionally, update kernel.js to check
getInitialized
right aftermakeKernelKeeper(kernelStorage, kernelSlog)
(maintaining the early error introduced by the current state of this PR). - Optionally, rename
setInitialized
to e.g.setVersion
and/orgetInitialized
to e.g.assertInitialized
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that sounds like the best plan. I'm not jazzed about the idea of makeKernelKeeper()
returning an API surface for the wrong data (e.g. if kernel.js
forgot to make the getInitialized
/assertInitialized
/assertVersion
check, and started using a non-upgraded DB), but the real problem is that that we need a kernelKeeper to populate the initial state, which of course happens before the DB initialized.
Hm, what about makeKernelKeeper(expectedVersion)
? We'd use undefined
for the initial construction, then the normal kernel.js
usage would pass in CURRENT_VERSION
or whatever. Oh, and what if we used 'uninitialized'
instead of undefined
? That would make it impossible to call makeKernelKeeper()
, and you'd have to be explicit about whether you're making one for initialization purposes, or for actual use?
Hm, now that makes me wonder if upgradeSwingset
could go back to being a method of kernelKeeper
, as I first tried. If we went that way, upgradeSwingset()
would be like makeKernelKeeper(..).upgrade()
.. hm, no, we wouldn't know what version to expect (it could be either 0 or 1, but shouldn't be missing entirely / uninitialized).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok I went with makeKernelKeeper(expectedVersion)
, and I like how it flows. kernelKeeper.js
exports the current version, so the kernel demands it, and won't get back an API that's using wrong-version data. And there's a way for initializeKernel
to demand an uninitialized one. The host app doesn't create the kernelKeeper, so it doesn't need to know anything about versions, it just calls upgradeSwingset
(either every boot, or every boot after the operator/host-app-author knows they've changed the kernel code).
if (!kvStore.has(`${vatID}.o.nextID`)) { | ||
initializeVatState(kvStore, transcriptStore, vatID); | ||
} | ||
assert(kvStore.has(`${vatID}.o.nextID`), `${vatID} was not initialized`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Style nit:
assert(kvStore.has(`${vatID}.o.nextID`), `${vatID} was not initialized`); | |
kvStore.has(`${vatID}.o.nextID`) || Fail`${vatID} was not initialized`; |
if (typeof value === 'number') { | ||
assert(value > 0, `threshold[${key}] = ${value}`); | ||
} else { | ||
assert.equal(value, 'never', `threshold[${key}] = ${value}`); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (typeof value === 'number') { | |
assert(value > 0, `threshold[${key}] = ${value}`); | |
} else { | |
assert.equal(value, 'never', `threshold[${key}] = ${value}`); | |
} | |
(typeof value === 'number' && value > 0) || | |
value === 'never' || | |
Fail`threshold[${assert.quote(key)}] ${value} must be a positive number or "never"`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll follow the previous pattern
I've pushed the feedback changes. I'll squash these into better shape before landing. @mhofman if you get time to look, the first four commits are organized/review-separately, the remainder are haphazard and not worth looking at separately. |
2fef729
to
1f7943e
Compare
Previously, vat state initialization (e.g. setting counters to zero) happened lazily, the first time that `provideVatKeeper()` was called. When creating a new vat, the pattern was: vk = kernelKeeper.provideVatKeeper(); vk.setSourceAndOptions(source, options); Now, we initialize both counters and source/options explicitly, in `recordVatOptions`, when the static/dynamic vat is first defined: kernelKeeper.createVatState(vatID, source, options); In the future, this will make it easier for `provideVatKeeper` to rely upon the presence of all vat state keys, especially `options`. Previously, vatKeeper.getOptions() would tolerate a missing key by returning empty options. The one place where this was needed (terminating a barely-created vat because startVat() failed, using getOptions().critical) was changed, so this tolerance is no longer needed, and was removed. The tolerance caused bug #9157 (kernel doesn't panic when it should), which continues to be present, but at least won't cause a crash. refs #8980
NOTE: deployed kernels require a new `upgradeSwingset()` call upon (at least) first boot after upgrading to this version of the kernel code. See below for details. `dispatch.bringOutYourDead()`, aka "reap", triggers garbage collection inside a vat, and gives it a chance to drop imported c-list vrefs that are no longer referenced by anything inside the vat. Previously, each vat has a configurable parameter named `reapInterval`, which defaults to a kernel-wide `defaultReapInterval` (but can be set separately for each vat). This defaults to 1, mainly for unit testing, but real applications set it to something like 1000. This caused BOYD to happen once every 1000 deliveries, plus an extra BOYD just before we save an XS heap-state snapshot. This commit switches to a "dirt"-based BOYD scheduler, wherein we consider the vat to get more and more dirty as it does work, and eventually it reaches a `reapDirtThreshold` that triggers the BOYD (which resets the dirt counter). We continue to track `dirt.deliveries` as before, with the same defaults. But we add a new `dirt.gcKrefs` counter, which is incremented by the krefs we submit to the vat in GC deliveries. For example, calling `dispatch.dropImports([kref1, kref2])` would increase `dirt.gcKrefs` by two. The `reapDirtThreshold.gcKrefs` limit defaults to 20. For normal use patterns, this will trigger a BOYD after ten krefs have been dropped and retired. We choose this value to allow the #8928 slow vat termination process to trigger BOYD frequently enough to keep the BOYD cranks small: since these will be happening constantly (in the "background"), we don't want them to take more than 500ms or so. Given the current size of the large vats that #8928 seeks to terminate, 10 krefs seems like a reasonable limit. And of course we don't want to perform too many BOYDs, so `gcKrefs: 20` is about the smallest threshold we'd want to use. External APIs continue to accept `reapInterval`, and now also accept `reapGCKrefs`, and `neverReap` (a boolean which inhibits all BOYD, even new forms of dirt added in the future). * kernel config record * takes `config.defaultReapInterval` and `defaultReapGCKrefs` * takes `vat.NAME.creationOptions.reapInterval` and `.reapGCKrefs` and `.neverReap` * `controller.changeKernelOptions()` still takes `defaultReapInterval` but now also accepts `defaultReapGCKrefs` The APIs available to userspace code (through `vatAdminSvc`) are unchanged (partially due to upgrade/backwards-compatibility limitations), and continue to only support setting `reapInterval`. Internally, this just modifies `reapDirtThreshold.deliveries`. * `E(vatAdminSvc).createVat(bcap, { reapInterval })` * `E(adminNode).upgrade(bcap, { reapInterval })` * `E(adminNode).changeOptions({ reapInterval })` Internally, the kernel-wide state records `defaultReapDirtThreshold` instead of `defaultReapInterval`, and each vat records `.reapDirtThreshold` in their `vNN.options` key instead of `vNN.reapInterval`. The vat-level records override the kernel-wide values. The current dirt level is recorded in `vNN.reapDirt`. NOTE: deployed kernels require explicit state upgrade, with: ```js import { upgradeSwingset } from '@agoric/swingset-vat'; .. upgradeSwingset(kernelStorage); ``` This must be called after upgrading to the new kernel code/release, and before calling `buildVatController()`. It is safe to call on every reboot (it will only modify the swingstore when the kernel version has changed). If changes are made, the host application is responsible for commiting them, as well as recording any export-data updates (if the host configured the swingstore with an export-data callback). During this upgrade, the old `reapCountdown` value is used to initialize the vat's `reapDirt.deliveries` counter, so the upgrade shouldn't disrupt the existing schedule. Vats which used `reapInterval = 'never'` (eg comms) will get a `reapDirtThreshold.never = true`, so they continue to inhibit BOYD. Any per-vat settings that match the kernel-wide settings are removed, allowing the kernel values to take precedence (as well as changes to the kernel-wide values; i.e. the per-vat settings are not sticky). We do not track dirt when the corresponding threshold is 'never', or if `neverReap` is true, to avoid incrementing the comms dirt counters forever. This design leaves room for adding `.computrons` to the dirt record, as well as tracking a separate `snapshotDirt` counter (to trigger XS heap snapshots, ala #6786). We add `reapDirtThreshold.computrons`, but do not yet expose an API to set it. Future work includes: * upgrade vat-vat-admin to let userspace set `reapDirtThreshold` New tests were added to exercise the upgrade process, and other tests were updated to match the new internal initialization pattern. We now reset the dirt counter upon any BOYD, so this also happens to help with #8665 (doing a `reapAllVats()` resets the delivery counters, so future BOYDs will be delayed, which is what we want). But we should still change `controller.reapAllVats()` to avoid BOYDs on vats which haven't received any deliveries. closes #8980
makeKernelKeeper() now takes the expected version, and throws if it doesn't match. This replaces the version check in kernel.js start().
Any changes will be committed as part of the first block after reboot. This should be in-consensus, because all nodes are supposed to change kernel versions at the same time, and only during a chain-halting upgrade.
The swingstore export-data callback gives us export-data records, which must be written into IAVL by sending them over to the golang side with swingStoreExportCallback . However, that callback isn't ready right away, so if e.g. openSwingStore() were to invoke it, we might lose those records. Likewise saveOutsideState() gathers the chainSends just before calling commit, so if the callback were invoked during commit(), those records would be left for a subsequent block, which would break consensus if the node crashed before the next commit. This commit adds an `allowExportCallback` flag, to catch these two cases. It is enabled at the start of AG_COSMOS_INIT and BEGIN_BLOCK, and then disabled just before we flush the chain sends in saveOutsideState() (called during COMMIT_BLOCK). Note that swingstore is within its rights to call exportCallback during openSwingStore() or commit(), it just happens to not do so right now. If that changes under maintenance, this guard should turn a corruption bug into a crash bug refs #9655
1f7943e
to
6547c83
Compare
fix(swingset): use "dirt" to schedule vat reap/bringOutYourDead
NOTE: deployed kernels require a new
upgradeSwingset()
call upon(at least) first boot after upgrading to this version of the kernel
code.
dispatch.bringOutYourDead()
, aka "reap", triggers garbage collectioninside a vat, and gives it a chance to drop imported c-list vrefs that
are no longer referenced by anything inside the vat.
Previously, each vat has a configurable parameter named
reapInterval
, which defaults to a kernel-widedefaultReapInterval
(but can be set separately for each vat). Thisdefaults to 1, mainly for unit testing, but real applications set it
to something like 1000.
This caused BOYD to happen once every 1000 deliveries, plus an extra
BOYD just before we save an XS heap-state snapshot.
This commit switches to a "dirt"-based BOYD scheduler, wherein we
consider the vat to get more and more dirty as it does work, and
eventually it reaches a
reapDirtThreshold
that triggers theBOYD (which resets the dirt counter).
We continue to track
dirt.deliveries
as before, with the samedefaults. But we add a new
dirt.gcKrefs
counter, which isincremented by the krefs we submit to the vat in GC deliveries. For
example, calling
dispatch.dropImports([kref1, kref2])
would increasedirt.gcKrefs
by two.The
reapDirtThreshold.gcKrefs
limit defaults to 20. For normal usepatterns, this will trigger a BOYD after ten krefs have been dropped
and retired. We choose this value to allow the #8928 slow vat
termination process to trigger BOYD frequently enough to keep the BOYD
cranks small: since these will be happening constantly (in the
"background"), we don't want them to take more than 500ms or so. Given
the current size of the large vats that #8928 seeks to terminate, 10
krefs seems like a reasonable limit. And of course we don't want to
perform too many BOYDs, so
gcKrefs: 20
is about the smallestthreshold we'd want to use.
External APIs continue to accept
reapInterval
, and now also acceptreapGCKrefs
, andneverReap
(a boolean which inhibits all BOYD,even new forms of dirt added in the future).
config.defaultReapInterval
anddefaultReapGCKrefs
vat.NAME.creationOptions.reapInterval
and.reapGCKrefs
and
.neverReap
controller.changeKernelOptions()
still takesdefaultReapInterval
but now also accepts
defaultReapGCKrefs
The APIs available to userspace code (through
vatAdminSvc
) areunchanged (partially due to upgrade/backwards-compatibility
limitations), and continue to only support setting
reapInterval
.Internally, this just modifies
reapDirtThreshold.deliveries
.E(vatAdminSvc).createVat(bcap, { reapInterval })
E(adminNode).upgrade(bcap, { reapInterval })
E(adminNode).changeOptions({ reapInterval })
Internally, the kernel-wide state records
defaultReapDirtThreshold
instead of
defaultReapInterval
, and each vat records.reapDirtThreshold
in theirvNN.options
key instead ofvNN.reapInterval
. The vat-level records override the kernel-widevalues. The current dirt level is recorded in
vNN.reapDirt
.NOTE: deployed kernels require explicit state upgrade, with:
This must be called after upgrading to the new kernel code/release,
and before calling
buildVatController()
. It is safe to call on everyreboot (it will only modify the swingstore when the kernel version has
changed). If changes are made, the host application is responsible for
commiting them, as well as recording any export-data updates (if the
host configured the swingstore with an export-data callback).
During this upgrade, the old
reapCountdown
value is used toinitialize the vat's
reapDirt.deliveries
counter, so the upgradeshouldn't disrupt the existing schedule. Vats which used
reapInterval = 'never'
(eg comms) will get areapDirtThreshold.never = true
, sothey continue to inhibit BOYD. Any per-vat settings that match the
kernel-wide settings are removed, allowing the kernel values to take
precedence (as well as changes to the kernel-wide values; i.e. the
per-vat settings are not sticky).
We do not track dirt when the corresponding threshold is 'never', or
if
neverReap
is true, to avoid incrementing the comms dirt countersforever.
This design leaves room for adding
.computrons
to the dirt record,as well as tracking a separate
snapshotDirt
counter (to trigger XSheap snapshots, ala #6786). We add
reapDirtThreshold.computrons
, butdo not yet expose an API to set it.
Future work includes:
reapDirtThreshold
New tests were added to exercise the upgrade process, and other tests
were updated to match the new internal initialization pattern.
We now reset the dirt counter upon any BOYD, so this also happens to
help with #8665 (doing a
reapAllVats()
resets the delivery counters,so future BOYDs will be delayed, which is what we want). But we should
still change
controller.reapAllVats()
to avoid BOYDs on vats whichhaven't received any deliveries.
closes #8980