diff --git a/packages/SwingSet/package.json b/packages/SwingSet/package.json index 06294ea34c07..2925158b4ac5 100644 --- a/packages/SwingSet/package.json +++ b/packages/SwingSet/package.json @@ -53,6 +53,7 @@ "@babel/core": "^7.5.0", "@babel/generator": "^7.6.4", "@endo/base64": "^0.1.0", + "@types/tmp": "^0.2.0", "anylogger": "^0.21.0", "esm": "^3.2.5", "re2": "^1.10.5", @@ -60,6 +61,7 @@ "rollup-plugin-node-resolve": "^5.2.0", "semver": "^6.3.0", "ses": "^0.12.6", + "tmp": "^0.2.1", "yargs": "^14.2.0" }, "files": [ diff --git a/packages/SwingSet/src/controller.js b/packages/SwingSet/src/controller.js index 48081e194e4c..578fede85cc9 100644 --- a/packages/SwingSet/src/controller.js +++ b/packages/SwingSet/src/controller.js @@ -1,5 +1,6 @@ /* global require */ import fs from 'fs'; +import path from 'path'; import process from 'process'; import re2 from 're2'; import { spawn } from 'child_process'; @@ -9,6 +10,7 @@ import * as babelCore from '@babel/core'; import * as babelParser from '@agoric/babel-parser'; import babelGenerate from '@babel/generator'; import anylogger from 'anylogger'; +import { tmpName } from 'tmp'; import { assert, details as X } from '@agoric/assert'; import { isTamed, tameMetering } from '@agoric/tame-metering'; @@ -16,7 +18,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 { xsnap } from '@agoric/xsnap'; +import { xsnap, makeSnapstore } from '@agoric/xsnap'; import { WeakRef, FinalizationRegistry } from './weakref'; import { startSubprocessWorker } from './spawnSubprocessWorker'; @@ -42,6 +44,56 @@ function unhandledRejectionHandler(e) { console.error('UnhandledPromiseRejectionWarning:', e); } +function makeStartXSnap(bundles, { snapstorePath, env }) { + const xsnapOpts = { + os: osType(), + spawn, + stdout: 'inherit', + stderr: 'inherit', + debug: !!env.XSNAP_DEBUG, + }; + + let snapStore; + + if (snapstorePath) { + fs.mkdirSync(snapstorePath, { recursive: true }); + + snapStore = makeSnapstore(snapstorePath, { + tmpName, + existsSync: fs.existsSync, + createReadStream: fs.createReadStream, + createWriteStream: fs.createWriteStream, + rename: fs.promises.rename, + unlink: fs.promises.unlink, + resolve: path.resolve, + }); + } + + let supervisorHash = ''; + return async function startXSnap(name, handleCommand) { + if (supervisorHash) { + return snapStore.load(supervisorHash, async snapshot => { + const xs = xsnap({ snapshot, name, handleCommand, ...xsnapOpts }); + await xs.evaluate('null'); // ensure that spawn is done + return xs; + }); + } + const worker = xsnap({ handleCommand, name, ...xsnapOpts }); + + for await (const bundle of bundles) { + assert( + bundle.moduleFormat === 'getExport', + X`unexpected: ${bundle.moduleFormat}`, + ); + await worker.evaluate(`(${bundle.source}\n)()`.trim()); + } + if (snapStore) { + supervisorHash = await snapStore.save(async fn => worker.snapshot(fn)); + } + return worker; + }; +} + export async function makeSwingsetController( hostStorage = initSwingStore().storage, deviceEndowments = {}, @@ -59,6 +111,7 @@ export async function makeSwingsetController( slogCallbacks, slogFile, testTrackDecref, + snapstorePath, } = runtimeOptions; if (typeof Compartment === 'undefined') { throw Error('SES must be installed before calling makeSwingsetController'); @@ -177,23 +230,11 @@ export async function makeSwingsetController( return startSubprocessWorker(process.execPath, ['-r', 'esm', supercode]); } - const startXSnap = (name, handleCommand) => { - const worker = xsnap({ - os: osType(), - spawn, - handleCommand, - name, - stdout: 'inherit', - stderr: 'inherit', - debug: !!env.XSNAP_DEBUG, - }); - - const bundles = { - lockdown: JSON.parse(hostStorage.get('lockdownBundle')), - supervisor: JSON.parse(hostStorage.get('supervisorBundle')), - }; - return harden({ worker, bundles }); - }; + const bundles = [ + JSON.parse(hostStorage.get('lockdownBundle')), + JSON.parse(hostStorage.get('supervisorBundle')), + ]; + const startXSnap = makeStartXSnap(bundles, { snapstorePath, env }); const slogF = slogFile && (await fs.createWriteStream(slogFile, { flags: 'a' })); // append @@ -323,12 +364,14 @@ export async function buildVatController( debugPrefix, slogCallbacks, testTrackDecref, + snapstorePath, } = runtimeOptions; const actualRuntimeOptions = { verbose, debugPrefix, testTrackDecref, slogCallbacks, + snapstorePath, }; const initializationOptions = { verbose, kernelBundles }; let bootstrapResult; diff --git a/packages/SwingSet/src/kernel/vatManager/manager-subprocess-xsnap.js b/packages/SwingSet/src/kernel/vatManager/manager-subprocess-xsnap.js index 104350dbb0df..109c3ab86cd2 100644 --- a/packages/SwingSet/src/kernel/vatManager/manager-subprocess-xsnap.js +++ b/packages/SwingSet/src/kernel/vatManager/manager-subprocess-xsnap.js @@ -18,7 +18,7 @@ const decoder = new TextDecoder(); * @param {{ * allVatPowers: VatPowers, * kernelKeeper: KernelKeeper, - * startXSnap: (name: string, handleCommand: SyncHandler) => { worker: XSnap, bundles: Record }, + * startXSnap: (name: string, handleCommand: SyncHandler) => Promise, * testLog: (...args: unknown[]) => void, * decref: (vatID: unknown, vref: unknown, count: number) => void, * }} tools @@ -128,15 +128,7 @@ export function makeXsSubprocessFactory({ } // start the worker and establish a connection - const { worker, bundles } = startXSnap(`${vatID}:${name}`, handleCommand); - for await (const [it, superCode] of Object.entries(bundles)) { - parentLog(vatID, 'eval bundle', it); - assert( - superCode.moduleFormat === 'getExport', - X`${it} unexpected: ${superCode.moduleFormat}`, - ); - await worker.evaluate(`(${superCode.source}\n)()`.trim()); - } + const worker = await startXSnap(`${vatID}:${name}`, handleCommand); /** @type { (item: Tagged) => Promise } */ async function issueTagged(item) {