Skip to content

Commit

Permalink
fix(xsnap): Iron out resolution types
Browse files Browse the repository at this point in the history
The xsnap controller passes a "baton" from the completion of one interaction with the xsnap subprocess to the next, regardless of whether the interaction completed sucessfully.  The xnsap controller can also kill the subprocess, and the child can terminate unexpectedly, so every interaction is a race to completion or the child process exiting.

In a prior solution to the baton passing, the result of an interaction could be a rejection. To allow the next interaction to commence, the baton would commence with the conclusion of the prior interaction, and if that produced an exception, the rejection would be caught and transformed into a resolution to an error.

This is weird.

This change makes both the child process exit and the baton promise the same type: Promise<void> and termination is always communicated as a rejection.

This change also adds a `terminate` method to allow a distinction between graceful close and forced termination.
  • Loading branch information
kriskowal authored and dckc committed Jan 21, 2021
1 parent c3c489e commit 1e2e10d
Showing 1 changed file with 29 additions and 19 deletions.
48 changes: 29 additions & 19 deletions packages/xsnap/src/xsnap.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export function xsnap(options) {
importMetaUrl,
).pathname;

/** @type {Deferred<Error?>} */
/** @type {Deferred<void>} */
const vatExit = defer();

const args = snapshot ? ['-r', snapshot] : [];
Expand All @@ -86,7 +86,7 @@ export function xsnap(options) {

xsnapProcess.on('exit', code => {
if (code === 0 || code === null) {
vatExit.resolve(null);
vatExit.resolve();
} else {
vatExit.reject(new Error(`${name} exited with code ${code}`));
}
Expand All @@ -103,8 +103,8 @@ export function xsnap(options) {
/** @type {AsyncIterable<Uint8Array>} */ (xsnapProcess.stdio[4]),
);

/** @type {Promise<Error?>} */
let baton = Promise.resolve(null);
/** @type {Promise<void>} */
let baton = Promise.resolve();

/**
* @returns {Promise<Uint8Array>}
Expand Down Expand Up @@ -133,43 +133,40 @@ export function xsnap(options) {

/**
* @param {string} code
* @returns {Promise<null>}
* @returns {Promise<void>}
*/
async function evaluate(code) {
const result = baton.then(async () => {
await messagesToXsnap.next(encoder.encode(`e${code}`));
await runToIdle();
return null;
});
baton = result.catch(() => null);
baton = result.catch(() => {});
return Promise.race([vatCancelled, result]);
}

/**
* @param {string} fileName
* @returns {Promise<Error?>}
* @returns {Promise<void>}
*/
async function execute(fileName) {
const result = baton.then(async () => {
await messagesToXsnap.next(encoder.encode(`s${fileName}`));
await runToIdle();
return null;
});
baton = result.catch(() => null);
baton = result.catch(() => {});
return Promise.race([vatCancelled, result]);
}

/**
* @param {string} fileName
* @returns {Promise<Error?>}
* @returns {Promise<void>}
*/
async function importModule(fileName) {
const result = baton.then(async () => {
await messagesToXsnap.next(encoder.encode(`m${fileName}`));
await runToIdle();
return null;
});
baton = result.catch(() => null);
baton = result.catch(() => {});
return Promise.race([vatCancelled, result]);
}

Expand All @@ -187,7 +184,7 @@ export function xsnap(options) {
});
baton = result.then(
() => {},
err => err,
() => {},
);
return Promise.race([vatCancelled, result]);
}
Expand All @@ -202,30 +199,43 @@ export function xsnap(options) {

/**
* @param {string} file
* @returns {Promise<Error?>}
* @returns {Promise<void>}
*/
async function writeSnapshot(file) {
baton = baton.then(async () => {
const result = baton.then(async () => {
await messagesToXsnap.next(encoder.encode(`w${file}`));
return runToIdle().catch(err => err);
await runToIdle();
});
baton = result.catch(() => {});
return Promise.race([vatExit.promise, baton]);
}

/**
* @returns {Promise<Error?>}
* @returns {Promise<void>}
*/
async function close() {
xsnapProcess.kill();
await messagesToXsnap.return();
baton = Promise.reject(new Error(`xsnap closed`));
baton.catch(() => {}); // Suppress Node.js unhandled exception warning.
return vatExit.promise;
}

/**
* @returns {Promise<void>}
*/
async function terminate() {
xsnapProcess.kill();
baton = Promise.reject(new Error(`xsnap closed`));
baton.catch(() => {}); // Suppress Node.js unhandled exception warning.
// Mute the vatExit exception: it is expected.
return vatExit.promise.catch(() => {});
}

return {
issueCommand,
issueStringCommand,
close,
terminate,
evaluate,
execute,
import: importModule,
Expand Down

0 comments on commit 1e2e10d

Please sign in to comment.