Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add APIs for tracking/debugging undesired object retention (aka "leaks") #7362

Merged
merged 1 commit into from
Apr 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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