Skip to content

Commit

Permalink
Merge pull request #4593 from Agoric/mhofman/4542-syscall-refactor
Browse files Browse the repository at this point in the history
SwingSet: Consolidate kernel run queue syscalls
  • Loading branch information
mergify[bot] authored Feb 18, 2022
2 parents 0cb192c + a03e508 commit eae0201
Show file tree
Hide file tree
Showing 6 changed files with 161 additions and 148 deletions.
8 changes: 5 additions & 3 deletions packages/SwingSet/src/kernel/initializeKernel.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import { insistVatID } from './id.js';
import { makeVatSlot } from '../parseVatSlots.js';
import { insistStorageAPI } from '../storageAPI.js';
import makeKernelKeeper from './state/kernelKeeper.js';
import { exportRootObject, doQueueToKref } from './kernel.js';
import { exportRootObject } from './kernel.js';
import { makeKernelQueueHandler } from './kernelQueue.js';

function makeVatRootObjectSlot() {
return makeVatSlot('object', true, 0);
Expand Down Expand Up @@ -182,6 +183,8 @@ export function initializeKernel(config, hostStorage, verbose = false) {
throw Error('bootstrap got unexpected pass-by-presence');
}

const { queueToKref } = makeKernelQueueHandler({ kernelKeeper });

const m = makeMarshal(convertValToSlot, undefined, {
marshalName: 'kernel:bootstrap',
// TODO Temporary hack.
Expand All @@ -191,8 +194,7 @@ export function initializeKernel(config, hostStorage, verbose = false) {
const args = harden([vatObj0s, deviceObj0s]);
// doQueueToKref() takes kernel-refs (ko+NN, kd+NN) in s.slots
const rootKref = exportRootObject(kernelKeeper, bootstrapVatID);
const resultKpid = doQueueToKref(
kernelKeeper,
const resultKpid = queueToKref(
rootKref,
'bootstrap',
m.serialize(args),
Expand Down
120 changes: 15 additions & 105 deletions packages/SwingSet/src/kernel/kernel.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import { parseVatSlot } from '../parseVatSlots.js';
import { insistCapData } from '../capdata.js';
import { insistMessage, insistVatDeliveryResult } from '../message.js';
import { insistDeviceID, insistVatID } from './id.js';
import { makeKernelSyscallHandler, doSend } from './kernelSyscall.js';
import { makeKernelQueueHandler } from './kernelQueue.js';
import { makeKernelSyscallHandler } from './kernelSyscall.js';
import { makeSlogger, makeDummySlogger } from './slogger.js';
import { makeDummyMeterControl } from './dummyMeterControl.js';
import { getKpidsToRetire } from './cleanup.js';
Expand Down Expand Up @@ -72,42 +73,6 @@ export function doAddExport(kernelKeeper, fromVatID, vref) {
return kref;
}

/**
* Enqueue a message to some kernel object, as if the message had been sent
* by some other vat. This requires a kref as a target.
*
* @param {*} kernelKeeper Kernel keeper managing persistent kernel state
* @param {string} kref Target of the message
* @param {string} method The message verb
* @param {*} args The message arguments
* @param {string} policy How the kernel should handle an eventual resolution
* or rejection of the message's result promise. Should be one of
* 'sendOnly' (don't even create a result promise), 'ignore' (do nothing),
* 'logAlways' (log the resolution or rejection), 'logFailure' (log only
* rejections), or 'panic' (panic the kernel upon a rejection).
*
* @returns {string?} the kpid of the sent message's result promise
*/
export function doQueueToKref(
kernelKeeper,
kref,
method,
args,
policy = 'ignore',
) {
// queue a message on the end of the queue, with 'absolute' krefs.
// Use 'step' or 'run' to execute it
insistCapData(args);
args.slots.forEach(s => parseKernelSlot(s));
let resultKPID;
if (policy !== 'none') {
resultKPID = kernelKeeper.addKernelPromise(policy);
}
const msg = harden({ method, args, result: resultKPID });
doSend(kernelKeeper, kref, msg);
return resultKPID;
}

export default function buildKernel(
kernelEndowments,
deviceEndowments,
Expand Down Expand Up @@ -248,17 +213,6 @@ export default function buildKernel(
return doAddExport(kernelKeeper, fromVatID, vatSlot);
}

const kernelSyscallHandler = makeKernelSyscallHandler({
kernelKeeper,
ephemeral,
// eslint-disable-next-line no-use-before-define
notify,
// eslint-disable-next-line no-use-before-define
doResolve,
// eslint-disable-next-line no-use-before-define
setTerminationTrigger,
});

// If `kernelPanic` is set to non-null, vat execution code will throw it as an
// error at the first opportunity
let kernelPanic = null;
Expand All @@ -269,61 +223,8 @@ export default function buildKernel(
kernelPanic = err || new Error(`kernel panic ${problem}`);
}

/**
* Enqueue a message to some kernel object, as if the message had been sent
* by some other vat.
*
* @param {string} kref Target of the message
* @param {string} method The message verb
* @param {*} args The message arguments
* @param {ResolutionPolicy=} policy How the kernel should handle an eventual
* resolution or rejection of the message's result promise. Should be
* one of 'sendOnly' (don't even create a result promise), 'ignore' (do
* nothing), 'logAlways' (log the resolution or rejection), 'logFailure'
* (log only rejections), or 'panic' (panic the kernel upon a
* rejection).
* @returns {string?} the kpid of the sent message's result promise, if any
*/
function queueToKref(kref, method, args, policy = 'ignore') {
return doQueueToKref(kernelKeeper, kref, method, args, policy);
}

function notify(vatID, kpid) {
const m = harden({ type: 'notify', vatID, kpid });
kernelKeeper.incrementRefCount(kpid, `enq|notify`);
kernelKeeper.addToRunQueue(m);
}

function doResolve(vatID, resolutions) {
if (vatID) {
insistVatID(vatID);
}
for (const resolution of resolutions) {
const [kpid, rejected, data] = resolution;
insistKernelType('promise', kpid);
insistCapData(data);
const p = kernelKeeper.getResolveablePromise(kpid, vatID);
const { subscribers } = p;
for (const subscriber of subscribers) {
if (subscriber !== vatID) {
notify(subscriber, kpid);
}
}
kernelKeeper.resolveKernelPromise(kpid, rejected, data);
const tag = rejected ? 'rejected' : 'fulfilled';
if (p.policy === 'logAlways' || (rejected && p.policy === 'logFailure')) {
console.log(
`${kpid}.policy ${p.policy}: ${tag} ${JSON.stringify(data)}`,
);
} else if (rejected && p.policy === 'panic') {
panic(`${kpid}.policy panic: ${tag} ${JSON.stringify(data)}`);
}
}
}

function resolveToError(kpid, errorData, expectedDecider) {
doResolve(expectedDecider, [[kpid, true, errorData]]);
}
const { doSend, doSubscribe, doResolve, resolveToError, queueToKref } =
makeKernelQueueHandler({ kernelKeeper, panic });

/**
* Terminate a vat; that is: delete vat DB state,
Expand Down Expand Up @@ -413,6 +314,15 @@ export default function buildKernel(
}
}

const kernelSyscallHandler = makeKernelSyscallHandler({
kernelKeeper,
ephemeral,
doSend,
doSubscribe,
doResolve,
setTerminationTrigger,
});

/**
* Perform one delivery to a vat.
*
Expand Down Expand Up @@ -918,7 +828,7 @@ export default function buildKernel(
if (ksc) {
try {
// this can throw if kernel is buggy
kres = kernelSyscallHandler.doKernelSyscall(ksc);
kres = kernelSyscallHandler(ksc);

// kres is a KernelResult: ['ok', value] or ['error', problem],
// where 'error' means we want the calling vat's syscall() to
Expand Down Expand Up @@ -1094,7 +1004,7 @@ export default function buildKernel(
function deviceSyscallHandler(deviceSyscallObject) {
const ksc =
translators.deviceSyscallToKernelSyscall(deviceSyscallObject);
const kres = kernelSyscallHandler.doKernelSyscall(ksc);
const kres = kernelSyscallHandler(ksc);
const dres = translators.kernelResultToDeviceResult(ksc[0], kres);
assert.equal(dres[0], 'ok');
return dres[1];
Expand Down
124 changes: 124 additions & 0 deletions packages/SwingSet/src/kernel/kernelQueue.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// @ts-check
import { insistKernelType, parseKernelSlot } from './parseKernelSlots.js';
import { insistCapData } from '../capdata.js';
import { insistMessage } from '../message.js';
import { insistVatID } from './id.js';

/**
* @param {Object} tools
* @param {*} tools.kernelKeeper Kernel keeper managing persistent kernel state
* @param {(problem: unknown, err?: Error) => void } [tools.panic]
*/
export function makeKernelQueueHandler(tools) {
const {
kernelKeeper,
panic = (problem, err) => {
throw err || new Error(`kernel panic ${problem}`);
},
} = tools;

function notify(vatID, kpid) {
const m = harden({ type: 'notify', vatID, kpid });
kernelKeeper.incrementRefCount(kpid, `enq|notify`);
kernelKeeper.addToRunQueue(m);
}

function doSubscribe(vatID, kpid) {
insistVatID(vatID);
const p = kernelKeeper.getKernelPromise(kpid);
if (p.state === 'unresolved') {
kernelKeeper.addSubscriberToPromise(kpid, vatID);
} else {
// otherwise it's already resolved, you probably want to know how
notify(vatID, kpid);
}
}

function doResolve(vatID, resolutions) {
if (vatID) {
insistVatID(vatID);
}
for (const resolution of resolutions) {
const [kpid, rejected, data] = resolution;
insistKernelType('promise', kpid);
insistCapData(data);
const p = kernelKeeper.getResolveablePromise(kpid, vatID);
const { subscribers } = p;
for (const subscriber of subscribers) {
if (subscriber !== vatID) {
notify(subscriber, kpid);
}
}
kernelKeeper.resolveKernelPromise(kpid, rejected, data);
const tag = rejected ? 'rejected' : 'fulfilled';
if (p.policy === 'logAlways' || (rejected && p.policy === 'logFailure')) {
console.log(
`${kpid}.policy ${p.policy}: ${tag} ${JSON.stringify(data)}`,
);
} else if (rejected && p.policy === 'panic') {
panic(`${kpid}.policy panic: ${tag} ${JSON.stringify(data)}`);
}
}
}

function resolveToError(kpid, errorData, expectedDecider) {
doResolve(expectedDecider, [[kpid, true, errorData]]);
}

function doSend(target, msg) {
parseKernelSlot(target);
insistMessage(msg);
const m = harden({ type: 'send', target, msg });
kernelKeeper.incrementRefCount(target, `enq|msg|t`);
if (msg.result) {
kernelKeeper.incrementRefCount(msg.result, `enq|msg|r`);
}
let idx = 0;
for (const argSlot of msg.args.slots) {
kernelKeeper.incrementRefCount(argSlot, `enq|msg|s${idx}`);
idx += 1;
}
kernelKeeper.addToRunQueue(m);
}

/**
* Enqueue a message to some kernel object, as if the message had been sent
* by some other vat. This requires a kref as a target.
*
* @param {string} kref Target of the message
* @param {string} method The message verb
* @param {*} args The message arguments
* @param {ResolutionPolicy=} policy How the kernel should handle an eventual
* resolution or rejection of the message's result promise. Should be
* one of 'none' (don't even create a result promise), 'ignore' (do
* nothing), 'logAlways' (log the resolution or rejection), 'logFailure'
* (log only rejections), or 'panic' (panic the kernel upon a
* rejection).
* @returns {string?} the kpid of the sent message's result promise, if any
*/
function queueToKref(kref, method, args, policy = 'ignore') {
// queue a message on the end of the queue, with 'absolute' krefs.
// Use 'step' or 'run' to execute it
insistCapData(args);
args.slots.forEach(s => parseKernelSlot(s));
let resultKPID;
if (policy !== 'none') {
resultKPID = kernelKeeper.addKernelPromise(policy);
}
// Should we actually increment these stats in this case?
kernelKeeper.incStat('syscalls');
kernelKeeper.incStat('syscallSend');
const msg = harden({ method, args, result: resultKPID });
doSend(kref, msg);
return resultKPID;
}

return harden({
doSend,
doSubscribe,
doResolve,
notify,
resolveToError,
queueToKref,
});
}
Loading

0 comments on commit eae0201

Please sign in to comment.