@agoric/swingset-liveslots 0.10.3-other-dev-8f8782b.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.
- package/README.md +2 -0
- package/package.json +34 -26
- package/src/boyd-gc.d.ts +12 -0
- package/src/boyd-gc.d.ts.map +1 -0
- package/src/boyd-gc.js +598 -0
- package/src/cache.d.ts +71 -0
- package/src/cache.d.ts.map +1 -0
- package/src/cache.js +3 -2
- package/src/capdata.d.ts +16 -0
- package/src/capdata.d.ts.map +1 -0
- package/src/capdata.js +17 -10
- package/src/collectionManager.d.ts +47 -0
- package/src/collectionManager.d.ts.map +1 -0
- package/src/collectionManager.js +220 -103
- package/src/facetiousness.d.ts +25 -0
- package/src/facetiousness.d.ts.map +1 -0
- package/src/facetiousness.js +1 -1
- package/src/index.d.ts +4 -0
- package/src/index.d.ts.map +1 -0
- package/src/index.js +4 -2
- package/src/kdebug.d.ts +7 -0
- package/src/kdebug.d.ts.map +1 -0
- package/src/liveslots.d.ts +42 -0
- package/src/liveslots.d.ts.map +1 -0
- package/src/liveslots.js +137 -305
- package/src/message.d.ts +49 -0
- package/src/message.d.ts.map +1 -0
- package/src/message.js +9 -5
- package/src/parseVatSlots.d.ts +125 -0
- package/src/parseVatSlots.d.ts.map +1 -0
- package/src/parseVatSlots.js +1 -1
- package/src/types-index.d.ts +4 -0
- package/src/types-index.js +2 -0
- package/src/types.d.ts +81 -0
- package/src/types.d.ts.map +1 -0
- package/src/types.js +14 -7
- package/src/vatDataTypes.d.ts +170 -0
- package/src/vatDataTypes.d.ts.map +1 -0
- package/src/vatDataTypes.ts +272 -0
- package/src/vatstore-iterators.d.ts +4 -0
- package/src/vatstore-iterators.d.ts.map +1 -0
- package/src/vatstore-iterators.js +2 -0
- package/src/vatstore-usage.md +198 -0
- package/src/virtualObjectManager.d.ts +44 -0
- package/src/virtualObjectManager.d.ts.map +1 -0
- package/src/virtualObjectManager.js +254 -84
- package/src/virtualReferences.d.ts +61 -0
- package/src/virtualReferences.d.ts.map +1 -0
- package/src/virtualReferences.js +135 -26
- package/src/vpid-tracking.md +92 -0
- package/src/watchedPromises.d.ts +31 -0
- package/src/watchedPromises.d.ts.map +1 -0
- package/src/watchedPromises.js +81 -24
- package/test/{test-baggage.js → baggage.test.js} +1 -2
- package/test/{test-cache.js → cache.test.js} +0 -1
- package/test/clear-collection.test.js +586 -0
- package/test/{test-collection-schema-refcount.js → collection-schema-refcount.test.js} +1 -2
- package/test/{test-collection-upgrade.js → collection-upgrade.test.js} +1 -3
- package/test/{test-collections.js → collections.test.js} +183 -18
- package/test/{test-dropped-collection-weakrefs.js → dropped-collection-weakrefs.test.js} +1 -2
- package/test/dropped-weakset-9939.test.js +80 -0
- package/test/dummyMeterControl.d.ts +2 -0
- package/test/dummyMeterControl.d.ts.map +1 -0
- package/test/dummyMeterControl.js +1 -1
- package/test/{test-durabilityChecks.js → durabilityChecks.test.js} +4 -4
- package/test/engine-gc.d.ts +3 -0
- package/test/engine-gc.d.ts.map +1 -0
- package/test/exo-utils.js +70 -0
- package/test/{test-facetiousness.js → facetiousness.test.js} +1 -2
- package/test/gc-and-finalize.d.ts +5 -0
- package/test/gc-and-finalize.d.ts.map +1 -0
- package/test/gc-and-finalize.js +30 -1
- package/test/gc-before-finalizer.test.js +230 -0
- package/test/gc-helpers.js +4 -5
- package/test/{test-gc-sensitivity.js → gc-sensitivity.test.js} +2 -2
- package/test/handled-promises.test.js +872 -0
- package/test/{test-initial-vrefs.js → initial-vrefs.test.js} +13 -20
- package/test/liveslots-helpers.d.ts +64 -0
- package/test/liveslots-helpers.d.ts.map +1 -0
- package/test/liveslots-helpers.js +13 -7
- package/test/{test-liveslots-mock-gc.js → liveslots-mock-gc.test.js} +101 -2
- package/test/{test-liveslots-real-gc.js → liveslots-real-gc.test.js} +73 -46
- package/test/{test-liveslots.js → liveslots.test.js} +17 -18
- package/test/mock-gc.js +1 -0
- package/test/storeGC/{test-lifecycle.js → lifecycle.test.js} +15 -14
- package/test/storeGC/{test-refcount-management.js → refcount-management.test.js} +1 -2
- package/test/storeGC/{test-scalar-store-kind.js → scalar-store-kind.test.js} +0 -1
- package/test/storeGC/{test-weak-key.js → weak-key.test.js} +1 -2
- package/test/strict-test-env-upgrade.test.js +94 -0
- package/test/util.d.ts +25 -0
- package/test/util.d.ts.map +1 -0
- package/test/util.js +4 -4
- package/test/vat-environment.test.js +65 -0
- package/test/vat-util.d.ts +9 -0
- package/test/vat-util.d.ts.map +1 -0
- package/test/vat-util.js +2 -2
- package/test/virtual-objects/{test-cease-recognition.js → cease-recognition.test.js} +2 -2
- package/test/virtual-objects/{test-cross-facet.js → cross-facet.test.js} +5 -4
- package/test/virtual-objects/{test-empty-data.js → empty-data.test.js} +1 -2
- package/test/virtual-objects/{test-facets.js → facets.test.js} +1 -2
- package/test/virtual-objects/{test-kind-changes.js → kind-changes.test.js} +2 -2
- package/test/virtual-objects/{test-reachable-vrefs.js → reachable-vrefs.test.js} +2 -2
- package/test/virtual-objects/{test-rep-tostring.js → rep-tostring.test.js} +3 -5
- package/test/virtual-objects/{test-retain-remotable.js → retain-remotable.test.js} +25 -24
- package/test/virtual-objects/set-debug-label-instances.js +1 -1
- package/test/virtual-objects/state-shape.test.js +389 -0
- package/test/virtual-objects/{test-virtualObjectGC.js → virtualObjectGC.test.js} +39 -38
- package/test/virtual-objects/{test-virtualObjectManager.js → virtualObjectManager.test.js} +104 -8
- package/test/virtual-objects/{test-vo-real-gc.js → vo-real-gc.test.js} +8 -8
- package/test/virtual-objects/{test-weakcollections-vref-handling.js → weakcollections-vref-handling.test.js} +1 -2
- package/test/{test-vo-test-harness.js → vo-test-harness.test.js} +13 -10
- package/test/{test-vpid-liveslots.js → vpid-liveslots.test.js} +105 -5
- package/test/waitUntilQuiescent.d.ts +3 -0
- package/test/waitUntilQuiescent.d.ts.map +1 -0
- package/test/waitUntilQuiescent.js +2 -1
- package/test/weakset-dropped-remotable.test.js +50 -0
- package/tools/fakeCollectionManager.d.ts +14 -0
- package/tools/fakeCollectionManager.d.ts.map +1 -0
- package/tools/fakeCollectionManager.js +44 -0
- package/tools/fakeVirtualObjectManager.d.ts +32 -0
- package/tools/fakeVirtualObjectManager.d.ts.map +1 -0
- package/tools/fakeVirtualObjectManager.js +62 -0
- package/tools/fakeVirtualSupport.d.ts +278 -0
- package/tools/fakeVirtualSupport.d.ts.map +1 -0
- package/tools/fakeVirtualSupport.js +389 -0
- package/tools/prepare-strict-test-env.d.ts +37 -0
- package/tools/prepare-strict-test-env.d.ts.map +1 -0
- package/tools/prepare-strict-test-env.js +124 -0
- package/tools/prepare-test-env.d.ts +2 -0
- package/tools/prepare-test-env.d.ts.map +1 -0
- package/tools/prepare-test-env.js +13 -0
- package/tools/setup-vat-data.d.ts +9 -0
- package/tools/setup-vat-data.d.ts.map +1 -0
- package/tools/setup-vat-data.js +95 -0
- package/tools/vo-test-harness.d.ts +33 -0
- package/tools/vo-test-harness.d.ts.map +1 -0
- package/tools/vo-test-harness.js +164 -0
- package/CHANGELOG.md +0 -61
- package/test/kmarshal.js +0 -79
- package/test/test-handled-promises.js +0 -360
- package/test/virtual-objects/test-state-shape.js +0 -298
package/src/virtualReferences.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
/* eslint-disable
|
|
1
|
+
/* eslint-disable jsdoc/require-returns-type */
|
|
2
2
|
|
|
3
|
-
import { assert, Fail } from '@
|
|
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 (
|
|
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
|
|
501
|
-
*
|
|
502
|
-
*
|
|
503
|
-
*
|
|
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
|
-
*
|
|
506
|
-
*
|
|
507
|
-
*
|
|
508
|
-
*
|
|
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
|
-
*
|
|
516
|
-
*
|
|
517
|
-
*
|
|
518
|
-
*
|
|
519
|
-
*
|
|
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'
|
|
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'
|
|
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
|
-
|
|
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 (
|
|
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
|
}
|
|
@@ -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"}
|
package/src/watchedPromises.js
CHANGED
|
@@ -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 '@
|
|
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,21 +35,31 @@ export function makeWatchedPromiseManager({
|
|
|
28
35
|
const { makeScalarBigMapStore } = collectionManager;
|
|
29
36
|
const { defineDurableKind } = vom;
|
|
30
37
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
38
|
+
/**
|
|
39
|
+
* Track promises watched in `buildRootObject` so `loadWatchedPromiseTable`
|
|
40
|
+
* can differentiate them from promises watched in a previous incarnation.
|
|
41
|
+
*
|
|
42
|
+
* @type {Set<string> | null}
|
|
43
|
+
*/
|
|
44
|
+
let buildRootObjectWatchedPromiseRefs;
|
|
35
45
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
46
|
+
/**
|
|
47
|
+
* watched promises by vpid: each entry is an array of watches on the
|
|
48
|
+
* corresponding vpid; each of these is in turn an array of a watcher object
|
|
49
|
+
* and the arguments associated with it by `watchPromise`.
|
|
50
|
+
* @type {MapStore<string, PromiseWatcherTuple<unknown>[]>}
|
|
51
|
+
*/
|
|
39
52
|
let watchedPromiseTable;
|
|
40
53
|
|
|
41
|
-
|
|
54
|
+
/**
|
|
55
|
+
* defined promise watcher objects indexed by kindHandle
|
|
56
|
+
*
|
|
57
|
+
* @type {MapStore<import('./vatDataTypes.js').DurableKindHandle, import('./types.js').PromiseWatcher<unknown>>}
|
|
58
|
+
*/
|
|
42
59
|
let promiseWatcherByKindTable;
|
|
43
60
|
|
|
44
61
|
function preparePromiseWatcherTables() {
|
|
45
|
-
|
|
62
|
+
buildRootObjectWatchedPromiseRefs = new Set();
|
|
46
63
|
let watcherTableID = syscall.vatstoreGet('watcherTableID');
|
|
47
64
|
if (watcherTableID) {
|
|
48
65
|
promiseWatcherByKindTable = convertSlotToVal(watcherTableID);
|
|
@@ -73,15 +90,21 @@ export function makeWatchedPromiseManager({
|
|
|
73
90
|
}
|
|
74
91
|
|
|
75
92
|
/**
|
|
76
|
-
*
|
|
77
|
-
* @param {Promise<
|
|
93
|
+
* @template T
|
|
94
|
+
* @param {Promise<T>} p
|
|
78
95
|
* @param {string} vpid
|
|
96
|
+
* @returns {void}
|
|
79
97
|
*/
|
|
80
98
|
function pseudoThen(p, vpid) {
|
|
99
|
+
/**
|
|
100
|
+
*
|
|
101
|
+
* @param {T} value
|
|
102
|
+
* @param {boolean} wasFulfilled
|
|
103
|
+
*/
|
|
81
104
|
function settle(value, wasFulfilled) {
|
|
82
105
|
const watches = watchedPromiseTable.get(vpid);
|
|
83
106
|
watchedPromiseTable.delete(vpid);
|
|
84
|
-
|
|
107
|
+
buildRootObjectWatchedPromiseRefs?.delete(vpid);
|
|
85
108
|
for (const watch of watches) {
|
|
86
109
|
const [watcher, ...args] = watch;
|
|
87
110
|
void Promise.resolve().then(() => {
|
|
@@ -115,26 +138,46 @@ export function makeWatchedPromiseManager({
|
|
|
115
138
|
*/
|
|
116
139
|
function loadWatchedPromiseTable(revivePromise) {
|
|
117
140
|
for (const vpid of watchedPromiseTable.keys()) {
|
|
141
|
+
if (buildRootObjectWatchedPromiseRefs?.has(vpid)) {
|
|
142
|
+
// We're only interested in reconnecting the promises from the previous
|
|
143
|
+
// incarnation. Any promise watched during buildRootObject would have
|
|
144
|
+
// already created a registration.
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
118
147
|
const p = revivePromise(vpid);
|
|
119
|
-
promiseRegistrations.init(vpid, p);
|
|
120
148
|
pseudoThen(p, vpid);
|
|
121
149
|
}
|
|
150
|
+
buildRootObjectWatchedPromiseRefs = null;
|
|
122
151
|
}
|
|
123
152
|
|
|
153
|
+
/**
|
|
154
|
+
* @template V
|
|
155
|
+
* @template {any[]} A]
|
|
156
|
+
* @param {import('./vatDataTypes.js').DurableKindHandle} kindHandle
|
|
157
|
+
* @param {(value: V, ...args: A) => void} fulfillHandler
|
|
158
|
+
* @param {(reason: any, ...args: A) => void} rejectHandler
|
|
159
|
+
* @returns {import('./types.js').PromiseWatcher<V, A>}
|
|
160
|
+
*/
|
|
124
161
|
function providePromiseWatcher(
|
|
125
162
|
kindHandle,
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
163
|
+
// @ts-expect-error xxx rest params in typedef
|
|
164
|
+
fulfillHandler = _value => {
|
|
165
|
+
// It's fine to not pass the value through since promise watchers are not chainable
|
|
166
|
+
},
|
|
167
|
+
// @ts-expect-error xxx rest params in typedef
|
|
168
|
+
rejectHandler = reason => {
|
|
169
|
+
// Replicate the unhandled rejection that would have happened if the
|
|
170
|
+
// watcher had not implemented an `onRejected` method. See `settle` above
|
|
171
|
+
throw reason;
|
|
129
172
|
},
|
|
130
173
|
) {
|
|
131
174
|
assert.typeof(fulfillHandler, 'function');
|
|
132
175
|
assert.typeof(rejectHandler, 'function');
|
|
133
176
|
|
|
134
177
|
const makeWatcher = defineDurableKind(kindHandle, initEmpty, {
|
|
135
|
-
|
|
178
|
+
/** @type {(context: unknown, res: V, ...args: A) => void} */
|
|
136
179
|
onFulfilled: (_context, res, ...args) => fulfillHandler(res, ...args),
|
|
137
|
-
|
|
180
|
+
/** @type {(context: unknown, rej: unknown, ...args: A) => void} */
|
|
138
181
|
onRejected: (_context, rej, ...args) => rejectHandler(rej, ...args),
|
|
139
182
|
});
|
|
140
183
|
|
|
@@ -147,6 +190,9 @@ export function makeWatchedPromiseManager({
|
|
|
147
190
|
}
|
|
148
191
|
}
|
|
149
192
|
|
|
193
|
+
/**
|
|
194
|
+
* @type {<P extends Promise<any>, A extends any[]>(p: P, watcher: import('./types.js').PromiseWatcher<Awaited<P>, A>, ...args: A) => void}
|
|
195
|
+
*/
|
|
150
196
|
function watchPromise(p, watcher, ...args) {
|
|
151
197
|
// The following wrapping defers setting up the promise watcher itself to a
|
|
152
198
|
// later turn so that if the promise to be watched was the return value from
|
|
@@ -163,7 +209,10 @@ export function makeWatchedPromiseManager({
|
|
|
163
209
|
const watcherVref = convertValToSlot(watcher);
|
|
164
210
|
assert(watcherVref, 'invalid watcher');
|
|
165
211
|
const { virtual, durable } = parseVatSlot(watcherVref);
|
|
166
|
-
|
|
212
|
+
virtual ||
|
|
213
|
+
durable ||
|
|
214
|
+
// separate line so easy to breakpoint on
|
|
215
|
+
Fail`promise watcher must be a virtual object`;
|
|
167
216
|
if (watcher.onFulfilled) {
|
|
168
217
|
assert.typeof(watcher.onFulfilled, 'function');
|
|
169
218
|
}
|
|
@@ -185,13 +234,21 @@ export function makeWatchedPromiseManager({
|
|
|
185
234
|
} else {
|
|
186
235
|
watchedPromiseTable.init(vpid, harden([[watcher, ...args]]));
|
|
187
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
|
+
|
|
188
248
|
// Ensure that this vat's promises are rejected at termination.
|
|
189
249
|
if (maybeExportPromise(vpid)) {
|
|
190
250
|
syscall.subscribe(vpid);
|
|
191
251
|
}
|
|
192
|
-
|
|
193
|
-
promiseRegistrations.init(vpid, p);
|
|
194
|
-
pseudoThen(p, vpid);
|
|
195
252
|
}
|
|
196
253
|
});
|
|
197
254
|
}
|
|
@@ -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) {
|