Skip to content

Commit

Permalink
feat: run vat creation and initialization in a crank to access to sys…
Browse files Browse the repository at this point in the history
…call and transcript logging

Closes #2910
  • Loading branch information
FUDCo committed Feb 17, 2022
1 parent dba86ff commit e0ec2fd
Show file tree
Hide file tree
Showing 35 changed files with 523 additions and 261 deletions.
6 changes: 6 additions & 0 deletions packages/SwingSet/src/kernel/initializeKernel.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ export function initializeKernel(config, hostStorage, verbose = false) {
creationOptions.vatParameters = vatParameters;
creationOptions.description = `static name=${name}`;
creationOptions.name = name;
if (creationOptions.useTranscript === undefined) {
creationOptions.useTranscript = true;
}
if (!creationOptions.managerType) {
creationOptions.managerType = kernelKeeper.getDefaultManagerType();
}
Expand All @@ -84,6 +87,9 @@ export function initializeKernel(config, hostStorage, verbose = false) {
const vatKeeper = kernelKeeper.provideVatKeeper(vatID);
vatKeeper.setSourceAndOptions({ bundleID }, creationOptions);
vatKeeper.initializeReapCountdown(creationOptions.reapInterval);
if (!creationOptions.enableSetup) {
kernelKeeper.addToRunQueue(harden({ type: 'startVat', vatID }));
}
if (name === 'vatAdmin') {
// Create a kref for the vatAdmin root, so the kernel can tell it
// about creation/termination of dynamic vats.
Expand Down
103 changes: 80 additions & 23 deletions packages/SwingSet/src/kernel/kernel.js
Original file line number Diff line number Diff line change
Expand Up @@ -336,31 +336,32 @@ export default function buildKernel(
*/
function terminateVat(vatID, shouldReject, info) {
insistCapData(info);
// guard against somebody telling vatAdmin to kill a vat twice
if (kernelKeeper.vatIsAlive(vatID)) {
const isDynamic = kernelKeeper.getDynamicVats().includes(vatID);
const promisesToReject = kernelKeeper.cleanupAfterTerminatedVat(vatID);
for (const kpid of promisesToReject) {
resolveToError(kpid, VAT_TERMINATION_ERROR, vatID);
}
if (isDynamic) {
assert(
vatAdminRootKref,
`initializeKernel did not set vatAdminRootKref`,
);
notifyTermination(
vatID,
vatAdminRootKref,
shouldReject,
info,
queueToKref,
);
}
// else... static... maybe panic???

// ISSUE: terminate stuff in its own crank like creation?
// eslint-disable-next-line no-use-before-define
vatWarehouse.vatWasTerminated(vatID);
}
if (vatAdminRootKref) {
// static vat termination can happen before vat admin vat exists
notifyTermination(
vatID,
vatAdminRootKref,
shouldReject,
info,
queueToKref,
);
} else {
console.log(
`warning: vat ${vatID} terminated without a vatAdmin to report to`,
);
}
}

let terminationTrigger;
Expand All @@ -370,6 +371,7 @@ export default function buildKernel(
terminationTrigger = undefined;
postAbortActions = {
meterDeductions: [], // list of { meterID, compute }
discardFailedDelivery: false,
};
}
resetDeliveryTriggers();
Expand Down Expand Up @@ -684,12 +686,40 @@ export default function buildKernel(
return harden(policyInput);
}

async function processStartVat(message) {
postAbortActions.discardFailedDelivery = true;
const { type, vatID } = message;
// console.log(`-- processStartVat(${vatID})`);
insistVatID(vatID);
// eslint-disable-next-line no-use-before-define
if (!vatWarehouse.lookup(vatID)) {
// Note a bit of weirdness here: if a vat's module initialization or
// buildRootObject function throws an error, the crank in which it was
// executed will be aborted, which will leave the 'startVat' delivery on
// the run queue (since the removal of the request from the run queue will
// have been rolled back by the abort). This means it will be delivered
// *again* on the next crank; that, however, will lead us here: the vat
// won't exist (because it was terminated) and the nugatory redundant
// 'startVat' will safely render as a no-op and go away.
/** @type { PolicyInput } */
const policyInput = harden(['create-vat', {}]);
return harden(policyInput);
}
const kd = harden([type]); // TODO(4381) add vatParameters here
// eslint-disable-next-line no-use-before-define
const vd = vatWarehouse.kernelDeliveryToVatDelivery(vatID, kd);
// TODO: can we provide a computron count to the run policy?
const policyInput = await deliverAndLogToVat(vatID, kd, vd, false);
return harden(policyInput);
}

/**
*
* @param { * } message
* @returns { Promise<PolicyInput> }
*/
async function processCreateVat(message) {
postAbortActions.discardFailedDelivery = true;
assert(vatAdminRootKref, `initializeKernel did not set vatAdminRootKref`);
const { vatID, source, dynamicOptions } = message;
kernelKeeper.addDynamicVatID(vatID);
Expand All @@ -703,6 +733,7 @@ export default function buildKernel(
}
vatKeeper.setSourceAndOptions(source, options);
vatKeeper.initializeReapCountdown(options.reapInterval);
const { enableSetup } = options;

function makeSuccessResponse() {
// build success message, giving admin vat access to the new vat's root
Expand Down Expand Up @@ -733,14 +764,22 @@ export default function buildKernel(

/** @type { PolicyInput } */
const policyInput = harden(['create-vat', {}]);
// TODO: combine this with the return value from processStartVat

// eslint-disable-next-line no-use-before-define
return vatWarehouse
.createDynamicVat(vatID)
.then(makeSuccessResponse, makeErrorResponse)
.then(sendResponse)
.catch(err => console.error(`error in vat creation`, err))
.then(() => policyInput);
return (
// eslint-disable-next-line no-use-before-define
vatWarehouse
.createDynamicVat(vatID)
// if createDynamicVat fails, go directly to makeErrorResponse
.then(_ =>
enableSetup ? null : processStartVat({ type: 'startVat', vatID }),
) // TODO(4381) add vatParameters here
// if processStartVat fails, do we need to clean up the vat first??
.then(makeSuccessResponse, makeErrorResponse)
.then(sendResponse)
.catch(err => console.error(`error in vat creation`, err))
.then(() => policyInput)
);
}

function legibilizeMessage(message) {
Expand All @@ -751,11 +790,17 @@ export default function buildKernel(
return `@${message.target} <- ${msg.method}(${argList}) : @${result}`;
} else if (message.type === 'notify') {
return `notify(vatID: ${message.vatID}, kpid: @${message.kpid})`;
} else if (message.type === 'create-vat') {
// prettier-ignore
return `create-vat ${message.vatID} opts: ${JSON.stringify(message.dynamicOptions)}`;
// eslint-disable-next-line no-use-before-define
} else if (gcMessages.includes(message.type)) {
// prettier-ignore
return `${message.type} ${message.vatID} ${message.krefs.map(e=>`@${e}`).join(' ')}`;
} else if (message.type === 'bringOutYourDead') {
} else if (
message.type === 'bringOutYourDead' ||
message.type === 'startVat'
) {
return `${message.type} ${message.vatID}`;
} else {
return `unknown message type ${message.type}`;
Expand Down Expand Up @@ -797,7 +842,10 @@ export default function buildKernel(
kernelKeeper.decrementRefCount(message.kpid, `deq|notify`);
policyInput = await processNotify(message);
} else if (message.type === 'create-vat') {
// creating a new dynamic vat will immediately do start-vat
policyInput = await processCreateVat(message);
} else if (message.type === 'startVat') {
policyInput = await processStartVat(message);
} else if (message.type === 'bringOutYourDead') {
policyInput = await processBringOutYourDead(message);
} else if (gcMessages.includes(message.type)) {
Expand All @@ -814,11 +862,20 @@ export default function buildKernel(
kernelKeeper.abortCrank();
didAbort = true;
// but metering deductions and underflow notifications must survive
const { meterDeductions } = postAbortActions;
const { meterDeductions, discardFailedDelivery } = postAbortActions;
for (const { meterID, compute } of meterDeductions) {
deductMeter(meterID, compute, false);
// that will re-push any notifications
}
if (discardFailedDelivery) {
// kernelKeeper.abortCrank removed all evidence that the crank ever
// happened, including, notably, the removal of the delivery itself
// from the head of the run queue, which will result in it being
// 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();
}
}
// state changes reflecting the termination must also survive, so
// these happen after a possible abortCrank()
Expand Down
63 changes: 37 additions & 26 deletions packages/SwingSet/src/kernel/liveSlots.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,9 @@ const DEFAULT_VIRTUAL_OBJECT_CACHE_SIZE = 3; // XXX ridiculously small value to
* @param {*} gcTools { WeakRef, FinalizationRegistry, waitUntilQuiescent, gcAndFinalize,
* meterControl }
* @param {Console} console
* @returns {*} { vatGlobals, inescapableGlobalProperties, dispatch, setBuildRootObject }
* @param {*} buildVatNamespace
*
* setBuildRootObject should be called, once, with a function that will
* create a root object for the new vat The caller provided buildRootObject
* function produces and returns the new vat's root object:
*
* buildRootObject(vatPowers, vatParameters)
* @returns {*} { dispatch }
*/
function build(
syscall,
Expand All @@ -54,6 +50,7 @@ function build(
vatParameters,
gcTools,
console,
buildVatNamespace,
) {
const { WeakRef, FinalizationRegistry, meterControl } = gcTools;
const enableLSDebug = false;
Expand All @@ -63,7 +60,7 @@ function build(
}
}

let didRoot = false;
let didStartVat = false;

const outstandingProxies = new WeakSet();

Expand Down Expand Up @@ -825,7 +822,7 @@ function build(
}

function deliver(target, method, argsdata, result) {
assert(didRoot);
assert(didStartVat);
insistCapData(argsdata);
lsdebug(
`ls[${forVatID}].dispatch.deliver ${target}.${method} -> ${result}`,
Expand Down Expand Up @@ -951,7 +948,7 @@ function build(
}

function notify(resolutions) {
assert(didRoot);
assert(didStartVat);
beginCollectingPromiseImports();
for (const resolution of resolutions) {
const [vpid, rejected, data] = resolution;
Expand Down Expand Up @@ -1103,9 +1100,22 @@ function build(
assert(key.match(/^[-\w.+/]+$/), X`invalid vatstore key`);
}

function setBuildRootObject(buildRootObject) {
assert(!didRoot);
didRoot = true;
async function startVat() {
assert(!didStartVat);
didStartVat = true;
// consume the first (unmetered) turn to ensure vat module code runs with metering
await Promise.resolve();
// syscalls/VatData/makeKind must be enabled before invoking buildVatNamespace
const vatNS = await buildVatNamespace(
vatGlobals,
inescapableGlobalProperties,
);
const buildRootObject = vatNS.buildRootObject;
assert.typeof(
buildRootObject,
'function',
X`vat source bundle lacks buildRootObject() function`,
);

// Build the `vatPowers` provided to `buildRootObject`. We include
// vatGlobals and inescapableGlobalProperties to make it easier to write
Expand Down Expand Up @@ -1192,6 +1202,7 @@ function build(
* @returns { void }
*/
function dispatchToUserspace(delivery) {
let result;
const [type, ...args] = delivery;
switch (type) {
case 'message': {
Expand Down Expand Up @@ -1220,9 +1231,14 @@ function build(
retireImports(vrefs);
break;
}
case 'startVat': {
result = startVat();
break;
}
default:
assert.fail(X`unknown delivery type ${type}`);
}
return result;
}

// the first turn of each dispatch is spent in liveslots, and is not
Expand Down Expand Up @@ -1299,10 +1315,10 @@ function build(

// we return 'possiblyDeadSet' for unit tests
return harden({
dispatch,
startVat,
vatGlobals,
inescapableGlobalProperties,
setBuildRootObject,
dispatch,
m,
possiblyDeadSet,
testHooks,
Expand All @@ -1322,7 +1338,9 @@ function build(
* @param {boolean} enableVatstore
* @param {*} gcTools { WeakRef, FinalizationRegistry, waitUntilQuiescent }
* @param {Pick<Console, 'debug' | 'log' | 'info' | 'warn' | 'error'>} [liveSlotsConsole]
* @returns {*} { vatGlobals, inescapableGlobalProperties, dispatch, setBuildRootObject }
* @param {*} buildVatNamespace
*
* @returns {*} { vatGlobals, inescapableGlobalProperties, dispatch }
*
* setBuildRootObject should be called, once, with a function that will
* create a root object for the new vat The caller provided buildRootObject
Expand Down Expand Up @@ -1356,6 +1374,7 @@ export function makeLiveSlots(
enableVatstore = false,
gcTools,
liveSlotsConsole = console,
buildVatNamespace,
) {
const allVatPowers = {
...vatPowers,
Expand All @@ -1371,20 +1390,12 @@ export function makeLiveSlots(
vatParameters,
gcTools,
liveSlotsConsole,
buildVatNamespace,
);
const {
vatGlobals,
inescapableGlobalProperties,
dispatch,
setBuildRootObject,
possiblyDeadSet,
testHooks,
} = r; // omit 'm'
const { dispatch, startVat, possiblyDeadSet, testHooks } = r; // omit 'm'
return harden({
vatGlobals,
inescapableGlobalProperties,
dispatch,
setBuildRootObject,
startVat,
possiblyDeadSet,
testHooks,
});
Expand Down
3 changes: 3 additions & 0 deletions packages/SwingSet/src/kernel/vatAdmin/vatAdminWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,9 @@ export function buildRootObject(vatPowers) {

// the kernel sends this when the vat halts
function vatTerminated(vatID, shouldReject, info) {
if (pending.has(vatID)) {
newVatCallback(vatID, { error: info });
}
if (!running.has(vatID)) {
// a static vat terminated, so we have nobody to notify
console.log(`DANGER: static vat ${vatID} terminated`);
Expand Down
10 changes: 9 additions & 1 deletion packages/SwingSet/src/kernel/vatManager/factory.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,15 +99,23 @@ export function makeVatManagerFactory({
if (setup && managerType !== 'local') {
console.warn(`TODO: stop using setup() with ${managerType}`);
}
if (managerType === 'local' || enableSetup) {
if (enableSetup) {
if (setup) {
return localFactory.createFromSetup(
vatID,
setup,
managerOptions,
vatSyscallHandler,
);
} else {
return localFactory.createFromBundle(
vatID,
bundle,
managerOptions,
vatSyscallHandler,
);
}
} else if (managerType === 'local') {
return localFactory.createFromBundle(
vatID,
bundle,
Expand Down
Loading

0 comments on commit e0ec2fd

Please sign in to comment.