From 021c8770aeac4a98224f4f1d016a4e06f2d6daaf Mon Sep 17 00:00:00 2001 From: Siarhei Liakh Date: Thu, 5 Sep 2024 14:19:23 +0000 Subject: [PATCH 01/24] Fix(xsnap-native):update git subproject for xsnap EOF --- packages/xsnap/xsnap-native | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/xsnap/xsnap-native b/packages/xsnap/xsnap-native index eef9b67da55..ab1ad696ba0 160000 --- a/packages/xsnap/xsnap-native +++ b/packages/xsnap/xsnap-native @@ -1 +1 @@ -Subproject commit eef9b67da5517ed18ff9e0073b842db20924eae3 +Subproject commit ab1ad696ba09713b52e31f731fd368bff94a8839 From cbf490d40608b5720089a0af5d9c194a65174d74 Mon Sep 17 00:00:00 2001 From: Siarhei Liakh Date: Tue, 17 Sep 2024 16:06:57 +0000 Subject: [PATCH 02/24] fix(xsnap-worker): update submodule, add tests --- packages/xsnap/build.env | 2 +- packages/xsnap/src/xsnap.js | 4 +- packages/xsnap/test/xsnap-eof.test.js | 268 ++++++++++++++++++++++++++ packages/xsnap/xsnap-native | 2 +- 4 files changed, 272 insertions(+), 4 deletions(-) create mode 100644 packages/xsnap/test/xsnap-eof.test.js diff --git a/packages/xsnap/build.env b/packages/xsnap/build.env index b816a3002cd..af91dc3f82a 100644 --- a/packages/xsnap/build.env +++ b/packages/xsnap/build.env @@ -1,4 +1,4 @@ MODDABLE_URL=https://github.com/agoric-labs/moddable.git MODDABLE_COMMIT_HASH=f6c5951fc055e4ca592b9166b9ae3cbb9cca6bf0 XSNAP_NATIVE_URL=https://github.com/agoric-labs/xsnap-pub -XSNAP_NATIVE_COMMIT_HASH=eef9b67da5517ed18ff9e0073b842db20924eae3 +XSNAP_NATIVE_COMMIT_HASH=a9738b2799a614e6fda5698c8f3cd6e29569ebf8 diff --git a/packages/xsnap/src/xsnap.js b/packages/xsnap/src/xsnap.js index dcce6295a07..6b9e661f8d1 100644 --- a/packages/xsnap/src/xsnap.js +++ b/packages/xsnap/src/xsnap.js @@ -141,8 +141,8 @@ const makeSnapshotLoaderWithPipe = async ( * @property {AsyncIterable} [snapshotStream] * @property {string} [snapshotDescription] * @property {boolean} [snapshotUseFs] - * @property {'ignore' | 'inherit'} [stdout] - * @property {'ignore' | 'inherit'} [stderr] + * @property {'ignore' | 'inherit' | 'pipe'} [stdout] + * @property {'ignore' | 'inherit' | 'pipe'} [stderr] * @property {number} [meteringLimit] * @property {Record} [env] */ diff --git a/packages/xsnap/test/xsnap-eof.test.js b/packages/xsnap/test/xsnap-eof.test.js new file mode 100644 index 00000000000..dd9574414b8 --- /dev/null +++ b/packages/xsnap/test/xsnap-eof.test.js @@ -0,0 +1,268 @@ +/* global setTimeout */ + +import test from 'ava'; +import * as proc from 'child_process'; +import * as os from 'os'; +import fs from 'fs'; +import process from 'node:process'; +import { tmpName } from 'tmp'; +import { xsnap } from '../src/xsnap.js'; +import { options } from './message-tools.js'; + +const sleep = ms => new Promise(res => setTimeout(res, ms)); + +async function waitFor(conditionFn) { + while (!conditionFn()) { + await sleep(100); + } +} + +test('xsnap-worker complains while waiting for answer when parent is killed', async t => { + const parentText = ` + import * as proc from 'child_process'; + import * as os from 'os'; + import fs from 'fs'; + import process from 'node:process'; + import { tmpName } from 'tmp'; + import '@endo/init'; + import { xsnap } from '../src/xsnap.js'; + import { options, decode, encode, loader } from './message-tools.js'; + import console from 'console'; + const io = { spawn: proc.spawn, os: os.type(), fs, tmpName }; + + async function handleCommand(message) { + process.kill(process.pid, 'SIGKILL'); + return new Uint8Array(); + } + + const vat = await xsnap({ ...options(io), handleCommand }); + await vat.evaluate('function handleCommand(message) { issueCommand(new Uint8Array().buffer); };'); + await vat.issueStringCommand('0'); + console.error('Error: parent was not killed!'); + await vat.close(); + `; + + let nodeExited = false; + let nodeError = null; + let nodeExitCode = null; + let nodeExitSignal = null; + let strOut = ''; + let strErr = ''; + + const p = proc.spawn('node', ['--input-type=module', '-e', parentText], { + cwd: `${process.cwd()}/test`, + }); + + p.stdout.on('data', data => { + strOut = `${strOut}${data}`; + }); + + p.stderr.on('data', data => { + strErr = `${strErr}${data}`; + }); + + p.on('error', err => { + nodeError = err; + }); + + p.on('exit', (code, signal) => { + nodeExited = true; + nodeExitCode = code; + nodeExitSignal = signal; + }); + + p.stdin.end(); + + await waitFor(() => { + return nodeExited; + }); + + t.is(`${strOut}`, ``, 'wrong stdout output'); + t.regex(`${strErr}`, /Has parent died\?/, 'wrong stderr output'); + t.is(nodeError, null, 'parent returned an error'); + t.is(nodeExited, true, 'parent did not exit'); + t.is(nodeExitCode, null, 'parent exit code is not null'); + t.is(`${nodeExitSignal}`, `SIGKILL`, 'wrong parent exit signal'); +}); + +async function doSetup(handleCommand) { + let xsnapProcess; + const spawnSpy = (...args) => { + xsnapProcess = proc.spawn(...args); + return xsnapProcess; + }; + + const io = { spawn: spawnSpy, os: os.type(), fs, tmpName }; + const vat = await xsnap({ ...options(io), handleCommand, stderr: 'pipe' }); + await vat.evaluate( + 'function handleCommand(message) { issueCommand(new Uint8Array().buffer); };', + ); + return { vat, xsnapProcess }; +} + +test('xsnap-worker complains while trying to READ an answer when pipes are closed', async t => { + let xsnapProcess; + let toXsnap; + let fromXsnap; + let issueCommandError; + let vatCloseError; + let vatExitCode; + let vatExitSignal; + let strErr = ''; + + async function handleCommand(_message) { + // suspend the child process so the pipes appear + // to close atomically from its perspective + xsnapProcess.kill('SIGSTOP'); + // wait for signal delivery + await waitFor(() => { + return xsnapProcess.killed; + }); + + // close the pipes + toXsnap.end(); + toXsnap.destroy(); + fromXsnap.end(); + fromXsnap.destroy(); + + // un-suspend the child process + xsnapProcess.kill('SIGCONT'); + // wait for signal delivery + await waitFor(() => { + return xsnapProcess.killed; + }); + + return new Uint8Array(); + } + + const setup = await doSetup(handleCommand); + const { vat } = setup; + ({ xsnapProcess } = setup); + + toXsnap = xsnapProcess.stdio[3]; + fromXsnap = xsnapProcess.stdio[4]; + + xsnapProcess.on('exit', (code, signal) => { + vatExitCode = code; + vatExitSignal = signal; + }); + + xsnapProcess.stdio[2].on('data', data => { + strErr = `${strErr}${data}`; + }); + + try { + // this will error out since we are intentionally + // breaking communications with the worker + await vat.issueStringCommand('0'); + } catch (err) { + issueCommandError = err; + } + + // wait for the child to exit + await waitFor(() => { + return vatExitCode || vatExitSignal; + }); + + try { + // this will error out since vat has died. + await vat.close(); + } catch (err) { + vatCloseError = err; + } + t.truthy(issueCommandError, 'issueCommand() did not error out'); + t.truthy(vatCloseError, 'vat.close() did not error out'); + t.falsy(vatExitSignal, 'wrong vat exit signal'); + t.is(vatExitCode, 2 /* E_IO_ERROR */, 'wrong vat exit code'); + t.is( + strErr, + 'Got EOF on netstring read. Has parent died?\n', + 'wrong vat stderr message', + ); +}); + +test('xsnap-worker complains while trying to WRITE when pipes are closed', async t => { + let xsnapProcess; + let toXsnap; + let fromXsnap; + let issueCommandError; + let vatCloseError; + let vatExitCode; + let vatExitSignal; + let strErr = ''; + + async function handleCommand(_message) { + // suspend the child process so the pipes appear + // to close atomically from its perspective + await xsnapProcess.kill('SIGSTOP'); + // wait for signal delivery + await waitFor(() => { + return xsnapProcess.killed; + }); + fromXsnap.end(); + fromXsnap.destroy(); + // write a fake but valid response into a pipe buffer, + // forcing the worker to move to the next state where it would + // attempt to write out return value from its own "handleCommand()" + // into a pipe which we've just closed above. + toXsnap.write('2:/0,\n'); + toXsnap.end(); + toXsnap.destroy(); + + // Release the kraken! + await xsnapProcess.kill('SIGCONT'); + // wait for signal delivery + await waitFor(() => { + return xsnapProcess.killed; + }); + + // + return new Uint8Array(); + } + + const setup = await doSetup(handleCommand); + const { vat } = setup; + ({ xsnapProcess } = setup); + + toXsnap = xsnapProcess.stdio[3]; + fromXsnap = xsnapProcess.stdio[4]; + + xsnapProcess.on('exit', (code, signal) => { + vatExitCode = code; + vatExitSignal = signal; + }); + + xsnapProcess.stdio[2].on('data', data => { + strErr = `${strErr}${data}`; + }); + + try { + // this will error out since we are intentionally + // breaking communications with the worker + await vat.issueStringCommand('0'); + } catch (err) { + issueCommandError = err; + } + + // wait for the child to exit + await waitFor(() => { + return vatExitCode || vatExitSignal; + }); + + try { + // this will error out since vat has died. + await vat.close(); + } catch (err) { + vatCloseError = err; + } + + t.truthy(issueCommandError, 'issueCommand() did not error out'); + t.truthy(vatCloseError, 'vat.close() did not error out'); + t.falsy(vatExitSignal, 'wrong vat exit signal'); + t.is(vatExitCode, 2 /* E_IO_ERROR */, 'wrong vat exit code'); + t.is( + strErr, + 'Caught SIGPIPE. Has parent died?\n', + 'wrong vat stderr message', + ); +}); diff --git a/packages/xsnap/xsnap-native b/packages/xsnap/xsnap-native index ab1ad696ba0..a9738b2799a 160000 --- a/packages/xsnap/xsnap-native +++ b/packages/xsnap/xsnap-native @@ -1 +1 @@ -Subproject commit ab1ad696ba09713b52e31f731fd368bff94a8839 +Subproject commit a9738b2799a614e6fda5698c8f3cd6e29569ebf8 From 80c9735dd1cb3a95a06d08893472e16273e64749 Mon Sep 17 00:00:00 2001 From: Siarhei Liakh Date: Fri, 20 Sep 2024 17:36:26 +0000 Subject: [PATCH 03/24] fix(xsnap): incorporate PR review suggestions --- packages/xsnap/test/fixture-xsnap-eof.js | 24 ++ packages/xsnap/test/xsnap-eof.test.js | 319 +++++++++-------------- 2 files changed, 140 insertions(+), 203 deletions(-) create mode 100644 packages/xsnap/test/fixture-xsnap-eof.js diff --git a/packages/xsnap/test/fixture-xsnap-eof.js b/packages/xsnap/test/fixture-xsnap-eof.js new file mode 100644 index 00000000000..a9a7c5f6feb --- /dev/null +++ b/packages/xsnap/test/fixture-xsnap-eof.js @@ -0,0 +1,24 @@ +import console from 'console'; +import * as proc from 'child_process'; +import * as os from 'os'; +import fs from 'fs'; +import process from 'node:process'; +import { tmpName } from 'tmp'; +import '@endo/init'; +import { xsnap } from '../src/xsnap.js'; +import { options } from './message-tools.js'; + +const io = { spawn: proc.spawn, os: os.type(), fs, tmpName }; + +async function handleCommand(_message) { + process.kill(process.pid, 'SIGKILL'); + return new Uint8Array(); +} + +const vat = await xsnap({ ...options(io), handleCommand }); +await vat.evaluate( + 'function handleCommand(message) { issueCommand(new Uint8Array().buffer); };', +); +await vat.issueStringCommand('0'); +console.error('Error: parent was not killed!'); +await vat.close(); diff --git a/packages/xsnap/test/xsnap-eof.test.js b/packages/xsnap/test/xsnap-eof.test.js index dd9574414b8..4ee8e1effc5 100644 --- a/packages/xsnap/test/xsnap-eof.test.js +++ b/packages/xsnap/test/xsnap-eof.test.js @@ -1,93 +1,58 @@ -/* global setTimeout */ - import test from 'ava'; import * as proc from 'child_process'; import * as os from 'os'; import fs from 'fs'; -import process from 'node:process'; +import { fileURLToPath, URL } from 'url'; import { tmpName } from 'tmp'; -import { xsnap } from '../src/xsnap.js'; +import { text } from 'stream/consumers'; +import { makeNetstringWriter } from '@endo/netstring'; +import { makeNodeWriter } from '@endo/stream-node'; +import { makePromiseKit } from '@endo/promise-kit'; import { options } from './message-tools.js'; - -const sleep = ms => new Promise(res => setTimeout(res, ms)); - -async function waitFor(conditionFn) { - while (!conditionFn()) { - await sleep(100); - } -} +import { xsnap } from '../src/xsnap.js'; test('xsnap-worker complains while waiting for answer when parent is killed', async t => { - const parentText = ` - import * as proc from 'child_process'; - import * as os from 'os'; - import fs from 'fs'; - import process from 'node:process'; - import { tmpName } from 'tmp'; - import '@endo/init'; - import { xsnap } from '../src/xsnap.js'; - import { options, decode, encode, loader } from './message-tools.js'; - import console from 'console'; - const io = { spawn: proc.spawn, os: os.type(), fs, tmpName }; - - async function handleCommand(message) { - process.kill(process.pid, 'SIGKILL'); - return new Uint8Array(); - } - - const vat = await xsnap({ ...options(io), handleCommand }); - await vat.evaluate('function handleCommand(message) { issueCommand(new Uint8Array().buffer); };'); - await vat.issueStringCommand('0'); - console.error('Error: parent was not killed!'); - await vat.close(); - `; - - let nodeExited = false; - let nodeError = null; - let nodeExitCode = null; - let nodeExitSignal = null; - let strOut = ''; - let strErr = ''; + const exitedPKit = makePromiseKit(); - const p = proc.spawn('node', ['--input-type=module', '-e', parentText], { - cwd: `${process.cwd()}/test`, - }); + const p = + /** @type {import("child_process").ChildProcessWithoutNullStreams} */ ( + proc.fork( + fileURLToPath(new URL('./fixture-xsnap-eof.js', import.meta.url)), + { stdio: ['ignore', 'pipe', 'pipe', 'ipc'] }, + ) + ); - p.stdout.on('data', data => { - strOut = `${strOut}${data}`; - }); - - p.stderr.on('data', data => { - strErr = `${strErr}${data}`; - }); + const outP = text(p.stdout); + const errP = text(p.stderr); p.on('error', err => { - nodeError = err; + exitedPKit.reject(err); }); p.on('exit', (code, signal) => { - nodeExited = true; - nodeExitCode = code; - nodeExitSignal = signal; + exitedPKit.resolve({ code, signal }); }); - p.stdin.end(); - - await waitFor(() => { - return nodeExited; - }); + const [strOut, strErr, { code: nodeExitCode, signal: nodeExitSignal }] = + await Promise.all([outP, errP, exitedPKit.promise]); - t.is(`${strOut}`, ``, 'wrong stdout output'); - t.regex(`${strErr}`, /Has parent died\?/, 'wrong stderr output'); - t.is(nodeError, null, 'parent returned an error'); - t.is(nodeExited, true, 'parent did not exit'); - t.is(nodeExitCode, null, 'parent exit code is not null'); - t.is(`${nodeExitSignal}`, `SIGKILL`, 'wrong parent exit signal'); + t.is(strOut, '', 'stdout must be empty'); + t.regex( + strErr, + /Has parent died\?/, + 'stderr must contain "Has parent died?"', + ); + t.is(nodeExitCode, null, 'exit code must be null'); + t.is(nodeExitSignal, 'SIGKILL', 'exit signal must be "SIGKILL"'); }); -async function doSetup(handleCommand) { +async function spawnReflectiveWorker(handleCommand) { + const exitedPKit = makePromiseKit(); let xsnapProcess; + + /** @type {typeof import('child_process').spawn} */ const spawnSpy = (...args) => { + // @ts-expect-error overloaded signature xsnapProcess = proc.spawn(...args); return xsnapProcess; }; @@ -97,60 +62,27 @@ async function doSetup(handleCommand) { await vat.evaluate( 'function handleCommand(message) { issueCommand(new Uint8Array().buffer); };', ); - return { vat, xsnapProcess }; -} -test('xsnap-worker complains while trying to READ an answer when pipes are closed', async t => { - let xsnapProcess; - let toXsnap; - let fromXsnap; - let issueCommandError; - let vatCloseError; - let vatExitCode; - let vatExitSignal; - let strErr = ''; - - async function handleCommand(_message) { - // suspend the child process so the pipes appear - // to close atomically from its perspective - xsnapProcess.kill('SIGSTOP'); - // wait for signal delivery - await waitFor(() => { - return xsnapProcess.killed; - }); - - // close the pipes - toXsnap.end(); - toXsnap.destroy(); - fromXsnap.end(); - fromXsnap.destroy(); - - // un-suspend the child process - xsnapProcess.kill('SIGCONT'); - // wait for signal delivery - await waitFor(() => { - return xsnapProcess.killed; - }); - - return new Uint8Array(); - } - - const setup = await doSetup(handleCommand); - const { vat } = setup; - ({ xsnapProcess } = setup); + const toXsnap = xsnapProcess.stdio[3]; + const fromXsnap = xsnapProcess.stdio[4]; + const stderrP = text(xsnapProcess.stderr); - toXsnap = xsnapProcess.stdio[3]; - fromXsnap = xsnapProcess.stdio[4]; - - xsnapProcess.on('exit', (code, signal) => { - vatExitCode = code; - vatExitSignal = signal; + xsnapProcess.on('error', err => { + exitedPKit.reject(err); }); - xsnapProcess.stdio[2].on('data', data => { - strErr = `${strErr}${data}`; + xsnapProcess!.on('exit', (code, signal) => { + exitedPKit.resolve({ code, signal }); }); + return { vat, xsnapProcess, toXsnap, fromXsnap, stderrP, exitedPKit }; +} + +async function issueCommandAndWait(worker) { + let issueCommandError; + let vatCloseError; + const { vat, stderrP, exitedPKit } = worker; + try { // this will error out since we are intentionally // breaking communications with the worker @@ -159,10 +91,9 @@ test('xsnap-worker complains while trying to READ an answer when pipes are close issueCommandError = err; } - // wait for the child to exit - await waitFor(() => { - return vatExitCode || vatExitSignal; - }); + // wait for the worker to exit + const [strErr, { code: vatExitCode, signal: vatExitSignal }] = + await Promise.all([stderrP, exitedPKit.promise]); try { // this will error out since vat has died. @@ -170,99 +101,81 @@ test('xsnap-worker complains while trying to READ an answer when pipes are close } catch (err) { vatCloseError = err; } - t.truthy(issueCommandError, 'issueCommand() did not error out'); - t.truthy(vatCloseError, 'vat.close() did not error out'); - t.falsy(vatExitSignal, 'wrong vat exit signal'); - t.is(vatExitCode, 2 /* E_IO_ERROR */, 'wrong vat exit code'); + return { + issueCommandError, + vatCloseError, + strErr, + vatExitCode, + vatExitSignal, + }; +} + +test('xsnap-worker complains while trying to READ an answer when pipes are closed', async t => { + const worker = await spawnReflectiveWorker( + async function handleCommand(_message) { + // the worker is blocked on read here, so we should close "fromXsnap" pipe first + worker.fromXsnap.end(); + worker.fromXsnap.destroy(); + worker.toXsnap.end(); + worker.toXsnap.destroy(); + return new Uint8Array(); + }, + ); + + const { + issueCommandError, + vatCloseError, + strErr, + vatExitCode, + vatExitSignal, + } = await issueCommandAndWait(worker); + + t.not(issueCommandError, undefined, 'issueCommand() must produce and error'); + t.not(vatCloseError, 'vat.close() must produce and error'); + t.is(vatExitSignal, null, 'vat exit signal must be null'); + t.is(vatExitCode, 2 /* E_IO_ERROR */, 'vat exit code must be 2'); t.is( strErr, 'Got EOF on netstring read. Has parent died?\n', - 'wrong vat stderr message', + 'stderr must be "Got EOF on netstring read. Has parent died?"', ); }); test('xsnap-worker complains while trying to WRITE when pipes are closed', async t => { - let xsnapProcess; - let toXsnap; - let fromXsnap; - let issueCommandError; - let vatCloseError; - let vatExitCode; - let vatExitSignal; - let strErr = ''; - - async function handleCommand(_message) { - // suspend the child process so the pipes appear - // to close atomically from its perspective - await xsnapProcess.kill('SIGSTOP'); - // wait for signal delivery - await waitFor(() => { - return xsnapProcess.killed; - }); - fromXsnap.end(); - fromXsnap.destroy(); - // write a fake but valid response into a pipe buffer, - // forcing the worker to move to the next state where it would - // attempt to write out return value from its own "handleCommand()" - // into a pipe which we've just closed above. - toXsnap.write('2:/0,\n'); - toXsnap.end(); - toXsnap.destroy(); - - // Release the kraken! - await xsnapProcess.kill('SIGCONT'); - // wait for signal delivery - await waitFor(() => { - return xsnapProcess.killed; - }); - - // - return new Uint8Array(); - } - - const setup = await doSetup(handleCommand); - const { vat } = setup; - ({ xsnapProcess } = setup); - - toXsnap = xsnapProcess.stdio[3]; - fromXsnap = xsnapProcess.stdio[4]; - - xsnapProcess.on('exit', (code, signal) => { - vatExitCode = code; - vatExitSignal = signal; - }); - - xsnapProcess.stdio[2].on('data', data => { - strErr = `${strErr}${data}`; - }); - - try { - // this will error out since we are intentionally - // breaking communications with the worker - await vat.issueStringCommand('0'); - } catch (err) { - issueCommandError = err; - } - - // wait for the child to exit - await waitFor(() => { - return vatExitCode || vatExitSignal; - }); - - try { - // this will error out since vat has died. - await vat.close(); - } catch (err) { - vatCloseError = err; - } + const worker = await spawnReflectiveWorker( + async function handleCommand(_message) { + // the worker is blocked on read here, so we should close "fromXsnap" pipe first + worker.fromXsnap.end(); + worker.fromXsnap.destroy(); + // write a fake but valid response into a pipe buffer, + // forcing the worker to move to the next state where it would + // attempt to write out return value from its own "handleCommand()" + // into a pipe which we've just closed above. + await makeNetstringWriter(makeNodeWriter(worker.toXsnap)).next([ + new TextEncoder().encode('/') /* QUERY_RESPONSE_BUF */, + new Uint8Array(), + ]); + worker.toXsnap.end(); + worker.toXsnap.destroy(); + return new Uint8Array(); + }, + ); - t.truthy(issueCommandError, 'issueCommand() did not error out'); - t.truthy(vatCloseError, 'vat.close() did not error out'); - t.falsy(vatExitSignal, 'wrong vat exit signal'); - t.is(vatExitCode, 2 /* E_IO_ERROR */, 'wrong vat exit code'); + const { + issueCommandError, + vatCloseError, + strErr, + vatExitCode, + vatExitSignal, + } = await issueCommandAndWait(worker); + + t.not(issueCommandError, undefined, 'issueCommand() must produce and error'); + t.not(vatCloseError, 'vat.close() must produce and error'); + t.is(vatExitSignal, null, 'vat exit signal must be null'); + t.is(vatExitCode, 2 /* E_IO_ERROR */, 'vat exit code must be 2'); t.is( strErr, 'Caught SIGPIPE. Has parent died?\n', - 'wrong vat stderr message', + 'stderr must be "Caught SIGPIPE. Has parent died?"', ); }); From 8e1ad66ccaac2c3e44d76e06c49653bda98e9a31 Mon Sep 17 00:00:00 2001 From: Siarhei Liakh Date: Fri, 20 Sep 2024 17:45:05 +0000 Subject: [PATCH 04/24] fixup(xsnap): fixed a type-o --- packages/xsnap/test/xsnap-eof.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/xsnap/test/xsnap-eof.test.js b/packages/xsnap/test/xsnap-eof.test.js index 4ee8e1effc5..8ce3ca35c47 100644 --- a/packages/xsnap/test/xsnap-eof.test.js +++ b/packages/xsnap/test/xsnap-eof.test.js @@ -71,7 +71,7 @@ async function spawnReflectiveWorker(handleCommand) { exitedPKit.reject(err); }); - xsnapProcess!.on('exit', (code, signal) => { + xsnapProcess.on('exit', (code, signal) => { exitedPKit.resolve({ code, signal }); }); From e0a2cb6a877a881095b1d121ded30955b7a3e5e5 Mon Sep 17 00:00:00 2001 From: Siarhei Liakh Date: Fri, 20 Sep 2024 17:51:19 +0000 Subject: [PATCH 05/24] fix(xsnap): more fixes in response to PR review --- packages/xsnap/src/xsnap.js | 2 +- packages/xsnap/test/xsnap-eof.test.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/xsnap/src/xsnap.js b/packages/xsnap/src/xsnap.js index 6b9e661f8d1..1672959cb8f 100644 --- a/packages/xsnap/src/xsnap.js +++ b/packages/xsnap/src/xsnap.js @@ -27,7 +27,7 @@ const decoder = new TextDecoder(); const COMMAND_BUF = encoder.encode('?'); const QUERY = '?'.charCodeAt(0); -const QUERY_RESPONSE_BUF = encoder.encode('/'); +export const QUERY_RESPONSE_BUF = encoder.encode('/'); const OK = '.'.charCodeAt(0); const ERROR = '!'.charCodeAt(0); diff --git a/packages/xsnap/test/xsnap-eof.test.js b/packages/xsnap/test/xsnap-eof.test.js index 8ce3ca35c47..0c796a94578 100644 --- a/packages/xsnap/test/xsnap-eof.test.js +++ b/packages/xsnap/test/xsnap-eof.test.js @@ -9,7 +9,7 @@ import { makeNetstringWriter } from '@endo/netstring'; import { makeNodeWriter } from '@endo/stream-node'; import { makePromiseKit } from '@endo/promise-kit'; import { options } from './message-tools.js'; -import { xsnap } from '../src/xsnap.js'; +import { xsnap, QUERY_RESPONSE_BUF } from '../src/xsnap.js'; test('xsnap-worker complains while waiting for answer when parent is killed', async t => { const exitedPKit = makePromiseKit(); @@ -152,7 +152,7 @@ test('xsnap-worker complains while trying to WRITE when pipes are closed', async // attempt to write out return value from its own "handleCommand()" // into a pipe which we've just closed above. await makeNetstringWriter(makeNodeWriter(worker.toXsnap)).next([ - new TextEncoder().encode('/') /* QUERY_RESPONSE_BUF */, + QUERY_RESPONSE_BUF, new Uint8Array(), ]); worker.toXsnap.end(); From b369ef17f2251b98446245fdfc5b676496164721 Mon Sep 17 00:00:00 2001 From: Mathieu Hofman <86499+mhofman@users.noreply.github.com> Date: Fri, 20 Sep 2024 12:25:49 -0700 Subject: [PATCH 06/24] test(xsnap): fix child process types --- packages/xsnap/test/xsnap-eof.test.js | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/packages/xsnap/test/xsnap-eof.test.js b/packages/xsnap/test/xsnap-eof.test.js index 0c796a94578..89f89883447 100644 --- a/packages/xsnap/test/xsnap-eof.test.js +++ b/packages/xsnap/test/xsnap-eof.test.js @@ -11,6 +11,21 @@ import { makePromiseKit } from '@endo/promise-kit'; import { options } from './message-tools.js'; import { xsnap, QUERY_RESPONSE_BUF } from '../src/xsnap.js'; +/** + * @import { Readable, Writable, Duplex } from 'stream' + */ + +/** + * @typedef {Omit & +* { +* stdin: null; +* stdout: null; +* stderr: Readable; +* readonly stdio: [null, null, Readable, Duplex, Duplex, undefined, undefined, Duplex, Duplex]; +* } +* } XsnapChildProcess +*/ + test('xsnap-worker complains while waiting for answer when parent is killed', async t => { const exitedPKit = makePromiseKit(); @@ -48,13 +63,15 @@ test('xsnap-worker complains while waiting for answer when parent is killed', as async function spawnReflectiveWorker(handleCommand) { const exitedPKit = makePromiseKit(); + /** @type {XsnapChildProcess | undefined} */ let xsnapProcess; /** @type {typeof import('child_process').spawn} */ const spawnSpy = (...args) => { // @ts-expect-error overloaded signature - xsnapProcess = proc.spawn(...args); - return xsnapProcess; + const cp = proc.spawn(...args) + xsnapProcess = cp; + return cp; }; const io = { spawn: spawnSpy, os: os.type(), fs, tmpName }; @@ -63,6 +80,8 @@ async function spawnReflectiveWorker(handleCommand) { 'function handleCommand(message) { issueCommand(new Uint8Array().buffer); };', ); + assert(xsnapProcess); + const toXsnap = xsnapProcess.stdio[3]; const fromXsnap = xsnapProcess.stdio[4]; const stderrP = text(xsnapProcess.stderr); From 9c4390a9f560b6b1495d967653568415bf9c32d3 Mon Sep 17 00:00:00 2001 From: Siarhei Liakh Date: Fri, 20 Sep 2024 21:22:06 +0000 Subject: [PATCH 07/24] fix(xsnap): more PR-related fixes --- ...ure-xsnap-kill-parent-in-handlecommand.js} | 0 packages/xsnap/test/xsnap-eof.test.js | 55 ++++++++++++------- 2 files changed, 34 insertions(+), 21 deletions(-) rename packages/xsnap/test/{fixture-xsnap-eof.js => fixture-xsnap-kill-parent-in-handlecommand.js} (100%) diff --git a/packages/xsnap/test/fixture-xsnap-eof.js b/packages/xsnap/test/fixture-xsnap-kill-parent-in-handlecommand.js similarity index 100% rename from packages/xsnap/test/fixture-xsnap-eof.js rename to packages/xsnap/test/fixture-xsnap-kill-parent-in-handlecommand.js diff --git a/packages/xsnap/test/xsnap-eof.test.js b/packages/xsnap/test/xsnap-eof.test.js index 89f89883447..0ad599c74be 100644 --- a/packages/xsnap/test/xsnap-eof.test.js +++ b/packages/xsnap/test/xsnap-eof.test.js @@ -17,34 +17,39 @@ import { xsnap, QUERY_RESPONSE_BUF } from '../src/xsnap.js'; /** * @typedef {Omit & -* { -* stdin: null; -* stdout: null; -* stderr: Readable; -* readonly stdio: [null, null, Readable, Duplex, Duplex, undefined, undefined, Duplex, Duplex]; -* } -* } XsnapChildProcess -*/ + * { + * stdin: null; + * stdout: null; + * stderr: Readable; + * readonly stdio: [null, null, Readable, Duplex, Duplex, undefined, undefined, Duplex, Duplex]; + * } + * } XsnapChildProcess + */ test('xsnap-worker complains while waiting for answer when parent is killed', async t => { const exitedPKit = makePromiseKit(); - const p = + const child = /** @type {import("child_process").ChildProcessWithoutNullStreams} */ ( proc.fork( - fileURLToPath(new URL('./fixture-xsnap-eof.js', import.meta.url)), + fileURLToPath( + new URL( + './fixture-xsnap-kill-parent-in-handlecommand.js', + import.meta.url, + ), + ), { stdio: ['ignore', 'pipe', 'pipe', 'ipc'] }, ) ); - const outP = text(p.stdout); - const errP = text(p.stderr); + const outP = text(child.stdout); + const errP = text(child.stderr); - p.on('error', err => { + child.on('error', err => { exitedPKit.reject(err); }); - p.on('exit', (code, signal) => { + child.on('exit', (code, signal) => { exitedPKit.resolve({ code, signal }); }); @@ -69,7 +74,7 @@ async function spawnReflectiveWorker(handleCommand) { /** @type {typeof import('child_process').spawn} */ const spawnSpy = (...args) => { // @ts-expect-error overloaded signature - const cp = proc.spawn(...args) + const cp = proc.spawn(...args); xsnapProcess = cp; return cp; }; @@ -149,10 +154,14 @@ test('xsnap-worker complains while trying to READ an answer when pipes are close vatExitSignal, } = await issueCommandAndWait(worker); - t.not(issueCommandError, undefined, 'issueCommand() must produce and error'); - t.not(vatCloseError, 'vat.close() must produce and error'); + t.not(issueCommandError, undefined, 'issueCommand() must produce an error'); + t.not(vatCloseError, 'vat.close() must produce an error'); t.is(vatExitSignal, null, 'vat exit signal must be null'); - t.is(vatExitCode, 2 /* E_IO_ERROR */, 'vat exit code must be 2'); + t.is( + vatExitCode, + 2 /* E_IO_ERROR */, + 'vat exit code must indicate an IO error', + ); t.is( strErr, 'Got EOF on netstring read. Has parent died?\n', @@ -188,10 +197,14 @@ test('xsnap-worker complains while trying to WRITE when pipes are closed', async vatExitSignal, } = await issueCommandAndWait(worker); - t.not(issueCommandError, undefined, 'issueCommand() must produce and error'); - t.not(vatCloseError, 'vat.close() must produce and error'); + t.not(issueCommandError, undefined, 'issueCommand() must produce an error'); + t.not(vatCloseError, 'vat.close() must produce an error'); t.is(vatExitSignal, null, 'vat exit signal must be null'); - t.is(vatExitCode, 2 /* E_IO_ERROR */, 'vat exit code must be 2'); + t.is( + vatExitCode, + 2 /* E_IO_ERROR */, + 'vat exit code must indicate an IO error', + ); t.is( strErr, 'Caught SIGPIPE. Has parent died?\n', From 2942f2917ec0bfb4adf31b3e8abefe369eaf0845 Mon Sep 17 00:00:00 2001 From: Siarhei Liakh Date: Fri, 20 Sep 2024 21:41:03 +0000 Subject: [PATCH 08/24] fix(xsnap): use test.macro() in xsnap-eof.test.js --- packages/xsnap/test/xsnap-eof.test.js | 104 +++++++++++--------------- 1 file changed, 45 insertions(+), 59 deletions(-) diff --git a/packages/xsnap/test/xsnap-eof.test.js b/packages/xsnap/test/xsnap-eof.test.js index 0ad599c74be..f3590a9e0b4 100644 --- a/packages/xsnap/test/xsnap-eof.test.js +++ b/packages/xsnap/test/xsnap-eof.test.js @@ -10,6 +10,7 @@ import { makeNodeWriter } from '@endo/stream-node'; import { makePromiseKit } from '@endo/promise-kit'; import { options } from './message-tools.js'; import { xsnap, QUERY_RESPONSE_BUF } from '../src/xsnap.js'; +import { ExitCode } from '../api.js'; /** * @import { Readable, Writable, Duplex } from 'stream' @@ -134,17 +135,13 @@ async function issueCommandAndWait(worker) { }; } -test('xsnap-worker complains while trying to READ an answer when pipes are closed', async t => { - const worker = await spawnReflectiveWorker( - async function handleCommand(_message) { - // the worker is blocked on read here, so we should close "fromXsnap" pipe first - worker.fromXsnap.end(); - worker.fromXsnap.destroy(); - worker.toXsnap.end(); - worker.toXsnap.destroy(); - return new Uint8Array(); - }, - ); +const testInterruption = test.macro(async (t, onRequest, expectedStderr) => { + const handleCommand = message => { + // @ts-expect-error onRequest is untyped + // eslint-disable-next-line no-use-before-define + return onRequest(worker, message); + }; + const worker = await spawnReflectiveWorker(handleCommand); const { issueCommandError, @@ -159,55 +156,44 @@ test('xsnap-worker complains while trying to READ an answer when pipes are close t.is(vatExitSignal, null, 'vat exit signal must be null'); t.is( vatExitCode, - 2 /* E_IO_ERROR */, + ExitCode.E_IO_ERROR, 'vat exit code must indicate an IO error', ); - t.is( - strErr, - 'Got EOF on netstring read. Has parent died?\n', - 'stderr must be "Got EOF on netstring read. Has parent died?"', - ); + t.is(strErr, expectedStderr, 'stderr must indicate expected error'); }); -test('xsnap-worker complains while trying to WRITE when pipes are closed', async t => { - const worker = await spawnReflectiveWorker( - async function handleCommand(_message) { - // the worker is blocked on read here, so we should close "fromXsnap" pipe first - worker.fromXsnap.end(); - worker.fromXsnap.destroy(); - // write a fake but valid response into a pipe buffer, - // forcing the worker to move to the next state where it would - // attempt to write out return value from its own "handleCommand()" - // into a pipe which we've just closed above. - await makeNetstringWriter(makeNodeWriter(worker.toXsnap)).next([ - QUERY_RESPONSE_BUF, - new Uint8Array(), - ]); - worker.toXsnap.end(); - worker.toXsnap.destroy(); - return new Uint8Array(); - }, - ); - - const { - issueCommandError, - vatCloseError, - strErr, - vatExitCode, - vatExitSignal, - } = await issueCommandAndWait(worker); - - t.not(issueCommandError, undefined, 'issueCommand() must produce an error'); - t.not(vatCloseError, 'vat.close() must produce an error'); - t.is(vatExitSignal, null, 'vat exit signal must be null'); - t.is( - vatExitCode, - 2 /* E_IO_ERROR */, - 'vat exit code must indicate an IO error', - ); - t.is( - strErr, - 'Caught SIGPIPE. Has parent died?\n', - 'stderr must be "Caught SIGPIPE. Has parent died?"', - ); -}); +test( + 'xsnap-worker complains while trying to READ an answer when pipes are closed', + testInterruption, + async function onRequest(worker, _message) { + // the worker is blocked on read here, so we should close "fromXsnap" pipe first + worker.fromXsnap.end(); + worker.fromXsnap.destroy(); + worker.toXsnap.end(); + worker.toXsnap.destroy(); + return new Uint8Array(); + }, + 'Got EOF on netstring read. Has parent died?\n', +); + +test( + 'xsnap-worker complains while trying to WRITE an answer when pipes are closed', + testInterruption, + async function onRequest(worker, _message) { + // the worker is blocked on read here, so we should close "fromXsnap" pipe first + worker.fromXsnap.end(); + worker.fromXsnap.destroy(); + // write a fake but valid response into a pipe buffer, + // forcing the worker to move to the next state where it would + // attempt to write out return value from its own "handleCommand()" + // into a pipe which we've just closed above. + await makeNetstringWriter(makeNodeWriter(worker.toXsnap)).next([ + QUERY_RESPONSE_BUF, + new Uint8Array(), + ]); + worker.toXsnap.end(); + worker.toXsnap.destroy(); + return new Uint8Array(); + }, + 'Caught SIGPIPE. Has parent died?\n', +); From 99767e251f570538ddd4f75cd27c17bab28ae403 Mon Sep 17 00:00:00 2001 From: Siarhei Liakh Date: Tue, 24 Sep 2024 13:26:36 +0000 Subject: [PATCH 09/24] fix(xsnap): refactor, add more tests to xsnap-eof --- packages/xsnap/test/xsnap-eof.test.js | 187 +++++++++++++++++++------- 1 file changed, 139 insertions(+), 48 deletions(-) diff --git a/packages/xsnap/test/xsnap-eof.test.js b/packages/xsnap/test/xsnap-eof.test.js index f3590a9e0b4..826143f034f 100644 --- a/packages/xsnap/test/xsnap-eof.test.js +++ b/packages/xsnap/test/xsnap-eof.test.js @@ -5,11 +5,9 @@ import fs from 'fs'; import { fileURLToPath, URL } from 'url'; import { tmpName } from 'tmp'; import { text } from 'stream/consumers'; -import { makeNetstringWriter } from '@endo/netstring'; -import { makeNodeWriter } from '@endo/stream-node'; import { makePromiseKit } from '@endo/promise-kit'; import { options } from './message-tools.js'; -import { xsnap, QUERY_RESPONSE_BUF } from '../src/xsnap.js'; +import { xsnap } from '../src/xsnap.js'; import { ExitCode } from '../api.js'; /** @@ -20,9 +18,9 @@ import { ExitCode } from '../api.js'; * @typedef {Omit & * { * stdin: null; - * stdout: null; + * stdout: Readable; * stderr: Readable; - * readonly stdio: [null, null, Readable, Duplex, Duplex, undefined, undefined, Duplex, Duplex]; + * readonly stdio: [null, Readable, Readable, Duplex, Duplex, undefined, undefined, Duplex, Duplex]; * } * } XsnapChildProcess */ @@ -81,7 +79,12 @@ async function spawnReflectiveWorker(handleCommand) { }; const io = { spawn: spawnSpy, os: os.type(), fs, tmpName }; - const vat = await xsnap({ ...options(io), handleCommand, stderr: 'pipe' }); + const vat = await xsnap({ + ...options(io), + handleCommand, + stdout: 'pipe', + stderr: 'pipe', + }); await vat.evaluate( 'function handleCommand(message) { issueCommand(new Uint8Array().buffer); };', ); @@ -91,6 +94,7 @@ async function spawnReflectiveWorker(handleCommand) { const toXsnap = xsnapProcess.stdio[3]; const fromXsnap = xsnapProcess.stdio[4]; const stderrP = text(xsnapProcess.stderr); + const stdoutP = text(xsnapProcess.stdout); xsnapProcess.on('error', err => { exitedPKit.reject(err); @@ -100,100 +104,187 @@ async function spawnReflectiveWorker(handleCommand) { exitedPKit.resolve({ code, signal }); }); - return { vat, xsnapProcess, toXsnap, fromXsnap, stderrP, exitedPKit }; + return { + vat, + xsnapProcess, + toXsnap, + fromXsnap, + stdoutP, + stderrP, + exitedP: exitedPKit.promise, + }; } -async function issueCommandAndWait(worker) { - let issueCommandError; - let vatCloseError; - const { vat, stderrP, exitedPKit } = worker; +async function issueCommandAndWait(worker, beforeWait, afterWait) { + let beforeWaitError; + let afterWaitError; + const { stdoutP, stderrP, exitedP } = worker; try { - // this will error out since we are intentionally + // this may error out since we are intentionally // breaking communications with the worker - await vat.issueStringCommand('0'); + await beforeWait(worker); // vat.issueStringCommand('0'); } catch (err) { - issueCommandError = err; + beforeWaitError = err; } // wait for the worker to exit - const [strErr, { code: vatExitCode, signal: vatExitSignal }] = - await Promise.all([stderrP, exitedPKit.promise]); + const [strOut, strErr, { code: vatExitCode, signal: vatExitSignal }] = + await Promise.all([stdoutP, stderrP, exitedP]); try { - // this will error out since vat has died. - await vat.close(); + // this may error out since vat has died. + await afterWait(worker); // vat.close(); } catch (err) { - vatCloseError = err; + afterWaitError = err; } return { - issueCommandError, - vatCloseError, + beforeWaitError, + afterWaitError, + strOut, strErr, vatExitCode, vatExitSignal, }; } -const testInterruption = test.macro(async (t, onRequest, expectedStderr) => { - const handleCommand = message => { - // @ts-expect-error onRequest is untyped - // eslint-disable-next-line no-use-before-define - return onRequest(worker, message); - }; - const worker = await spawnReflectiveWorker(handleCommand); +async function issueCommandZero(worker) { + await worker.vat.issueCommand('0'); +} +async function closeVat(worker) { + await worker.vat.close(); +} + +async function verifyStdError(t, results, expectedStderr) { const { - issueCommandError, - vatCloseError, + beforeWaitError, + afterWaitError, + strOut, strErr, vatExitCode, vatExitSignal, - } = await issueCommandAndWait(worker); + } = results; - t.not(issueCommandError, undefined, 'issueCommand() must produce an error'); - t.not(vatCloseError, 'vat.close() must produce an error'); + t.not(beforeWaitError, undefined, 'issueCommand() must produce an error'); + t.not(afterWaitError, undefined, 'vat.close() must produce an error'); t.is(vatExitSignal, null, 'vat exit signal must be null'); t.is( vatExitCode, ExitCode.E_IO_ERROR, 'vat exit code must indicate an IO error', ); + t.is(strOut, '', 'stdout must be empty'); t.is(strErr, expectedStderr, 'stderr must indicate expected error'); -}); +} + +const testInterruption = test.macro( + async (t, onRequest, beforeWait, afterWait, verifyResults) => { + const handleCommand = message => { + // @ts-expect-error onRequest is untyped + // eslint-disable-next-line no-use-before-define + return onRequest(worker, message); + }; + const worker = await spawnReflectiveWorker(handleCommand); + const results = await issueCommandAndWait(worker, beforeWait, afterWait); + // @ts-expect-error verifyResults is untyped + await verifyResults(t, results); + }, +); test( 'xsnap-worker complains while trying to READ an answer when pipes are closed', testInterruption, async function onRequest(worker, _message) { - // the worker is blocked on read here, so we should close "fromXsnap" pipe first - worker.fromXsnap.end(); - worker.fromXsnap.destroy(); + // the worker is blocked on read here, so closing "toXsnap" pipe + // should cause an immediate read error on worker side. worker.toXsnap.end(); worker.toXsnap.destroy(); return new Uint8Array(); }, - 'Got EOF on netstring read. Has parent died?\n', + issueCommandZero, + closeVat, + async function verifyResults(t, results) { + await verifyStdError( + t, + results, + 'Got EOF on netstring read. Has parent died?\n', + ); + }, ); test( 'xsnap-worker complains while trying to WRITE an answer when pipes are closed', testInterruption, async function onRequest(worker, _message) { - // the worker is blocked on read here, so we should close "fromXsnap" pipe first + // The worker is blocked on read here, so closing "fromXsnap" pipe + // does not cause an immediate exit. However, an attempt to send an + // answer back to us will cause a write error with a SIGPIPE. + worker.fromXsnap.end(); + worker.fromXsnap.destroy(); + return new Uint8Array(); + }, + issueCommandZero, + closeVat, + async function verifyResults(t, results) { + await verifyStdError(t, results, 'Caught SIGPIPE. Has parent died?\n'); + }, +); + +test( + 'xsnap-worker exits quietly when pipes are closed in quiescent state', + testInterruption, + async function onRequest(_worker, _message) { + return new Uint8Array(); + }, + async function beforeWait(worker) { + // Simulating an orderly shutdown in quiescent state by closing both pipes. + // The worker is blocked on read here, so we should close "fromXsnap" pipe first worker.fromXsnap.end(); worker.fromXsnap.destroy(); - // write a fake but valid response into a pipe buffer, - // forcing the worker to move to the next state where it would - // attempt to write out return value from its own "handleCommand()" - // into a pipe which we've just closed above. - await makeNetstringWriter(makeNodeWriter(worker.toXsnap)).next([ - QUERY_RESPONSE_BUF, - new Uint8Array(), - ]); worker.toXsnap.end(); worker.toXsnap.destroy(); + }, + closeVat, + async function verifyResults(t, results) { + const { + beforeWaitError, + afterWaitError, + strOut, + strErr, + vatExitCode, + vatExitSignal, + } = results; + + t.is(beforeWaitError, undefined, 'worker pipes must close without error'); + t.is(afterWaitError, undefined, 'vat.close() must not produce an error'); + t.is(vatExitSignal, null, 'vat exit signal must be null'); + t.is( + vatExitCode, + ExitCode.E_SUCCESS, + 'vat exit code must indicate success', + ); + t.is(strOut, '', 'stdout must be empty'); + t.is(strErr, '', 'stderr must be empty'); + }, +); + +test( + 'xsnap-worker complains while trying to WRITE a query when pipes are closed', + testInterruption, + async function onRequest(_worker, _message) { return new Uint8Array(); }, - 'Caught SIGPIPE. Has parent died?\n', + async function beforeWait(worker) { + // The worker is blocked on read here, so we close "fromXsnap" pipe before + // issuing a command. This will trigger worker's handleCommand(), which will + // attempt to issueCommand() (query) back to us. + worker.fromXsnap.end(); + worker.fromXsnap.destroy(); + await issueCommandZero(worker); + }, + closeVat, + async function verifyResults(t, results) { + await verifyStdError(t, results, 'Caught SIGPIPE. Has parent died?\n'); + }, ); From 09441886048a4ae6780ee20f642590c9b8bea0b9 Mon Sep 17 00:00:00 2001 From: siarhei-agoric Date: Tue, 24 Sep 2024 13:00:54 -0400 Subject: [PATCH 10/24] Update packages/xsnap/test/xsnap-eof.test.js Co-authored-by: Richard Gibson --- packages/xsnap/test/xsnap-eof.test.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/xsnap/test/xsnap-eof.test.js b/packages/xsnap/test/xsnap-eof.test.js index 826143f034f..bb1cd40b535 100644 --- a/packages/xsnap/test/xsnap-eof.test.js +++ b/packages/xsnap/test/xsnap-eof.test.js @@ -65,6 +65,15 @@ test('xsnap-worker complains while waiting for answer when parent is killed', as t.is(nodeExitSignal, 'SIGKILL', 'exit signal must be "SIGKILL"'); }); +/** + * Launch an xsnap vat that responds to every command by issuing an empty + * request of its own, to be received by the provided `handleCommand`. + * Returns the vat along with values capturing its surface area (ChildProcess + * object and exit promise, command input/output streams, and promises for + * stdout/stderr text). + * + * @param {import('../src/xsnap.js').XSnapOptions['handleCommand']} handleCommand + */ async function spawnReflectiveWorker(handleCommand) { const exitedPKit = makePromiseKit(); /** @type {XsnapChildProcess | undefined} */ From 871e4f229710ccf7e80f2468c35dc14d1e7976f1 Mon Sep 17 00:00:00 2001 From: siarhei-agoric Date: Tue, 24 Sep 2024 13:02:09 -0400 Subject: [PATCH 11/24] Update packages/xsnap/test/xsnap-eof.test.js Co-authored-by: Richard Gibson --- packages/xsnap/test/xsnap-eof.test.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/xsnap/test/xsnap-eof.test.js b/packages/xsnap/test/xsnap-eof.test.js index bb1cd40b535..86cb49da8f2 100644 --- a/packages/xsnap/test/xsnap-eof.test.js +++ b/packages/xsnap/test/xsnap-eof.test.js @@ -124,7 +124,12 @@ async function spawnReflectiveWorker(handleCommand) { }; } -async function issueCommandAndWait(worker, beforeWait, afterWait) { +/** + * @param {ReturnType} worker + * @param {(worker: typeof worker) => Promise} beforeWait + * @param {(worker: typeof worker) => Promise} afterWait + */ +async function expectTermination(worker, beforeWait, afterWait) { let beforeWaitError; let afterWaitError; const { stdoutP, stderrP, exitedP } = worker; From 0ab3672742403c166a8d826992fcbfd6f52b06e2 Mon Sep 17 00:00:00 2001 From: siarhei-agoric Date: Tue, 24 Sep 2024 13:02:19 -0400 Subject: [PATCH 12/24] Update packages/xsnap/test/xsnap-eof.test.js Co-authored-by: Richard Gibson --- packages/xsnap/test/xsnap-eof.test.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/packages/xsnap/test/xsnap-eof.test.js b/packages/xsnap/test/xsnap-eof.test.js index 86cb49da8f2..77070f90cfc 100644 --- a/packages/xsnap/test/xsnap-eof.test.js +++ b/packages/xsnap/test/xsnap-eof.test.js @@ -162,14 +162,6 @@ async function expectTermination(worker, beforeWait, afterWait) { }; } -async function issueCommandZero(worker) { - await worker.vat.issueCommand('0'); -} - -async function closeVat(worker) { - await worker.vat.close(); -} - async function verifyStdError(t, results, expectedStderr) { const { beforeWaitError, From a6ce279320f18686e8b654bfbf9391faf7f9edf0 Mon Sep 17 00:00:00 2001 From: siarhei-agoric Date: Tue, 24 Sep 2024 13:02:34 -0400 Subject: [PATCH 13/24] Update packages/xsnap/test/xsnap-eof.test.js Co-authored-by: Richard Gibson --- packages/xsnap/test/xsnap-eof.test.js | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/packages/xsnap/test/xsnap-eof.test.js b/packages/xsnap/test/xsnap-eof.test.js index 77070f90cfc..9292e352668 100644 --- a/packages/xsnap/test/xsnap-eof.test.js +++ b/packages/xsnap/test/xsnap-eof.test.js @@ -208,15 +208,13 @@ test( worker.toXsnap.destroy(); return new Uint8Array(); }, - issueCommandZero, - closeVat, - async function verifyResults(t, results) { - await verifyStdError( - t, - results, - 'Got EOF on netstring read. Has parent died?\n', - ); - }, + worker => worker.vat.issueCommand('0'), + worker => worker.vat.close(), + (t, results) => verifyStdError( + t, + results, + 'Got EOF on netstring read. Has parent died?\n', + ), ); test( From d44502ac1cc97aef883a56d905703859560a337c Mon Sep 17 00:00:00 2001 From: siarhei-agoric Date: Tue, 24 Sep 2024 13:02:45 -0400 Subject: [PATCH 14/24] Update packages/xsnap/test/xsnap-eof.test.js Co-authored-by: Richard Gibson --- packages/xsnap/test/xsnap-eof.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/xsnap/test/xsnap-eof.test.js b/packages/xsnap/test/xsnap-eof.test.js index 9292e352668..b033866287f 100644 --- a/packages/xsnap/test/xsnap-eof.test.js +++ b/packages/xsnap/test/xsnap-eof.test.js @@ -194,7 +194,7 @@ const testInterruption = test.macro( const worker = await spawnReflectiveWorker(handleCommand); const results = await issueCommandAndWait(worker, beforeWait, afterWait); // @ts-expect-error verifyResults is untyped - await verifyResults(t, results); + verifyResults(t, results); }, ); From c8a42a2ef11eca273d33a9be286979943978fadc Mon Sep 17 00:00:00 2001 From: siarhei-agoric Date: Tue, 24 Sep 2024 13:02:56 -0400 Subject: [PATCH 15/24] Update packages/xsnap/test/xsnap-eof.test.js Co-authored-by: Richard Gibson --- packages/xsnap/test/xsnap-eof.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/xsnap/test/xsnap-eof.test.js b/packages/xsnap/test/xsnap-eof.test.js index b033866287f..cd8a0766c63 100644 --- a/packages/xsnap/test/xsnap-eof.test.js +++ b/packages/xsnap/test/xsnap-eof.test.js @@ -162,7 +162,7 @@ async function expectTermination(worker, beforeWait, afterWait) { }; } -async function verifyStdError(t, results, expectedStderr) { +function verifyStdError(t, results, expectedStderr) { const { beforeWaitError, afterWaitError, From 6996f1a124c5e2e84de5ae34f2535d9b477e63ef Mon Sep 17 00:00:00 2001 From: siarhei-agoric Date: Tue, 24 Sep 2024 13:03:34 -0400 Subject: [PATCH 16/24] Update packages/xsnap/test/xsnap-eof.test.js Co-authored-by: Richard Gibson --- packages/xsnap/test/xsnap-eof.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/xsnap/test/xsnap-eof.test.js b/packages/xsnap/test/xsnap-eof.test.js index cd8a0766c63..db38ea12005 100644 --- a/packages/xsnap/test/xsnap-eof.test.js +++ b/packages/xsnap/test/xsnap-eof.test.js @@ -185,7 +185,7 @@ function verifyStdError(t, results, expectedStderr) { } const testInterruption = test.macro( - async (t, onRequest, beforeWait, afterWait, verifyResults) => { + async (t, beforeWait, onRequest, afterWait, verifyResults) => { const handleCommand = message => { // @ts-expect-error onRequest is untyped // eslint-disable-next-line no-use-before-define From ff101fbee6daffa3d4020b9b05bdfd758b37162f Mon Sep 17 00:00:00 2001 From: Siarhei Liakh Date: Tue, 24 Sep 2024 17:20:08 +0000 Subject: [PATCH 17/24] fix(xsnap): fix style in xsnap-eof.test.js --- packages/xsnap/test/xsnap-eof.test.js | 47 ++++++++++++--------------- 1 file changed, 21 insertions(+), 26 deletions(-) diff --git a/packages/xsnap/test/xsnap-eof.test.js b/packages/xsnap/test/xsnap-eof.test.js index db38ea12005..ef618f404fa 100644 --- a/packages/xsnap/test/xsnap-eof.test.js +++ b/packages/xsnap/test/xsnap-eof.test.js @@ -192,7 +192,7 @@ const testInterruption = test.macro( return onRequest(worker, message); }; const worker = await spawnReflectiveWorker(handleCommand); - const results = await issueCommandAndWait(worker, beforeWait, afterWait); + const results = await expectTermination(worker, beforeWait, afterWait); // @ts-expect-error verifyResults is untyped verifyResults(t, results); }, @@ -201,6 +201,7 @@ const testInterruption = test.macro( test( 'xsnap-worker complains while trying to READ an answer when pipes are closed', testInterruption, + worker => worker.vat.issueCommand('0'), async function onRequest(worker, _message) { // the worker is blocked on read here, so closing "toXsnap" pipe // should cause an immediate read error on worker side. @@ -208,18 +209,15 @@ test( worker.toXsnap.destroy(); return new Uint8Array(); }, - worker => worker.vat.issueCommand('0'), worker => worker.vat.close(), - (t, results) => verifyStdError( - t, - results, - 'Got EOF on netstring read. Has parent died?\n', - ), + (t, results) => + verifyStdError(t, results, 'Got EOF on netstring read. Has parent died?\n'), ); test( 'xsnap-worker complains while trying to WRITE an answer when pipes are closed', testInterruption, + worker => worker.vat.issueCommand('0'), async function onRequest(worker, _message) { // The worker is blocked on read here, so closing "fromXsnap" pipe // does not cause an immediate exit. However, an attempt to send an @@ -228,19 +226,14 @@ test( worker.fromXsnap.destroy(); return new Uint8Array(); }, - issueCommandZero, - closeVat, - async function verifyResults(t, results) { - await verifyStdError(t, results, 'Caught SIGPIPE. Has parent died?\n'); - }, + worker => worker.vat.close(), + (t, results) => + verifyStdError(t, results, 'Caught SIGPIPE. Has parent died?\n'), ); test( 'xsnap-worker exits quietly when pipes are closed in quiescent state', testInterruption, - async function onRequest(_worker, _message) { - return new Uint8Array(); - }, async function beforeWait(worker) { // Simulating an orderly shutdown in quiescent state by closing both pipes. // The worker is blocked on read here, so we should close "fromXsnap" pipe first @@ -249,8 +242,11 @@ test( worker.toXsnap.end(); worker.toXsnap.destroy(); }, - closeVat, - async function verifyResults(t, results) { + async function onRequest(_worker, _message) { + return new Uint8Array(); + }, + worker => worker.vat.close(), + (t, results) => { const { beforeWaitError, afterWaitError, @@ -276,19 +272,18 @@ test( test( 'xsnap-worker complains while trying to WRITE a query when pipes are closed', testInterruption, - async function onRequest(_worker, _message) { - return new Uint8Array(); - }, - async function beforeWait(worker) { + async worker => { // The worker is blocked on read here, so we close "fromXsnap" pipe before // issuing a command. This will trigger worker's handleCommand(), which will // attempt to issueCommand() (query) back to us. worker.fromXsnap.end(); worker.fromXsnap.destroy(); - await issueCommandZero(worker); + await worker.vat.issueCommand('0'); }, - closeVat, - async function verifyResults(t, results) { - await verifyStdError(t, results, 'Caught SIGPIPE. Has parent died?\n'); + async function onRequest(_worker, _message) { + return new Uint8Array(); }, -); + worker => worker.vat.close(), + (t, results) => + verifyStdError(t, results, 'Caught SIGPIPE. Has parent died?\n'), +); \ No newline at end of file From 747288cb88eb39de8471162e7443cee2d629ef10 Mon Sep 17 00:00:00 2001 From: siarhei-agoric Date: Tue, 24 Sep 2024 14:06:42 -0400 Subject: [PATCH 18/24] Fix lint issues in xsnap-eof.test.js Co-authored-by: Richard Gibson --- packages/xsnap/test/xsnap-eof.test.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/xsnap/test/xsnap-eof.test.js b/packages/xsnap/test/xsnap-eof.test.js index ef618f404fa..fb89da3a448 100644 --- a/packages/xsnap/test/xsnap-eof.test.js +++ b/packages/xsnap/test/xsnap-eof.test.js @@ -125,9 +125,13 @@ async function spawnReflectiveWorker(handleCommand) { } /** - * @param {ReturnType} worker - * @param {(worker: typeof worker) => Promise} beforeWait - * @param {(worker: typeof worker) => Promise} afterWait + * @typedef {Awaited>} ReflectiveWorker + */ + +/** + * @param {ReflectiveWorker} worker + * @param {(worker: ReflectiveWorker) => Promise} beforeWait + * @param {(worker: ReflectiveWorker) => Promise} afterWait */ async function expectTermination(worker, beforeWait, afterWait) { let beforeWaitError; From b015dcfa7857a3cec40ed00cbf0ef8757e7fb35d Mon Sep 17 00:00:00 2001 From: Siarhei Liakh Date: Tue, 24 Sep 2024 18:15:20 +0000 Subject: [PATCH 19/24] fix(xsnap): more lint fixes in xsnap-of.test.js --- packages/xsnap/test/xsnap-eof.test.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/xsnap/test/xsnap-eof.test.js b/packages/xsnap/test/xsnap-eof.test.js index fb89da3a448..17f99043f53 100644 --- a/packages/xsnap/test/xsnap-eof.test.js +++ b/packages/xsnap/test/xsnap-eof.test.js @@ -205,7 +205,7 @@ const testInterruption = test.macro( test( 'xsnap-worker complains while trying to READ an answer when pipes are closed', testInterruption, - worker => worker.vat.issueCommand('0'), + async worker => worker.vat.issueCommand('0'), async function onRequest(worker, _message) { // the worker is blocked on read here, so closing "toXsnap" pipe // should cause an immediate read error on worker side. @@ -221,7 +221,7 @@ test( test( 'xsnap-worker complains while trying to WRITE an answer when pipes are closed', testInterruption, - worker => worker.vat.issueCommand('0'), + async worker => worker.vat.issueCommand('0'), async function onRequest(worker, _message) { // The worker is blocked on read here, so closing "fromXsnap" pipe // does not cause an immediate exit. However, an attempt to send an @@ -249,7 +249,7 @@ test( async function onRequest(_worker, _message) { return new Uint8Array(); }, - worker => worker.vat.close(), + async worker => worker.vat.close(), (t, results) => { const { beforeWaitError, @@ -287,7 +287,7 @@ test( async function onRequest(_worker, _message) { return new Uint8Array(); }, - worker => worker.vat.close(), + async worker => worker.vat.close(), (t, results) => verifyStdError(t, results, 'Caught SIGPIPE. Has parent died?\n'), -); \ No newline at end of file +); From 48b7d9d32bfe2b2c2a682788cc65c5bb3e6ceb0b Mon Sep 17 00:00:00 2001 From: Siarhei Liakh Date: Tue, 24 Sep 2024 18:42:51 +0000 Subject: [PATCH 20/24] fix(xsnap): yet another lint fix... --- packages/xsnap/test/xsnap-eof.test.js | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/packages/xsnap/test/xsnap-eof.test.js b/packages/xsnap/test/xsnap-eof.test.js index 17f99043f53..a0dda3e89ed 100644 --- a/packages/xsnap/test/xsnap-eof.test.js +++ b/packages/xsnap/test/xsnap-eof.test.js @@ -130,8 +130,8 @@ async function spawnReflectiveWorker(handleCommand) { /** * @param {ReflectiveWorker} worker - * @param {(worker: ReflectiveWorker) => Promise} beforeWait - * @param {(worker: ReflectiveWorker) => Promise} afterWait + * @param {(worker: ReflectiveWorker) => Promise} beforeWait + * @param {(worker: ReflectiveWorker) => Promise} afterWait */ async function expectTermination(worker, beforeWait, afterWait) { let beforeWaitError; @@ -189,15 +189,20 @@ function verifyStdError(t, results, expectedStderr) { } const testInterruption = test.macro( + /** + * @param {import('ava').ExecutionContext} t + * @param {(worker: ReflectiveWorker) => Promise} beforeWait + * @param {(worker: ReflectiveWorker, message: Uint8Array) => Promise} onRequest + * @param {(worker: ReflectiveWorker) => Promise} afterWait + * @param {(t: import('ava').ExecutionContext, results: Awaited>) => void} verifyResults + */ async (t, beforeWait, onRequest, afterWait, verifyResults) => { const handleCommand = message => { - // @ts-expect-error onRequest is untyped // eslint-disable-next-line no-use-before-define return onRequest(worker, message); }; const worker = await spawnReflectiveWorker(handleCommand); const results = await expectTermination(worker, beforeWait, afterWait); - // @ts-expect-error verifyResults is untyped verifyResults(t, results); }, ); @@ -205,7 +210,7 @@ const testInterruption = test.macro( test( 'xsnap-worker complains while trying to READ an answer when pipes are closed', testInterruption, - async worker => worker.vat.issueCommand('0'), + async worker => worker.vat.issueStringCommand('0'), async function onRequest(worker, _message) { // the worker is blocked on read here, so closing "toXsnap" pipe // should cause an immediate read error on worker side. @@ -221,7 +226,7 @@ test( test( 'xsnap-worker complains while trying to WRITE an answer when pipes are closed', testInterruption, - async worker => worker.vat.issueCommand('0'), + async worker => worker.vat.issueStringCommand('0'), async function onRequest(worker, _message) { // The worker is blocked on read here, so closing "fromXsnap" pipe // does not cause an immediate exit. However, an attempt to send an @@ -282,7 +287,7 @@ test( // attempt to issueCommand() (query) back to us. worker.fromXsnap.end(); worker.fromXsnap.destroy(); - await worker.vat.issueCommand('0'); + await worker.vat.issueStringCommand('0'); }, async function onRequest(_worker, _message) { return new Uint8Array(); From eaf56612ccd56d23ea44e4c2553f5e7fd24b41dc Mon Sep 17 00:00:00 2001 From: siarhei-agoric Date: Wed, 25 Sep 2024 10:17:43 -0400 Subject: [PATCH 21/24] fix(xsnap): Apply suggestions from code review Co-authored-by: Mathieu Hofman <86499+mhofman@users.noreply.github.com> --- packages/xsnap/test/xsnap-eof.test.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/xsnap/test/xsnap-eof.test.js b/packages/xsnap/test/xsnap-eof.test.js index a0dda3e89ed..e771f2a17d9 100644 --- a/packages/xsnap/test/xsnap-eof.test.js +++ b/packages/xsnap/test/xsnap-eof.test.js @@ -208,7 +208,7 @@ const testInterruption = test.macro( ); test( - 'xsnap-worker complains while trying to READ an answer when pipes are closed', + 'xsnap-worker complains while trying to READ a query answer when pipes are closed', testInterruption, async worker => worker.vat.issueStringCommand('0'), async function onRequest(worker, _message) { @@ -224,7 +224,7 @@ test( ); test( - 'xsnap-worker complains while trying to WRITE an answer when pipes are closed', + 'xsnap-worker complains while trying to WRITE a command result when pipes are closed', testInterruption, async worker => worker.vat.issueStringCommand('0'), async function onRequest(worker, _message) { @@ -241,7 +241,7 @@ test( ); test( - 'xsnap-worker exits quietly when pipes are closed in quiescent state', + 'xsnap-worker exits quietly when pipes are closed in quiescent state (waiting to READ a command)', testInterruption, async function beforeWait(worker) { // Simulating an orderly shutdown in quiescent state by closing both pipes. @@ -282,7 +282,7 @@ test( 'xsnap-worker complains while trying to WRITE a query when pipes are closed', testInterruption, async worker => { - // The worker is blocked on read here, so we close "fromXsnap" pipe before + // The worker is blocked on reading a command here, so we close "fromXsnap" pipe before // issuing a command. This will trigger worker's handleCommand(), which will // attempt to issueCommand() (query) back to us. worker.fromXsnap.end(); From 3db210c336416fe1098c5c5e8a910b238c76b0c5 Mon Sep 17 00:00:00 2001 From: Siarhei Liakh Date: Wed, 25 Sep 2024 14:30:31 +0000 Subject: [PATCH 22/24] fix(xsnap): throw an error if handleCommand unexpectedly triggered --- packages/xsnap/src/xsnap.js | 2 +- packages/xsnap/test/xsnap-eof.test.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/xsnap/src/xsnap.js b/packages/xsnap/src/xsnap.js index 1672959cb8f..6b9e661f8d1 100644 --- a/packages/xsnap/src/xsnap.js +++ b/packages/xsnap/src/xsnap.js @@ -27,7 +27,7 @@ const decoder = new TextDecoder(); const COMMAND_BUF = encoder.encode('?'); const QUERY = '?'.charCodeAt(0); -export const QUERY_RESPONSE_BUF = encoder.encode('/'); +const QUERY_RESPONSE_BUF = encoder.encode('/'); const OK = '.'.charCodeAt(0); const ERROR = '!'.charCodeAt(0); diff --git a/packages/xsnap/test/xsnap-eof.test.js b/packages/xsnap/test/xsnap-eof.test.js index e771f2a17d9..6a8933e2304 100644 --- a/packages/xsnap/test/xsnap-eof.test.js +++ b/packages/xsnap/test/xsnap-eof.test.js @@ -252,7 +252,7 @@ test( worker.toXsnap.destroy(); }, async function onRequest(_worker, _message) { - return new Uint8Array(); + throw new Error('handleCommand() / onRequest() must never trigger'); }, async worker => worker.vat.close(), (t, results) => { @@ -290,7 +290,7 @@ test( await worker.vat.issueStringCommand('0'); }, async function onRequest(_worker, _message) { - return new Uint8Array(); + throw new Error('handleCommand() / onRequest() must never trigger'); }, async worker => worker.vat.close(), (t, results) => From 8de78726eab3880db8bf3f5f2e2797bcd0b4713b Mon Sep 17 00:00:00 2001 From: Siarhei Liakh Date: Wed, 25 Sep 2024 14:36:52 +0000 Subject: [PATCH 23/24] fix(xsnap): yet another small fix.. --- packages/xsnap/test/xsnap-eof.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/xsnap/test/xsnap-eof.test.js b/packages/xsnap/test/xsnap-eof.test.js index 6a8933e2304..e51407391ce 100644 --- a/packages/xsnap/test/xsnap-eof.test.js +++ b/packages/xsnap/test/xsnap-eof.test.js @@ -243,7 +243,7 @@ test( test( 'xsnap-worker exits quietly when pipes are closed in quiescent state (waiting to READ a command)', testInterruption, - async function beforeWait(worker) { + async worker => { // Simulating an orderly shutdown in quiescent state by closing both pipes. // The worker is blocked on read here, so we should close "fromXsnap" pipe first worker.fromXsnap.end(); From 2d8ff0dac0ce0bc66162ff80c58d38e5aabe4496 Mon Sep 17 00:00:00 2001 From: Siarhei Liakh Date: Wed, 25 Sep 2024 18:07:53 +0000 Subject: [PATCH 24/24] fix(xsnap): point submodule to LAG xsnap-pub commit --- packages/xsnap/build.env | 2 +- packages/xsnap/xsnap-native | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/xsnap/build.env b/packages/xsnap/build.env index af91dc3f82a..b365372b3d8 100644 --- a/packages/xsnap/build.env +++ b/packages/xsnap/build.env @@ -1,4 +1,4 @@ MODDABLE_URL=https://github.com/agoric-labs/moddable.git MODDABLE_COMMIT_HASH=f6c5951fc055e4ca592b9166b9ae3cbb9cca6bf0 XSNAP_NATIVE_URL=https://github.com/agoric-labs/xsnap-pub -XSNAP_NATIVE_COMMIT_HASH=a9738b2799a614e6fda5698c8f3cd6e29569ebf8 +XSNAP_NATIVE_COMMIT_HASH=105bc6862050695b1723fa76df91564fe8111a37 diff --git a/packages/xsnap/xsnap-native b/packages/xsnap/xsnap-native index a9738b2799a..105bc686205 160000 --- a/packages/xsnap/xsnap-native +++ b/packages/xsnap/xsnap-native @@ -1 +1 @@ -Subproject commit a9738b2799a614e6fda5698c8f3cd6e29569ebf8 +Subproject commit 105bc6862050695b1723fa76df91564fe8111a37