@agoric/swingset-liveslots 0.10.3-dev-bdbd5df.0 → 0.10.3-dev-962e6c4.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agoric/swingset-liveslots",
3
- "version": "0.10.3-dev-bdbd5df.0+bdbd5df",
3
+ "version": "0.10.3-dev-962e6c4.0+962e6c4",
4
4
  "description": "SwingSet ocap support layer",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -19,8 +19,8 @@
19
19
  "postpack": "git clean -f ':!src/types-index.d.ts' '*.d.ts*' '*.tsbuildinfo'"
20
20
  },
21
21
  "dependencies": {
22
- "@agoric/internal": "0.3.3-dev-bdbd5df.0+bdbd5df",
23
- "@agoric/store": "0.9.3-dev-bdbd5df.0+bdbd5df",
22
+ "@agoric/internal": "0.3.3-dev-962e6c4.0+962e6c4",
23
+ "@agoric/store": "0.9.3-dev-962e6c4.0+962e6c4",
24
24
  "@endo/env-options": "^1.1.8",
25
25
  "@endo/errors": "^1.2.9",
26
26
  "@endo/eventual-send": "^1.3.0",
@@ -34,7 +34,7 @@
34
34
  "@endo/promise-kit": "^1.1.9"
35
35
  },
36
36
  "devDependencies": {
37
- "@agoric/kmarshal": "0.1.1-dev-bdbd5df.0+bdbd5df",
37
+ "@agoric/kmarshal": "0.1.1-dev-962e6c4.0+962e6c4",
38
38
  "ava": "^5.3.0"
39
39
  },
40
40
  "files": [
@@ -68,5 +68,5 @@
68
68
  "typeCoverage": {
69
69
  "atLeast": 75.24
70
70
  },
71
- "gitHead": "bdbd5df2e3fc45803658717b723a0f20e2c6379a"
71
+ "gitHead": "962e6c4bec8123a60fed4673fa04f53622d4c082"
72
72
  }
package/src/liveslots.js CHANGED
@@ -797,6 +797,7 @@ function build(
797
797
  for (const resolution of resolutions) {
798
798
  const [xvpid] = resolution;
799
799
  maybeNewVPIDs.delete(xvpid);
800
+ unregisterUnreferencedVPID(xvpid);
800
801
  }
801
802
  }
802
803
  for (const newVPID of Array.from(maybeNewVPIDs).sort()) {
@@ -1012,14 +1013,13 @@ function build(
1012
1013
  for (const resolution of resolutions) {
1013
1014
  const [xvpid] = resolution;
1014
1015
  maybeNewVPIDs.delete(xvpid);
1016
+ unregisterUnreferencedVPID(xvpid);
1015
1017
  }
1016
1018
  // track everything that's left
1017
1019
  for (const newVPID of Array.from(maybeNewVPIDs).sort()) {
1018
1020
  maybeExportPromise(newVPID);
1019
1021
  }
1020
1022
 
1021
- // only the primary can possibly be newly resolved
1022
- unregisterUnreferencedVPID(vpid);
1023
1023
  exportedVPIDs.delete(vpid);
1024
1024
  }
1025
1025
 
@@ -45,34 +45,50 @@ const buildPromiseWatcherRootObject = (vatPowers, vatParameters, baggage) => {
45
45
 
46
46
  const root = Far('root', {
47
47
  getPromise: name => {
48
- const promise = knownPromises.get(name);
49
- promise || Fail`promise doesn't exists: ${name}`;
48
+ knownPromises.has(name) || Fail`promise not found: ${name}`;
49
+ const { promise } = knownPromises.get(name);
50
50
  return { promise };
51
51
  },
52
52
  importPromise: (name, promise) => {
53
53
  !knownPromises.has(name) || Fail`promise already exists: ${name}`;
54
- knownPromises.set(name, promise);
54
+ knownPromises.set(name, { promise });
55
55
  return `imported promise: ${name}`;
56
56
  },
57
57
  createLocalPromise: (name, fulfillment, rejection) => {
58
58
  !knownPromises.has(name) || Fail`promise already exists: ${name}`;
59
59
  const { promise, resolve, reject } = makePromiseKit();
60
+ let resolvers = {};
60
61
  if (fulfillment !== undefined) {
61
62
  resolve(fulfillment);
62
63
  } else if (rejection !== undefined) {
63
64
  reject(rejection);
65
+ } else {
66
+ resolvers = { resolve, reject };
64
67
  }
65
- knownPromises.set(name, promise);
68
+ knownPromises.set(name, { promise, ...resolvers });
66
69
  return `created local promise: ${name}`;
67
70
  },
71
+ resolveLocalPromise: (name, rejection, value) => {
72
+ knownPromises.has(name) || Fail`promise not found: ${name}`;
73
+ const { resolve, reject, promise } = knownPromises.get(name);
74
+ (resolve && reject) || Fail`promise not resolvable: ${name}`;
75
+ (rejection ? reject : resolve)(value);
76
+ knownPromises.set(name, { promise });
77
+ return `resolved promise: ${name}`;
78
+ },
68
79
  watchPromise: name => {
69
80
  knownPromises.has(name) || Fail`promise not found: ${name}`;
70
- watchPromise(knownPromises.get(name), watcher, name);
81
+ watchPromise(knownPromises.get(name).promise, watcher, name);
71
82
  return `watched promise: ${name}`;
72
83
  },
73
84
  getWatchResolution: name => {
74
85
  return watchResolutions.get(name);
75
86
  },
87
+ sendToPromise: (name, method, ...args) => {
88
+ knownPromises.has(name) || Fail`promise not found: ${name}`;
89
+ const { promise } = knownPromises.get(name);
90
+ return HandledPromise.applyMethod(promise, method, args);
91
+ },
76
92
  });
77
93
 
78
94
  const startOperations = vatParameters?.startOperations || [];
@@ -132,6 +148,29 @@ const kvStoreDataV1KeysToDelete = ['vc.4.sp+6', 'vc.4.sp-9'];
132
148
  const kvStoreDataV1VpidsToKeep = ['p-8'];
133
149
  const kvStoreDataV1KeysToKeep = ['vc.4.sp-8'];
134
150
 
151
+ // Ignore vatstore syscalls.
152
+ const extractDispatchLogs = vatLogs =>
153
+ vatLogs.splice(0).filter(m => !m.type.startsWith('vatstore'));
154
+
155
+ const settlementMessage = (vpid, rejected, value) => ({
156
+ type: 'resolve',
157
+ resolutions: [[vpid, rejected, kser(value)]],
158
+ });
159
+ const fulfillmentMessage = (vpid, value) =>
160
+ settlementMessage(vpid, false, value);
161
+ const rejectionMessage = (vpid, value) => settlementMessage(vpid, true, value);
162
+ const subscribeMessage = vpid => ({
163
+ type: 'subscribe',
164
+ target: vpid,
165
+ });
166
+
167
+ /** @param {`p-${number}` | `p+${number}`} p */
168
+ const extractPNum = p => {
169
+ const r = /^p[-+](\d+)$/.exec(p);
170
+ if (!r) throw Fail`Invalid promise ${p}`;
171
+ return parseInt(r[1], 10);
172
+ };
173
+
135
174
  test('past-incarnation watched promises', async t => {
136
175
  const S = 'settlement';
137
176
  // Anchor promise counters upon which the other assertions depend.
@@ -139,14 +178,14 @@ test('past-incarnation watched promises', async t => {
139
178
  // cf. src/liveslots.js:initialIDCounters
140
179
  const firstPExport = 5;
141
180
 
142
- const startImportedP = firstPImport - 2;
181
+ const startImportedP = `p-${firstPImport - 2}`;
143
182
 
144
183
  const v1StartOperations = [
145
184
  ['createLocalPromise', 'start orphaned'],
146
185
  ['watchPromise', 'start orphaned'],
147
186
  ['createLocalPromise', 'start fulfilled', S],
148
187
  ['watchPromise', 'start fulfilled'],
149
- ['importPromise', 'start imported', kslot(`p-${startImportedP}`)],
188
+ ['importPromise', 'start imported', kslot(startImportedP)],
150
189
  ['watchPromise', 'start imported'],
151
190
  ];
152
191
  const kvStore = new Map();
@@ -166,172 +205,133 @@ test('past-incarnation watched promises', async t => {
166
205
  );
167
206
  let vatLogs = v.log;
168
207
 
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);
208
+ /** @type {`p-${number}`} */
209
+ let rp = `p-0`;
176
210
  /** @type {typeof rawDispatch} */
177
- const dispatchMessage = (...args) => {
178
- dispatches += 1;
179
- return rawDispatch(...args);
211
+ const dispatchMessage = async (...args) => {
212
+ rp = /** @type {`p-${number}`} */ (await rawDispatch(...args));
213
+ return rp;
180
214
  };
181
- const recordExportedPromise = name => {
182
- exportedPromises.add(name);
183
- return name;
215
+ let lastPExport = firstPExport - 1;
216
+ const exportedPromises = new Map();
217
+ const recordNextExportedPromise = name => {
218
+ const p = `p+${(lastPExport += 1)}`;
219
+ exportedPromises.set(p, name);
220
+ return p;
221
+ };
222
+ const recordExportedPromiseNotification = p => {
223
+ t.true(exportedPromises.delete(p));
224
+ return p;
184
225
  };
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
226
 
201
227
  // 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),
228
+ const startOrphanedP = recordNextExportedPromise('start orphaned');
229
+ const startFulfilledP = recordNextExportedPromise('start fulfilled');
230
+ t.deepEqual(extractDispatchLogs(vatLogs), [
231
+ subscribeMessage(startImportedP),
232
+ subscribeMessage(startOrphanedP),
233
+ subscribeMessage(startFulfilledP),
234
+ fulfillmentMessage(startFulfilledP, S),
211
235
  ]);
212
236
  await dispatchMessage('createLocalPromise', 'exported', S);
213
- t.deepEqual(getDispatchLogs(), [
214
- fulfillmentMessage(`p-${nextPImport()}`, 'created local promise: exported'),
237
+ t.deepEqual(extractDispatchLogs(vatLogs), [
238
+ fulfillmentMessage(rp, 'created local promise: exported'),
215
239
  ]);
216
- await dispatchMessage('getPromise', recordExportedPromise('exported'));
217
- t.deepEqual(getDispatchLogs(), [
218
- fulfillmentMessage(`p-${nextPImport()}`, {
219
- promise: kslot(`p+${nextPExport()}`),
240
+ await dispatchMessage('getPromise', 'exported');
241
+ const exportedP = recordNextExportedPromise('exported');
242
+ t.deepEqual(extractDispatchLogs(vatLogs), [
243
+ fulfillmentMessage(rp, {
244
+ promise: kslot(exportedP),
220
245
  }),
221
- fulfillmentMessage(`p+${lastPExport}`, S),
246
+ fulfillmentMessage(recordExportedPromiseNotification(exportedP), S),
222
247
  ]);
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'),
248
+ const importedP = `p-${firstPImport - 1}`;
249
+ await dispatchMessage('importPromise', 'imported', kslot(importedP));
250
+ t.deepEqual(extractDispatchLogs(vatLogs), [
251
+ subscribeMessage(importedP),
252
+ fulfillmentMessage(rp, 'imported promise: imported'),
228
253
  ]);
229
254
  await dispatchMessage('createLocalPromise', 'orphaned');
230
- t.deepEqual(getDispatchLogs(), [
231
- fulfillmentMessage(`p-${nextPImport()}`, 'created local promise: orphaned'),
255
+ t.deepEqual(extractDispatchLogs(vatLogs), [
256
+ fulfillmentMessage(rp, 'created local promise: orphaned'),
232
257
  ]);
233
258
  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}`),
259
+ t.deepEqual(extractDispatchLogs(vatLogs), [
260
+ fulfillmentMessage(rp, 'created local promise: orphaned exported'),
261
+ ]);
262
+ await dispatchMessage('getPromise', 'orphaned exported');
263
+ const orphanedExportedP = recordNextExportedPromise('orphaned exported');
264
+ t.deepEqual(extractDispatchLogs(vatLogs), [
265
+ fulfillmentMessage(rp, {
266
+ promise: kslot(orphanedExportedP),
247
267
  }),
248
268
  ]);
249
269
  await dispatchMessage('createLocalPromise', 'fulfilled', S);
250
- t.deepEqual(getDispatchLogs(), [
251
- fulfillmentMessage(
252
- `p-${nextPImport()}`,
253
- 'created local promise: fulfilled',
254
- ),
270
+ t.deepEqual(extractDispatchLogs(vatLogs), [
271
+ fulfillmentMessage(rp, 'created local promise: fulfilled'),
255
272
  ]);
256
273
  await dispatchMessage('createLocalPromise', 'rejected', undefined, S);
257
- t.deepEqual(getDispatchLogs(), [
258
- fulfillmentMessage(`p-${nextPImport()}`, 'created local promise: rejected'),
274
+ t.deepEqual(extractDispatchLogs(vatLogs), [
275
+ fulfillmentMessage(rp, 'created local promise: rejected'),
259
276
  ]);
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
277
 
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'),
278
+ await dispatchMessage('watchPromise', 'orphaned');
279
+ const orphanedP = recordNextExportedPromise('orphaned');
280
+ t.deepEqual(extractDispatchLogs(vatLogs), [
281
+ subscribeMessage(orphanedP),
282
+ fulfillmentMessage(rp, 'watched promise: orphaned'),
276
283
  ]);
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),
284
+ await dispatchMessage('watchPromise', 'fulfilled');
285
+ const fulfilledP = recordNextExportedPromise('fulfilled');
286
+ t.deepEqual(extractDispatchLogs(vatLogs), [
287
+ subscribeMessage(fulfilledP),
288
+ fulfillmentMessage(rp, 'watched promise: fulfilled'),
289
+ fulfillmentMessage(recordExportedPromiseNotification(fulfilledP), S),
282
290
  ]);
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),
291
+ await dispatchMessage('watchPromise', 'rejected');
292
+ const rejectedP = recordNextExportedPromise('rejected');
293
+ t.deepEqual(extractDispatchLogs(vatLogs), [
294
+ subscribeMessage(rejectedP),
295
+ fulfillmentMessage(rp, 'watched promise: rejected'),
296
+ rejectionMessage(recordExportedPromiseNotification(rejectedP), S),
288
297
  ]);
289
298
  await dispatchMessage('watchPromise', 'imported');
290
- t.deepEqual(getDispatchLogs(), [
299
+ t.deepEqual(extractDispatchLogs(vatLogs), [
291
300
  // no subscribe, we already did at import
292
- fulfillmentMessage(`p-${nextPImport()}`, 'watched promise: imported'),
301
+ fulfillmentMessage(rp, 'watched promise: imported'),
293
302
  ]);
294
303
  await dispatchMessage('getWatchResolution', 'fulfilled');
295
- t.deepEqual(getDispatchLogs(), [
296
- fulfillmentMessage(`p-${nextPImport()}`, {
304
+ t.deepEqual(extractDispatchLogs(vatLogs), [
305
+ fulfillmentMessage(rp, {
297
306
  status: 'fulfilled',
298
307
  value: S,
299
308
  }),
300
309
  ]);
301
310
  await dispatchMessage('getWatchResolution', 'rejected');
302
- t.deepEqual(getDispatchLogs(), [
303
- fulfillmentMessage(`p-${nextPImport()}`, {
311
+ t.deepEqual(extractDispatchLogs(vatLogs), [
312
+ fulfillmentMessage(rp, {
304
313
  status: 'rejected',
305
314
  reason: S,
306
315
  }),
307
316
  ]);
308
317
  await dispatchMessage('getWatchResolution', 'start fulfilled');
309
- t.deepEqual(getDispatchLogs(), [
310
- fulfillmentMessage(`p-${nextPImport()}`, {
318
+ t.deepEqual(extractDispatchLogs(vatLogs), [
319
+ fulfillmentMessage(rp, {
311
320
  status: 'fulfilled',
312
321
  value: S,
313
322
  }),
314
323
  ]);
315
324
 
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
- );
325
+ const v2FirstPromise = extractPNum(rp) + 1;
326
326
 
327
327
  // Simulate upgrade by starting from the non-empty kvStore.
328
328
  // t.log(Object.fromEntries([...kvStore.entries()].sort(compareEntriesByKey)));
329
329
  const clonedStore = new Map(kvStore);
330
- const startImported2P = firstPImport - 3;
330
+ const startImported2P = `p-${firstPImport - 3}`;
331
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
332
+ ['importPromise', 'start imported 2', kslot(startImported2P)], // import of new promise
333
+ ['importPromise', 'imported', kslot(importedP)], // import previously imported and watched promise
334
+ ['importPromise', 'orphaned exported', kslot(orphanedExportedP)], // import previously exported but unwatched promise
335
335
  ['watchPromise', 'orphaned exported'],
336
336
  ];
337
337
  ({
@@ -344,45 +344,44 @@ test('past-incarnation watched promises', async t => {
344
344
  'durable-promise-watcher-v2',
345
345
  {
346
346
  kvStore: clonedStore,
347
- nextPromiseImportNumber: lastPImport + 1,
347
+ nextPromiseImportNumber: v2FirstPromise,
348
348
  vatParameters: { startOperations: v2StartOperations },
349
349
  },
350
350
  ));
351
351
  vatLogs = v.log;
352
352
 
353
353
  // startVat logs
354
- t.deepEqual(getDispatchLogs(), [
355
- subscribeMessage(`p-${startImported2P}`),
356
- subscribeMessage(`p-${importedP}`),
357
- subscribeMessage(`p+${orphanedExportedP}`),
354
+ t.deepEqual(extractDispatchLogs(vatLogs), [
355
+ subscribeMessage(startImported2P),
356
+ subscribeMessage(importedP),
357
+ subscribeMessage(orphanedExportedP),
358
358
  ]);
359
359
  // Simulate kernel rejection of promises orphaned by termination/upgrade of their decider vat.
360
360
  const expectedDeletions = [...clonedStore.entries()].filter(entry =>
361
361
  entry[1].includes('orphaned'),
362
362
  );
363
363
  t.true(expectedDeletions.length >= 1);
364
- for (const orphanedPExport of v1OrphanedPExports) {
365
- await dispatch(
366
- makeReject(`p+${orphanedPExport}`, kser('tomorrow never came')),
367
- );
364
+ for (const [orphanedPExport] of exportedPromises) {
365
+ await dispatch(makeReject(orphanedPExport, kser('tomorrow never came')));
368
366
  }
367
+ exportedPromises.clear();
369
368
  await dispatchMessage('getWatchResolution', 'orphaned');
370
- t.deepEqual(getDispatchLogs(), [
371
- fulfillmentMessage(`p-${nextPImport()}`, {
369
+ t.deepEqual(extractDispatchLogs(vatLogs), [
370
+ fulfillmentMessage(rp, {
372
371
  status: 'rejected',
373
372
  reason: 'tomorrow never came',
374
373
  }),
375
374
  ]);
376
375
  await dispatchMessage('getWatchResolution', 'start orphaned');
377
- t.deepEqual(getDispatchLogs(), [
378
- fulfillmentMessage(`p-${nextPImport()}`, {
376
+ t.deepEqual(extractDispatchLogs(vatLogs), [
377
+ fulfillmentMessage(rp, {
379
378
  status: 'rejected',
380
379
  reason: 'tomorrow never came',
381
380
  }),
382
381
  ]);
383
382
  await dispatchMessage('getWatchResolution', 'orphaned exported');
384
- t.deepEqual(getDispatchLogs(), [
385
- fulfillmentMessage(`p-${nextPImport()}`, {
383
+ t.deepEqual(extractDispatchLogs(vatLogs), [
384
+ fulfillmentMessage(rp, {
386
385
  status: 'rejected',
387
386
  reason: 'tomorrow never came',
388
387
  }),
@@ -391,46 +390,50 @@ test('past-incarnation watched promises', async t => {
391
390
  t.false(clonedStore.has(key), `entry should be removed: ${key}: ${value}`);
392
391
  }
393
392
  // Simulate resolution of imported promises watched in previous incarnation
394
- await dispatch(makeResolve(`p-${importedP}`, kser(undefined)));
393
+ await dispatch(makeResolve(importedP, kser(undefined)));
395
394
  await dispatchMessage('getWatchResolution', 'imported');
396
- t.deepEqual(getDispatchLogs(), [
397
- fulfillmentMessage(`p-${nextPImport()}`, {
395
+ t.deepEqual(extractDispatchLogs(vatLogs), [
396
+ fulfillmentMessage(rp, {
398
397
  status: 'fulfilled',
399
398
  value: undefined,
400
399
  }),
401
400
  ]);
402
- await dispatch(makeResolve(`p-${startImportedP}`, kser(undefined)));
401
+ await dispatch(makeResolve(startImportedP, kser(undefined)));
403
402
  await dispatchMessage('getWatchResolution', 'start imported');
404
- t.deepEqual(getDispatchLogs(), [
405
- fulfillmentMessage(`p-${nextPImport()}`, {
403
+ t.deepEqual(extractDispatchLogs(vatLogs), [
404
+ fulfillmentMessage(rp, {
406
405
  status: 'fulfilled',
407
406
  value: undefined,
408
407
  }),
409
408
  ]);
410
- await dispatch(makeResolve(`p-${startImported2P}`, kser(undefined)));
409
+ await dispatch(makeResolve(startImported2P, kser(undefined)));
411
410
  await dispatchMessage('getWatchResolution', 'start imported 2');
412
- t.deepEqual(getDispatchLogs(), [
413
- fulfillmentMessage(`p-${nextPImport()}`, undefined),
411
+ t.deepEqual(extractDispatchLogs(vatLogs), [
412
+ fulfillmentMessage(rp, undefined),
414
413
  ]);
415
414
  // simulate resolution of imported promise watched after resolution
416
415
  await dispatchMessage('watchPromise', 'start imported 2');
417
- t.deepEqual(getDispatchLogs(), [
416
+ const startImported2ReexportedP =
417
+ recordNextExportedPromise('start imported 2');
418
+ t.deepEqual(extractDispatchLogs(vatLogs), [
418
419
  // Promise was previously resolved, so it is re-exported
419
- subscribeMessage(`p+${nextPExport()}`),
420
+ subscribeMessage(startImported2ReexportedP),
421
+ fulfillmentMessage(rp, 'watched promise: start imported 2'),
420
422
  fulfillmentMessage(
421
- `p-${nextPImport()}`,
422
- 'watched promise: start imported 2',
423
+ recordExportedPromiseNotification(startImported2ReexportedP),
424
+ undefined,
423
425
  ),
424
- fulfillmentMessage(`p+${lastPExport}`, undefined),
425
426
  ]);
426
427
  await dispatchMessage('getWatchResolution', 'start imported 2');
427
- t.deepEqual(getDispatchLogs(), [
428
- fulfillmentMessage(`p-${nextPImport()}`, {
428
+ t.deepEqual(extractDispatchLogs(vatLogs), [
429
+ fulfillmentMessage(rp, {
429
430
  status: 'fulfilled',
430
431
  value: undefined,
431
432
  }),
432
433
  ]);
433
434
 
435
+ const finalFirstPromise = extractPNum(rp) + 1;
436
+
434
437
  // Verify that the data is still in loadable condition.
435
438
  const finalClonedStore = new Map(clonedStore);
436
439
  ({
@@ -441,19 +444,25 @@ test('past-incarnation watched promises', async t => {
441
444
  t,
442
445
  buildPromiseWatcherRootObject,
443
446
  'durable-promise-watcher-final',
444
- { kvStore: finalClonedStore, nextPromiseImportNumber: lastPImport + 1 },
447
+ { kvStore: finalClonedStore, nextPromiseImportNumber: finalFirstPromise },
445
448
  ));
446
449
  vatLogs = v.log;
447
450
  vatLogs.length = 0;
451
+ t.deepEqual([...exportedPromises], [], 'exportedPromises is empty');
448
452
  await dispatchMessage('createLocalPromise', 'final', S);
453
+ t.deepEqual(extractDispatchLogs(vatLogs), [
454
+ fulfillmentMessage(rp, 'created local promise: final'),
455
+ ]);
449
456
  await dispatchMessage('watchPromise', 'final');
457
+ const finalP = recordNextExportedPromise('final');
458
+ t.deepEqual(extractDispatchLogs(vatLogs), [
459
+ subscribeMessage(finalP),
460
+ fulfillmentMessage(rp, 'watched promise: final'),
461
+ fulfillmentMessage(recordExportedPromiseNotification(finalP), S),
462
+ ]);
450
463
  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()}`, {
464
+ t.deepEqual(extractDispatchLogs(vatLogs), [
465
+ fulfillmentMessage(rp, {
457
466
  status: 'fulfilled',
458
467
  value: S,
459
468
  }),
@@ -504,3 +513,361 @@ test('past-incarnation watched promises from original-format kvStore', async t =
504
513
  t.false(finalClonedStore.has(key), `key should be removed: ${key}`);
505
514
  }
506
515
  });
516
+
517
+ test('watched local promises should not leak slotToVal entries', async t => {
518
+ const S = 'settlement';
519
+ // cf. src/liveslots.js:initialIDCounters
520
+ const firstPExport = 5;
521
+
522
+ const {
523
+ v: { log: vatLogs },
524
+ dispatchMessage,
525
+ testHooks,
526
+ } = await setupTestLiveslots(t, buildPromiseWatcherRootObject, 'vatA');
527
+ const { slotToVal } = testHooks;
528
+ const initial = slotToVal.size;
529
+
530
+ let lastPExport = firstPExport - 1;
531
+ const nextPExport = () => `p+${(lastPExport += 1)}`;
532
+
533
+ let rp;
534
+
535
+ // Watch already resolved promise
536
+ rp = await dispatchMessage('createLocalPromise', 'p1', S);
537
+ t.deepEqual(extractDispatchLogs(vatLogs), [
538
+ fulfillmentMessage(rp, 'created local promise: p1'),
539
+ ]);
540
+ rp = await dispatchMessage('watchPromise', 'p1');
541
+ const p1 = nextPExport();
542
+ t.deepEqual(extractDispatchLogs(vatLogs), [
543
+ subscribeMessage(p1),
544
+ fulfillmentMessage(rp, 'watched promise: p1'),
545
+ fulfillmentMessage(p1, S),
546
+ ]);
547
+ t.is(slotToVal.size, initial); // exported promise did not leak
548
+ rp = await dispatchMessage('getWatchResolution', 'p1');
549
+ t.deepEqual(extractDispatchLogs(vatLogs), [
550
+ fulfillmentMessage(rp, {
551
+ status: 'fulfilled',
552
+ value: S,
553
+ }),
554
+ ]);
555
+
556
+ // Watch subsequently resolved promise
557
+ rp = await dispatchMessage('createLocalPromise', 'p2');
558
+ t.deepEqual(extractDispatchLogs(vatLogs), [
559
+ fulfillmentMessage(rp, 'created local promise: p2'),
560
+ ]);
561
+ t.is(slotToVal.size, initial);
562
+
563
+ rp = await dispatchMessage('watchPromise', 'p2');
564
+ const p2 = nextPExport();
565
+ t.deepEqual(extractDispatchLogs(vatLogs), [
566
+ subscribeMessage(p2),
567
+ fulfillmentMessage(rp, 'watched promise: p2'),
568
+ ]);
569
+ t.is(slotToVal.size, initial + 1); // exported promise
570
+
571
+ rp = await dispatchMessage('resolveLocalPromise', 'p2', false, S);
572
+ t.deepEqual(extractDispatchLogs(vatLogs), [
573
+ fulfillmentMessage(p2, S),
574
+ fulfillmentMessage(rp, 'resolved promise: p2'),
575
+ ]);
576
+ t.is(slotToVal.size, initial); // exported promise did not leak
577
+
578
+ rp = await dispatchMessage('getWatchResolution', 'p2');
579
+ t.deepEqual(extractDispatchLogs(vatLogs), [
580
+ fulfillmentMessage(rp, {
581
+ status: 'fulfilled',
582
+ value: S,
583
+ }),
584
+ ]);
585
+ });
586
+
587
+ // Remaining case for https://github.com/Agoric/agoric-sdk/issues/10756
588
+ // The workaround doesn't handle this case because it learns about the
589
+ // settlement before the virtual object system
590
+ // See https://github.com/Agoric/agoric-sdk/issues/10757
591
+ // prettier-ignore
592
+ test.failing('watched imported promises should not leak slotToVal entries', async t => {
593
+ const S = 'settlement';
594
+ // cf. src/liveslots.js:initialIDCounters
595
+ const firstPExport = 5;
596
+
597
+ const {
598
+ v: { log: vatLogs },
599
+ dispatch,
600
+ dispatchMessage,
601
+ nextPImport,
602
+ testHooks,
603
+ } = await setupTestLiveslots(t, buildPromiseWatcherRootObject, 'vatA');
604
+ const { slotToVal } = testHooks;
605
+ const initial = slotToVal.size;
606
+
607
+ let lastPExport = firstPExport - 1;
608
+ const nextPExport = () => `p+${(lastPExport += 1)}`;
609
+
610
+ let rp;
611
+
612
+ // Watch already imported promise
613
+ const importedP = nextPImport();
614
+ rp = await dispatchMessage('importPromise', 'importedP', kslot(importedP));
615
+ t.deepEqual(extractDispatchLogs(vatLogs), [
616
+ subscribeMessage(importedP),
617
+ fulfillmentMessage(rp, 'imported promise: importedP'),
618
+ ]);
619
+ t.is(slotToVal.size, initial + 1); // imported promise
620
+
621
+ rp = await dispatchMessage('watchPromise', 'importedP');
622
+ t.deepEqual(extractDispatchLogs(vatLogs), [
623
+ fulfillmentMessage(rp, 'watched promise: importedP'),
624
+ ]);
625
+ t.is(slotToVal.size, initial + 1); // imported promise
626
+
627
+ await dispatch(makeResolve(importedP, kser(S)));
628
+ t.deepEqual(extractDispatchLogs(vatLogs), []);
629
+ t.is(slotToVal.size, initial); // should not leak
630
+
631
+ rp = await dispatchMessage('getPromise', 'importedP');
632
+ const reexportedP = nextPExport(); // Should allocate a new exported promise
633
+ t.deepEqual(extractDispatchLogs(vatLogs), [
634
+ fulfillmentMessage(rp, { promise: kslot(reexportedP) }),
635
+ fulfillmentMessage(reexportedP, S),
636
+ ]);
637
+ t.is(slotToVal.size, initial); // reexported promise did not leak
638
+ });
639
+
640
+ test('known imported promises in resolutions should not leak slotToVal entries', async t => {
641
+ const S = 'settlement';
642
+ // cf. src/liveslots.js:initialIDCounters
643
+ const firstPExport = 5;
644
+
645
+ const {
646
+ v: { log: vatLogs },
647
+ dispatch,
648
+ dispatchMessage,
649
+ nextPImport,
650
+ testHooks,
651
+ } = await setupTestLiveslots(t, buildPromiseWatcherRootObject, 'vatA');
652
+ const { slotToVal } = testHooks;
653
+ const initial = slotToVal.size;
654
+
655
+ let lastPExport = firstPExport - 1;
656
+ const nextPExport = () => `p+${(lastPExport += 1)}`;
657
+
658
+ let rp;
659
+
660
+ const importedP = nextPImport();
661
+ rp = await dispatchMessage('importPromise', 'importedP', kslot(importedP));
662
+ t.deepEqual(extractDispatchLogs(vatLogs), [
663
+ subscribeMessage(importedP),
664
+ fulfillmentMessage(rp, 'imported promise: importedP'),
665
+ ]);
666
+ t.is(slotToVal.size, initial + 1); // imported promise
667
+
668
+ rp = await dispatchMessage('getPromise', 'importedP');
669
+ t.deepEqual(extractDispatchLogs(vatLogs), [
670
+ fulfillmentMessage(rp, { promise: kslot(importedP) }),
671
+ ]);
672
+ t.is(slotToVal.size, initial + 1); // imported promise
673
+
674
+ await dispatch(makeResolve(importedP, kser(S)));
675
+ t.deepEqual(extractDispatchLogs(vatLogs), []);
676
+ t.is(slotToVal.size, initial); // promise no longer imported
677
+
678
+ // The first getPromise causes liveslots to learn about the resolution value
679
+ // of importedP since liveslots currently only tracks known promise
680
+ // resolutions on export.
681
+ rp = await dispatchMessage('getPromise', 'importedP');
682
+ const reexportedP = nextPExport();
683
+ t.deepEqual(extractDispatchLogs(vatLogs), [
684
+ fulfillmentMessage(rp, { promise: kslot(reexportedP) }),
685
+ fulfillmentMessage(reexportedP, S), // liveslots learns about the resolution
686
+ ]);
687
+ t.is(slotToVal.size, initial);
688
+
689
+ // The second getPromise verifies that liveslots does not leak when notifying
690
+ // a known resolved promise. It allocates a new exported promise because the
691
+ // resolution was previously notified
692
+ rp = await dispatchMessage('getPromise', 'importedP');
693
+ const reexportedP2 = nextPExport(); // allocates a new export promise
694
+ t.deepEqual(extractDispatchLogs(vatLogs), [
695
+ {
696
+ type: 'resolve',
697
+ resolutions: [
698
+ [rp, false, kser({ promise: kslot(reexportedP2) })],
699
+ [reexportedP2, false, kser(S)], // one shot resolution notification
700
+ ],
701
+ },
702
+ ]);
703
+ t.is(slotToVal.size, initial); // did not leak reexportedP2
704
+
705
+ // The 3rd getPromise ensures that the 2nd fully cleaned up, and that liveslots
706
+ // doesn't attempt to re-use a the previously exported promise
707
+ rp = await dispatchMessage('getPromise', 'importedP');
708
+ const reexportedP3 = nextPExport(); // allocates a new export promise
709
+ t.deepEqual(extractDispatchLogs(vatLogs), [
710
+ {
711
+ type: 'resolve',
712
+ resolutions: [
713
+ [rp, false, kser({ promise: kslot(reexportedP3) })],
714
+ [reexportedP3, false, kser(S)], // still one shot resolution
715
+ ],
716
+ },
717
+ ]);
718
+ t.is(slotToVal.size, initial); // did not leak reexportedP3
719
+ });
720
+
721
+ test('known exported promises in resolutions should not leak slotToVal entries', async t => {
722
+ const S = 'settlement';
723
+ // cf. src/liveslots.js:initialIDCounters
724
+ const firstPExport = 5;
725
+
726
+ const {
727
+ v: { log: vatLogs },
728
+ dispatchMessage,
729
+ testHooks,
730
+ } = await setupTestLiveslots(t, buildPromiseWatcherRootObject, 'vatA');
731
+ const { slotToVal } = testHooks;
732
+ const initial = slotToVal.size;
733
+
734
+ let lastPExport = firstPExport - 1;
735
+ const nextPExport = () => `p+${(lastPExport += 1)}`;
736
+
737
+ let rp;
738
+
739
+ rp = await dispatchMessage('createLocalPromise', 'localP');
740
+ t.deepEqual(extractDispatchLogs(vatLogs), [
741
+ fulfillmentMessage(rp, 'created local promise: localP'),
742
+ ]);
743
+ t.is(slotToVal.size, initial);
744
+
745
+ rp = await dispatchMessage('getPromise', 'localP');
746
+ const localP = nextPExport();
747
+ t.deepEqual(extractDispatchLogs(vatLogs), [
748
+ fulfillmentMessage(rp, { promise: kslot(localP) }),
749
+ ]);
750
+ t.is(slotToVal.size, initial + 1); // exported promise
751
+
752
+ rp = await dispatchMessage('resolveLocalPromise', 'localP', false, S);
753
+ t.deepEqual(extractDispatchLogs(vatLogs), [
754
+ fulfillmentMessage(localP, S),
755
+ fulfillmentMessage(rp, 'resolved promise: localP'),
756
+ ]);
757
+ t.is(slotToVal.size, initial); // promise no longer exported
758
+
759
+ // The first getPromise verifies that liveslots does not leak when notifying
760
+ // a known resolved promise. It allocates a new exported promise because the
761
+ // resolution was previously notified
762
+ rp = await dispatchMessage('getPromise', 'localP');
763
+ const reexportedP = nextPExport();
764
+ t.deepEqual(extractDispatchLogs(vatLogs), [
765
+ {
766
+ type: 'resolve',
767
+ resolutions: [
768
+ [rp, false, kser({ promise: kslot(reexportedP) })],
769
+ [reexportedP, false, kser(S)], // one shot resolution notification
770
+ ],
771
+ },
772
+ ]);
773
+ t.is(slotToVal.size, initial);
774
+
775
+ // The second getPromise ensures that previous fully cleaned up, and that
776
+ // liveslots doesn't attempt to re-use a the previously exported promise
777
+ rp = await dispatchMessage('getPromise', 'localP');
778
+ const reexportedP2 = nextPExport(); // allocates a new export promise
779
+ t.deepEqual(extractDispatchLogs(vatLogs), [
780
+ {
781
+ type: 'resolve',
782
+ resolutions: [
783
+ [rp, false, kser({ promise: kslot(reexportedP2) })],
784
+ [reexportedP2, false, kser(S)], // still one shot resolution
785
+ ],
786
+ },
787
+ ]);
788
+ t.is(slotToVal.size, initial); // did not leak reexportedP2
789
+ });
790
+
791
+ test('known promises in message sends should not leak slotToVal entries', async t => {
792
+ const S = 'settlement';
793
+ // cf. src/liveslots.js:initialIDCounters
794
+ const firstPExport = 5;
795
+
796
+ const targetO = `o-1`;
797
+
798
+ const {
799
+ v: { log: vatLogs },
800
+ dispatch,
801
+ dispatchMessage,
802
+ testHooks,
803
+ } = await setupTestLiveslots(t, buildPromiseWatcherRootObject, 'vatA', {
804
+ vatParameters: {
805
+ startOperations: [['createLocalPromise', 'target', kslot(targetO)]],
806
+ },
807
+ });
808
+ const { slotToVal } = testHooks;
809
+ const initial = slotToVal.size;
810
+
811
+ let lastPExport = firstPExport - 1;
812
+ const nextPExport = () => `p+${(lastPExport += 1)}`;
813
+
814
+ let rp;
815
+
816
+ rp = await dispatchMessage('createLocalPromise', 'targetP');
817
+ t.deepEqual(extractDispatchLogs(vatLogs), [
818
+ fulfillmentMessage(rp, 'created local promise: targetP'),
819
+ ]);
820
+ t.is(slotToVal.size, initial);
821
+
822
+ rp = await dispatchMessage('createLocalPromise', 'localP');
823
+ t.deepEqual(extractDispatchLogs(vatLogs), [
824
+ fulfillmentMessage(rp, 'created local promise: localP'),
825
+ ]);
826
+ t.is(slotToVal.size, initial);
827
+
828
+ rp = await dispatchMessage('getPromise', 'localP');
829
+ const localP = nextPExport();
830
+ t.deepEqual(extractDispatchLogs(vatLogs), [
831
+ fulfillmentMessage(rp, { promise: kslot(localP) }),
832
+ ]);
833
+ t.is(slotToVal.size, initial + 1); // localP promise
834
+
835
+ rp = await dispatchMessage('sendToPromise', 'targetP', 'foo', kslot(localP));
836
+ const sendToPromiseResultP = rp;
837
+ t.deepEqual(extractDispatchLogs(vatLogs), []);
838
+ t.is(slotToVal.size, initial + 2); // localP and sendToPromiseResultP promises
839
+
840
+ rp = await dispatchMessage('resolveLocalPromise', 'localP', false, S);
841
+ t.deepEqual(extractDispatchLogs(vatLogs), [
842
+ fulfillmentMessage(localP, S),
843
+ fulfillmentMessage(rp, 'resolved promise: localP'),
844
+ ]);
845
+ t.is(slotToVal.size, initial + 1); // sendToPromiseResultP promise
846
+
847
+ rp = await dispatchMessage(
848
+ 'resolveLocalPromise',
849
+ 'targetP',
850
+ false,
851
+ kslot(targetO),
852
+ );
853
+ const reexportedP = nextPExport(); // Allocate a new promise for importedP
854
+ const sendResultP = nextPExport();
855
+ t.deepEqual(extractDispatchLogs(vatLogs), [
856
+ fulfillmentMessage(rp, 'resolved promise: targetP'), // no await so first syscall
857
+ {
858
+ type: 'send',
859
+ targetSlot: targetO,
860
+ methargs: kser(['foo', [kslot(reexportedP)]]),
861
+ resultSlot: sendResultP,
862
+ },
863
+ fulfillmentMessage(reexportedP, S), // known resolutions comes before subscribe of send result
864
+ subscribeMessage(sendResultP),
865
+ ]);
866
+ t.is(slotToVal.size, initial + 2); // sendToPromiseResultP and sendResultP promises
867
+
868
+ await dispatch(makeResolve(sendResultP, kser(S)));
869
+ t.deepEqual(extractDispatchLogs(vatLogs), [
870
+ fulfillmentMessage(sendToPromiseResultP, S),
871
+ ]);
872
+ t.is(slotToVal.size, initial);
873
+ });
@@ -57,6 +57,7 @@ export function setupTestLiveslots(t: import("ava").ExecutionContext, buildRootO
57
57
  dispatchDropExports: (...vrefs: any[]) => Promise<void>;
58
58
  dispatchRetireExports: (...vrefs: any[]) => Promise<void>;
59
59
  dispatchRetireImports: (...vrefs: any[]) => Promise<void>;
60
+ nextPImport: () => string;
60
61
  testHooks: any;
61
62
  }>;
62
63
  export function findSyscallsByType(log: any, type: any): any;
@@ -1 +1 @@
1
- {"version":3,"file":"liveslots-helpers.d.ts","sourceRoot":"","sources":["liveslots-helpers.js"],"names":[],"mappings":"AAgBA;;;;GAIG;AACH,uCAHG;IAA0B,WAAW;IACC,OAAO;CAAC;;;;;;;;;;;;;;;;EA2GhD;AAED;;;GA2BC;AAUD;;;;;;;;;;GAUG;AACH,sCAVW,OAAO,KAAK,EAAE,gBAAgB,sCAE9B,MAAM,YAEd;IAA0B,OAAO;IACK,OAAO;IACpB,uBAAuB;IACtB,WAAW;IACf,aAAa,GAA3B,GAAG;CAAyB;;;;;;;;;;;;;;GA4GtC;AAED,6DAEC"}
1
+ {"version":3,"file":"liveslots-helpers.d.ts","sourceRoot":"","sources":["liveslots-helpers.js"],"names":[],"mappings":"AAgBA;;;;GAIG;AACH,uCAHG;IAA0B,WAAW;IACC,OAAO;CAAC;;;;;;;;;;;;;;;;EA2GhD;AAED;;;GA2BC;AAUD;;;;;;;;;;GAUG;AACH,sCAVW,OAAO,KAAK,EAAE,gBAAgB,sCAE9B,MAAM,YAEd;IAA0B,OAAO;IACK,OAAO;IACpB,uBAAuB;IACtB,WAAW;IACf,aAAa,GAA3B,GAAG;CAAyB;;;;;;;;;;;;;;;GA6GtC;AAED,6DAEC"}
@@ -278,6 +278,7 @@ export async function setupTestLiveslots(
278
278
  dispatchDropExports,
279
279
  dispatchRetireExports,
280
280
  dispatchRetireImports,
281
+ nextPImport: nextRP,
281
282
  testHooks,
282
283
  };
283
284
  }
@@ -1,42 +0,0 @@
1
- import test from 'ava';
2
-
3
- import { Far } from '@endo/marshal';
4
- import { makePromiseKit } from '@endo/promise-kit';
5
- import { setupTestLiveslots } from './liveslots-helpers.js';
6
-
7
- const build = vatPowers => {
8
- const { VatData } = vatPowers;
9
- const { makeKindHandle, defineDurableKind, watchPromise } = VatData;
10
-
11
- const kh = makeKindHandle('handler');
12
- const init = () => ({});
13
- const behavior = {
14
- onFulfilled: _value => 0,
15
- onRejected: _reason => 0,
16
- };
17
- const makeHandler = defineDurableKind(kh, init, behavior);
18
-
19
- return Far('root', {
20
- async run() {
21
- const pr = makePromiseKit();
22
- const handler = makeHandler();
23
- watchPromise(pr.promise, handler);
24
- pr.resolve('ignored');
25
- },
26
- });
27
- };
28
-
29
- test('watched local promises should not leak slotToVal entries', async t => {
30
- const { dispatchMessage, testHooks } = await setupTestLiveslots(
31
- t,
32
- build,
33
- 'vatA',
34
- );
35
- const { slotToVal } = testHooks;
36
- const initial = slotToVal.size;
37
-
38
- await dispatchMessage('run');
39
- t.is(slotToVal.size, initial);
40
- await dispatchMessage('run');
41
- t.is(slotToVal.size, initial);
42
- });