Skip to content

Commit

Permalink
feat(swingset): makeVatWarehouse (WIP)
Browse files Browse the repository at this point in the history
  - move makeVatWarehouse from test/ to src/
  - use X`` rather than d``

some @ts-check stuff WIP
  • Loading branch information
dckc committed May 18, 2021
1 parent 08cd3fe commit 10c4d44
Show file tree
Hide file tree
Showing 2 changed files with 178 additions and 170 deletions.
175 changes: 175 additions & 0 deletions packages/SwingSet/src/kernel/vatManager/vat-warehouse.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
// @ts-check
import { assert, q, details as X } from '@agoric/assert';
import { makeVatTranslators } from '../vatTranslator';

/**
* @param { ReturnType<typeof import('../state/kernelKeeper').default> } kernelKeeper
* @param { SwingStore['storage'] } storage
* @param { Record<string, VatManagerFactory> } factories
* @param { (vatID: string, translators: unknown) => VatSyscallHandler } buildVatSyscallHandler
* @param {{ sizeHint?: number }=} policyOptions
*
* @typedef { ReturnType<typeof import('@agoric/swing-store-simple').initSwingStore> } SwingStore
* @typedef {(syscall: VatSyscallObject) => ['error', string] | ['ok', null] | ['ok', Capdata]} VatSyscallHandler
* @typedef {{ body: string, slots: unknown[] }} Capdata
* @typedef { [unknown, ...unknown[]] } Tagged
* @typedef { { moduleFormat: string }} Bundle
*/
export function makeVatWarehouse(
kernelKeeper,
storage,
factories,
buildVatSyscallHandler,
policyOptions,
) {
const { sizeHint = 10 } = policyOptions || {};
/** @type { Map<string, VatManager> } */
const idToManager = new Map();

const { parse, stringify } = JSON;

// v$NN.source = JSON({ bundle }) or JSON({ bundleName })
// v$NN.options = JSON
// TODO: check against createVatDynamically
// TODO transaction boundaries?
// ensure kernel doesn't start transaction until after it gets manager?
const idToInitDetail = {
/** @type { (vatID: string) => { vatID: string, bundle: Bundle, options: ManagerOptions } } */
get(vatID) {
const source = storage.get(`${vatID}.source`);
assert(source, X`${q(vatID)} missing .source`);
const { bundle } = parse(source);
assert(bundle, X`${q(vatID)}: bundleName not supported`);

const optionsData = storage.get(`${vatID}.options`);
assert(optionsData, X`${q(vatID)} missing .options`);
const options = parse(optionsData);
// TODO: validate options
return { vatID, bundle, options };
},
/** @type { (vatID: string, d: { bundle: Bundle, options: ManagerOptions }) => void } */
set(vatID, { options, bundle }) {
storage.set(`${vatID}.source`, stringify({ bundle }));
storage.set(`${vatID}.options`, stringify(options));
},
has(/** @type { unknown } */ vatID) {
return storage.has(`${vatID}.source`) && storage.has(`${vatID}.options`);
},
};

/**
* @param {string} vatID
* @param {Bundle} bundle
* @param {ManagerOptions} options
*/
function initVatManager(vatID, bundle, options) {
assert(
!idToInitDetail.has(vatID),
X`vat with id ${q(vatID)} already initialized`,
);
const { managerType } = options;
assert(managerType in factories, X`unknown managerType ${managerType}`);

idToInitDetail.set(vatID, { bundle, options });

// TODO: add a way to remove a gatekeeper from ephemeral in kernel.js
// so that we can get rid of a vatKeeper when we evict its vat.
kernelKeeper.allocateVatKeeper(vatID);
}

/**
* @param {string} vatID
* @returns { Promise<VatManager> }
*/
async function provideVatManager(vatID) {
const mgr = idToManager.get(vatID);
if (mgr) return mgr;
const detail = idToInitDetail.get(vatID);
assert(detail, X`no vat with ID ${q(vatID)} initialized`);

// TODO: move kernelKeeper.allocateVatKeeper(vatID);
// to here
// TODO: load from snapshot
const { bundle, options } = detail;
const { managerType } = options;

const translators = makeVatTranslators(vatID, kernelKeeper);
const vatSyscallHandler = buildVatSyscallHandler(vatID, translators);

// console.log('provide: creating from bundle', vatID);
const manager = await factories[managerType].createFromBundle(
vatID,
bundle,
options,
vatSyscallHandler,
);

await manager.replayTranscript();
idToManager.set(vatID, manager);
return manager;
}

/** @type { (vatID: string) => Promise<void> } */
async function evict(vatID) {
assert(
idToInitDetail.has(vatID),
X`no vat with ID ${q(vatID)} initialized`,
);
const mgr = idToManager.get(vatID);
if (!mgr) return;
idToManager.delete(vatID);
// console.log('evict: shutting down', vatID);
await mgr.shutdown();
}

/** @type { string[] } */
const recent = [];

/**
* Simple fixed-size LRU cache policy
*
* TODO: policy input: did a vat get a message? how long ago?
* "important" vat option?
* options: pay $/block to keep in RAM - advisory; not consensus
* creation arg: # of vats to keep in RAM (LRU 10~50~100)
*
* @param {string} currentVatID
*/
async function applyAvailabilityPolicy(currentVatID) {
// console.log('applyAvailabilityPolicy', currentVatID, recent);
const pos = recent.indexOf(currentVatID);
// already most recently used
if (pos + 1 === sizeHint) return;
if (pos >= 0) recent.splice(pos, 1);
recent.push(currentVatID);
// not yet full
if (recent.length <= sizeHint) return;
const [lru] = recent.splice(0, 1);
await evict(lru);
}

/** @type {(vatID: string, d: VatDeliveryObject) => Promise<Tagged> } */
async function deliverToVat(vatID, delivery) {
await applyAvailabilityPolicy(vatID);
return (await provideVatManager(vatID)).deliver(delivery);
}

/** @type { (vatID: string) => Promise<void> } */
async function shutdown(vatID) {
await evict(vatID);
idToInitDetail.delete(vatID);
}

return harden({
// TODO: startup() method for start of kernel process
// see // instantiate all static vats
// in kernel.js
initVatManager,
deliverToVat,

// mostly for testing?
activeVatIDs: () => [...idToManager.keys()],

shutdown, // should this be shutdown for the whole thing?
});
}
173 changes: 3 additions & 170 deletions packages/SwingSet/test/workers/test-vatwarehouse.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { spawn } from 'child_process';

// eslint-disable-next-line import/order
import { test } from '../../tools/prepare-test-env-ava';
import { assert, q, details as d } from '@agoric/assert';
import { assert } from '@agoric/assert';
import { initSwingStore } from '@agoric/swing-store-simple';
import bundleSource from '@agoric/bundle-source';
import { Remotable, getInterfaceOf } from '@agoric/marshal';
Expand All @@ -16,176 +16,9 @@ import { wrapStorage } from '../../src/kernel/state/storageWrapper';

import { loadBasedir } from '../../src';
import { makeStartXSnap } from '../../src/controller';
import { makeVatTranslators } from '../../src/kernel/vatTranslator';
import { makeVatWarehouse } from '../../src/kernel/vatManager/vat-warehouse';

/**
* @param { ReturnType<typeof makeKernelKeeper> } kernelKeeper
* @param { SwingStore['storage'] } storage
* @param { Record<string, VatManagerFactory> } factories
* @param { (vatID: string, translators: unknown) => VatSyscallHandler } buildVatSyscallHandler
* @param {{ sizeHint?: number }=} policyOptions
*
* @typedef { ReturnType<typeof initSwingStore> } SwingStore
* @typedef {(syscall: Tagged) => ['error', string] | ['ok', null] | ['ok', Capdata]} VatSyscallHandler
* @typedef {{ body: string, slots: unknown[] }} Capdata
* @typedef { [unknown, ...unknown[]] } Tagged
* @typedef { { moduleFormat: string }} Bundle
*/
export function makeVatWarehouse(
kernelKeeper,
storage,
factories,
buildVatSyscallHandler,
policyOptions,
) {
const { sizeHint = 10 } = policyOptions || {};
/** @type { Map<string, VatManager> } */
const idToManager = new Map();

const { parse, stringify } = JSON;

// v$NN.source = JSON({ bundle }) or JSON({ bundleName })
// v$NN.options = JSON
// TODO: check against createVatDynamically
// TODO transaction boundaries?
// ensure kernel doesn't start transaction until after it gets manager?
const idToInitDetail = {
/** @type { (vatID: string) => { vatID: string, bundle: Bundle, options: ManagerOptions } } */
get(vatID) {
const { bundle } = parse(storage.get(`${vatID}.source`));
assert(bundle, d`${q(vatID)}: bundleName not supported`);

const options = parse(storage.get(`${vatID}.options`));
// TODO: validate options

return { vatID, bundle, options };
},
/** @type { (vatID: string, d: { bundle: Bundle, options: ManagerOptions }) => void } */
set(vatID, { options, bundle }) {
storage.set(`${vatID}.source`, stringify({ bundle }));
storage.set(`${vatID}.options`, stringify(options));
},
has(/** @type { unknown } */ vatID) {
return storage.has(`${vatID}.source`) && storage.has(`${vatID}.options`);
},
};

/**
* @param {string} vatID
* @param {Bundle} bundle
* @param {ManagerOptions} options
*/
function initVatManager(vatID, bundle, options) {
assert(
!idToInitDetail.has(vatID),
d`vat with id ${vatID} already initialized`,
);
const { managerType } = options;
assert(managerType in factories, d`unknown managerType ${managerType}`);

idToInitDetail.set(vatID, { bundle, options });

// TODO: add a way to remove a gatekeeper from ephemeral in kernel.js
// so that we can get rid of a vatKeeper when we evict its vat.
kernelKeeper.allocateVatKeeper(vatID);
}

/**
* @param {string} vatID
* @returns { Promise<VatManager> }
*/
async function provideVatManager(vatID) {
const mgr = idToManager.get(vatID);
if (mgr) return mgr;
const detail = idToInitDetail.get(vatID);
assert(detail, d`no vat with ID ${vatID} initialized`);

// TODO: move kernelKeeper.allocateVatKeeper(vatID);
// to here

// TODO: load from snapshot

const { bundle, options } = detail;
const { managerType } = options;

const translators = makeVatTranslators(vatID, kernelKeeper);
const vatSyscallHandler = buildVatSyscallHandler(vatID, translators);

// console.log('provide: creating from bundle', vatID);
const manager = await factories[managerType].createFromBundle(
vatID,
bundle,
options,
vatSyscallHandler,
);

await manager.replayTranscript();
idToManager.set(vatID, manager);
return manager;
}

/** @type { (vatID: string) => Promise<void> } */
async function evict(vatID) {
assert(idToInitDetail.has(vatID), d`no vat with ID ${vatID} initialized`);
const mgr = idToManager.get(vatID);
if (!mgr) return;
idToManager.delete(vatID);
// console.log('evict: shutting down', vatID);
await mgr.shutdown();
}

/** @type { string[] } */
const recent = [];

/**
* Simple fixed-size LRU cache policy
*
* TODO: policy input: did a vat get a message? how long ago?
* "important" vat option?
* options: pay $/block to keep in RAM - advisory; not consensus
* creation arg: # of vats to keep in RAM (LRU 10~50~100)
*
* @param {string} currentVatID
*/
async function applyAvailabilityPolicy(currentVatID) {
// console.log('applyAvailabilityPolicy', currentVatID, recent);
const pos = recent.indexOf(currentVatID);
// already most recently used
if (pos + 1 === sizeHint) return;
if (pos >= 0) recent.splice(pos, 1);
recent.push(currentVatID);
// not yet full
if (recent.length <= sizeHint) return;
const [lru] = recent.splice(0, 1);
await evict(lru);
}

/** @type {(vatID: string, d: VatDeliveryObject) => Promise<Tagged> } */
async function deliverToVat(vatID, delivery) {
await applyAvailabilityPolicy(vatID);
return (await provideVatManager(vatID)).deliver(delivery);
}

/** @type { (vatID: string) => Promise<void> } */
async function shutdown(vatID) {
await evict(vatID);
idToInitDetail.delete(vatID);
}

return harden({
// TODO: startup() method for start of kernel process
// see // instantiate all static vats
// in kernel.js

initVatManager,
deliverToVat,

// mostly for testing?
activeVatIDs: () => [...idToManager.keys()],

shutdown, // should this be shutdown for the whole thing?
});
}
import '../../src/types';

function aStorageAndKeeper() {
const { storage: hostStorage } = initSwingStore(undefined);
Expand Down

0 comments on commit 10c4d44

Please sign in to comment.