@agoric/swingset-vat 0.32.3-dev-046eb05.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 +12 -12
- package/src/controller/controller.js +6 -0
- package/src/controller/initializeKernel.js +1 -0
- package/src/controller/startNodeSubprocess.js +17 -0
- package/src/kernel/kernel.js +2 -0
- package/src/kernel/state/kernelKeeper.js +1 -1
- package/src/kernel/vat-loader/manager-factory.js +18 -0
- package/src/kernel/vat-loader/manager-subprocess-node.js +118 -0
- package/src/kernel/vat-loader/manager-subprocess-xsnap.js +4 -4
- package/src/lib/recordVatOptions.js +6 -1
- package/src/lib/workerOptions.js +17 -3
- package/src/lib-nodejs/spawnSubprocessWorker.js +87 -0
- package/src/supervisors/subprocess-node/supervisor-subprocess-node.js +204 -0
- package/src/typeGuards.js +7 -2
- package/src/types-external.js +3 -2
- package/src/types-internal.js +5 -4
- package/src/vats/vat-admin/vat-vat-admin.js +5 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agoric/swingset-vat",
|
|
3
|
-
"version": "0.32.3-dev-
|
|
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-
|
|
32
|
-
"@agoric/internal": "0.3.3-dev-
|
|
33
|
-
"@agoric/store": "0.9.3-dev-
|
|
34
|
-
"@agoric/swing-store": "0.9.2-dev-
|
|
35
|
-
"@agoric/swingset-liveslots": "0.10.3-dev-
|
|
36
|
-
"@agoric/swingset-xsnap-supervisor": "0.10.3-dev-
|
|
37
|
-
"@agoric/time": "0.3.3-dev-
|
|
38
|
-
"@agoric/vat-data": "0.5.3-dev-
|
|
39
|
-
"@agoric/xsnap": "0.14.3-dev-
|
|
40
|
-
"@agoric/xsnap-lockdown": "0.14.1-dev-
|
|
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": "
|
|
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
|
+
}
|
package/src/kernel/kernel.js
CHANGED
|
@@ -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 `
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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,
|
package/src/lib/workerOptions.js
CHANGED
|
@@ -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(
|
|
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
|
|
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
|
|
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(
|
|
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({}, {
|
|
14
|
+
creationOptions: M.splitRecord({}, { critical: M.boolean() }),
|
|
10
15
|
parameters: M.recordOf(M.string(), M.any()),
|
|
11
16
|
});
|
|
12
17
|
|
package/src/types-external.js
CHANGED
|
@@ -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 {
|
|
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
|
package/src/types-internal.js
CHANGED
|
@@ -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
|
-
*
|
|
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');
|