@agoric/swingset-liveslots 0.10.3-u16.1 → 0.10.3-u17.1
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 +17 -18
- package/src/boyd-gc.js +598 -0
- package/src/cache.js +1 -1
- package/src/capdata.js +1 -1
- package/src/collectionManager.js +65 -21
- package/src/facetiousness.js +1 -1
- package/src/liveslots.js +83 -202
- package/src/message.js +1 -1
- package/src/parseVatSlots.js +1 -1
- package/src/vatDataTypes.d.ts +7 -2
- package/src/virtualObjectManager.js +1 -6
- package/src/virtualReferences.js +83 -25
- package/src/watchedPromises.js +7 -2
- package/test/clear-collection.test.js +586 -0
- package/test/collections.test.js +41 -0
- package/test/dropped-weakset-9939.test.js +80 -0
- package/test/dummyMeterControl.js +1 -1
- package/test/durabilityChecks.test.js +1 -1
- package/test/gc-before-finalizer.test.js +230 -0
- package/test/handled-promises.test.js +266 -50
- package/test/liveslots-helpers.js +6 -1
- package/test/liveslots-mock-gc.test.js +99 -0
- package/test/liveslots-real-gc.test.js +18 -2
- package/test/liveslots.test.js +1 -1
- package/test/vat-environment.test.js +65 -0
- package/test/vat-util.js +1 -1
- package/test/virtual-objects/rep-tostring.test.js +1 -2
- package/test/vpid-liveslots.test.js +102 -1
- package/test/weakset-dropped-remotable.test.js +50 -0
- package/tools/fakeVirtualSupport.js +1 -3
- package/tools/setup-vat-data.js +10 -3
package/src/collectionManager.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { assert, q, Fail } from '@
|
|
1
|
+
import { assert, q, Fail } from '@endo/errors';
|
|
2
2
|
import { Far, passStyleOf } from '@endo/far';
|
|
3
3
|
import {
|
|
4
4
|
zeroPad,
|
|
5
5
|
makeEncodePassable,
|
|
6
6
|
makeDecodePassable,
|
|
7
|
-
isEncodedRemotable,
|
|
8
7
|
compareRank,
|
|
9
8
|
} from '@endo/marshal';
|
|
10
9
|
import {
|
|
@@ -66,6 +65,12 @@ function prefixc(collectionID, dbEntryKey) {
|
|
|
66
65
|
return `vc.${collectionID}.${dbEntryKey}`;
|
|
67
66
|
}
|
|
68
67
|
|
|
68
|
+
export const collectionMetaKeys = new Set([
|
|
69
|
+
'|entryCount',
|
|
70
|
+
'|nextOrdinal',
|
|
71
|
+
'|schemata',
|
|
72
|
+
]);
|
|
73
|
+
|
|
69
74
|
/**
|
|
70
75
|
* @typedef {object} SchemaCacheValue
|
|
71
76
|
* @property {Pattern} keyShape
|
|
@@ -293,6 +298,20 @@ export function makeCollectionManager(
|
|
|
293
298
|
return `${dbKeyPrefix}${dbEntryKey}`;
|
|
294
299
|
}
|
|
295
300
|
|
|
301
|
+
// A "vref" is a string like "o-4" or "o+d44/2:0"
|
|
302
|
+
// An "EncodedKey" is the output of encode-passable:
|
|
303
|
+
// * strings become `s${string}`, like "foo" -> "sfoo"
|
|
304
|
+
// * small positive BigInts become `p${len}:${digits}`, like 47n -> "p2:47"
|
|
305
|
+
// * refs are assigned an "ordinal" and use `r${fixedLengthOrdinal}:${vref}`
|
|
306
|
+
// * e.g. vref(o-4) becomes "r0000000001:o-4"
|
|
307
|
+
// A "DBKey" is used to index the vatstore. DBKeys for collection
|
|
308
|
+
// entries join a collection prefix and an EncodedKey. Some
|
|
309
|
+
// possible DBKeys for entries of collection "5", using collection
|
|
310
|
+
// prefix "vc.5.", are:
|
|
311
|
+
// * "foo" -> "vc.5.sfoo"
|
|
312
|
+
// * 47n -> "vc.5.p2:47"
|
|
313
|
+
// * vref(o-4) -> "vc.5.r0000000001:o-4"
|
|
314
|
+
|
|
296
315
|
const encodeRemotable = remotable => {
|
|
297
316
|
// eslint-disable-next-line no-use-before-define
|
|
298
317
|
const ordinal = getOrdinal(remotable);
|
|
@@ -308,10 +327,11 @@ export function makeCollectionManager(
|
|
|
308
327
|
// the resulting function will encode only `Key` arguments.
|
|
309
328
|
const encodeKey = makeEncodePassable({ encodeRemotable });
|
|
310
329
|
|
|
311
|
-
const
|
|
330
|
+
const vrefFromEncodedKey = encodedKey =>
|
|
331
|
+
encodedKey.substring(1 + BIGINT_TAG_LEN + 1);
|
|
312
332
|
|
|
313
333
|
const decodeRemotable = encodedKey =>
|
|
314
|
-
convertSlotToVal(
|
|
334
|
+
convertSlotToVal(vrefFromEncodedKey(encodedKey));
|
|
315
335
|
|
|
316
336
|
// `makeDecodePassable` has three named options:
|
|
317
337
|
// `decodeRemotable`, `decodeError`, and `decodePromise`.
|
|
@@ -347,10 +367,16 @@ export function makeCollectionManager(
|
|
|
347
367
|
}
|
|
348
368
|
|
|
349
369
|
function dbKeyToKey(dbKey) {
|
|
370
|
+
// convert e.g. vc.5.r0000000001:o+v10/1 to r0000000001:o+v10/1
|
|
350
371
|
const dbEntryKey = dbKey.substring(dbKeyPrefix.length);
|
|
351
372
|
return decodeKey(dbEntryKey);
|
|
352
373
|
}
|
|
353
374
|
|
|
375
|
+
function dbKeyToEncodedKey(dbKey) {
|
|
376
|
+
assert(dbKey.startsWith(dbKeyPrefix), dbKey);
|
|
377
|
+
return dbKey.substring(dbKeyPrefix.length);
|
|
378
|
+
}
|
|
379
|
+
|
|
354
380
|
function has(key) {
|
|
355
381
|
const { keyShape } = getSchema();
|
|
356
382
|
if (!matches(key, keyShape)) {
|
|
@@ -553,40 +579,58 @@ export function makeCollectionManager(
|
|
|
553
579
|
*/
|
|
554
580
|
function clearInternalFull() {
|
|
555
581
|
let doMoreGC = false;
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
//
|
|
561
|
-
for (const dbKey of
|
|
562
|
-
const
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
if (
|
|
567
|
-
|
|
582
|
+
|
|
583
|
+
// visit every DB entry associated with the collection, which
|
|
584
|
+
// (due to sorting) will be collection entries first, and then a
|
|
585
|
+
// mixture of ordinal-assignment mappings and size-independent
|
|
586
|
+
// metadata (both of which start with "|").
|
|
587
|
+
for (const dbKey of enumerateKeysWithPrefix(syscall, dbKeyPrefix)) {
|
|
588
|
+
const encodedKey = dbKeyToEncodedKey(dbKey);
|
|
589
|
+
|
|
590
|
+
// preserve general metadata ("|entryCount" and friends are
|
|
591
|
+
// cleared by our caller)
|
|
592
|
+
if (collectionMetaKeys.has(encodedKey)) continue;
|
|
593
|
+
|
|
594
|
+
if (encodedKey.startsWith('|')) {
|
|
595
|
+
// ordinal assignment; decref or de-recognize its vref
|
|
596
|
+
const keyVref = encodedKey.substring(1);
|
|
597
|
+
parseVatSlot(keyVref);
|
|
568
598
|
if (hasWeakKeys) {
|
|
569
599
|
vrm.removeRecognizableVref(keyVref, `${collectionID}`, true);
|
|
570
600
|
} else {
|
|
571
601
|
doMoreGC = vrm.removeReachableVref(keyVref) || doMoreGC;
|
|
572
602
|
}
|
|
573
|
-
|
|
603
|
+
} else {
|
|
604
|
+
// a collection entry; decref slots from its value
|
|
605
|
+
const value = JSON.parse(syscall.vatstoreGet(dbKey));
|
|
606
|
+
doMoreGC =
|
|
607
|
+
value.slots.map(vrm.removeReachableVref).some(b => b) || doMoreGC;
|
|
574
608
|
}
|
|
609
|
+
|
|
610
|
+
// in either case, delete the DB entry
|
|
611
|
+
syscall.vatstoreDelete(dbKey);
|
|
575
612
|
}
|
|
613
|
+
|
|
576
614
|
return doMoreGC;
|
|
577
615
|
}
|
|
578
616
|
|
|
579
617
|
function clearInternal(isDeleting, keyPatt, valuePatt) {
|
|
580
618
|
let doMoreGC = false;
|
|
581
|
-
if (isDeleting
|
|
619
|
+
if (isDeleting) {
|
|
582
620
|
doMoreGC = clearInternalFull();
|
|
621
|
+
// |entryCount will be deleted along with metadata keys
|
|
622
|
+
} else if (matchAny(keyPatt) && matchAny(valuePatt)) {
|
|
623
|
+
doMoreGC = clearInternalFull();
|
|
624
|
+
if (!hasWeakKeys) {
|
|
625
|
+
syscall.vatstoreSet(prefix('|entryCount'), '0');
|
|
626
|
+
}
|
|
583
627
|
} else {
|
|
628
|
+
let numDeleted = 0;
|
|
584
629
|
for (const k of keys(keyPatt, valuePatt)) {
|
|
630
|
+
numDeleted += 1;
|
|
585
631
|
doMoreGC = deleteInternal(k) || doMoreGC;
|
|
586
632
|
}
|
|
587
|
-
|
|
588
|
-
if (!hasWeakKeys && !isDeleting) {
|
|
589
|
-
syscall.vatstoreSet(prefix('|entryCount'), '0');
|
|
633
|
+
updateEntryCount(-numDeleted);
|
|
590
634
|
}
|
|
591
635
|
return doMoreGC;
|
|
592
636
|
}
|
package/src/facetiousness.js
CHANGED
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
|
|
@@ -170,52 +166,6 @@ function build(
|
|
|
170
166
|
}
|
|
171
167
|
}
|
|
172
168
|
|
|
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
169
|
function finalizeDroppedObject(baseRef) {
|
|
220
170
|
// TODO: Ideally this function should assert that it is not metered. This
|
|
221
171
|
// appears to be fine in practice, but it breaks a number of unit tests in
|
|
@@ -240,113 +190,6 @@ function build(
|
|
|
240
190
|
}
|
|
241
191
|
const vreffedObjectRegistry = new FinalizationRegistry(finalizeDroppedObject);
|
|
242
192
|
|
|
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
|
-
await null;
|
|
255
|
-
do {
|
|
256
|
-
doMore = false;
|
|
257
|
-
|
|
258
|
-
await gcTools.gcAndFinalize();
|
|
259
|
-
|
|
260
|
-
// possiblyDeadSet contains a baseref for everything (Presences,
|
|
261
|
-
// Remotables, Representatives) that might have lost a
|
|
262
|
-
// pillar. The object might still be supported by other pillars,
|
|
263
|
-
// and the lost pillar might have been reinstantiated by the
|
|
264
|
-
// time we get here. The first step is to filter this down to a
|
|
265
|
-
// list of definitely dead baserefs.
|
|
266
|
-
|
|
267
|
-
const deadSet = new Set();
|
|
268
|
-
|
|
269
|
-
for (const baseRef of possiblyDeadSet) {
|
|
270
|
-
if (slotToVal.has(baseRef)) {
|
|
271
|
-
continue; // RAM pillar remains
|
|
272
|
-
}
|
|
273
|
-
const { virtual, durable, type } = parseVatSlot(baseRef);
|
|
274
|
-
assert(type === 'object', `unprepared to track ${type}`);
|
|
275
|
-
if (virtual || durable) {
|
|
276
|
-
// eslint-disable-next-line no-use-before-define
|
|
277
|
-
if (vrm.isVirtualObjectReachable(baseRef)) {
|
|
278
|
-
continue; // vdata or export pillar remains
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
deadSet.add(baseRef);
|
|
282
|
-
}
|
|
283
|
-
possiblyDeadSet.clear();
|
|
284
|
-
|
|
285
|
-
// deadSet now contains objects which are certainly dead
|
|
286
|
-
|
|
287
|
-
// possiblyRetiredSet holds (a subset of??) baserefs which have
|
|
288
|
-
// lost a recognizer recently. TODO recheck this
|
|
289
|
-
|
|
290
|
-
for (const vref of possiblyRetiredSet) {
|
|
291
|
-
// eslint-disable-next-line no-use-before-define
|
|
292
|
-
if (!getValForSlot(vref) && !deadSet.has(vref)) {
|
|
293
|
-
// Don't retire things that haven't yet made the transition to dead,
|
|
294
|
-
// i.e., always drop before retiring
|
|
295
|
-
// eslint-disable-next-line no-use-before-define
|
|
296
|
-
if (!vrm.isVrefRecognizable(vref)) {
|
|
297
|
-
importsToRetire.add(vref);
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
possiblyRetiredSet.clear();
|
|
302
|
-
|
|
303
|
-
const deadBaseRefs = Array.from(deadSet);
|
|
304
|
-
deadBaseRefs.sort();
|
|
305
|
-
for (const baseRef of deadBaseRefs) {
|
|
306
|
-
const { virtual, durable, allocatedByVat, type } =
|
|
307
|
-
parseVatSlot(baseRef);
|
|
308
|
-
type === 'object' || Fail`unprepared to track ${type}`;
|
|
309
|
-
if (virtual || durable) {
|
|
310
|
-
// Representative: send nothing, but perform refcount checking
|
|
311
|
-
// eslint-disable-next-line no-use-before-define
|
|
312
|
-
const [gcAgain, retirees] = vrm.deleteVirtualObject(baseRef);
|
|
313
|
-
if (retirees) {
|
|
314
|
-
retirees.map(retiree => exportsToRetire.add(retiree));
|
|
315
|
-
}
|
|
316
|
-
doMore = doMore || gcAgain;
|
|
317
|
-
} else if (allocatedByVat) {
|
|
318
|
-
// Remotable: send retireExport
|
|
319
|
-
// for remotables, vref === baseRef
|
|
320
|
-
if (kernelRecognizableRemotables.has(baseRef)) {
|
|
321
|
-
kernelRecognizableRemotables.delete(baseRef);
|
|
322
|
-
exportsToRetire.add(baseRef);
|
|
323
|
-
}
|
|
324
|
-
} else {
|
|
325
|
-
// Presence: send dropImport unless reachable by VOM
|
|
326
|
-
// eslint-disable-next-line no-lonely-if, no-use-before-define
|
|
327
|
-
if (!vrm.isPresenceReachable(baseRef)) {
|
|
328
|
-
importsToDrop.add(baseRef);
|
|
329
|
-
// eslint-disable-next-line no-use-before-define
|
|
330
|
-
if (!vrm.isVrefRecognizable(baseRef)) {
|
|
331
|
-
// for presences, baseRef === vref
|
|
332
|
-
importsToRetire.add(baseRef);
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
} while (possiblyDeadSet.size > 0 || possiblyRetiredSet.size > 0 || doMore);
|
|
338
|
-
|
|
339
|
-
if (importsToDrop.size) {
|
|
340
|
-
syscall.dropImports(Array.from(importsToDrop).sort());
|
|
341
|
-
}
|
|
342
|
-
if (importsToRetire.size) {
|
|
343
|
-
syscall.retireImports(Array.from(importsToRetire).sort());
|
|
344
|
-
}
|
|
345
|
-
if (exportsToRetire.size) {
|
|
346
|
-
syscall.retireExports(Array.from(exportsToRetire).sort());
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
|
|
350
193
|
/**
|
|
351
194
|
* Remember disavowed Presences which will kill the vat if you try to talk
|
|
352
195
|
* to them
|
|
@@ -747,39 +590,50 @@ function build(
|
|
|
747
590
|
try {
|
|
748
591
|
val = vrm.reanimate(baseRef);
|
|
749
592
|
} catch (err) {
|
|
750
|
-
const wrappedError =
|
|
751
|
-
|
|
593
|
+
const wrappedError = makeError(X`failed to reanimate ${iface}`);
|
|
594
|
+
annotateError(wrappedError, X`Original error: ${err}`);
|
|
752
595
|
throw wrappedError;
|
|
753
596
|
}
|
|
754
597
|
if (facet !== undefined) {
|
|
755
598
|
result = vrm.getFacet(id, val, facet);
|
|
756
599
|
}
|
|
757
|
-
} else {
|
|
600
|
+
} else if (type === 'object') {
|
|
601
|
+
// Note: an abandonned (e.g. by an upgrade) exported ephemeral or virtual
|
|
602
|
+
// object would appear as an import if re-introduced. In the future we
|
|
603
|
+
// may need to change that if we want to keep recognizing such references
|
|
604
|
+
// In that case we'd need to create an imported presence for these
|
|
605
|
+
// unknown vrefs allocated by the vat.
|
|
606
|
+
// See https://github.com/Agoric/agoric-sdk/issues/9746
|
|
758
607
|
!allocatedByVat || Fail`I don't remember allocating ${slot}`;
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
val = makeDeviceNode(slot, iface);
|
|
779
|
-
importedDevices.add(val);
|
|
608
|
+
// this is a new import value
|
|
609
|
+
val = makeImportedPresence(slot, iface);
|
|
610
|
+
} else if (type === 'promise') {
|
|
611
|
+
// We unconditionally create a promise record, even if the promise looks
|
|
612
|
+
// like it was allocated by us. This can happen when re-importing a
|
|
613
|
+
// promise created by the previous incarnation. We may or may not have
|
|
614
|
+
// been the decider of the promise. If we were, the kernel will be
|
|
615
|
+
// rejecting the promise on our behalf. We may have previously been
|
|
616
|
+
// subscribed to that promise, but subscription is idempotent.
|
|
617
|
+
const pRec = makePipelinablePromise(slot);
|
|
618
|
+
importedVPIDs.set(slot, pRec);
|
|
619
|
+
val = pRec.promise;
|
|
620
|
+
// ideally we'd wait until .then is called on p before subscribing,
|
|
621
|
+
// but the current Promise API doesn't give us a way to discover
|
|
622
|
+
// this, so we must subscribe right away. If we were using Vows or
|
|
623
|
+
// some other then-able, we could just hook then() to notify us.
|
|
624
|
+
if (importedPromises) {
|
|
625
|
+
// leave the subscribe() up to dispatch.notify()
|
|
626
|
+
importedPromises.add(slot);
|
|
780
627
|
} else {
|
|
781
|
-
|
|
628
|
+
// probably in dispatch.deliver(), so subscribe now
|
|
629
|
+
syscall.subscribe(slot);
|
|
782
630
|
}
|
|
631
|
+
} else if (type === 'device') {
|
|
632
|
+
!allocatedByVat || Fail`unexpected device ${slot} allocated by vat`;
|
|
633
|
+
val = makeDeviceNode(slot, iface);
|
|
634
|
+
importedDevices.add(val);
|
|
635
|
+
} else {
|
|
636
|
+
Fail`unrecognized slot type '${type}'`;
|
|
783
637
|
}
|
|
784
638
|
registerValue(baseRef, val, facet !== undefined);
|
|
785
639
|
if (!result) {
|
|
@@ -792,7 +646,25 @@ function build(
|
|
|
792
646
|
meterControl.assertNotMetered();
|
|
793
647
|
const { type } = parseVatSlot(slot);
|
|
794
648
|
type === 'promise' || Fail`revivePromise called on non-promise ${slot}`;
|
|
795
|
-
|
|
649
|
+
const val = getValForSlot(slot);
|
|
650
|
+
if (val) {
|
|
651
|
+
// revivePromise is only called by loadWatchedPromiseTable, which runs
|
|
652
|
+
// after buildRootObject(), which is given the deserialized vatParameters.
|
|
653
|
+
// The only way revivePromise() might encounter a pre-existing vpid is if
|
|
654
|
+
// these vatParameters include a promise that the previous incarnation
|
|
655
|
+
// watched, but that `buildRootObject` in the new incarnation didn't
|
|
656
|
+
// explicitly watch again. This can be either a previously imported
|
|
657
|
+
// promise, or a promise the previous incarnation exported, regardless of
|
|
658
|
+
// who the decider now is.
|
|
659
|
+
//
|
|
660
|
+
// In that case convertSlotToVal() has already deserialized the vpid, but
|
|
661
|
+
// since `buildRootObject` didn't explicitely call watchPromise on it, no
|
|
662
|
+
// registration exists so loadWatchedPromiseTable attempts to revive the
|
|
663
|
+
// promise.
|
|
664
|
+
return val;
|
|
665
|
+
}
|
|
666
|
+
// NOTE: it is important that this code not do anything *more*
|
|
667
|
+
// than what convertSlotToVal(vpid) would do
|
|
796
668
|
const pRec = makePipelinablePromise(slot);
|
|
797
669
|
importedVPIDs.set(slot, pRec);
|
|
798
670
|
const p = pRec.promise;
|
|
@@ -1341,6 +1213,7 @@ function build(
|
|
|
1341
1213
|
const inescapableGlobalProperties = harden({
|
|
1342
1214
|
WeakMap: vom.VirtualObjectAwareWeakMap,
|
|
1343
1215
|
WeakSet: vom.VirtualObjectAwareWeakSet,
|
|
1216
|
+
[PassStyleOfEndowmentSymbol]: passStyleOf,
|
|
1344
1217
|
});
|
|
1345
1218
|
|
|
1346
1219
|
function getRetentionStats() {
|
|
@@ -1520,16 +1393,15 @@ function build(
|
|
|
1520
1393
|
// metered
|
|
1521
1394
|
const unmeteredDispatch = meterControl.unmetered(dispatchToUserspace);
|
|
1522
1395
|
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
}
|
|
1396
|
+
const { scanForDeadObjects } = makeBOYDKit({
|
|
1397
|
+
gcTools,
|
|
1398
|
+
slotToVal,
|
|
1399
|
+
vrm,
|
|
1400
|
+
kernelRecognizableRemotables,
|
|
1401
|
+
syscall,
|
|
1402
|
+
possiblyDeadSet,
|
|
1403
|
+
possiblyRetiredSet,
|
|
1404
|
+
});
|
|
1533
1405
|
|
|
1534
1406
|
/**
|
|
1535
1407
|
* @param { import('./types.js').SwingSetCapData } _disconnectObjectCapData
|
|
@@ -1549,6 +1421,16 @@ function build(
|
|
|
1549
1421
|
vom.flushStateCache();
|
|
1550
1422
|
}
|
|
1551
1423
|
|
|
1424
|
+
const bringOutYourDead = async () => {
|
|
1425
|
+
await scanForDeadObjects();
|
|
1426
|
+
// Now flush all the vatstore changes (deletions and refcounts) we
|
|
1427
|
+
// made. dispatch() calls afterDispatchActions() automatically for
|
|
1428
|
+
// most methods, but not bringOutYourDead().
|
|
1429
|
+
afterDispatchActions();
|
|
1430
|
+
// XXX TODO: make this conditional on a config setting
|
|
1431
|
+
return getRetentionStats();
|
|
1432
|
+
};
|
|
1433
|
+
|
|
1552
1434
|
/**
|
|
1553
1435
|
* This 'dispatch' function is the entry point for the vat as a whole: the
|
|
1554
1436
|
* vat-worker supervisor gives us VatDeliveryObjects (like
|
|
@@ -1595,12 +1477,10 @@ function build(
|
|
|
1595
1477
|
} else if (delivery[0] === 'stopVat') {
|
|
1596
1478
|
return meterControl.runWithoutMeteringAsync(() => stopVat(delivery[1]));
|
|
1597
1479
|
} else {
|
|
1598
|
-
let complete = false;
|
|
1599
1480
|
// Start user code running, record any internal liveslots errors. We do
|
|
1600
1481
|
// *not* directly wait for the userspace function to complete, nor for
|
|
1601
1482
|
// any promise it returns to fire.
|
|
1602
1483
|
const p = Promise.resolve(delivery).then(unmeteredDispatch);
|
|
1603
|
-
void p.finally(() => (complete = true));
|
|
1604
1484
|
|
|
1605
1485
|
// Instead, we wait for userspace to become idle by draining the
|
|
1606
1486
|
// promise queue. We clean up and then examine/return 'p' so any
|
|
@@ -1611,10 +1491,11 @@ function build(
|
|
|
1611
1491
|
return gcTools.waitUntilQuiescent().then(() => {
|
|
1612
1492
|
afterDispatchActions();
|
|
1613
1493
|
// eslint-disable-next-line prefer-promise-reject-errors
|
|
1614
|
-
return
|
|
1494
|
+
return Promise.race([p, Promise.reject('buildRootObject unresolved')]);
|
|
1615
1495
|
// the only delivery that pays attention to a user-provided
|
|
1616
1496
|
// Promise is startVat, so the error message is specialized to
|
|
1617
|
-
// the only user problem that could cause
|
|
1497
|
+
// the only user problem that could cause the promise to not be
|
|
1498
|
+
// settled.
|
|
1618
1499
|
});
|
|
1619
1500
|
}
|
|
1620
1501
|
}
|
package/src/message.js
CHANGED
package/src/parseVatSlots.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Nat } from '@endo/nat';
|
|
2
|
-
import { assert, Fail } from '@
|
|
2
|
+
import { assert, Fail } from '@endo/errors';
|
|
3
3
|
|
|
4
4
|
// NOTE: confusing terminology: "slot" vs. "reference". All these things
|
|
5
5
|
// called "slots" are references, but the word "slot" suggests something into
|
package/src/vatDataTypes.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ import type {
|
|
|
14
14
|
} from '@agoric/store';
|
|
15
15
|
import type { Amplify, IsInstance, ReceivePower, StateShape } from '@endo/exo';
|
|
16
16
|
import type { RemotableObject } from '@endo/pass-style';
|
|
17
|
+
import type { RemotableBrand } from '@endo/eventual-send';
|
|
17
18
|
import type { InterfaceGuard, Pattern } from '@endo/patterns';
|
|
18
19
|
import type { makeWatchedPromiseManager } from './watchedPromises.js';
|
|
19
20
|
|
|
@@ -38,9 +39,13 @@ type OmitFirstArg<F> = F extends (x: any, ...args: infer P) => infer R
|
|
|
38
39
|
? (...args: P) => R
|
|
39
40
|
: never;
|
|
40
41
|
|
|
41
|
-
|
|
42
|
+
// The type of a passable local object with methods.
|
|
43
|
+
// An internal helper to avoid having to repeat `O`.
|
|
44
|
+
type PrimaryRemotable<O> = O & RemotableObject & RemotableBrand<{}, O>;
|
|
45
|
+
|
|
46
|
+
export type KindFacet<O> = PrimaryRemotable<{
|
|
42
47
|
[K in keyof O]: OmitFirstArg<O[K]>; // omit the 'context' parameter
|
|
43
|
-
}
|
|
48
|
+
}>;
|
|
44
49
|
|
|
45
50
|
export type KindFacets<B> = {
|
|
46
51
|
[FacetKey in keyof B]: KindFacet<B[FacetKey]>;
|
|
@@ -2,12 +2,7 @@
|
|
|
2
2
|
/* eslint-disable no-use-before-define, jsdoc/require-returns-type */
|
|
3
3
|
|
|
4
4
|
import { environmentOptionsListHas } from '@endo/env-options';
|
|
5
|
-
import {
|
|
6
|
-
assert,
|
|
7
|
-
throwRedacted as Fail,
|
|
8
|
-
quote as q,
|
|
9
|
-
bare as b,
|
|
10
|
-
} from '@endo/errors';
|
|
5
|
+
import { assert, Fail, q, b } from '@endo/errors';
|
|
11
6
|
import { assertPattern, mustMatch } from '@agoric/store';
|
|
12
7
|
import { defendPrototype, defendPrototypeKit } from '@endo/exo/tools.js';
|
|
13
8
|
import { Far, passStyleOf } from '@endo/marshal';
|