@agoric/swingset-vat 0.32.3-dev-ff36f02.0 → 0.32.3-dev-26028d4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agoric/swingset-vat",
3
- "version": "0.32.3-dev-ff36f02.0+ff36f02",
3
+ "version": "0.32.3-dev-26028d4.0+26028d4",
4
4
  "description": "Vat/Container Launcher",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -28,16 +28,16 @@
28
28
  "yargs-parser": "^21.1.1"
29
29
  },
30
30
  "dependencies": {
31
- "@agoric/assert": "0.6.1-dev-ff36f02.0+ff36f02",
32
- "@agoric/internal": "0.3.3-dev-ff36f02.0+ff36f02",
33
- "@agoric/store": "0.9.3-dev-ff36f02.0+ff36f02",
34
- "@agoric/swing-store": "0.9.2-dev-ff36f02.0+ff36f02",
35
- "@agoric/swingset-liveslots": "0.10.3-dev-ff36f02.0+ff36f02",
36
- "@agoric/swingset-xsnap-supervisor": "0.10.3-dev-ff36f02.0+ff36f02",
37
- "@agoric/time": "0.3.3-dev-ff36f02.0+ff36f02",
38
- "@agoric/vat-data": "0.5.3-dev-ff36f02.0+ff36f02",
39
- "@agoric/xsnap": "0.14.3-dev-ff36f02.0+ff36f02",
40
- "@agoric/xsnap-lockdown": "0.14.1-dev-ff36f02.0+ff36f02",
31
+ "@agoric/assert": "0.6.1-dev-26028d4.0+26028d4",
32
+ "@agoric/internal": "0.3.3-dev-26028d4.0+26028d4",
33
+ "@agoric/store": "0.9.3-dev-26028d4.0+26028d4",
34
+ "@agoric/swing-store": "0.9.2-dev-26028d4.0+26028d4",
35
+ "@agoric/swingset-liveslots": "0.10.3-dev-26028d4.0+26028d4",
36
+ "@agoric/swingset-xsnap-supervisor": "0.10.3-dev-26028d4.0+26028d4",
37
+ "@agoric/time": "0.3.3-dev-26028d4.0+26028d4",
38
+ "@agoric/vat-data": "0.5.3-dev-26028d4.0+26028d4",
39
+ "@agoric/xsnap": "0.14.3-dev-26028d4.0+26028d4",
40
+ "@agoric/xsnap-lockdown": "0.14.1-dev-26028d4.0+26028d4",
41
41
  "@endo/base64": "^0.2.31",
42
42
  "@endo/bundle-source": "^2.5.1",
43
43
  "@endo/captp": "^3.1.1",
@@ -92,5 +92,5 @@
92
92
  "publishConfig": {
93
93
  "access": "public"
94
94
  },
95
- "gitHead": "ff36f02c5fb8cc42248bc04d1313127397530de1"
95
+ "gitHead": "26028d4e8fd81c1cc8d0d6291978d6d8bc684cba"
96
96
  }
@@ -16,6 +16,7 @@ import { initSwingStore } from '@agoric/swing-store';
16
16
 
17
17
  import { checkBundle } from '@endo/check-bundle/lite.js';
18
18
  import engineGC from '../lib-nodejs/engine-gc.js';
19
+ import { startSubprocessWorker } from '../lib-nodejs/spawnSubprocessWorker.js';
19
20
  import { waitUntilQuiescent } from '../lib-nodejs/waitUntilQuiescent.js';
20
21
  import { makeGcAndFinalize } from '../lib-nodejs/gc-and-finalize.js';
21
22
  import { kslot } from '../lib/kmarshal.js';
@@ -30,6 +31,7 @@ import {
30
31
  makeXsnapBundleData,
31
32
  } from './bundle-handler.js';
32
33
  import { makeStartXSnap } from './startXSnap.js';
34
+ import { makeStartSubprocessWorkerNode } from './startNodeSubprocess.js';
33
35
 
34
36
  /** @param {Uint8Array} bytes */
35
37
  export function computeSha512(bytes) {
@@ -138,6 +140,9 @@ export async function makeSwingsetController(
138
140
  debug: !!env.XSNAP_DEBUG,
139
141
  workerTraceRootPath: env.XSNAP_TEST_RECORD,
140
142
  });
143
+ const startSubprocessWorkerNode = makeStartSubprocessWorkerNode(
144
+ startSubprocessWorker,
145
+ );
141
146
 
142
147
  function writeSlogObject(obj) {
143
148
  if (!slogSender) {
@@ -211,6 +216,7 @@ export async function makeSwingsetController(
211
216
  debugPrefix,
212
217
  vatEndowments,
213
218
  makeConsole,
219
+ startSubprocessWorkerNode,
214
220
  startXSnap,
215
221
  slogCallbacks,
216
222
  writeSlogObject,
@@ -86,6 +86,7 @@ export async function initializeKernel(config, kernelStorage, options = {}) {
86
86
  'useTranscript',
87
87
  'critical',
88
88
  'reapInterval',
89
+ 'nodeOptions',
89
90
  ]);
90
91
  const vatID = kernelKeeper.allocateVatIDForNameIfNeeded(name);
91
92
  logStartup(`assigned VatID ${vatID} for genesis vat ${name}`);
@@ -0,0 +1,17 @@
1
+ import process from 'process';
2
+
3
+ export function makeStartSubprocessWorkerNode(startSubprocessWorker) {
4
+ // launch a worker in a subprocess (which runs Node.js)
5
+ function startSubprocessWorkerNode(nameDisplayArg, nodeOptions) {
6
+ const supervisorCodePath = new URL(
7
+ '../supervisors/subprocess-node/supervisor-subprocess-node.js',
8
+ import.meta.url,
9
+ ).pathname;
10
+ const args = nodeOptions ? [...nodeOptions] : [];
11
+ args.push(supervisorCodePath);
12
+ args.push(nameDisplayArg);
13
+ return startSubprocessWorker(process.execPath, args);
14
+ }
15
+
16
+ return startSubprocessWorkerNode;
17
+ }
@@ -92,6 +92,7 @@ export default function buildKernel(
92
92
  vatEndowments,
93
93
  slogCallbacks = {},
94
94
  makeConsole,
95
+ startSubprocessWorkerNode,
95
96
  startXSnap,
96
97
  writeSlogObject,
97
98
  WeakRef,
@@ -1428,6 +1429,7 @@ export default function buildKernel(
1428
1429
  allVatPowers,
1429
1430
  kernelKeeper,
1430
1431
  vatEndowments,
1432
+ startSubprocessWorkerNode,
1431
1433
  startXSnap,
1432
1434
  gcTools,
1433
1435
  kernelSlog,
@@ -335,7 +335,7 @@ export default function makeKernelKeeper(kernelStorage, kernelSlog) {
335
335
  */
336
336
 
337
337
  function insistManagerType(mt) {
338
- assert(['local', 'xsnap', 'xs-worker'].includes(mt));
338
+ assert(['local', 'node-subprocess', 'xsnap', 'xs-worker'].includes(mt));
339
339
  }
340
340
 
341
341
  function getDefaultManagerType() {
@@ -1,12 +1,14 @@
1
1
  import { assert, Fail } from '@agoric/assert';
2
2
  import { assertKnownOptions } from '../../lib/assertOptions.js';
3
3
  import { makeLocalVatManagerFactory } from './manager-local.js';
4
+ import { makeNodeSubprocessFactory } from './manager-subprocess-node.js';
4
5
  import { makeXsSubprocessFactory } from './manager-subprocess-xsnap.js';
5
6
 
6
7
  export function makeVatManagerFactory({
7
8
  allVatPowers,
8
9
  kernelKeeper,
9
10
  vatEndowments,
11
+ startSubprocessWorkerNode,
10
12
  startXSnap,
11
13
  gcTools,
12
14
  kernelSlog,
@@ -17,6 +19,13 @@ export function makeVatManagerFactory({
17
19
  gcTools,
18
20
  });
19
21
 
22
+ const nodeSubprocessFactory = makeNodeSubprocessFactory({
23
+ startSubprocessWorker: startSubprocessWorkerNode,
24
+ kernelKeeper,
25
+ kernelSlog,
26
+ testLog: allVatPowers.testLog,
27
+ });
28
+
20
29
  const xsWorkerFactory = makeXsSubprocessFactory({
21
30
  startXSnap,
22
31
  kernelKeeper,
@@ -92,6 +101,15 @@ export function makeVatManagerFactory({
92
101
  );
93
102
  }
94
103
 
104
+ if (type === 'node-subprocess') {
105
+ return nodeSubprocessFactory.createFromBundle(
106
+ vatID,
107
+ managerOptions.bundle,
108
+ managerOptions,
109
+ liveSlotsOptions,
110
+ );
111
+ }
112
+
95
113
  if (type === 'xsnap') {
96
114
  assert(managerOptions.bundle, 'xsnap requires Bundle');
97
115
  return xsWorkerFactory.createFromBundle(
@@ -0,0 +1,118 @@
1
+ // import { spawn } from 'child_process'; // not from Compartment
2
+
3
+ import { assert, Fail } from '@agoric/assert';
4
+ import { E } from '@endo/eventual-send';
5
+ import { makePromiseKit } from '@endo/promise-kit';
6
+ import { makeManagerKit } from './manager-helper.js';
7
+
8
+ // start a "Worker" (Node's tool for starting new threads) and load a bundle
9
+ // into it
10
+
11
+ // eslint-disable-next-line no-unused-vars
12
+ function parentLog(first, ...args) {
13
+ // console.error(`--parent: ${first}`, ...args);
14
+ }
15
+
16
+ export function makeNodeSubprocessFactory(tools) {
17
+ const { startSubprocessWorker, testLog } = tools;
18
+
19
+ function createFromBundle(vatID, bundle, managerOptions, liveSlotsOptions) {
20
+ assert(!managerOptions.enableSetup, 'not supported at all');
21
+ assert(
22
+ managerOptions.useTranscript,
23
+ 'node-subprocess: useTranscript=false not supported',
24
+ );
25
+ const { name, workerOptions } = managerOptions;
26
+ const { nodeOptions } = workerOptions;
27
+
28
+ !nodeOptions ||
29
+ Array.isArray(nodeOptions) ||
30
+ Fail`nodeOptions must be an array`;
31
+
32
+ // Node worker subprocess adds `nameDisplayArg` as a dummy argument so that
33
+ // 'ps'shows which worker is for which vat
34
+ const nameDisplayArg = `${vatID}:${name !== undefined ? name : ''}`;
35
+
36
+ const mk = makeManagerKit();
37
+
38
+ // start the worker and establish a connection
39
+ const { fromChild, toChild, terminate, done } = startSubprocessWorker(
40
+ nameDisplayArg,
41
+ nodeOptions,
42
+ );
43
+
44
+ function sendToWorker(msg) {
45
+ assert(Array.isArray(msg));
46
+ toChild.write(msg);
47
+ }
48
+
49
+ // TODO: make the worker responsible for checking themselves: we send
50
+ // both the delivery and the expected syscalls, and the supervisor
51
+ // compares what the bundle does with what it was told to expect.
52
+ // Modulo flow control, we just stream transcript entries at the
53
+ // worker and eventually get back an "ok" or an error. When we do
54
+ // that, doSyscall won't even see replayed syscalls from the worker.
55
+
56
+ const { promise: dispatchReadyP, resolve: dispatchIsReady } =
57
+ makePromiseKit();
58
+ let waiting;
59
+
60
+ /**
61
+ * @param {import('@agoric/swingset-liveslots').VatDeliveryObject} delivery
62
+ * @returns {Promise<import('@agoric/swingset-liveslots').VatDeliveryResult>}
63
+ */
64
+ function deliverToWorker(delivery) {
65
+ parentLog(`sending delivery`, delivery);
66
+ !waiting || Fail`already waiting for delivery`;
67
+ const pr = makePromiseKit();
68
+ waiting = pr.resolve;
69
+ sendToWorker(['deliver', delivery]);
70
+ return pr.promise;
71
+ }
72
+ mk.setDeliverToWorker(deliverToWorker);
73
+
74
+ function handleUpstream([type, ...args]) {
75
+ parentLog(`received`, type);
76
+ if (type === 'setUplinkAck') {
77
+ parentLog(`upload ready`);
78
+ } else if (type === 'gotBundle') {
79
+ parentLog(`bundle loaded`);
80
+ } else if (type === 'dispatchReady') {
81
+ parentLog(`dispatch() ready`);
82
+ // wait10ms().then(dispatchIsReady); // stall to let logs get printed
83
+ dispatchIsReady(undefined);
84
+ } else if (type === 'syscall') {
85
+ parentLog(`syscall`, args);
86
+ const [vatSyscallObject] = args;
87
+ const vsr = mk.syscallFromWorker(vatSyscallObject);
88
+ sendToWorker(vsr);
89
+ } else if (type === 'testLog') {
90
+ testLog(...args);
91
+ } else if (type === 'deliverDone') {
92
+ parentLog(`deliverDone`);
93
+ if (waiting) {
94
+ const resolve = waiting;
95
+ waiting = null;
96
+ const [vatDeliveryResults] = args;
97
+ resolve(vatDeliveryResults);
98
+ }
99
+ } else {
100
+ parentLog(`unrecognized uplink message ${type}`);
101
+ }
102
+ }
103
+
104
+ fromChild.on('data', handleUpstream);
105
+
106
+ parentLog(`instructing worker to load bundle..`);
107
+ sendToWorker(['setBundle', vatID, bundle, liveSlotsOptions]);
108
+
109
+ function shutdown() {
110
+ terminate();
111
+ return E.when(done, _ => undefined);
112
+ }
113
+ const manager = mk.getManager(shutdown);
114
+ return dispatchReadyP.then(() => manager);
115
+ }
116
+
117
+ return harden({ createFromBundle });
118
+ }
@@ -132,16 +132,16 @@ export function makeXsSubprocessFactory({
132
132
  kernelSlog.write({ type: 'heap-snapshot-load', vatID, ...snapshotInfo });
133
133
  }
134
134
 
135
- // `startXSnap` adds `argName` as a dummy argument so that 'ps'
135
+ // `startXSnap` adds `nameDisplayArg` as a dummy argument so that 'ps'
136
136
  // shows which worker is for which vat, but is careful to prevent
137
137
  // a shell-escape attack
138
- const argName = `${vatID}:${vatName !== undefined ? vatName : ''}`;
138
+ const nameDisplayArg = `${vatID}:${vatName !== undefined ? vatName : ''}`;
139
139
 
140
140
  /** @type {ReturnType<typeof makeRevokableHandleCommandKit> | undefined} */
141
141
  let handleCommandKit = makeRevokableHandleCommandKit(handleUpstream);
142
142
 
143
143
  // start the worker and establish a connection
144
- let worker = await startXSnap(argName, {
144
+ let worker = await startXSnap(nameDisplayArg, {
145
145
  bundleIDs,
146
146
  handleCommand: handleCommandKit.handleCommand,
147
147
  metered,
@@ -256,7 +256,7 @@ export function makeXsSubprocessFactory({
256
256
  let snapshotResults;
257
257
  const closeP = worker.close();
258
258
  [worker, snapshotResults] = await Promise.all([
259
- startXSnap(argName, {
259
+ startXSnap(nameDisplayArg, {
260
260
  bundleIDs,
261
261
  handleCommand: handleCommandKit.handleCommand,
262
262
  metered,
@@ -14,13 +14,18 @@ export const makeVatOptionRecorder = (kernelKeeper, bundleHandler) => {
14
14
  critical = false,
15
15
  meterID = undefined,
16
16
  managerType = kernelKeeper.getDefaultManagerType(),
17
+ nodeOptions = undefined,
17
18
  ...leftover
18
19
  } = options;
19
20
  const unused = Object.keys(leftover);
20
21
  if (unused.length) {
21
22
  Fail`OptionRecorder: ${vatID} unused options ${unused.join(',')}`;
22
23
  }
23
- const workerOptions = await makeWorkerOptions(managerType, bundleHandler);
24
+ const workerOptions = await makeWorkerOptions(
25
+ managerType,
26
+ bundleHandler,
27
+ nodeOptions,
28
+ );
24
29
  /** @type { import('../types-internal.js').RecordedVatOptions } */
25
30
  const vatOptions = harden({
26
31
  workerOptions,
@@ -1,17 +1,29 @@
1
+ import { Fail } from '@agoric/assert';
2
+
1
3
  /**
2
4
  * @param {string} managerType
3
5
  * @param {import("../controller/bundle-handler").BundleHandler} bundleHandler
6
+ * @param {string[]} [nodeOptions]
4
7
  * @returns {Promise<import("../types-internal").WorkerOptions>}
5
8
  */
6
- export async function makeWorkerOptions(managerType, bundleHandler) {
9
+ export async function makeWorkerOptions(
10
+ managerType,
11
+ bundleHandler,
12
+ nodeOptions,
13
+ ) {
7
14
  await null;
15
+ if (nodeOptions && managerType !== 'node-subprocess') {
16
+ Fail`nodeOptions requires managerType 'node-subprocess'`;
17
+ }
8
18
  if (managerType === 'local') {
9
19
  return harden({ type: 'local' });
10
20
  } else if (managerType === 'xsnap' || managerType === 'xs-worker') {
11
21
  const bundleIDs = await bundleHandler.getCurrentBundleIDs();
12
22
  return harden({ type: 'xsnap', bundleIDs });
23
+ } else if (managerType === 'node-subprocess') {
24
+ return harden({ type: 'node-subprocess', nodeOptions });
13
25
  }
14
- throw Error(`unknown managerType '${managerType}'`);
26
+ throw Fail`unknown managerType '${managerType}'`;
15
27
  }
16
28
 
17
29
  /**
@@ -27,9 +39,11 @@ export async function updateWorkerOptions(
27
39
  await null;
28
40
  if (type === 'local') {
29
41
  return origWorkerOptions;
42
+ } else if (type === 'node-subprocess') {
43
+ return origWorkerOptions;
30
44
  } else if (type === 'xsnap') {
31
45
  const bundleIDs = await bundleHandler.getCurrentBundleIDs();
32
46
  return harden({ ...origWorkerOptions, bundleIDs });
33
47
  }
34
- throw Error(`unknown worker type '${type}'`);
48
+ throw Fail`unknown worker type '${type}'`;
35
49
  }
@@ -0,0 +1,87 @@
1
+ // this file is loaded by the controller, in the start compartment
2
+ import { spawn } from 'child_process';
3
+ import { makePromiseKit } from '@endo/promise-kit';
4
+ import { NonNullish } from '@agoric/assert';
5
+ import { arrayEncoderStream, arrayDecoderStream } from './worker-protocol.js';
6
+ import {
7
+ netstringEncoderStream,
8
+ netstringDecoderStream,
9
+ } from '../lib/netstring.js';
10
+
11
+ // Start a subprocess from a given executable, and arrange a bidirectional
12
+ // message channel with a "supervisor" within that process. Return a {
13
+ // toChild, fromChild } pair of Streams which accept/emit hardened Arrays of
14
+ // JSON-serializable data.
15
+
16
+ // eslint-disable-next-line no-unused-vars
17
+ function parentLog(first, ...args) {
18
+ // console.error(`--parent: ${first}`, ...args);
19
+ }
20
+
21
+ /** @typedef { import('child_process').IOType } IOType */
22
+ /** @typedef { import('stream').Writable } Writable */
23
+
24
+ // we send on fd3, and receive on fd4. We pass fd1/2 (stdout/err) through, so
25
+ // console log/err from the child shows up normally. We don't use Node's
26
+ // built-in serialization feature ('ipc') because the child process won't
27
+ // always be Node.
28
+ /** @type { IOType[] } */
29
+ const stdio = harden(['inherit', 'inherit', 'inherit', 'pipe', 'pipe']);
30
+
31
+ export function startSubprocessWorker(
32
+ execPath,
33
+ procArgs = [],
34
+ { netstringMaxChunkSize = undefined } = {},
35
+ ) {
36
+ const proc = spawn(execPath, procArgs, { stdio });
37
+
38
+ const toChild = arrayEncoderStream();
39
+ toChild
40
+ .pipe(netstringEncoderStream())
41
+ .pipe(/** @type {Writable} */ (proc.stdio[3]));
42
+ // proc.stdio[4].setEncoding('utf-8');
43
+ const fromChild = NonNullish(proc.stdio[4])
44
+ .pipe(netstringDecoderStream(netstringMaxChunkSize))
45
+ .pipe(arrayDecoderStream());
46
+
47
+ // fromChild.addListener('data', data => parentLog(`fd4 data`, data));
48
+ // toChild.write('hello child');
49
+
50
+ const pk = makePromiseKit();
51
+
52
+ proc.once('exit', code => {
53
+ parentLog('child exit', code);
54
+ pk.resolve(code);
55
+ });
56
+ proc.once('error', e => {
57
+ parentLog('child error', e);
58
+ pk.reject(e);
59
+ });
60
+ parentLog(`waiting on child`);
61
+
62
+ function terminate() {
63
+ proc.kill();
64
+ }
65
+
66
+ // the Transform objects don't like being hardened, so we wrap the methods
67
+ // that get used
68
+ /* @type {typeof fromChild} */
69
+ const wrappedFromChild = {
70
+ on: (...args) =>
71
+ fromChild.on(.../** @type {Parameters<typeof fromChild['on']>} */ (args)),
72
+ };
73
+ /* @type {typeof toChild} */
74
+ const wrappedToChild = {
75
+ write: (...args) =>
76
+ toChild.write(
77
+ .../** @type {Parameters<typeof toChild['write']>} */ (args),
78
+ ),
79
+ };
80
+
81
+ return harden({
82
+ fromChild: wrappedFromChild,
83
+ toChild: wrappedToChild,
84
+ terminate,
85
+ done: pk.promise,
86
+ });
87
+ }
@@ -0,0 +1,204 @@
1
+ /* global WeakRef, FinalizationRegistry */
2
+
3
+ // this file is loaded at the start of a new subprocess
4
+ import '@endo/init';
5
+
6
+ import anylogger from 'anylogger';
7
+ import fs from 'fs';
8
+ import { Buffer } from 'buffer';
9
+
10
+ import { assert, details as X, Fail } from '@agoric/assert';
11
+ import { importBundle } from '@endo/import-bundle';
12
+ import { makeMarshal } from '@endo/marshal';
13
+ import {
14
+ makeLiveSlots,
15
+ insistVatDeliveryObject,
16
+ insistVatSyscallResult,
17
+ } from '@agoric/swingset-liveslots';
18
+ import engineGC from '../../lib-nodejs/engine-gc.js';
19
+ import { makeGcAndFinalize } from '../../lib-nodejs/gc-and-finalize.js';
20
+ import { makeDummyMeterControl } from '../../kernel/dummyMeterControl.js';
21
+ import { encode, decode } from '../../lib/netstring.js';
22
+ import { waitUntilQuiescent } from '../../lib-nodejs/waitUntilQuiescent.js';
23
+ import {
24
+ makeSupervisorDispatch,
25
+ makeSupervisorSyscall,
26
+ makeVatConsole,
27
+ } from '../supervisor-helper.js';
28
+
29
+ // eslint-disable-next-line no-unused-vars
30
+ function workerLog(first, ...args) {
31
+ // console.error(`---worker: ${first}`, ...args);
32
+ }
33
+
34
+ workerLog(`supervisor started`);
35
+
36
+ function makeNetstringReader({ fd, encoding }) {
37
+ const input = Buffer.alloc(32 * 1024);
38
+ let buffered = Buffer.alloc(0);
39
+ let decoded = [];
40
+
41
+ const readMore = () => {
42
+ assert(!decoded.length);
43
+ // we could be smarter about read lengths (parse the the netstring
44
+ // header and do a blocking read of the entire payload), but the
45
+ // efficiency gain is not huge
46
+ const bytesRead = fs.readSync(fd, input); // blocking read
47
+ if (!bytesRead) {
48
+ throw Error('read pipe closed');
49
+ }
50
+ const more = input.subarray(0, bytesRead);
51
+ buffered = Buffer.concat([buffered, more]);
52
+ const { leftover, payloads } = decode(buffered);
53
+ buffered = leftover;
54
+ decoded = payloads;
55
+ };
56
+
57
+ return harden({
58
+ read: () => {
59
+ for (;;) {
60
+ if (decoded.length) {
61
+ const ns = decoded.shift();
62
+ return JSON.parse(ns.toString(encoding));
63
+ }
64
+ readMore(); // blocks
65
+ }
66
+ },
67
+ });
68
+ }
69
+
70
+ let dispatch;
71
+
72
+ function writeToParent(command) {
73
+ let buf = encode(Buffer.from(JSON.stringify(command)));
74
+ while (buf.length) {
75
+ const bytesWritten = fs.writeSync(4, buf);
76
+ if (!bytesWritten) {
77
+ throw Error('write pipe closed');
78
+ }
79
+ buf = buf.subarray(bytesWritten);
80
+ }
81
+ }
82
+ const toParent = { write: writeToParent };
83
+ const fromParent = makeNetstringReader({ fd: 3, encoding: 'utf-8' });
84
+
85
+ function sendUplink(msg) {
86
+ msg instanceof Array || Fail`msg must be an Array`;
87
+ toParent.write(msg);
88
+ }
89
+
90
+ function handleStart(_margs) {
91
+ // TODO: parent should send ['start', vatID]
92
+ workerLog(`got start`);
93
+ sendUplink(['gotStart']);
94
+ }
95
+
96
+ function handleSetBundle(margs) {
97
+ const [vatID, bundle, liveSlotsOptions] = margs;
98
+
99
+ function testLog(...args) {
100
+ sendUplink(['testLog', ...args]);
101
+ }
102
+
103
+ // syscallToManager can throw or return OK/ERR
104
+ function syscallToManager(vatSyscallObject) {
105
+ sendUplink(['syscall', vatSyscallObject]);
106
+ const result = fromParent.read();
107
+ workerLog(' ... syscall result:', result);
108
+ insistVatSyscallResult(result);
109
+ return result;
110
+ }
111
+ // this 'syscall' throws or returns data
112
+ const syscall = makeSupervisorSyscall(syscallToManager);
113
+ const vatPowers = {
114
+ makeMarshal,
115
+ testLog,
116
+ };
117
+
118
+ const gcTools = harden({
119
+ WeakRef,
120
+ FinalizationRegistry,
121
+ waitUntilQuiescent,
122
+ gcAndFinalize: makeGcAndFinalize(engineGC),
123
+ meterControl: makeDummyMeterControl(),
124
+ });
125
+
126
+ const makeLogMaker = tag => {
127
+ const logger = anylogger(tag);
128
+ const makeLog = level => {
129
+ const log = logger[level];
130
+ assert.typeof(log, 'function', X`logger[${level}] must be a function`);
131
+ return (...args) => {
132
+ log(...args);
133
+ };
134
+ };
135
+ return makeLog;
136
+ };
137
+
138
+ // Enable or disable the console accordingly.
139
+ const workerEndowments = {
140
+ console: makeVatConsole(makeLogMaker(`SwingSet:vat:${vatID}`)),
141
+ assert,
142
+ };
143
+
144
+ async function buildVatNamespace(lsEndowments, inescapableGlobalProperties) {
145
+ const vatNS = await importBundle(bundle, {
146
+ endowments: { ...workerEndowments, ...lsEndowments },
147
+ inescapableGlobalProperties,
148
+ });
149
+ workerLog(`got vatNS:`, Object.keys(vatNS).join(','));
150
+ return vatNS;
151
+ }
152
+
153
+ const ls = makeLiveSlots(
154
+ syscall,
155
+ vatID,
156
+ vatPowers,
157
+ liveSlotsOptions,
158
+ gcTools,
159
+ makeVatConsole(makeLogMaker(`SwingSet:ls:${vatID}`)),
160
+ buildVatNamespace,
161
+ );
162
+
163
+ sendUplink(['gotBundle']);
164
+ assert(ls.dispatch);
165
+ dispatch = makeSupervisorDispatch(ls.dispatch);
166
+ workerLog(`created dispatch()`);
167
+ sendUplink(['dispatchReady']);
168
+ }
169
+
170
+ async function handleDeliver(margs) {
171
+ if (!dispatch) {
172
+ throw Error(`error: deliver before dispatchReady`);
173
+ }
174
+ const [vatDeliveryObject] = margs;
175
+ harden(vatDeliveryObject);
176
+ insistVatDeliveryObject(vatDeliveryObject);
177
+ const vatDeliveryResults = await dispatch(vatDeliveryObject);
178
+ sendUplink(['deliverDone', vatDeliveryResults]);
179
+ }
180
+
181
+ async function handleCommand(command) {
182
+ const [type, ...margs] = command;
183
+ workerLog(`received`, type);
184
+ switch (type) {
185
+ case 'start':
186
+ return handleStart(margs);
187
+ case 'setBundle':
188
+ return handleSetBundle(margs);
189
+ case 'deliver':
190
+ return handleDeliver(margs);
191
+ default:
192
+ throw Error(`unrecognized downlink message ${type}`);
193
+ }
194
+ }
195
+
196
+ async function loop() {
197
+ await 47; // I can wait for anything, so I choose 47. It's the most ideal number.
198
+ for (;;) {
199
+ const command = fromParent.read();
200
+ await handleCommand(command);
201
+ }
202
+ }
203
+
204
+ loop().catch(err => console.log(`error in loop`, err));
package/src/typeGuards.js CHANGED
@@ -1,12 +1,17 @@
1
1
  // @jessie-check
2
2
  import { M } from '@agoric/store';
3
3
 
4
- export const ManagerType = M.or('xsnap', 'xs-worker', 'local');
4
+ export const ManagerType = M.or(
5
+ 'xsnap',
6
+ 'xs-worker',
7
+ 'node-subprocess',
8
+ 'local',
9
+ );
5
10
 
6
11
  const Bundle = M.splitRecord({ moduleType: M.string() });
7
12
 
8
13
  const SwingsetConfigOptions = harden({
9
- creationOptions: M.splitRecord({}, { critial: M.boolean() }),
14
+ creationOptions: M.splitRecord({}, { critical: M.boolean() }),
10
15
  parameters: M.recordOf(M.string(), M.any()),
11
16
  });
12
17
 
@@ -17,7 +17,7 @@ export {};
17
17
  * @typedef { { moduleFormat: 'nestedEvaluate', source: string, sourceMap?: string } } NestedEvaluateBundle
18
18
  * @typedef { EndoZipBase64Bundle | GetExportBundle | NestedEvaluateBundle } Bundle
19
19
  *
20
- * @typedef { 'local' | 'xsnap' | 'xs-worker' } ManagerType
20
+ * @typedef { 'local' | 'node-subprocess' | 'xsnap' | 'xs-worker' } ManagerType
21
21
  */
22
22
 
23
23
  /**
@@ -308,7 +308,8 @@ export {};
308
308
  * config.vats[name].creationOptions: StaticVatOptions
309
309
  *
310
310
  * @typedef { { enableDisavow?: boolean } } OptEnableDisavow
311
- * @typedef { BaseVatOptions & OptManagerType & OptEnableDisavow } StaticVatOptions
311
+ * @typedef { { nodeOptions?: string[] } } OptNodeOptions
312
+ * @typedef { BaseVatOptions & OptManagerType & OptEnableDisavow & OptNodeOptions } StaticVatOptions
312
313
  *
313
314
  * @typedef { { vatParameters?: object, upgradeMessage?: string } } VatUpgradeOptions
314
315
  * @typedef { { incarnationNumber: number } } VatUpgradeResults
@@ -1,15 +1,16 @@
1
1
  export {};
2
2
 
3
3
  /**
4
- * The internal data that controls which worker we use (and how we use
5
- * it) is stored in a WorkerOptions record, which comes in "local" and
6
- * "xsnap" flavors.
4
+ * The internal data that controls which worker we use (and how we use it) is
5
+ * stored in a WorkerOptions record, which comes in "local", "node-subprocess",
6
+ * and "xsnap" flavors.
7
7
  *
8
8
  * @typedef { { type: 'local' } } LocalWorkerOptions
9
9
  * @typedef { { type: 'xsnap', bundleIDs: BundleID[] } } XSnapWorkerOptions
10
+ * @typedef { { type: 'node-subprocess', nodeOptions?: string[] } } NodeSubprocessWorkerOptions
10
11
  * bundleIDs indicate the SES lockdown and supervisor/liveslots bundles to
11
12
  * evaluate into a new xsnap worker
12
- * @typedef { LocalWorkerOptions | XSnapWorkerOptions } WorkerOptions
13
+ * @typedef { LocalWorkerOptions | XSnapWorkerOptions | NodeSubprocessWorkerOptions } WorkerOptions
13
14
  *
14
15
  * @typedef { string } VatID
15
16
  * @typedef { string } MeterID
@@ -19,7 +19,7 @@ import {
19
19
 
20
20
  const { quote: q, Fail } = assert;
21
21
 
22
- const managerTypes = ['local', 'xsnap', 'xs-worker']; // xs-worker is alias
22
+ const managerTypes = ['local', 'node-subprocess', 'xsnap', 'xs-worker']; // xs-worker is alias
23
23
 
24
24
  function producePRR() {
25
25
  const { promise, resolve, reject } = makePromiseKit();
@@ -339,6 +339,7 @@ export function buildRootObject(vatPowers, _vatParameters, baggage) {
339
339
  name,
340
340
  meter, // stripped out and converted
341
341
  managerType, // TODO: not sure we want vats to be able to control this
342
+ nodeOptions, // TODO: ditto
342
343
  vatParameters, // stripped out and re-added
343
344
  enableSetup,
344
345
  enablePipelining,
@@ -367,6 +368,9 @@ export function buildRootObject(vatPowers, _vatParameters, baggage) {
367
368
  managerTypes.includes(managerType) ||
368
369
  Fail`CreateVatOptions: bad managerType ${managerType}`;
369
370
  }
371
+ if (managerType !== 'node-subprocess' && nodeOptions) {
372
+ Fail`CreateVatOptions: nodeOptions requires managerType='node-subprocess'`;
373
+ }
370
374
  assertType('enableSetup', enableSetup, 'boolean');
371
375
  assertType('enablePipelining', enablePipelining, 'boolean');
372
376
  assertType('useTranscript', useTranscript, 'boolean');