diff --git a/packages/xsnap/src/xsnap.js b/packages/xsnap/src/xsnap.js index fac8b1c7fee..840f57dc8d5 100644 --- a/packages/xsnap/src/xsnap.js +++ b/packages/xsnap/src/xsnap.js @@ -56,6 +56,76 @@ function echoCommand(arg) { const safeHintFromDescription = description => description.replaceAll(/[^a-zA-Z0-9_.-]/g, '-'); +/** + * @typedef {object} SnapshotLoader + * @property {string} snapPath + * @property {(destStream?: Writable) => Promise} afterSpawn + * @property {() => Promise} cleanup + */ + +/** + * @callback MakeSnapshotLoader + * @param {AsyncIterable} sourceBytes + * @param {string} description + * @param {{fs: Pick, ptmpName: (opts: import('tmp').TmpNameOptions) => Promise}} ioPowers + * @returns {Promise} + */ + +/** @type {MakeSnapshotLoader} */ +const makeSnapshotLoaderWithFS = async ( + sourceBytes, + description, + { fs, ptmpName }, +) => { + const snapPath = await ptmpName({ + template: `load-snapshot-${safeHintFromDescription(description)}-XXXXXX.xss`, + }); + + const afterSpawn = async () => {}; + const cleanup = async () => fs.unlink(snapPath); + + try { + const tmpSnap = await fs.open(snapPath, 'w'); + // @ts-expect-error incorrect typings; writeFile does support AsyncIterable + await tmpSnap.writeFile(sourceBytes); + await tmpSnap.close(); + } catch (e) { + await cleanup(); + throw e; + } + + return harden({ + snapPath, + afterSpawn, + cleanup, + }); +}; + +/** @type {MakeSnapshotLoader} */ +const makeSnapshotLoaderWithPipe = async ( + sourceBytes, + description, + _ioPowers, +) => { + let done = Promise.resolve(); + + const cleanup = async () => done; + + const afterSpawn = async destStream => { + const sourceStream = Readable.from(sourceBytes); + sourceStream.pipe(destStream, { end: false }); + + done = finished(sourceStream); + void done.catch(noop).then(() => sourceStream.unpipe(destStream)); + }; + + return harden({ + snapPath: `@${SNAPSHOT_LOAD_FD}:${safeHintFromDescription(description)}`, + afterSpawn, + cleanup, + }); +}; + /** * @param {XSnapOptions} options * @@ -87,7 +157,7 @@ export async function xsnap(options) { netstringMaxChunkSize = undefined, parserBufferSize = undefined, snapshotStream, - snapshotDescription = snapshotStream && 'unknown', + snapshotDescription = 'unknown', snapshotUseFs = false, stdout = 'ignore', stderr = 'ignore', @@ -105,58 +175,6 @@ export async function xsnap(options) { throw Error(`xsnap does not support platform ${os}`); } - /** @type {(opts: import('tmp').TmpNameOptions) => Promise} */ - const ptmpName = fs.tmpName && promisify(fs.tmpName); - - const makeLoadSnapshotHandlerWithFS = async sourceBytes => { - const snapPath = await ptmpName({ - template: `load-snapshot-${safeHintFromDescription( - snapshotDescription, - )}-XXXXXX.xss`, - }); - - const afterSpawn = async () => {}; - const cleanup = async () => fs.unlink(snapPath); - - try { - const tmpSnap = await fs.open(snapPath, 'w'); - await tmpSnap.writeFile(sourceBytes); - await tmpSnap.close(); - } catch (e) { - await cleanup(); - throw e; - } - - return harden({ - snapPath, - afterSpawn, - cleanup, - }); - }; - - const makeLoadSnapshotHandlerWithPipe = async sourceBytes => { - let done = Promise.resolve(); - - const cleanup = async () => done; - - /** @param {Writable} destStream */ - const afterSpawn = async destStream => { - const sourceStream = Readable.from(sourceBytes); - sourceStream.pipe(destStream, { end: false }); - - done = finished(sourceStream); - void done.catch(noop).then(() => sourceStream.unpipe(destStream)); - }; - - return harden({ - snapPath: `@${SNAPSHOT_LOAD_FD}:${safeHintFromDescription( - snapshotDescription, - )}`, - afterSpawn, - cleanup, - }); - }; - let bin = new URL( `../xsnap-native/xsnap/build/bin/${platform}/${ debug ? 'debug' : 'release' @@ -169,15 +187,18 @@ export async function xsnap(options) { assert(!/^-/.test(name), `name '${name}' cannot start with hyphen`); - let loadSnapshotHandler = await (snapshotStream && - (snapshotUseFs - ? makeLoadSnapshotHandlerWithFS - : makeLoadSnapshotHandlerWithPipe)(snapshotStream)); + /** @type {(opts: import('tmp').TmpNameOptions) => Promise} */ + const ptmpName = fs.tmpName && promisify(fs.tmpName); + const makeSnapshotLoader = snapshotUseFs + ? makeSnapshotLoaderWithFS + : makeSnapshotLoaderWithPipe; + let snapshotLoader = await (snapshotStream && + makeSnapshotLoader(snapshotStream, snapshotDescription, { fs, ptmpName })); let args = [name]; - if (loadSnapshotHandler) { - args.push('-r', loadSnapshotHandler.snapPath); + if (snapshotLoader) { + args.push('-r', snapshotLoader.snapPath); } if (meteringLimit) { @@ -248,13 +269,13 @@ export async function xsnap(options) { const snapshotSaveStream = xsnapProcessStdio[SNAPSHOT_SAVE_FD]; const snapshotLoadStream = xsnapProcessStdio[SNAPSHOT_LOAD_FD]; - await loadSnapshotHandler?.afterSpawn(snapshotLoadStream); + await snapshotLoader?.afterSpawn(snapshotLoadStream); - if (loadSnapshotHandler) { + if (snapshotLoader) { void vatExit.promise.catch(noop).then(() => { - if (loadSnapshotHandler) { - const { cleanup } = loadSnapshotHandler; - loadSnapshotHandler = undefined; + if (snapshotLoader) { + const { cleanup } = snapshotLoader; + snapshotLoader = undefined; return cleanup(); } }); @@ -276,9 +297,9 @@ export async function xsnap(options) { async function runToIdle() { for await (const _ of forever) { const iteration = await messagesFromXsnap.next(undefined); - if (loadSnapshotHandler) { - const { cleanup } = loadSnapshotHandler; - loadSnapshotHandler = undefined; + if (snapshotLoader) { + const { cleanup } = snapshotLoader; + snapshotLoader = undefined; await cleanup(); } if (iteration.done) {