From 3a7793701a86a11847d60e208f7651ff111abbfb Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Fri, 20 Dec 2024 15:41:51 -0800 Subject: [PATCH] chore: add failing test of slotToVal memory leak for watchPromise Liveslots has a bug (#10757) which leaks slotToVal entries when a tracked Promise is still being held in virtual data (e.g. a merely-virtual MapStore) at the time it becomes settled. This is triggered by `watchPromise` because of the order in which we attach two handlers: one which notices the resolution and is inhibited from deleting the slotToVal entry, and a second which removes the Promise from the (virtual) `promiseRegistrations` collection (thus enabling the deletion). For any watched Promise that is resolved, we leave a `slotToVal` entry (with an empty WeakRef) in RAM until the end of the incarnation. This commit adds a test.failing to demonstrate the presence of the bug. Each time we watch and then resolve a promise, the slotToVal table grows by one entry. refs #10756 --- .../test/watch-promise.test.js | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 packages/swingset-liveslots/test/watch-promise.test.js 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..52c4d033599 --- /dev/null +++ b/packages/swingset-liveslots/test/watch-promise.test.js @@ -0,0 +1,42 @@ +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.failing('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); + await dispatchMessage('run'); + t.is(slotToVal.size, initial); +});