diff --git a/packages/swingset-liveslots/src/watchedPromises.js b/packages/swingset-liveslots/src/watchedPromises.js index 799171f5d99..2101354d160 100644 --- a/packages/swingset-liveslots/src/watchedPromises.js +++ b/packages/swingset-liveslots/src/watchedPromises.js @@ -235,13 +235,13 @@ export function makeWatchedPromiseManager({ } else { watchedPromiseTable.init(vpid, harden([[watcher, ...args]])); + promiseRegistrations.init(vpid, p); + pseudoThen(p, vpid); + // Ensure that this vat's promises are rejected at termination. if (maybeExportPromise(vpid)) { syscall.subscribe(vpid); } - - promiseRegistrations.init(vpid, p); - pseudoThen(p, vpid); } }); } diff --git a/packages/swingset-liveslots/test/watch-promise.test.js b/packages/swingset-liveslots/test/watch-promise.test.js new file mode 100644 index 00000000000..7bb4305f899 --- /dev/null +++ b/packages/swingset-liveslots/test/watch-promise.test.js @@ -0,0 +1,40 @@ +import test from 'ava'; + +import { Far } from '@endo/marshal'; +import { makePromiseKit } from '@endo/promise-kit'; +import { setupTestLiveslots } from './liveslots-helpers.js'; + +const build = vatPowers => { + const { VatData } = vatPowers; + const { makeKindHandle, defineDurableKind, watchPromise } = VatData; + + const kh = makeKindHandle('handler'); + const init = () => ({}); + const behavior = { + onFulfilled: _value => 0, + onRejected: _reason => 0, + }; + const makeHandler = defineDurableKind(kh, init, behavior); + + return Far('root', { + async run() { + const pr = makePromiseKit(); + const handler = makeHandler(); + watchPromise(pr.promise, handler); + pr.resolve('ignored'); + }, + }); +}; + +test('watched local promises should not leak slotToVal entries', async t => { + const { dispatchMessage, testHooks } = await setupTestLiveslots( + t, + build, + 'vatA', + ); + const { slotToVal } = testHooks; + const initial = slotToVal.size; + + await dispatchMessage('run'); + t.is(slotToVal.size, initial); +});