Skip to content

Commit

Permalink
Merge pull request #7362 from Agoric/7318-leak-diagnostics
Browse files Browse the repository at this point in the history
Add APIs for tracking/debugging undesired object retention (aka "leaks")
  • Loading branch information
mergify[bot] authored Apr 10, 2023
2 parents 1b39051 + 0a7221b commit fb41c31
Show file tree
Hide file tree
Showing 12 changed files with 141 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ export function makeXsSubprocessFactory({
// @ts-ignore I don't know how to appease tsc
const deliverResult = harden([
result.reply[0], // 'ok' or 'error'
result.reply[1] || null, // problem or null
result.reply[1] || null, // results or problem or null
result.meterUsage || null, // meter usage statistics or null
]);
insistVatDeliveryResult(deliverResult);
Expand Down
1 change: 0 additions & 1 deletion packages/SwingSet/src/lib/message.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,6 @@ export function insistVatDeliveryResult(vdr) {
const [type, problem, _usage] = vdr;
switch (type) {
case 'ok': {
assert.equal(problem, null);
break;
}
case 'error': {
Expand Down
2 changes: 1 addition & 1 deletion packages/SwingSet/src/supervisors/supervisor-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ function makeSupervisorDispatch(dispatch) {
return Promise.resolve(delivery)
.then(dispatch)
.then(
() => harden(['ok', null, null]),
res => harden(['ok', res, null]),
err => {
// TODO react more thoughtfully, maybe terminate the vat
console.warn(`error during vat dispatch() of ${delivery}`, err);
Expand Down
11 changes: 10 additions & 1 deletion packages/swingset-liveslots/src/collectionManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -940,7 +940,11 @@ export function makeCollectionManager(
return collectionToWeakSetStore(reanimateCollection(vobjID));
}

const testHooks = { obtainStoreKindID, storeSizeInternal, makeCollection };
const testHooks = {
obtainStoreKindID,
storeSizeInternal,
makeCollection,
};

/**
* @param {Pattern} baseKeyShape
Expand Down Expand Up @@ -1008,6 +1012,10 @@ export function makeCollectionManager(
const makeScalarBigWeakSetStore = (label = 'weakSet', options = {}) =>
makeBigWeakSetStore(label, narrowKeyShapeOption(M.scalar(), options));

function getRetentionStats() {
return {};
}

return harden({
initializeStoreKindInfo,
deleteAllVirtualCollections,
Expand All @@ -1016,6 +1024,7 @@ export function makeCollectionManager(
makeScalarBigSetStore,
makeScalarBigWeakSetStore,
provideBaggage,
getRetentionStats,
testHooks,
});
}
30 changes: 29 additions & 1 deletion packages/swingset-liveslots/src/liveslots.js
Original file line number Diff line number Diff line change
Expand Up @@ -1340,12 +1340,39 @@ function build(
WeakSet: vom.VirtualObjectAwareWeakSet,
});

function getRetentionStats() {
return {
...collectionManager.getRetentionStats(),
...vrm.getRetentionStats(),
...vom.getRetentionStats(),
exportedRemotables: exportedRemotables.size,
importedDevices: importedDevices.size,
kernelRecognizableRemotables: kernelRecognizableRemotables.size,
exportedVPIDs: exportedVPIDs.size,
importedVPIDs: importedVPIDs.size,
possiblyDeadSet: possiblyDeadSet.size,
possiblyRetiredSet: possiblyRetiredSet.size,
slotToVal: slotToVal.size,
};
}

const testHooks = harden({
...vom.testHooks,
...vrm.testHooks,
...collectionManager.testHooks,
setSyscallCapdataLimits,
vatGlobals,

getRetentionStats,
exportedRemotables,
importedDevices,
kernelRecognizableRemotables,
exportedVPIDs,
importedVPIDs,
possiblyDeadSet,
possiblyRetiredSet,
slotToVal,
valToSlot,
});

function setVatOption(option, _value) {
Expand Down Expand Up @@ -1492,6 +1519,8 @@ function build(
await scanForDeadObjects();
// now flush all the vatstore changes (deletions) we made
vom.flushStateCache();
// XXX TODO: make this conditional on a config setting
return getRetentionStats();
}

/**
Expand Down Expand Up @@ -1586,7 +1615,6 @@ function build(
return harden({
dispatch,
m,
possiblyDeadSet,
testHooks,
});
}
Expand Down
2 changes: 1 addition & 1 deletion packages/swingset-liveslots/src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
* } VatDeliveryObject
*
* @typedef { { compute: number } } MeterConsumption
* @typedef { [tag: 'ok', message: null, usage: MeterConsumption | null] |
* @typedef { [tag: 'ok', results: any, usage: MeterConsumption | null] |
* [tag: 'error', message: string, usage: MeterConsumption | null] } VatDeliveryResult
*
*
Expand Down
11 changes: 11 additions & 0 deletions packages/swingset-liveslots/src/virtualObjectManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -1078,6 +1078,9 @@ export function makeVirtualObjectManager(

const testHooks = {
countWeakKeysForCollection,

definedDurableKinds,
nextInstanceIDs,
};

const flushStateCache = () => {
Expand All @@ -1086,6 +1089,13 @@ export function makeVirtualObjectManager(
}
};

function getRetentionStats() {
return {
definedDurableKinds: definedDurableKinds.size,
nextInstanceIDs: nextInstanceIDs.size,
};
}

return harden({
initializeKindHandleKind,
defineKind,
Expand All @@ -1097,6 +1107,7 @@ export function makeVirtualObjectManager(
VirtualObjectAwareWeakMap,
VirtualObjectAwareWeakSet,
flushStateCache,
getRetentionStats,
testHooks,
canBeDurable,
});
Expand Down
13 changes: 13 additions & 0 deletions packages/swingset-liveslots/src/virtualReferences.js
Original file line number Diff line number Diff line change
Expand Up @@ -651,8 +651,20 @@ export function makeVirtualReferenceManager(
const testHooks = {
getReachableRefCount,
countCollectionsForWeakKey,

remotableRefCounts,
vrefRecognizers,
kindInfoTable,
};

function getRetentionStats() {
return {
remotableRefCounts: remotableRefCounts.size,
vrefRecognizers: vrefRecognizers.size,
kindInfoTable: kindInfoTable.size,
};
}

return harden({
droppedCollectionRegistry,
isDurable,
Expand All @@ -676,6 +688,7 @@ export function makeVirtualReferenceManager(
possibleVirtualObjectDeath,
ceaseRecognition,
setDeleteCollectionEntry,
getRetentionStats,
testHooks,
});
}
74 changes: 72 additions & 2 deletions packages/swingset-liveslots/test/test-liveslots-mock-gc.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ import { Far } from '@endo/marshal';
import { makeLiveSlots } from '../src/liveslots.js';
import { kslot, kser } from './kmarshal.js';
import { buildSyscall } from './liveslots-helpers.js';
import { makeMessage, makeStartVat, makeBringOutYourDead } from './util.js';
import {
makeMessage,
makeStartVat,
makeBringOutYourDead,
makeResolve,
} from './util.js';
import { makeMockGC } from './mock-gc.js';

test('dropImports', async t => {
Expand All @@ -31,7 +36,8 @@ test('dropImports', async t => {
const ls = makeLiveSlots(syscall, 'vatA', {}, {}, gcTools, undefined, () => ({
buildRootObject: build,
}));
const { dispatch, possiblyDeadSet } = ls;
const { dispatch, testHooks } = ls;
const { possiblyDeadSet } = testHooks;
await dispatch(makeStartVat(kser()));
const allFRs = gcTools.getAllFRs();
t.is(allFRs.length, 2);
Expand All @@ -45,6 +51,7 @@ test('dropImports', async t => {
// "COLLECTED" state
t.deepEqual(possiblyDeadSet, new Set());
t.is(FR.countCallbacks(), 1);

FR.runOneCallback(); // moves to FINALIZED
t.deepEqual(possiblyDeadSet, new Set(['o-1']));
possiblyDeadSet.delete('o-1'); // pretend liveslots did syscall.dropImport
Expand All @@ -54,6 +61,7 @@ test('dropImports', async t => {
t.deepEqual(possiblyDeadSet, new Set());
t.is(FR.countCallbacks(), 0);
await dispatch(makeMessage(rootA, 'free', []));

t.deepEqual(possiblyDeadSet, new Set());
t.is(FR.countCallbacks(), 1);
FR.runOneCallback(); // moves to FINALIZED
Expand Down Expand Up @@ -147,3 +155,65 @@ test('dropImports', async t => {
t.deepEqual(possiblyDeadSet, new Set());
t.is(FR.countCallbacks(), 0);
});

test('retention counters', async t => {
const { syscall } = buildSyscall();
let held;
const gcTools = makeMockGC();

function buildRootObject(_vatPowers) {
const root = Far('root', {
hold(imp) {
held = imp;
},
exportRemotable() {
return Far('exported', {});
},
});
return root;
}

const makeNS = () => ({ buildRootObject });
const ls = makeLiveSlots(syscall, 'vatA', {}, {}, gcTools, undefined, makeNS);
const { dispatch, testHooks } = ls;
const { getRetentionStats } = testHooks;

const rootA = 'o+0';
const presenceVref = 'o-1';
const promiseVref = 'p-1';
const resultVref = 'p-2';

await dispatch(makeStartVat(kser()));
const count1 = await dispatch(makeBringOutYourDead());
t.deepEqual(count1, getRetentionStats());
t.is(count1.importedVPIDs, 0);
t.is(count1.exportedRemotables, 1);
t.is(count1.kernelRecognizableRemotables, 1);

await dispatch(makeMessage(rootA, 'hold', [kslot(presenceVref)]));
t.truthy(held);

const count2 = await dispatch(makeBringOutYourDead());
t.is(count2.slotToVal, count1.slotToVal + 1);

gcTools.kill(held);
gcTools.flushAllFRs();
const count3 = await dispatch(makeBringOutYourDead());
t.is(count3.slotToVal, count2.slotToVal - 1);

await dispatch(makeMessage(rootA, 'hold', [kslot(promiseVref)]));
const count4 = await dispatch(makeBringOutYourDead());
t.is(count4.slotToVal, count3.slotToVal + 1);
t.is(count4.importedVPIDs, 1);

await dispatch(makeResolve(promiseVref, kser(undefined)));
const count5 = await dispatch(makeBringOutYourDead());
t.is(count5.slotToVal, count4.slotToVal - 1);
t.is(count5.importedVPIDs, 0);

await dispatch(makeMessage(rootA, 'exportRemotable', [], resultVref));
const count6 = await dispatch(makeBringOutYourDead());
t.is(count6.exportedRemotables, 2);
t.is(count6.kernelRecognizableRemotables, 2);
t.is(count6.slotToVal, count5.slotToVal + 1);
});
1 change: 1 addition & 0 deletions packages/swingset-liveslots/test/test-liveslots.js
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,7 @@ test('liveslots vs symbols', async t => {
});
}
const { dispatch } = await makeDispatch(syscall, build);

log.length = 0; // assume pre-build vatstore operations are correct
const rootA = 'o+0';
const target = 'o-1';
Expand Down
5 changes: 1 addition & 4 deletions packages/swingset-runner/demo/virtualObjectGC/swingset.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@
"sourceSpec": "bootstrap.js"
},
"bob": {
"sourceSpec": "vat-bob.js",
"creationOptions": {
"virtualObjectCacheSize": 0
}
"sourceSpec": "vat-bob.js"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ function makeSupervisorDispatch(dispatch) {
return Promise.resolve(delivery)
.then(dispatch)
.then(
() => harden(['ok', null, null]),
res => harden(['ok', res, null]),
err => {
// TODO react more thoughtfully, maybe terminate the vat
console.warn(`error during vat dispatch() of ${delivery}`, err);
Expand Down

0 comments on commit fb41c31

Please sign in to comment.