Skip to content

Commit

Permalink
fix(swingset): schedule vat creation on the run-queue
Browse files Browse the repository at this point in the history
This handles properly async vat creation, such as the process used by
subprocess-xsnap, in which the vat isn't ready for use right away.
Previously, we queued the "vat ready" message (to vatAdmin) at a random time,
which was both non-deterministic, and it left the run-queue empty, making
tests and certain runtime configurations think the kernel was idle, exiting
too early.

Now, when the vatAdminVat uses its device node to create a dynamic vat, we
merely queue a new `create-vat` event on the run-queue. Later, when this
arrives at the front of the queue, we perform a crank which starts the vat
manager and waits for it to complete. Any state changes that result from this
process (such as queueing the vat-is-ready message) get committed as a part
of this crank.

closes #2911
  • Loading branch information
warner committed Apr 23, 2021
1 parent b8f3c75 commit 51cf813
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 134 deletions.
66 changes: 62 additions & 4 deletions packages/SwingSet/src/kernel/kernel.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { makeKernelSyscallHandler, doSend } from './kernelSyscall';
import { makeSlogger, makeDummySlogger } from './slogger';
import { getKpidsToRetire } from './cleanup';

import { makeVatLoader } from './loadVat';
import { makeVatRootObjectSlot, makeVatLoader } from './loadVat';
import { makeDeviceTranslators } from './deviceTranslator';

function abbreviateReplacer(_, arg) {
Expand Down Expand Up @@ -507,6 +507,57 @@ export default function buildKernel(
}
}

async function processCreateVat(message) {
const { vatID, source, dynamicOptions } = message;
kernelKeeper.addDynamicVatID(vatID);
const vatKeeper = kernelKeeper.allocateVatKeeper(vatID);
const options = { ...dynamicOptions };
if (!dynamicOptions.managerType) {
options.managerType = kernelKeeper.getDefaultManagerType();
}
vatKeeper.setSourceAndOptions(source, options);

function makeSuccessResponse() {
// build success message, giving admin vat access to the new vat's root
// object
const kernelRootObjSlot = addExport(vatID, makeVatRootObjectSlot());
return {
body: JSON.stringify([
vatID,
{ rootObject: { '@qclass': 'slot', index: 0 } },
]),
slots: [kernelRootObjSlot],
};
}

function makeErrorResponse(error) {
// delete partial vat state
kernelKeeper.cleanupAfterTerminatedVat(vatID);
return {
body: JSON.stringify([vatID, { error: `${error}` }]),
slots: [],
};
}

function sendResponse(args) {
const vatAdminVatId = vatNameToID('vatAdmin');
const vatAdminRootObjectSlot = makeVatRootObjectSlot();
queueToExport(
vatAdminVatId,
vatAdminRootObjectSlot,
'newVatCallback',
args,
'logFailure',
);
}

// eslint-disable-next-line no-use-before-define
return createVatDynamically(vatID, source, dynamicOptions)
.then(makeSuccessResponse, makeErrorResponse)
.then(sendResponse)
.catch(err => console.error(`error in vat creation`, err));
}

function legibilizeMessage(message) {
if (message.type === 'send') {
const msg = message.msg;
Expand Down Expand Up @@ -543,6 +594,8 @@ export default function buildKernel(
} else if (message.type === 'notify') {
kernelKeeper.decrementRefCount(message.kpid, `deq|notify`);
await processNotify(message);
} else if (message.type === 'create-vat') {
await processCreateVat(message);
} else {
assert.fail(X`unable to process message.type ${message.type}`);
}
Expand Down Expand Up @@ -684,13 +737,11 @@ export default function buildKernel(
recreateStaticVat,
loadTestVat,
} = makeVatLoader({
allocateUnusedVatID: kernelKeeper.allocateUnusedVatID,
vatNameToID,
vatManagerFactory,
kernelSlog,
makeVatConsole,
addVatManager,
addExport,
queueToExport,
kernelKeeper,
panic,
Expand Down Expand Up @@ -821,7 +872,14 @@ export default function buildKernel(

// the admin device is endowed directly by the kernel
deviceEndowments.vatAdmin = {
create: createVatDynamically,
pushCreateVatEvent(source, dynamicOptions) {
const vatID = kernelKeeper.allocateUnusedVatID();
const event = { type: 'create-vat', vatID, source, dynamicOptions };
kernelKeeper.addToRunQueue(harden(event));
// the device gets the new vatID immediately, and will be notified
// later when it is created and a root object is available
return vatID;
},
stats: collectVatStats,
terminate: (vatID, reason) => terminateVat(vatID, true, reason),
};
Expand Down
178 changes: 53 additions & 125 deletions packages/SwingSet/src/kernel/loadVat.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,44 +10,30 @@ export function makeVatRootObjectSlot() {

export function makeVatLoader(stuff) {
const {
allocateUnusedVatID,
vatNameToID,
vatManagerFactory,
kernelSlog,
makeVatConsole,
addVatManager,
addExport,
queueToExport,
kernelKeeper,
panic,
buildVatSyscallHandler,
} = stuff;

/**
* Create a new vat at runtime (called from the vatAdmin device).
* Create a new vat at runtime (called when a 'create-vat' event reaches
* the top of the run-queue).
*
* @param { string } vatID The pre-allocated vatID
* @param {*} source The source object implementing the vat
* @param {*} dynamicOptions Options bag governing vat creation
*
* @returns {string} The vatID of the newly created vat
* @returns {Promise<void>} The vatID of the newly created vat
*/
function createVatDynamically(source, dynamicOptions = {}) {
const vatID = allocateUnusedVatID();
kernelKeeper.addDynamicVatID(vatID);
const vatKeeper = kernelKeeper.allocateVatKeeper(vatID);
if (!dynamicOptions.managerType) {
dynamicOptions = {
...dynamicOptions,
managerType: kernelKeeper.getDefaultManagerType(),
};
}
vatKeeper.setSourceAndOptions(source, dynamicOptions);
function createVatDynamically(vatID, source, dynamicOptions = {}) {
// eslint-disable-next-line no-use-before-define
create(vatID, source, dynamicOptions, true, true);
// we ignore the Promise create() returns: the invoking vat will be
// notified via makeSuccessResponse rather than via the return value of
// this function
return vatID;
return create(vatID, source, dynamicOptions, true);
}

/**
Expand All @@ -61,7 +47,11 @@ export function makeVatLoader(stuff) {
*/
function recreateDynamicVat(vatID, source, dynamicOptions) {
// eslint-disable-next-line no-use-before-define
return create(vatID, source, dynamicOptions, false, true);
return create(vatID, source, dynamicOptions, true).catch(err =>
panic(`unable to re-create vat ${vatID}`, err),
);
// if we fail to recreate the vat during replay, crash the kernel,
// because we no longer have any way to inform the original caller
}

/**
Expand All @@ -76,7 +66,9 @@ export function makeVatLoader(stuff) {
*/
function recreateStaticVat(vatID, source, staticOptions) {
// eslint-disable-next-line no-use-before-define
return create(vatID, source, staticOptions, false, false);
return create(vatID, source, staticOptions, false).catch(err =>
panic(`unable to re-create vat ${vatID}`, err),
);
}

const allowedDynamicOptions = [
Expand Down Expand Up @@ -137,21 +129,23 @@ export function makeVatLoader(stuff) {
* without waiting for the promises to be resolved. If false, such
* messages will be queued inside the kernel. Defaults to false.
*
* @param {boolean} notifyNewVat If true, the success or failure of the
* operation will be reported in a message to the admin vat, citing the
* vatID
* @param {boolean} isDynamic If true, the vat being created is a dynamic vat;
* if false, it's a static vat (these have differences in their allowed
* options and some of their option defaults).
*
* @returns {Promise<void>} A Promise which fires (with undefined) when the
* vat is ready for messages.
*/
function create(vatID, source, options, notifyNewVat, isDynamic) {
async function create(vatID, source, options, isDynamic) {
assert(source.bundle || source.bundleName, 'broken source');
const vatSourceBundle =
source.bundle || kernelKeeper.getBundle(source.bundleName);
assert(vatSourceBundle, X`Bundle ${source.bundleName} not found`);
assert.typeof(
vatSourceBundle,
'object',
X`vat creation requires a bundle, not a plain string`,
);

assertKnownOptions(
options,
Expand Down Expand Up @@ -201,108 +195,42 @@ export function makeVatLoader(stuff) {
);
}

async function build() {
assert.typeof(
vatSourceBundle,
'object',
X`vat creation requires a bundle, not a plain string`,
);

kernelSlog.addVat(vatID, isDynamic, description, name, vatSourceBundle);
const managerOptions = {
managerType,
bundle: vatSourceBundle,
metered,
enableDisavow,
enableSetup,
enablePipelining,
enableInternalMetering: !isDynamic,
notifyTermination,
vatConsole: makeVatConsole('vat', vatID),
liveSlotsConsole: makeVatConsole('ls', vatID),
vatParameters,
virtualObjectCacheSize,
name,
};
// TODO: We need to support within-vat metering (for the Spawner) until
// #1343 is fixed, after which we can remove
// managerOptions.enableInternalMetering. For now, it needs to be enabled
// for our internal unit test (which could easily add this to its config
// object) and for the spawner vat (not so easy). To avoid deeper changes,
// we enable it for *all* static vats here. Once #1343 is fixed, remove
// this addition and all support for internal metering.
const translators = makeVatTranslators(vatID, kernelKeeper);
const vatSyscallHandler = buildVatSyscallHandler(vatID, translators);

const finish = kernelSlog.startup(vatID);
const manager = await vatManagerFactory(
vatID,
managerOptions,
vatSyscallHandler,
);
finish();
addVatManager(vatID, manager, translators, managerOptions);
}

function makeSuccessResponse() {
// build success message, giving admin vat access to the new vat's root
// object
const kernelRootObjSlot = addExport(vatID, makeVatRootObjectSlot());

return {
body: JSON.stringify([
vatID,
{ rootObject: { '@qclass': 'slot', index: 0 } },
]),
slots: [kernelRootObjSlot],
};
}

function makeErrorResponse(error) {
return {
body: JSON.stringify([vatID, { error: `${error}` }]),
slots: [],
};
}

function errorDuringReplay(error) {
// if we fail to recreate the vat during replay, crash the kernel,
// because we no longer have any way to inform the original caller
panic(`unable to re-create vat ${vatID}`, error);
}
kernelSlog.addVat(vatID, isDynamic, description, name, vatSourceBundle);
const managerOptions = {
managerType,
bundle: vatSourceBundle,
metered,
enableDisavow,
enableSetup,
enablePipelining,
enableInternalMetering: !isDynamic,
notifyTermination,
vatConsole: makeVatConsole('vat', vatID),
liveSlotsConsole: makeVatConsole('ls', vatID),
vatParameters,
virtualObjectCacheSize,
name,
};

function sendResponse(args) {
const vatAdminVatId = vatNameToID('vatAdmin');
const vatAdminRootObjectSlot = makeVatRootObjectSlot();
queueToExport(
vatAdminVatId,
vatAdminRootObjectSlot,
'newVatCallback',
args,
'logFailure',
);
}
// TODO: We need to support within-vat metering (for the Spawner) until
// #1343 is fixed, after which we can remove
// managerOptions.enableInternalMetering. For now, it needs to be enabled
// for our internal unit test (which could easily add this to its config
// object) and for the spawner vat (not so easy). To avoid deeper changes,
// we enable it for *all* static vats here. Once #1343 is fixed, remove
// this addition and all support for internal metering.

// vatManagerFactory is async, so we prepare a callback chain to execute
// the resulting setup function, create the new vat around the resulting
// dispatch object, and notify the admin vat of our success (or failure).
// We presume that importBundle's Promise will fire promptly (before
// setImmediate does, i.e. importBundle is async but doesn't do any IO,
// so it doesn't really need to be async), because otherwise the
// queueToExport might fire (and insert messages into the kernel run
// queue) in the middle of some other vat's crank. TODO: find a safer
// way, maybe the response should go out to the controller's "queue
// things single file into the kernel" queue, once such a thing exists.
const p = Promise.resolve().then(build);
if (notifyNewVat) {
p.then(makeSuccessResponse, makeErrorResponse)
.then(sendResponse)
.catch(err => console.error(`error in vat creation`, err));
} else {
p.catch(errorDuringReplay);
}
const translators = makeVatTranslators(vatID, kernelKeeper);
const vatSyscallHandler = buildVatSyscallHandler(vatID, translators);

return p;
const finish = kernelSlog.startup(vatID);
const manager = await vatManagerFactory(
vatID,
managerOptions,
vatSyscallHandler,
);
finish();
addVatManager(vatID, manager, translators, managerOptions);
}

async function loadTestVat(vatID, setup, creationOptions) {
Expand Down
10 changes: 5 additions & 5 deletions packages/SwingSet/src/kernel/vatAdmin/vatAdmin-src.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { Far } from '@agoric/marshal';

export function buildRootDeviceNode({ endowments, serialize }) {
const {
create: kernelVatCreationFn,
pushCreateVatEvent,
stats: kernelVatStatsFn,
terminate: kernelTerminateVatFn,
} = endowments;
Expand All @@ -31,12 +31,12 @@ export function buildRootDeviceNode({ endowments, serialize }) {
// Called by the wrapper vat to create a new vat. Gets a new ID from the
// kernel's vat creator fn. Remember that the root object will arrive
// separately. Clean up the outgoing and incoming arguments.
create(bundle, options) {
const vatID = kernelVatCreationFn({ bundle }, options);
create(bundle, options = {}) {
const vatID = pushCreateVatEvent({ bundle }, options);
return vatID;
},
createByName(bundleName, options) {
const vatID = kernelVatCreationFn({ bundleName }, options);
createByName(bundleName, options = {}) {
const vatID = pushCreateVatEvent({ bundleName }, options);
return vatID;
},
terminateWithFailure(vatID, reason) {
Expand Down

0 comments on commit 51cf813

Please sign in to comment.