diff --git a/packages/SwingSet/src/kernel/vat-loader/manager-subprocess-xsnap.js b/packages/SwingSet/src/kernel/vat-loader/manager-subprocess-xsnap.js index f08d25ac3318..5855db32d01a 100644 --- a/packages/SwingSet/src/kernel/vat-loader/manager-subprocess-xsnap.js +++ b/packages/SwingSet/src/kernel/vat-loader/manager-subprocess-xsnap.js @@ -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); diff --git a/packages/SwingSet/src/lib/message.js b/packages/SwingSet/src/lib/message.js index 2b74506bc552..75de6c502a3b 100644 --- a/packages/SwingSet/src/lib/message.js +++ b/packages/SwingSet/src/lib/message.js @@ -96,7 +96,6 @@ export function insistVatDeliveryResult(vdr) { const [type, problem, _usage] = vdr; switch (type) { case 'ok': { - assert.equal(problem, null); break; } case 'error': { diff --git a/packages/SwingSet/src/supervisors/supervisor-helper.js b/packages/SwingSet/src/supervisors/supervisor-helper.js index 32274d001efc..4057bfed3c8e 100644 --- a/packages/SwingSet/src/supervisors/supervisor-helper.js +++ b/packages/SwingSet/src/supervisors/supervisor-helper.js @@ -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); diff --git a/packages/swingset-liveslots/src/collectionManager.js b/packages/swingset-liveslots/src/collectionManager.js index 493ed519fb1a..8950b0cc5275 100644 --- a/packages/swingset-liveslots/src/collectionManager.js +++ b/packages/swingset-liveslots/src/collectionManager.js @@ -942,7 +942,11 @@ export function makeCollectionManager( return collectionToWeakSetStore(reanimateCollection(vobjID)); } - const testHooks = { obtainStoreKindID, storeSizeInternal, makeCollection }; + const testHooks = { + obtainStoreKindID, + storeSizeInternal, + makeCollection, + }; /** * @param {Pattern} baseKeyShape @@ -1010,6 +1014,10 @@ export function makeCollectionManager( const makeScalarBigWeakSetStore = (label = 'weakSet', options = {}) => makeBigWeakSetStore(label, narrowKeyShapeOption(M.scalar(), options)); + function getRetentionStats() { + return {}; + } + return harden({ initializeStoreKindInfo, deleteAllVirtualCollections, @@ -1018,6 +1026,7 @@ export function makeCollectionManager( makeScalarBigSetStore, makeScalarBigWeakSetStore, provideBaggage, + getRetentionStats, testHooks, }); } diff --git a/packages/swingset-liveslots/src/liveslots.js b/packages/swingset-liveslots/src/liveslots.js index fbc21f637f47..ce910f287013 100644 --- a/packages/swingset-liveslots/src/liveslots.js +++ b/packages/swingset-liveslots/src/liveslots.js @@ -1342,12 +1342,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) { @@ -1500,6 +1527,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(); } /** @@ -1594,7 +1623,6 @@ function build( return harden({ dispatch, m, - possiblyDeadSet, testHooks, }); } diff --git a/packages/swingset-liveslots/src/types.js b/packages/swingset-liveslots/src/types.js index 06ca26db2e3f..e74e380c8f2f 100644 --- a/packages/swingset-liveslots/src/types.js +++ b/packages/swingset-liveslots/src/types.js @@ -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 * * diff --git a/packages/swingset-liveslots/src/virtualObjectManager.js b/packages/swingset-liveslots/src/virtualObjectManager.js index a267b942b254..29b5360140e9 100644 --- a/packages/swingset-liveslots/src/virtualObjectManager.js +++ b/packages/swingset-liveslots/src/virtualObjectManager.js @@ -1082,6 +1082,9 @@ export function makeVirtualObjectManager( const testHooks = { countWeakKeysForCollection, + + definedDurableKinds, + nextInstanceIDs, }; const flushStateCache = () => { @@ -1090,6 +1093,13 @@ export function makeVirtualObjectManager( } }; + function getRetentionStats() { + return { + definedDurableKinds: definedDurableKinds.size, + nextInstanceIDs: nextInstanceIDs.size, + }; + } + return harden({ initializeKindHandleKind, defineKind, @@ -1101,6 +1111,7 @@ export function makeVirtualObjectManager( VirtualObjectAwareWeakMap, VirtualObjectAwareWeakSet, flushStateCache, + getRetentionStats, testHooks, canBeDurable, }); diff --git a/packages/swingset-liveslots/src/virtualReferences.js b/packages/swingset-liveslots/src/virtualReferences.js index 33b921cbb305..a1fdb030e74a 100644 --- a/packages/swingset-liveslots/src/virtualReferences.js +++ b/packages/swingset-liveslots/src/virtualReferences.js @@ -654,8 +654,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, @@ -679,6 +691,7 @@ export function makeVirtualReferenceManager( possibleVirtualObjectDeath, ceaseRecognition, setDeleteCollectionEntry, + getRetentionStats, testHooks, }); } diff --git a/packages/swingset-liveslots/test/test-liveslots-mock-gc.js b/packages/swingset-liveslots/test/test-liveslots-mock-gc.js index 9df0d4fc9a01..84d8546dbe3a 100644 --- a/packages/swingset-liveslots/test/test-liveslots-mock-gc.js +++ b/packages/swingset-liveslots/test/test-liveslots-mock-gc.js @@ -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 => { @@ -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); @@ -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 @@ -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 @@ -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); +}); diff --git a/packages/swingset-liveslots/test/test-liveslots.js b/packages/swingset-liveslots/test/test-liveslots.js index 40313fee8b29..6e9fc2710c51 100644 --- a/packages/swingset-liveslots/test/test-liveslots.js +++ b/packages/swingset-liveslots/test/test-liveslots.js @@ -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'; diff --git a/packages/swingset-runner/demo/virtualObjectGC/swingset.json b/packages/swingset-runner/demo/virtualObjectGC/swingset.json index 613882ef641d..22a3feae5aee 100644 --- a/packages/swingset-runner/demo/virtualObjectGC/swingset.json +++ b/packages/swingset-runner/demo/virtualObjectGC/swingset.json @@ -5,10 +5,7 @@ "sourceSpec": "bootstrap.js" }, "bob": { - "sourceSpec": "vat-bob.js", - "creationOptions": { - "virtualObjectCacheSize": 0 - } + "sourceSpec": "vat-bob.js" } } } diff --git a/packages/swingset-xsnap-supervisor/lib/supervisor-helper.js b/packages/swingset-xsnap-supervisor/lib/supervisor-helper.js index 831f48c87b6a..79926bbc0cac 100644 --- a/packages/swingset-xsnap-supervisor/lib/supervisor-helper.js +++ b/packages/swingset-xsnap-supervisor/lib/supervisor-helper.js @@ -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);