Skip to content

Commit

Permalink
feat(swingset): initial VatWarehouse for demand-paged vats
Browse files Browse the repository at this point in the history
initial policy: most recently used 20 vats are kept online

 - buildKernel:
   - makeVatWarehouse takes over
     - instantiating static, dynamic vats in start()
     - ephemeral.vats
       - vatWarehouse.lookup() replaces ephemeral.vats.has()
     - addVatManager, removeVatManager
     - deliverToVat
   - re-wire notifyTermination
 - makeVatLoader:
   - create methods return Promise<VatManager>
   - translators are passed in
   - no longer uses vatNameToID, queueToExport
   - notifyTermination has been hoisted to its own file
   - clarify source bundle type
 - test vat-target: exhibit stateful behavior
  • Loading branch information
dckc committed May 25, 2021
1 parent 5b86d6c commit 88d3016
Show file tree
Hide file tree
Showing 7 changed files with 373 additions and 167 deletions.
167 changes: 49 additions & 118 deletions packages/SwingSet/src/kernel/kernel.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { assert, details as X } from '@agoric/assert';
import { importBundle } from '@agoric/import-bundle';
import { assertKnownOptions } from '../assertOptions';
import { makeVatManagerFactory } from './vatManager/factory';
import { makeVatWarehouse } from './vatManager/vat-warehouse';
import makeDeviceManager from './deviceManager';
import { wrapStorage } from './state/storageWrapper';
import makeKernelKeeper from './state/kernelKeeper';
Expand All @@ -20,6 +21,7 @@ import { getKpidsToRetire } from './cleanup';

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

function abbreviateReplacer(_, arg) {
if (typeof arg === 'bigint') {
Expand Down Expand Up @@ -126,20 +128,12 @@ export default function buildKernel(
let started = false;

/**
* @typedef {{
* manager: VatManager,
* enablePipelining: boolean,
* notifyTermination: (s: boolean, info: unknown) => void,
* translators: ReturnType<typeof import('./vatTranslator').makeVatTranslators>,
* }} VatInfo
* @typedef {{
* manager: unknown,
* translators: ReturnType<makeDeviceTranslators>,
* }} DeviceInfo
*/
const ephemeral = {
/** @type {Map<string, VatInfo> }> } key is vatID */
vats: new Map(),
/** @type { Map<string, DeviceInfo> } key is deviceID */
devices: new Map(),
/** @type {string[]} */
Expand Down Expand Up @@ -328,26 +322,35 @@ export default function buildKernel(
doResolve(expectedDecider, [[kpid, true, errorData]]);
}

function removeVatManager(vatID, shouldReject, info) {
insistCapData(info);
const old = ephemeral.vats.get(vatID);
assert(old, `no such vat: ${vatID}`);
ephemeral.vats.delete(vatID);
old.notifyTermination(shouldReject, info);
return old.manager.shutdown();
}

/*
* delete vat DB state,
* resolve orphaned promises,
* notify parent,
* shutdown worker
*/
function terminateVat(vatID, shouldReject, info) {
insistCapData(info);
if (kernelKeeper.getVatKeeper(vatID)) {
const isDynamic = kernelKeeper.getDynamicVats().includes(vatID);
const promisesToReject = kernelKeeper.cleanupAfterTerminatedVat(vatID);
for (const kpid of promisesToReject) {
resolveToError(kpid, VAT_TERMINATION_ERROR, vatID);
}
removeVatManager(vatID, shouldReject, info).then(
() => kdebug(`terminated vat ${vatID}`),
e => console.error(`problem terminating vat ${vatID}`, e),
);
if (isDynamic) {
const vatAdminVatID = vatNameToID('vatAdmin');
notifyTermination(
vatID,
vatAdminVatID,
shouldReject,
info,
queueToExport,
);
}
// else... static... maybe panic???

// ISSUE: terminate stuff in its own crank like creation?
// eslint-disable-next-line no-use-before-define
vatWarehouse.vatWasTerminated(vatID);
}
}

Expand All @@ -363,8 +366,6 @@ export default function buildKernel(
}

async function deliverAndLogToVat(vatID, kernelDelivery, vatDelivery) {
const vat = ephemeral.vats.get(vatID);
assert(vat);
const crankNum = kernelKeeper.getCrankNumber();
/** @typedef { any } FinishFunction TODO: static types for slog? */
/** @type { FinishFunction } */
Expand All @@ -375,7 +376,11 @@ export default function buildKernel(
vatDelivery,
);
try {
const deliveryResult = await vat.manager.deliver(vatDelivery);
// eslint-disable-next-line no-use-before-define
const deliveryResult = await vatWarehouse.deliverToVat(
vatID,
vatDelivery,
);
insistVatDeliveryResult(deliveryResult);
finish(deliveryResult);
const [status, problem] = deliveryResult;
Expand All @@ -391,14 +396,15 @@ export default function buildKernel(

async function deliverToVat(vatID, target, msg) {
insistMessage(msg);
const vat = ephemeral.vats.get(vatID);
kernelKeeper.incStat('dispatches');
kernelKeeper.incStat('dispatchDeliver');
if (!vat) {
// eslint-disable-next-line no-use-before-define
if (!vatWarehouse.lookup(vatID)) {
resolveToError(msg.result, VAT_TERMINATION_ERROR);
} else {
const kd = harden(['message', target, msg]);
const vd = vat.translators.kernelDeliveryToVatDelivery(kd);
// eslint-disable-next-line no-use-before-define
const vd = vatWarehouse.kernelDeliveryToVatDelivery(vatID, kd);
await deliverAndLogToVat(vatID, kd, vd);
}
}
Expand Down Expand Up @@ -459,7 +465,8 @@ export default function buildKernel(
kernelKeeper.addMessageToPromiseQueue(target, msg);
} else {
insistVatID(kp.decider);
const deciderVat = ephemeral.vats.get(kp.decider);
// eslint-disable-next-line no-use-before-define
const deciderVat = vatWarehouse.lookup(kp.decider);
if (deciderVat) {
if (deciderVat.enablePipelining) {
await deliverToVat(kp.decider, target, msg);
Expand All @@ -482,9 +489,9 @@ export default function buildKernel(
const { vatID, kpid } = message;
insistVatID(vatID);
insistKernelType('promise', kpid);
const vat = ephemeral.vats.get(vatID);
kernelKeeper.incStat('dispatches');
if (!vat) {
// eslint-disable-next-line no-use-before-define
if (!vatWarehouse.lookup(vatID)) {
kdebug(`dropping notify of ${kpid} to ${vatID} because vat is dead`);
} else {
const p = kernelKeeper.getKernelPromise(kpid);
Expand All @@ -508,7 +515,8 @@ export default function buildKernel(
resolutions.push([toResolve, kernelKeeper.getKernelPromise(toResolve)]);
}
const kd = harden(['notify', resolutions]);
const vd = vat.translators.kernelDeliveryToVatDelivery(kd);
// eslint-disable-next-line no-use-before-define
const vd = vatWarehouse.kernelDeliveryToVatDelivery(vatID, kd);
vatKeeper.deleteCListEntriesForKernelSlots(targets);
await deliverAndLogToVat(vatID, kd, vd);
}
Expand Down Expand Up @@ -559,7 +567,8 @@ export default function buildKernel(
}

// eslint-disable-next-line no-use-before-define
return createVatDynamically(vatID, source, options)
return vatWarehouse
.createDynamicVat(vatID)
.then(makeSuccessResponse, makeErrorResponse)
.then(sendResponse)
.catch(err => console.error(`error in vat creation`, err));
Expand Down Expand Up @@ -657,7 +666,8 @@ export default function buildKernel(
// the VatManager+VatWorker will see the error case, but liveslots will
// not
function vatSyscallHandler(vatSyscallObject) {
if (!ephemeral.vats.get(vatID)) {
// eslint-disable-next-line no-use-before-define
if (!vatWarehouse.lookup(vatID)) {
// This is a safety check -- this case should never happen unless the
// vatManager is somehow confused.
console.error(`vatSyscallHandler invoked on dead vat ${vatID}`);
Expand Down Expand Up @@ -708,53 +718,17 @@ export default function buildKernel(
return vatSyscallHandler;
}

/*
* Take an existing VatManager (which is already configured to talk to a
* VatWorker, loaded with some vat code) and connect it to the rest of the
* kernel. The vat must be ready to go: any initial buildRootObject
* construction should have happened by this point. However the kernel
* might tell the manager to replay the transcript later, if it notices
* we're reloading a saved state vector.
*/
function addVatManager(vatID, manager, translators, managerOptions) {
// addVatManager takes a manager, not a promise for one
assert(
manager.deliver,
`manager lacks .deliver, isPromise=${manager instanceof Promise}`,
);
const {
enablePipelining = false,
notifyTermination = () => {},
} = managerOptions;

ephemeral.vats.set(
vatID,
harden({
translators,
manager,
notifyTermination,
enablePipelining: Boolean(enablePipelining),
}),
);
}

const {
createVatDynamically,
recreateDynamicVat,
recreateStaticVat,
loadTestVat,
} = makeVatLoader({
vatNameToID,
const vatLoader = makeVatLoader({
vatManagerFactory,
kernelSlog,
makeVatConsole,
addVatManager,
queueToExport,
kernelKeeper,
panic,
buildVatSyscallHandler,
});

const vatWarehouse = makeVatWarehouse(kernelKeeper, hostStorage, vatLoader);

/**
* Create a dynamically generated vat for testing purposes. Such vats are
* defined by providing a setup function rather than a bundle and can be
Expand Down Expand Up @@ -794,7 +768,7 @@ export default function buildKernel(
logStartup(`assigned VatID ${vatID} for test vat ${name}`);
kernelKeeper.allocateVatKeeper(vatID);

await loadTestVat(vatID, setup, creationOptions);
await vatWarehouse.loadTestVat(vatID, setup, creationOptions);
return vatID;
}

Expand Down Expand Up @@ -857,25 +831,7 @@ export default function buildKernel(
started = true;
assert(kernelKeeper.getInitialized(), X`kernel not initialized`);

// instantiate all static vats
for (const [name, vatID] of kernelKeeper.getStaticVats()) {
logStartup(`starting static vat ${name} as vat ${vatID}`);
const vatKeeper = kernelKeeper.allocateVatKeeper(vatID);
const { source, options } = vatKeeper.getSourceAndOptions();
// eslint-disable-next-line no-await-in-loop
await recreateStaticVat(vatID, source, options);
// now the vatManager is attached and ready for transcript replay
}

// instantiate all dynamic vats
for (const vatID of kernelKeeper.getDynamicVats()) {
logStartup(`starting dynamic vat ${vatID}`);
const vatKeeper = kernelKeeper.allocateVatKeeper(vatID);
const { source, options } = vatKeeper.getSourceAndOptions();
// eslint-disable-next-line no-await-in-loop
await recreateDynamicVat(vatID, source, options);
// now the vatManager is attached and ready for transcript replay
}
await vatWarehouse.start(logStartup);

// the admin device is endowed directly by the kernel
deviceEndowments.vatAdmin = {
Expand Down Expand Up @@ -924,30 +880,6 @@ export default function buildKernel(
}
}

// replay any transcripts
// This happens every time, now that initialisation is separated from
// execution.
kdebug('Replaying SwingSet transcripts');
const oldLength = kernelKeeper.getRunQueueLength();
for (const vatID of ephemeral.vats.keys()) {
logStartup(`Replaying transcript of vatID ${vatID}`);
const vat = ephemeral.vats.get(vatID);
if (!vat) {
logStartup(`skipping reload of dead vat ${vatID}`);
} else {
/** @type { FinishFunction } */
const slogDone = kernelSlog.replayVatTranscript(vatID);
// eslint-disable-next-line no-await-in-loop
await vat.manager.replayTranscript();
slogDone();
logStartup(`finished replaying vatID ${vatID} transcript `);
const newLength = kernelKeeper.getRunQueueLength();
if (newLength !== oldLength) {
console.log(`SPURIOUS RUNQUEUE`, kernelKeeper.dump().runQueue);
assert.fail(X`replay ${vatID} added spurious run-queue entries`);
}
}
}
kernelKeeper.loadStats();
kernelKeeper.incrementCrankNumber();
}
Expand Down Expand Up @@ -992,8 +924,7 @@ export default function buildKernel(

// mostly used by tests, only needed with thread/process-based workers
function shutdown() {
const vatRecs = Array.from(ephemeral.vats.values());
return Promise.all(vatRecs.map(rec => rec.manager.shutdown()));
return vatWarehouse.shutdown();
}

function kpRegisterInterest(kpid) {
Expand Down
Loading

0 comments on commit 88d3016

Please sign in to comment.