diff --git a/packages/SwingSet/src/kernel/vatManager/supervisor-subprocess-xsnap.js b/packages/SwingSet/src/kernel/vatManager/supervisor-subprocess-xsnap.js index dee76357452c..fb95c8926029 100644 --- a/packages/SwingSet/src/kernel/vatManager/supervisor-subprocess-xsnap.js +++ b/packages/SwingSet/src/kernel/vatManager/supervisor-subprocess-xsnap.js @@ -20,12 +20,12 @@ function workerLog(first, ...args) { workerLog(`supervisor started`); /** - * @param { (cmd: ArrayBuffer) => ArrayBuffer } issueCommand - * @typedef { [unknown, ...unknown[]] } Tagged - * @typedef { (item: Tagged) => unknown } DataHandler - * @typedef { (item: Tagged) => Promise } AsyncHandler + * Wrap byte-level protocols with tagged array codec. + * + * @param { (cmd: ArrayBuffer) => ArrayBuffer } issueCommand as from xsnap + * @typedef { [unknown, ...unknown[]] } Tagged tagged array */ -function ManagerPort(issueCommand) { +function managerPort(issueCommand) { /** @type { (item: Tagged) => ArrayBuffer } */ const encode = item => encoder.encode(JSON.stringify(item)).buffer; @@ -46,20 +46,31 @@ function ManagerPort(issueCommand) { send: item => { issueCommand(encode(item)); }, - /** @type { DataHandler } */ + /** @type { (item: Tagged) => unknown } */ call: item => decodeData(issueCommand(encode(item))), - /** @type { (f: AsyncHandler) => ((msg: ArrayBuffer) => { result?: ArrayBuffer })} */ - handler: f => msg => { - const report = {}; - f(decode(msg)) - .then(item => { - workerLog('result', item); - report.result = encode(item); - }) - .catch(err => { - report.result = encode(['err', err.message]); - }); - return report; + + /** + * Wrap an async Tagged handler in the xsnap async reporting idiom. + * + * @param { (item: Tagged) => Promise } f async Tagged handler + * @returns { (msg: ArrayBuffer) => Report } xsnap style handleCommand + * + * @typedef { { result?: T } } Report report T when idle + * @template T + */ + handlerFrom(f) { + return msg => { + const report = {}; + f(decode(msg)) + .then(item => { + workerLog('result', item); + report.result = encode(item); + }) + .catch(err => { + report.result = encode(['err', err.message]); + }); + return report; + }; }, }); } @@ -76,7 +87,7 @@ function runAndWait(f, errmsg) { } /** - * @param { ReturnType } port + * @param { ReturnType } port */ function makeWorker(port) { /** @type { Record void> | null } */ @@ -214,13 +225,12 @@ function makeWorker(port) { } return harden({ - /** @type { AsyncHandler } */ handleItem, }); } // @ts-ignore xsnap provides issueCommand global // eslint-disable-next-line no-undef -const port = ManagerPort(issueCommand); +const port = managerPort(issueCommand); const worker = makeWorker(port); -globalThis.handleCommand = port.handler(worker.handleItem); +globalThis.handleCommand = port.handlerFrom(worker.handleItem); diff --git a/packages/xsnap/README.md b/packages/xsnap/README.md index 9bf906a71213..47074ceb4e89 100644 --- a/packages/xsnap/README.md +++ b/packages/xsnap/README.md @@ -34,6 +34,10 @@ The parent and child communicate using "commands". and receive as response from the Node.js parent. - The XS child can implement a synchronous `handleCommand` function to respond to commands from the Node.js parent. + - The XS child `handleCommand` may be asynchronous after a fashion: it + may return an object and, before the promise queue becomes empty, + set the `result` property of this object to an `ArrayBuffer`. + See the **evaluate and report** test for an example. - The Node.js parent uses an asynchronous `issueCommand` method to send a request and receive a response from the XS child. - The Node.js parent can implement an asynchronous `handleCommand` function to