@agoric/swingset-liveslots 0.10.3-u16.1 → 0.10.3-u17.1
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/package.json +17 -18
- package/src/boyd-gc.js +598 -0
- package/src/cache.js +1 -1
- package/src/capdata.js +1 -1
- package/src/collectionManager.js +65 -21
- package/src/facetiousness.js +1 -1
- package/src/liveslots.js +83 -202
- package/src/message.js +1 -1
- package/src/parseVatSlots.js +1 -1
- package/src/vatDataTypes.d.ts +7 -2
- package/src/virtualObjectManager.js +1 -6
- package/src/virtualReferences.js +83 -25
- package/src/watchedPromises.js +7 -2
- package/test/clear-collection.test.js +586 -0
- package/test/collections.test.js +41 -0
- package/test/dropped-weakset-9939.test.js +80 -0
- package/test/dummyMeterControl.js +1 -1
- package/test/durabilityChecks.test.js +1 -1
- package/test/gc-before-finalizer.test.js +230 -0
- package/test/handled-promises.test.js +266 -50
- package/test/liveslots-helpers.js +6 -1
- package/test/liveslots-mock-gc.test.js +99 -0
- package/test/liveslots-real-gc.test.js +18 -2
- package/test/liveslots.test.js +1 -1
- package/test/vat-environment.test.js +65 -0
- package/test/vat-util.js +1 -1
- package/test/virtual-objects/rep-tostring.test.js +1 -2
- package/test/vpid-liveslots.test.js +102 -1
- package/test/weakset-dropped-remotable.test.js +50 -0
- package/tools/fakeVirtualSupport.js +1 -3
- package/tools/setup-vat-data.js +10 -3
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import test from 'ava';
|
|
2
2
|
|
|
3
|
+
import { Fail } from '@endo/errors';
|
|
3
4
|
import { Far } from '@endo/marshal';
|
|
4
|
-
import { Fail } from '@agoric/assert';
|
|
5
5
|
import { M, provideLazy as provide } from '@agoric/store';
|
|
6
6
|
import { makePromiseKit } from '@endo/promise-kit';
|
|
7
7
|
// Disabled to avoid circular dependencies.
|
|
@@ -84,7 +84,7 @@ const makeExoUtils = VatData => {
|
|
|
84
84
|
};
|
|
85
85
|
|
|
86
86
|
// cf. packages/SwingSet/test/vat-durable-promise-watcher.js
|
|
87
|
-
const buildPromiseWatcherRootObject = (vatPowers,
|
|
87
|
+
const buildPromiseWatcherRootObject = (vatPowers, vatParameters, baggage) => {
|
|
88
88
|
const { VatData } = vatPowers;
|
|
89
89
|
const { watchPromise } = VatData;
|
|
90
90
|
const { prepareExo } = makeExoUtils(VatData);
|
|
@@ -93,37 +93,62 @@ const buildPromiseWatcherRootObject = (vatPowers, _vatParameters, baggage) => {
|
|
|
93
93
|
onFulfilled: M.call(M.any(), M.string()).returns(),
|
|
94
94
|
onRejected: M.call(M.any(), M.string()).returns(),
|
|
95
95
|
});
|
|
96
|
+
const watchResolutions = new Map();
|
|
96
97
|
const watcher = prepareExo(
|
|
97
98
|
baggage,
|
|
99
|
+
// No longer ignoring, but the name is set in stone in `kvStoreDataV1`
|
|
98
100
|
'DurablePromiseIgnorer',
|
|
99
101
|
PromiseWatcherI,
|
|
100
102
|
{
|
|
101
|
-
onFulfilled(
|
|
102
|
-
|
|
103
|
+
onFulfilled(value, name) {
|
|
104
|
+
watchResolutions.set(name, { status: 'fulfilled', value });
|
|
105
|
+
},
|
|
106
|
+
onRejected(reason, name) {
|
|
107
|
+
watchResolutions.set(name, { status: 'rejected', reason });
|
|
108
|
+
},
|
|
103
109
|
},
|
|
104
110
|
);
|
|
105
111
|
|
|
106
|
-
const
|
|
112
|
+
const knownPromises = new Map();
|
|
107
113
|
|
|
108
|
-
|
|
109
|
-
|
|
114
|
+
const root = Far('root', {
|
|
115
|
+
getPromise: name => {
|
|
116
|
+
const promise = knownPromises.get(name);
|
|
117
|
+
promise || Fail`promise doesn't exists: ${name}`;
|
|
118
|
+
return { promise };
|
|
119
|
+
},
|
|
120
|
+
importPromise: (name, promise) => {
|
|
121
|
+
!knownPromises.has(name) || Fail`promise already exists: ${name}`;
|
|
122
|
+
knownPromises.set(name, promise);
|
|
123
|
+
return `imported promise: ${name}`;
|
|
124
|
+
},
|
|
110
125
|
createLocalPromise: (name, fulfillment, rejection) => {
|
|
111
|
-
!
|
|
126
|
+
!knownPromises.has(name) || Fail`promise already exists: ${name}`;
|
|
112
127
|
const { promise, resolve, reject } = makePromiseKit();
|
|
113
128
|
if (fulfillment !== undefined) {
|
|
114
129
|
resolve(fulfillment);
|
|
115
130
|
} else if (rejection !== undefined) {
|
|
116
131
|
reject(rejection);
|
|
117
132
|
}
|
|
118
|
-
|
|
133
|
+
knownPromises.set(name, promise);
|
|
119
134
|
return `created local promise: ${name}`;
|
|
120
135
|
},
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
watchPromise(
|
|
124
|
-
return `watched
|
|
136
|
+
watchPromise: name => {
|
|
137
|
+
knownPromises.has(name) || Fail`promise not found: ${name}`;
|
|
138
|
+
watchPromise(knownPromises.get(name), watcher, name);
|
|
139
|
+
return `watched promise: ${name}`;
|
|
140
|
+
},
|
|
141
|
+
getWatchResolution: name => {
|
|
142
|
+
return watchResolutions.get(name);
|
|
125
143
|
},
|
|
126
144
|
});
|
|
145
|
+
|
|
146
|
+
const startOperations = vatParameters?.startOperations || [];
|
|
147
|
+
for (const [method, ...args] of startOperations) {
|
|
148
|
+
root[method](...args);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return root;
|
|
127
152
|
};
|
|
128
153
|
const kvStoreDataV1 = Object.entries({
|
|
129
154
|
baggageID: 'o+d6/1',
|
|
@@ -176,23 +201,55 @@ const kvStoreDataV1VpidsToKeep = ['p-8'];
|
|
|
176
201
|
const kvStoreDataV1KeysToKeep = ['vc.4.sp-8'];
|
|
177
202
|
|
|
178
203
|
test('past-incarnation watched promises', async t => {
|
|
204
|
+
const S = 'settlement';
|
|
205
|
+
// Anchor promise counters upon which the other assertions depend.
|
|
206
|
+
const firstPImport = 9;
|
|
207
|
+
// cf. src/liveslots.js:initialIDCounters
|
|
208
|
+
const firstPExport = 5;
|
|
209
|
+
|
|
210
|
+
const startImportedP = firstPImport - 2;
|
|
211
|
+
|
|
212
|
+
const v1StartOperations = [
|
|
213
|
+
['createLocalPromise', 'start orphaned'],
|
|
214
|
+
['watchPromise', 'start orphaned'],
|
|
215
|
+
['createLocalPromise', 'start fulfilled', S],
|
|
216
|
+
['watchPromise', 'start fulfilled'],
|
|
217
|
+
['importPromise', 'start imported', kslot(`p-${startImportedP}`)],
|
|
218
|
+
['watchPromise', 'start imported'],
|
|
219
|
+
];
|
|
179
220
|
const kvStore = new Map();
|
|
180
|
-
let {
|
|
221
|
+
let {
|
|
222
|
+
v,
|
|
223
|
+
dispatch,
|
|
224
|
+
dispatchMessage: rawDispatch,
|
|
225
|
+
} = await setupTestLiveslots(
|
|
181
226
|
t,
|
|
182
227
|
buildPromiseWatcherRootObject,
|
|
183
228
|
'durable-promise-watcher',
|
|
184
|
-
{
|
|
229
|
+
{
|
|
230
|
+
kvStore,
|
|
231
|
+
nextPromiseImportNumber: firstPImport,
|
|
232
|
+
vatParameters: { startOperations: v1StartOperations },
|
|
233
|
+
},
|
|
185
234
|
);
|
|
186
235
|
let vatLogs = v.log;
|
|
187
236
|
|
|
188
|
-
// Anchor promise counters upon which the other assertions depend.
|
|
189
|
-
const firstPImport = 1;
|
|
190
|
-
// cf. src/liveslots.js:initialIDCounters
|
|
191
|
-
const firstPExport = 5;
|
|
192
237
|
let lastPImport = firstPImport - 1;
|
|
193
238
|
let lastPExport = firstPExport - 1;
|
|
239
|
+
let dispatches = 0;
|
|
240
|
+
const exportedPromises = new Set();
|
|
241
|
+
const v1OrphanedPExports = [];
|
|
194
242
|
const nextPImport = () => (lastPImport += 1);
|
|
195
243
|
const nextPExport = () => (lastPExport += 1);
|
|
244
|
+
/** @type {typeof rawDispatch} */
|
|
245
|
+
const dispatchMessage = (...args) => {
|
|
246
|
+
dispatches += 1;
|
|
247
|
+
return rawDispatch(...args);
|
|
248
|
+
};
|
|
249
|
+
const recordExportedPromise = name => {
|
|
250
|
+
exportedPromises.add(name);
|
|
251
|
+
return name;
|
|
252
|
+
};
|
|
196
253
|
// Ignore vatstore syscalls.
|
|
197
254
|
const getDispatchLogs = () =>
|
|
198
255
|
vatLogs.splice(0).filter(m => !m.type.startsWith('vatstore'));
|
|
@@ -208,18 +265,55 @@ test('past-incarnation watched promises', async t => {
|
|
|
208
265
|
type: 'subscribe',
|
|
209
266
|
target: vpid,
|
|
210
267
|
});
|
|
211
|
-
|
|
212
|
-
|
|
268
|
+
|
|
269
|
+
// startVat logs
|
|
270
|
+
v1OrphanedPExports.push(nextPExport());
|
|
271
|
+
recordExportedPromise('start orphaned');
|
|
272
|
+
v1OrphanedPExports.push(nextPExport());
|
|
273
|
+
recordExportedPromise('start fulfilled');
|
|
213
274
|
t.deepEqual(getDispatchLogs(), [
|
|
214
|
-
|
|
215
|
-
|
|
275
|
+
subscribeMessage(`p-${startImportedP}`),
|
|
276
|
+
subscribeMessage(`p+${lastPExport - 1}`),
|
|
277
|
+
subscribeMessage(`p+${lastPExport}`),
|
|
278
|
+
fulfillmentMessage(`p+${lastPExport}`, S),
|
|
279
|
+
]);
|
|
280
|
+
await dispatchMessage('createLocalPromise', 'exported', S);
|
|
281
|
+
t.deepEqual(getDispatchLogs(), [
|
|
282
|
+
fulfillmentMessage(`p-${nextPImport()}`, 'created local promise: exported'),
|
|
283
|
+
]);
|
|
284
|
+
await dispatchMessage('getPromise', recordExportedPromise('exported'));
|
|
285
|
+
t.deepEqual(getDispatchLogs(), [
|
|
286
|
+
fulfillmentMessage(`p-${nextPImport()}`, {
|
|
287
|
+
promise: kslot(`p+${nextPExport()}`),
|
|
288
|
+
}),
|
|
289
|
+
fulfillmentMessage(`p+${lastPExport}`, S),
|
|
290
|
+
]);
|
|
291
|
+
const importedP = firstPImport - 1;
|
|
292
|
+
await dispatchMessage('importPromise', 'imported', kslot(`p-${importedP}`));
|
|
293
|
+
t.deepEqual(getDispatchLogs(), [
|
|
294
|
+
subscribeMessage(`p-${importedP}`),
|
|
295
|
+
fulfillmentMessage(`p-${nextPImport()}`, 'imported promise: imported'),
|
|
216
296
|
]);
|
|
217
|
-
|
|
218
|
-
const S = 'settlement';
|
|
219
297
|
await dispatchMessage('createLocalPromise', 'orphaned');
|
|
220
298
|
t.deepEqual(getDispatchLogs(), [
|
|
221
299
|
fulfillmentMessage(`p-${nextPImport()}`, 'created local promise: orphaned'),
|
|
222
300
|
]);
|
|
301
|
+
await dispatchMessage('createLocalPromise', 'orphaned exported');
|
|
302
|
+
await dispatchMessage(
|
|
303
|
+
'getPromise',
|
|
304
|
+
recordExportedPromise('orphaned exported'),
|
|
305
|
+
);
|
|
306
|
+
const orphanedExportedP = nextPExport();
|
|
307
|
+
v1OrphanedPExports.push(orphanedExportedP);
|
|
308
|
+
t.deepEqual(getDispatchLogs(), [
|
|
309
|
+
fulfillmentMessage(
|
|
310
|
+
`p-${nextPImport()}`,
|
|
311
|
+
'created local promise: orphaned exported',
|
|
312
|
+
),
|
|
313
|
+
fulfillmentMessage(`p-${nextPImport()}`, {
|
|
314
|
+
promise: kslot(`p+${orphanedExportedP}`),
|
|
315
|
+
}),
|
|
316
|
+
]);
|
|
223
317
|
await dispatchMessage('createLocalPromise', 'fulfilled', S);
|
|
224
318
|
t.deepEqual(getDispatchLogs(), [
|
|
225
319
|
fulfillmentMessage(
|
|
@@ -233,68 +327,185 @@ test('past-incarnation watched promises', async t => {
|
|
|
233
327
|
]);
|
|
234
328
|
t.is(
|
|
235
329
|
lastPImport - firstPImport + 1,
|
|
236
|
-
|
|
237
|
-
|
|
330
|
+
dispatches,
|
|
331
|
+
`imported ${dispatches} promises (1 per dispatch)`,
|
|
332
|
+
);
|
|
333
|
+
t.is(
|
|
334
|
+
lastPExport - firstPExport + 1,
|
|
335
|
+
exportedPromises.size,
|
|
336
|
+
`exported ${exportedPromises.size} promises: ${[...exportedPromises].join(', ')}`,
|
|
238
337
|
);
|
|
239
|
-
t.is(lastPExport - firstPExport + 1, 1, 'exported 1 promise: first');
|
|
240
338
|
|
|
241
|
-
await dispatchMessage('
|
|
339
|
+
await dispatchMessage('watchPromise', recordExportedPromise('orphaned'));
|
|
340
|
+
v1OrphanedPExports.push(nextPExport());
|
|
242
341
|
t.deepEqual(getDispatchLogs(), [
|
|
243
|
-
subscribeMessage(`p+${
|
|
244
|
-
fulfillmentMessage(`p-${nextPImport()}`, 'watched
|
|
342
|
+
subscribeMessage(`p+${lastPExport}`),
|
|
343
|
+
fulfillmentMessage(`p-${nextPImport()}`, 'watched promise: orphaned'),
|
|
245
344
|
]);
|
|
246
|
-
await dispatchMessage('
|
|
345
|
+
await dispatchMessage('watchPromise', recordExportedPromise('fulfilled'));
|
|
247
346
|
t.deepEqual(getDispatchLogs(), [
|
|
248
347
|
subscribeMessage(`p+${nextPExport()}`),
|
|
249
|
-
fulfillmentMessage(
|
|
250
|
-
`p-${nextPImport()}`,
|
|
251
|
-
'watched local promise: fulfilled',
|
|
252
|
-
),
|
|
348
|
+
fulfillmentMessage(`p-${nextPImport()}`, 'watched promise: fulfilled'),
|
|
253
349
|
fulfillmentMessage(`p+${lastPExport}`, S),
|
|
254
350
|
]);
|
|
255
|
-
await dispatchMessage('
|
|
351
|
+
await dispatchMessage('watchPromise', recordExportedPromise('rejected'));
|
|
256
352
|
t.deepEqual(getDispatchLogs(), [
|
|
257
353
|
subscribeMessage(`p+${nextPExport()}`),
|
|
258
|
-
fulfillmentMessage(`p-${nextPImport()}`, 'watched
|
|
354
|
+
fulfillmentMessage(`p-${nextPImport()}`, 'watched promise: rejected'),
|
|
259
355
|
rejectionMessage(`p+${lastPExport}`, S),
|
|
260
356
|
]);
|
|
357
|
+
await dispatchMessage('watchPromise', 'imported');
|
|
358
|
+
t.deepEqual(getDispatchLogs(), [
|
|
359
|
+
// no subscribe, we already did at import
|
|
360
|
+
fulfillmentMessage(`p-${nextPImport()}`, 'watched promise: imported'),
|
|
361
|
+
]);
|
|
362
|
+
await dispatchMessage('getWatchResolution', 'fulfilled');
|
|
363
|
+
t.deepEqual(getDispatchLogs(), [
|
|
364
|
+
fulfillmentMessage(`p-${nextPImport()}`, {
|
|
365
|
+
status: 'fulfilled',
|
|
366
|
+
value: S,
|
|
367
|
+
}),
|
|
368
|
+
]);
|
|
369
|
+
await dispatchMessage('getWatchResolution', 'rejected');
|
|
370
|
+
t.deepEqual(getDispatchLogs(), [
|
|
371
|
+
fulfillmentMessage(`p-${nextPImport()}`, {
|
|
372
|
+
status: 'rejected',
|
|
373
|
+
reason: S,
|
|
374
|
+
}),
|
|
375
|
+
]);
|
|
376
|
+
await dispatchMessage('getWatchResolution', 'start fulfilled');
|
|
377
|
+
t.deepEqual(getDispatchLogs(), [
|
|
378
|
+
fulfillmentMessage(`p-${nextPImport()}`, {
|
|
379
|
+
status: 'fulfilled',
|
|
380
|
+
value: S,
|
|
381
|
+
}),
|
|
382
|
+
]);
|
|
383
|
+
|
|
261
384
|
t.is(
|
|
262
385
|
lastPImport - firstPImport + 1,
|
|
263
|
-
|
|
264
|
-
|
|
386
|
+
dispatches,
|
|
387
|
+
`imported ${dispatches} promises (1 per dispatch)`,
|
|
265
388
|
);
|
|
266
389
|
t.is(
|
|
267
390
|
lastPExport - firstPExport + 1,
|
|
268
|
-
|
|
269
|
-
|
|
391
|
+
exportedPromises.size,
|
|
392
|
+
`exported ${exportedPromises.size} promises: ${[...exportedPromises].join(', ')}`,
|
|
270
393
|
);
|
|
271
394
|
|
|
272
395
|
// Simulate upgrade by starting from the non-empty kvStore.
|
|
273
396
|
// t.log(Object.fromEntries([...kvStore.entries()].sort(compareEntriesByKey)));
|
|
274
397
|
const clonedStore = new Map(kvStore);
|
|
275
|
-
|
|
398
|
+
const startImported2P = firstPImport - 3;
|
|
399
|
+
const v2StartOperations = [
|
|
400
|
+
['importPromise', 'start imported 2', kslot(`p-${startImported2P}`)], // import of new promise
|
|
401
|
+
['importPromise', 'imported', kslot(`p-${importedP}`)], // import previously imported and watched promise
|
|
402
|
+
['importPromise', 'orphaned exported', kslot(`p+${orphanedExportedP}`)], // import previously exported but unwatched promise
|
|
403
|
+
['watchPromise', 'orphaned exported'],
|
|
404
|
+
];
|
|
405
|
+
({
|
|
406
|
+
v,
|
|
407
|
+
dispatch,
|
|
408
|
+
dispatchMessage: rawDispatch,
|
|
409
|
+
} = await setupTestLiveslots(
|
|
276
410
|
t,
|
|
277
411
|
buildPromiseWatcherRootObject,
|
|
278
412
|
'durable-promise-watcher-v2',
|
|
279
|
-
{
|
|
413
|
+
{
|
|
414
|
+
kvStore: clonedStore,
|
|
415
|
+
nextPromiseImportNumber: lastPImport + 1,
|
|
416
|
+
vatParameters: { startOperations: v2StartOperations },
|
|
417
|
+
},
|
|
280
418
|
));
|
|
281
419
|
vatLogs = v.log;
|
|
282
420
|
|
|
421
|
+
// startVat logs
|
|
422
|
+
t.deepEqual(getDispatchLogs(), [
|
|
423
|
+
subscribeMessage(`p-${startImported2P}`),
|
|
424
|
+
subscribeMessage(`p-${importedP}`),
|
|
425
|
+
subscribeMessage(`p+${orphanedExportedP}`),
|
|
426
|
+
]);
|
|
283
427
|
// Simulate kernel rejection of promises orphaned by termination/upgrade of their decider vat.
|
|
284
428
|
const expectedDeletions = [...clonedStore.entries()].filter(entry =>
|
|
285
429
|
entry[1].includes('orphaned'),
|
|
286
430
|
);
|
|
287
431
|
t.true(expectedDeletions.length >= 1);
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
432
|
+
for (const orphanedPExport of v1OrphanedPExports) {
|
|
433
|
+
await dispatch(
|
|
434
|
+
makeReject(`p+${orphanedPExport}`, kser('tomorrow never came')),
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
await dispatchMessage('getWatchResolution', 'orphaned');
|
|
438
|
+
t.deepEqual(getDispatchLogs(), [
|
|
439
|
+
fulfillmentMessage(`p-${nextPImport()}`, {
|
|
440
|
+
status: 'rejected',
|
|
441
|
+
reason: 'tomorrow never came',
|
|
442
|
+
}),
|
|
443
|
+
]);
|
|
444
|
+
await dispatchMessage('getWatchResolution', 'start orphaned');
|
|
445
|
+
t.deepEqual(getDispatchLogs(), [
|
|
446
|
+
fulfillmentMessage(`p-${nextPImport()}`, {
|
|
447
|
+
status: 'rejected',
|
|
448
|
+
reason: 'tomorrow never came',
|
|
449
|
+
}),
|
|
450
|
+
]);
|
|
451
|
+
await dispatchMessage('getWatchResolution', 'orphaned exported');
|
|
452
|
+
t.deepEqual(getDispatchLogs(), [
|
|
453
|
+
fulfillmentMessage(`p-${nextPImport()}`, {
|
|
454
|
+
status: 'rejected',
|
|
455
|
+
reason: 'tomorrow never came',
|
|
456
|
+
}),
|
|
457
|
+
]);
|
|
291
458
|
for (const [key, value] of expectedDeletions) {
|
|
292
459
|
t.false(clonedStore.has(key), `entry should be removed: ${key}: ${value}`);
|
|
293
460
|
}
|
|
461
|
+
// Simulate resolution of imported promises watched in previous incarnation
|
|
462
|
+
await dispatch(makeResolve(`p-${importedP}`, kser(undefined)));
|
|
463
|
+
await dispatchMessage('getWatchResolution', 'imported');
|
|
464
|
+
t.deepEqual(getDispatchLogs(), [
|
|
465
|
+
fulfillmentMessage(`p-${nextPImport()}`, {
|
|
466
|
+
status: 'fulfilled',
|
|
467
|
+
value: undefined,
|
|
468
|
+
}),
|
|
469
|
+
]);
|
|
470
|
+
await dispatch(makeResolve(`p-${startImportedP}`, kser(undefined)));
|
|
471
|
+
await dispatchMessage('getWatchResolution', 'start imported');
|
|
472
|
+
t.deepEqual(getDispatchLogs(), [
|
|
473
|
+
fulfillmentMessage(`p-${nextPImport()}`, {
|
|
474
|
+
status: 'fulfilled',
|
|
475
|
+
value: undefined,
|
|
476
|
+
}),
|
|
477
|
+
]);
|
|
478
|
+
await dispatch(makeResolve(`p-${startImported2P}`, kser(undefined)));
|
|
479
|
+
await dispatchMessage('getWatchResolution', 'start imported 2');
|
|
480
|
+
t.deepEqual(getDispatchLogs(), [
|
|
481
|
+
fulfillmentMessage(`p-${nextPImport()}`, undefined),
|
|
482
|
+
]);
|
|
483
|
+
// simulate resolution of imported promise watched after resolution
|
|
484
|
+
await dispatchMessage('watchPromise', 'start imported 2');
|
|
485
|
+
t.deepEqual(getDispatchLogs(), [
|
|
486
|
+
// Promise was previously resolved, so it is re-exported
|
|
487
|
+
subscribeMessage(`p+${nextPExport()}`),
|
|
488
|
+
fulfillmentMessage(
|
|
489
|
+
`p-${nextPImport()}`,
|
|
490
|
+
'watched promise: start imported 2',
|
|
491
|
+
),
|
|
492
|
+
fulfillmentMessage(`p+${lastPExport}`, undefined),
|
|
493
|
+
]);
|
|
494
|
+
await dispatchMessage('getWatchResolution', 'start imported 2');
|
|
495
|
+
t.deepEqual(getDispatchLogs(), [
|
|
496
|
+
fulfillmentMessage(`p-${nextPImport()}`, {
|
|
497
|
+
status: 'fulfilled',
|
|
498
|
+
value: undefined,
|
|
499
|
+
}),
|
|
500
|
+
]);
|
|
294
501
|
|
|
295
502
|
// Verify that the data is still in loadable condition.
|
|
296
503
|
const finalClonedStore = new Map(clonedStore);
|
|
297
|
-
({
|
|
504
|
+
({
|
|
505
|
+
v,
|
|
506
|
+
dispatch,
|
|
507
|
+
dispatchMessage: rawDispatch,
|
|
508
|
+
} = await setupTestLiveslots(
|
|
298
509
|
t,
|
|
299
510
|
buildPromiseWatcherRootObject,
|
|
300
511
|
'durable-promise-watcher-final',
|
|
@@ -303,12 +514,17 @@ test('past-incarnation watched promises', async t => {
|
|
|
303
514
|
vatLogs = v.log;
|
|
304
515
|
vatLogs.length = 0;
|
|
305
516
|
await dispatchMessage('createLocalPromise', 'final', S);
|
|
306
|
-
await dispatchMessage('
|
|
517
|
+
await dispatchMessage('watchPromise', 'final');
|
|
518
|
+
await dispatchMessage('getWatchResolution', 'final');
|
|
307
519
|
t.deepEqual(getDispatchLogs(), [
|
|
308
520
|
fulfillmentMessage(`p-${nextPImport()}`, 'created local promise: final'),
|
|
309
521
|
subscribeMessage(`p+${nextPExport()}`),
|
|
310
|
-
fulfillmentMessage(`p-${nextPImport()}`, 'watched
|
|
522
|
+
fulfillmentMessage(`p-${nextPImport()}`, 'watched promise: final'),
|
|
311
523
|
fulfillmentMessage(`p+${lastPExport}`, S),
|
|
524
|
+
fulfillmentMessage(`p-${nextPImport()}`, {
|
|
525
|
+
status: 'fulfilled',
|
|
526
|
+
value: S,
|
|
527
|
+
}),
|
|
312
528
|
]);
|
|
313
529
|
});
|
|
314
530
|
|
|
@@ -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
|
|
|
@@ -171,6 +172,7 @@ function makeRPMaker(nextNumber = 1) {
|
|
|
171
172
|
* @param {Map<string, string>} [options.kvStore]
|
|
172
173
|
* @param {number} [options.nextPromiseImportNumber]
|
|
173
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) {
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
makeStartVat,
|
|
12
12
|
makeBringOutYourDead,
|
|
13
13
|
makeResolve,
|
|
14
|
+
makeRetireImports,
|
|
14
15
|
} from './util.js';
|
|
15
16
|
import { makeMockGC } from './mock-gc.js';
|
|
16
17
|
|
|
@@ -465,3 +466,101 @@ for (const firstType of ['object', 'collection']) {
|
|
|
465
466
|
}
|
|
466
467
|
|
|
467
468
|
// test('double-free', doublefreetest, { firstType: 'object', lastType: 'collection', order: 'first->last' });
|
|
469
|
+
|
|
470
|
+
test('retirement', async t => {
|
|
471
|
+
const { syscall, fakestore, log } = buildSyscall();
|
|
472
|
+
const gcTools = makeMockGC();
|
|
473
|
+
|
|
474
|
+
// A is a weak collection, with one entry, whose key is B (a
|
|
475
|
+
// Presence). We drop the RAM pillar for B and do a BOYD, which
|
|
476
|
+
// should provoke a syscall.dropImports. Then, when we delete A (by
|
|
477
|
+
// dropping the RAM pillar), the next BOYD should see a
|
|
478
|
+
// `syscall.retireImports`.
|
|
479
|
+
|
|
480
|
+
let weakmapA;
|
|
481
|
+
let presenceB;
|
|
482
|
+
|
|
483
|
+
function buildRootObject(vatPowers) {
|
|
484
|
+
const { VatData } = vatPowers;
|
|
485
|
+
const { makeScalarBigWeakMapStore } = VatData;
|
|
486
|
+
|
|
487
|
+
weakmapA = makeScalarBigWeakMapStore();
|
|
488
|
+
|
|
489
|
+
return Far('root', {
|
|
490
|
+
add: p => {
|
|
491
|
+
presenceB = p;
|
|
492
|
+
weakmapA.init(presenceB, 'value');
|
|
493
|
+
},
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
const makeNS = () => ({ buildRootObject });
|
|
498
|
+
const ls = makeLiveSlots(syscall, 'vatA', {}, {}, gcTools, undefined, makeNS);
|
|
499
|
+
const { dispatch, testHooks } = ls;
|
|
500
|
+
const { valToSlot } = testHooks;
|
|
501
|
+
|
|
502
|
+
await dispatch(makeStartVat(kser()));
|
|
503
|
+
log.length = 0;
|
|
504
|
+
const weakmapAvref = valToSlot.get(weakmapA);
|
|
505
|
+
const { subid } = parseVatSlot(weakmapAvref);
|
|
506
|
+
const collectionID = String(subid);
|
|
507
|
+
|
|
508
|
+
const rootA = 'o+0';
|
|
509
|
+
const presenceBvref = 'o-1';
|
|
510
|
+
await dispatch(makeMessage(rootA, 'add', [kslot(presenceBvref)]));
|
|
511
|
+
log.length = 0;
|
|
512
|
+
|
|
513
|
+
// the fact that weakmapA can recognize presenceA is recorded in a
|
|
514
|
+
// vatstore key
|
|
515
|
+
const recognizerKey = `vom.ir.${presenceBvref}|${collectionID}`;
|
|
516
|
+
t.is(fakestore.get(recognizerKey), '1');
|
|
517
|
+
|
|
518
|
+
// tell mockGC that userspace has dropped presenceB
|
|
519
|
+
gcTools.kill(presenceB);
|
|
520
|
+
gcTools.flushAllFRs();
|
|
521
|
+
|
|
522
|
+
await dispatch(makeBringOutYourDead());
|
|
523
|
+
const priorKey = `vom.ir.${presenceBvref}|`;
|
|
524
|
+
|
|
525
|
+
t.deepEqual(log.splice(0), [
|
|
526
|
+
// when a Presence is dropped, scanForDeadObjects can't drop the
|
|
527
|
+
// underlying vref import until it knows that virtual data isn't
|
|
528
|
+
// holding a reference, so we expect a refcount check
|
|
529
|
+
{ type: 'vatstoreGet', key: `vom.rc.${presenceBvref}`, result: undefined },
|
|
530
|
+
|
|
531
|
+
// the vref is now in importsToDrop, but since this commonly means
|
|
532
|
+
// it can be retired too, scanForDeadObjects goes ahead and checks
|
|
533
|
+
// for recognizers
|
|
534
|
+
{ type: 'vatstoreGetNextKey', priorKey, result: recognizerKey },
|
|
535
|
+
|
|
536
|
+
// it found a recognizer, so the vref cannot be retired
|
|
537
|
+
// yet. scanForDeadObjects finishes the BOYD by emitting the
|
|
538
|
+
// dropImports, but should keep watching for an opportunity to
|
|
539
|
+
// retire it too
|
|
540
|
+
{ type: 'dropImports', slots: [presenceBvref] },
|
|
541
|
+
]);
|
|
542
|
+
|
|
543
|
+
// now tell mockGC that we're dropping the weakmap too
|
|
544
|
+
gcTools.kill(weakmapA);
|
|
545
|
+
gcTools.flushAllFRs();
|
|
546
|
+
|
|
547
|
+
// this will provoke the deletion of the collection and all its
|
|
548
|
+
// data. It should *also* trigger a syscall.retireImports of the
|
|
549
|
+
// no-longer-recognizable key
|
|
550
|
+
await dispatch(makeBringOutYourDead());
|
|
551
|
+
const retires = log.filter(e => e.type === 'retireImports');
|
|
552
|
+
|
|
553
|
+
t.deepEqual(retires, [{ type: 'retireImports', slots: [presenceBvref] }]);
|
|
554
|
+
|
|
555
|
+
// If the bug is present, the vat won't send `syscall.retireImports`
|
|
556
|
+
// to the kernel. In a full system, that means the kernel can
|
|
557
|
+
// eventually send a `dispatch.retireImports` into the vat, if/when
|
|
558
|
+
// the object's hosting vat decides to drop it. Make sure that won't
|
|
559
|
+
// cause a crash.
|
|
560
|
+
|
|
561
|
+
if (!retires.length) {
|
|
562
|
+
console.log(`testing kernel's dispatch.retireImports`);
|
|
563
|
+
await dispatch(makeRetireImports(presenceBvref));
|
|
564
|
+
console.log(`dispatch.retireImports did not crash`);
|
|
565
|
+
}
|
|
566
|
+
});
|
|
@@ -308,6 +308,14 @@ test.serial('GC dispatch.dropExports', async t => {
|
|
|
308
308
|
// that should allow ex1 to be collected
|
|
309
309
|
t.true(collected.result);
|
|
310
310
|
|
|
311
|
+
// upon collection, the vat should scan for local recognizers (weak
|
|
312
|
+
// collection keys) in case any need to be dropped, and find none
|
|
313
|
+
t.deepEqual(log.shift(), {
|
|
314
|
+
type: 'vatstoreGetNextKey',
|
|
315
|
+
priorKey: `vom.ir.${ex1}|`,
|
|
316
|
+
result: 'vom.rc.o+d6/1',
|
|
317
|
+
});
|
|
318
|
+
|
|
311
319
|
// and once it's collected, the vat should emit `syscall.retireExport`
|
|
312
320
|
// because nobody else will be able to recognize it again
|
|
313
321
|
const l2 = log.shift();
|
|
@@ -394,8 +402,16 @@ test.serial(
|
|
|
394
402
|
// which should let the export be collected
|
|
395
403
|
t.true(collected.result);
|
|
396
404
|
|
|
397
|
-
// the vat should
|
|
398
|
-
//
|
|
405
|
+
// the vat should scan for local recognizers (weak collection
|
|
406
|
+
// keys) in case any need to be dropped, and find none
|
|
407
|
+
t.deepEqual(log.shift(), {
|
|
408
|
+
type: 'vatstoreGetNextKey',
|
|
409
|
+
priorKey: 'vom.ir.o+10|',
|
|
410
|
+
result: 'vom.rc.o+d6/1',
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
// the vat should *not* emit `syscall.retireExport`, because it
|
|
414
|
+
// already received a dispatch.retireExport
|
|
399
415
|
t.deepEqual(log, []);
|
|
400
416
|
},
|
|
401
417
|
);
|
package/test/liveslots.test.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
2
|
import test from 'ava';
|
|
3
3
|
|
|
4
|
+
import { Fail } from '@endo/errors';
|
|
4
5
|
import { E } from '@endo/eventual-send';
|
|
5
6
|
import { Far } from '@endo/marshal';
|
|
6
7
|
import { makePromiseKit } from '@endo/promise-kit';
|
|
7
|
-
import { Fail } from '@agoric/assert';
|
|
8
8
|
import { kslot, kser, kunser } from '@agoric/kmarshal';
|
|
9
9
|
import { M } from '@agoric/store';
|
|
10
10
|
import { makeLiveSlots, makeMarshaller } from '../src/liveslots.js';
|