@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
|
@@ -1,56 +1,57 @@
|
|
|
1
|
-
|
|
1
|
+
// @ts-nocheck
|
|
2
2
|
import test from 'ava';
|
|
3
|
-
import '@endo/init/debug.js';
|
|
4
3
|
|
|
5
4
|
import { Far } from '@endo/marshal';
|
|
6
5
|
import { initEmpty } from '@agoric/store';
|
|
7
6
|
|
|
8
7
|
import engineGC from '../engine-gc.js';
|
|
9
|
-
import { makeGcAndFinalize } from '../gc-and-finalize.js';
|
|
8
|
+
import { makeGcAndFinalize, watchCollected } from '../gc-and-finalize.js';
|
|
10
9
|
import { makeFakeVirtualStuff } from '../../tools/fakeVirtualSupport.js';
|
|
11
10
|
|
|
12
|
-
function
|
|
13
|
-
const held = Far(
|
|
14
|
-
const
|
|
11
|
+
function makeStashKit(name = 'held') {
|
|
12
|
+
const held = Far(name);
|
|
13
|
+
const collected = watchCollected(held);
|
|
15
14
|
const ws = new WeakSet(); // note: real WeakSet, not vref-aware
|
|
16
15
|
ws.add(held);
|
|
17
16
|
function isHeld(obj) {
|
|
18
17
|
return ws.has(obj);
|
|
19
18
|
}
|
|
20
|
-
|
|
19
|
+
function isCollected() {
|
|
20
|
+
return collected.result;
|
|
21
|
+
}
|
|
22
|
+
return { held, isCollected, isHeld };
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
function prepareEphemeral(vom) {
|
|
24
|
-
const
|
|
25
|
-
vom.registerEntry('o+12345',
|
|
26
|
-
|
|
27
|
-
return { wr };
|
|
26
|
+
const { held, isCollected } = makeStashKit('ephemeral');
|
|
27
|
+
vom.registerEntry('o+12345', held);
|
|
28
|
+
return { isCollected };
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
function stashRemotableOne(weakStore, key1) {
|
|
31
|
-
const { held,
|
|
32
|
+
const { held, isCollected, isHeld } = makeStashKit();
|
|
32
33
|
weakStore.init(key1, held);
|
|
33
|
-
return {
|
|
34
|
+
return { isCollected, isHeld };
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
function stashRemotableTwo(weakStore, key1) {
|
|
37
|
-
const { held,
|
|
38
|
+
const { held, isCollected, isHeld } = makeStashKit();
|
|
38
39
|
weakStore.init(key1, 'initial');
|
|
39
40
|
weakStore.set(key1, held);
|
|
40
|
-
return {
|
|
41
|
+
return { isCollected, isHeld };
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
function stashRemotableThree(holderMaker) {
|
|
44
|
-
const { held,
|
|
45
|
+
const { held, isCollected, isHeld } = makeStashKit();
|
|
45
46
|
const holder = holderMaker(held);
|
|
46
|
-
return {
|
|
47
|
+
return { isCollected, isHeld, holder };
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
function stashRemotableFour(holderMaker) {
|
|
50
|
-
const { held,
|
|
51
|
+
const { held, isCollected, isHeld } = makeStashKit();
|
|
51
52
|
const holder = holderMaker('initial');
|
|
52
53
|
holder.setHeld(held);
|
|
53
|
-
return {
|
|
54
|
+
return { isCollected, isHeld, holder };
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
test('remotables retained by virtualized data', async t => {
|
|
@@ -74,7 +75,7 @@ test('remotables retained by virtualized data', async t => {
|
|
|
74
75
|
// false positive in the subsequent test
|
|
75
76
|
const stash0 = prepareEphemeral(vom);
|
|
76
77
|
await gcAndFinalize();
|
|
77
|
-
t.
|
|
78
|
+
t.true(stash0.isCollected(), `caution: fake VOM didn't release Remotable`);
|
|
78
79
|
|
|
79
80
|
// stash a Remotable in the value of a weakStore
|
|
80
81
|
const key1 = makeKey();
|
|
@@ -84,14 +85,14 @@ test('remotables retained by virtualized data', async t => {
|
|
|
84
85
|
// Representatives or Presences, so the value is not holding a strong
|
|
85
86
|
// reference to the Remotable. The VOM is supposed to keep it alive, via
|
|
86
87
|
// reachableRemotables.
|
|
87
|
-
t.
|
|
88
|
+
t.false(stash1.isCollected());
|
|
88
89
|
t.truthy(stash1.isHeld(weakStore.get(key1)));
|
|
89
90
|
|
|
90
91
|
// do the same, but exercise weakStore.set instead of .init
|
|
91
92
|
const key2 = makeKey();
|
|
92
93
|
const stash2 = stashRemotableTwo(weakStore, key2);
|
|
93
94
|
await gcAndFinalize();
|
|
94
|
-
t.
|
|
95
|
+
t.false(stash2.isCollected());
|
|
95
96
|
t.truthy(stash2.isHeld(weakStore.get(key2)));
|
|
96
97
|
|
|
97
98
|
// now stash a Remotable in the state of a virtual object during init()
|
|
@@ -100,12 +101,12 @@ test('remotables retained by virtualized data', async t => {
|
|
|
100
101
|
// Each state property is virtualized upon write (via the generated
|
|
101
102
|
// setters). So again we rely on the VOM to keep the Remotable alive in
|
|
102
103
|
// case someone retrieves it again.
|
|
103
|
-
t.
|
|
104
|
+
t.false(stash3.isCollected());
|
|
104
105
|
t.truthy(stash3.isHeld(stash3.holder.getHeld()));
|
|
105
106
|
|
|
106
107
|
// same, but stash after init()
|
|
107
108
|
const stash4 = stashRemotableFour(makeHolder);
|
|
108
109
|
await gcAndFinalize();
|
|
109
|
-
t.
|
|
110
|
+
t.false(stash4.isCollected());
|
|
110
111
|
t.truthy(stash4.isHeld(stash4.holder.getHeld()));
|
|
111
112
|
});
|
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
import test from 'ava';
|
|
3
|
+
|
|
4
|
+
import { Fail } from '@endo/errors';
|
|
5
|
+
import { Far } from '@endo/marshal';
|
|
6
|
+
import { kser, kslot, kunser } from '@agoric/kmarshal';
|
|
7
|
+
import { M } from '@agoric/store';
|
|
8
|
+
import { makeLiveSlots } from '../../src/liveslots.js';
|
|
9
|
+
import { buildSyscall } from '../liveslots-helpers.js';
|
|
10
|
+
import { makeStartVat, makeMessage } from '../util.js';
|
|
11
|
+
import { makeMockGC } from '../mock-gc.js';
|
|
12
|
+
import { makeFakeVirtualStuff } from '../../tools/fakeVirtualSupport.js';
|
|
13
|
+
|
|
14
|
+
/** @import {VatOneResolution} from '../../src/types.js'; */
|
|
15
|
+
/** @import {VatData} from '../../src/vatDataTypes.js'; */
|
|
16
|
+
|
|
17
|
+
let lastPnum = 100;
|
|
18
|
+
/**
|
|
19
|
+
* Send a "message" syscall into a liveslots instance and return the
|
|
20
|
+
* unserialized result, or an error if the result is unsettled or rejected.
|
|
21
|
+
*
|
|
22
|
+
* @param {ReturnType<typeof makeLiveSlots>} ls
|
|
23
|
+
* @param {ReturnType<typeof buildSyscall>['log']} syscallLog
|
|
24
|
+
* @param {[target: unknown, method: string | symbol, args?: unknown[]]} message
|
|
25
|
+
*/
|
|
26
|
+
const dispatchForResult = async (ls, syscallLog, message) => {
|
|
27
|
+
const oldSyscallCount = syscallLog.length;
|
|
28
|
+
lastPnum += 1;
|
|
29
|
+
const resultVpid = `p-${lastPnum}`;
|
|
30
|
+
const vdo = makeMessage(...message.concat(undefined).slice(0, 3), resultVpid);
|
|
31
|
+
await ls.dispatch(vdo);
|
|
32
|
+
const newSyscalls = syscallLog.slice(oldSyscallCount);
|
|
33
|
+
/** @type {VatOneResolution[]} */
|
|
34
|
+
const newResolutions = newSyscalls.flatMap(vso =>
|
|
35
|
+
vso.type === 'resolve' ? vso.resolutions : [],
|
|
36
|
+
);
|
|
37
|
+
const [_vpid, isRejection, capdata] =
|
|
38
|
+
newResolutions.find(resolution => resolution[0] === resultVpid) ||
|
|
39
|
+
Fail`unsettled by syscalls ${syscallLog.slice(oldSyscallCount)}`;
|
|
40
|
+
if (isRejection) throw Error('rejected', { cause: kunser(capdata) });
|
|
41
|
+
return kunser(capdata);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const eph1 = Far('ephemeral1');
|
|
45
|
+
const eph2 = Far('ephemeral2');
|
|
46
|
+
|
|
47
|
+
const initHolder = fields => ({ ...fields });
|
|
48
|
+
const holderMethods = {
|
|
49
|
+
get: ({ state }, ...fields) =>
|
|
50
|
+
// We require fields to be explicit because they are currently defined on
|
|
51
|
+
// the state *prototype*.
|
|
52
|
+
Object.fromEntries(
|
|
53
|
+
fields.flatMap(key => (key in state ? [[key, state[key]]] : [])),
|
|
54
|
+
),
|
|
55
|
+
set: ({ state }, fields) => {
|
|
56
|
+
Object.assign(state, fields);
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Define a virtual or durable kind for getting and setting state constrained
|
|
61
|
+
* by the provided shape.
|
|
62
|
+
*
|
|
63
|
+
* @template {VatData.defineKind | VatData.defineDurableKind} D
|
|
64
|
+
* @param {D} defineKind
|
|
65
|
+
* @param {Parameters<D>[0]} kindIdentifier
|
|
66
|
+
* @param {import('@endo/patterns').Pattern} stateShape
|
|
67
|
+
*/
|
|
68
|
+
const defineHolder = (defineKind, kindIdentifier, stateShape) =>
|
|
69
|
+
defineKind(kindIdentifier, initHolder, holderMethods, { stateShape });
|
|
70
|
+
|
|
71
|
+
// virtual/durable Kinds can specify a 'stateShape', which should be
|
|
72
|
+
// enforced at both initialization and subsequent state changes
|
|
73
|
+
const testStateShape = test.macro((t, valueShape, config) => {
|
|
74
|
+
const { goodValues, badValues, throwsExpectation } = config;
|
|
75
|
+
const { vom } = makeFakeVirtualStuff();
|
|
76
|
+
const { defineKind } = vom;
|
|
77
|
+
const makeHolder = defineHolder(defineKind, 'kindTag', { value: valueShape });
|
|
78
|
+
const instance = goodValues.map(value => makeHolder({ value })).at(-1);
|
|
79
|
+
for (const value of goodValues) {
|
|
80
|
+
instance.set({ value });
|
|
81
|
+
}
|
|
82
|
+
for (const value of badValues || []) {
|
|
83
|
+
t.throws(() => makeHolder({ value }), throwsExpectation);
|
|
84
|
+
t.throws(() => instance.set({ value }), throwsExpectation);
|
|
85
|
+
}
|
|
86
|
+
t.pass();
|
|
87
|
+
});
|
|
88
|
+
test('constrain state shape - M.any()', testStateShape, M.any(), {
|
|
89
|
+
goodValues: [eph1, eph2, 1, 2, 'string'],
|
|
90
|
+
});
|
|
91
|
+
test('constrain state shape - M.number()', testStateShape, M.number(), {
|
|
92
|
+
goodValues: [1],
|
|
93
|
+
badValues: [eph1, 'string'],
|
|
94
|
+
throwsExpectation: { message: /Must be a number/ },
|
|
95
|
+
});
|
|
96
|
+
test('constrain state shape - M.string()', testStateShape, M.string(), {
|
|
97
|
+
goodValues: ['string'],
|
|
98
|
+
badValues: [eph1, 1, 2],
|
|
99
|
+
throwsExpectation: { message: /Must be a string/ },
|
|
100
|
+
});
|
|
101
|
+
test('constrain state shape - M.remotable()', testStateShape, M.remotable(), {
|
|
102
|
+
goodValues: [eph1, eph2],
|
|
103
|
+
badValues: ['string', 1, 2],
|
|
104
|
+
throwsExpectation: { message: /Must be a remotable/ },
|
|
105
|
+
});
|
|
106
|
+
test('constrain state shape - specific remotable', testStateShape, eph1, {
|
|
107
|
+
goodValues: [eph1],
|
|
108
|
+
badValues: ['string', 1, 2],
|
|
109
|
+
throwsExpectation: { message: /Must be:.*Alleged: ephemeral1/ },
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// durable Kinds serialize and store their stateShape, which must
|
|
113
|
+
// itself be durable
|
|
114
|
+
test('durable state shape', t => {
|
|
115
|
+
// note: relaxDurabilityRules defaults to true in fake tools
|
|
116
|
+
const { vom } = makeFakeVirtualStuff({ relaxDurabilityRules: false });
|
|
117
|
+
const { makeKindHandle, defineDurableKind } = vom;
|
|
118
|
+
|
|
119
|
+
let kindNumber = 0;
|
|
120
|
+
const defineNextKind = valueShape => {
|
|
121
|
+
kindNumber += 1;
|
|
122
|
+
const kh = makeKindHandle(`kind${kindNumber}`);
|
|
123
|
+
return defineHolder(defineDurableKind, kh, { value: valueShape });
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const makeKind1 = defineNextKind();
|
|
127
|
+
makeKind1({ value: undefined });
|
|
128
|
+
|
|
129
|
+
const makeKind2 = defineNextKind();
|
|
130
|
+
makeKind2();
|
|
131
|
+
|
|
132
|
+
const makeKind3 = defineNextKind(M.any());
|
|
133
|
+
const obj3 = makeKind3();
|
|
134
|
+
|
|
135
|
+
const makeKind4 = defineNextKind(M.string());
|
|
136
|
+
const obj4 = makeKind4({ value: 'string' });
|
|
137
|
+
|
|
138
|
+
const makeKind5 = defineNextKind(M.remotable());
|
|
139
|
+
const durableValueFail = { message: /value for "value" is not durable/ };
|
|
140
|
+
t.throws(() => makeKind5({ value: eph1 }), durableValueFail);
|
|
141
|
+
|
|
142
|
+
const durableShapeFail = { message: /stateShape.*is not durable: slot 0 of/ };
|
|
143
|
+
t.throws(() => defineNextKind(eph1), durableShapeFail);
|
|
144
|
+
|
|
145
|
+
const makeKind7 = defineNextKind(obj4); // obj4 is durable
|
|
146
|
+
makeKind7({ value: obj4 });
|
|
147
|
+
const specificRemotableFail = { message: /kind3.*Must be:.*kind4/ };
|
|
148
|
+
t.throws(() => makeKind7({ value: obj3 }), specificRemotableFail);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// durable Kinds maintain refcounts on their serialized stateShape
|
|
152
|
+
test('durable stateShape refcounts', async t => {
|
|
153
|
+
const kvStore = new Map();
|
|
154
|
+
const { syscall: sc1 } = buildSyscall({ kvStore });
|
|
155
|
+
const gcTools = makeMockGC();
|
|
156
|
+
|
|
157
|
+
const ls1 = makeLiveSlots(sc1, 'vatA', {}, {}, gcTools, undefined, () => {
|
|
158
|
+
const buildRootObject = (vatPowers, _vatParameters, baggage) => {
|
|
159
|
+
const { VatData } = vatPowers;
|
|
160
|
+
const { makeKindHandle, defineDurableKind } = VatData;
|
|
161
|
+
|
|
162
|
+
return Far('root', {
|
|
163
|
+
acceptRef: _ref => {}, // assigns a vref
|
|
164
|
+
defineDurableKind: valueShape => {
|
|
165
|
+
const kh = makeKindHandle('shaped');
|
|
166
|
+
baggage.init('kh', kh);
|
|
167
|
+
defineHolder(defineDurableKind, kh, { value: valueShape });
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
};
|
|
171
|
+
return { buildRootObject };
|
|
172
|
+
});
|
|
173
|
+
await ls1.dispatch(makeStartVat());
|
|
174
|
+
const root = 'o+0';
|
|
175
|
+
|
|
176
|
+
// accepting a ref but doing nothing with it does not increment its refcount
|
|
177
|
+
const vref1 = 'o-1';
|
|
178
|
+
await ls1.dispatch(makeMessage(root, 'acceptRef', [kslot(vref1)]));
|
|
179
|
+
t.is(ls1.testHooks.getReachableRefCount(vref1), 0);
|
|
180
|
+
|
|
181
|
+
// ...but using it in stateShape does
|
|
182
|
+
await ls1.dispatch(makeMessage(root, 'defineDurableKind', [kslot(vref1)]));
|
|
183
|
+
t.is(ls1.testHooks.getReachableRefCount(vref1), 1);
|
|
184
|
+
|
|
185
|
+
// ------
|
|
186
|
+
|
|
187
|
+
// Simulate upgrade by starting from the non-empty kvStore.
|
|
188
|
+
const clonedStore = new Map(kvStore);
|
|
189
|
+
const { syscall: sc2 } = buildSyscall({ kvStore: clonedStore });
|
|
190
|
+
|
|
191
|
+
// to test refcount increment/decrement, we need to override the
|
|
192
|
+
// usual rule that the new version must exactly match the original
|
|
193
|
+
// stateShape
|
|
194
|
+
const opts = { allowStateShapeChanges: true };
|
|
195
|
+
const ls2 = makeLiveSlots(sc2, 'vatA', {}, opts, gcTools, undefined, () => {
|
|
196
|
+
const buildRootObject = (vatPowers, vatParameters, baggage) => {
|
|
197
|
+
const { VatData } = vatPowers;
|
|
198
|
+
const { defineDurableKind } = VatData;
|
|
199
|
+
const { valueShape } = vatParameters;
|
|
200
|
+
const kh = baggage.get('kh');
|
|
201
|
+
defineHolder(defineDurableKind, kh, { value: valueShape });
|
|
202
|
+
|
|
203
|
+
return Far('root', {});
|
|
204
|
+
};
|
|
205
|
+
return { buildRootObject };
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// redefining the durable kind's stateShape to replace its vref1 reference
|
|
209
|
+
// with a vref2 reference will decrement the former refcount and increment the
|
|
210
|
+
// latter
|
|
211
|
+
const vref2 = 'o-2';
|
|
212
|
+
const vatParameters = { valueShape: kslot(vref2) };
|
|
213
|
+
await ls2.dispatch(makeStartVat(kser(vatParameters)));
|
|
214
|
+
t.is(ls2.testHooks.getReachableRefCount(vref1), 0);
|
|
215
|
+
t.is(ls2.testHooks.getReachableRefCount(vref2), 1);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
test('durable stateShape must match or extend', async t => {
|
|
219
|
+
const store1 = new Map();
|
|
220
|
+
const { syscall: sc1, log: log1 } = buildSyscall({ kvStore: store1 });
|
|
221
|
+
const gcTools = makeMockGC();
|
|
222
|
+
|
|
223
|
+
const fields = ['x', 'x2', 'y']; // but "x2" is not initially present
|
|
224
|
+
const ls1 = makeLiveSlots(sc1, 'vatA', {}, {}, gcTools, undefined, () => {
|
|
225
|
+
const buildRootObject = (vatPowers, _vatParameters, baggage) => {
|
|
226
|
+
const { VatData } = vatPowers;
|
|
227
|
+
const { makeKindHandle, defineDurableKind } = VatData;
|
|
228
|
+
|
|
229
|
+
return Far('root', {
|
|
230
|
+
makeShapedDurable: (x, y) => {
|
|
231
|
+
const kh = makeKindHandle('shaped');
|
|
232
|
+
baggage.init('kh', kh);
|
|
233
|
+
const makeInstance = defineHolder(defineDurableKind, kh, { x, y });
|
|
234
|
+
return makeInstance(harden({ x, y }));
|
|
235
|
+
},
|
|
236
|
+
});
|
|
237
|
+
};
|
|
238
|
+
return { buildRootObject };
|
|
239
|
+
});
|
|
240
|
+
await ls1.dispatch(makeStartVat());
|
|
241
|
+
const root = 'o+0';
|
|
242
|
+
|
|
243
|
+
const vref1 = 'o-1';
|
|
244
|
+
const vref2 = 'o-2';
|
|
245
|
+
const passables = new Map([
|
|
246
|
+
[vref1, kslot(vref1, vref1)],
|
|
247
|
+
[vref2, kslot(vref2, vref2)],
|
|
248
|
+
]);
|
|
249
|
+
const instance1 = await dispatchForResult(ls1, log1, [
|
|
250
|
+
root,
|
|
251
|
+
'makeShapedDurable',
|
|
252
|
+
[passables.get(vref1), passables.get(vref2)],
|
|
253
|
+
]);
|
|
254
|
+
const objRef = instance1.getKref();
|
|
255
|
+
const state1 = await dispatchForResult(ls1, log1, [objRef, 'get', fields]);
|
|
256
|
+
t.deepEqual(
|
|
257
|
+
kser(state1),
|
|
258
|
+
kser({ x: passables.get(vref1), y: passables.get(vref2) }),
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
// ------
|
|
262
|
+
|
|
263
|
+
// Simulate upgrade by starting from the non-empty kvStore.
|
|
264
|
+
const store2 = new Map(store1);
|
|
265
|
+
const { syscall: sc2, log: log2 } = buildSyscall({ kvStore: store2 });
|
|
266
|
+
|
|
267
|
+
// we do *not* override allowStateShapeChanges
|
|
268
|
+
// const opts = { allowStateShapeChanges: true };
|
|
269
|
+
const opts = undefined;
|
|
270
|
+
const stateShapeMismatch = { message: /durable Kind stateShape mismatch/ };
|
|
271
|
+
const ls2 = makeLiveSlots(sc2, 'vatA', {}, opts, gcTools, undefined, () => {
|
|
272
|
+
const buildRootObject = (vatPowers, vatParameters, baggage) => {
|
|
273
|
+
const { VatData } = vatPowers;
|
|
274
|
+
const { x, y } = vatParameters;
|
|
275
|
+
const kh = baggage.get('kh');
|
|
276
|
+
const redefineDurableKind = stateShape =>
|
|
277
|
+
defineHolder(VatData.defineDurableKind, kh, stateShape);
|
|
278
|
+
// several shapes that are not compatible
|
|
279
|
+
const badShapes = [
|
|
280
|
+
{ x, y: M.any() },
|
|
281
|
+
{ x },
|
|
282
|
+
{ x, y, z: M.string() },
|
|
283
|
+
{ x: M.or(x, M.string()), y },
|
|
284
|
+
{ x: y, y: x }, // wrong slots
|
|
285
|
+
];
|
|
286
|
+
for (const stateShape of badShapes) {
|
|
287
|
+
t.throws(() => redefineDurableKind(stateShape), stateShapeMismatch);
|
|
288
|
+
}
|
|
289
|
+
// the correct shape
|
|
290
|
+
redefineDurableKind({ x, y });
|
|
291
|
+
|
|
292
|
+
return Far('root', {});
|
|
293
|
+
};
|
|
294
|
+
return { buildRootObject };
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
const vatParameters2 = { x: passables.get(vref1), y: passables.get(vref2) };
|
|
298
|
+
await ls2.dispatch(makeStartVat(kser(vatParameters2)));
|
|
299
|
+
|
|
300
|
+
const state2 = await dispatchForResult(ls2, log2, [objRef, 'get', fields]);
|
|
301
|
+
t.deepEqual(kser(state2), kser(state1));
|
|
302
|
+
|
|
303
|
+
// ------
|
|
304
|
+
|
|
305
|
+
// Now upgrade again, first to add a new optional stateShape field x2 and then
|
|
306
|
+
// to preserve the new shape.
|
|
307
|
+
const store3 = new Map(store2);
|
|
308
|
+
const { syscall: sc3, log: log3 } = buildSyscall({ kvStore: store3 });
|
|
309
|
+
const ls3 = makeLiveSlots(sc3, 'vatA', {}, {}, gcTools, undefined, () => {
|
|
310
|
+
const buildRootObject = ({ VatData }, vatParameters, baggage) => {
|
|
311
|
+
const { x, y } = vatParameters;
|
|
312
|
+
const kh = baggage.get('kh');
|
|
313
|
+
const redefineDurableKind = stateShape =>
|
|
314
|
+
defineHolder(VatData.defineDurableKind, kh, stateShape);
|
|
315
|
+
const badShapes = [{ x, x2: x, y }];
|
|
316
|
+
for (const stateShape of badShapes) {
|
|
317
|
+
t.throws(() => redefineDurableKind(stateShape), stateShapeMismatch);
|
|
318
|
+
}
|
|
319
|
+
redefineDurableKind({ x, x2: M.or(M.undefined(), x), y });
|
|
320
|
+
return Far('root', {});
|
|
321
|
+
};
|
|
322
|
+
return { buildRootObject };
|
|
323
|
+
});
|
|
324
|
+
const vatParameters3 = { x: passables.get(vref1), y: passables.get(vref2) };
|
|
325
|
+
await ls3.dispatch(makeStartVat(kser(vatParameters3)));
|
|
326
|
+
|
|
327
|
+
const state3 = await dispatchForResult(ls3, log3, [objRef, 'get', fields]);
|
|
328
|
+
t.deepEqual(kser(state3), kser({ ...state1, x2: undefined }));
|
|
329
|
+
const makeVerificationCase = (x, x2, y, throwsMessage) => [
|
|
330
|
+
`{ x: ${x}, x2: ${x2}, y: ${y} }`,
|
|
331
|
+
{ x: passables.get(x), x2: passables.get(x2), y: passables.get(y) },
|
|
332
|
+
throwsMessage,
|
|
333
|
+
];
|
|
334
|
+
const verifications = [
|
|
335
|
+
makeVerificationCase(vref1, vref2, vref2, 'rejected'),
|
|
336
|
+
makeVerificationCase(vref1, vref1, vref2),
|
|
337
|
+
makeVerificationCase(vref1, undefined, vref2),
|
|
338
|
+
];
|
|
339
|
+
let lastState = state3;
|
|
340
|
+
for (const [label, value, message] of verifications) {
|
|
341
|
+
const tryUpdate = () =>
|
|
342
|
+
dispatchForResult(ls3, log3, [objRef, 'set', [value]]);
|
|
343
|
+
await (message
|
|
344
|
+
? t.throwsAsync(tryUpdate, { message }, `${label} ${message}`)
|
|
345
|
+
: tryUpdate());
|
|
346
|
+
const state = await dispatchForResult(ls3, log3, [objRef, 'get', fields]);
|
|
347
|
+
const expectState = message ? lastState : value;
|
|
348
|
+
t.deepEqual(kser(state), kser(expectState), `${label}`);
|
|
349
|
+
lastState = state;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const store4 = new Map(store3);
|
|
353
|
+
const { syscall: sc4, log: log4 } = buildSyscall({ kvStore: store4 });
|
|
354
|
+
const ls4 = makeLiveSlots(sc4, 'vatA', {}, {}, gcTools, undefined, () => {
|
|
355
|
+
const buildRootObject = ({ VatData }, vatParameters, baggage) => {
|
|
356
|
+
const { x, y } = vatParameters;
|
|
357
|
+
const kh = baggage.get('kh');
|
|
358
|
+
const redefineDurableKind = stateShape =>
|
|
359
|
+
defineHolder(VatData.defineDurableKind, kh, stateShape);
|
|
360
|
+
const badShapes = [
|
|
361
|
+
// Removing optionality from x2.
|
|
362
|
+
{ x, x2: x, y },
|
|
363
|
+
// Equivalent but not *equal* to M.or(M.undefined(), x).
|
|
364
|
+
{ x, x2: M.or(undefined, x), y },
|
|
365
|
+
{ x, x2: M.or(x, M.undefined()), y },
|
|
366
|
+
];
|
|
367
|
+
for (const stateShape of badShapes) {
|
|
368
|
+
t.throws(() => redefineDurableKind(stateShape), stateShapeMismatch);
|
|
369
|
+
}
|
|
370
|
+
redefineDurableKind({ x, x2: M.or(M.undefined(), x), y });
|
|
371
|
+
return Far('root', {});
|
|
372
|
+
};
|
|
373
|
+
return { buildRootObject };
|
|
374
|
+
});
|
|
375
|
+
const vatParameters4 = { x: passables.get(vref1), y: passables.get(vref2) };
|
|
376
|
+
await ls4.dispatch(makeStartVat(kser(vatParameters4)));
|
|
377
|
+
|
|
378
|
+
for (const [label, value, message] of verifications) {
|
|
379
|
+
const tryUpdate = () =>
|
|
380
|
+
dispatchForResult(ls4, log4, [objRef, 'set', [value]]);
|
|
381
|
+
await (message
|
|
382
|
+
? t.throwsAsync(tryUpdate, { message }, `${label} ${message}`)
|
|
383
|
+
: tryUpdate());
|
|
384
|
+
const state = await dispatchForResult(ls4, log4, [objRef, 'get', fields]);
|
|
385
|
+
const expectState = message ? lastState : value;
|
|
386
|
+
t.deepEqual(kser(state), kser(expectState), `${label}`);
|
|
387
|
+
lastState = state;
|
|
388
|
+
}
|
|
389
|
+
});
|