Skip to content

Commit

Permalink
feat(swingset): add Meters to kernel state
Browse files Browse the repository at this point in the history
This introduces the "meterID" and the "meter record": a pair of
Nats (`remaining` and `threshold`). kernelKeeper functions are added to
create and manipulate them, and test-state.js is enhanced to exercise these.

refs #3308
  • Loading branch information
warner committed Jul 25, 2021
1 parent 5b95638 commit 03f148b
Show file tree
Hide file tree
Showing 2 changed files with 139 additions and 0 deletions.
95 changes: 95 additions & 0 deletions packages/SwingSet/src/kernel/state/kernelKeeper.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const enableKernelGC = true;
// device.names = JSON([names..])
// device.name.$NAME = $deviceID = d$NN
// device.nextID = $NN
// meter.nextID = $NN // used to make m$NN

// kernelBundle = JSON(bundle)
// bundle.$NAME = JSON(bundle)
Expand All @@ -54,6 +55,9 @@ const enableKernelGC = true;
// v$NN.vs.$key = string
// v$NN.lastSnapshot = JSON({ snapshotID, startPos })

// m$NN.remaining = $NN // remaining capacity (in computrons) or 'unlimited'
// m$NN.threshold = $NN // notify when .remaining first drops below this

// d$NN.o.nextID = $NN
// d$NN.c.$kernelSlot = $deviceSlot = o-$NN/d+$NN/d-$NN
// d$NN.c.$deviceSlot = $kernelSlot = ko$NN/kd$NN
Expand Down Expand Up @@ -90,6 +94,12 @@ export function commaSplit(s) {
return s.split(',');
}

function insistMeterID(m) {
assert.typeof(m, 'string');
assert.equal(m[0], 'm');
Nat(BigInt(m.slice(1)));
}

// we use different starting index values for the various vNN/koNN/kdNN/kpNN
// slots, to reduce confusing overlap when looking at debug messages (e.g.
// seeing both kp1 and ko1, which are completely unrelated despite having the
Expand All @@ -106,6 +116,7 @@ const FIRST_OBJECT_ID = 20n;
const FIRST_DEVNODE_ID = 30n;
const FIRST_PROMISE_ID = 40n;
const FIRST_CRANK_NUMBER = 0n;
const FIRST_METER_ID = 1n;

/**
* @param {KVStorePlus} kvStore
Expand Down Expand Up @@ -228,6 +239,7 @@ export default function makeKernelKeeper(
kvStore.set('ko.nextID', `${FIRST_OBJECT_ID}`);
kvStore.set('kd.nextID', `${FIRST_DEVNODE_ID}`);
kvStore.set('kp.nextID', `${FIRST_PROMISE_ID}`);
kvStore.set('meter.nextID', `${FIRST_METER_ID}`);
kvStore.set('gcActions', '[]');
kvStore.set('runQueue', JSON.stringify([]));
kvStore.set('crankNumber', `${FIRST_CRANK_NUMBER}`);
Expand Down Expand Up @@ -706,6 +718,82 @@ export default function makeKernelKeeper(
return msg;
}

function allocateMeter(remaining, threshold) {
if (remaining !== 'unlimited') {
assert.typeof(remaining, 'bigint');
Nat(remaining);
}
assert.typeof(threshold, 'bigint');
Nat(threshold);
const nextID = Nat(BigInt(getRequired('meter.nextID')));
kvStore.set('meter.nextID', `${nextID + 1n}`);
const meterID = `m${nextID}`;
kvStore.set(`${meterID}.remaining`, `${remaining}`);
kvStore.set(`${meterID}.threshold`, `${threshold}`);
return meterID;
}

function addMeterRemaining(meterID, delta) {
insistMeterID(meterID);
assert.typeof(delta, 'bigint');
Nat(delta);
/** @type { bigint | string } */
let remaining = getRequired(`${meterID}.remaining`);
if (remaining !== 'unlimited') {
remaining = Nat(BigInt(remaining));
kvStore.set(`${meterID}.remaining`, `${remaining + delta}`);
}
}

function setMeterThreshold(meterID, threshold) {
insistMeterID(meterID);
assert.typeof(threshold, 'bigint');
Nat(threshold);
kvStore.set(`${meterID}.threshold`, `${threshold}`);
}

function getMeter(meterID) {
insistMeterID(meterID);
/** @type { bigint | string } */
let remaining = getRequired(`${meterID}.remaining`);
if (remaining !== 'unlimited') {
remaining = BigInt(remaining);
}
const threshold = BigInt(getRequired(`${meterID}.threshold`));
return harden({ remaining, threshold });
}

function deductMeter(meterID, spent) {
insistMeterID(meterID);
assert.typeof(spent, 'bigint');
Nat(spent);
let underflow = false;
let notify = false;
/** @type { bigint | string } */
let oldRemaining = getRequired(`${meterID}.remaining`);
if (oldRemaining !== 'unlimited') {
oldRemaining = BigInt(oldRemaining);
const threshold = BigInt(getRequired(`${meterID}.threshold`));
let remaining = oldRemaining - spent;
if (remaining < 0n) {
underflow = true;
remaining = 0n;
}
if (remaining < threshold && oldRemaining >= threshold) {
// only notify once per crossing
notify = true;
}
kvStore.set(`${meterID}.remaining`, `${Nat(remaining)}`);
}
return harden({ underflow, notify });
}

function deleteMeter(meterID) {
insistMeterID(meterID);
kvStore.delete(`${meterID}.remaining`);
kvStore.delete(`${meterID}.threshold`);
}

function hasVatWithName(name) {
return kvStore.has(`vat.name.${name}`);
}
Expand Down Expand Up @@ -1205,6 +1293,13 @@ export default function makeKernelKeeper(
getRunQueueLength,
getNextMsg,

allocateMeter,
addMeterRemaining,
setMeterThreshold,
getMeter,
deductMeter,
deleteMeter,

hasVatWithName,
getVatIDForName,
allocateVatIDForNameIfNeeded,
Expand Down
44 changes: 44 additions & 0 deletions packages/SwingSet/test/test-state.js
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ test('kernel state', async t => {
['kd.nextID', '30'],
['kp.nextID', '40'],
['kernel.defaultManagerType', 'local'],
['meter.nextID', '1'],
]);
});

Expand Down Expand Up @@ -321,6 +322,7 @@ test('kernelKeeper vat names', async t => {
['vat.name.vatname5', 'v1'],
['vat.name.Frank', 'v2'],
['kernel.defaultManagerType', 'local'],
['meter.nextID', '1'],
]);
t.deepEqual(k.getStaticVats(), [
['Frank', 'v2'],
Expand Down Expand Up @@ -369,6 +371,7 @@ test('kernelKeeper device names', async t => {
['device.name.devicename5', 'd7'],
['device.name.Frank', 'd8'],
['kernel.defaultManagerType', 'local'],
['meter.nextID', '1'],
]);
t.deepEqual(k.getDevices(), [
['Frank', 'd8'],
Expand Down Expand Up @@ -550,6 +553,7 @@ test('kernelKeeper promises', async t => {
[`${ko}.owner`, 'v1'],
[`${ko}.refCount`, '1,1'],
['kernel.defaultManagerType', 'local'],
['meter.nextID', '1'],
]);
});

Expand Down Expand Up @@ -663,3 +667,43 @@ test('XS vatKeeper defaultManagerType', async t => {
k.createStartingKernelState('xs-worker');
t.is(k.getDefaultManagerType(), 'xs-worker');
});

test('meters', async t => {
const { kvStore, streamStore } = buildKeeperStorageInMemory();
const k = makeKernelKeeper(kvStore, streamStore);
k.createStartingKernelState('local');
const m1 = k.allocateMeter(100n, 10n);
const m2 = k.allocateMeter(200n, 150n);
t.not(m1, m2);
k.deleteMeter(m2);
t.deepEqual(k.getMeter(m1), { remaining: 100n, threshold: 10n });
t.deepEqual(k.deductMeter(m1, 10n), { underflow: false, notify: false });
t.deepEqual(k.deductMeter(m1, 10n), { underflow: false, notify: false });
t.deepEqual(k.getMeter(m1), { remaining: 80n, threshold: 10n });
t.deepEqual(k.deductMeter(m1, 70n), { underflow: false, notify: false });
t.deepEqual(k.deductMeter(m1, 1n), { underflow: false, notify: true });
t.deepEqual(k.getMeter(m1), { remaining: 9n, threshold: 10n });
t.deepEqual(k.deductMeter(m1, 1n), { underflow: false, notify: false });
t.deepEqual(k.deductMeter(m1, 9n), { underflow: true, notify: false });
t.deepEqual(k.getMeter(m1), { remaining: 0n, threshold: 10n });
t.deepEqual(k.deductMeter(m1, 2n), { underflow: true, notify: false });
t.deepEqual(k.getMeter(m1), { remaining: 0n, threshold: 10n });
k.addMeterRemaining(m1, 50n);
t.deepEqual(k.getMeter(m1), { remaining: 50n, threshold: 10n });
t.deepEqual(k.deductMeter(m1, 30n), { underflow: false, notify: false });
t.deepEqual(k.deductMeter(m1, 25n), { underflow: true, notify: true });
t.deepEqual(k.getMeter(m1), { remaining: 0n, threshold: 10n });

k.addMeterRemaining(m1, 50n);
k.setMeterThreshold(m1, 40n);
t.deepEqual(k.getMeter(m1), { remaining: 50n, threshold: 40n });
t.deepEqual(k.deductMeter(m1, 10n), { underflow: false, notify: false });
t.deepEqual(k.deductMeter(m1, 10n), { underflow: false, notify: true });
t.deepEqual(k.getMeter(m1), { remaining: 30n, threshold: 40n });

const m3 = k.allocateMeter('unlimited', 10n);
k.setMeterThreshold(m3, 5n);
t.deepEqual(k.getMeter(m3), { remaining: 'unlimited', threshold: 5n });
t.deepEqual(k.deductMeter(m3, 1000n), { underflow: false, notify: false });
t.deepEqual(k.getMeter(m3), { remaining: 'unlimited', threshold: 5n });
});

0 comments on commit 03f148b

Please sign in to comment.