@agoric/swingset-liveslots 0.10.3-u16.1 → 0.10.3-u17.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.
@@ -1,10 +1,9 @@
1
- import { assert, q, Fail } from '@agoric/assert';
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 vrefFromDBKey = dbKey => dbKey.substring(BIGINT_TAG_LEN + 2);
330
+ const vrefFromEncodedKey = encodedKey =>
331
+ encodedKey.substring(1 + BIGINT_TAG_LEN + 1);
312
332
 
313
333
  const decodeRemotable = encodedKey =>
314
- convertSlotToVal(vrefFromDBKey(encodedKey));
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
- const [coverStart, coverEnd] = getRankCover(M.any(), encodeKey);
557
- const start = prefix(coverStart);
558
- const end = prefix(coverEnd);
559
-
560
- // this yields all keys for which (start <= key < end)
561
- for (const dbKey of enumerateKeysStartEnd(syscall, start, end)) {
562
- const value = JSON.parse(syscall.vatstoreGet(dbKey));
563
- doMoreGC =
564
- value.slots.map(vrm.removeReachableVref).some(b => b) || doMoreGC;
565
- syscall.vatstoreDelete(dbKey);
566
- if (isEncodedRemotable(dbKey)) {
567
- const keyVref = vrefFromDBKey(dbKey);
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
- syscall.vatstoreDelete(prefix(`|${keyVref}`));
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 || (matchAny(keyPatt) && matchAny(valuePatt))) {
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
  }
@@ -1,4 +1,4 @@
1
- import { Fail } from '@agoric/assert';
1
+ import { Fail } from '@endo/errors';
2
2
 
3
3
  /**
4
4
  * Assess the facetiousness of a value. If the value is an object containing
package/src/liveslots.js CHANGED
@@ -1,10 +1,7 @@
1
- import {
2
- Remotable,
3
- passStyleOf,
4
- getInterfaceOf,
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 = assert.error(X`failed to reanimate ${iface}`);
751
- assert.note(wrappedError, X`Original error: ${err}`);
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
- if (type === 'object') {
760
- // this is a new import value
761
- val = makeImportedPresence(slot, iface);
762
- } else if (type === 'promise') {
763
- const pRec = makePipelinablePromise(slot);
764
- importedVPIDs.set(slot, pRec);
765
- val = pRec.promise;
766
- // ideally we'd wait until .then is called on p before subscribing,
767
- // but the current Promise API doesn't give us a way to discover
768
- // this, so we must subscribe right away. If we were using Vows or
769
- // some other then-able, we could just hook then() to notify us.
770
- if (importedPromises) {
771
- // leave the subscribe() up to dispatch.notify()
772
- importedPromises.add(slot);
773
- } else {
774
- // probably in dispatch.deliver(), so subscribe now
775
- syscall.subscribe(slot);
776
- }
777
- } else if (type === 'device') {
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
- Fail`unrecognized slot type '${type}'`;
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
- !getValForSlot(slot) || Fail`revivePromise called on pre-existing ${slot}`;
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
- async function bringOutYourDead() {
1524
- await scanForDeadObjects();
1525
- // Now flush all the vatstore changes (deletions and refcounts) we
1526
- // made. dispatch() calls afterDispatchActions() automatically for
1527
- // most methods, but not bringOutYourDead().
1528
- // eslint-disable-next-line no-use-before-define
1529
- afterDispatchActions();
1530
- // XXX TODO: make this conditional on a config setting
1531
- return getRetentionStats();
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 complete ? p : Promise.reject('buildRootObject unresolved');
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 complete===false
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
@@ -1,4 +1,4 @@
1
- import { assert, Fail } from '@agoric/assert';
1
+ import { assert, Fail } from '@endo/errors';
2
2
  import { insistCapData } from './capdata.js';
3
3
 
4
4
  /**
@@ -1,5 +1,5 @@
1
1
  import { Nat } from '@endo/nat';
2
- import { assert, Fail } from '@agoric/assert';
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
@@ -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
- export type KindFacet<O> = RemotableObject & {
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';