Skip to content

Commit

Permalink
feat(swingset): add controller.resetAllMeters()
Browse files Browse the repository at this point in the history
Add a feature which allows the host application to reset all
non-unlimited Meters to a new absolute value.

refs #7938
  • Loading branch information
warner committed Aug 29, 2023
1 parent 20cbc3a commit 7550c6f
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 6 deletions.
21 changes: 19 additions & 2 deletions packages/SwingSet/docs/metering.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,23 @@ const control = await E(vatAdmin).createVat(bundle, { meter });

## runPolicy

TODO: The host application can limit the number of cranks processed in a single call to `controller.run()` by providing a `runPolicy` object. This policy object is informed about each crank and the number of computrons it consumed. By comparing the cumulative computrons against an experimentally (and externally) determined threshold, the `runLimit` object can tell the kernel to stop processing before the run-queue is drained. For a busy kernel, with an ever-increasing amount of work to do, this can limit the size of a commitment domain (e.g. the "block" in a blockchain / consensus machine).
The host application can limit the number of cranks processed in a single call to `controller.run()` by providing a `runPolicy` object. This policy object is informed about each crank and the number of computrons it consumed. By comparing the cumulative computrons against an experimentally (and externally) determined threshold, the `runPolicy` object can tell the kernel to exit the `run()` loop before the run-queue is drained, leaving some work for the future.

This is a work in process, please follow issue #3460 for progress.
For a busy kernel, with an ever-increasing amount of work to do, this can limit the size of a commitment domain (e.g. the "block" in a blockchain / consensus machine).

## External Refill

In addition to refilling a `Meter` from *within* a Vat (using `E(meter).addRemaining(99n)`), the kernel offers an *external* API to refill all existing Meter objects. This may be useful for runtime-limitation approaches where the host application is willing to reset the meters on a periodic basis, but the vats holding the meters are not prepared to assist.

Calling `controller.resetAllMeters(remaining)` will immediately change all existing Meters to the provided `remaining` value. It takes an absolute number of computrons, as a BigInt (unlike `E(meter).addRemaining()`, which takes a positive delta). This change in value will not trigger the `setThreshold` notification, even if `remaining` is actually lower than the old value.

Only regular `Meter` objects, existing at the time of the call, will have their value changed. Meters created after the call will get whatever `remaining` value their `E(vatAdmin).createMeter()` call provides. `UnlimitedMeter` objects will remain unlimited.

`resetAllMeters` should only be called while the kernel is idle: not during a `controller.step()` or `controller.run()`. Otherwise, the exact timing of when vats experience the meter's new value will be hard to predict.

`resetAllMeters` causes the kernel to write changes to the swing-store DB, which should eventually be committed. Host applications should generally use a sequence like:

* `controller.resetAllMeters()`
* call device inputs
* `await controller.run(runPolicy)`
* `await swingstore.hostStorage.commit()`
4 changes: 4 additions & 0 deletions packages/SwingSet/src/controller/controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,10 @@ export async function makeSwingsetController(
kernel.changeKernelOptions(options);
},

resetAllMeters(remaining) {
kernel.resetAllMeters(remaining);
},

getStats() {
return defensiveCopy(kernel.getStats());
},
Expand Down
5 changes: 5 additions & 0 deletions packages/SwingSet/src/kernel/kernel.js
Original file line number Diff line number Diff line change
Expand Up @@ -1871,6 +1871,10 @@ export default function buildKernel(
}
}

function resetAllMeters(remaining) {
kernelKeeper.resetAllMeters(remaining);
}

function kpRegisterInterest(kpid) {
kernelKeeper.incrementRefCount(kpid, 'external');
}
Expand Down Expand Up @@ -1934,6 +1938,7 @@ export default function buildKernel(
run,
shutdown,
changeKernelOptions,
resetAllMeters,

// the rest are for testing and debugging

Expand Down
16 changes: 16 additions & 0 deletions packages/SwingSet/src/kernel/state/kernelKeeper.js
Original file line number Diff line number Diff line change
Expand Up @@ -1063,6 +1063,21 @@ export default function makeKernelKeeper(kernelStorage, kernelSlog) {
kvStore.delete(`${meterID}.threshold`);
}

function resetAllMeters(remaining) {
remaining = Nat(BigInt(remaining));
const keyRE = /^m\d+\.remaining$/;
for (const key of enumeratePrefixedKeys(kvStore, 'm')) {
// meter data is stored in m$NN keys, however there are other keys
// that start with 'm' too
if (keyRE.test(key)) {
// UnlimitedMeters remain unchanged
if (getRequired(key) !== 'unlimited') {
kvStore.set(key, `${remaining}`);
}
}
}
}

function hasVatWithName(name) {
return kvStore.has(`vat.name.${name}`);
}
Expand Down Expand Up @@ -1604,6 +1619,7 @@ export default function makeKernelKeeper(kernelStorage, kernelSlog) {
checkMeter,
deductMeter,
deleteMeter,
resetAllMeters,

hasVatWithName,
getVatIDForName,
Expand Down
18 changes: 14 additions & 4 deletions packages/SwingSet/test/metering/test-dynamic-vat-metered.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { restartVatAdminVat } from '../util.js';
import { kunser, krefOf } from '../../src/lib/kmarshal.js';
import { enumeratePrefixedKeys } from '../../src/kernel/state/storageHelper.js';

const LOTS = 100_000_000n; // 100Mc

async function prepare() {
const kernelBundles = await buildKernelBundles();
// we'll give this bundle to the loader vat, which will use it to create a
Expand Down Expand Up @@ -80,6 +82,10 @@ async function meterObjectsTest(t, doVatAdminRestarts) {
await doMeter('setMeterThreshold', 7n);
await vaRestart();
t.deepEqual(await getMeter(), { remaining: 18n, threshold: 7n });

// resetAllMeters() will update it
c.resetAllMeters(99n);
t.deepEqual(await getMeter(), { remaining: 99n, threshold: 7n });
}

test('meter objects', async t => {
Expand Down Expand Up @@ -293,12 +299,11 @@ test('meter decrements', async t => {
// times, but this is sensitive to SES and other libraries, so we
// try to be tolerant of variation over time.

const lots = 100_000_000n; // TODO 100m
const t0 = await createMeteredVat(c, t, dynamicVatBundle, lots, 0n);
const t0 = await createMeteredVat(c, t, dynamicVatBundle, LOTS, 0n);
let remaining0 = await t0.getMeter();
const consumedByStartVat = lots - remaining0;
const consumedByStartVat = LOTS - remaining0;
console.log(`-- consumedByStartVat`, consumedByStartVat);
t.not(remaining0, lots);
t.not(remaining0, LOTS);
await t0.consume(true);
const remaining1 = await t0.getMeter();
const firstConsume = remaining0 - remaining1;
Expand Down Expand Up @@ -440,6 +445,11 @@ async function unlimitedMeterTest(t, doVatAdminRestarts) {
t.is(remaining, 'unlimited');
await vaRestart();

// resetAllMeters() will not change an UnlimitedMeter
c.resetAllMeters(LOTS);
remaining = await getMeter();
t.is(remaining, 'unlimited');

// but each crank is still limited, so an infinite loop will kill the vat
const kp4 = c.queueToVatRoot('bootstrap', 'explode', ['compute']);
await c.run();
Expand Down

0 comments on commit 7550c6f

Please sign in to comment.