@agoric/swingset-liveslots 0.10.3-other-dev-1f26562.0 → 0.10.3-other-dev-3eb1a1d.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/README.md +2 -0
- package/package.json +27 -19
- package/src/boyd-gc.js +598 -0
- package/src/cache.js +3 -2
- package/src/capdata.js +1 -2
- package/src/collectionManager.js +219 -103
- package/src/facetiousness.js +1 -1
- package/src/index.js +4 -2
- package/src/liveslots.js +131 -301
- package/src/message.js +5 -5
- package/src/parseVatSlots.js +1 -1
- package/src/types-index.d.ts +4 -0
- package/src/types-index.js +2 -0
- package/src/types.js +8 -2
- package/src/vatstore-iterators.js +2 -0
- package/src/virtualObjectManager.js +185 -71
- package/src/virtualReferences.js +135 -26
- package/src/watchedPromises.js +67 -17
- package/test/{test-baggage.js → baggage.test.js} +1 -2
- package/test/{test-cache.js → cache.test.js} +0 -1
- package/test/clear-collection.test.js +586 -0
- package/test/{test-collection-schema-refcount.js → collection-schema-refcount.test.js} +1 -2
- package/test/{test-collection-upgrade.js → collection-upgrade.test.js} +1 -3
- package/test/{test-collections.js → collections.test.js} +158 -14
- package/test/{test-dropped-collection-weakrefs.js → dropped-collection-weakrefs.test.js} +1 -2
- package/test/dropped-weakset-9939.test.js +80 -0
- package/test/dummyMeterControl.js +1 -1
- package/test/{test-durabilityChecks.js → durabilityChecks.test.js} +4 -4
- package/test/exo-utils.js +70 -0
- package/test/{test-facetiousness.js → facetiousness.test.js} +1 -2
- package/test/gc-and-finalize.js +30 -1
- package/test/gc-before-finalizer.test.js +230 -0
- package/test/gc-helpers.js +2 -3
- package/test/{test-gc-sensitivity.js → gc-sensitivity.test.js} +2 -2
- package/test/handled-promises.test.js +506 -0
- package/test/{test-initial-vrefs.js → initial-vrefs.test.js} +2 -3
- package/test/liveslots-helpers.js +12 -7
- package/test/{test-liveslots-mock-gc.js → liveslots-mock-gc.test.js} +101 -2
- package/test/{test-liveslots-real-gc.js → liveslots-real-gc.test.js} +62 -37
- package/test/{test-liveslots.js → liveslots.test.js} +14 -15
- package/test/mock-gc.js +1 -0
- package/test/storeGC/{test-lifecycle.js → lifecycle.test.js} +2 -2
- package/test/storeGC/{test-refcount-management.js → refcount-management.test.js} +1 -2
- package/test/storeGC/{test-scalar-store-kind.js → scalar-store-kind.test.js} +0 -1
- package/test/storeGC/{test-weak-key.js → weak-key.test.js} +1 -2
- package/test/strict-test-env-upgrade.test.js +94 -0
- package/test/util.js +2 -2
- package/test/vat-environment.test.js +65 -0
- package/test/vat-util.js +2 -2
- package/test/virtual-objects/{test-cease-recognition.js → cease-recognition.test.js} +2 -2
- package/test/virtual-objects/{test-cross-facet.js → cross-facet.test.js} +5 -4
- package/test/virtual-objects/{test-empty-data.js → empty-data.test.js} +1 -2
- package/test/virtual-objects/{test-facets.js → facets.test.js} +1 -2
- package/test/virtual-objects/{test-kind-changes.js → kind-changes.test.js} +2 -2
- package/test/virtual-objects/{test-reachable-vrefs.js → reachable-vrefs.test.js} +2 -2
- package/test/virtual-objects/{test-rep-tostring.js → rep-tostring.test.js} +3 -5
- package/test/virtual-objects/{test-retain-remotable.js → retain-remotable.test.js} +25 -24
- package/test/virtual-objects/set-debug-label-instances.js +1 -1
- package/test/virtual-objects/{test-state-shape.js → state-shape.test.js} +2 -2
- package/test/virtual-objects/{test-virtualObjectGC.js → virtualObjectGC.test.js} +2 -2
- package/test/virtual-objects/{test-virtualObjectManager.js → virtualObjectManager.test.js} +126 -8
- package/test/virtual-objects/{test-vo-real-gc.js → vo-real-gc.test.js} +8 -8
- package/test/virtual-objects/{test-weakcollections-vref-handling.js → weakcollections-vref-handling.test.js} +1 -2
- package/test/{test-vo-test-harness.js → vo-test-harness.test.js} +0 -1
- package/test/{test-vpid-liveslots.js → vpid-liveslots.test.js} +105 -5
- package/test/waitUntilQuiescent.js +2 -1
- package/test/weakset-dropped-remotable.test.js +50 -0
- package/tools/fakeCollectionManager.js +44 -0
- package/tools/fakeVirtualObjectManager.js +62 -0
- package/tools/fakeVirtualSupport.js +389 -0
- package/tools/prepare-strict-test-env.js +124 -0
- package/tools/prepare-test-env.js +13 -0
- package/tools/setup-vat-data.js +96 -0
- package/tools/vo-test-harness.js +143 -0
- package/CHANGELOG.md +0 -61
- package/test/kmarshal.js +0 -79
- package/test/test-handled-promises.js +0 -360
package/src/liveslots.js
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
makeMarshal,
|
|
6
|
-
} from '@endo/marshal';
|
|
7
|
-
import { assert, Fail } from '@agoric/assert';
|
|
1
|
+
import { annotateError, assert, Fail, makeError, X } from '@endo/errors';
|
|
2
|
+
import { passStyleOf } from '@endo/pass-style';
|
|
3
|
+
import { PassStyleOfEndowmentSymbol } from '@endo/pass-style/endow.js';
|
|
4
|
+
import { Remotable, getInterfaceOf, makeMarshal } from '@endo/marshal';
|
|
8
5
|
import { isPromise } from '@endo/promise-kit';
|
|
9
6
|
import { E, HandledPromise } from '@endo/eventual-send';
|
|
10
7
|
import { insistVatType, makeVatSlot, parseVatSlot } from './parseVatSlots.js';
|
|
@@ -15,12 +12,11 @@ import { makeVirtualReferenceManager } from './virtualReferences.js';
|
|
|
15
12
|
import { makeVirtualObjectManager } from './virtualObjectManager.js';
|
|
16
13
|
import { makeCollectionManager } from './collectionManager.js';
|
|
17
14
|
import { makeWatchedPromiseManager } from './watchedPromises.js';
|
|
15
|
+
import { makeBOYDKit } from './boyd-gc.js';
|
|
18
16
|
|
|
19
17
|
const SYSCALL_CAPDATA_BODY_SIZE_LIMIT = 10_000_000;
|
|
20
18
|
const SYSCALL_CAPDATA_SLOTS_LENGTH_LIMIT = 10_000;
|
|
21
19
|
|
|
22
|
-
const { details: X } = assert;
|
|
23
|
-
|
|
24
20
|
// 'makeLiveSlots' is a dispatcher which uses javascript Maps to keep track
|
|
25
21
|
// of local objects which have been exported. These cannot be persisted
|
|
26
22
|
// beyond the runtime of the javascript environment, so this mechanism is not
|
|
@@ -33,7 +29,7 @@ const { details: X } = assert;
|
|
|
33
29
|
* @param {*} syscall Kernel syscall interface that the vat will have access to
|
|
34
30
|
* @param {*} forVatID Vat ID label, for use in debug diagnostics
|
|
35
31
|
* @param {*} vatPowers
|
|
36
|
-
* @param {import('./types').LiveSlotsOptions} liveSlotsOptions
|
|
32
|
+
* @param {import('./types.js').LiveSlotsOptions} liveSlotsOptions
|
|
37
33
|
* @param {*} gcTools { WeakRef, FinalizationRegistry, waitUntilQuiescent, gcAndFinalize,
|
|
38
34
|
* meterControl }
|
|
39
35
|
* @param {Pick<Console, 'debug' | 'log' | 'info' | 'warn' | 'error'>} console
|
|
@@ -159,10 +155,8 @@ function build(
|
|
|
159
155
|
const { type, allocatedByVat, virtual, durable } = parseVatSlot(vref);
|
|
160
156
|
if (type === 'object' && allocatedByVat) {
|
|
161
157
|
if (virtual || durable) {
|
|
162
|
-
// eslint-disable-next-line no-use-before-define
|
|
163
158
|
vrm.setExportStatus(vref, 'reachable');
|
|
164
159
|
} else {
|
|
165
|
-
// eslint-disable-next-line no-use-before-define
|
|
166
160
|
const remotable = requiredValForSlot(vref);
|
|
167
161
|
exportedRemotables.add(remotable);
|
|
168
162
|
kernelRecognizableRemotables.add(vref);
|
|
@@ -170,52 +164,6 @@ function build(
|
|
|
170
164
|
}
|
|
171
165
|
}
|
|
172
166
|
|
|
173
|
-
/*
|
|
174
|
-
Imports are in one of 5 states: UNKNOWN, REACHABLE, UNREACHABLE,
|
|
175
|
-
COLLECTED, FINALIZED. Note that there's no actual state machine with those
|
|
176
|
-
values, and we can't observe all of the transitions from JavaScript, but
|
|
177
|
-
we can describe what operations could cause a transition, and what our
|
|
178
|
-
observations allow us to deduce about the state:
|
|
179
|
-
|
|
180
|
-
* UNKNOWN moves to REACHABLE when a crank introduces a new import
|
|
181
|
-
* userspace holds a reference only in REACHABLE
|
|
182
|
-
* REACHABLE moves to UNREACHABLE only during a userspace crank
|
|
183
|
-
* UNREACHABLE moves to COLLECTED when GC runs, which queues the finalizer
|
|
184
|
-
* COLLECTED moves to FINALIZED when a new turn runs the finalizer
|
|
185
|
-
* liveslots moves from FINALIZED to UNKNOWN by syscalling dropImports
|
|
186
|
-
|
|
187
|
-
convertSlotToVal either imports a vref for the first time, or
|
|
188
|
-
re-introduces a previously-seen vref. It transitions from:
|
|
189
|
-
|
|
190
|
-
* UNKNOWN to REACHABLE by creating a new Presence
|
|
191
|
-
* UNREACHABLE to REACHABLE by re-using the old Presence that userspace
|
|
192
|
-
forgot about
|
|
193
|
-
* COLLECTED/FINALIZED to REACHABLE by creating a new Presence
|
|
194
|
-
|
|
195
|
-
Our tracking tables hold data that depends on the current state:
|
|
196
|
-
|
|
197
|
-
* slotToVal holds a WeakRef in [REACHABLE, UNREACHABLE, COLLECTED]
|
|
198
|
-
* that WeakRef .deref()s into something in [REACHABLE, UNREACHABLE]
|
|
199
|
-
* deadSet holds the vref only in FINALIZED
|
|
200
|
-
* re-introduction must ensure the vref is not in the deadSet
|
|
201
|
-
|
|
202
|
-
Each state thus has a set of perhaps-measurable properties:
|
|
203
|
-
|
|
204
|
-
* UNKNOWN: slotToVal[baseRef] is missing, baseRef not in deadSet
|
|
205
|
-
* REACHABLE: slotToVal has live weakref, userspace can reach
|
|
206
|
-
* UNREACHABLE: slotToVal has live weakref, userspace cannot reach
|
|
207
|
-
* COLLECTED: slotToVal[baseRef] has dead weakref
|
|
208
|
-
* FINALIZED: slotToVal[baseRef] is missing, baseRef is in deadSet
|
|
209
|
-
|
|
210
|
-
Our finalizer callback is queued by the engine's transition from
|
|
211
|
-
UNREACHABLE to COLLECTED, but the baseRef might be re-introduced before the
|
|
212
|
-
callback has a chance to run. There might even be multiple copies of the
|
|
213
|
-
finalizer callback queued. So the callback must deduce the current state
|
|
214
|
-
and only perform cleanup (i.e. delete the slotToVal entry and add the
|
|
215
|
-
baseRef to the deadSet) in the COLLECTED state.
|
|
216
|
-
|
|
217
|
-
*/
|
|
218
|
-
|
|
219
167
|
function finalizeDroppedObject(baseRef) {
|
|
220
168
|
// TODO: Ideally this function should assert that it is not metered. This
|
|
221
169
|
// appears to be fine in practice, but it breaks a number of unit tests in
|
|
@@ -233,124 +181,12 @@ function build(
|
|
|
233
181
|
|
|
234
182
|
if (wr && !wr.deref()) {
|
|
235
183
|
// we're in the COLLECTED state, or FINALIZED after a re-introduction
|
|
236
|
-
// eslint-disable-next-line no-use-before-define
|
|
237
184
|
addToPossiblyDeadSet(baseRef);
|
|
238
185
|
slotToVal.delete(baseRef);
|
|
239
186
|
}
|
|
240
187
|
}
|
|
241
188
|
const vreffedObjectRegistry = new FinalizationRegistry(finalizeDroppedObject);
|
|
242
189
|
|
|
243
|
-
async function scanForDeadObjects() {
|
|
244
|
-
// `possiblyDeadSet` accumulates vrefs which have lost a supporting
|
|
245
|
-
// pillar (in-memory, export, or virtualized data refcount) since the
|
|
246
|
-
// last call to scanForDeadObjects. The vref might still be supported
|
|
247
|
-
// by a remaining pillar, or the pillar which was dropped might be back
|
|
248
|
-
// (e.g., given a new in-memory manifestation).
|
|
249
|
-
|
|
250
|
-
const importsToDrop = new Set();
|
|
251
|
-
const importsToRetire = new Set();
|
|
252
|
-
const exportsToRetire = new Set();
|
|
253
|
-
let doMore;
|
|
254
|
-
do {
|
|
255
|
-
doMore = false;
|
|
256
|
-
|
|
257
|
-
// Yes, we know this is an await inside a loop. Too bad. (Also, it's a
|
|
258
|
-
// `do {} while` loop, which means there's no conditional bypass of the
|
|
259
|
-
// await.)
|
|
260
|
-
// eslint-disable-next-line no-await-in-loop, @jessie.js/no-nested-await
|
|
261
|
-
await gcTools.gcAndFinalize();
|
|
262
|
-
|
|
263
|
-
// possiblyDeadSet contains a baseref for everything (Presences,
|
|
264
|
-
// Remotables, Representatives) that might have lost a
|
|
265
|
-
// pillar. The object might still be supported by other pillars,
|
|
266
|
-
// and the lost pillar might have been reinstantiated by the
|
|
267
|
-
// time we get here. The first step is to filter this down to a
|
|
268
|
-
// list of definitely dead baserefs.
|
|
269
|
-
|
|
270
|
-
const deadSet = new Set();
|
|
271
|
-
|
|
272
|
-
for (const baseRef of possiblyDeadSet) {
|
|
273
|
-
// eslint-disable-next-line no-use-before-define
|
|
274
|
-
if (slotToVal.has(baseRef)) {
|
|
275
|
-
continue; // RAM pillar remains
|
|
276
|
-
}
|
|
277
|
-
const { virtual, durable, type } = parseVatSlot(baseRef);
|
|
278
|
-
assert(type === 'object', `unprepared to track ${type}`);
|
|
279
|
-
if (virtual || durable) {
|
|
280
|
-
// eslint-disable-next-line no-use-before-define
|
|
281
|
-
if (vrm.isVirtualObjectReachable(baseRef)) {
|
|
282
|
-
continue; // vdata or export pillar remains
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
deadSet.add(baseRef);
|
|
286
|
-
}
|
|
287
|
-
possiblyDeadSet.clear();
|
|
288
|
-
|
|
289
|
-
// deadSet now contains objects which are certainly dead
|
|
290
|
-
|
|
291
|
-
// possiblyRetiredSet holds (a subset of??) baserefs which have
|
|
292
|
-
// lost a recognizer recently. TODO recheck this
|
|
293
|
-
|
|
294
|
-
for (const vref of possiblyRetiredSet) {
|
|
295
|
-
// eslint-disable-next-line no-use-before-define
|
|
296
|
-
if (!getValForSlot(vref) && !deadSet.has(vref)) {
|
|
297
|
-
// Don't retire things that haven't yet made the transition to dead,
|
|
298
|
-
// i.e., always drop before retiring
|
|
299
|
-
// eslint-disable-next-line no-use-before-define
|
|
300
|
-
if (!vrm.isVrefRecognizable(vref)) {
|
|
301
|
-
importsToRetire.add(vref);
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
possiblyRetiredSet.clear();
|
|
306
|
-
|
|
307
|
-
const deadBaseRefs = Array.from(deadSet);
|
|
308
|
-
deadBaseRefs.sort();
|
|
309
|
-
for (const baseRef of deadBaseRefs) {
|
|
310
|
-
const { virtual, durable, allocatedByVat, type } =
|
|
311
|
-
parseVatSlot(baseRef);
|
|
312
|
-
type === 'object' || Fail`unprepared to track ${type}`;
|
|
313
|
-
if (virtual || durable) {
|
|
314
|
-
// Representative: send nothing, but perform refcount checking
|
|
315
|
-
// eslint-disable-next-line no-use-before-define
|
|
316
|
-
const [gcAgain, retirees] = vrm.deleteVirtualObject(baseRef);
|
|
317
|
-
if (retirees) {
|
|
318
|
-
retirees.map(retiree => exportsToRetire.add(retiree));
|
|
319
|
-
}
|
|
320
|
-
doMore = doMore || gcAgain;
|
|
321
|
-
} else if (allocatedByVat) {
|
|
322
|
-
// Remotable: send retireExport
|
|
323
|
-
// for remotables, vref === baseRef
|
|
324
|
-
if (kernelRecognizableRemotables.has(baseRef)) {
|
|
325
|
-
kernelRecognizableRemotables.delete(baseRef);
|
|
326
|
-
exportsToRetire.add(baseRef);
|
|
327
|
-
}
|
|
328
|
-
} else {
|
|
329
|
-
// Presence: send dropImport unless reachable by VOM
|
|
330
|
-
// eslint-disable-next-line no-lonely-if, no-use-before-define
|
|
331
|
-
if (!vrm.isPresenceReachable(baseRef)) {
|
|
332
|
-
importsToDrop.add(baseRef);
|
|
333
|
-
// eslint-disable-next-line no-use-before-define
|
|
334
|
-
if (!vrm.isVrefRecognizable(baseRef)) {
|
|
335
|
-
// for presences, baseRef === vref
|
|
336
|
-
importsToRetire.add(baseRef);
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
} while (possiblyDeadSet.size > 0 || possiblyRetiredSet.size > 0 || doMore);
|
|
342
|
-
|
|
343
|
-
if (importsToDrop.size) {
|
|
344
|
-
syscall.dropImports(Array.from(importsToDrop).sort());
|
|
345
|
-
}
|
|
346
|
-
if (importsToRetire.size) {
|
|
347
|
-
syscall.retireImports(Array.from(importsToRetire).sort());
|
|
348
|
-
}
|
|
349
|
-
if (exportsToRetire.size) {
|
|
350
|
-
syscall.retireExports(Array.from(exportsToRetire).sort());
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
|
|
354
190
|
/**
|
|
355
191
|
* Remember disavowed Presences which will kill the vat if you try to talk
|
|
356
192
|
* to them
|
|
@@ -370,11 +206,9 @@ function build(
|
|
|
370
206
|
// Support: o~.[prop](...args) remote method invocation
|
|
371
207
|
lsdebug(`makeImportedPresence handler.applyMethod (${slot})`);
|
|
372
208
|
if (disavowedPresences.has(o)) {
|
|
373
|
-
// eslint-disable-next-line no-use-before-define
|
|
374
209
|
exitVatWithFailure(disavowalError);
|
|
375
210
|
throw disavowalError;
|
|
376
211
|
}
|
|
377
|
-
// eslint-disable-next-line no-use-before-define
|
|
378
212
|
return queueMessage(slot, prop, args, returnedP);
|
|
379
213
|
},
|
|
380
214
|
applyFunction(o, args, returnedP) {
|
|
@@ -383,7 +217,6 @@ function build(
|
|
|
383
217
|
get(o, prop) {
|
|
384
218
|
lsdebug(`makeImportedPresence handler.get (${slot})`);
|
|
385
219
|
if (disavowedPresences.has(o)) {
|
|
386
|
-
// eslint-disable-next-line no-use-before-define
|
|
387
220
|
exitVatWithFailure(disavowalError);
|
|
388
221
|
throw disavowalError;
|
|
389
222
|
}
|
|
@@ -455,7 +288,6 @@ function build(
|
|
|
455
288
|
console.error(`mIPromise handler called after resolution`);
|
|
456
289
|
Fail`mIPromise handler called after resolution`;
|
|
457
290
|
}
|
|
458
|
-
// eslint-disable-next-line no-use-before-define
|
|
459
291
|
return queueMessage(vpid, prop, args, returnedP);
|
|
460
292
|
},
|
|
461
293
|
get(p, prop) {
|
|
@@ -500,54 +332,6 @@ function build(
|
|
|
500
332
|
return Remotable(iface);
|
|
501
333
|
}
|
|
502
334
|
|
|
503
|
-
/**
|
|
504
|
-
* Counters to track the next number for various categories of allocation.
|
|
505
|
-
* `exportID` starts at 1 because 'o+0' is always automatically
|
|
506
|
-
* pre-assigned to the root object.
|
|
507
|
-
* `promiseID` starts at 5 as a very minor aid to debugging: when puzzling
|
|
508
|
-
* over trace logs and the like, it helps for the numbers in various species
|
|
509
|
-
* of IDs that are jumbled together to be a little out of sync and thus a
|
|
510
|
-
* little less similar to each other.
|
|
511
|
-
*/
|
|
512
|
-
const initialIDCounters = { exportID: 1, collectionID: 1, promiseID: 5 };
|
|
513
|
-
/** @type {Record<string, number>} */
|
|
514
|
-
let idCounters;
|
|
515
|
-
let idCountersAreDirty = false;
|
|
516
|
-
|
|
517
|
-
function initializeIDCounters() {
|
|
518
|
-
if (!idCounters) {
|
|
519
|
-
// the saved value might be missing, or from an older liveslots
|
|
520
|
-
// (with fewer counters), so merge it with our initial values
|
|
521
|
-
const saved = JSON.parse(syscall.vatstoreGet('idCounters') || '{}');
|
|
522
|
-
idCounters = { ...initialIDCounters, ...saved };
|
|
523
|
-
idCountersAreDirty = true;
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
function allocateNextID(name) {
|
|
528
|
-
if (!idCounters) {
|
|
529
|
-
// Normally `initializeIDCounters` would be called from startVat, but some
|
|
530
|
-
// tests bypass that so this is a backstop. Note that the invocation from
|
|
531
|
-
// startVat is there to make vatStore access patterns a bit more
|
|
532
|
-
// consistent from one vat to another, principally as a confusion
|
|
533
|
-
// reduction measure in service of debugging; it is not a correctness
|
|
534
|
-
// issue.
|
|
535
|
-
initializeIDCounters();
|
|
536
|
-
}
|
|
537
|
-
const result = idCounters[name];
|
|
538
|
-
result !== undefined || Fail`unknown idCounters[${name}]`;
|
|
539
|
-
idCounters[name] += 1;
|
|
540
|
-
idCountersAreDirty = true;
|
|
541
|
-
return result;
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
function flushIDCounters() {
|
|
545
|
-
if (idCountersAreDirty) {
|
|
546
|
-
syscall.vatstoreSet('idCounters', JSON.stringify(idCounters));
|
|
547
|
-
idCountersAreDirty = false;
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
|
|
551
335
|
// TODO: fix awkward non-orthogonality: allocateExportID() returns a number,
|
|
552
336
|
// allocatePromiseID() returns a slot, registerPromise() uses the slot from
|
|
553
337
|
// allocatePromiseID(), exportPassByPresence() generates a slot itself using
|
|
@@ -556,15 +340,15 @@ function build(
|
|
|
556
340
|
// use a slot from the corresponding allocateX
|
|
557
341
|
|
|
558
342
|
function allocateExportID() {
|
|
559
|
-
return allocateNextID('exportID');
|
|
343
|
+
return vrm.allocateNextID('exportID');
|
|
560
344
|
}
|
|
561
345
|
|
|
562
346
|
function allocateCollectionID() {
|
|
563
|
-
return allocateNextID('collectionID');
|
|
347
|
+
return vrm.allocateNextID('collectionID');
|
|
564
348
|
}
|
|
565
349
|
|
|
566
350
|
function allocatePromiseID() {
|
|
567
|
-
const promiseID = allocateNextID('promiseID');
|
|
351
|
+
const promiseID = vrm.allocateNextID('promiseID');
|
|
568
352
|
return makeVatSlot('promise', true, promiseID);
|
|
569
353
|
}
|
|
570
354
|
|
|
@@ -591,10 +375,8 @@ function build(
|
|
|
591
375
|
// do a syscall.resolve when it fires. The caller must finish
|
|
592
376
|
// doing their syscall before this turn finishes, to ensure the
|
|
593
377
|
// kernel isn't surprised by a spurious resolution.
|
|
594
|
-
// eslint-disable-next-line no-use-before-define
|
|
595
378
|
const p = requiredValForSlot(vpid);
|
|
596
379
|
// if (!knownResolutions.has(p)) { // TODO really?
|
|
597
|
-
// eslint-disable-next-line no-use-before-define
|
|
598
380
|
followForKernel(vpid, p);
|
|
599
381
|
return true;
|
|
600
382
|
}
|
|
@@ -606,20 +388,18 @@ function build(
|
|
|
606
388
|
return makeVatSlot('object', true, exportID);
|
|
607
389
|
}
|
|
608
390
|
|
|
609
|
-
// eslint-disable-next-line no-use-before-define
|
|
610
391
|
const m = makeMarshal(convertValToSlot, convertSlotToVal, {
|
|
611
392
|
marshalName: `liveSlots:${forVatID}`,
|
|
612
393
|
serializeBodyFormat: 'smallcaps',
|
|
613
394
|
// TODO Temporary hack.
|
|
614
395
|
// See https://github.com/Agoric/agoric-sdk/issues/2780
|
|
615
|
-
errorIdNum:
|
|
396
|
+
errorIdNum: 70_000,
|
|
616
397
|
marshalSaveError: err =>
|
|
617
398
|
// By sending this to `console.warn`, under cosmic-swingset this is
|
|
618
399
|
// controlled by the `console` option given to makeLiveSlots.
|
|
619
400
|
console.warn('Logging sent error stack', err),
|
|
620
401
|
});
|
|
621
402
|
const unmeteredUnserialize = meterControl.unmetered(m.unserialize);
|
|
622
|
-
// eslint-disable-next-line no-use-before-define
|
|
623
403
|
const unmeteredConvertSlotToVal = meterControl.unmetered(convertSlotToVal);
|
|
624
404
|
|
|
625
405
|
function getSlotForVal(val) {
|
|
@@ -664,7 +444,6 @@ function build(
|
|
|
664
444
|
allocateExportID,
|
|
665
445
|
getSlotForVal,
|
|
666
446
|
requiredValForSlot,
|
|
667
|
-
// eslint-disable-next-line no-use-before-define
|
|
668
447
|
registerValue,
|
|
669
448
|
m.serialize,
|
|
670
449
|
unmeteredUnserialize,
|
|
@@ -677,10 +456,8 @@ function build(
|
|
|
677
456
|
vrm,
|
|
678
457
|
allocateExportID,
|
|
679
458
|
allocateCollectionID,
|
|
680
|
-
// eslint-disable-next-line no-use-before-define
|
|
681
459
|
convertValToSlot,
|
|
682
460
|
unmeteredConvertSlotToVal,
|
|
683
|
-
// eslint-disable-next-line no-use-before-define
|
|
684
461
|
registerValue,
|
|
685
462
|
m.serialize,
|
|
686
463
|
unmeteredUnserialize,
|
|
@@ -692,7 +469,6 @@ function build(
|
|
|
692
469
|
vrm,
|
|
693
470
|
vom,
|
|
694
471
|
collectionManager,
|
|
695
|
-
// eslint-disable-next-line no-use-before-define
|
|
696
472
|
convertValToSlot,
|
|
697
473
|
convertSlotToVal: unmeteredConvertSlotToVal,
|
|
698
474
|
maybeExportPromise,
|
|
@@ -722,7 +498,6 @@ function build(
|
|
|
722
498
|
slot = allocatePromiseID();
|
|
723
499
|
} else {
|
|
724
500
|
if (disavowedPresences.has(val)) {
|
|
725
|
-
// eslint-disable-next-line no-use-before-define
|
|
726
501
|
exitVatWithFailure(disavowalError);
|
|
727
502
|
throw disavowalError; // cannot reference a disavowed object
|
|
728
503
|
}
|
|
@@ -759,9 +534,9 @@ function build(
|
|
|
759
534
|
Fail`registerValue(${baseRef} should not receive individual facets`;
|
|
760
535
|
slotToVal.set(baseRef, new WeakRef(val));
|
|
761
536
|
if (valIsCohort) {
|
|
762
|
-
vrm.getFacetNames(id).
|
|
537
|
+
for (const [index, name] of vrm.getFacetNames(id).entries()) {
|
|
763
538
|
valToSlot.set(val[name], `${baseRef}:${index}`);
|
|
764
|
-
}
|
|
539
|
+
}
|
|
765
540
|
} else {
|
|
766
541
|
valToSlot.set(val, baseRef);
|
|
767
542
|
}
|
|
@@ -796,39 +571,50 @@ function build(
|
|
|
796
571
|
try {
|
|
797
572
|
val = vrm.reanimate(baseRef);
|
|
798
573
|
} catch (err) {
|
|
799
|
-
const wrappedError =
|
|
800
|
-
|
|
574
|
+
const wrappedError = makeError(X`failed to reanimate ${iface}`);
|
|
575
|
+
annotateError(wrappedError, X`Original error: ${err}`);
|
|
801
576
|
throw wrappedError;
|
|
802
577
|
}
|
|
803
578
|
if (facet !== undefined) {
|
|
804
579
|
result = vrm.getFacet(id, val, facet);
|
|
805
580
|
}
|
|
806
|
-
} else {
|
|
581
|
+
} else if (type === 'object') {
|
|
582
|
+
// Note: an abandonned (e.g. by an upgrade) exported ephemeral or virtual
|
|
583
|
+
// object would appear as an import if re-introduced. In the future we
|
|
584
|
+
// may need to change that if we want to keep recognizing such references
|
|
585
|
+
// In that case we'd need to create an imported presence for these
|
|
586
|
+
// unknown vrefs allocated by the vat.
|
|
587
|
+
// See https://github.com/Agoric/agoric-sdk/issues/9746
|
|
807
588
|
!allocatedByVat || Fail`I don't remember allocating ${slot}`;
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
val = makeDeviceNode(slot, iface);
|
|
828
|
-
importedDevices.add(val);
|
|
589
|
+
// this is a new import value
|
|
590
|
+
val = makeImportedPresence(slot, iface);
|
|
591
|
+
} else if (type === 'promise') {
|
|
592
|
+
// We unconditionally create a promise record, even if the promise looks
|
|
593
|
+
// like it was allocated by us. This can happen when re-importing a
|
|
594
|
+
// promise created by the previous incarnation. We may or may not have
|
|
595
|
+
// been the decider of the promise. If we were, the kernel will be
|
|
596
|
+
// rejecting the promise on our behalf. We may have previously been
|
|
597
|
+
// subscribed to that promise, but subscription is idempotent.
|
|
598
|
+
const pRec = makePipelinablePromise(slot);
|
|
599
|
+
importedVPIDs.set(slot, pRec);
|
|
600
|
+
val = pRec.promise;
|
|
601
|
+
// ideally we'd wait until .then is called on p before subscribing,
|
|
602
|
+
// but the current Promise API doesn't give us a way to discover
|
|
603
|
+
// this, so we must subscribe right away. If we were using Vows or
|
|
604
|
+
// some other then-able, we could just hook then() to notify us.
|
|
605
|
+
if (importedPromises) {
|
|
606
|
+
// leave the subscribe() up to dispatch.notify()
|
|
607
|
+
importedPromises.add(slot);
|
|
829
608
|
} else {
|
|
830
|
-
|
|
609
|
+
// probably in dispatch.deliver(), so subscribe now
|
|
610
|
+
syscall.subscribe(slot);
|
|
831
611
|
}
|
|
612
|
+
} else if (type === 'device') {
|
|
613
|
+
!allocatedByVat || Fail`unexpected device ${slot} allocated by vat`;
|
|
614
|
+
val = makeDeviceNode(slot, iface);
|
|
615
|
+
importedDevices.add(val);
|
|
616
|
+
} else {
|
|
617
|
+
Fail`unrecognized slot type '${type}'`;
|
|
832
618
|
}
|
|
833
619
|
registerValue(baseRef, val, facet !== undefined);
|
|
834
620
|
if (!result) {
|
|
@@ -841,7 +627,25 @@ function build(
|
|
|
841
627
|
meterControl.assertNotMetered();
|
|
842
628
|
const { type } = parseVatSlot(slot);
|
|
843
629
|
type === 'promise' || Fail`revivePromise called on non-promise ${slot}`;
|
|
844
|
-
|
|
630
|
+
const val = getValForSlot(slot);
|
|
631
|
+
if (val) {
|
|
632
|
+
// revivePromise is only called by loadWatchedPromiseTable, which runs
|
|
633
|
+
// after buildRootObject(), which is given the deserialized vatParameters.
|
|
634
|
+
// The only way revivePromise() might encounter a pre-existing vpid is if
|
|
635
|
+
// these vatParameters include a promise that the previous incarnation
|
|
636
|
+
// watched, but that `buildRootObject` in the new incarnation didn't
|
|
637
|
+
// explicitly watch again. This can be either a previously imported
|
|
638
|
+
// promise, or a promise the previous incarnation exported, regardless of
|
|
639
|
+
// who the decider now is.
|
|
640
|
+
//
|
|
641
|
+
// In that case convertSlotToVal() has already deserialized the vpid, but
|
|
642
|
+
// since `buildRootObject` didn't explicitely call watchPromise on it, no
|
|
643
|
+
// registration exists so loadWatchedPromiseTable attempts to revive the
|
|
644
|
+
// promise.
|
|
645
|
+
return val;
|
|
646
|
+
}
|
|
647
|
+
// NOTE: it is important that this code not do anything *more*
|
|
648
|
+
// than what convertSlotToVal(vpid) would do
|
|
845
649
|
const pRec = makePipelinablePromise(slot);
|
|
846
650
|
importedVPIDs.set(slot, pRec);
|
|
847
651
|
const p = pRec.promise;
|
|
@@ -863,7 +667,6 @@ function build(
|
|
|
863
667
|
const priorResolution = knownResolutions.get(p);
|
|
864
668
|
if (priorResolution && !doneResolutions.has(slot)) {
|
|
865
669
|
const [priorRejected, priorRes] = priorResolution;
|
|
866
|
-
// eslint-disable-next-line no-use-before-define
|
|
867
670
|
collect(slot, priorRejected, priorRes);
|
|
868
671
|
}
|
|
869
672
|
}
|
|
@@ -983,12 +786,20 @@ function build(
|
|
|
983
786
|
return null;
|
|
984
787
|
}
|
|
985
788
|
syscall.resolve(resolutions);
|
|
986
|
-
|
|
987
|
-
resolutionCD
|
|
988
|
-
|
|
989
|
-
|
|
789
|
+
for (const resolution of resolutions) {
|
|
790
|
+
const [_xvpid, _isReject, resolutionCD] = resolution;
|
|
791
|
+
for (const vref of resolutionCD.slots) {
|
|
792
|
+
maybeNewVPIDs.add(vref);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
for (const resolution of resolutions) {
|
|
796
|
+
const [xvpid] = resolution;
|
|
797
|
+
maybeNewVPIDs.delete(xvpid);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
for (const newVPID of Array.from(maybeNewVPIDs).sort()) {
|
|
801
|
+
maybeExportPromise(newVPID);
|
|
990
802
|
}
|
|
991
|
-
Array.from(maybeNewVPIDs).sort().forEach(maybeExportPromise);
|
|
992
803
|
|
|
993
804
|
// ideally we'd wait until .then is called on p before subscribing, but
|
|
994
805
|
// the current Promise API doesn't give us a way to discover this, so we
|
|
@@ -1141,7 +952,6 @@ function build(
|
|
|
1141
952
|
}
|
|
1142
953
|
// in both cases, we are now the decider, so treat it like an
|
|
1143
954
|
// exported promise
|
|
1144
|
-
// eslint-disable-next-line no-use-before-define
|
|
1145
955
|
followForKernel(resultVPID, p);
|
|
1146
956
|
}
|
|
1147
957
|
}
|
|
@@ -1190,13 +1000,21 @@ function build(
|
|
|
1190
1000
|
|
|
1191
1001
|
const maybeNewVPIDs = new Set();
|
|
1192
1002
|
// if we mention a vpid, we might need to track it
|
|
1193
|
-
|
|
1194
|
-
resolutionCD
|
|
1195
|
-
|
|
1003
|
+
for (const resolution of resolutions) {
|
|
1004
|
+
const [_xvpid, _isReject, resolutionCD] = resolution;
|
|
1005
|
+
for (const vref of resolutionCD.slots) {
|
|
1006
|
+
maybeNewVPIDs.add(vref);
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1196
1009
|
// but not if we just resolved it (including the primary)
|
|
1197
|
-
|
|
1010
|
+
for (const resolution of resolutions) {
|
|
1011
|
+
const [xvpid] = resolution;
|
|
1012
|
+
maybeNewVPIDs.delete(xvpid);
|
|
1013
|
+
}
|
|
1198
1014
|
// track everything that's left
|
|
1199
|
-
Array.from(maybeNewVPIDs).sort()
|
|
1015
|
+
for (const newVPID of Array.from(maybeNewVPIDs).sort()) {
|
|
1016
|
+
maybeExportPromise(newVPID);
|
|
1017
|
+
}
|
|
1200
1018
|
|
|
1201
1019
|
// only the primary can possibly be newly resolved
|
|
1202
1020
|
unregisterUnreferencedVPID(vpid);
|
|
@@ -1254,11 +1072,11 @@ function build(
|
|
|
1254
1072
|
// 'imports' is an exclusively-owned Set that holds all new
|
|
1255
1073
|
// promise vpids, both resolved and unresolved
|
|
1256
1074
|
const imports = finishCollectingPromiseImports();
|
|
1257
|
-
|
|
1075
|
+
for (const vpid of retiredVPIDs) {
|
|
1258
1076
|
unregisterUnreferencedVPID(vpid); // unregisters if not in vdata
|
|
1259
1077
|
importedVPIDs.delete(vpid);
|
|
1260
1078
|
imports.delete(vpid); // resolved, so don't subscribe()
|
|
1261
|
-
}
|
|
1079
|
+
}
|
|
1262
1080
|
for (const vpid of Array.from(imports).sort()) {
|
|
1263
1081
|
syscall.subscribe(vpid);
|
|
1264
1082
|
}
|
|
@@ -1298,14 +1116,18 @@ function build(
|
|
|
1298
1116
|
|
|
1299
1117
|
function retireExports(vrefs) {
|
|
1300
1118
|
assert(Array.isArray(vrefs));
|
|
1301
|
-
vrefs
|
|
1119
|
+
for (const vref of vrefs) {
|
|
1120
|
+
retireOneExport(vref);
|
|
1121
|
+
}
|
|
1302
1122
|
}
|
|
1303
1123
|
|
|
1304
1124
|
function retireImports(vrefs) {
|
|
1305
1125
|
assert(Array.isArray(vrefs));
|
|
1306
1126
|
vrefs.map(vref => insistVatType('object', vref));
|
|
1307
1127
|
vrefs.map(vref => assert(!parseVatSlot(vref).allocatedByVat));
|
|
1308
|
-
vrefs
|
|
1128
|
+
for (const vref of vrefs) {
|
|
1129
|
+
vrm.ceaseRecognition(vref);
|
|
1130
|
+
}
|
|
1309
1131
|
}
|
|
1310
1132
|
|
|
1311
1133
|
// TODO: when we add notifyForward, guard against cycles
|
|
@@ -1370,6 +1192,7 @@ function build(
|
|
|
1370
1192
|
const inescapableGlobalProperties = harden({
|
|
1371
1193
|
WeakMap: vom.VirtualObjectAwareWeakMap,
|
|
1372
1194
|
WeakSet: vom.VirtualObjectAwareWeakSet,
|
|
1195
|
+
[PassStyleOfEndowmentSymbol]: passStyleOf,
|
|
1373
1196
|
});
|
|
1374
1197
|
|
|
1375
1198
|
function getRetentionStats() {
|
|
@@ -1405,7 +1228,6 @@ function build(
|
|
|
1405
1228
|
possiblyRetiredSet,
|
|
1406
1229
|
slotToVal,
|
|
1407
1230
|
valToSlot,
|
|
1408
|
-
// eslint-disable-next-line no-use-before-define
|
|
1409
1231
|
afterDispatchActions,
|
|
1410
1232
|
});
|
|
1411
1233
|
|
|
@@ -1450,7 +1272,7 @@ function build(
|
|
|
1450
1272
|
}
|
|
1451
1273
|
harden(vpow);
|
|
1452
1274
|
|
|
1453
|
-
initializeIDCounters();
|
|
1275
|
+
vrm.initializeIDCounters();
|
|
1454
1276
|
vom.initializeKindHandleKind();
|
|
1455
1277
|
collectionManager.initializeStoreKindInfo();
|
|
1456
1278
|
|
|
@@ -1496,8 +1318,8 @@ function build(
|
|
|
1496
1318
|
}
|
|
1497
1319
|
|
|
1498
1320
|
/**
|
|
1499
|
-
* @param {import('./types').VatDeliveryObject} delivery
|
|
1500
|
-
* @returns {
|
|
1321
|
+
* @param {import('./types.js').VatDeliveryObject} delivery
|
|
1322
|
+
* @returns {undefined | ReturnType<startVat>}
|
|
1501
1323
|
*/
|
|
1502
1324
|
function dispatchToUserspace(delivery) {
|
|
1503
1325
|
let result;
|
|
@@ -1549,19 +1371,18 @@ function build(
|
|
|
1549
1371
|
// metered
|
|
1550
1372
|
const unmeteredDispatch = meterControl.unmetered(dispatchToUserspace);
|
|
1551
1373
|
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
}
|
|
1374
|
+
const { scanForDeadObjects } = makeBOYDKit({
|
|
1375
|
+
gcTools,
|
|
1376
|
+
slotToVal,
|
|
1377
|
+
vrm,
|
|
1378
|
+
kernelRecognizableRemotables,
|
|
1379
|
+
syscall,
|
|
1380
|
+
possiblyDeadSet,
|
|
1381
|
+
possiblyRetiredSet,
|
|
1382
|
+
});
|
|
1562
1383
|
|
|
1563
1384
|
/**
|
|
1564
|
-
* @param { import('./types').SwingSetCapData } _disconnectObjectCapData
|
|
1385
|
+
* @param { import('./types.js').SwingSetCapData } _disconnectObjectCapData
|
|
1565
1386
|
* @returns {Promise<void>}
|
|
1566
1387
|
*/
|
|
1567
1388
|
async function stopVat(_disconnectObjectCapData) {
|
|
@@ -1573,11 +1394,21 @@ function build(
|
|
|
1573
1394
|
* dispatch has completed and user code has relinquished agency.
|
|
1574
1395
|
*/
|
|
1575
1396
|
function afterDispatchActions() {
|
|
1576
|
-
flushIDCounters();
|
|
1397
|
+
vrm.flushIDCounters();
|
|
1577
1398
|
collectionManager.flushSchemaCache();
|
|
1578
1399
|
vom.flushStateCache();
|
|
1579
1400
|
}
|
|
1580
1401
|
|
|
1402
|
+
const bringOutYourDead = async () => {
|
|
1403
|
+
await scanForDeadObjects();
|
|
1404
|
+
// Now flush all the vatstore changes (deletions and refcounts) we
|
|
1405
|
+
// made. dispatch() calls afterDispatchActions() automatically for
|
|
1406
|
+
// most methods, but not bringOutYourDead().
|
|
1407
|
+
afterDispatchActions();
|
|
1408
|
+
// XXX TODO: make this conditional on a config setting
|
|
1409
|
+
return getRetentionStats();
|
|
1410
|
+
};
|
|
1411
|
+
|
|
1581
1412
|
/**
|
|
1582
1413
|
* This 'dispatch' function is the entry point for the vat as a whole: the
|
|
1583
1414
|
* vat-worker supervisor gives us VatDeliveryObjects (like
|
|
@@ -1613,7 +1444,7 @@ function build(
|
|
|
1613
1444
|
* terminate the vat). Userspace should not be able to cause the delivery
|
|
1614
1445
|
* to fail: only a bug in liveslots should trigger a failure.
|
|
1615
1446
|
*
|
|
1616
|
-
* @param {import('./types').VatDeliveryObject} delivery
|
|
1447
|
+
* @param {import('./types.js').VatDeliveryObject} delivery
|
|
1617
1448
|
* @returns {Promise<void>}
|
|
1618
1449
|
*/
|
|
1619
1450
|
async function dispatch(delivery) {
|
|
@@ -1624,12 +1455,10 @@ function build(
|
|
|
1624
1455
|
} else if (delivery[0] === 'stopVat') {
|
|
1625
1456
|
return meterControl.runWithoutMeteringAsync(() => stopVat(delivery[1]));
|
|
1626
1457
|
} else {
|
|
1627
|
-
let complete = false;
|
|
1628
1458
|
// Start user code running, record any internal liveslots errors. We do
|
|
1629
1459
|
// *not* directly wait for the userspace function to complete, nor for
|
|
1630
1460
|
// any promise it returns to fire.
|
|
1631
1461
|
const p = Promise.resolve(delivery).then(unmeteredDispatch);
|
|
1632
|
-
p.finally(() => (complete = true));
|
|
1633
1462
|
|
|
1634
1463
|
// Instead, we wait for userspace to become idle by draining the
|
|
1635
1464
|
// promise queue. We clean up and then examine/return 'p' so any
|
|
@@ -1640,10 +1469,11 @@ function build(
|
|
|
1640
1469
|
return gcTools.waitUntilQuiescent().then(() => {
|
|
1641
1470
|
afterDispatchActions();
|
|
1642
1471
|
// eslint-disable-next-line prefer-promise-reject-errors
|
|
1643
|
-
return
|
|
1472
|
+
return Promise.race([p, Promise.reject('buildRootObject unresolved')]);
|
|
1644
1473
|
// the only delivery that pays attention to a user-provided
|
|
1645
1474
|
// Promise is startVat, so the error message is specialized to
|
|
1646
|
-
// the only user problem that could cause
|
|
1475
|
+
// the only user problem that could cause the promise to not be
|
|
1476
|
+
// settled.
|
|
1647
1477
|
});
|
|
1648
1478
|
}
|
|
1649
1479
|
}
|
|
@@ -1664,7 +1494,7 @@ function build(
|
|
|
1664
1494
|
* @param {*} syscall Kernel syscall interface that the vat will have access to
|
|
1665
1495
|
* @param {*} forVatID Vat ID label, for use in debug diagostics
|
|
1666
1496
|
* @param {*} vatPowers
|
|
1667
|
-
* @param {import('./types').LiveSlotsOptions} liveSlotsOptions
|
|
1497
|
+
* @param {import('./types.js').LiveSlotsOptions} liveSlotsOptions
|
|
1668
1498
|
* @param {*} gcTools { WeakRef, FinalizationRegistry, waitUntilQuiescent }
|
|
1669
1499
|
* @param {Pick<Console, 'debug' | 'log' | 'info' | 'warn' | 'error'>} [liveSlotsConsole]
|
|
1670
1500
|
* @param {*} [buildVatNamespace]
|