Skip to content

Commit

Permalink
refactor(xsnap): Abstract snapshot loaders into top-level helpers
Browse files Browse the repository at this point in the history
  • Loading branch information
gibson042 committed May 24, 2024
1 parent 17d5522 commit 275a050
Showing 1 changed file with 81 additions and 67 deletions.
148 changes: 81 additions & 67 deletions packages/xsnap/src/xsnap.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,69 @@ function echoCommand(arg) {
const safeHintFromDescription = description =>
description.replaceAll(/[^a-zA-Z0-9_.-]/g, '-');

/**
* @callback MakeSnapshotLoader
* @param {AsyncIterable<Uint8Array>} sourceBytes
* @param {string} description
* @param {{fs: Pick<typeof import('fs/promises'), 'open' | 'unlink'>, ptmpName: (opts: import('tmp').TmpNameOptions) => Promise<string>}} ioPowers
*/

/** @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;

/** @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(description)}`,
afterSpawn,
cleanup,
});
};

/**
* @param {XSnapOptions} options
*
Expand Down Expand Up @@ -87,7 +150,7 @@ export async function xsnap(options) {
netstringMaxChunkSize = undefined,
parserBufferSize = undefined,
snapshotStream,
snapshotDescription = snapshotStream && 'unknown',
snapshotDescription = 'unknown',
snapshotUseFs = false,
stdout = 'ignore',
stderr = 'ignore',
Expand All @@ -105,58 +168,6 @@ export async function xsnap(options) {
throw Error(`xsnap does not support platform ${os}`);
}

/** @type {(opts: import('tmp').TmpNameOptions) => Promise<string>} */
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'
Expand All @@ -169,15 +180,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<string>} */
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) {
Expand Down Expand Up @@ -248,13 +262,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();
}
});
Expand All @@ -276,9 +290,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) {
Expand Down

0 comments on commit 275a050

Please sign in to comment.