Skip to content

Commit

Permalink
feat(SwingSet): Split syscall processing into its own crank
Browse files Browse the repository at this point in the history
  • Loading branch information
mhofman committed Feb 22, 2022
1 parent 63b731b commit b9d3bf4
Show file tree
Hide file tree
Showing 7 changed files with 157 additions and 54 deletions.
81 changes: 63 additions & 18 deletions packages/SwingSet/src/kernel/kernel.js
Original file line number Diff line number Diff line change
Expand Up @@ -731,15 +731,8 @@ export default function buildKernel(

const gcMessages = ['dropExports', 'retireExports', 'retireImports'];

function processSyscallQueue() {
while (!kernelKeeper.isSyscallQueueEmpty()) {
const syscallMessage = kernelKeeper.getNextSyscallQueueMsg();
kernelKeeper.addToRunQueue(syscallMessage);
}
}

let processQueueRunning;
async function processQueueMessage(message) {
async function processDeliveryMessage(message) {
kdebug(`processQ ${JSON.stringify(message)}`);
kdebug(legibilizeMessage(message));
if (processQueueRunning) {
Expand Down Expand Up @@ -803,7 +796,7 @@ export default function buildKernel(
// delivered again on the next crank. If we don't want that, then
// we need to remove it again.
// eslint-disable-next-line no-use-before-define
getNextMessage();
getNextDeliveryMessage();
}
}
// state changes reflecting the termination must also survive, so
Expand Down Expand Up @@ -833,6 +826,38 @@ export default function buildKernel(
return harden(policyInput);
}

async function processSyscallMessage(message) {
kdebug(`processSyscallQ ${JSON.stringify(message)}`);
kdebug(legibilizeMessage(message));
if (processQueueRunning) {
console.error(`We're currently already running at`, processQueueRunning);
assert.fail(X`Kernel reentrancy is forbidden`);
}
kernelSlog.write({ type: 'crank-start', message });
/** @type { PolicyInput } */
const policyInput = ['none'];
try {
processQueueRunning = Error('here');

kernelKeeper.addToRunQueue(message);

kernelKeeper.processRefcounts();
kernelKeeper.saveStats();
const crankNum = kernelKeeper.getCrankNumber();
kernelKeeper.incrementCrankNumber();
const { crankhash, activityhash } = kernelKeeper.commitCrank();
kernelSlog.write({
type: 'crank-finish',
crankNum,
crankhash,
activityhash,
});
} finally {
processQueueRunning = undefined;
}
return harden(policyInput);
}

const gcTools = harden({
WeakRef,
FinalizationRegistry,
Expand Down Expand Up @@ -1118,7 +1143,7 @@ export default function buildKernel(
kernelKeeper.loadStats();
}

function getNextMessage() {
function getNextDeliveryMessage() {
const gcMessage = processNextGCAction(kernelKeeper);
if (gcMessage) {
return gcMessage;
Expand All @@ -1134,18 +1159,39 @@ export default function buildKernel(
return undefined;
}

function getNextSyscallMessage() {
if (!kernelKeeper.isSyscallQueueEmpty()) {
return kernelKeeper.getNextSyscallQueueMsg();
}
return undefined;
}

function maybeProcessNextMessage() {
/** @type {Promise<PolicyInput> | undefined} */
let resultPromise;
let message = getNextSyscallMessage();
if (message) {
resultPromise = processSyscallMessage(message);
} else {
message = getNextDeliveryMessage();
if (message) {
resultPromise = processDeliveryMessage(message);
}
}
return { resultPromise };
}

async function step() {
if (kernelPanic) {
throw kernelPanic;
}
if (!started) {
throw new Error('must do kernel.start() before step()');
}
processSyscallQueue();
const { resultPromise } = maybeProcessNextMessage();
// process a single message
const message = getNextMessage();
if (message) {
await processQueueMessage(message);
if (resultPromise) {
await resultPromise;
if (kernelPanic) {
throw kernelPanic;
}
Expand Down Expand Up @@ -1174,15 +1220,14 @@ export default function buildKernel(
}
let count = 0;
for (;;) {
processSyscallQueue();
const message = getNextMessage();
if (!message) {
const { resultPromise } = maybeProcessNextMessage();
if (!resultPromise) {
break;
}
count += 1;
/** @type { PolicyInput } */
// eslint-disable-next-line no-await-in-loop
const policyInput = await processQueueMessage(message);
const policyInput = await resultPromise;
if (kernelPanic) {
throw kernelPanic;
}
Expand Down
30 changes: 21 additions & 9 deletions packages/SwingSet/test/devices/test-devices.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,8 @@ test.serial('d1', async t => {
await c.run();

c.queueToVatRoot('bootstrap', 'step1', capargs([]));
await c.step();
await c.step(); // syscall
await c.step(); // deliver
t.deepEqual(c.dump().log, [
'callNow',
'invoke 1 2',
Expand Down Expand Up @@ -155,10 +156,13 @@ async function test2(t, mode) {
const c = await makeSwingsetController(hostStorage, {});
for (let i = 0; i < 5; i += 1) {
// eslint-disable-next-line no-await-in-loop
await c.step(); // vat start
await c.step(); // vat start syscall
// eslint-disable-next-line no-await-in-loop
await c.step(); // vat start deliver
}
c.pinVatRoot('bootstrap');
await c.step();
await c.step(); // syscall
await c.step(); // deliver
if (mode === '1') {
t.deepEqual(c.dump().log, ['calling d2.method1', 'method1 hello', 'done']);
} else if (mode === '2') {
Expand Down Expand Up @@ -187,15 +191,17 @@ async function test2(t, mode) {
]);
} else if (mode === '5') {
t.deepEqual(c.dump().log, ['calling v2.method5', 'called']);
await c.step();
await c.step(); // syscall
await c.step(); // deliver
t.deepEqual(c.dump().log, [
'calling v2.method5',
'called',
'left5',
'method5 hello',
'left5 did d2.method5, got ok',
]);
await c.step();
await c.step(); // syscall
await c.step(); // deliver
t.deepEqual(c.dump().log, [
'calling v2.method5',
'called',
Expand Down Expand Up @@ -346,11 +352,14 @@ test.serial('liveslots throws when D() gets promise', async t => {

for (let i = 0; i < 4; i += 1) {
// eslint-disable-next-line no-await-in-loop
await c.step(); // vat start
await c.step(); // vat start syscall
// eslint-disable-next-line no-await-in-loop
await c.step(); // vat start deliver
// eslint-disable-next-line no-await-in-loop
await c.step(); // bring out your dead
}
await c.step();
await c.step(); // syscall
await c.step(); // deliver
// When liveslots catches an attempt to send a promise into D(), it throws
// a regular error, which the vat can catch.
t.deepEqual(c.dump().log, ['sending Promise', 'good: callNow failed']);
Expand Down Expand Up @@ -388,11 +397,14 @@ test.serial('syscall.callNow(promise) is vat-fatal', async t => {
const c = await makeSwingsetController(hostStorage, {});
for (let i = 0; i < 3; i += 1) {
// eslint-disable-next-line no-await-in-loop
await c.step(); // vat start
await c.step(); // vat start syscall
// eslint-disable-next-line no-await-in-loop
await c.step(); // vat start deliver
// eslint-disable-next-line no-await-in-loop
await c.step(); // bring out your dead
}
await c.step(); // bootstrap, which will fail
await c.step(); // syscall
await c.step(); // deliver bootstrap, which will fail
// if the kernel paniced, that c.step() will reject, and the await will throw
t.deepEqual(c.dump().log, ['sending Promise', 'good: callNow failed']);
// now check that the vat was terminated: this should throw an exception
Expand Down
2 changes: 2 additions & 0 deletions packages/SwingSet/test/gc/test-gc-vat.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,11 @@ async function dropPresence(t, dropExport) {
c.queueToVatRoot('bootstrap', 'one', capargs([]));
if (dropExport) {
c.queueToVatRoot('bootstrap', 'drop', capargs([]));
await c.step(); // syscall
await c.step(); // message
await c.step(); // reap
}
await c.step(); // syscall
await c.step(); // message
await c.step(); // reap

Expand Down
14 changes: 7 additions & 7 deletions packages/SwingSet/test/run-policy/test-run-policy.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,17 +67,17 @@ async function testCranks(t, mode) {
let more;

if (mode === 'messages' || mode === 'resolutions') {
more = await c.run(crankCounter(7, 0, true));
more = await c.run(crankCounter(15, 0, true));
t.truthy(more, 'vat was supposed to run forever');
t.is(elapsedCranks(), 7);
t.is(elapsedCranks(), 15);

more = await c.run(crankCounter(1, 0, true));
more = await c.run(crankCounter(2, 0, true));
t.truthy(more, 'vat was supposed to run forever');
t.is(elapsedCranks(), 1);
t.is(elapsedCranks(), 2);

more = await c.run(crankCounter(8, 0, true));
more = await c.run(crankCounter(16, 0, true));
t.truthy(more, 'vat was supposed to run forever');
t.is(elapsedCranks(), 8);
t.is(elapsedCranks(), 16);
} else if (mode === 'computrons') {
// the doMessage cycle has four steps:
// 1: normal delivery (122k-134k computrons)
Expand All @@ -91,7 +91,7 @@ async function testCranks(t, mode) {
// setting a threshold of 4M, we should finish c.run() just after that
// extra-compute step.
await c.run(computronCounter(4_000_000n));
t.is(elapsedCranks(), 17);
t.is(elapsedCranks(), 35);
const ckey = `${rightID}.vs.vvs.seqnum`;
const seqnum = parseInt(hostStorage.kvStore.get(ckey), 10);
t.is(seqnum, 5);
Expand Down
22 changes: 16 additions & 6 deletions packages/SwingSet/test/test-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,9 @@ test('bootstrap', async t => {
const c = await buildVatController(config);
for (let i = 0; i < 5; i += 1) {
// eslint-disable-next-line no-await-in-loop
await c.step(); // vat starts
await c.step(); // vat start syscall
// eslint-disable-next-line no-await-in-loop
await c.step(); // vat start deliver
}
t.deepEqual(c.dump().log, ['bootstrap called']);
});
Expand All @@ -133,7 +135,9 @@ test('XS bootstrap', async t => {
const c = await buildVatController(config, [], { hostStorage });
for (let i = 0; i < 5; i += 1) {
// eslint-disable-next-line no-await-in-loop
await c.step(); // vat starts
await c.step(); // vat start syscall
// eslint-disable-next-line no-await-in-loop
await c.step(); // vat start deliver
}
t.deepEqual(c.dump().log, ['bootstrap called']);
t.is(
Expand Down Expand Up @@ -170,7 +174,9 @@ test('static vats are unmetered on XS', async t => {
);
for (let i = 0; i < 5; i += 1) {
// eslint-disable-next-line no-await-in-loop
await c.step(); // vat starts
await c.step(); // vat start syscall
// eslint-disable-next-line no-await-in-loop
await c.step(); // vat start deliver
}
t.deepEqual(c.dump().log, ['bootstrap called']);
t.deepEqual(limited, [false, false, false, false]);
Expand Down Expand Up @@ -265,9 +271,9 @@ test.serial('bootstrap export', async t => {
},
]);

// this test was designed before GC, and wants to single-step the kernel,
// but doesn't care about the GC action steps, so we use this helper
// function
// this test was designed before GC and syscall queues, and wants to
// single-step the kernel, but doesn't care about the GC action steps,
// or the temporary syscall queue, so we use this helper function
async function stepGC() {
while (c.dump().gcActions.length) {
// eslint-disable-next-line no-await-in-loop
Expand All @@ -277,6 +283,10 @@ test.serial('bootstrap export', async t => {
// eslint-disable-next-line no-await-in-loop
await c.step();
}
while (c.dump().syscallQueue.length) {
// eslint-disable-next-line no-await-in-loop
await c.step();
}
await c.step(); // the non- GC action
}

Expand Down
Loading

0 comments on commit b9d3bf4

Please sign in to comment.