@agoric/swingset-xsnap-supervisor 0.9.1-dev-6e51de2.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.
@@ -0,0 +1 @@
1
+ 9ed047832309a39356fd383c3e74e8b0973c7230f29b7b02d71a2e3555e4d60f
package/lib/capdata.js ADDED
@@ -0,0 +1,20 @@
1
+ import { Fail } from '@agoric/assert';
2
+
3
+ /* eslint-disable jsdoc/require-returns-check */
4
+ /**
5
+ * Assert function to ensure that something expected to be a capdata object
6
+ * actually is. A capdata object should have a .body property that's a string
7
+ * and a .slots property that's an array of strings.
8
+ *
9
+ * @param {any} capdata The object to be tested
10
+ * @throws {Error} if, upon inspection, the parameter does not satisfy the above
11
+ * criteria.
12
+ * @returns {asserts capdata is import('@endo/marshal').CapData<string>}
13
+ */
14
+ export function insistCapData(capdata) {
15
+ typeof capdata.body === 'string' ||
16
+ Fail`capdata has non-string .body ${capdata.body}`;
17
+ Array.isArray(capdata.slots) ||
18
+ Fail`capdata has non-Array slots ${capdata.slots}`;
19
+ // TODO check that the .slots array elements are actually strings
20
+ }
package/lib/entry.js ADDED
@@ -0,0 +1 @@
1
+ import './supervisor-subprocess-xsnap.js';
@@ -0,0 +1,91 @@
1
+ /* global setImmediate */
2
+
3
+ /* A note on our GC terminology:
4
+ *
5
+ * We define four states for any JS object to be in:
6
+ *
7
+ * REACHABLE: There exists a path from some root (live export or top-level
8
+ * global) to this object, making it ineligible for collection. Userspace vat
9
+ * code has a strong reference to it (and userspace is not given access to
10
+ * WeakRef, so it has no weak reference that might be used to get access).
11
+ *
12
+ * UNREACHABLE: There is no strong reference from a root to the object.
13
+ * Userspace vat code has no means to access the object, although liveslots
14
+ * might (via a WeakRef). The object is eligible for collection, but that
15
+ * collection has not yet happened. The liveslots WeakRef is still alive: if
16
+ * it were to call `.deref()`, it would get the object.
17
+ *
18
+ * COLLECTED: The JS engine has performed enough GC to notice the
19
+ * unreachability of the object, and has collected it. The liveslots WeakRef
20
+ * is dead: `wr.deref() === undefined`. Neither liveslots nor userspace has
21
+ * any way to reach the object, and never will again. A finalizer callback
22
+ * has been queued, but not yet executed.
23
+ *
24
+ * FINALIZED: The JS engine has run the finalizer callback. Once the
25
+ * callback completes, the object is thoroughly dead and unremembered,
26
+ * and no longer exists in one of these four states.
27
+ *
28
+ * The transition from REACHABLE to UNREACHABLE always happens as a result of
29
+ * a message delivery or resolution notification (e.g when userspace
30
+ * overwrites a variable, deletes a Map entry, or a callback on the promise
31
+ * queue which closed over some objects is retired and deleted).
32
+ *
33
+ * The transition from UNREACHABLE to COLLECTED can happen spontaneously, as
34
+ * the JS engine decides it wants to perform GC. It will also happen
35
+ * deliberately if we provoke a GC call with a magic function like `gc()`
36
+ * (when Node.js imports `engine-gc`, which is morally-equivalent to
37
+ * running with `--expose-gc`, or when XS is configured to provide it as a
38
+ * C-level callback). We can force GC, but we cannot prevent it from happening
39
+ * at other times.
40
+ *
41
+ * FinalizationRegistry callbacks are defined to run on their own turn, so
42
+ * the transition from COLLECTED to FINALIZED occurs at a turn boundary.
43
+ * Node.js appears to schedule these finalizers on the timer/IO queue, not
44
+ * the promise/microtask queue. So under Node.js, you need a `setImmediate()`
45
+ * or two to ensure that finalizers have had a chance to run. XS is different
46
+ * but responds well to similar techniques.
47
+ */
48
+
49
+ /*
50
+ * `gcAndFinalize` must be defined in the start compartment. It uses
51
+ * platform-specific features to provide a function which provokes a full GC
52
+ * operation: all "UNREACHABLE" objects should transition to "COLLECTED"
53
+ * before it returns. In addition, `gcAndFinalize()` returns a Promise. This
54
+ * Promise will resolve (with `undefined`) after all FinalizationRegistry
55
+ * callbacks have executed, causing all COLLECTED objects to transition to
56
+ * FINALIZED. If the caller can manage call gcAndFinalize with an empty
57
+ * promise queue, then their .then callback will also start with an empty
58
+ * promise queue, and there will be minimal uncollected unreachable objects
59
+ * in the heap when it begins.
60
+ *
61
+ * `gcAndFinalize` depends upon platform-specific tools to provoke a GC sweep
62
+ * and wait for finalizers to run: a `gc()` function, and `setImmediate`. If
63
+ * these tools do not exist, this function will do nothing, and return a
64
+ * dummy pre-resolved Promise.
65
+ */
66
+
67
+ export function makeGcAndFinalize(gcPower) {
68
+ if (typeof gcPower !== 'function') {
69
+ if (gcPower !== false) {
70
+ // We weren't explicitly disabled, so warn.
71
+ console.warn(
72
+ Error(`no gcPower() function; skipping finalizer provocation`),
73
+ );
74
+ }
75
+ }
76
+ return async function gcAndFinalize() {
77
+ if (typeof gcPower !== 'function') {
78
+ return;
79
+ }
80
+
81
+ // on Node.js, GC seems to work better if the promise queue is empty first
82
+ await new Promise(setImmediate);
83
+ // on xsnap, we must do it twice for some reason
84
+ await new Promise(setImmediate);
85
+ gcPower();
86
+ // this gives finalizers a chance to run
87
+ await new Promise(setImmediate);
88
+ // Node.js seems to need another for promises to get cleared out
89
+ await new Promise(setImmediate);
90
+ };
91
+ }
@@ -0,0 +1,169 @@
1
+ import { assert } from '@agoric/assert';
2
+ import {
3
+ insistVatSyscallObject,
4
+ insistVatSyscallResult,
5
+ } from '@agoric/swingset-liveslots';
6
+
7
+ /**
8
+ * @typedef {import('@agoric/swingset-liveslots').VatDeliveryObject} VatDeliveryObject
9
+ * @typedef {import('@agoric/swingset-liveslots').VatDeliveryResult} VatDeliveryResult
10
+ * @typedef {import('@agoric/swingset-liveslots').VatSyscallObject} VatSyscallObject
11
+ * @typedef {import('@agoric/swingset-liveslots').VatSyscaller} VatSyscaller
12
+ * @typedef {import('@endo/marshal').CapData<string>} SwingSetCapData
13
+ * @typedef { (delivery: VatDeliveryObject) => (VatDeliveryResult | Promise<VatDeliveryResult>) } VatDispatcherSyncAsync
14
+ * @typedef { (delivery: VatDeliveryObject) => Promise<VatDeliveryResult> } VatDispatcher
15
+ */
16
+
17
+ /**
18
+ * Given the liveslots 'dispatch' function, return a version that never
19
+ * rejects. It will always return a VatDeliveryResult, even if liveslots
20
+ * throws or rejects. All supervisors should wrap the liveslots `dispatch`
21
+ * function with this one, and call it in response to messages from the
22
+ * manager process.
23
+ *
24
+ * @param {VatDispatcherSyncAsync} dispatch
25
+ * @returns {VatDispatcher}
26
+ */
27
+ function makeSupervisorDispatch(dispatch) {
28
+ /**
29
+ * @param {VatDeliveryObject} delivery
30
+ * @returns {Promise<VatDeliveryResult>}
31
+ */
32
+ async function dispatchToVat(delivery) {
33
+ // the (low-level) vat is responsible for giving up agency, but we still
34
+ // protect against exceptions
35
+ return Promise.resolve(delivery)
36
+ .then(dispatch)
37
+ .then(
38
+ () => harden(['ok', null, null]),
39
+ err => {
40
+ // TODO react more thoughtfully, maybe terminate the vat
41
+ console.warn(`error during vat dispatch() of ${delivery}`, err);
42
+ return harden(['error', `${err}`, null]);
43
+ },
44
+ );
45
+ }
46
+
47
+ return harden(dispatchToVat);
48
+ }
49
+ harden(makeSupervisorDispatch);
50
+ export { makeSupervisorDispatch };
51
+
52
+ /**
53
+ * This returns a function that is provided to liveslots as the 'syscall'
54
+ * argument: an object with one method per syscall type. These methods return
55
+ * data, or nothing. If the kernel experiences a problem executing the syscall,
56
+ * the method will throw, or the kernel will kill the vat, or both.
57
+ *
58
+ * I should be given a `syscallToManager` function that accepts a
59
+ * VatSyscallObject and (synchronously) returns a VatSyscallResult.
60
+ *
61
+ * @param {VatSyscaller} syscallToManager
62
+ * @param {boolean} workerCanBlock
63
+ * @typedef { unknown } TheSyscallObjectWithMethodsThatLiveslotsWants
64
+ * @returns {TheSyscallObjectWithMethodsThatLiveslotsWants}
65
+ */
66
+ function makeSupervisorSyscall(syscallToManager, workerCanBlock) {
67
+ function doSyscall(fields) {
68
+ insistVatSyscallObject(fields);
69
+ /** @type { VatSyscallObject } */
70
+ const vso = harden(fields);
71
+ let r;
72
+ try {
73
+ r = syscallToManager(vso);
74
+ } catch (err) {
75
+ console.warn(`worker got error during syscall:`, err);
76
+ throw err;
77
+ }
78
+ if (!workerCanBlock) {
79
+ // we don't expect an answer
80
+ return null;
81
+ }
82
+ const vsr = r;
83
+ insistVatSyscallResult(vsr);
84
+ const [type, ...rest] = vsr;
85
+ switch (type) {
86
+ case 'ok': {
87
+ const [data] = rest;
88
+ return data;
89
+ }
90
+ case 'error': {
91
+ const [err] = rest;
92
+ throw Error(`syscall.${fields[0]} failed: ${err}`);
93
+ }
94
+ default:
95
+ throw Error(`unknown result type ${type}`);
96
+ }
97
+ }
98
+
99
+ // this will be given to liveslots, it should have distinct methods that
100
+ // return immediate results or throw errors
101
+ const syscallForVat = {
102
+ /** @type {(target: string, method: string, args: SwingSetCapData, result?: string) => unknown } */
103
+ send: (target, methargs, result) =>
104
+ doSyscall(['send', target, { methargs, result }]),
105
+ subscribe: vpid => doSyscall(['subscribe', vpid]),
106
+ resolve: resolutions => doSyscall(['resolve', resolutions]),
107
+ exit: (isFailure, data) => doSyscall(['exit', isFailure, data]),
108
+ dropImports: vrefs => doSyscall(['dropImports', vrefs]),
109
+ retireImports: vrefs => doSyscall(['retireImports', vrefs]),
110
+ retireExports: vrefs => doSyscall(['retireExports', vrefs]),
111
+ abandonExports: vrefs => doSyscall(['abandonExports', vrefs]),
112
+
113
+ // These syscalls should be omitted if the worker cannot get a
114
+ // synchronous return value back from the kernel, such as when the worker
115
+ // is in a child process or thread, and cannot be blocked until the
116
+ // result gets back. vatstoreSet and vatstoreDelete are included because
117
+ // vatstoreSet requires a result, and we offer them as a group.
118
+ callNow: (target, method, args) =>
119
+ doSyscall(['callNow', target, method, args]),
120
+ vatstoreGet: key => {
121
+ const result = doSyscall(['vatstoreGet', key]);
122
+ return result === null ? undefined : result;
123
+ },
124
+ vatstoreGetNextKey: priorKey => doSyscall(['vatstoreGetNextKey', priorKey]),
125
+ vatstoreSet: (key, value) => doSyscall(['vatstoreSet', key, value]),
126
+ vatstoreDelete: key => doSyscall(['vatstoreDelete', key]),
127
+ };
128
+
129
+ const blocking = [
130
+ 'callNow',
131
+ 'vatstoreGet',
132
+ 'vatstoreGetNextKey',
133
+ 'vatstoreSet',
134
+ 'vatstoreDelete',
135
+ ];
136
+
137
+ if (!workerCanBlock) {
138
+ for (const name of blocking) {
139
+ const err = `this non-blocking worker transport cannot syscall.${name}`;
140
+ syscallForVat[name] = () => assert.fail(err);
141
+ }
142
+ }
143
+
144
+ return harden(syscallForVat);
145
+ }
146
+
147
+ harden(makeSupervisorSyscall);
148
+ export { makeSupervisorSyscall };
149
+
150
+ /**
151
+ * Create a vat console from a log stream maker.
152
+ *
153
+ * TODO: consider other methods per SES VirtualConsole.
154
+ * See https://github.com/Agoric/agoric-sdk/issues/2146
155
+ *
156
+ * @param {(level: string) => (...args: any[]) => void} makeLog
157
+ */
158
+ function makeVatConsole(makeLog) {
159
+ return harden({
160
+ debug: makeLog('debug'),
161
+ log: makeLog('log'),
162
+ info: makeLog('info'),
163
+ warn: makeLog('warn'),
164
+ error: makeLog('error'),
165
+ });
166
+ }
167
+
168
+ harden(makeVatConsole);
169
+ export { makeVatConsole };
@@ -0,0 +1,333 @@
1
+ /* global globalThis WeakRef FinalizationRegistry */
2
+ import { assert, Fail } from '@agoric/assert';
3
+ import { importBundle } from '@endo/import-bundle';
4
+ import { makeMarshal } from '@endo/marshal';
5
+ import {
6
+ makeLiveSlots,
7
+ insistVatDeliveryObject,
8
+ insistVatSyscallResult,
9
+ } from '@agoric/swingset-liveslots';
10
+ // import '../../types-ambient.js';
11
+ // grumble... waitUntilQuiescent is exported and closes over ambient authority
12
+ import { waitUntilQuiescent } from './waitUntilQuiescent.js';
13
+ import { makeGcAndFinalize } from './gc-and-finalize.js';
14
+
15
+ import {
16
+ makeSupervisorDispatch,
17
+ makeSupervisorSyscall,
18
+ makeVatConsole,
19
+ } from './supervisor-helper.js';
20
+
21
+ /**
22
+ * @typedef {import('@agoric/swingset-liveslots').VatDeliveryObject} VatDeliveryObject
23
+ * @typedef {import('@agoric/swingset-liveslots').VatDeliveryResult} VatDeliveryResult
24
+ * @typedef {import('@agoric/swingset-liveslots').VatSyscallObject} VatSyscallObject
25
+ * @typedef {import('@agoric/swingset-liveslots').VatSyscallResult} VatSyscallResult
26
+ * @typedef {import('@agoric/swingset-liveslots').VatSyscaller} VatSyscaller
27
+ * @typedef {import('@agoric/swingset-liveslots').LiveSlotsOptions} LiveSlotsOptions
28
+ * @typedef {import('@agoric/swingset-liveslots').MeterControl} MeterControl
29
+ */
30
+
31
+ const encoder = new TextEncoder();
32
+ const decoder = new TextDecoder();
33
+
34
+ // eslint-disable-next-line no-unused-vars
35
+ function workerLog(first, ...args) {
36
+ // eslint-disable-next-line
37
+ // console.log(`---worker: ${first}`, ...args);
38
+ }
39
+
40
+ workerLog(`supervisor started`);
41
+
42
+ function makeMeterControl() {
43
+ let meteringDisabled = 0;
44
+
45
+ function isMeteringDisabled() {
46
+ return !!meteringDisabled;
47
+ }
48
+
49
+ function assertIsMetered(msg) {
50
+ assert(!meteringDisabled, msg);
51
+ }
52
+ function assertNotMetered(msg) {
53
+ assert(!!meteringDisabled, msg);
54
+ }
55
+
56
+ function runWithoutMetering(thunk) {
57
+ const limit = globalThis.currentMeterLimit();
58
+ const before = globalThis.resetMeter(0, 0);
59
+ meteringDisabled += 1;
60
+ try {
61
+ return thunk();
62
+ } finally {
63
+ globalThis.resetMeter(limit, before);
64
+ meteringDisabled -= 1;
65
+ }
66
+ }
67
+
68
+ async function runWithoutMeteringAsync(thunk) {
69
+ const limit = globalThis.currentMeterLimit();
70
+ const before = globalThis.resetMeter(0, 0);
71
+ meteringDisabled += 1;
72
+ return Promise.resolve()
73
+ .then(() => thunk())
74
+ .finally(() => {
75
+ globalThis.resetMeter(limit, before);
76
+ meteringDisabled -= 1;
77
+ });
78
+ }
79
+
80
+ // return a version of f that runs outside metering
81
+ function unmetered(f) {
82
+ function wrapped(...args) {
83
+ return runWithoutMetering(() => f(...args));
84
+ }
85
+ return harden(wrapped);
86
+ }
87
+
88
+ /** @type { MeterControl } */
89
+ const meterControl = {
90
+ isMeteringDisabled,
91
+ assertIsMetered,
92
+ assertNotMetered,
93
+ runWithoutMetering,
94
+ runWithoutMeteringAsync,
95
+ unmetered,
96
+ };
97
+ return harden(meterControl);
98
+ }
99
+
100
+ const meterControl = makeMeterControl();
101
+
102
+ /**
103
+ * Wrap byte-level protocols with tagged array codec.
104
+ *
105
+ * @param {(cmd: ArrayBuffer) => ArrayBuffer} issueCommand as from xsnap
106
+ * @typedef { [unknown, ...unknown[]] } Tagged tagged array
107
+ */
108
+ function managerPort(issueCommand) {
109
+ /** @type { (item: Tagged) => ArrayBuffer } */
110
+ const encode = item => {
111
+ let txt;
112
+ try {
113
+ txt = JSON.stringify(item);
114
+ } catch (nope) {
115
+ workerLog(nope.message, item);
116
+ throw nope;
117
+ }
118
+ return encoder.encode(txt).buffer;
119
+ };
120
+
121
+ /** @type { (msg: ArrayBuffer) => any } */
122
+ const decodeData = msg => JSON.parse(decoder.decode(msg) || 'null');
123
+
124
+ /** @type { (msg: ArrayBuffer) => Tagged } */
125
+ function decode(msg) {
126
+ /** @type { Tagged } */
127
+ const item = decodeData(msg);
128
+ Array.isArray(item) || Fail`expected array`;
129
+ item.length > 0 || Fail`empty array lacks tag`;
130
+ return item;
131
+ }
132
+
133
+ return harden({
134
+ /** @type { (item: Tagged) => void } */
135
+ send: item => {
136
+ issueCommand(encode(item));
137
+ },
138
+ /** @type { (item: Tagged) => unknown } */
139
+ call: item => decodeData(issueCommand(encode(item))),
140
+
141
+ /**
142
+ * Wrap an async Tagged handler in the xsnap async reporting idiom.
143
+ *
144
+ * @param {(item: Tagged) => Promise<Tagged>} f async Tagged handler
145
+ * @returns {(msg: ArrayBuffer) => Report<ArrayBuffer>} xsnap style handleCommand
146
+ *
147
+ * @typedef { { result?: T } } Report<T> report T when idle
148
+ * @template T
149
+ */
150
+ handlerFrom(f) {
151
+ const lastResort = encoder.encode(`exception from ${f.name}`).buffer;
152
+ return msg => {
153
+ const report = {};
154
+ f(decode(msg))
155
+ .then(item => {
156
+ workerLog('result', item);
157
+ report.result = encode(item);
158
+ })
159
+ .catch(err => {
160
+ report.result = encode(['err', err.name, err.message]);
161
+ })
162
+ .catch(_err => {
163
+ report.result = lastResort;
164
+ });
165
+ return report;
166
+ };
167
+ },
168
+ });
169
+ }
170
+
171
+ // please excuse copy-and-paste from kernel.js
172
+ function abbreviateReplacer(_, arg) {
173
+ if (typeof arg === 'bigint') {
174
+ // since testLog is only for testing, 2^53 is enough.
175
+ // precedent: 32a1dd3
176
+ return Number(arg);
177
+ }
178
+ if (typeof arg === 'string' && arg.length >= 40) {
179
+ // truncate long strings
180
+ return `${arg.slice(0, 15)}...${arg.slice(arg.length - 15)}`;
181
+ }
182
+ return arg;
183
+ }
184
+
185
+ /**
186
+ * @param {ReturnType<typeof managerPort>} port
187
+ */
188
+ function makeWorker(port) {
189
+ /** @type { ((delivery: VatDeliveryObject) => Promise<VatDeliveryResult>) | null } */
190
+ let dispatch = null;
191
+
192
+ /**
193
+ * @param {unknown} vatID
194
+ * @param {unknown} bundle
195
+ * @param {LiveSlotsOptions} liveSlotsOptions
196
+ * @returns {Promise<Tagged>}
197
+ */
198
+ async function setBundle(vatID, bundle, liveSlotsOptions) {
199
+ /** @type { VatSyscaller } */
200
+ function syscallToManager(vatSyscallObject) {
201
+ workerLog('doSyscall', vatSyscallObject);
202
+ const result = port.call(['syscall', vatSyscallObject]);
203
+ workerLog(' ... syscall result:', result);
204
+ insistVatSyscallResult(result);
205
+ return result;
206
+ }
207
+
208
+ const syscall = makeSupervisorSyscall(syscallToManager, true);
209
+
210
+ const vatPowers = {
211
+ makeMarshal,
212
+ testLog: (...args) =>
213
+ port.send([
214
+ 'testLog',
215
+ ...args.map(arg =>
216
+ typeof arg === 'string'
217
+ ? arg
218
+ : JSON.stringify(arg, abbreviateReplacer),
219
+ ),
220
+ ]),
221
+ };
222
+
223
+ const gcTools = harden({
224
+ WeakRef,
225
+ FinalizationRegistry,
226
+ waitUntilQuiescent,
227
+ gcAndFinalize: makeGcAndFinalize(globalThis.gc),
228
+ meterControl,
229
+ });
230
+
231
+ /** @param {string} source */
232
+ const makeLogMaker = source => {
233
+ /** @param {string} level */
234
+ const makeLog = level => {
235
+ // Capture the `console.log`, etc.'s `printAll` function.
236
+ const printAll = console[level];
237
+ assert.typeof(printAll, 'function');
238
+ const portSendingPrinter = (...args) => {
239
+ port.send(['sourcedConsole', source, level, ...args]);
240
+ };
241
+ return (...args) => {
242
+ // Use the causal console, but output to the port.
243
+ //
244
+ // FIXME: This is a hack until the start compartment can create
245
+ // Console objects that log to a different destination than
246
+ // `globalThis.console`.
247
+ const { print: savePrinter } = globalThis;
248
+ try {
249
+ globalThis.print = portSendingPrinter;
250
+ printAll(...args);
251
+ } finally {
252
+ globalThis.print = savePrinter;
253
+ }
254
+ };
255
+ };
256
+ return makeLog;
257
+ };
258
+
259
+ const workerEndowments = {
260
+ console: makeVatConsole(makeLogMaker('vat')),
261
+ assert,
262
+ // bootstrap provides HandledPromise
263
+ HandledPromise: globalThis.HandledPromise,
264
+ TextEncoder,
265
+ TextDecoder,
266
+ Base64: globalThis.Base64, // Present only in XSnap
267
+ };
268
+
269
+ async function buildVatNamespace(
270
+ lsEndowments,
271
+ inescapableGlobalProperties,
272
+ ) {
273
+ const vatNS = await importBundle(bundle, {
274
+ endowments: { ...workerEndowments, ...lsEndowments },
275
+ inescapableGlobalProperties,
276
+ });
277
+ workerLog(`got vatNS:`, Object.keys(vatNS).join(','));
278
+ return vatNS;
279
+ }
280
+
281
+ const ls = makeLiveSlots(
282
+ syscall,
283
+ vatID,
284
+ vatPowers,
285
+ liveSlotsOptions,
286
+ gcTools,
287
+ makeVatConsole(makeLogMaker('ls')),
288
+ buildVatNamespace,
289
+ );
290
+
291
+ assert(ls.dispatch);
292
+ dispatch = makeSupervisorDispatch(ls.dispatch);
293
+ workerLog(`got dispatch`);
294
+ return ['dispatchReady'];
295
+ }
296
+
297
+ /** @type { (item: Tagged) => Promise<Tagged> } */
298
+ async function handleItem([tag, ...args]) {
299
+ workerLog('handleItem', tag, args.length);
300
+ switch (tag) {
301
+ case 'setBundle': {
302
+ assert(!dispatch, 'cannot setBundle again');
303
+ const liveSlotsOptions = /** @type LiveSlotsOptions */ (args[2]);
304
+ return setBundle(args[0], args[1], liveSlotsOptions);
305
+ }
306
+ case 'deliver': {
307
+ assert(dispatch, 'cannot deliver before setBundle');
308
+ const [vatDeliveryObject] = args;
309
+ harden(vatDeliveryObject);
310
+ insistVatDeliveryObject(vatDeliveryObject);
311
+ return dispatch(vatDeliveryObject);
312
+ }
313
+ default:
314
+ workerLog('handleItem: bad tag', tag, args.length);
315
+ return ['bad tag', tag];
316
+ }
317
+ }
318
+
319
+ return harden({
320
+ handleItem,
321
+ });
322
+ }
323
+
324
+ // xsnap provides issueCommand global
325
+ const port = managerPort(globalThis.issueCommand);
326
+ const worker = makeWorker(port);
327
+
328
+ // Send unexpected console messages to the manager port.
329
+ globalThis.print = (...args) => {
330
+ port.send(['sourcedConsole', 'xsnap', 'error', ...args]);
331
+ };
332
+
333
+ globalThis.handleCommand = port.handlerFrom(worker.handleItem);
@@ -0,0 +1,18 @@
1
+ /* global setImmediate */
2
+ import { makePromiseKit } from '@endo/promise-kit';
3
+
4
+ // This can only be imported from the Start Compartment, where 'setImmediate'
5
+ // is available.
6
+
7
+ export function waitUntilQuiescent() {
8
+ // the delivery might cause some number of (native) Promises to be
9
+ // created and resolved, so we use the IO queue to detect when the
10
+ // Promise queue is empty. The IO queue (setImmediate and setTimeout) is
11
+ // lower-priority than the Promise queue on browsers and Node 11, but on
12
+ // Node 10 it is higher. So this trick requires Node 11.
13
+ // https://jsblog.insiderattack.net/new-changes-to-timers-and-microtasks-from-node-v11-0-0-and-above-68d112743eb3
14
+ /** @type {import('@endo/promise-kit').PromiseKit<void>} */
15
+ const { promise: queueEmptyP, resolve } = makePromiseKit();
16
+ setImmediate(() => resolve());
17
+ return queueEmptyP;
18
+ }
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@agoric/swingset-xsnap-supervisor",
3
+ "version": "0.9.1-dev-6e51de2.0+6e51de2",
4
+ "description": "Supervisor/Liveslots bundle for swingset xsnap workers",
5
+ "author": "Agoric",
6
+ "license": "Apache-2.0",
7
+ "type": "module",
8
+ "main": "./src/index.js",
9
+ "exports": {
10
+ ".": "./src/index.js",
11
+ "./package.json": "./package.json"
12
+ },
13
+ "scripts": {
14
+ "build:bundle": "node scripts/build-bundle.js",
15
+ "build": "yarn build:bundle",
16
+ "clean": "rm -rf dist",
17
+ "lint": "run-s --continue-on-error lint:*",
18
+ "lint:js": "eslint 'lib/**/*.js' 'src/**/*.js' 'scripts/**/*.js' 'test/**/*.js'",
19
+ "lint:types": "tsc -p jsconfig.json",
20
+ "lint-fix": "eslint --fix 'lib/**/*.js' 'src/**/*.js' 'scripts/**/*.js' 'test/**/*.js'",
21
+ "test": "ava",
22
+ "test:c8": "c8 $C8_OPTIONS ava --config=ava-nesm.config.js",
23
+ "test:xs": "exit 0"
24
+ },
25
+ "devDependencies": {
26
+ "@agoric/assert": "0.5.2-dev-6e51de2.0+6e51de2",
27
+ "@agoric/swingset-liveslots": "0.9.1-dev-6e51de2.0+6e51de2",
28
+ "@endo/bundle-source": "^2.4.2",
29
+ "@endo/import-bundle": "^0.3.0",
30
+ "@endo/init": "^0.5.52",
31
+ "@endo/marshal": "^0.8.1",
32
+ "ava": "^5.2.0",
33
+ "c8": "^7.13.0"
34
+ },
35
+ "files": [
36
+ "LICENSE*",
37
+ "lib",
38
+ "dist",
39
+ "src"
40
+ ],
41
+ "publishConfig": {
42
+ "access": "public"
43
+ },
44
+ "ava": {
45
+ "files": [
46
+ "test/**/test-*.js"
47
+ ],
48
+ "timeout": "2m",
49
+ "workerThreads": false
50
+ },
51
+ "gitHead": "6e51de2dd0b371a2e52355a23be623a25d6390ff"
52
+ }