Skip to content

Commit

Permalink
feat(swingset): finally activate GC
Browse files Browse the repository at this point in the history
This makes the final step to activate swingset garbage collection (at least
pieces covered by #3106):

* implement kernel-side handling of the three GC syscalls (`dropImports`,
`retireImports`, `retireExports`), some of which happens during translation,
the remainder in `kernelSyscalls.js`
* implement kernel-side translators for the GC deliveries (`dropExports`,
`retireExports`, `retireImports`)
* populate the GC Actions set during `processRefcounts()`
* change `c.step()/run()` to execute any pending GC Action before consulting
the run-queue
* add test-gc-kernel.js to exercise basic checks

Also, a few miscellaneous error messages were improved.

closes #3109
refs #3106
  • Loading branch information
warner committed Jun 1, 2021
1 parent bbe07ef commit 3182072
Show file tree
Hide file tree
Showing 8 changed files with 341 additions and 26 deletions.
27 changes: 27 additions & 0 deletions packages/SwingSet/src/kernel/kernel.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { makeMeterManager } from './metering';
import { makeKernelSyscallHandler, doSend } from './kernelSyscall';
import { makeSlogger, makeDummySlogger } from './slogger';
import { getKpidsToRetire } from './cleanup';
import { processNextGCAction } from './gc-actions';

import { makeVatRootObjectSlot, makeVatLoader } from './loadVat';
import { makeDeviceTranslators } from './deviceTranslator';
Expand Down Expand Up @@ -527,6 +528,24 @@ export default function buildKernel(
}
}

async function processGCMessage(message) {
// used for dropExports, retireExports, and retireImports
const { type, vatID, krefs } = message;
insistVatID(vatID);
const vat = ephemeral.vats.get(vatID);
if (!vat) {
return; // can't collect from the dead
}
const kd = harden([type, krefs]);
if (type === 'retireExports') {
for (const kref of krefs) {
kernelKeeper.deleteKernelObject(kref);
}
}
const vd = vat.translators.kernelDeliveryToVatDelivery(kd);
await deliverAndLogToVat(vatID, kd, vd);
}

async function processCreateVat(message) {
assert(vatAdminRootKref, `initializeKernel did not set vatAdminRootKref`);
const { vatID, source, dynamicOptions } = message;
Expand Down Expand Up @@ -584,6 +603,8 @@ export default function buildKernel(
}
}

const gcMessages = ['dropExports', 'retireExports', 'retireImports'];

let processQueueRunning;
async function processQueueMessage(message) {
kdebug(`processQ ${JSON.stringify(message)}`);
Expand Down Expand Up @@ -611,6 +632,8 @@ export default function buildKernel(
await processNotify(message);
} else if (message.type === 'create-vat') {
await processCreateVat(message);
} else if (gcMessages.includes(message.type)) {
await processGCMessage(message);
} else {
assert.fail(X`unable to process message.type ${message.type}`);
}
Expand Down Expand Up @@ -968,6 +991,10 @@ export default function buildKernel(
}

function getNextMessage() {
const gcMessage = processNextGCAction(kernelKeeper);
if (gcMessage) {
return gcMessage;
}
if (!kernelKeeper.isRunQueueEmpty()) {
return kernelKeeper.getNextMsg();
}
Expand Down
18 changes: 15 additions & 3 deletions packages/SwingSet/src/kernel/kernelSyscall.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,19 +117,31 @@ export function makeKernelSyscallHandler(tools) {

function dropImports(koids) {
assert(Array.isArray(koids), X`dropImports given non-Array ${koids}`);
console.log(`-- kernel ignoring dropImports ${koids.join(',')}`);
// all the work was done during translation, there's nothing to do here
console.log(`-- kernel did dropImports ${koids.join(',')}`);
return OKNULL;
}

function retireImports(koids) {
assert(Array.isArray(koids), X`retireImports given non-Array ${koids}`);
console.log(`-- kernel ignoring retireImports ${koids.join(',')}`);
// all the work was done during translation, there's nothing to do here
console.log(`-- kernel did retireImports ${koids.join(',')}`);
return OKNULL;
}

function retireExports(koids) {
assert(Array.isArray(koids), X`retireExports given non-Array ${koids}`);
console.log(`-- kernel ignoring retireExports ${koids.join(',')}`);
console.log(`-- kernel doing retireExports ${koids.join(',')}`);
const newActions = [];
for (const koid of koids) {
const importers = kernelKeeper.getImporters(koid);
for (const vatID of importers) {
newActions.push(`${vatID} retireImport ${koid}`);
// TODO: decref and delete any #2069 auxdata
kernelKeeper.deleteKernelObject(koid);
}
}
kernelKeeper.addGCActions(newActions);
return OKNULL;
}

Expand Down
6 changes: 3 additions & 3 deletions packages/SwingSet/src/kernel/liveSlots.js
Original file line number Diff line number Diff line change
Expand Up @@ -837,7 +837,7 @@ function build(
assert(Array.isArray(vrefs));
vrefs.map(vref => insistVatType('object', vref));
vrefs.map(vref => assert(parseVatSlot(vref).allocatedByVat));
console.log(`-- liveslots acting upon dropExports`);
console.log(`-- liveslots acting upon dropExports ${vrefs.join(',')}`);
for (const vref of vrefs) {
const wr = slotToVal.get(vref);
const o = wr && wr.deref();
Expand All @@ -851,14 +851,14 @@ function build(
assert(Array.isArray(vrefs));
vrefs.map(vref => insistVatType('object', vref));
vrefs.map(vref => assert(parseVatSlot(vref).allocatedByVat));
console.log(`-- liveslots ignoring retireExports`);
console.log(`-- liveslots ignoring retireExports ${vrefs.join(',')}`);
}

function retireImports(vrefs) {
assert(Array.isArray(vrefs));
vrefs.map(vref => insistVatType('object', vref));
vrefs.map(vref => assert(!parseVatSlot(vref).allocatedByVat));
console.log(`-- liveslots ignoring retireImports`);
console.log(`-- liveslots ignoring retireImports ${vrefs.join(',')}`);
}

// TODO: when we add notifyForward, guard against cycles
Expand Down
2 changes: 1 addition & 1 deletion packages/SwingSet/src/kernel/state/kernelKeeper.js
Original file line number Diff line number Diff line change
Expand Up @@ -828,7 +828,7 @@ export default function makeKernelKeeper(kvStore, streamStore, kernelSlog) {
}
}
}
// setGCActions(actions); // disabled for now
setGCActions(actions);
}
maybeFreeKrefs.clear();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ function makeSupervisorDispatch(dispatch) {
() => harden(['ok', null, null]),
err => {
// TODO react more thoughtfully, maybe terminate the vat
console.log(`error ${err} during vat dispatch of ${delivery}`);
console.log(`error ${err} during vat dispatch() of ${delivery}`);
return harden(['error', `${err.message}`, null]);
},
);
Expand Down
84 changes: 68 additions & 16 deletions packages/SwingSet/src/kernel/vatTranslator.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,17 +84,58 @@ function makeTranslateKernelDeliveryToVatDelivery(vatID, kernelKeeper) {
return vatDelivery;
}

function translateDropExports(krefs) {
const vrefs = krefs.map(kref => mapKernelSlotToVatSlot(kref, false));
krefs.map(vatKeeper.clearReachableFlag);
const vatDelivery = harden(['dropExports', vrefs]);
return vatDelivery;
}

function translateRetireExports(krefs) {
const vrefs = [];
for (const kref of krefs) {
const vref = mapKernelSlotToVatSlot(kref, false);
// 'false' means skip the decref, the object is already deleted
vatKeeper.deleteCListEntry(kref, vref, false);
vrefs.push(vref);
}
const vatDelivery = harden(['retireExports', vrefs]);
return vatDelivery;
}

function translateRetireImports(krefs) {
const vrefs = [];
for (const kref of krefs) {
const vref = mapKernelSlotToVatSlot(kref, false);
vatKeeper.deleteCListEntry(kref, vref, false);
vrefs.push(vref);
}
const vatDelivery = harden(['retireImports', vrefs]);
return vatDelivery;
}

function kernelDeliveryToVatDelivery(kd) {
const [type, ...args] = kd;
switch (type) {
case 'message':
return translateMessage(...args);
case 'notify':
return translateNotify(...args);
case 'dropExports':
return translateDropExports(...args);
case 'retireExports':
return translateRetireExports(...args);
case 'retireImports':
return translateRetireImports(...args);
default:
assert.fail(X`unknown kernelDelivery.type ${type}`);
}
// returns ['message', target, msg] or ['notify', resolutions] or null
// returns one of:
// ['message', target, msg]
// ['notify', resolutions]
// ['dropExports', vrefs]
// ['retireExports', vrefs]
// ['retireImports', vrefs]
}

return kernelDeliveryToVatDelivery;
Expand All @@ -106,7 +147,7 @@ function makeTranslateKernelDeliveryToVatDelivery(vatID, kernelKeeper) {
*/
function makeTranslateVatSyscallToKernelSyscall(vatID, kernelKeeper) {
const vatKeeper = kernelKeeper.getVatKeeper(vatID);
const { mapVatSlotToKernelSlot } = vatKeeper;
const { mapVatSlotToKernelSlot, clearReachableFlag } = vatKeeper;

function translateSend(targetSlot, msg) {
assert.typeof(targetSlot, 'string', 'non-string targetSlot');
Expand Down Expand Up @@ -186,39 +227,50 @@ function makeTranslateVatSyscallToKernelSyscall(vatID, kernelKeeper) {

function translateDropImports(vrefs) {
assert(Array.isArray(vrefs), X`dropImports() given non-Array ${vrefs}`);
// TODO: dropImports does not affect the c-list, but makes the kernel
// calculate the refcount of a kref, possibly causing further action.
const krefs = vrefs.map(vref => {
insistVatType('object', vref); // TODO: probably device nodes too
const kref = mapVatSlotToKernelSlot(vref);
const { type, allocatedByVat } = parseVatSlot(vref);
assert.equal(type, 'object');
assert.equal(allocatedByVat, false); // drop *imports*, not exports
// we translate into krefs, *not* setting the reachable flag
const kref = mapVatSlotToKernelSlot(vref, false);
// and we clear the reachable flag, which might the decrement the
// reachable refcount, which might tag the kref for processing
clearReachableFlag(kref);
return kref;
});
// we've done all the work here, during translation
return harden(['dropImports', krefs]);
}

function translateRetireImports(vrefs) {
assert(Array.isArray(vrefs), X`retireImports() given non-Array ${vrefs}`);
// TODO: retireImports will delete clist entries as we translate, which
// will (TODO) decref the krefs. When we're done with that loop, we hand
// the set of krefs to kernelSyscall so it can (TODO) check
// newly-decremented refcounts against zero, and maybe delete even more.
const krefs = vrefs.map(vref => {
insistVatType('object', vref); // TODO: probably device nodes too
const kref = mapVatSlotToKernelSlot(vref);
// vatKeeper.deleteCListEntry(kref, vref);
const { type, allocatedByVat } = parseVatSlot(vref);
assert.equal(type, 'object');
assert.equal(allocatedByVat, false); // retire *imports*, not exports
const kref = mapVatSlotToKernelSlot(vref, false);
vatKeeper.insistNotReachable(kref);
// 'true' means decref the object
vatKeeper.deleteCListEntry(kref, vref, true);
kernelKeeper.decrementRecognizableCount(kref);
return kref;
});
// we've done all the work here, during translation
return harden(['retireImports', krefs]);
}

function translateRetireExports(vrefs) {
assert(Array.isArray(vrefs), X`retireExports() given non-Array ${vrefs}`);
// TODO: not sure how the kernel should react to this yet
const krefs = vrefs.map(vref => {
insistVatType('object', vref); // TODO: probably device nodes too
const kref = mapVatSlotToKernelSlot(vref);
const { type, allocatedByVat } = parseVatSlot(vref);
assert.equal(type, 'object');
assert.equal(allocatedByVat, true); // retire *exports*, not imports
const kref = mapVatSlotToKernelSlot(vref, false);
vatKeeper.insistNotReachable(kref);
vatKeeper.deleteCListEntry(kref, vref, true);
return kref;
});
// retireExports still has work to do
return harden(['retireExports', krefs]);
}

Expand Down
Loading

0 comments on commit 3182072

Please sign in to comment.