Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(swingset): xsnap vat worker #2225

Merged
merged 23 commits into from
Jan 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
1ff6034
feat(xsnap): setImmediate and print
dckc Jan 19, 2021
48d9b8b
build(xsnap): don't set mxDebug in release builds
dckc Jan 20, 2021
6aefa2f
build(xsnap): build GOAL=debug too
dckc Jan 22, 2021
7caa380
fix(xsnap): don't swallow error message
dckc Jan 22, 2021
19ad452
feat(xsnap): return data from xsnap.evaluate()
dckc Jan 22, 2021
7119d81
chore(xs-vat-worker): prune obsolete dependencies
dckc Jan 16, 2021
98d0b2c
build(xs-vat-worker): moddable submodule is obsolete
dckc Jan 16, 2021
9ba6e65
style(xs-vat-worker): use canonical @agoric style
dckc Jan 16, 2021
51a30cd
feat(xs-vat-worker): TextDecoder, HandledPromise before lockdown
dckc Jan 18, 2021
69d08c7
refactor(SwingSet): unify vat-worker filenames
dckc Jan 15, 2021
961f0b5
feat(swingset): xsnap vat manager
dckc Jan 17, 2021
a19da62
fix: crank 1 comment in vat-target.js
dckc Jan 23, 2021
c16d6ce
fix: supply groupCollapsed etc. in console-shim for SES
dckc Jan 23, 2021
454362d
fix(xsnap): handle edge cases in sending replies to e, ?
dckc Jan 23, 2021
63e2eb1
refactor: avoid 2nd round trip to xsnap
dckc Jan 23, 2021
58dfb17
feat(xsnap worker): pass console log messages to manager
dckc Jan 23, 2021
7df4d3b
fix(xsnap): build args
dckc Jan 23, 2021
f1b2000
refactor(xsnap): fold in what's left of xs-vat-worker
dckc Jan 23, 2021
1ebf699
chore(xsnap): move lockdown-shim out of src/ to avoid tsc errors
dckc Jan 23, 2021
aab7d0d
docs(xsnap): document XS handleCommand async idiom
dckc Jan 23, 2021
701e2f4
refactor: build XS bundles with Kernel bundles
dckc Jan 23, 2021
d200428
fix(xsnap worker): update syscall API to use .resolve()
dckc Jan 24, 2021
37a658c
chore(xsnap): provide non-trivial console in start compartment
dckc Jan 24, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
[submodule "packages/xs-vat-worker/moddable"]
path = packages/xs-vat-worker/moddable
url = https://github.com/agoric-labs/moddable.git
branch = ag-linux-cli
[submodule "packages/xsnap/moddable"]
path = packages/xsnap/moddable
url = https://github.com/Moddable-OpenSource/moddable.git
2 changes: 1 addition & 1 deletion packages/SwingSet/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
"@agoric/tame-metering": "^1.3.0",
"@agoric/transform-eventual-send": "^1.4.0",
"@agoric/transform-metering": "^1.4.0",
"@agoric/xs-vat-worker": "^0.4.0",
"@agoric/xsnap": "^0.1.0",
"@babel/core": "^7.5.0",
"@babel/generator": "^7.6.4",
"anylogger": "^0.21.0",
Expand Down
33 changes: 23 additions & 10 deletions packages/SwingSet/src/controller.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import fs from 'fs';
import path from 'path';
import process from 'process';
import re2 from 're2';
import { spawn } from 'child_process';
import { type as osType } from 'os';
import { Worker } from 'worker_threads';
import * as babelCore from '@babel/core';
import * as babelParser from '@agoric/babel-parser';
Expand All @@ -14,7 +15,7 @@ import { importBundle } from '@agoric/import-bundle';
import { initSwingStore } from '@agoric/swing-store-simple';
import { makeMeteringTransformer } from '@agoric/transform-metering';
import { makeTransform } from '@agoric/transform-eventual-send';
import { locateWorkerBin } from '@agoric/xs-vat-worker';
import { xsnap } from '@agoric/xsnap';

import { WeakRef, FinalizationRegistry } from './weakref';
import { startSubprocessWorker } from './spawnSubprocessWorker';
Expand Down Expand Up @@ -144,24 +145,36 @@ export async function makeSwingsetController(
// TODO: after we move away from `-r esm` and use real ES6 modules, point
// this at nodeWorkerSupervisor.js instead of the CJS intermediate
const supercode = require.resolve(
'./kernel/vatManager/nodeWorkerSupervisorCJS.js',
'./kernel/vatManager/supervisor-nodeworker-cjs.js',
);
return new Worker(supercode);
}

// launch a worker in a subprocess (which runs Node.js)
function startSubprocessWorkerNode() {
const supercode = require.resolve(
'./kernel/vatManager/subprocessSupervisor.js',
'./kernel/vatManager/supervisor-subprocess-node.js',
);
return startSubprocessWorker(process.execPath, ['-r', 'esm', supercode]);
}

let startSubprocessWorkerXS;
const xsWorkerBin = locateWorkerBin({ resolve: path.resolve });
if (fs.existsSync(xsWorkerBin)) {
startSubprocessWorkerXS = () => startSubprocessWorker(xsWorkerBin);
}
const startXSnap = (name, handleCommand) => {
const worker = xsnap({
os: osType(),
spawn,
handleCommand,
name,
stdout: 'inherit',
stderr: 'inherit',
// debug: true,
});

const bundles = {
lockdown: JSON.parse(hostStorage.get('lockdownBundle')),
supervisor: JSON.parse(hostStorage.get('supervisorBundle')),
};
return harden({ worker, bundles });
};

const slogF =
slogFile && (await fs.createWriteStream(slogFile, { flags: 'a' })); // append
Expand All @@ -186,7 +199,7 @@ export async function makeSwingsetController(
transformTildot,
makeNodeWorker,
startSubprocessWorkerNode,
startSubprocessWorkerXS,
startXSnap,
writeSlogObject,
WeakRef,
FinalizationRegistry,
Expand Down
42 changes: 25 additions & 17 deletions packages/SwingSet/src/initializeSwingset.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,33 @@ import { initSwingStore } from '@agoric/swing-store-simple';
import { insistStorageAPI } from './storageAPI';
import { initializeKernel } from './kernel/initializeKernel';

const zip = (xs, ys) => xs.map((x, i) => [x, ys[i]]);
const { keys, values, fromEntries } = Object;
const allValues = async obj =>
fromEntries(zip(keys(obj), await Promise.all(values(obj))));

/**
* Build the kernel source bundles.
*
* Build the source bundles for the kernel and xsnap vat worker.
*/
export async function buildKernelBundles() {
// this takes 2.7s on my computer
const sources = {
kernel: require.resolve('./kernel/kernel.js'),
adminDevice: require.resolve('./kernel/vatAdmin/vatAdmin-src'),
adminVat: require.resolve('./kernel/vatAdmin/vatAdminWrapper'),
comms: require.resolve('./vats/comms'),
vattp: require.resolve('./vats/vat-tp'),
timer: require.resolve('./vats/vat-timerWrapper'),
};
const kernelBundles = {};
for (const name of Object.keys(sources)) {
// this was harder to read with Promise.all
// eslint-disable-next-line no-await-in-loop
kernelBundles[name] = await bundleSource(sources[name]);
}
return harden(kernelBundles);

const src = rel => bundleSource(require.resolve(rel));
const srcGE = rel => bundleSource(require.resolve(rel), 'getExport');

const bundles = await allValues({
kernel: src('./kernel/kernel.js'),
adminDevice: src('./kernel/vatAdmin/vatAdmin-src'),
adminVat: src('./kernel/vatAdmin/vatAdminWrapper'),
comms: src('./vats/comms'),
vattp: src('./vats/vat-tp'),
timer: src('./vats/vat-timerWrapper'),

lockdown: srcGE('./kernel/vatManager/lockdown-subprocess-xsnap.js'),
supervisor: srcGE('./kernel/vatManager/supervisor-subprocess-xsnap.js'),
});

return harden(bundles);
}

function byName(a, b) {
Expand Down Expand Up @@ -246,6 +252,8 @@ export async function initializeSwingset(
const { kernelBundles = await buildKernelBundles() } = initializationOptions;

hostStorage.set('kernelBundle', JSON.stringify(kernelBundles.kernel));
hostStorage.set('lockdownBundle', JSON.stringify(kernelBundles.lockdown));
hostStorage.set('supervisorBundle', JSON.stringify(kernelBundles.supervisor));

if (config.bootstrap && argv) {
if (config.vats[config.bootstrap]) {
Expand Down
4 changes: 2 additions & 2 deletions packages/SwingSet/src/kernel/kernel.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export default function buildKernel(
transformTildot,
makeNodeWorker,
startSubprocessWorkerNode,
startSubprocessWorkerXS,
startXSnap,
writeSlogObject,
WeakRef,
FinalizationRegistry,
Expand Down Expand Up @@ -560,7 +560,7 @@ export default function buildKernel(
waitUntilQuiescent,
makeNodeWorker,
startSubprocessWorkerNode,
startSubprocessWorkerXS,
startXSnap,
gcTools,
});

Expand Down
16 changes: 7 additions & 9 deletions packages/SwingSet/src/kernel/vatManager/factory.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { assert } from '@agoric/assert';
import { assertKnownOptions } from '../../assertOptions';
import { makeLocalVatManagerFactory } from './localVatManager';
import { makeNodeWorkerVatManagerFactory } from './nodeWorker';
import { makeNodeSubprocessFactory } from './worker-subprocess-node';
import { makeLocalVatManagerFactory } from './manager-local';
import { makeNodeWorkerVatManagerFactory } from './manager-nodeworker';
import { makeNodeSubprocessFactory } from './manager-subprocess-node';
import { makeXsSubprocessFactory } from './manager-subprocess-xsnap';

export function makeVatManagerFactory({
allVatPowers,
Expand All @@ -13,7 +14,7 @@ export function makeVatManagerFactory({
waitUntilQuiescent,
makeNodeWorker,
startSubprocessWorkerNode,
startSubprocessWorkerXS,
startXSnap,
gcTools,
}) {
const localFactory = makeLocalVatManagerFactory({
Expand All @@ -40,8 +41,8 @@ export function makeVatManagerFactory({
decref: gcTools.decref,
});

const xsWorkerFactory = makeNodeSubprocessFactory({
startSubprocessWorker: startSubprocessWorkerXS,
const xsWorkerFactory = makeXsSubprocessFactory({
startXSnap,
kernelKeeper,
testLog: allVatPowers.testLog,
decref: gcTools.decref,
Expand Down Expand Up @@ -98,9 +99,6 @@ export function makeVatManagerFactory({
}

if (managerType === 'xs-worker') {
if (!startSubprocessWorkerXS) {
throw new Error('manager type xs-worker not available');
}
return xsWorkerFactory.createFromBundle(vatID, bundle, managerOptions);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import '@agoric/xsnap/lib/bootstrap';
183 changes: 183 additions & 0 deletions packages/SwingSet/src/kernel/vatManager/manager-subprocess-xsnap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
// @ts-check
import { assert, details } from '@agoric/assert';
import { makeTranscriptManager } from './transcript';
import { createSyscall } from './syscall';

// eslint-disable-next-line no-unused-vars
function parentLog(first, ...args) {
// console.error(`--parent: ${first}`, ...args);
}

const encoder = new TextEncoder();
const decoder = new TextDecoder();

/**
* @param {{
* startXSnap: (name: string, handleCommand: SyncHandler) => { worker: XSnap, bundles: Record<string, ExportBundle> },
* kernelKeeper: KernelKeeper,
* testLog: (...args: unknown[]) => void,
* decref: (vatID: unknown, vref: unknown, count: number) => void,
* }} tools
* @returns { VatManagerFactory }
*
* @typedef { { moduleFormat: 'getExport', source: string } } ExportBundle
* @typedef { (msg: Uint8Array) => Uint8Array } SyncHandler
* @typedef { ReturnType<typeof import('@agoric/xsnap').xsnap> } XSnap
dckc marked this conversation as resolved.
Show resolved Hide resolved
* @typedef { ReturnType<typeof import('../state/kernelKeeper').default> } KernelKeeper
* @typedef { ReturnType<typeof import('./manager-nodeworker').makeNodeWorkerVatManagerFactory> } VatManagerFactory
* @typedef { [unknown, ...unknown[]] } Tagged
*/
export function makeXsSubprocessFactory({
startXSnap,
kernelKeeper,
testLog,
decref,
}) {
/**
* @param { unknown } vatID
* @param { unknown } bundle
* @param { ManagerOptions } managerOptions
*/
async function createFromBundle(vatID, bundle, managerOptions) {
parentLog('createFromBundle', { vatID });
const { vatParameters, virtualObjectCacheSize } = managerOptions;
assert(!managerOptions.metered, 'not supported yet');
assert(!managerOptions.enableSetup, 'not supported at all');
if (managerOptions.enableInternalMetering) {
// TODO: warn+ignore, rather than throw, because the kernel enables it
// for all vats, because the Spawner still needs it. When the kernel
// stops doing that, turn this into a regular assert
console.log(`xsnap worker does not support enableInternalMetering`);
}
const vatKeeper = kernelKeeper.getVatKeeper(vatID);
const transcriptManager = makeTranscriptManager(
kernelKeeper,
vatKeeper,
vatID,
);

const { doSyscall, setVatSyscallHandler } = createSyscall(
transcriptManager,
);

/** @type { (vatSyscallObject: Tagged) => unknown } */
function handleSyscall(vatSyscallObject) {
return doSyscall(vatSyscallObject);
}

/** @type { (vref: unknown, count: number) => void } */
function vatDecref(vref, count) {
decref(vatID, vref, count);
}

/** @type { (item: Tagged) => unknown } */
function handleUpstream([type, ...args]) {
kriskowal marked this conversation as resolved.
Show resolved Hide resolved
parentLog(`handleUpstream`, type, args.length);
switch (type) {
case 'syscall': {
parentLog(`syscall`, args);
const [scTag, ...vatSyscallArgs] = args;
return handleSyscall([scTag, ...vatSyscallArgs]);
}
case 'console': {
const [level, tag, ...rest] = args;
if (typeof level === 'string' && level in console) {
console[level](tag, ...rest);
} else {
console.error('bad console level', level);
}
return ['ok'];
}
case 'testLog':
testLog(...args);
return ['OK'];
case 'decref': {
const [vref, count] = args;
assert(typeof count === 'number');
vatDecref(vref, count);
return ['OK'];
}
default:
throw new Error(`unrecognized uplink message ${type}`);
}
}

/** @type { (msg: Uint8Array) => Uint8Array } */
function handleCommand(msg) {
parentLog('handleCommand', { length: msg.byteLength });
const tagged = handleUpstream(JSON.parse(decoder.decode(msg)));
return encoder.encode(JSON.stringify(tagged));
}

// start the worker and establish a connection
const { worker, bundles } = startXSnap(`${vatID}`, handleCommand);
for await (const [it, superCode] of Object.entries(bundles)) {
parentLog('bundle', it);
assert(
superCode.moduleFormat === 'getExport',
details`${it} unexpected: ${superCode.moduleFormat}`,
);
await worker.evaluate(`(${superCode.source}\n)()`.trim());
}

/** @type { (item: Tagged) => Promise<Tagged> } */
async function issueTagged(item) {
parentLog('issueTagged', item[0]);
const txt = await worker.issueStringCommand(JSON.stringify(item));
const reply = JSON.parse(txt);
assert(Array.isArray(reply));
dckc marked this conversation as resolved.
Show resolved Hide resolved
const [tag, ...rest] = reply;
return [tag, ...rest];
}

parentLog(`instructing worker to load bundle..`);
const bundleReply = await issueTagged([
'setBundle',
vatID,
bundle,
vatParameters,
virtualObjectCacheSize,
]);
if (bundleReply[0] === 'dispatchReady') {
parentLog(`bundle loaded. dispatch ready.`);
} else {
throw new Error(`failed to setBundle: ${bundleReply}`);
}

/** @type { (item: Tagged) => Promise<Tagged> } */
async function deliver(delivery) {
parentLog(`sending delivery`, delivery);
const result = await issueTagged(['deliver', ...delivery]);
parentLog(`deliverDone`, result[0], result.length);
return result;
}

async function replayTranscript() {
transcriptManager.startReplay();
for (const t of vatKeeper.getTranscript()) {
transcriptManager.checkReplayError();
transcriptManager.startReplayDelivery(t.syscalls);
// eslint-disable-next-line no-await-in-loop
await deliver(t.d);
}
transcriptManager.checkReplayError();
transcriptManager.finishReplay();
}

function shutdown() {
return worker.close();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@warner I’m open to changing xsnapWorker.close() to xsnapWorker.shutdown() for one less name-is-different/behavior-is-same red herring lingering among names.

}

const manager = harden({
replayTranscript,
setVatSyscallHandler,
deliver,
shutdown,
});

parentLog('manager', Object.keys(manager));
return manager;
}

return harden({ createFromBundle });
}
Loading