Skip to content

Commit

Permalink
WIP: failing test
Browse files Browse the repository at this point in the history
refs #9939

WIP: more precise failing test
  • Loading branch information
warner committed Aug 25, 2024
1 parent 7296b47 commit c374e45
Show file tree
Hide file tree
Showing 4 changed files with 178 additions and 1 deletion.
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);
},
});
}
79 changes: 79 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,82 @@ 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('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();
}

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

export function buildRootObject() {
export function buildRootObject(_vatPowers, _vatParameters, baggage) {
let ws = new WeakSet();

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

store: x => {
baggage.init('x', x);
ws.add(x);
},
dummy: () => 0,
drop: () => (ws = undefined),
});
}
84 changes: 84 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,84 @@
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('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);

console.log(`-- deleting myPresence`);
log.length = 0;
gcTools.kill(myPresence);
gcTools.flushAllFRs();
await dispatch(makeBringOutYourDead());

console.log(log);
log.length = 0;

console.log(`-- deleting myVOAwareWeakSet`);
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;
});

0 comments on commit c374e45

Please sign in to comment.