Skip to content

Commit

Permalink
fix(swingset): implement dispatch.retireExports for Remotables
Browse files Browse the repository at this point in the history
This updates liveslots to implement kernel-delivered
`dispatch.retireExports`, but only when the object being retired is a
Remotable (virtual objects are left alone for now).

Retiring an exported Remotable means we remove it from the `slotToVal` and
`valToSlot` tables, and unregister it from the `droppedRegistry`. At this
point, vat code might still hold a strong reference to the Remoteable, but
liveslots is unaware of it. If the vat re-exports the object, it will get a
new vref.
  • Loading branch information
warner committed Jun 14, 2021
1 parent 05f7276 commit e8b0f3a
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 3 deletions.
45 changes: 42 additions & 3 deletions packages/SwingSet/src/kernel/liveSlots.js
Original file line number Diff line number Diff line change
Expand Up @@ -849,11 +849,50 @@ function build(
}
}

function retireOneExport(vref) {
insistVatType('object', vref);
const { virtual, allocatedByVat, type } = parseVatSlot(vref);
assert(allocatedByVat);
assert.equal(type, 'object');
if (virtual) {
// virtual object: ignore for now, but TODO we must still not make
// syscall.retireExport for vrefs that were already retired by the
// kernel
// console.log(`-- liveslots ignoring retireExports ${vref}`);
} else {
// Remotable
// console.log(`-- liveslots acting on retireExports ${vref}`);
const wr = slotToVal.get(vref);
if (wr) {
const val = wr.deref();
if (val) {
// it's fine to still have a value, that just means the kernel
// (and other vats) have completely forgotten about this, but we
// still know about it

if (exportedRemotables.has(val)) {
// however this is weird: we still think the Remotable is
// reachable, otherwise we would have removed it from
// exportedRemotables. The kernel was supposed to send
// dispatch.dropExports first.
console.log(`err: kernel retired undropped ${vref}`);
// TODO: find a way to make this more severe, it's cause for
// panicing the kernel, except that vats don't have that
// authority. It's *not* cause for terminating the vat, since
// it wasn't necessarily our fault.
return;
}
valToSlot.delete(val);
droppedRegistry.unregister(val);
}
slotToVal.delete(vref);
}
}
}

function retireExports(vrefs) {
assert(Array.isArray(vrefs));
vrefs.map(vref => insistVatType('object', vref));
vrefs.map(vref => assert(parseVatSlot(vref).allocatedByVat));
console.log(`-- liveslots ignoring retireExports`);
vrefs.forEach(retireOneExport);
}

function retireImports(vrefs) {
Expand Down
67 changes: 67 additions & 0 deletions packages/SwingSet/test/test-liveslots.js
Original file line number Diff line number Diff line change
Expand Up @@ -1112,3 +1112,70 @@ test('GC dispatch.dropExports', async t => {
});
t.deepEqual(log, []);
});

test('GC dispatch.retireExports inhibits syscall.retireExports', async t => {
const { log, syscall } = buildSyscall();
let wr;
function build(_vatPowers) {
let ex1;
const root = Far('root', {
hold() {
ex1 = Far('export', {});
wr = new WeakRef(ex1);
return ex1;
},
two() {},
drop() {
// eslint-disable-next-line no-unused-vars
ex1 = undefined; // drop the last userspace strongref
},
});
return root;
}
const dispatch = makeDispatch(syscall, build, 'vatA', true);
t.deepEqual(log, []);
const rootA = 'o+0';

// rp1 = root~.hold()
// ex1 = await rp1
const rp1 = 'p-1';
await dispatch(makeMessage(rootA, 'hold', capargs([]), rp1));
const l1 = log.shift();
const ex1 = l1.resolutions[0][2].slots[0];
t.deepEqual(l1, {
type: 'resolve',
resolutions: [[rp1, false, capdataOneSlot(ex1)]],
});
t.deepEqual(log, []);

// the exported Remotable should be held in place by exportedRemotables
// until we tell the vat we don't need it any more
t.truthy(wr.deref());

// an intermediate message will trigger GC, but the presence is still held
await dispatch(makeMessage(rootA, 'two', capargs([])));
t.truthy(wr.deref());

// now tell the vat we don't need a strong reference to that export.
await dispatch(makeDropExports(ex1));

// that removes the liveslots strongref, but the vat's remains in place
t.truthy(wr.deref());

// now the kernel tells the vat we can't even recognize the export
await dispatch(makeRetireExports(ex1));

// that ought to delete the table entry, but doesn't affect the vat
// strongref
t.truthy(wr.deref());

// now tell the vat to drop its strongref
await dispatch(makeMessage(rootA, 'drop', capargs([])));

// which should let the export be collected
t.falsy(wr.deref());

// the vat should *not* emit `syscall.retireExport`, because it already
// received a dispatch.retireExport
t.deepEqual(log, []);
});

0 comments on commit e8b0f3a

Please sign in to comment.