@agoric/swingset-liveslots 0.10.3-other-dev-1f26562.0 → 0.10.3-other-dev-3eb1a1d.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/package.json +27 -19
- package/src/boyd-gc.js +598 -0
- package/src/cache.js +3 -2
- package/src/capdata.js +1 -2
- package/src/collectionManager.js +219 -103
- package/src/facetiousness.js +1 -1
- package/src/index.js +4 -2
- package/src/liveslots.js +131 -301
- package/src/message.js +5 -5
- package/src/parseVatSlots.js +1 -1
- package/src/types-index.d.ts +4 -0
- package/src/types-index.js +2 -0
- package/src/types.js +8 -2
- package/src/vatstore-iterators.js +2 -0
- package/src/virtualObjectManager.js +185 -71
- package/src/virtualReferences.js +135 -26
- package/src/watchedPromises.js +67 -17
- 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} +158 -14
- 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.js +1 -1
- package/test/{test-durabilityChecks.js → durabilityChecks.test.js} +4 -4
- package/test/exo-utils.js +70 -0
- package/test/{test-facetiousness.js → facetiousness.test.js} +1 -2
- package/test/gc-and-finalize.js +30 -1
- package/test/gc-before-finalizer.test.js +230 -0
- package/test/gc-helpers.js +2 -3
- package/test/{test-gc-sensitivity.js → gc-sensitivity.test.js} +2 -2
- package/test/handled-promises.test.js +506 -0
- package/test/{test-initial-vrefs.js → initial-vrefs.test.js} +2 -3
- package/test/liveslots-helpers.js +12 -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} +62 -37
- package/test/{test-liveslots.js → liveslots.test.js} +14 -15
- package/test/mock-gc.js +1 -0
- package/test/storeGC/{test-lifecycle.js → lifecycle.test.js} +2 -2
- 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.js +2 -2
- package/test/vat-environment.test.js +65 -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/{test-state-shape.js → state-shape.test.js} +2 -2
- package/test/virtual-objects/{test-virtualObjectGC.js → virtualObjectGC.test.js} +2 -2
- package/test/virtual-objects/{test-virtualObjectManager.js → virtualObjectManager.test.js} +126 -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} +0 -1
- package/test/{test-vpid-liveslots.js → vpid-liveslots.test.js} +105 -5
- package/test/waitUntilQuiescent.js +2 -1
- package/test/weakset-dropped-remotable.test.js +50 -0
- package/tools/fakeCollectionManager.js +44 -0
- package/tools/fakeVirtualObjectManager.js +62 -0
- package/tools/fakeVirtualSupport.js +389 -0
- package/tools/prepare-strict-test-env.js +124 -0
- package/tools/prepare-test-env.js +13 -0
- package/tools/setup-vat-data.js +96 -0
- package/tools/vo-test-harness.js +143 -0
- package/CHANGELOG.md +0 -61
- package/test/kmarshal.js +0 -79
- package/test/test-handled-promises.js +0 -360
|
@@ -0,0 +1,506 @@
|
|
|
1
|
+
import test from 'ava';
|
|
2
|
+
|
|
3
|
+
import { Fail } from '@endo/errors';
|
|
4
|
+
import { Far } from '@endo/marshal';
|
|
5
|
+
import { M } from '@agoric/store';
|
|
6
|
+
import { makePromiseKit } from '@endo/promise-kit';
|
|
7
|
+
// Disabled to avoid circular dependencies.
|
|
8
|
+
// import { makeStoreUtils } from '@agoric/vat-data/src/vat-data-bindings.js';
|
|
9
|
+
// import { makeExoUtils } from '@agoric/vat-data/src/exo-utils.js';
|
|
10
|
+
import { kslot, kser } from '@agoric/kmarshal';
|
|
11
|
+
import { setupTestLiveslots } from './liveslots-helpers.js';
|
|
12
|
+
import { makeResolve, makeReject } from './util.js';
|
|
13
|
+
import { makeExoUtils } from './exo-utils.js';
|
|
14
|
+
|
|
15
|
+
// eslint-disable-next-line no-unused-vars
|
|
16
|
+
const compareEntriesByKey = ([ka], [kb]) => (ka < kb ? -1 : 1);
|
|
17
|
+
|
|
18
|
+
// cf. packages/SwingSet/test/vat-durable-promise-watcher.js
|
|
19
|
+
const buildPromiseWatcherRootObject = (vatPowers, vatParameters, baggage) => {
|
|
20
|
+
const { VatData } = vatPowers;
|
|
21
|
+
const { watchPromise } = VatData;
|
|
22
|
+
const { prepareExo } = makeExoUtils(VatData);
|
|
23
|
+
// const { makeScalarBigMapStore } = makeStoreUtils(VatData);
|
|
24
|
+
const PromiseWatcherI = M.interface('ExtraArgPromiseWatcher', {
|
|
25
|
+
onFulfilled: M.call(M.any(), M.string()).returns(),
|
|
26
|
+
onRejected: M.call(M.any(), M.string()).returns(),
|
|
27
|
+
});
|
|
28
|
+
const watchResolutions = new Map();
|
|
29
|
+
const watcher = prepareExo(
|
|
30
|
+
baggage,
|
|
31
|
+
// No longer ignoring, but the name is set in stone in `kvStoreDataV1`
|
|
32
|
+
'DurablePromiseIgnorer',
|
|
33
|
+
PromiseWatcherI,
|
|
34
|
+
{
|
|
35
|
+
onFulfilled(value, name) {
|
|
36
|
+
watchResolutions.set(name, { status: 'fulfilled', value });
|
|
37
|
+
},
|
|
38
|
+
onRejected(reason, name) {
|
|
39
|
+
watchResolutions.set(name, { status: 'rejected', reason });
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
const knownPromises = new Map();
|
|
45
|
+
|
|
46
|
+
const root = Far('root', {
|
|
47
|
+
getPromise: name => {
|
|
48
|
+
const promise = knownPromises.get(name);
|
|
49
|
+
promise || Fail`promise doesn't exists: ${name}`;
|
|
50
|
+
return { promise };
|
|
51
|
+
},
|
|
52
|
+
importPromise: (name, promise) => {
|
|
53
|
+
!knownPromises.has(name) || Fail`promise already exists: ${name}`;
|
|
54
|
+
knownPromises.set(name, promise);
|
|
55
|
+
return `imported promise: ${name}`;
|
|
56
|
+
},
|
|
57
|
+
createLocalPromise: (name, fulfillment, rejection) => {
|
|
58
|
+
!knownPromises.has(name) || Fail`promise already exists: ${name}`;
|
|
59
|
+
const { promise, resolve, reject } = makePromiseKit();
|
|
60
|
+
if (fulfillment !== undefined) {
|
|
61
|
+
resolve(fulfillment);
|
|
62
|
+
} else if (rejection !== undefined) {
|
|
63
|
+
reject(rejection);
|
|
64
|
+
}
|
|
65
|
+
knownPromises.set(name, promise);
|
|
66
|
+
return `created local promise: ${name}`;
|
|
67
|
+
},
|
|
68
|
+
watchPromise: name => {
|
|
69
|
+
knownPromises.has(name) || Fail`promise not found: ${name}`;
|
|
70
|
+
watchPromise(knownPromises.get(name), watcher, name);
|
|
71
|
+
return `watched promise: ${name}`;
|
|
72
|
+
},
|
|
73
|
+
getWatchResolution: name => {
|
|
74
|
+
return watchResolutions.get(name);
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const startOperations = vatParameters?.startOperations || [];
|
|
79
|
+
for (const [method, ...args] of startOperations) {
|
|
80
|
+
root[method](...args);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return root;
|
|
84
|
+
};
|
|
85
|
+
const kvStoreDataV1 = Object.entries({
|
|
86
|
+
baggageID: 'o+d6/1',
|
|
87
|
+
idCounters: '{"exportID":11,"collectionID":5,"promiseID":9}',
|
|
88
|
+
kindIDID: '1',
|
|
89
|
+
storeKindIDTable:
|
|
90
|
+
'{"scalarMapStore":2,"scalarWeakMapStore":3,"scalarSetStore":4,"scalarWeakSetStore":5,"scalarDurableMapStore":6,"scalarDurableWeakMapStore":7,"scalarDurableSetStore":8,"scalarDurableWeakSetStore":9}',
|
|
91
|
+
'vc.1.sDurablePromiseIgnorer_kindHandle':
|
|
92
|
+
'{"body":"#\\"$0.Alleged: kind\\"","slots":["o+d1/10"]}',
|
|
93
|
+
'vc.1.sthe_DurablePromiseIgnorer':
|
|
94
|
+
'{"body":"#\\"$0.Alleged: DurablePromiseIgnorer\\"","slots":["o+d10/1"]}',
|
|
95
|
+
'vc.1.|entryCount': '2',
|
|
96
|
+
'vc.1.|nextOrdinal': '1',
|
|
97
|
+
'vc.1.|schemata':
|
|
98
|
+
'{"label":"baggage","body":"#{\\"keyShape\\":{\\"#tag\\":\\"match:string\\",\\"payload\\":[]}}","slots":[]}',
|
|
99
|
+
// non-durable
|
|
100
|
+
// 'vc.2.sp+6': '{"body":"#\\"&0\\"","slots":["p+6"]}',
|
|
101
|
+
// 'vc.2.|entryCount': '1',
|
|
102
|
+
// 'vc.2.|nextOrdinal': '1',
|
|
103
|
+
// 'vc.2.|schemata': '{"label":"promiseRegistrations","body":"#{\\"keyShape\\":{\\"#tag\\":\\"match:scalar\\",\\"payload\\":\\"#undefined\\"}}","slots":[]}',
|
|
104
|
+
'vc.3.|entryCount': '0',
|
|
105
|
+
'vc.3.|nextOrdinal': '1',
|
|
106
|
+
'vc.3.|schemata':
|
|
107
|
+
'{"label":"promiseWatcherByKind","body":"#{\\"keyShape\\":{\\"#tag\\":\\"match:scalar\\",\\"payload\\":\\"#undefined\\"}}","slots":[]}',
|
|
108
|
+
'vc.4.sp+6':
|
|
109
|
+
'{"body":"#[[\\"$0.Alleged: DurablePromiseIgnorer\\",\\"orphaned\\"]]","slots":["o+d10/1"]}',
|
|
110
|
+
'vc.4.sp-8':
|
|
111
|
+
'{"body":"#[[\\"$0.Alleged: DurablePromiseIgnorer\\",\\"unresolved\\"]]","slots":["o+d10/1"]}',
|
|
112
|
+
'vc.4.sp-9':
|
|
113
|
+
'{"body":"#[[\\"$0.Alleged: DurablePromiseIgnorer\\",\\"late-rejected\\"]]","slots":["o+d10/1"]}',
|
|
114
|
+
'vc.4.|entryCount': '3',
|
|
115
|
+
'vc.4.|nextOrdinal': '1',
|
|
116
|
+
'vc.4.|schemata':
|
|
117
|
+
'{"label":"watchedPromises","body":"#{\\"keyShape\\":{\\"#tag\\":\\"match:and\\",\\"payload\\":[{\\"#tag\\":\\"match:scalar\\",\\"payload\\":\\"#undefined\\"},{\\"#tag\\":\\"match:string\\",\\"payload\\":[]}]}}","slots":[]}',
|
|
118
|
+
'vom.dkind.10.descriptor':
|
|
119
|
+
'{"kindID":"10","tag":"DurablePromiseIgnorer","unfaceted":true}',
|
|
120
|
+
'vom.dkind.10.nextID': '2',
|
|
121
|
+
'vom.o+d10/1': '{}',
|
|
122
|
+
'vom.rc.o+d1/10': '1',
|
|
123
|
+
'vom.rc.o+d10/1': '3',
|
|
124
|
+
'vom.rc.o+d6/1': '1',
|
|
125
|
+
'vom.rc.o+d6/3': '1',
|
|
126
|
+
'vom.rc.o+d6/4': '1',
|
|
127
|
+
watchedPromiseTableID: 'o+d6/4',
|
|
128
|
+
watcherTableID: 'o+d6/3',
|
|
129
|
+
});
|
|
130
|
+
const kvStoreDataV1VpidsToReject = ['p+6', 'p-9'];
|
|
131
|
+
const kvStoreDataV1KeysToDelete = ['vc.4.sp+6', 'vc.4.sp-9'];
|
|
132
|
+
const kvStoreDataV1VpidsToKeep = ['p-8'];
|
|
133
|
+
const kvStoreDataV1KeysToKeep = ['vc.4.sp-8'];
|
|
134
|
+
|
|
135
|
+
test('past-incarnation watched promises', async t => {
|
|
136
|
+
const S = 'settlement';
|
|
137
|
+
// Anchor promise counters upon which the other assertions depend.
|
|
138
|
+
const firstPImport = 9;
|
|
139
|
+
// cf. src/liveslots.js:initialIDCounters
|
|
140
|
+
const firstPExport = 5;
|
|
141
|
+
|
|
142
|
+
const startImportedP = firstPImport - 2;
|
|
143
|
+
|
|
144
|
+
const v1StartOperations = [
|
|
145
|
+
['createLocalPromise', 'start orphaned'],
|
|
146
|
+
['watchPromise', 'start orphaned'],
|
|
147
|
+
['createLocalPromise', 'start fulfilled', S],
|
|
148
|
+
['watchPromise', 'start fulfilled'],
|
|
149
|
+
['importPromise', 'start imported', kslot(`p-${startImportedP}`)],
|
|
150
|
+
['watchPromise', 'start imported'],
|
|
151
|
+
];
|
|
152
|
+
const kvStore = new Map();
|
|
153
|
+
let {
|
|
154
|
+
v,
|
|
155
|
+
dispatch,
|
|
156
|
+
dispatchMessage: rawDispatch,
|
|
157
|
+
} = await setupTestLiveslots(
|
|
158
|
+
t,
|
|
159
|
+
buildPromiseWatcherRootObject,
|
|
160
|
+
'durable-promise-watcher',
|
|
161
|
+
{
|
|
162
|
+
kvStore,
|
|
163
|
+
nextPromiseImportNumber: firstPImport,
|
|
164
|
+
vatParameters: { startOperations: v1StartOperations },
|
|
165
|
+
},
|
|
166
|
+
);
|
|
167
|
+
let vatLogs = v.log;
|
|
168
|
+
|
|
169
|
+
let lastPImport = firstPImport - 1;
|
|
170
|
+
let lastPExport = firstPExport - 1;
|
|
171
|
+
let dispatches = 0;
|
|
172
|
+
const exportedPromises = new Set();
|
|
173
|
+
const v1OrphanedPExports = [];
|
|
174
|
+
const nextPImport = () => (lastPImport += 1);
|
|
175
|
+
const nextPExport = () => (lastPExport += 1);
|
|
176
|
+
/** @type {typeof rawDispatch} */
|
|
177
|
+
const dispatchMessage = (...args) => {
|
|
178
|
+
dispatches += 1;
|
|
179
|
+
return rawDispatch(...args);
|
|
180
|
+
};
|
|
181
|
+
const recordExportedPromise = name => {
|
|
182
|
+
exportedPromises.add(name);
|
|
183
|
+
return name;
|
|
184
|
+
};
|
|
185
|
+
// Ignore vatstore syscalls.
|
|
186
|
+
const getDispatchLogs = () =>
|
|
187
|
+
vatLogs.splice(0).filter(m => !m.type.startsWith('vatstore'));
|
|
188
|
+
const settlementMessage = (vpid, rejected, value) => ({
|
|
189
|
+
type: 'resolve',
|
|
190
|
+
resolutions: [[vpid, rejected, kser(value)]],
|
|
191
|
+
});
|
|
192
|
+
const fulfillmentMessage = (vpid, value) =>
|
|
193
|
+
settlementMessage(vpid, false, value);
|
|
194
|
+
const rejectionMessage = (vpid, value) =>
|
|
195
|
+
settlementMessage(vpid, true, value);
|
|
196
|
+
const subscribeMessage = vpid => ({
|
|
197
|
+
type: 'subscribe',
|
|
198
|
+
target: vpid,
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// startVat logs
|
|
202
|
+
v1OrphanedPExports.push(nextPExport());
|
|
203
|
+
recordExportedPromise('start orphaned');
|
|
204
|
+
v1OrphanedPExports.push(nextPExport());
|
|
205
|
+
recordExportedPromise('start fulfilled');
|
|
206
|
+
t.deepEqual(getDispatchLogs(), [
|
|
207
|
+
subscribeMessage(`p-${startImportedP}`),
|
|
208
|
+
subscribeMessage(`p+${lastPExport - 1}`),
|
|
209
|
+
subscribeMessage(`p+${lastPExport}`),
|
|
210
|
+
fulfillmentMessage(`p+${lastPExport}`, S),
|
|
211
|
+
]);
|
|
212
|
+
await dispatchMessage('createLocalPromise', 'exported', S);
|
|
213
|
+
t.deepEqual(getDispatchLogs(), [
|
|
214
|
+
fulfillmentMessage(`p-${nextPImport()}`, 'created local promise: exported'),
|
|
215
|
+
]);
|
|
216
|
+
await dispatchMessage('getPromise', recordExportedPromise('exported'));
|
|
217
|
+
t.deepEqual(getDispatchLogs(), [
|
|
218
|
+
fulfillmentMessage(`p-${nextPImport()}`, {
|
|
219
|
+
promise: kslot(`p+${nextPExport()}`),
|
|
220
|
+
}),
|
|
221
|
+
fulfillmentMessage(`p+${lastPExport}`, S),
|
|
222
|
+
]);
|
|
223
|
+
const importedP = firstPImport - 1;
|
|
224
|
+
await dispatchMessage('importPromise', 'imported', kslot(`p-${importedP}`));
|
|
225
|
+
t.deepEqual(getDispatchLogs(), [
|
|
226
|
+
subscribeMessage(`p-${importedP}`),
|
|
227
|
+
fulfillmentMessage(`p-${nextPImport()}`, 'imported promise: imported'),
|
|
228
|
+
]);
|
|
229
|
+
await dispatchMessage('createLocalPromise', 'orphaned');
|
|
230
|
+
t.deepEqual(getDispatchLogs(), [
|
|
231
|
+
fulfillmentMessage(`p-${nextPImport()}`, 'created local promise: orphaned'),
|
|
232
|
+
]);
|
|
233
|
+
await dispatchMessage('createLocalPromise', 'orphaned exported');
|
|
234
|
+
await dispatchMessage(
|
|
235
|
+
'getPromise',
|
|
236
|
+
recordExportedPromise('orphaned exported'),
|
|
237
|
+
);
|
|
238
|
+
const orphanedExportedP = nextPExport();
|
|
239
|
+
v1OrphanedPExports.push(orphanedExportedP);
|
|
240
|
+
t.deepEqual(getDispatchLogs(), [
|
|
241
|
+
fulfillmentMessage(
|
|
242
|
+
`p-${nextPImport()}`,
|
|
243
|
+
'created local promise: orphaned exported',
|
|
244
|
+
),
|
|
245
|
+
fulfillmentMessage(`p-${nextPImport()}`, {
|
|
246
|
+
promise: kslot(`p+${orphanedExportedP}`),
|
|
247
|
+
}),
|
|
248
|
+
]);
|
|
249
|
+
await dispatchMessage('createLocalPromise', 'fulfilled', S);
|
|
250
|
+
t.deepEqual(getDispatchLogs(), [
|
|
251
|
+
fulfillmentMessage(
|
|
252
|
+
`p-${nextPImport()}`,
|
|
253
|
+
'created local promise: fulfilled',
|
|
254
|
+
),
|
|
255
|
+
]);
|
|
256
|
+
await dispatchMessage('createLocalPromise', 'rejected', undefined, S);
|
|
257
|
+
t.deepEqual(getDispatchLogs(), [
|
|
258
|
+
fulfillmentMessage(`p-${nextPImport()}`, 'created local promise: rejected'),
|
|
259
|
+
]);
|
|
260
|
+
t.is(
|
|
261
|
+
lastPImport - firstPImport + 1,
|
|
262
|
+
dispatches,
|
|
263
|
+
`imported ${dispatches} promises (1 per dispatch)`,
|
|
264
|
+
);
|
|
265
|
+
t.is(
|
|
266
|
+
lastPExport - firstPExport + 1,
|
|
267
|
+
exportedPromises.size,
|
|
268
|
+
`exported ${exportedPromises.size} promises: ${[...exportedPromises].join(', ')}`,
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
await dispatchMessage('watchPromise', recordExportedPromise('orphaned'));
|
|
272
|
+
v1OrphanedPExports.push(nextPExport());
|
|
273
|
+
t.deepEqual(getDispatchLogs(), [
|
|
274
|
+
subscribeMessage(`p+${lastPExport}`),
|
|
275
|
+
fulfillmentMessage(`p-${nextPImport()}`, 'watched promise: orphaned'),
|
|
276
|
+
]);
|
|
277
|
+
await dispatchMessage('watchPromise', recordExportedPromise('fulfilled'));
|
|
278
|
+
t.deepEqual(getDispatchLogs(), [
|
|
279
|
+
subscribeMessage(`p+${nextPExport()}`),
|
|
280
|
+
fulfillmentMessage(`p-${nextPImport()}`, 'watched promise: fulfilled'),
|
|
281
|
+
fulfillmentMessage(`p+${lastPExport}`, S),
|
|
282
|
+
]);
|
|
283
|
+
await dispatchMessage('watchPromise', recordExportedPromise('rejected'));
|
|
284
|
+
t.deepEqual(getDispatchLogs(), [
|
|
285
|
+
subscribeMessage(`p+${nextPExport()}`),
|
|
286
|
+
fulfillmentMessage(`p-${nextPImport()}`, 'watched promise: rejected'),
|
|
287
|
+
rejectionMessage(`p+${lastPExport}`, S),
|
|
288
|
+
]);
|
|
289
|
+
await dispatchMessage('watchPromise', 'imported');
|
|
290
|
+
t.deepEqual(getDispatchLogs(), [
|
|
291
|
+
// no subscribe, we already did at import
|
|
292
|
+
fulfillmentMessage(`p-${nextPImport()}`, 'watched promise: imported'),
|
|
293
|
+
]);
|
|
294
|
+
await dispatchMessage('getWatchResolution', 'fulfilled');
|
|
295
|
+
t.deepEqual(getDispatchLogs(), [
|
|
296
|
+
fulfillmentMessage(`p-${nextPImport()}`, {
|
|
297
|
+
status: 'fulfilled',
|
|
298
|
+
value: S,
|
|
299
|
+
}),
|
|
300
|
+
]);
|
|
301
|
+
await dispatchMessage('getWatchResolution', 'rejected');
|
|
302
|
+
t.deepEqual(getDispatchLogs(), [
|
|
303
|
+
fulfillmentMessage(`p-${nextPImport()}`, {
|
|
304
|
+
status: 'rejected',
|
|
305
|
+
reason: S,
|
|
306
|
+
}),
|
|
307
|
+
]);
|
|
308
|
+
await dispatchMessage('getWatchResolution', 'start fulfilled');
|
|
309
|
+
t.deepEqual(getDispatchLogs(), [
|
|
310
|
+
fulfillmentMessage(`p-${nextPImport()}`, {
|
|
311
|
+
status: 'fulfilled',
|
|
312
|
+
value: S,
|
|
313
|
+
}),
|
|
314
|
+
]);
|
|
315
|
+
|
|
316
|
+
t.is(
|
|
317
|
+
lastPImport - firstPImport + 1,
|
|
318
|
+
dispatches,
|
|
319
|
+
`imported ${dispatches} promises (1 per dispatch)`,
|
|
320
|
+
);
|
|
321
|
+
t.is(
|
|
322
|
+
lastPExport - firstPExport + 1,
|
|
323
|
+
exportedPromises.size,
|
|
324
|
+
`exported ${exportedPromises.size} promises: ${[...exportedPromises].join(', ')}`,
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
// Simulate upgrade by starting from the non-empty kvStore.
|
|
328
|
+
// t.log(Object.fromEntries([...kvStore.entries()].sort(compareEntriesByKey)));
|
|
329
|
+
const clonedStore = new Map(kvStore);
|
|
330
|
+
const startImported2P = firstPImport - 3;
|
|
331
|
+
const v2StartOperations = [
|
|
332
|
+
['importPromise', 'start imported 2', kslot(`p-${startImported2P}`)], // import of new promise
|
|
333
|
+
['importPromise', 'imported', kslot(`p-${importedP}`)], // import previously imported and watched promise
|
|
334
|
+
['importPromise', 'orphaned exported', kslot(`p+${orphanedExportedP}`)], // import previously exported but unwatched promise
|
|
335
|
+
['watchPromise', 'orphaned exported'],
|
|
336
|
+
];
|
|
337
|
+
({
|
|
338
|
+
v,
|
|
339
|
+
dispatch,
|
|
340
|
+
dispatchMessage: rawDispatch,
|
|
341
|
+
} = await setupTestLiveslots(
|
|
342
|
+
t,
|
|
343
|
+
buildPromiseWatcherRootObject,
|
|
344
|
+
'durable-promise-watcher-v2',
|
|
345
|
+
{
|
|
346
|
+
kvStore: clonedStore,
|
|
347
|
+
nextPromiseImportNumber: lastPImport + 1,
|
|
348
|
+
vatParameters: { startOperations: v2StartOperations },
|
|
349
|
+
},
|
|
350
|
+
));
|
|
351
|
+
vatLogs = v.log;
|
|
352
|
+
|
|
353
|
+
// startVat logs
|
|
354
|
+
t.deepEqual(getDispatchLogs(), [
|
|
355
|
+
subscribeMessage(`p-${startImported2P}`),
|
|
356
|
+
subscribeMessage(`p-${importedP}`),
|
|
357
|
+
subscribeMessage(`p+${orphanedExportedP}`),
|
|
358
|
+
]);
|
|
359
|
+
// Simulate kernel rejection of promises orphaned by termination/upgrade of their decider vat.
|
|
360
|
+
const expectedDeletions = [...clonedStore.entries()].filter(entry =>
|
|
361
|
+
entry[1].includes('orphaned'),
|
|
362
|
+
);
|
|
363
|
+
t.true(expectedDeletions.length >= 1);
|
|
364
|
+
for (const orphanedPExport of v1OrphanedPExports) {
|
|
365
|
+
await dispatch(
|
|
366
|
+
makeReject(`p+${orphanedPExport}`, kser('tomorrow never came')),
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
await dispatchMessage('getWatchResolution', 'orphaned');
|
|
370
|
+
t.deepEqual(getDispatchLogs(), [
|
|
371
|
+
fulfillmentMessage(`p-${nextPImport()}`, {
|
|
372
|
+
status: 'rejected',
|
|
373
|
+
reason: 'tomorrow never came',
|
|
374
|
+
}),
|
|
375
|
+
]);
|
|
376
|
+
await dispatchMessage('getWatchResolution', 'start orphaned');
|
|
377
|
+
t.deepEqual(getDispatchLogs(), [
|
|
378
|
+
fulfillmentMessage(`p-${nextPImport()}`, {
|
|
379
|
+
status: 'rejected',
|
|
380
|
+
reason: 'tomorrow never came',
|
|
381
|
+
}),
|
|
382
|
+
]);
|
|
383
|
+
await dispatchMessage('getWatchResolution', 'orphaned exported');
|
|
384
|
+
t.deepEqual(getDispatchLogs(), [
|
|
385
|
+
fulfillmentMessage(`p-${nextPImport()}`, {
|
|
386
|
+
status: 'rejected',
|
|
387
|
+
reason: 'tomorrow never came',
|
|
388
|
+
}),
|
|
389
|
+
]);
|
|
390
|
+
for (const [key, value] of expectedDeletions) {
|
|
391
|
+
t.false(clonedStore.has(key), `entry should be removed: ${key}: ${value}`);
|
|
392
|
+
}
|
|
393
|
+
// Simulate resolution of imported promises watched in previous incarnation
|
|
394
|
+
await dispatch(makeResolve(`p-${importedP}`, kser(undefined)));
|
|
395
|
+
await dispatchMessage('getWatchResolution', 'imported');
|
|
396
|
+
t.deepEqual(getDispatchLogs(), [
|
|
397
|
+
fulfillmentMessage(`p-${nextPImport()}`, {
|
|
398
|
+
status: 'fulfilled',
|
|
399
|
+
value: undefined,
|
|
400
|
+
}),
|
|
401
|
+
]);
|
|
402
|
+
await dispatch(makeResolve(`p-${startImportedP}`, kser(undefined)));
|
|
403
|
+
await dispatchMessage('getWatchResolution', 'start imported');
|
|
404
|
+
t.deepEqual(getDispatchLogs(), [
|
|
405
|
+
fulfillmentMessage(`p-${nextPImport()}`, {
|
|
406
|
+
status: 'fulfilled',
|
|
407
|
+
value: undefined,
|
|
408
|
+
}),
|
|
409
|
+
]);
|
|
410
|
+
await dispatch(makeResolve(`p-${startImported2P}`, kser(undefined)));
|
|
411
|
+
await dispatchMessage('getWatchResolution', 'start imported 2');
|
|
412
|
+
t.deepEqual(getDispatchLogs(), [
|
|
413
|
+
fulfillmentMessage(`p-${nextPImport()}`, undefined),
|
|
414
|
+
]);
|
|
415
|
+
// simulate resolution of imported promise watched after resolution
|
|
416
|
+
await dispatchMessage('watchPromise', 'start imported 2');
|
|
417
|
+
t.deepEqual(getDispatchLogs(), [
|
|
418
|
+
// Promise was previously resolved, so it is re-exported
|
|
419
|
+
subscribeMessage(`p+${nextPExport()}`),
|
|
420
|
+
fulfillmentMessage(
|
|
421
|
+
`p-${nextPImport()}`,
|
|
422
|
+
'watched promise: start imported 2',
|
|
423
|
+
),
|
|
424
|
+
fulfillmentMessage(`p+${lastPExport}`, undefined),
|
|
425
|
+
]);
|
|
426
|
+
await dispatchMessage('getWatchResolution', 'start imported 2');
|
|
427
|
+
t.deepEqual(getDispatchLogs(), [
|
|
428
|
+
fulfillmentMessage(`p-${nextPImport()}`, {
|
|
429
|
+
status: 'fulfilled',
|
|
430
|
+
value: undefined,
|
|
431
|
+
}),
|
|
432
|
+
]);
|
|
433
|
+
|
|
434
|
+
// Verify that the data is still in loadable condition.
|
|
435
|
+
const finalClonedStore = new Map(clonedStore);
|
|
436
|
+
({
|
|
437
|
+
v,
|
|
438
|
+
dispatch,
|
|
439
|
+
dispatchMessage: rawDispatch,
|
|
440
|
+
} = await setupTestLiveslots(
|
|
441
|
+
t,
|
|
442
|
+
buildPromiseWatcherRootObject,
|
|
443
|
+
'durable-promise-watcher-final',
|
|
444
|
+
{ kvStore: finalClonedStore, nextPromiseImportNumber: lastPImport + 1 },
|
|
445
|
+
));
|
|
446
|
+
vatLogs = v.log;
|
|
447
|
+
vatLogs.length = 0;
|
|
448
|
+
await dispatchMessage('createLocalPromise', 'final', S);
|
|
449
|
+
await dispatchMessage('watchPromise', 'final');
|
|
450
|
+
await dispatchMessage('getWatchResolution', 'final');
|
|
451
|
+
t.deepEqual(getDispatchLogs(), [
|
|
452
|
+
fulfillmentMessage(`p-${nextPImport()}`, 'created local promise: final'),
|
|
453
|
+
subscribeMessage(`p+${nextPExport()}`),
|
|
454
|
+
fulfillmentMessage(`p-${nextPImport()}`, 'watched promise: final'),
|
|
455
|
+
fulfillmentMessage(`p+${lastPExport}`, S),
|
|
456
|
+
fulfillmentMessage(`p-${nextPImport()}`, {
|
|
457
|
+
status: 'fulfilled',
|
|
458
|
+
value: S,
|
|
459
|
+
}),
|
|
460
|
+
]);
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
test('past-incarnation watched promises from original-format kvStore', async t => {
|
|
464
|
+
const kvStore = new Map(kvStoreDataV1);
|
|
465
|
+
for (const key of [
|
|
466
|
+
...kvStoreDataV1KeysToDelete,
|
|
467
|
+
...kvStoreDataV1KeysToKeep,
|
|
468
|
+
]) {
|
|
469
|
+
t.true(kvStore.has(key), `key must be initially present: ${key}`);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
let { v, dispatch, dispatchMessage } = await setupTestLiveslots(
|
|
473
|
+
t,
|
|
474
|
+
buildPromiseWatcherRootObject,
|
|
475
|
+
'durable-promise-watcher',
|
|
476
|
+
{ kvStore, nextPromiseImportNumber: 100 },
|
|
477
|
+
);
|
|
478
|
+
let vatLogs = v.log;
|
|
479
|
+
for (const vpid of kvStoreDataV1VpidsToReject) {
|
|
480
|
+
await dispatch(makeReject(vpid, kser('tomorrow never came')));
|
|
481
|
+
}
|
|
482
|
+
for (const key of kvStoreDataV1KeysToDelete) {
|
|
483
|
+
t.false(kvStore.has(key), `key should be removed: ${key}`);
|
|
484
|
+
}
|
|
485
|
+
for (const key of kvStoreDataV1KeysToKeep) {
|
|
486
|
+
t.true(kvStore.has(key), `key should remain: ${key}`);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Verify that the data is still in loadable condition.
|
|
490
|
+
const finalClonedStore = new Map(kvStore);
|
|
491
|
+
// eslint-disable-next-line no-unused-vars
|
|
492
|
+
({ v, dispatch, dispatchMessage } = await setupTestLiveslots(
|
|
493
|
+
t,
|
|
494
|
+
buildPromiseWatcherRootObject,
|
|
495
|
+
'durable-promise-watcher-final',
|
|
496
|
+
{ kvStore: finalClonedStore, nextPromiseImportNumber: 200 },
|
|
497
|
+
));
|
|
498
|
+
vatLogs = v.log;
|
|
499
|
+
vatLogs.length = 0;
|
|
500
|
+
for (const vpid of kvStoreDataV1VpidsToKeep) {
|
|
501
|
+
await dispatch(makeResolve(vpid, kser('finally')));
|
|
502
|
+
}
|
|
503
|
+
for (const key of kvStoreDataV1KeysToKeep) {
|
|
504
|
+
t.false(finalClonedStore.has(key), `key should be removed: ${key}`);
|
|
505
|
+
}
|
|
506
|
+
});
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import test from 'ava';
|
|
2
|
-
import '@endo/init/debug.js';
|
|
3
2
|
|
|
4
3
|
import { Far } from '@endo/far';
|
|
4
|
+
import { kunser } from '@agoric/kmarshal';
|
|
5
5
|
import { M } from '@agoric/store';
|
|
6
6
|
import { setupTestLiveslots } from './liveslots-helpers.js';
|
|
7
|
-
import { kunser } from './kmarshal.js';
|
|
8
7
|
|
|
9
8
|
function buildRootObject(vatPowers, vatParameters, baggage) {
|
|
10
9
|
const vd = vatPowers.VatData;
|
|
@@ -148,5 +147,5 @@ test('vrefs', async t => {
|
|
|
148
147
|
const expectedStore1Vref = `o+v${initialKindIDs.scalarMapStore}/5`;
|
|
149
148
|
const store1Vref = (await run('getStore1')).slots[0];
|
|
150
149
|
t.is(store1Vref, expectedStore1Vref);
|
|
151
|
-
t.
|
|
150
|
+
t.is(kunser(JSON.parse(fakestore.get(`vc.5.s${'key'}`))), 'value');
|
|
152
151
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/* global WeakRef, FinalizationRegistry */
|
|
2
|
-
import
|
|
2
|
+
import { kser } from '@agoric/kmarshal';
|
|
3
3
|
|
|
4
|
+
import engineGC from './engine-gc.js';
|
|
4
5
|
import { waitUntilQuiescent } from './waitUntilQuiescent.js';
|
|
5
6
|
import { makeGcAndFinalize } from './gc-and-finalize.js';
|
|
6
7
|
import { makeDummyMeterControl } from './dummyMeterControl.js';
|
|
@@ -12,12 +13,11 @@ import {
|
|
|
12
13
|
makeRetireExports,
|
|
13
14
|
makeBringOutYourDead,
|
|
14
15
|
} from './util.js';
|
|
15
|
-
import { kser } from './kmarshal.js';
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* @param {object} [options]
|
|
19
|
-
* @param {boolean} [options.skipLogging
|
|
20
|
-
* @param {Map<string, string>} [options.kvStore
|
|
19
|
+
* @param {boolean} [options.skipLogging]
|
|
20
|
+
* @param {Map<string, string>} [options.kvStore]
|
|
21
21
|
*/
|
|
22
22
|
export function buildSyscall(options = {}) {
|
|
23
23
|
const { skipLogging = false, kvStore: fakestore = new Map() } = options;
|
|
@@ -131,6 +131,7 @@ export async function makeDispatch(
|
|
|
131
131
|
build,
|
|
132
132
|
vatID = 'vatA',
|
|
133
133
|
liveSlotsOptions = {},
|
|
134
|
+
vatParameters = undefined,
|
|
134
135
|
) {
|
|
135
136
|
const gcTools = harden({
|
|
136
137
|
WeakRef,
|
|
@@ -150,7 +151,7 @@ export async function makeDispatch(
|
|
|
150
151
|
return { buildRootObject: build };
|
|
151
152
|
},
|
|
152
153
|
);
|
|
153
|
-
await dispatch(['startVat', kser()]);
|
|
154
|
+
await dispatch(['startVat', kser(vatParameters)]);
|
|
154
155
|
return { dispatch, testHooks };
|
|
155
156
|
}
|
|
156
157
|
|
|
@@ -168,9 +169,10 @@ function makeRPMaker(nextNumber = 1) {
|
|
|
168
169
|
* @param {string} vatName
|
|
169
170
|
* @param {object} [options]
|
|
170
171
|
* @param {boolean} [options.forceGC]
|
|
171
|
-
* @param {Map<string, string>} [options.kvStore
|
|
172
|
+
* @param {Map<string, string>} [options.kvStore]
|
|
172
173
|
* @param {number} [options.nextPromiseImportNumber]
|
|
173
|
-
* @param {boolean} [options.skipLogging
|
|
174
|
+
* @param {boolean} [options.skipLogging]
|
|
175
|
+
* @param {any} [options.vatParameters]
|
|
174
176
|
*/
|
|
175
177
|
export async function setupTestLiveslots(
|
|
176
178
|
t,
|
|
@@ -183,6 +185,7 @@ export async function setupTestLiveslots(
|
|
|
183
185
|
kvStore = new Map(),
|
|
184
186
|
nextPromiseImportNumber,
|
|
185
187
|
skipLogging = false,
|
|
188
|
+
vatParameters,
|
|
186
189
|
} = options;
|
|
187
190
|
const { log, syscall, fakestore } = buildSyscall({ skipLogging, kvStore });
|
|
188
191
|
const nextRP = makeRPMaker(nextPromiseImportNumber);
|
|
@@ -190,6 +193,8 @@ export async function setupTestLiveslots(
|
|
|
190
193
|
syscall,
|
|
191
194
|
buildRootObject,
|
|
192
195
|
vatName,
|
|
196
|
+
{},
|
|
197
|
+
vatParameters,
|
|
193
198
|
);
|
|
194
199
|
|
|
195
200
|
async function dispatchMessage(message, ...args) {
|