@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
@@ -1,6 +1,6 @@
1
- /* eslint-disable no-use-before-define, jsdoc/require-returns-type */
1
+ /* eslint-disable jsdoc/require-returns-type */
2
2
 
3
- import { assert, Fail } from '@agoric/assert';
3
+ import { assert, Fail } from '@endo/errors';
4
4
  import { Nat } from '@endo/nat';
5
5
  import { parseVatSlot } from './parseVatSlots.js';
6
6
  import {
@@ -290,7 +290,10 @@ export function makeVirtualReferenceManager(
290
290
  */
291
291
  function isDurable(vref) {
292
292
  const { type, id, virtual, durable, allocatedByVat } = parseVatSlot(vref);
293
- if (relaxDurabilityRules) {
293
+ if (type === 'promise') {
294
+ // promises are not durable even if `relaxDurabilityRules === true`
295
+ return false;
296
+ } else if (relaxDurabilityRules) {
294
297
  // we'll pretend an object is durable if running with relaxed rules
295
298
  return true;
296
299
  } else if (type === 'device') {
@@ -497,26 +500,51 @@ export function makeVirtualReferenceManager(
497
500
  }
498
501
 
499
502
  /**
500
- * A vref is "recognizable" when it is used as the key of a weak Map
501
- * or Set: that Map/Set can be used to query whether a future
502
- * specimen matches the original or not, without holding onto the
503
- * original.
503
+ * A vref is "recognizable" when it is used as the key of a weak
504
+ * collection, like a virtual/durable WeakMapStore or WeakSetStore,
505
+ * or the ephemeral voAwareWeakMap/Set that we impose upon userspace
506
+ * as "WeakMap/WeakSet". The collection can be used to query whether
507
+ * a future specimen matches the original or not, without holding
508
+ * onto the original.
509
+ *
510
+ * We need "recognition records" to map from the vref to the
511
+ * collection that can recognize it. When the vref is retired, we
512
+ * use the record to find all the collections from which we need to
513
+ * delete entries, so we can release the matching values. This might
514
+ * happen because the vref was for a Presence and the kernel just
515
+ * told us the upstream vat has deleted it (dispatch.retireImports),
516
+ * or because it was for a locally-managed object (an ephemeral
517
+ * Remotable or a virtual/durable Representative) and we decided to
518
+ * delete it.
519
+ *
520
+ * The virtual/durable collections track their "recognition records"
521
+ * in the vatstore, in keys like "vom.ir.${vref}|${collectionID}".
522
+ * These records do not contribute to our RAM usage.
523
+ *
524
+ * voAwareWeakMap and voAwareWeakSet store their recognition records
525
+ * in RAM, using this Map named 'vrefRecognizers'. Each key is a
526
+ * vref, and the value is a Set of recognizers. Each recognizer is
527
+ * the internal 'virtualObjectMap' in which the collection maps from
528
+ * vref to value. These in-RAM collections only use virtualObjectMap
529
+ * to track Presence-style (imports) and Representative-style
530
+ * (virtual/durable) vrefs: any Remotable-style keys are stored in
531
+ * the collection's internal (real) WeakMap under the Remotable
532
+ * object itself (because the engine handles the bookkeeping, and
533
+ * there is no virtual data in the value that we need to clean up at
534
+ * deletion time).
504
535
  *
505
- * This 'vrefRecognizers' is a Map from those vrefs to the set of
506
- * recognizing weak collections, for virtual keys and non-virtual
507
- * collections. Specifically, the vrefs correspond to imported
508
- * Presences or virtual-object Representatives (Remotables do not
509
- * participate: they are keyed by the actual Remotable object, not
510
- * its vref). The collections are either a VirtualObjectAwareWeakMap
511
- * or a VirtualObjectAwareWeakSet. We remove the entry when the key
512
- * is removed from the collection, and when the entire collection is
513
- * deleted.
536
+ * Each voAwareWeakMap/Set must have a distinct recognizer, so we
537
+ * can remove the key from the right ones. The recognizer is held
538
+ * strongly by the recognition record, so it must not be the
539
+ * voAwareWeakMap/Set itself (which would inhibit GC).
514
540
  *
515
- * It is critical that each collection have exactly one recognizer that is
516
- * unique to that collection, because the recognizers themselves will be
517
- * tracked by their object identities, but the recognizer cannot be the
518
- * collection itself else it would prevent the collection from being garbage
519
- * collected.
541
+ * When an individual entry is deleted from the weak collection, we
542
+ * must also delete the recognition record. When the collection
543
+ * itself is deleted (i.e. because nothing was referencing it), we
544
+ * must both delete all recognition records and also notify the
545
+ * kernel about any Presence-style vrefs that we can no longer
546
+ * recognize (syscall.retireImports). The kernel doesn't care about
547
+ * Remotable- or Representative- style vrefs, only the imports.
520
548
  *
521
549
  * TODO: all the "recognizers" in principle could be, and probably should be,
522
550
  * reduced to deleter functions. However, since the VirtualObjectAware
@@ -532,14 +560,24 @@ export function makeVirtualReferenceManager(
532
560
  /** @type {Map<string, Set<Recognizer>>} */
533
561
  const vrefRecognizers = new Map();
534
562
 
563
+ /**
564
+ * @param {*} value The vref-bearing object used as the collection key
565
+ * @param {string|Recognizer} recognizer The collectionID or virtualObjectMap for the collection
566
+ * @param {boolean} [recognizerIsVirtual] true for virtual/durable Stores, false for voAwareWeakMap/Set
567
+ */
535
568
  function addRecognizableValue(value, recognizer, recognizerIsVirtual) {
536
569
  const vref = getSlotForVal(value);
537
570
  if (vref) {
538
571
  const { type, allocatedByVat, virtual, durable } = parseVatSlot(vref);
539
- if (type === 'object' && (!allocatedByVat || virtual || durable)) {
572
+ if (type === 'object') {
573
+ // recognizerSet (voAwareWeakMap/Set) doesn't track Remotables
574
+ const notRemotable = !allocatedByVat || virtual || durable;
575
+
540
576
  if (recognizerIsVirtual) {
577
+ assert.typeof(recognizer, 'string');
541
578
  syscall.vatstoreSet(`vom.ir.${vref}|${recognizer}`, '1');
542
- } else {
579
+ } else if (notRemotable) {
580
+ assert.typeof(recognizer, 'object');
543
581
  let recognizerSet = vrefRecognizers.get(vref);
544
582
  if (!recognizerSet) {
545
583
  recognizerSet = new Set();
@@ -551,18 +589,33 @@ export function makeVirtualReferenceManager(
551
589
  }
552
590
  }
553
591
 
592
+ /**
593
+ * @param {string} vref The vref or the object used as the collection key
594
+ * @param {string|Recognizer} recognizer The collectionID or virtualObjectMap for the collection
595
+ * @param {boolean} [recognizerIsVirtual] true for virtual/durable Stores, false for voAwareWeakMap/Set
596
+ */
554
597
  function removeRecognizableVref(vref, recognizer, recognizerIsVirtual) {
555
598
  const { type, allocatedByVat, virtual, durable } = parseVatSlot(vref);
556
- if (type === 'object' && (!allocatedByVat || virtual || durable)) {
599
+ if (type === 'object') {
600
+ // addToPossiblyDeadSet only needs Presence-style vrefs
601
+ const isPresence = !allocatedByVat;
602
+ // recognizerSet (voAwareWeakMap/Set) doesn't track Remotables
603
+ const notRemotable = !allocatedByVat || virtual || durable;
604
+
557
605
  if (recognizerIsVirtual) {
606
+ assert.typeof(recognizer, 'string');
558
607
  syscall.vatstoreDelete(`vom.ir.${vref}|${recognizer}`);
559
- } else {
608
+ if (isPresence) {
609
+ addToPossiblyRetiredSet(vref);
610
+ }
611
+ } else if (notRemotable) {
612
+ assert.typeof(recognizer, 'object');
560
613
  const recognizerSet = vrefRecognizers.get(vref);
561
614
  assert(recognizerSet && recognizerSet.has(recognizer));
562
615
  recognizerSet.delete(recognizer);
563
616
  if (recognizerSet.size === 0) {
564
617
  vrefRecognizers.delete(vref);
565
- if (!allocatedByVat) {
618
+ if (isPresence) {
566
619
  addToPossiblyRetiredSet(vref);
567
620
  }
568
621
  }
@@ -570,6 +623,11 @@ export function makeVirtualReferenceManager(
570
623
  }
571
624
  }
572
625
 
626
+ /**
627
+ * @param {*} value The vref-bearing object used as the collection key
628
+ * @param {string|Recognizer} recognizer The collectionID or virtualObjectMap for the collection
629
+ * @param {boolean} [recognizerIsVirtual] true for virtual/durable Stores, false for voAwareWeakMap/Set
630
+ */
573
631
  function removeRecognizableValue(value, recognizer, recognizerIsVirtual) {
574
632
  const vref = getSlotForVal(value);
575
633
  if (vref) {
@@ -682,6 +740,54 @@ export function makeVirtualReferenceManager(
682
740
  return size;
683
741
  }
684
742
 
743
+ /**
744
+ * Counters to track the next number for various categories of allocation.
745
+ * `exportID` starts at 1 because 'o+0' is always automatically
746
+ * pre-assigned to the root object.
747
+ * `promiseID` starts at 5 as a very minor aid to debugging: when puzzling
748
+ * over trace logs and the like, it helps for the numbers in various species
749
+ * of IDs that are jumbled together to be a little out of sync and thus a
750
+ * little less similar to each other.
751
+ */
752
+ const initialIDCounters = { exportID: 1, collectionID: 1, promiseID: 5 };
753
+ /** @type {Record<string, number>} */
754
+ let idCounters;
755
+ let idCountersAreDirty = false;
756
+
757
+ function initializeIDCounters() {
758
+ if (!idCounters) {
759
+ // the saved value might be missing, or from an older liveslots
760
+ // (with fewer counters), so merge it with our initial values
761
+ const saved = JSON.parse(syscall.vatstoreGet('idCounters') || '{}');
762
+ idCounters = { ...initialIDCounters, ...saved };
763
+ idCountersAreDirty = true;
764
+ }
765
+ }
766
+
767
+ function allocateNextID(name) {
768
+ if (!idCounters) {
769
+ // Normally `initializeIDCounters` would be called from startVat, but some
770
+ // tests bypass that so this is a backstop. Note that the invocation from
771
+ // startVat is there to make vatStore access patterns a bit more
772
+ // consistent from one vat to another, principally as a confusion
773
+ // reduction measure in service of debugging; it is not a correctness
774
+ // issue.
775
+ initializeIDCounters();
776
+ }
777
+ const result = idCounters[name];
778
+ result !== undefined || Fail`unknown idCounters[${name}]`;
779
+ idCounters[name] += 1;
780
+ idCountersAreDirty = true;
781
+ return result;
782
+ }
783
+
784
+ function flushIDCounters() {
785
+ if (idCountersAreDirty) {
786
+ syscall.vatstoreSet('idCounters', JSON.stringify(idCounters));
787
+ idCountersAreDirty = false;
788
+ }
789
+ }
790
+
685
791
  const testHooks = {
686
792
  getReachableRefCount,
687
793
  countCollectionsForWeakKey,
@@ -726,6 +832,9 @@ export function makeVirtualReferenceManager(
726
832
  ceaseRecognition,
727
833
  setDeleteCollectionEntry,
728
834
  getRetentionStats,
835
+ initializeIDCounters,
836
+ allocateNextID,
837
+ flushIDCounters,
729
838
  testHooks,
730
839
  });
731
840
  }
@@ -1,11 +1,18 @@
1
+ // @ts-check
1
2
  // no-lonely-if is a stupid rule that really should be disabled globally
2
3
  /* eslint-disable no-lonely-if */
3
4
 
4
- import { assert } from '@agoric/assert';
5
- import { initEmpty, M } from '@agoric/store';
5
+ import { Fail, assert } from '@endo/errors';
6
6
  import { E } from '@endo/eventual-send';
7
+ import { initEmpty, M } from '@agoric/store';
7
8
  import { parseVatSlot } from './parseVatSlots.js';
8
9
 
10
+ /**
11
+ * @template V
12
+ * @template {any[]} [A=unknown[]]
13
+ * @typedef {[watcher: import('./types.js').PromiseWatcher<V, A>, ...args: A]} PromiseWatcherTuple
14
+ */
15
+
9
16
  /**
10
17
  * @param {object} options
11
18
  * @param {*} options.syscall
@@ -28,17 +35,28 @@ export function makeWatchedPromiseManager({
28
35
  const { makeScalarBigMapStore } = collectionManager;
29
36
  const { defineDurableKind } = vom;
30
37
 
31
- // virtual Store (not durable) mapping vpid to Promise objects, to
32
- // maintain the slotToVal registration until resolution. Without
33
- // this, slotToVal would forget local Promises that aren't exported.
38
+ /**
39
+ * virtual Store (not durable) mapping vpid to Promise objects, to
40
+ * maintain the slotToVal registration until resolution. Without
41
+ * this, slotToVal would forget local Promises that aren't exported.
42
+ *
43
+ * @type {MapStore<string, Promise<unknown>>}
44
+ */
34
45
  let promiseRegistrations;
35
46
 
36
- // watched promises by vpid: each entry is an array of watches on the
37
- // corresponding vpid; each of these is in turn an array of a watcher object
38
- // and the arguments associated with it by `watchPromise`.
47
+ /**
48
+ * watched promises by vpid: each entry is an array of watches on the
49
+ * corresponding vpid; each of these is in turn an array of a watcher object
50
+ * and the arguments associated with it by `watchPromise`.
51
+ * @type {MapStore<string, PromiseWatcherTuple<unknown>[]>}
52
+ */
39
53
  let watchedPromiseTable;
40
54
 
41
- // defined promise watcher objects indexed by kindHandle
55
+ /**
56
+ * defined promise watcher objects indexed by kindHandle
57
+ *
58
+ * @type {MapStore<import('./vatDataTypes.js').DurableKindHandle, import('./types.js').PromiseWatcher<unknown>>}
59
+ */
42
60
  let promiseWatcherByKindTable;
43
61
 
44
62
  function preparePromiseWatcherTables() {
@@ -73,11 +91,17 @@ export function makeWatchedPromiseManager({
73
91
  }
74
92
 
75
93
  /**
76
- *
77
- * @param {Promise<unknown>} p
94
+ * @template T
95
+ * @param {Promise<T>} p
78
96
  * @param {string} vpid
97
+ * @returns {void}
79
98
  */
80
99
  function pseudoThen(p, vpid) {
100
+ /**
101
+ *
102
+ * @param {T} value
103
+ * @param {boolean} wasFulfilled
104
+ */
81
105
  function settle(value, wasFulfilled) {
82
106
  const watches = watchedPromiseTable.get(vpid);
83
107
  watchedPromiseTable.delete(vpid);
@@ -115,26 +139,46 @@ export function makeWatchedPromiseManager({
115
139
  */
116
140
  function loadWatchedPromiseTable(revivePromise) {
117
141
  for (const vpid of watchedPromiseTable.keys()) {
142
+ if (promiseRegistrations.has(vpid)) {
143
+ // We're only interested in reconnecting the promises from the previous
144
+ // incarnation. Any promise watched during buildRootObject would have
145
+ // already created a registration.
146
+ continue;
147
+ }
118
148
  const p = revivePromise(vpid);
119
149
  promiseRegistrations.init(vpid, p);
120
150
  pseudoThen(p, vpid);
121
151
  }
122
152
  }
123
153
 
154
+ /**
155
+ * @template V
156
+ * @template {any[]} A]
157
+ * @param {import('./vatDataTypes.js').DurableKindHandle} kindHandle
158
+ * @param {(value: V, ...args: A) => void} fulfillHandler
159
+ * @param {(reason: any, ...args: A) => void} rejectHandler
160
+ * @returns {import('./types.js').PromiseWatcher<V, A>}
161
+ */
124
162
  function providePromiseWatcher(
125
163
  kindHandle,
126
- fulfillHandler = x => x,
127
- rejectHandler = x => {
128
- throw x;
164
+ // @ts-expect-error xxx rest params in typedef
165
+ fulfillHandler = _value => {
166
+ // It's fine to not pass the value through since promise watchers are not chainable
167
+ },
168
+ // @ts-expect-error xxx rest params in typedef
169
+ rejectHandler = reason => {
170
+ // Replicate the unhandled rejection that would have happened if the
171
+ // watcher had not implemented an `onRejected` method. See `settle` above
172
+ throw reason;
129
173
  },
130
174
  ) {
131
175
  assert.typeof(fulfillHandler, 'function');
132
176
  assert.typeof(rejectHandler, 'function');
133
177
 
134
178
  const makeWatcher = defineDurableKind(kindHandle, initEmpty, {
135
- // @ts-expect-error TS is confused by the spread operator
179
+ /** @type {(context: unknown, res: V, ...args: A) => void} */
136
180
  onFulfilled: (_context, res, ...args) => fulfillHandler(res, ...args),
137
- // @ts-expect-error
181
+ /** @type {(context: unknown, rej: unknown, ...args: A) => void} */
138
182
  onRejected: (_context, rej, ...args) => rejectHandler(rej, ...args),
139
183
  });
140
184
 
@@ -147,6 +191,9 @@ export function makeWatchedPromiseManager({
147
191
  }
148
192
  }
149
193
 
194
+ /**
195
+ * @type {<P extends Promise<any>, A extends any[]>(p: P, watcher: import('./types.js').PromiseWatcher<Awaited<P>, A>, ...args: A) => void}
196
+ */
150
197
  function watchPromise(p, watcher, ...args) {
151
198
  // The following wrapping defers setting up the promise watcher itself to a
152
199
  // later turn so that if the promise to be watched was the return value from
@@ -163,7 +210,10 @@ export function makeWatchedPromiseManager({
163
210
  const watcherVref = convertValToSlot(watcher);
164
211
  assert(watcherVref, 'invalid watcher');
165
212
  const { virtual, durable } = parseVatSlot(watcherVref);
166
- assert(virtual || durable, 'promise watcher must be a virtual object');
213
+ virtual ||
214
+ durable ||
215
+ // separate line so easy to breakpoint on
216
+ Fail`promise watcher must be a virtual object`;
167
217
  if (watcher.onFulfilled) {
168
218
  assert.typeof(watcher.onFulfilled, 'function');
169
219
  }
@@ -1,10 +1,9 @@
1
1
  import test from 'ava';
2
- import '@endo/init/debug.js';
3
2
 
4
3
  import { Far } from '@endo/marshal';
4
+ import { kunser } from '@agoric/kmarshal';
5
5
  import { setupTestLiveslots } from './liveslots-helpers.js';
6
6
  import { vstr } from './util.js';
7
- import { kunser } from './kmarshal.js';
8
7
  import { parseVatSlot } from '../src/parseVatSlots.js';
9
8
 
10
9
  function buildRootObject(vatPowers, vatParameters, baggage) {
@@ -1,5 +1,4 @@
1
1
  import test from 'ava';
2
- import '@endo/init/debug.js';
3
2
 
4
3
  import { makeCache } from '../src/cache.js';
5
4