Skip to content

Commit

Permalink
chore(swingset): add failing test of #3482 object retention
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
warner committed Jul 22, 2021
1 parent 706be79 commit 60ba067
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 1 deletion.
6 changes: 6 additions & 0 deletions packages/SwingSet/test/gc/bootstrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -17,5 +19,9 @@ export function buildRootObject() {
A = null;
B = null;
},

async makeInvitation0() {
await E(target).makeInvitationTarget(zoe);
},
});
}
76 changes: 75 additions & 1 deletion packages/SwingSet/test/gc/test-gc-vat.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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);
});
10 changes: 10 additions & 0 deletions packages/SwingSet/test/gc/vat-fake-zoe.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Far } from '@agoric/marshal';

export function buildRootObject() {
const C = Far('Zoe Invitation payment', { hello() {} });
return Far('root', {
async makeInvitationZoe() {
return C;
},
});
}
4 changes: 4 additions & 0 deletions packages/SwingSet/test/gc/vat-target.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,9 @@ export function buildRootObject() {
// A=ko26 B=ko27
await E(A).hello(B);
},

makeInvitationTarget(zoe) {
return E(zoe).makeInvitationZoe();
},
});
}

0 comments on commit 60ba067

Please sign in to comment.