Skip to content

Commit

Permalink
add tests for #9939 to both SwingSet and liveslots
Browse files Browse the repository at this point in the history
These tests will fail without the fixes in the next commit

One new test is disabled because of additional pending bugs that
interfere with the test setup (a combo of #9956 and #9959), which will
be re-enabled in PR #9961 (to address #8756 and others)
  • Loading branch information
warner committed Aug 30, 2024
1 parent e726ff1 commit 4490a73
Show file tree
Hide file tree
Showing 6 changed files with 433 additions and 5 deletions.
5 changes: 5 additions & 0 deletions packages/SwingSet/test/gc/bootstrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,10 @@ export function buildRootObject() {
async makeInvitation0() {
await E(target).makeInvitationTarget(zoe);
},

// for #9939
async storePresenceInWeakSet() {
await E(target).store(A);
},
});
}
81 changes: 81 additions & 0 deletions packages/SwingSet/test/gc/gc-vat.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,3 +158,84 @@ test('forward to fake zoe', async t => {
// 'makeInvitationTarget' result promise with it, then dropped it
t.is(findClist(c, targetID, invitation), undefined);
});

// see #9939
test.failing('drop without retire', async t => {
let targetID;
let didBOYD = false;
// const msgs = ['deliver', 'deliver-result', 'syscall', 'syscall-result'];

function slogSender(slogObj) {
const {
time: _1,
replay: _2,
crankNum: _3,
deliveryNum: _4,
monotime: _5,
...o
} = slogObj;
if (o.vatID !== targetID) return;
if (o.type === 'deliver' && o.kd[0] === 'bringOutYourDead') {
didBOYD = true;
}
// if (msgs.includes(o.type)) console.log(JSON.stringify(o));
}
const config = {
bootstrap: 'bootstrap', // v6
vats: {
bootstrap: {
// v6
sourceSpec: new URL('bootstrap.js', import.meta.url).pathname,
},
target: {
// v1
sourceSpec: new URL('vat-target.js', import.meta.url).pathname,
// avoid V8's GC nondeterminism, only needed on the target vat
creationOptions: { managerType: 'xs-worker' },
},
},
};
const kernelStorage = initSwingStore().kernelStorage;
await initializeSwingset(config, [], kernelStorage);
const c = await makeSwingsetController(kernelStorage, {}, { slogSender });
t.teardown(c.shutdown);

c.pinVatRoot('bootstrap');
targetID = c.vatNameToID('target');
c.pinVatRoot('target');

await c.run();

c.queueToVatRoot('bootstrap', 'storePresenceInWeakSet', []);
await c.run();

// now do enough dummy messages to trigger a new BOYD
didBOYD = false;
while (!didBOYD) {
c.queueToVatRoot('target', 'dummy', []);
await c.run();
}

// now tell vat-target to drop its WeakSet
c.queueToVatRoot('target', 'drop', []);
await c.run();

// and trigger a second BOYD
didBOYD = false;
while (!didBOYD) {
// this will fail once the vat is terminated
try {
c.queueToVatRoot('target', 'dummy', []);
} catch (e) {
if (/vat name "target" must exist/.test(e.message)) {
t.fail('vat terminated, bug is present');
break;
}
}
await c.run();
}
t.true(didBOYD);

// the test passes if the vat survived
return t.pass();
});
14 changes: 13 additions & 1 deletion packages/SwingSet/test/gc/vat-target.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { Far, E } from '@endo/far';

export function buildRootObject() {
export function buildRootObject(_vatPowers, _vatParameters, baggage) {
/** @type { WeakSet | undefined } */
let ws = new WeakSet();

return Far('root', {
async two(A, B) {
// A=ko26 B=ko27
Expand All @@ -10,5 +13,14 @@ export function buildRootObject() {
makeInvitationTarget(zoe) {
return E(zoe).makeInvitationZoe();
},

// these three are for testing #9939
store: x => {
baggage.init('x', x);
assert(ws);
ws.add(x);
},
dummy: () => 0,
drop: () => (ws = undefined),
});
}
80 changes: 80 additions & 0 deletions packages/swingset-liveslots/test/dropped-weakset-9939.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import test from 'ava';
import { Far } from '@endo/marshal';
import { kser, kslot } from '@agoric/kmarshal';
import { makeLiveSlots } from '../src/liveslots.js';
import { buildSyscall } from './liveslots-helpers.js';
import { makeStartVat, makeMessage, makeBringOutYourDead } from './util.js';
import { makeMockGC } from './mock-gc.js';

// Test for https://github.com/Agoric/agoric-sdk/issues/9939

test.failing('weakset deletion vs retire', async t => {
const { syscall, log } = buildSyscall();
const gcTools = makeMockGC();

// #9939 was a bug in liveslots that caused a vat to emit
// syscall.retireImports despite not having done dropImports
// first. The setup is:
//
// * import a Presence (raising the RAM pillar)
// * store it in a virtual object (raising the vdata pillar)
// * use it as a key of a voAwareWeakMap or voAwareWeakSet
// * drop the Presence (dropping the RAM pillar)
// * do a BOYD
// * delete the voAwareWeakSet
// * do a BOYD
//
// When the voAwareWeakSet is collected, a finalizer callback named
// finalizeDroppedCollection is called, which clears the entries,
// and adds all its vref keys to possiblyRetiredSet. Later, during
// BOYD, a loop examines possiblyRetiredSet and adds qualified vrefs
// to importsToRetire, for the syscall.retireImports at the end.
//
// That qualification check was sufficient to prevent the retirement
// of vrefs that still have a RAM pillar, and also vrefs that were
// being dropped in this particular BOYD, but it was not sufficient
// to protect vrefs that still have a vdata pillar.

let myVOAwareWeakSet;
let myPresence;
function buildRootObject(vatPowers, _vatParameters, baggage) {
const { WeakSet } = vatPowers;
myVOAwareWeakSet = new WeakSet();
return Far('root', {
store: p => {
myPresence = p;
myVOAwareWeakSet.add(p);
baggage.init('presence', p);
},
});
}

const makeNS = () => ({ buildRootObject });
const ls = makeLiveSlots(syscall, 'vatA', {}, {}, gcTools, undefined, makeNS);
const { dispatch } = ls;
await dispatch(makeStartVat(kser()));
t.truthy(myVOAwareWeakSet);

await dispatch(makeMessage('o+0', 'store', [kslot('o-1')]));
t.truthy(myPresence);

log.length = 0;
gcTools.kill(myPresence);
gcTools.flushAllFRs();
await dispatch(makeBringOutYourDead());

log.length = 0;
gcTools.kill(myVOAwareWeakSet);
gcTools.flushAllFRs();
await dispatch(makeBringOutYourDead());

// The imported vref is still reachable by the 'baggage' durable
// store, so it must not be dropped or retired yet. The bug caused
// the vref to be retired without first doing a drop, which is a
// vat-fatal syscall error
const gcCalls = log.filter(
l => l.type === 'dropImports' || l.type === 'retireImports',
);
t.deepEqual(gcCalls, []);
log.length = 0;
});
Loading

0 comments on commit 4490a73

Please sign in to comment.