From c7febd81c7d53a307a34e95258f38fdd458bf886 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Fri, 16 Jul 2021 19:12:46 -0700 Subject: [PATCH] chore(swingset): add failing test of #3482 object retention The new test, 'forward to fake zoe', mimics an execution pathway in the tap-fungible-faucet load-generator task. In the loadgen task, the client asks a fungible-faucet contract (an instance of packages/zoe/src/contracts/mintPayments.js) for an Invitation. The method in the faucet contract immediately sends off a requests to Zoe (through the zcf facet) and returns the result Promise. In my analysis of the loadgen slogfile, the Invitation object (a Zoe Invitation payment) is imported into the faucet contract vat, sent back out again as the resolution of its result promise, but then never dropped. I see no good reason for the faucet contract to hold onto the object: the code doesn't even have a place to put it. I initially thought this was a problem with XS (#3406), but when I reproduced the issue in a unit test and changed it to use a Node.js worker, the problem remained. We traced it down to a problem in liveslots (#3482), which will be fixed by the upcoming commit. This test will fail until that commit. The test first talks to a fake Zoe vat to export the simulated Invitation object and learn its kref. Then it instructs the bootstrap vat to ask vat-target for an invitation, and vat-target delegates to vat-fake-zoe. Once the kernel is done, and vat-target should have dropped the kref, the test examines the clists. The test would pass if the vat-target clist did not include the Invitation object's kref. Instead, vat-target still references the kref, so the test fails. --- packages/SwingSet/test/gc/bootstrap.js | 6 ++ packages/SwingSet/test/gc/test-gc-vat.js | 76 +++++++++++++++++++++++- packages/SwingSet/test/gc/vat-target.js | 4 ++ 3 files changed, 85 insertions(+), 1 deletion(-) diff --git a/packages/SwingSet/test/gc/bootstrap.js b/packages/SwingSet/test/gc/bootstrap.js index 21517092dcad..5e5255a8feb4 100644 --- a/packages/SwingSet/test/gc/bootstrap.js +++ b/packages/SwingSet/test/gc/bootstrap.js @@ -5,10 +5,12 @@ export function buildRootObject() { let A = Far('A', { hello() {} }); let B = Far('B', { hello() {} }); let target; + let zoe; return Far('root', { async bootstrap(vats) { target = vats.target; + zoe = vats.zoe; }, async one() { await E(target).two(A, B); @@ -17,5 +19,9 @@ export function buildRootObject() { A = null; B = null; }, + + async makeInvitation0() { + await E(target).makeInvitationTarget(zoe); + }, }); } diff --git a/packages/SwingSet/test/gc/test-gc-vat.js b/packages/SwingSet/test/gc/test-gc-vat.js index d89e6c7599a3..ef8ad1d9888c 100644 --- a/packages/SwingSet/test/gc/test-gc-vat.js +++ b/packages/SwingSet/test/gc/test-gc-vat.js @@ -16,6 +16,20 @@ function dumpObjects(c) { return out; } +function dumpClist(c) { + // returns array like [ko27/v3/o+1, ..] + return c.dump().kernelTable.map(e => `${e[0]}/${e[1]}/${e[2]}`); +} + +function findClist(c, vatID, kref) { + for (const e of c.dump().kernelTable) { + if (e[0] === kref && e[1] === vatID) { + return e[2]; + } + } + return undefined; +} + async function dropPresence(t, dropExport) { const config = { bootstrap: 'bootstrap', @@ -78,4 +92,64 @@ async function dropPresence(t, dropExport) { } test('drop presence (export retains)', t => dropPresence(t, false)); -test.skip('drop presence (export drops)', t => dropPresence(t, true)); +test('drop presence (export drops)', t => dropPresence(t, true)); + +test('forward to fake zoe', async t => { + const config = { + bootstrap: 'bootstrap', + vats: { + bootstrap: { + sourceSpec: path.join(__dirname, 'bootstrap.js'), + }, + target: { + sourceSpec: path.join(__dirname, 'vat-target.js'), + // creationOptions: { managerType: 'xs-worker' }, + creationOptions: { managerType: 'local' }, + }, + zoe: { + sourceSpec: path.join(__dirname, 'vat-fake-zoe.js'), + }, + }, + }; + const hostStorage = provideHostStorage(); + await initializeSwingset(config, [], hostStorage); + const c = await makeSwingsetController(hostStorage); + c.pinVatRoot('bootstrap'); + const targetID = c.vatNameToID('target'); + c.pinVatRoot('target'); + const zoeID = c.vatNameToID('zoe'); + c.pinVatRoot('zoe'); + t.teardown(c.shutdown); + await c.run(); + + // first we ask vat-fake-zoe for the invitation object, to learn its kref + + const r1 = c.queueToVatRoot('zoe', 'makeInvitationZoe', capargs([])); + await c.run(); + const invitation = c.kpResolution(r1).slots[0]; + // ko27/v3/o+1 is the export + console.log(`invitation: ${invitation}`); + console.log(`targetID: ${targetID}`); + + // confirm that zoe is exporting it + t.is(findClist(c, zoeID, invitation), 'o+1'); + t.true(dumpClist(c).includes(`${invitation}/${zoeID}/o+1`)); + // confirm that vat-target has not seen it yet + t.is(findClist(c, targetID, invitation), undefined); + + // console.log(c.dump().kernelTable); + console.log(`calling makeInvitation`); + + // Then we ask bootstrap to ask vat-target to ask vat-fake-zoe for the + // invitation. We try to mimic the pattern used by a simple + // tap-fungible-faucet loadgen task, which is where I observed XS not + // releasing the invitation object. + + c.queueToVatRoot('bootstrap', 'makeInvitation0', capargs([])); + await c.run(); + // console.log(c.dump().kernelTable); + + // vat-target should have learned about the invitation object, resolved the + // 'makeInvitationTarget' result promise with it, then dropped it + t.is(findClist(c, targetID, invitation), undefined); +}); diff --git a/packages/SwingSet/test/gc/vat-target.js b/packages/SwingSet/test/gc/vat-target.js index 02e76ab71b67..a0e8f886bec6 100644 --- a/packages/SwingSet/test/gc/vat-target.js +++ b/packages/SwingSet/test/gc/vat-target.js @@ -7,5 +7,9 @@ export function buildRootObject() { // A=ko26 B=ko27 await E(A).hello(B); }, + + makeInvitationTarget(zoe) { + return E(zoe).makeInvitationZoe(); + }, }); }