Skip to content

Commit 4b5c636

Browse files
committed
chore(swingset): refactor vatManagerFactory()
Refactor the creation of VatManagers to use a single function (vatManagerFactory), which takes "managerOptions", which include everything needed to configure the new manager. The handling of options was tightened up, with precondition checks on the options-bag contents. One new managerOption is `managerType`, which specifies where we want the new vat to run. Only `local` is supported right now (our usual Compartment sharing the main thread approach), but #1299 / #1127 will introduce new types that use separate threads, or subprocesses. The controller/kernel acquired a new `shutdown()` method, to use in unit tests. This is unused now, but once we add workers that spawn threads or subprocesses, we'll need to call+await this at the end of any unit test that creates thread/subprocess-based workers, otherwise the worker thread would keep the process from ever exiting.
1 parent 8748674 commit 4b5c636

14 files changed

+370
-273
lines changed
+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { assert } from '@agoric/assert';
2+
3+
export function assertKnownOptions(options, knownNames) {
4+
assert(knownNames instanceof Array);
5+
for (const name of Object.keys(options)) {
6+
if (knownNames.indexOf(name) === -1) {
7+
throw Error(`unknown option ${name}`);
8+
}
9+
}
10+
}

packages/SwingSet/src/controller.js

+32-20
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,21 @@
33
import fs from 'fs';
44
import path from 'path';
55
import re2 from 're2';
6-
import { assert } from '@agoric/assert';
6+
import * as babelCore from '@babel/core';
7+
import * as babelParser from '@agoric/babel-parser';
8+
import babelGenerate from '@babel/generator';
9+
import anylogger from 'anylogger';
710

11+
import { assert } from '@agoric/assert';
812
import { isTamed, tameMetering } from '@agoric/tame-metering';
913
import bundleSource from '@agoric/bundle-source';
1014
import { importBundle } from '@agoric/import-bundle';
1115
import { initSwingStore } from '@agoric/swing-store-simple';
1216
import { HandledPromise } from '@agoric/eventual-send';
13-
1417
import { makeMeteringTransformer } from '@agoric/transform-metering';
15-
import * as babelCore from '@babel/core';
1618
import { makeTransform } from '@agoric/transform-eventual-send';
17-
import * as babelParser from '@agoric/babel-parser';
18-
import babelGenerate from '@babel/generator';
19-
20-
import anylogger from 'anylogger';
2119

20+
import { assertKnownOptions } from './assertOptions';
2221
import { waitUntilQuiescent } from './waitUntilQuiescent';
2322
import { insistStorageAPI } from './storageAPI';
2423
import { insistCapData } from './capdata';
@@ -53,6 +52,13 @@ function byName(a, b) {
5352
return 0;
5453
}
5554

55+
const KNOWN_CREATION_OPTIONS = harden([
56+
'enablePipelining',
57+
'metered',
58+
'enableSetup',
59+
'managerType',
60+
]);
61+
5662
/**
5763
* Scan a directory for files defining the vats to bootstrap for a swingset, and
5864
* produce a swingset config object for what was found there. Looks for files
@@ -246,7 +252,15 @@ export async function buildVatController(
246252
// two vattps, must handle somehow.
247253
const commsVatSourcePath = require.resolve('./vats/comms');
248254
const commsVatBundle = await bundleSource(commsVatSourcePath);
249-
kernel.addGenesisVat('comms', commsVatBundle, {}, { enablePipelining: true }); // todo: allowSetup
255+
kernel.addGenesisVat(
256+
'comms',
257+
commsVatBundle,
258+
{},
259+
{
260+
enablePipelining: true,
261+
enableSetup: true,
262+
},
263+
);
250264

251265
// vat-tp is added automatically, but TODO: bootstraps must still connect
252266
// it to comms
@@ -260,7 +274,7 @@ export async function buildVatController(
260274
const timerWrapperBundle = await bundleSource(timerWrapperSourcePath);
261275
kernel.addGenesisVat('timer', timerWrapperBundle);
262276

263-
async function addGenesisVat(
277+
function addGenesisVat(
264278
name,
265279
bundleName,
266280
vatParameters = {},
@@ -370,19 +384,13 @@ export async function buildVatController(
370384
}
371385

372386
if (config.vats) {
373-
const vats = [];
374387
for (const name of Object.keys(config.vats)) {
375-
const v = config.vats[name];
376-
vats.push(
377-
addGenesisVat(
378-
name,
379-
v.bundleName || name,
380-
v.parameters || {},
381-
v.creationOptions || {},
382-
),
383-
);
388+
const { bundleName, parameters, creationOptions = {} } = config.vats[
389+
name
390+
];
391+
assertKnownOptions(creationOptions, KNOWN_CREATION_OPTIONS);
392+
addGenesisVat(name, bundleName || name, parameters, creationOptions);
384393
}
385-
await Promise.all(vats);
386394
}
387395

388396
// start() may queue bootstrap if state doesn't say we did it already. It
@@ -413,6 +421,10 @@ export async function buildVatController(
413421
return kernel.step();
414422
},
415423

424+
async shutdown() {
425+
return kernel.shutdown();
426+
},
427+
416428
getStats() {
417429
return JSON.parse(JSON.stringify(kernel.getStats()));
418430
},

packages/SwingSet/src/kernel/dynamicVat.js

+14-26
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { assertKnownOptions } from '../assertOptions';
12
import { makeVatSlot } from '../parseVatSlots';
23

34
export function makeVatRootObjectSlot() {
@@ -35,17 +36,9 @@ export function makeDynamicVatCreator(stuff) {
3536
* citing this vatID
3637
*/
3738

38-
function createVatDynamically(vatSourceBundle, options = {}) {
39-
const {
40-
metered = true,
41-
creationOptions = {},
42-
vatParameters = {},
43-
...unknownOptions
44-
} = options;
45-
if (Object.keys(unknownOptions).length) {
46-
const msg = JSON.stringify(Object.keys(unknownOptions));
47-
throw Error(`createVatDynamically got unknown options ${msg}`);
48-
}
39+
function createVatDynamically(vatSourceBundle, dynamicOptions = {}) {
40+
assertKnownOptions(dynamicOptions, ['metered', 'vatParameters']);
41+
const { metered = true, vatParameters = {} } = dynamicOptions;
4942

5043
const vatID = allocateUnusedVatID();
5144
let terminated = false;
@@ -77,28 +70,23 @@ export function makeDynamicVatCreator(stuff) {
7770
);
7871
}
7972

80-
const managerOptions = {
81-
metered,
82-
notifyTermination: metered ? notifyTermination : undefined,
83-
vatPowerType: 'dynamic',
84-
creationOptions,
85-
vatParameters,
86-
};
87-
8873
async function build() {
8974
if (typeof vatSourceBundle !== 'object') {
9075
throw Error(
9176
`createVatDynamically() requires bundle, not a plain string`,
9277
);
9378
}
9479

95-
const manager = await vatManagerFactory.createFromBundle(
96-
vatSourceBundle,
97-
vatID,
98-
managerOptions,
99-
);
100-
const addOptions = {}; // enablePipelining:false
101-
addVatManager(vatID, manager, addOptions);
80+
const managerOptions = {
81+
bundle: vatSourceBundle,
82+
metered,
83+
enableSetup: false,
84+
enableInternalMetering: false,
85+
notifyTermination: metered ? notifyTermination : undefined,
86+
vatParameters,
87+
};
88+
const manager = await vatManagerFactory(vatID, managerOptions);
89+
addVatManager(vatID, manager, managerOptions);
10290
}
10391

10492
function makeSuccessResponse() {

packages/SwingSet/src/kernel/kernel.js

+63-55
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
import { makeMarshal, Remotable, getInterfaceOf } from '@agoric/marshal';
44
import { assert, details } from '@agoric/assert';
55
import { importBundle } from '@agoric/import-bundle';
6-
import { makeVatManagerFactory } from './vatManager/vatManager';
6+
import { assertKnownOptions } from '../assertOptions';
7+
import { makeVatManagerFactory } from './vatManager/factory';
78
import makeDeviceManager from './deviceManager';
89
import { wrapStorage } from './state/storageWrapper';
910
import makeKernelKeeper from './state/kernelKeeper';
@@ -82,16 +83,21 @@ export default function buildKernel(kernelEndowments) {
8283
// in the kernel table, promises and resolvers are both indexed by the same
8384
// value. kernelPromises[promiseID] = { decider, subscribers }
8485

85-
// The 'staticVatPowers' are given to vats as arguments of setup().
86-
// Liveslots provides them, and more, as the only argument to
87-
// buildRootObject(). They represent controlled authorities that come from
88-
// the kernel but that do not go through the syscall mechanism (so they
89-
// aren't included in the replay transcript), so they must not be
90-
// particularly stateful. If any of them behave differently from one
91-
// invocation to the next, the vat code which uses it will not be a
92-
// deterministic function of the transcript, breaking our
93-
// orthogonal-persistence model. They can have access to state, but they
94-
// must not let it influence the data they return to the vat.
86+
// The "Vat Powers" are given to vats as arguments of setup(). Liveslots
87+
// provides them, and more, as the only argument to buildRootObject(). They
88+
// represent controlled authorities that come from the kernel but that do
89+
// not go through the syscall mechanism (so they aren't included in the
90+
// replay transcript), so they must not be particularly stateful. If any of
91+
// them behave differently from one invocation to the next, the vat code
92+
// which uses it will not be a deterministic function of the transcript,
93+
// breaking our orthogonal-persistence model. They can have access to
94+
// state, but they must not let it influence the data they return to the
95+
// vat.
96+
97+
// Not all vats get all powers. We're phasing out in-vat metering, so
98+
// `makeGetMeter` and `transformMetering` are only available to static vats
99+
// in a local worker, and will eventually go away entirely once Spawner
100+
// uses dynamic vats.
95101

96102
// These will eventually be provided by the in-worker supervisor instead.
97103

@@ -101,9 +107,9 @@ export default function buildKernel(kernelEndowments) {
101107
// maybe transformMetering) are imported by the vat, not passed in an
102108
// argument. The powerful one (makeGetMeter) should only be given to the
103109
// root object, to share with (or withhold from) other objects as it sees
104-
// fit. TODO: makeGetMeter and transformMetering will go away once #1288
105-
// lands and zoe no longer needs to do metering within a vat.
106-
const staticVatPowers = harden({
110+
// fit. TODO: makeGetMeter and transformMetering will go away
111+
112+
const allVatPowers = harden({
107113
Remotable,
108114
getInterfaceOf,
109115
makeGetMeter: meterManager.makeGetMeter,
@@ -114,14 +120,6 @@ export default function buildKernel(kernelEndowments) {
114120
testLog,
115121
});
116122

117-
// dynamic vats don't get control over their own metering, nor testLog
118-
const dynamicVatPowers = harden({
119-
Remotable,
120-
getInterfaceOf,
121-
transformTildot: (...args) =>
122-
meterManager.runWithoutGlobalMeter(transformTildot, ...args),
123-
});
124-
125123
function vatNameToID(name) {
126124
const vatID = kernelKeeper.getVatIDForName(name);
127125
insistVatID(vatID);
@@ -405,21 +403,20 @@ export default function buildKernel(kernelEndowments) {
405403
if (typeof setup !== 'function') {
406404
throw Error(`setup is not a function, rather ${setup}`);
407405
}
408-
const knownCreationOptions = new Set(['enablePipelining', 'metered']);
409-
for (const k of Object.keys(creationOptions)) {
410-
if (!knownCreationOptions.has(k)) {
411-
throw Error(`unknown option ${k}`);
412-
}
413-
}
414-
406+
assertKnownOptions(creationOptions, ['enablePipelining', 'metered']);
415407
if (started) {
416408
throw Error(`addGenesisVat() cannot be called after kernel.start`);
417409
}
418410
if (genesisVats.has(name)) {
419411
throw Error(`vatID ${name} already added`);
420412
}
421-
422-
genesisVats.set(name, { setup, vatParameters, creationOptions });
413+
const managerOptions = {
414+
...creationOptions,
415+
setup,
416+
enableSetup: true,
417+
vatParameters,
418+
};
419+
genesisVats.set(name, managerOptions);
423420
}
424421

425422
function addGenesisVat(
@@ -434,19 +431,33 @@ export default function buildKernel(kernelEndowments) {
434431
if (typeof bundle !== 'object') {
435432
throw Error(`bundle is not an object, rather ${bundle}`);
436433
}
437-
const knownCreationOptions = new Set(['enablePipelining', 'metered']);
438-
for (const k of Object.getOwnPropertyNames(creationOptions)) {
439-
if (!knownCreationOptions.has(k)) {
440-
throw Error(`unknown option ${k}`);
441-
}
442-
}
434+
assertKnownOptions(creationOptions, [
435+
'enablePipelining',
436+
'metered',
437+
'managerType',
438+
'enableSetup',
439+
'enableInternalMetering',
440+
]);
443441
if (started) {
444442
throw Error(`addGenesisVat() cannot be called after kernel.start`);
445443
}
446444
if (genesisVats.has(name)) {
447445
throw Error(`vatID ${name} already added`);
448446
}
449-
genesisVats.set(name, { bundle, vatParameters, creationOptions });
447+
// TODO: We need to support within-vat metering (for the Spawner) until
448+
// #1343 is fixed, after which we can remove
449+
// managerOptions.enableInternalMetering . For now, it needs to be
450+
// enabled for our internal unit test (which could easily add this to its
451+
// config object) and for the spawner vat (not so easy). To avoid deeper
452+
// changes, we enable it for *all* static vats here. Once #1343 is fixed,
453+
// remove this addition and all support for internal metering.
454+
const managerOptions = {
455+
...creationOptions,
456+
bundle,
457+
enableInternalMetering: true,
458+
vatParameters,
459+
};
460+
genesisVats.set(name, managerOptions);
450461
}
451462

452463
function addGenesisDevice(name, bundle, endowments) {
@@ -547,11 +558,10 @@ export default function buildKernel(kernelEndowments) {
547558
}
548559

549560
const vatManagerFactory = makeVatManagerFactory({
550-
dynamicVatPowers,
561+
allVatPowers,
551562
kernelKeeper,
552563
makeVatEndowments,
553564
meterManager,
554-
staticVatPowers,
555565
testLog,
556566
transformMetering,
557567
waitUntilQuiescent,
@@ -565,13 +575,13 @@ export default function buildKernel(kernelEndowments) {
565575
* might tell the manager to replay the transcript later, if it notices
566576
* we're reloading a saved state vector.
567577
*/
568-
function addVatManager(vatID, manager, creationOptions) {
578+
function addVatManager(vatID, manager, managerOptions) {
569579
// addVatManager takes a manager, not a promise for one
570580
assert(
571581
manager.deliver && manager.setVatSyscallHandler,
572582
`manager lacks .deliver, isPromise=${manager instanceof Promise}`,
573583
);
574-
const { enablePipelining = false } = creationOptions;
584+
const { enablePipelining = false } = managerOptions;
575585
// This should create the vatKeeper. Other users get it from the
576586
// kernelKeeper, so we don't need a reference ourselves.
577587
kernelKeeper.allocateVatKeeperIfNeeded(vatID);
@@ -715,22 +725,12 @@ export default function buildKernel(kernelEndowments) {
715725

716726
// instantiate all vats
717727
for (const name of genesisVats.keys()) {
718-
const { setup, bundle, vatParameters, creationOptions } = genesisVats.get(
719-
name,
720-
);
728+
const managerOptions = genesisVats.get(name);
721729
const vatID = kernelKeeper.allocateVatIDForNameIfNeeded(name);
722730
console.debug(`Assigned VatID ${vatID} for genesis vat ${name}`);
723731
// eslint-disable-next-line no-await-in-loop
724-
const manager = await (setup
725-
? vatManagerFactory.createFromSetup(setup, vatID)
726-
: vatManagerFactory.createFromBundle(bundle, vatID, {
727-
metered: false,
728-
vatPowerType: 'static',
729-
allowSetup: true, // TODO: only needed by comms, disallow elsewhere
730-
vatParameters,
731-
creationOptions,
732-
}));
733-
addVatManager(vatID, manager, creationOptions);
732+
const manager = await vatManagerFactory(vatID, managerOptions);
733+
addVatManager(vatID, manager, managerOptions);
734734
}
735735

736736
function createVatDynamicallyByName(bundleName, options) {
@@ -854,6 +854,12 @@ export default function buildKernel(kernelEndowments) {
854854
return count;
855855
}
856856

857+
// mostly used by tests, only needed with thread/process-based workers
858+
function shutdown() {
859+
const vatRecs = Array.from(ephemeral.vats.values());
860+
return Promise.all(vatRecs.map(rec => rec.manager.shutdown()));
861+
}
862+
857863
const kernel = harden({
858864
// these are meant for the controller
859865
addGenesisVat,
@@ -867,6 +873,8 @@ export default function buildKernel(kernelEndowments) {
867873
step,
868874
run,
869875

876+
shutdown,
877+
870878
// the rest are for testing and debugging
871879

872880
addGenesisVatSetup,

0 commit comments

Comments
 (0)