@agoric/swingset-liveslots 0.10.3-other-dev-8f8782b.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.
Files changed (77) hide show
  1. package/README.md +2 -0
  2. package/package.json +27 -19
  3. package/src/boyd-gc.js +598 -0
  4. package/src/cache.js +3 -2
  5. package/src/capdata.js +1 -2
  6. package/src/collectionManager.js +219 -103
  7. package/src/facetiousness.js +1 -1
  8. package/src/index.js +4 -2
  9. package/src/liveslots.js +131 -301
  10. package/src/message.js +5 -5
  11. package/src/parseVatSlots.js +1 -1
  12. package/src/types-index.d.ts +4 -0
  13. package/src/types-index.js +2 -0
  14. package/src/types.js +8 -2
  15. package/src/vatstore-iterators.js +2 -0
  16. package/src/virtualObjectManager.js +185 -71
  17. package/src/virtualReferences.js +135 -26
  18. package/src/watchedPromises.js +67 -17
  19. package/test/{test-baggage.js → baggage.test.js} +1 -2
  20. package/test/{test-cache.js → cache.test.js} +0 -1
  21. package/test/clear-collection.test.js +586 -0
  22. package/test/{test-collection-schema-refcount.js → collection-schema-refcount.test.js} +1 -2
  23. package/test/{test-collection-upgrade.js → collection-upgrade.test.js} +1 -3
  24. package/test/{test-collections.js → collections.test.js} +158 -14
  25. package/test/{test-dropped-collection-weakrefs.js → dropped-collection-weakrefs.test.js} +1 -2
  26. package/test/dropped-weakset-9939.test.js +80 -0
  27. package/test/dummyMeterControl.js +1 -1
  28. package/test/{test-durabilityChecks.js → durabilityChecks.test.js} +4 -4
  29. package/test/exo-utils.js +70 -0
  30. package/test/{test-facetiousness.js → facetiousness.test.js} +1 -2
  31. package/test/gc-and-finalize.js +30 -1
  32. package/test/gc-before-finalizer.test.js +230 -0
  33. package/test/gc-helpers.js +2 -3
  34. package/test/{test-gc-sensitivity.js → gc-sensitivity.test.js} +2 -2
  35. package/test/handled-promises.test.js +506 -0
  36. package/test/{test-initial-vrefs.js → initial-vrefs.test.js} +2 -3
  37. package/test/liveslots-helpers.js +12 -7
  38. package/test/{test-liveslots-mock-gc.js → liveslots-mock-gc.test.js} +101 -2
  39. package/test/{test-liveslots-real-gc.js → liveslots-real-gc.test.js} +62 -37
  40. package/test/{test-liveslots.js → liveslots.test.js} +14 -15
  41. package/test/mock-gc.js +1 -0
  42. package/test/storeGC/{test-lifecycle.js → lifecycle.test.js} +2 -2
  43. package/test/storeGC/{test-refcount-management.js → refcount-management.test.js} +1 -2
  44. package/test/storeGC/{test-scalar-store-kind.js → scalar-store-kind.test.js} +0 -1
  45. package/test/storeGC/{test-weak-key.js → weak-key.test.js} +1 -2
  46. package/test/strict-test-env-upgrade.test.js +94 -0
  47. package/test/util.js +2 -2
  48. package/test/vat-environment.test.js +65 -0
  49. package/test/vat-util.js +2 -2
  50. package/test/virtual-objects/{test-cease-recognition.js → cease-recognition.test.js} +2 -2
  51. package/test/virtual-objects/{test-cross-facet.js → cross-facet.test.js} +5 -4
  52. package/test/virtual-objects/{test-empty-data.js → empty-data.test.js} +1 -2
  53. package/test/virtual-objects/{test-facets.js → facets.test.js} +1 -2
  54. package/test/virtual-objects/{test-kind-changes.js → kind-changes.test.js} +2 -2
  55. package/test/virtual-objects/{test-reachable-vrefs.js → reachable-vrefs.test.js} +2 -2
  56. package/test/virtual-objects/{test-rep-tostring.js → rep-tostring.test.js} +3 -5
  57. package/test/virtual-objects/{test-retain-remotable.js → retain-remotable.test.js} +25 -24
  58. package/test/virtual-objects/set-debug-label-instances.js +1 -1
  59. package/test/virtual-objects/{test-state-shape.js → state-shape.test.js} +2 -2
  60. package/test/virtual-objects/{test-virtualObjectGC.js → virtualObjectGC.test.js} +2 -2
  61. package/test/virtual-objects/{test-virtualObjectManager.js → virtualObjectManager.test.js} +126 -8
  62. package/test/virtual-objects/{test-vo-real-gc.js → vo-real-gc.test.js} +8 -8
  63. package/test/virtual-objects/{test-weakcollections-vref-handling.js → weakcollections-vref-handling.test.js} +1 -2
  64. package/test/{test-vo-test-harness.js → vo-test-harness.test.js} +0 -1
  65. package/test/{test-vpid-liveslots.js → vpid-liveslots.test.js} +105 -5
  66. package/test/waitUntilQuiescent.js +2 -1
  67. package/test/weakset-dropped-remotable.test.js +50 -0
  68. package/tools/fakeCollectionManager.js +44 -0
  69. package/tools/fakeVirtualObjectManager.js +62 -0
  70. package/tools/fakeVirtualSupport.js +389 -0
  71. package/tools/prepare-strict-test-env.js +124 -0
  72. package/tools/prepare-test-env.js +13 -0
  73. package/tools/setup-vat-data.js +96 -0
  74. package/tools/vo-test-harness.js +143 -0
  75. package/CHANGELOG.md +0 -61
  76. package/test/kmarshal.js +0 -79
  77. package/test/test-handled-promises.js +0 -360
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
@@ -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: 70000,
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).forEach((name, index) => {
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 = assert.error(X`failed to reanimate ${iface}`);
800
- assert.note(wrappedError, X`Original error: ${err}`);
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
- if (type === 'object') {
809
- // this is a new import value
810
- val = makeImportedPresence(slot, iface);
811
- } else if (type === 'promise') {
812
- const pRec = makePipelinablePromise(slot);
813
- importedVPIDs.set(slot, pRec);
814
- val = pRec.promise;
815
- // ideally we'd wait until .then is called on p before subscribing,
816
- // but the current Promise API doesn't give us a way to discover
817
- // this, so we must subscribe right away. If we were using Vows or
818
- // some other then-able, we could just hook then() to notify us.
819
- if (importedPromises) {
820
- // leave the subscribe() up to dispatch.notify()
821
- importedPromises.add(slot);
822
- } else {
823
- // probably in dispatch.deliver(), so subscribe now
824
- syscall.subscribe(slot);
825
- }
826
- } else if (type === 'device') {
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
- Fail`unrecognized slot type '${type}'`;
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
- !getValForSlot(slot) || Fail`revivePromise called on pre-existing ${slot}`;
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
- resolutions.forEach(([_xvpid, _isReject, resolutionCD]) => {
987
- resolutionCD.slots.forEach(vref => maybeNewVPIDs.add(vref));
988
- });
989
- resolutions.forEach(([xvpid]) => maybeNewVPIDs.delete(xvpid));
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
- resolutions.forEach(([_xvpid, _isReject, resolutionCD]) => {
1194
- resolutionCD.slots.forEach(vref => maybeNewVPIDs.add(vref));
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
- resolutions.forEach(([xvpid]) => maybeNewVPIDs.delete(xvpid));
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().forEach(maybeExportPromise);
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
- retiredVPIDs.forEach(vpid => {
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.forEach(retireOneExport);
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.forEach(vrm.ceaseRecognition);
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 {void | Promise<void>}
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
- async function bringOutYourDead() {
1553
- await scanForDeadObjects();
1554
- // Now flush all the vatstore changes (deletions and refcounts) we
1555
- // made. dispatch() calls afterDispatchActions() automatically for
1556
- // most methods, but not bringOutYourDead().
1557
- // eslint-disable-next-line no-use-before-define
1558
- afterDispatchActions();
1559
- // XXX TODO: make this conditional on a config setting
1560
- return getRetentionStats();
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 complete ? p : Promise.reject('buildRootObject unresolved');
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 complete===false
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]