@agoric/swingset-liveslots 0.10.3-other-dev-3eb1a1d.0 → 0.10.3-other-dev-fbe72e7.0.fbe72e7

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 (86) hide show
  1. package/package.json +26 -26
  2. package/src/boyd-gc.d.ts +12 -0
  3. package/src/boyd-gc.d.ts.map +1 -0
  4. package/src/cache.d.ts +71 -0
  5. package/src/cache.d.ts.map +1 -0
  6. package/src/capdata.d.ts +16 -0
  7. package/src/capdata.d.ts.map +1 -0
  8. package/src/capdata.js +16 -8
  9. package/src/collectionManager.d.ts +47 -0
  10. package/src/collectionManager.d.ts.map +1 -0
  11. package/src/collectionManager.js +1 -0
  12. package/src/facetiousness.d.ts +25 -0
  13. package/src/facetiousness.d.ts.map +1 -0
  14. package/src/index.d.ts +4 -0
  15. package/src/index.d.ts.map +1 -0
  16. package/src/kdebug.d.ts +7 -0
  17. package/src/kdebug.d.ts.map +1 -0
  18. package/src/liveslots.d.ts +42 -0
  19. package/src/liveslots.d.ts.map +1 -0
  20. package/src/liveslots.js +6 -4
  21. package/src/message.d.ts +49 -0
  22. package/src/message.d.ts.map +1 -0
  23. package/src/message.js +7 -3
  24. package/src/parseVatSlots.d.ts +125 -0
  25. package/src/parseVatSlots.d.ts.map +1 -0
  26. package/src/types.d.ts +81 -0
  27. package/src/types.d.ts.map +1 -0
  28. package/src/types.js +6 -5
  29. package/src/vatDataTypes.d.ts +170 -0
  30. package/src/vatDataTypes.d.ts.map +1 -0
  31. package/src/vatDataTypes.ts +272 -0
  32. package/src/vatstore-iterators.d.ts +4 -0
  33. package/src/vatstore-iterators.d.ts.map +1 -0
  34. package/src/vatstore-usage.md +198 -0
  35. package/src/virtualObjectManager.d.ts +44 -0
  36. package/src/virtualObjectManager.d.ts.map +1 -0
  37. package/src/virtualObjectManager.js +70 -14
  38. package/src/virtualReferences.d.ts +61 -0
  39. package/src/virtualReferences.d.ts.map +1 -0
  40. package/src/vpid-tracking.md +92 -0
  41. package/src/watchedPromises.d.ts +31 -0
  42. package/src/watchedPromises.d.ts.map +1 -0
  43. package/src/watchedPromises.js +19 -12
  44. package/test/collections.test.js +25 -4
  45. package/test/dummyMeterControl.d.ts +2 -0
  46. package/test/dummyMeterControl.d.ts.map +1 -0
  47. package/test/engine-gc.d.ts +3 -0
  48. package/test/engine-gc.d.ts.map +1 -0
  49. package/test/gc-and-finalize.d.ts +5 -0
  50. package/test/gc-and-finalize.d.ts.map +1 -0
  51. package/test/gc-helpers.js +2 -2
  52. package/test/handled-promises.test.js +529 -163
  53. package/test/initial-vrefs.test.js +12 -18
  54. package/test/liveslots-helpers.d.ts +64 -0
  55. package/test/liveslots-helpers.d.ts.map +1 -0
  56. package/test/liveslots-helpers.js +1 -0
  57. package/test/liveslots-real-gc.test.js +11 -9
  58. package/test/liveslots.test.js +3 -3
  59. package/test/storeGC/lifecycle.test.js +13 -12
  60. package/test/util.d.ts +25 -0
  61. package/test/util.d.ts.map +1 -0
  62. package/test/util.js +2 -2
  63. package/test/vat-util.d.ts +9 -0
  64. package/test/vat-util.d.ts.map +1 -0
  65. package/test/virtual-objects/state-shape.test.js +312 -221
  66. package/test/virtual-objects/virtualObjectGC.test.js +37 -36
  67. package/test/virtual-objects/virtualObjectManager.test.js +41 -63
  68. package/test/vo-test-harness.test.js +13 -9
  69. package/test/waitUntilQuiescent.d.ts +3 -0
  70. package/test/waitUntilQuiescent.d.ts.map +1 -0
  71. package/tools/fakeCollectionManager.d.ts +14 -0
  72. package/tools/fakeCollectionManager.d.ts.map +1 -0
  73. package/tools/fakeVirtualObjectManager.d.ts +32 -0
  74. package/tools/fakeVirtualObjectManager.d.ts.map +1 -0
  75. package/tools/fakeVirtualSupport.d.ts +278 -0
  76. package/tools/fakeVirtualSupport.d.ts.map +1 -0
  77. package/tools/prepare-strict-test-env.d.ts +37 -0
  78. package/tools/prepare-strict-test-env.d.ts.map +1 -0
  79. package/tools/prepare-test-env.d.ts +2 -0
  80. package/tools/prepare-test-env.d.ts.map +1 -0
  81. package/tools/setup-vat-data.d.ts +9 -0
  82. package/tools/setup-vat-data.d.ts.map +1 -0
  83. package/tools/setup-vat-data.js +0 -1
  84. package/tools/vo-test-harness.d.ts +33 -0
  85. package/tools/vo-test-harness.d.ts.map +1 -0
  86. package/tools/vo-test-harness.js +21 -0
@@ -7,6 +7,7 @@ import { assertPattern, mustMatch } from '@agoric/store';
7
7
  import { defendPrototype, defendPrototypeKit } from '@endo/exo/tools.js';
8
8
  import { Far, passStyleOf } from '@endo/marshal';
9
9
  import { Nat } from '@endo/nat';
10
+ import { kindOf } from '@endo/patterns';
10
11
  import { parseVatSlot, makeBaseRef } from './parseVatSlots.js';
11
12
  import { enumerateKeysWithPrefix } from './vatstore-iterators.js';
12
13
  import { makeCache } from './cache.js';
@@ -20,6 +21,8 @@ import {
20
21
  * @import {DefineKindOptions} from '@agoric/swingset-liveslots'
21
22
  * @import {ClassContextProvider, KitContextProvider} from '@endo/exo'
22
23
  * @import {ToCapData, FromCapData} from '@endo/marshal';
24
+ * @import {Pattern} from '@endo/patterns';
25
+ * @import {SwingSetCapData} from './types.js';
23
26
  */
24
27
 
25
28
  const {
@@ -241,12 +244,47 @@ const insistDurableCapdata = (vrm, what, capdata, valueFor) => {
241
244
  }
242
245
  };
243
246
 
244
- const insistSameCapData = (oldCD, newCD) => {
245
- // NOTE: this assumes both were marshalled with the same format
246
- // (e.g. smallcaps vs pre-smallcaps). To somewhat tolerate new
247
- // formats, we'd need to `serialize(unserialize(oldCD))`.
247
+ const isUndefinedPatt = patt =>
248
+ patt === undefined ||
249
+ (kindOf(patt) === 'match:kind' && patt.payload === 'undefined');
250
+
251
+ /**
252
+ * Assert that a new stateShape either matches the old, or only differs in the
253
+ * addition of new clearly-optional top-level fields as conveyed by an
254
+ * [Endo `M.or`]{@link https://endojs.github.io/endo/interfaces/_endo_patterns.PatternMatchers.html#or}
255
+ * Pattern with at least one `undefined` alternative.
256
+ *
257
+ * @param {SwingSetCapData} oldCD
258
+ * @param {SwingSetCapData} newCD
259
+ * @param {{ newShape: Record<string, Pattern>, serialize: ToCapData<string>, unserialize: FromCapData<string> }} powers
260
+ */
261
+ const insistCompatibleShapeCapData = (
262
+ oldCD,
263
+ newCD,
264
+ { newShape, serialize, unserialize },
265
+ ) => {
248
266
  if (oldCD.body !== newCD.body) {
249
- Fail`durable Kind stateShape mismatch (body)`;
267
+ // Allow introduction of any clearly-optional new field at top level.
268
+ const oldShape =
269
+ unserialize(oldCD) ||
270
+ Fail`durable Kind stateShape mismatch (no old shape)`;
271
+ passStyleOf(oldShape) === 'copyRecord' ||
272
+ Fail`durable Kind stateShape mismatch (invalid old shape)`;
273
+ assertPattern(oldShape);
274
+ for (const [name, oldPatt] of Object.entries(oldShape)) {
275
+ // Assert presence and CapData shape, but save slots for their own clause.
276
+ (Object.hasOwn(newShape, name) &&
277
+ serialize(newShape[name]).body === serialize(oldPatt).body) ||
278
+ Fail`durable Kind stateShape mismatch (body ${name})`;
279
+ }
280
+ for (const [name, newPatt] of Object.entries(newShape)) {
281
+ if (Object.hasOwn(oldShape, name)) continue;
282
+ kindOf(newPatt) === 'match:or' ||
283
+ Fail`durable Kind stateShape mismatch (body new field ${name})`;
284
+ // @ts-expect-error A "match:or" pattern has a `payload` array of Patterns
285
+ newPatt.payload.some(patt => isUndefinedPatt(patt)) ||
286
+ Fail`durable Kind stateShape mismatch (body ${name})`;
287
+ }
250
288
  }
251
289
  if (oldCD.slots.length !== newCD.slots.length) {
252
290
  Fail`durable Kind stateShape mismatch (slots.length)`;
@@ -532,7 +570,7 @@ export const makeVirtualObjectManager = (
532
570
  * tag: string,
533
571
  * unfaceted?: boolean,
534
572
  * facets?: string[],
535
- * stateShapeCapData?: import('./types.js').SwingSetCapData
573
+ * stateShapeCapData?: SwingSetCapData
536
574
  * }} DurableKindDescriptor
537
575
  */
538
576
 
@@ -803,7 +841,11 @@ export const makeVirtualObjectManager = (
803
841
 
804
842
  const oldStateShapeSlots = oldShapeCD ? oldShapeCD.slots : [];
805
843
  if (oldShapeCD && !allowStateShapeChanges) {
806
- insistSameCapData(oldShapeCD, newShapeCD);
844
+ insistCompatibleShapeCapData(oldShapeCD, newShapeCD, {
845
+ newShape: /** @type {Record<string, Pattern>} */ (stateShape),
846
+ serialize,
847
+ unserialize,
848
+ });
807
849
  }
808
850
  const newStateShapeSlots = newShapeCD.slots;
809
851
  vrm.updateReferenceCounts(oldStateShapeSlots, newStateShapeSlots);
@@ -859,9 +901,11 @@ export const makeVirtualObjectManager = (
859
901
  const baseRef = getBaseRef(this);
860
902
  const record = dataCache.get(baseRef);
861
903
  assert(record !== undefined);
862
- const { valueMap, capdatas } = record;
904
+ const { capdatas, valueMap } = record;
863
905
  if (!valueMap.has(prop)) {
864
- const value = harden(unserialize(capdatas[prop]));
906
+ const value = hasOwn(capdatas, prop)
907
+ ? harden(unserialize(capdatas[prop]))
908
+ : undefined;
865
909
  checkStatePropertyValue(value, prop);
866
910
  valueMap.set(prop, value);
867
911
  }
@@ -877,12 +921,19 @@ export const makeVirtualObjectManager = (
877
921
  }
878
922
  const record = dataCache.get(baseRef); // mutable
879
923
  assert(record !== undefined);
880
- const oldSlots = record.capdatas[prop].slots;
924
+ const { capdatas, valueMap } = record;
925
+ const oldSlots = hasOwn(capdatas, prop) ? capdatas[prop].slots : [];
881
926
  const newSlots = capdata.slots;
882
927
  vrm.updateReferenceCounts(oldSlots, newSlots);
883
- record.capdatas[prop] = capdata; // modify in place ..
884
- record.valueMap.set(prop, value);
885
- dataCache.set(baseRef, record); // .. but mark as dirty
928
+ // modify in place, but mark as dirty
929
+ defineProperty(capdatas, prop, {
930
+ value: capdata,
931
+ writable: true,
932
+ enumerable: true,
933
+ configurable: false,
934
+ });
935
+ valueMap.set(prop, value);
936
+ dataCache.set(baseRef, record);
886
937
  },
887
938
  enumerable: true,
888
939
  configurable: false,
@@ -1073,7 +1124,12 @@ export const makeVirtualObjectManager = (
1073
1124
  }
1074
1125
  // eslint-disable-next-line github/array-foreach
1075
1126
  valueCD.slots.forEach(vrm.addReachableVref);
1076
- capdatas[prop] = valueCD;
1127
+ defineProperty(capdatas, prop, {
1128
+ value: valueCD,
1129
+ writable: true,
1130
+ enumerable: true,
1131
+ configurable: false,
1132
+ });
1077
1133
  valueMap.set(prop, value);
1078
1134
  }
1079
1135
  // dataCache contents remain mutable: state setter modifies in-place
@@ -0,0 +1,61 @@
1
+ /**
2
+ * @param {*} syscall Vat's syscall object, used to access the vatstore operations.
3
+ * @param {(val: object) => string | undefined} getSlotForVal A function that returns the
4
+ * object ID (vref) for a given object, if any. their corresponding export
5
+ * IDs
6
+ * @param {(slot: string) => object} requiredValForSlot A function that
7
+ * converts an object ID (vref) to an object.
8
+ * @param {*} FinalizationRegistry Powerful JavaScript intrinsic normally denied
9
+ * by SES
10
+ * @param {*} WeakRef Powerful JavaScript intrinsic normally denied
11
+ * by SES
12
+ * @param {*} addToPossiblyDeadSet Function to record objects whose deaths
13
+ * should be reinvestigated
14
+ * @param {*} addToPossiblyRetiredSet Function to record dead objects whose
15
+ * retirement should be reinvestigated
16
+ * @param {boolean} relaxDurabilityRules True IFF the associated swingset is
17
+ * running with relaxed durability rules
18
+ */
19
+ export function makeVirtualReferenceManager(syscall: any, getSlotForVal: (val: object) => string | undefined, requiredValForSlot: (slot: string) => object, FinalizationRegistry: any, WeakRef: any, addToPossiblyDeadSet: any, addToPossiblyRetiredSet: any, relaxDurabilityRules: boolean): {
20
+ registerDroppedCollection: (target: any, descriptor: any) => void;
21
+ isDurable: (vref: string) => boolean;
22
+ isDurableKind: (kindID: string) => boolean;
23
+ registerKind: (kindID: string, reanimator?: (string: any, boolean: any) => object, deleter?: (string: any) => boolean, durable?: boolean) => void;
24
+ rememberFacetNames: (kindID: string, facetNames: string[] | null) => void;
25
+ getFacet: (kindID: any, facets: any, facetIndex: any) => any;
26
+ getFacetNames: (kindID: any) => any;
27
+ reanimate: (baseRef: string) => any;
28
+ addReachableVref: (vref: any) => void;
29
+ removeReachableVref: (vref: any) => boolean;
30
+ updateReferenceCounts: (beforeSlots: any, afterSlots: any) => void;
31
+ getReachablePromiseRefCount: (p: any) => number;
32
+ addRecognizableValue: (value: any, recognizer: string | (Map<string, any> | Set<string>), recognizerIsVirtual?: boolean) => void;
33
+ removeRecognizableVref: (vref: string, recognizer: string | (Map<string, any> | Set<string>), recognizerIsVirtual?: boolean) => void;
34
+ removeRecognizableValue: (value: any, recognizer: string | (Map<string, any> | Set<string>), recognizerIsVirtual?: boolean) => void;
35
+ vrefKey: (value: any) => string | undefined;
36
+ isPresenceReachable: (vref: string) => boolean;
37
+ isVrefRecognizable: (vref: any) => boolean;
38
+ setExportStatus: (vref: any, exportStatus: any) => void;
39
+ isVirtualObjectReachable: (baseRef: string) => boolean;
40
+ deleteVirtualObject: (baseRef: string) => [boolean, string[]];
41
+ ceaseRecognition: (vref: string) => boolean;
42
+ setDeleteCollectionEntry: (fn: any) => void;
43
+ getRetentionStats: () => {
44
+ remotableRefCounts: number;
45
+ vrefRecognizers: number;
46
+ kindInfoTable: number;
47
+ };
48
+ initializeIDCounters: () => void;
49
+ allocateNextID: (name: any) => number;
50
+ flushIDCounters: () => void;
51
+ testHooks: {
52
+ getReachableRefCount: (vref: any) => number | undefined;
53
+ countCollectionsForWeakKey: (vref: any) => number;
54
+ getDroppedCollectionRegistry: () => any;
55
+ remotableRefCounts: Map<any, number>;
56
+ vrefRecognizers: Map<string, Set<Map<string, any> | Set<string>>>;
57
+ kindInfoTable: Map<any, any>;
58
+ };
59
+ };
60
+ export type VirtualReferenceManager = ReturnType<typeof makeVirtualReferenceManager>;
61
+ //# sourceMappingURL=virtualReferences.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"virtualReferences.d.ts","sourceRoot":"","sources":["virtualReferences.js"],"names":[],"mappings":"AAUA;;;;;;;;;;;;;;;;;GAiBG;AACH,qDAjBW,GAAC,iBACD,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,sBAGnC,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,wBAExB,GAAC,WAED,GAAC,wBAED,GAAC,2BAED,GAAC,wBAED,OAAO;;sBAqQL,MAAM,KAEJ,OAAO;4BAfT,MAAM,KAEJ,OAAO;2BA9CT,MAAM,eACN,CAAC,MAAM,KAAA,EAAE,OAAO,KAAA,KAAK,MAAM,YAC3B,CAAC,MAAM,KAAA,KAAK,OAAO,YACnB,OAAO;iCASP,MAAM,cACN,MAAM,EAAE,GAAC,IAAI;;;yBA8Eb,MAAM;;;;;kCAmPN,GAAC,cACD,MAAM,mCAAW,wBACjB,OAAO;mCA2BP,MAAM,cACN,MAAM,mCAAW,wBACjB,OAAO;qCAgCP,GAAC,cACD,MAAM,mCAAW,wBACjB,OAAO;;gCAvIP,MAAM,KAEJ,OAAO;;;wCAzaT,MAAM,KAEJ,OAAO;mCAoBT,MAAM,KAEJ,CAAC,OAAO,EAAE,MAAM,EAAE,CAAC;6BAwiBrB,MAAM,KAEJ,OAAO;;;;;;;;;;;;;;;;;;EA+LrB;sCACa,UAAU,CAAC,OAAO,2BAA2B,CAAC"}
@@ -0,0 +1,92 @@
1
+ # Promise/VPID Management in Liveslots
2
+
3
+ Kernels and vats communicate about promises by referring to their VPIDs: vat(-centric) promise IDs. These are strings like `p+12` and `p-23`. Like VOIDs (object IDs), the plus/minus sign indicates which side of the boundary allocated the number (`p+12` and `o+12` are allocated by the vat, `p-13` and `o-13` are allocated by the kernel). But where the object ID sign also indicates which side "owns" the object (i.e. where the behavior lives), the promise ID sign is generally irrelevant.
4
+
5
+ Instead, we care about which side holds the resolution authority for a promise (referred to as being its **decider**). This is not indicated by the VPID sign, and in fact is not necessarily static. Liveslots does not currently have any mechanism to allow one promise to be forwarded to another, but if it acquires this some day, then the decider of a promise could shift from kernel to vat to kernel again before it finally gets resolved. And there *are* sequences that allow a vat to receive a reference to a promise in method arguments before receiving a message whose result promise uses that same VPID (e.g., `p1 = p2~.foo(); x~.bar(p1)`, then someone resolves `p2` to `x`. Consider also tildot-free code like `{ promise: p1, resolve: resolveP1 } = makePromiseKit(); p2 = E(p1).push('queued'); await E(observer).push(p2); resolveP1(observer);` with an observer whose `push` returns a prompt response). In such cases, the decider is initially the kernel but that authority is transferred to a vat later.
6
+
7
+ Each Promise starts out in the "unresolved" state, then later transitions irrevocably to the "resolved" state. Liveslots frequently (but not always) pays attention to this transition by calling `then` to attach fulfillment/rejection settlement callbacks. To handle resolution cycles, liveslots remembers the resolution of old promises in a `WeakMap` for as long as the Promise exists. Consequently, for liveslots' purposes, every Promise is either resolved (a callback has fired and liveslots remembers the settlement), or unresolved (liveslots has not yet seen a resolution that settles it).
8
+
9
+ There are roughly four ways that liveslots might become aware of a promise:
10
+
11
+ * serialization: a Promise instance is serialized, either for the arguments of an outbound `syscall.send` or `syscall.resolve`, the argument of `watchPromise()`, or to be stored into virtualized data (e.g. `bigMapStore.set(key, promise)`, or assignment to a property of a virtual object)
12
+ * creation for outbound result: liveslots allocates a VPID for the `result` of an outbound `syscall.send`, and creates a new Promise instance to give back to userspace
13
+ * deserialization: the arguments of an inbound `dispatch.deliver` or `dispatch.notify` are deserialized, and a new Promise instance is created
14
+ * inbound result: the kernel-allocated `result` VPID of an inbound `dispatch.deliver` is associated with the Promise we get back from `HandledPromise.applyMethod`
15
+
16
+ A Promise may be associated with a VPID even though the kernel does not know about it (i.e. the VPID is not in the kernel's c-list for that vat). This can occur when a Promise is stored into virtual data without also being sent to (or received from) the kernel, although note that every Promise associated with a durable promise watcher _is_ sent to the kernel so it can be rejected during vat upgrade. A Promise can also be resolved but still referenced in vdata and forgotten by the kernel (the kernel's knowledge is temporary; it retires VPIDs from c-lists upon `syscall.resolve` or `dispatch.notify` as appropriate). So a VPID might start out stored only in vdata, then get sent to the kernel, then get resolved, leaving it solely in vdata once more.
17
+
18
+ Each unresolved VPID has a decider: either the kernel or a vat. It can remain unresolved for arbitrarily long, but becomes resolved by the first of the following events:
19
+
20
+ * if liveslots learns about local resolution of the corresponding Promise by userspace, then liveslots will perform a `syscall.resolve()` (prompting 'notify' deliveries to other subscribed vats)
21
+ * if liveslots learns about resolution by inbound notify, then liveslots will unregister it as necessary and inform userspace of the resolution
22
+ * if the vat is terminated, the kernel internally rejects all remaining vat-decided KPIDs without involving the vat
23
+ * if the vat is upgraded, each of those terminate-associated rejections is followed by a 'notify' delivery to the new incarnation
24
+
25
+ Liveslots tracks promises in the following data structures:
26
+
27
+ * `slotToVal` / `valToSlot` : these manage *registration*, the mapping from VPID to Promise and vice versa. These also register objects (Presences, Remotables, and Representatives) to/from VOIDs, and device nodes.
28
+ * to support GC of objects, `slotToVal.get(vref)` is a WeakRef, and `valToSlot` is a WeakMap
29
+ * liveslots uses independent strong references to maintain object/promise lifetimes
30
+ * `exportedVPIDs`: a `Map<VPID, Promise>`: all Promises currently known to the kernel and decided by the vat
31
+ * `importedVPIDs`: a `Map<VPID, PromiseKit>`: all Promises currently known to the kernel and decided by the kernel
32
+ * `remotableRefCounts`: a `Map<Object|Promise, Number>`: all Promises (and Remotables) referenced by virtual data
33
+
34
+ The kernel's c-list for a vat contains all VPIDs in `exportedVPIDs` and `importedVPIDs`. The vat is the decider for `exportedVPIDs`, while the kernel is the decider for `importedVPIDs`. For every VPID in `exportedVPIDs`, we've used `then` on the Promise instance to arrange for a `syscall.resolve` when it settles (becomes fulfilled or rejected). For every VPID key of the `importedVPIDs` Map, the corresponding value is a `[resolve, reject]` "**pRec**", so one of the functions can be called during `dispatch.notify`. Every VPID in `slotToVal` is either in `exportedVPIDs` but not `importedVPIDs`, `importedVPIDs` but not `exportedVPIDs`, or neither.
35
+
36
+ If a VPID in `importedVPIDs` is resolved (by the kernel, via `dispatch.notify`), the VPID is removed from `importedVPIDs`. If a VPID in `exportedVPIDs` is resolved (by the vat, i.e. liveslots observes invocation of a previously-added settlement callback), liveslots invokes `syscall.resolve` and removes the VPID from `exportedVPIDs`. The c-list for a vat will not contain a VPID for any resolved promise.
37
+
38
+ The `slotToVal`/`valToSlot` registration must remain until all of the following are true:
39
+
40
+ * the kernel is no longer aware of the VPID
41
+ * the Promise is not present in any virtual data
42
+ * the promise is not being watched by a `promiseWatcher`.
43
+
44
+ If the registration were to be lost while any of the above conditions were still true, a replacement Promise might be created while the original was still around, causing confusion.
45
+
46
+ ## Maintaining Strong References
47
+
48
+ Remember that the `slotToVal` registration uses a WeakRef, so being registered there does not keep the Promise object alive.
49
+
50
+ `exportedVPIDs` and `importedVPIDs` keep their Promise alive in their value. vdata keeps it alive through the key of `remotableRefCounts`. `promiseWatcher` uses an internal `ScalarBigMapStore` to keep the Promise alive.
51
+
52
+ ## Promise/VPID Management Algorithm
53
+
54
+ * When a Promise is first serialized (it appears in `convertValToSlot`), a VPID is assigned and the VPID/Promise mapping is registered in `valToSlot`/`slotToVal`
55
+ * at this point, there is not yet a strong reference to the Promise
56
+ * When a VPID appears in the serialized arguments of `syscall.send` or `syscall.resolve`:
57
+ * if the VPID already exists in `exportedVPIDs` or `importedVPIDs`: do nothing
58
+ * else: use `followForKernel` to add the VPID to `exportedVPIDs` and attach `.then(onFulfill, onReject)` callbacks that will map fulfillment/rejection to `syscall.resolve()`
59
+ * When a `followForKernel` settlement callback is executed:
60
+ * do `syscall.resolve()`
61
+ * remove from `exportedVPIDs`
62
+ * if `remotableRefCounts` reports 0 references: unregister from `valToSlot`/`slotToVal`
63
+ * When the kernel delivers a `dispatch.notify`:
64
+ * retrieve the `[resolve, reject]` pRec from `importedVPIDs`
65
+ * invoke the appropriate function with the deserialized argument
66
+ * if `remotableRefCounts` reports 0 references: unregister from `valToSlot`/`slotToVal`
67
+ * When the vdata refcount for a VPID drops to zero:
68
+ * if the VPID still exists in `exportedVPIDs` or `importedVPIDs`: do nothing
69
+ * else: unregister from `valToSlot`/`slotToVal`
70
+ * When a new VPID is deserialized (it appears in `convertSlotToVal`), this must be the arguments of a delivery (not vdata)
71
+ * use `makePipelinablePromise` to create a HandledPromise for the VPID
72
+ * add the Promise and its `resolve`/`reject` pair to `importedVPIDs`
73
+ * register the Promise in `valToSlot`/`slotToVal`
74
+ * use `syscall.subscribe` to request a `dispatch.notify` delivery when the kernel resolves this promise
75
+ * When a VPID appears as the `result` of an outbound `syscall.send`: (_note overlap with the preceding_)
76
+ * use `allocateVPID` to allocate a new VPID
77
+ * use `makePipelinablePromise` to create a HandledPromise for the VPID
78
+ * add the Promise and its `resolve`/`reject` pair to `importedVPIDs`
79
+ * register the Promise in `valToSlot`/`slotToVal`
80
+ * use `syscall.subscribe` to request a `dispatch.notify` delivery when the kernel resolves this promise
81
+ * When a VPID appears as the `result` of an inbound `dispatch.deliver`, the vat is responsible for deciding it:
82
+ * construct a promise `res` to capture the userspace-provided result
83
+ * if the VPID is present in `importedVPIDs`: retrieve the `[resolve, reject]` pRec and use `resolve(res)` to forward eventual settlement of `res` to settlement of the previously-imported promise, then remove the VPID from `importedVPIDs`
84
+ * else: register marshaller association between the VPID and `res`
85
+ * in either case, use `followForKernel` to add the VPID to `exportedVPIDs` and attach `.then(onFulfill, onReject)` callbacks that will map fulfillment/rejection to `syscall.resolve()`
86
+
87
+
88
+ If the serialization is for storage in virtual data, the act of storing the VPID will add the Promise to `remotableRefCounts`, which maintains a strong reference for as long as the VPID is held. When it is removed from virtual data (or the object/collection is deleted), the refcount will be decremented. When the refcount drops to zero, we perform the `exportedVPIDs`/`importedVPIDs` check and then maybe unregister the promise.
89
+
90
+ If the serialization is for the arguments of an outbound `syscall.send` or `syscall.resolve` (or `syscall.callNow`, or `syscall.exit`), the VPID will be added to `exportedVPIDs`.
91
+
92
+ If the *un*serialization occurred when processing the arguments of an *in*bound `dispatch.deliver` or `dispatch.notify`, the VPID (and the "promise kit" trio of Promise, `resolve`, and `reject`) will be stored in `importedVPIDs`.
@@ -0,0 +1,31 @@
1
+ /**
2
+ * @template V
3
+ * @template {any[]} [A=unknown[]]
4
+ * @typedef {[watcher: import('./types.js').PromiseWatcher<V, A>, ...args: A]} PromiseWatcherTuple
5
+ */
6
+ /**
7
+ * @param {object} options
8
+ * @param {*} options.syscall
9
+ * @param {import('./virtualReferences.js').VirtualReferenceManager} options.vrm
10
+ * @param {import('./virtualObjectManager.js').VirtualObjectManager} options.vom
11
+ * @param {*} options.collectionManager
12
+ * @param {import('@endo/marshal').ConvertValToSlot<any>} options.convertValToSlot
13
+ * @param {import('@endo/marshal').ConvertSlotToVal<any>} options.convertSlotToVal
14
+ * @param {(vref: any) => boolean} options.maybeExportPromise
15
+ */
16
+ export function makeWatchedPromiseManager({ syscall, vrm, vom, collectionManager, convertValToSlot, convertSlotToVal, maybeExportPromise, }: {
17
+ syscall: any;
18
+ vrm: import("./virtualReferences.js").VirtualReferenceManager;
19
+ vom: import("./virtualObjectManager.js").VirtualObjectManager;
20
+ collectionManager: any;
21
+ convertValToSlot: import("@endo/marshal").ConvertValToSlot<any>;
22
+ convertSlotToVal: import("@endo/marshal").ConvertSlotToVal<any>;
23
+ maybeExportPromise: (vref: any) => boolean;
24
+ }): {
25
+ preparePromiseWatcherTables: () => void;
26
+ loadWatchedPromiseTable: (revivePromise: (vref: any) => Promise<any>) => void;
27
+ providePromiseWatcher: <V, A extends any[]>(kindHandle: import("./vatDataTypes.js").DurableKindHandle, fulfillHandler?: (value: V, ...args: A) => void, rejectHandler?: (reason: any, ...args: A) => void) => import("./types.js").PromiseWatcher<V, A>;
28
+ watchPromise: <P extends Promise<any>, A extends any[]>(p: P, watcher: import("./types.js").PromiseWatcher<Awaited<P>, A>, ...args: A) => void;
29
+ };
30
+ export type PromiseWatcherTuple<V, A extends any[] = unknown[]> = [watcher: import("./types.js").PromiseWatcher<V, A>, ...args: A];
31
+ //# sourceMappingURL=watchedPromises.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watchedPromises.d.ts","sourceRoot":"","sources":["watchedPromises.js"],"names":[],"mappings":"AASA;;;;GAIG;AAEH;;;;;;;;;GASG;AACH,6IARG;IAAmB,OAAO,EAAlB,GAAC;IACiE,GAAG,EAArE,OAAO,wBAAwB,EAAE,uBAAuB;IACU,GAAG,EAArE,OAAO,2BAA2B,EAAE,oBAAoB;IAC7C,iBAAiB,EAA5B,GAAC;IACsD,gBAAgB,EAAvE,OAAO,eAAe,EAAE,gBAAgB,CAAC,GAAG,CAAC;IACU,gBAAgB,EAAvE,OAAO,eAAe,EAAE,gBAAgB,CAAC,GAAG,CAAC;IACb,kBAAkB,EAAlD,CAAC,IAAI,EAAE,GAAG,KAAK,OAAO;CAChC;;6CA+GY,CAAC,IAAI,EAAE,GAAG,KAAK,OAAO,CAAC,GAAG,CAAC,KACzB,IAAI;4BAiBJ,CAAC,EACO,CAAC,SAAR,GAAG,EAAG,cACT,OAAO,mBAAmB,EAAE,iBAAiB,mBAC7C,CAAC,KAAK,EAAE,CAAC,EAAE,GAAG,IAAI,EAAE,CAAC,KAAK,IAAI,kBAC9B,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,IAAI,EAAE,CAAC,KAAK,IAAI,KAC/B,OAAO,YAAY,EAAE,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC;mBAmC3C,CAAC,SAAS,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,SAAS,GAAG,EAAE,KAAK,CAAC,WAAW,OAAO,YAAY,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,WAAW,CAAC,KAAK,IAAI;EAoE3I;gCA3PY,CAAC,EACQ,CAAC,SAAT,GAAG,EAAG,gBACP,CAAC,OAAO,EAAE,OAAO,YAAY,EAAE,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,CAAC,CAAC"}
@@ -36,13 +36,12 @@ export function makeWatchedPromiseManager({
36
36
  const { defineDurableKind } = vom;
37
37
 
38
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.
39
+ * Track promises watched in `buildRootObject` so `loadWatchedPromiseTable`
40
+ * can differentiate them from promises watched in a previous incarnation.
42
41
  *
43
- * @type {MapStore<string, Promise<unknown>>}
42
+ * @type {Set<string> | null}
44
43
  */
45
- let promiseRegistrations;
44
+ let buildRootObjectWatchedPromiseRefs;
46
45
 
47
46
  /**
48
47
  * watched promises by vpid: each entry is an array of watches on the
@@ -60,7 +59,7 @@ export function makeWatchedPromiseManager({
60
59
  let promiseWatcherByKindTable;
61
60
 
62
61
  function preparePromiseWatcherTables() {
63
- promiseRegistrations = makeScalarBigMapStore('promiseRegistrations');
62
+ buildRootObjectWatchedPromiseRefs = new Set();
64
63
  let watcherTableID = syscall.vatstoreGet('watcherTableID');
65
64
  if (watcherTableID) {
66
65
  promiseWatcherByKindTable = convertSlotToVal(watcherTableID);
@@ -105,7 +104,7 @@ export function makeWatchedPromiseManager({
105
104
  function settle(value, wasFulfilled) {
106
105
  const watches = watchedPromiseTable.get(vpid);
107
106
  watchedPromiseTable.delete(vpid);
108
- promiseRegistrations.delete(vpid);
107
+ buildRootObjectWatchedPromiseRefs?.delete(vpid);
109
108
  for (const watch of watches) {
110
109
  const [watcher, ...args] = watch;
111
110
  void Promise.resolve().then(() => {
@@ -139,16 +138,16 @@ export function makeWatchedPromiseManager({
139
138
  */
140
139
  function loadWatchedPromiseTable(revivePromise) {
141
140
  for (const vpid of watchedPromiseTable.keys()) {
142
- if (promiseRegistrations.has(vpid)) {
141
+ if (buildRootObjectWatchedPromiseRefs?.has(vpid)) {
143
142
  // We're only interested in reconnecting the promises from the previous
144
143
  // incarnation. Any promise watched during buildRootObject would have
145
144
  // already created a registration.
146
145
  continue;
147
146
  }
148
147
  const p = revivePromise(vpid);
149
- promiseRegistrations.init(vpid, p);
150
148
  pseudoThen(p, vpid);
151
149
  }
150
+ buildRootObjectWatchedPromiseRefs = null;
152
151
  }
153
152
 
154
153
  /**
@@ -235,13 +234,21 @@ export function makeWatchedPromiseManager({
235
234
  } else {
236
235
  watchedPromiseTable.init(vpid, harden([[watcher, ...args]]));
237
236
 
237
+ buildRootObjectWatchedPromiseRefs?.add(vpid);
238
+
239
+ // To avoid triggering
240
+ // https://github.com/Agoric/agoric-sdk/issues/10757 and
241
+ // preventing slotToVal cleanup, the `pseudoThen()` should
242
+ // precede `maybeExportPromise()`. This isn't foolproof, but
243
+ // does mitigate in advance of a proper fix. See #10756 for
244
+ // details of this particular mitigation, and #10757 for the
245
+ // deeper bug.
246
+ pseudoThen(p, vpid);
247
+
238
248
  // Ensure that this vat's promises are rejected at termination.
239
249
  if (maybeExportPromise(vpid)) {
240
250
  syscall.subscribe(vpid);
241
251
  }
242
-
243
- promiseRegistrations.init(vpid, p);
244
- pseudoThen(p, vpid);
245
252
  }
246
253
  });
247
254
  }
@@ -326,7 +326,7 @@ test('constrain map key shape', t => {
326
326
  t.throws(
327
327
  () => noStrings.init('foo', 'string not ok?'),
328
328
  m(
329
- 'invalid key type for collection "map key no strings": "foo" - Must fail negated pattern: "[match:string]"',
329
+ /^invalid key type for collection "map key no strings": "foo" - Must fail negated pattern: (.*)$/,
330
330
  ),
331
331
  );
332
332
  t.is(noStrings.get(47), 'number ok');
@@ -335,7 +335,14 @@ test('constrain map key shape', t => {
335
335
  t.throws(
336
336
  () => noStrings.get('foo'),
337
337
  m(
338
- 'invalid key type for collection "map key no strings": "foo" - Must fail negated pattern: "[match:string]"',
338
+ // TODO https://github.com/Agoric/agoric-sdk/issues/11605
339
+ // This is a golden error message test. We're currently in transition
340
+ // from having pattern error messages quote nested patterns using
341
+ // `q` to quoting them using `qp`. In order to tolerate both during
342
+ // this transition at reasonable cost, this golden error message
343
+ // pattern accepts anything in the position of the quoted nested
344
+ // pattern.
345
+ /^invalid key type for collection "map key no strings": "foo" - Must fail negated pattern: (.*)$/,
339
346
  ),
340
347
  );
341
348
 
@@ -397,7 +404,14 @@ test('constrain map value shape', t => {
397
404
  t.throws(
398
405
  () => noStrings.init('skey', 'string not ok?'),
399
406
  m(
400
- 'invalid value type for collection "map value no strings": "string not ok?" - Must fail negated pattern: "[match:string]"',
407
+ // TODO https://github.com/Agoric/agoric-sdk/issues/11605
408
+ // This is a golden error message test. We're currently in transition
409
+ // from having pattern error messages quote nested patterns using
410
+ // `q` to quoting them using `qp`. In order to tolerate both during
411
+ // this transition at reasonable cost, this golden error message
412
+ // pattern accepts anything in the position of the quoted nested
413
+ // pattern.
414
+ /^invalid value type for collection "map value no strings": "string not ok\?" - Must fail negated pattern: (.*)$/,
401
415
  ),
402
416
  );
403
417
  t.is(noStrings.get('nkey'), 47);
@@ -458,7 +472,14 @@ test('constrain set key shape', t => {
458
472
  t.throws(
459
473
  () => noStrings.add('foo?'),
460
474
  m(
461
- 'invalid key type for collection "no strings set": "foo?" - Must fail negated pattern: "[match:string]"',
475
+ // TODO https://github.com/Agoric/agoric-sdk/issues/11605
476
+ // This is a golden error message test. We're currently in transition
477
+ // from having pattern error messages quote nested patterns using
478
+ // `q` to quoting them using `qp`. In order to tolerate both during
479
+ // this transition at reasonable cost, this golden error message
480
+ // pattern accepts anything in the position of the quoted nested
481
+ // pattern.
482
+ /^invalid key type for collection "no strings set": "foo\?" - Must fail negated pattern: (.*)$/,
462
483
  ),
463
484
  );
464
485
  t.truthy(noStrings.has(47));
@@ -0,0 +1,2 @@
1
+ export function makeDummyMeterControl(): import("../src/types.js").MeterControl;
2
+ //# sourceMappingURL=dummyMeterControl.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dummyMeterControl.d.ts","sourceRoot":"","sources":["dummyMeterControl.js"],"names":[],"mappings":"AAEA,gFAmDC"}
@@ -0,0 +1,3 @@
1
+ export default engineGC;
2
+ declare const engineGC: GCFunction;
3
+ //# sourceMappingURL=engine-gc.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine-gc.d.ts","sourceRoot":"","sources":["engine-gc.js"],"names":[],"mappings":";AAoBA,mCAAwB"}
@@ -0,0 +1,5 @@
1
+ export function makeGcAndFinalize(gcPower: any): () => Promise<void>;
2
+ export function watchCollected(target: any): Promise<true> & {
3
+ result: boolean;
4
+ };
5
+ //# sourceMappingURL=gc-and-finalize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gc-and-finalize.d.ts","sourceRoot":"","sources":["gc-and-finalize.js"],"names":[],"mappings":"AAkEA,qEAwBC;AAwBD;YAbsD,OAAO;EAkB5D"}
@@ -9,8 +9,8 @@ import { parseVatSlot } from '../src/parseVatSlots.js';
9
9
  let aWeakMapStore;
10
10
  let aWeakSetStore;
11
11
 
12
- export const mainHolderIdx = 5;
13
- export const mainHeldIdx = 6;
12
+ export const mainHolderIdx = 4;
13
+ export const mainHeldIdx = 5;
14
14
 
15
15
  export function buildRootObject(vatPowers) {
16
16
  const { VatData } = vatPowers;