diff --git a/packages/SwingSet/misc-tools/extract-transcript-from-kerneldb.js b/packages/SwingSet/misc-tools/extract-transcript-from-kerneldb.js index 12ea79b688d..37c8821e8be 100644 --- a/packages/SwingSet/misc-tools/extract-transcript-from-kerneldb.js +++ b/packages/SwingSet/misc-tools/extract-transcript-from-kerneldb.js @@ -139,7 +139,7 @@ if (!vatName) { `Unexpected transcript item at position ${position} (expected to see item position ${expectedPosition})`, ); } - // item is JSON.stringify({ d, syscalls }), syscall is { d, response } + // item is JSON.stringify({ d, sc: syscalls, r }), syscall is { s, r } const t = { transcriptNum, ...JSON.parse(item) }; fs.writeSync(fd, `${JSON.stringify(t)}\n`); transcriptNum += 1; diff --git a/packages/SwingSet/misc-tools/replay-transcript.js b/packages/SwingSet/misc-tools/replay-transcript.js index 4adcdc8316e..ac28f4067a3 100644 --- a/packages/SwingSet/misc-tools/replay-transcript.js +++ b/packages/SwingSet/misc-tools/replay-transcript.js @@ -28,7 +28,7 @@ import { waitUntilQuiescent } from '../src/lib-nodejs/waitUntilQuiescent.js'; import { makeStartXSnap } from '../src/controller/startXSnap.js'; import { makeXsSubprocessFactory } from '../src/kernel/vat-loader/manager-subprocess-xsnap.js'; import { makeLocalVatManagerFactory } from '../src/kernel/vat-loader/manager-local.js'; -import { requireIdentical } from '../src/kernel/vat-loader/transcript.js'; +import { makeSyscallSimulator } from '../src/kernel/vat-warehouse.js'; import { makeDummyMeterControl } from '../src/kernel/dummyMeterControl.js'; import { makeGcAndFinalize } from '../src/lib-nodejs/gc-and-finalize.js'; import engineGC from '../src/lib-nodejs/engine-gc.js'; @@ -324,146 +324,24 @@ async function replay(transcriptFile) { bundleHandler, }); factory = makeXsSubprocessFactory({ - allVatPowers, + startXSnap, kernelKeeper: fakeKernelKeeper, kernelSlog, - startXSnap, testLog, }); } else if (worker === 'local') { factory = makeLocalVatManagerFactory({ allVatPowers, - kernelKeeper: fakeKernelKeeper, vatEndowments: {}, gcTools, - kernelSlog, }); } else { throw Error(`unhandled worker type ${worker}`); } - const [ - bestRequireIdentical, - extraSyscall, - missingSyscall, - vcSyscallRE, - supportsRelaxedSyscalls, - ] = await (async () => { - /** @type {any} */ - const transcriptModule = await import( - '../src/kernel/vat-loader/transcript.js' - ); - - /** @type {RegExp} */ - const syscallRE = - transcriptModule.vcSyscallRE || /^vc\.\d+\.\|(?:schemata|label)$/; - - if ( - typeof transcriptModule.requireIdenticalExceptStableVCSyscalls !== - 'function' - ) { - return [ - requireIdentical, - Symbol('never extra'), - Symbol('never missing'), - syscallRE, - false, - ]; - } - - /** @type {{requireIdenticalExceptStableVCSyscalls: import('../src/kernel/vat-loader/transcript.js').CompareSyscalls}} */ - const { requireIdenticalExceptStableVCSyscalls } = transcriptModule; - - if ( - typeof transcriptModule.extraSyscall === 'symbol' && - typeof transcriptModule.missingSyscall === 'symbol' - ) { - return [ - requireIdenticalExceptStableVCSyscalls, - /** @type {symbol} */ (transcriptModule.extraSyscall), - /** @type {symbol} */ (transcriptModule.missingSyscall), - syscallRE, - true, - ]; - } - - /** @type {unknown} */ - const dynamicExtraSyscall = requireIdenticalExceptStableVCSyscalls( - 'vat0', - ['vatstoreGet', 'vc.0.|label'], - ['vatstoreGet', 'ignoreExtraSyscall'], - ); - /** @type {unknown} */ - const dynamicMissingSyscall = requireIdenticalExceptStableVCSyscalls( - 'vat0', - ['vatstoreGet', 'ignoreMissingSyscall'], - ['vatstoreGet', 'vc.0.|label'], - ); - - return [ - requireIdenticalExceptStableVCSyscalls, - typeof dynamicExtraSyscall === 'symbol' - ? dynamicExtraSyscall - : Symbol('never extra'), - typeof dynamicMissingSyscall === 'symbol' - ? dynamicMissingSyscall - : Symbol('never missing'), - syscallRE, - typeof dynamicExtraSyscall === 'symbol' && - typeof dynamicMissingSyscall === 'symbol', - ]; - })(); - - if ( - (argv.simulateVcSyscalls || argv.skipExtraVcSyscalls) && - !supportsRelaxedSyscalls - ) { - console.warn( - 'Transcript replay does not support relaxed replay. Cannot simulate or skip syscalls', - ); - } - /** @type {Partial, Map>>} */ let syscallResults = {}; - const getResultKind = result => { - if (result === extraSyscall) { - return 'extra'; - } else if (result === missingSyscall) { - return 'missing'; - } else if (result) { - return 'error'; - } else { - return 'success'; - } - }; - - const reportWorkerResult = ({ - xsnapPID, - result, - originalSyscall, - newSyscall, - }) => { - if (!result) return; - if (workers.length <= 1) return; - const resultKind = getResultKind(result); - let kindSummary = syscallResults[resultKind]; - if (!kindSummary) { - /** @type {Map} */ - kindSummary = new Map(); - syscallResults[resultKind] = kindSummary; - } - const syscallKey = JSON.stringify( - resultKind === 'extra' ? originalSyscall : newSyscall, - ); - let workerList = kindSummary.get(syscallKey); - if (!workerList) { - workerList = []; - kindSummary.set(syscallKey, workerList); - } - workerList.push(xsnapPID); - }; - const analyzeSyscallResults = () => { const numWorkers = workers.length; let divergent = false; @@ -515,116 +393,14 @@ async function replay(transcriptFile) { workerData.deliveryTimeSinceLastSnapshot += deliveryTime; }; - /** @type {Map} */ - const knownVCSyscalls = new Map(); - - /** - * @param {import('../src/types-external.js').VatSyscallObject} vso - */ - const vatSyscallHandler = vso => { - if (vso[0] === 'vatstoreGet') { - const response = knownVCSyscalls.get(vso[1]); - - if (!response) { - throw new Error(`Unknown vc vatstore entry ${vso[1]}`); - } - - return response; - } - - throw new Error(`Unexpected syscall ${vso[0]}(${vso.slice(1).join(', ')})`); - }; - - /** - * @param {WorkerData} workerData - * @returns {import('../src/kernel/vat-loader/transcript.js').CompareSyscalls} - */ - const makeCompareSyscalls = workerData => { - const doCompare = ( - _vatID, - originalSyscall, - newSyscall, - originalResponse, - ) => { - const error = bestRequireIdentical(vatID, originalSyscall, newSyscall); - if ( - error && - JSON.stringify(originalSyscall).indexOf('error:liveSlots') !== -1 - ) { - return undefined; // Errors are serialized differently, sometimes - } - - if (error) { - console.error( - `during transcript num= ${lastTranscriptNum} for worker PID ${workerData.xsnapPID} (start delivery ${workerData.firstTranscriptNum})`, - ); - - if ( - // @ts-expect-error may be a symbol in some versions - error === extraSyscall && - !argv.skipExtraVcSyscalls - ) { - return new Error('Extra syscall disallowed'); - } - } - - const newSyscallKind = newSyscall[0]; - - if ( - // @ts-expect-error may be a symbol in some versions - error === missingSyscall && - !argv.simulateVcSyscalls - ) { - return new Error('Missing syscall disallowed'); - } - - if ( - argv.simulateVcSyscalls && - supportsRelaxedSyscalls && - !error && - (newSyscallKind === 'vatstoreGet' || - newSyscallKind === 'vatstoreSet') && - vcSyscallRE.test(newSyscall[1]) - ) { - if (newSyscallKind === 'vatstoreGet') { - if (originalResponse !== undefined) { - knownVCSyscalls.set(newSyscall[1], originalResponse); - } else if (!knownVCSyscalls.has(newSyscall[1])) { - console.warn( - `Cannot store vc syscall result for vatstoreGet(${newSyscall[1]})`, - ); - knownVCSyscalls.set(newSyscall[1], undefined); - } - } else if (newSyscallKind === 'vatstoreSet') { - knownVCSyscalls.set(newSyscall[1], ['ok', newSyscall[2]]); - } - } - - return error; - }; - const compareSyscalls = ( - _vatID, - originalSyscall, - newSyscall, - originalResponse, - ) => { + const wrapWithTimeTracker = (handler, workerData) => { + const wrapped = vso => { updateDeliveryTime(workerData); - const result = doCompare( - _vatID, - originalSyscall, - newSyscall, - originalResponse, - ); - reportWorkerResult({ - xsnapPID: workerData.xsnapPID, - result, - originalSyscall, - newSyscall, - }); + const vsr = handler(vso); workerData.timeOfLastCommand = performance.now(); - return result; + return vsr; }; - return compareSyscalls; + return wrapped; }; let vatParameters; @@ -656,7 +432,6 @@ async function replay(transcriptFile) { /** @type {Partial} */ ({ sourcedConsole: console, vatParameters, - compareSyscalls: makeCompareSyscalls(workerData), useTranscript: true, workerOptions: { type: worker === 'xs-worker' ? 'xsnap' : worker, @@ -672,7 +447,6 @@ async function replay(transcriptFile) { vatSourceBundle, managerOptions, {}, - vatSyscallHandler, ); return workerData; }; @@ -886,7 +660,7 @@ async function replay(transcriptFile) { } } } else { - const { transcriptNum, d: delivery, syscalls } = data; + const { transcriptNum, ...transcriptEntry } = data; lastTranscriptNum = transcriptNum; if (startTranscriptNum == null) { startTranscriptNum = transcriptNum - 1; @@ -914,18 +688,22 @@ async function replay(transcriptFile) { const snapshotIDs = await Promise.all( workers.map(async workerData => { workerData.timeOfLastCommand = performance.now(); - await workerData.manager.replayOneDelivery( - delivery, - syscalls, - transcriptNum, + const deliveryNum = transcriptNum; + const simulator = makeSyscallSimulator( + kernelSlog, + vatID, + deliveryNum, + transcriptEntry, ); + const timeTrackedSim = wrapWithTimeTracker( + simulator.syscallHandler, + workerData, + ); + await workerData.manager.deliver(transcriptEntry.d, timeTrackedSim); updateDeliveryTime(workerData); workerData.firstTranscriptNum ??= transcriptNum - 1; completeWorkerStep(workerData); await workersSynced; - - // console.log(`dr`, dr); - return makeSnapshot ? snapshotWorker(workerData) : null; }), ); diff --git a/packages/SwingSet/src/controller/controller.js b/packages/SwingSet/src/controller/controller.js index 1aaf0cabb97..b4d7411cbca 100644 --- a/packages/SwingSet/src/controller/controller.js +++ b/packages/SwingSet/src/controller/controller.js @@ -86,7 +86,7 @@ function unhandledRejectionHandler(e, pr) { * slogCallbacks?: unknown, * slogSender?: import('@agoric/telemetry').SlogSender, * testTrackDecref?: unknown, - * warehousePolicy?: { maxVatsOnline?: number }, + * warehousePolicy?: import('../types-external.js').VatWarehousePolicy, * overrideVatManagerOptions?: unknown, * spawn?: typeof import('child_process').spawn, * env?: Record, diff --git a/packages/SwingSet/src/controller/initializeKernel.js b/packages/SwingSet/src/controller/initializeKernel.js index 96902c93b08..c6e71c1eebb 100644 --- a/packages/SwingSet/src/controller/initializeKernel.js +++ b/packages/SwingSet/src/controller/initializeKernel.js @@ -8,7 +8,7 @@ import { insistVatID } from '../lib/id.js'; import { kser, kunser } from '../lib/kmarshal.js'; import { makeVatSlot } from '../lib/parseVatSlots.js'; import { insistStorageAPI } from '../lib/storageAPI.js'; -import { makeWorkerOptions } from '../lib/workerOptions.js'; +import { makeVatOptionRecorder } from '../lib/recordVatOptions.js'; import makeKernelKeeper from '../kernel/state/kernelKeeper.js'; import { exportRootObject } from '../kernel/kernel.js'; import { makeKernelQueueHandler } from '../kernel/kernelQueue.js'; @@ -27,6 +27,7 @@ export async function initializeKernel(config, kernelStorage, options = {}) { const kernelSlog = null; const kernelKeeper = makeKernelKeeper(kernelStorage, kernelSlog); + const optionRecorder = makeVatOptionRecorder(kernelKeeper, bundleHandler); const wasInitialized = kernelKeeper.getInitialized(); assert(!wasInitialized); @@ -85,28 +86,14 @@ export async function initializeKernel(config, kernelStorage, options = {}) { 'critical', 'reapInterval', ]); - const { - managerType = kernelKeeper.getDefaultManagerType(), - useTranscript = true, - reapInterval = kernelKeeper.getDefaultReapInterval(), - ...otherOptions - } = creationOptions; - // eslint-disable-next-line @jessie.js/no-nested-await,no-await-in-loop - const workerOptions = await makeWorkerOptions(managerType, bundleHandler); - /** @type {import('../types-internal.js').RecordedVatOptions} */ - const vatOptions = harden({ - name, - useTranscript, - reapInterval, - workerOptions, - ...otherOptions, - }); - const vatID = kernelKeeper.allocateVatIDForNameIfNeeded(name); logStartup(`assigned VatID ${vatID} for genesis vat ${name}`); - const vatKeeper = kernelKeeper.provideVatKeeper(vatID); - vatKeeper.setSourceAndOptions({ bundleID }, vatOptions); - vatKeeper.initializeReapCountdown(reapInterval); + + const source = { bundleID }; + const staticOptions = { name, ...creationOptions }; + // eslint-disable-next-line @jessie.js/no-nested-await,no-await-in-loop + await optionRecorder.recordStatic(vatID, source, staticOptions); + kernelKeeper.addToAcceptanceQueue( harden({ type: 'startVat', vatID, vatParameters: kser(vatParameters) }), ); diff --git a/packages/SwingSet/src/kernel/kernel.js b/packages/SwingSet/src/kernel/kernel.js index 5b911177b00..11228d270ee 100644 --- a/packages/SwingSet/src/kernel/kernel.js +++ b/packages/SwingSet/src/kernel/kernel.js @@ -19,10 +19,8 @@ import { parseVatSlot } from '../lib/parseVatSlots.js'; import { extractSingleSlot, insistCapData } from '../lib/capdata.js'; import { insistMessage, insistVatDeliveryResult } from '../lib/message.js'; import { insistDeviceID, insistVatID } from '../lib/id.js'; -import { - makeWorkerOptions, - updateWorkerOptions, -} from '../lib/workerOptions.js'; +import { updateWorkerOptions } from '../lib/workerOptions.js'; +import { makeVatOptionRecorder } from '../lib/recordVatOptions.js'; import { makeKernelQueueHandler } from './kernelQueue.js'; import { makeKernelSyscallHandler } from './kernelSyscall.js'; import { makeSlogger, makeDummySlogger } from './slogger.js'; @@ -144,6 +142,8 @@ export default function buildKernel( */ const deviceHooks = new Map(); + const optionRecorder = makeVatOptionRecorder(kernelKeeper, bundleHandler); + // This is a low-level output-only string logger used by old unit tests to // see whether vats made progress or not. The array it appends to is // available as c.dump().log . New unit tests should instead use the @@ -687,17 +687,7 @@ export default function buildKernel( const { vatID, source, vatParameters, dynamicOptions } = message; insistCapData(vatParameters); kernelKeeper.addDynamicVatID(vatID); - const vatKeeper = kernelKeeper.provideVatKeeper(vatID); - const { - managerType = kernelKeeper.getDefaultManagerType(), - reapInterval = kernelKeeper.getDefaultReapInterval(), - ...otherOptions - } = dynamicOptions; - const workerOptions = await makeWorkerOptions(managerType, bundleHandler); - /** @type {import('../types-internal.js').RecordedVatOptions} */ - const vatOptions = harden({ workerOptions, reapInterval, ...otherOptions }); - vatKeeper.setSourceAndOptions(source, vatOptions); - vatKeeper.initializeReapCountdown(reapInterval); + await optionRecorder.recordDynamic(vatID, source, dynamicOptions); // createDynamicVat makes the worker, installs lockdown and // supervisor, but does not load the vat bundle yet. It can fail @@ -1526,16 +1516,17 @@ export default function buildKernel( kernelSlog, makeSourcedConsole, kernelKeeper, - buildVatSyscallHandler, overrideVatManagerOptions, }); - vatWarehouse = makeVatWarehouse( + vatWarehouse = makeVatWarehouse({ + kernelSlog, kernelKeeper, vatLoader, + buildVatSyscallHandler, panic, warehousePolicy, - ); + }); /** * Create a dynamically generated vat for testing purposes. Such vats are @@ -1568,32 +1559,28 @@ export default function buildKernel( assertKnownOptions(creationOptions, [ 'bundleID', 'enablePipelining', - 'metered', 'reapInterval', ]); const { bundleID = 'b1-00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', reapInterval = 'never', - ...otherOptions + enablePipelining, } = creationOptions; - const vatID = kernelKeeper.allocateVatIDForNameIfNeeded(name); logStartup(`assigned VatID ${vatID} for test vat ${name}`); - const workerOptions = await makeWorkerOptions('local', bundleHandler); - /** @type {import('../types-internal.js').RecordedVatOptions} */ - const vatOptions = harden({ + const source = { bundleID }; + /** @type {import('../types-external.js').ManagerType} */ + const managerType = 'local'; + const options = { name, - workerOptions, reapInterval, - ...otherOptions, - }); - - const vatKeeper = kernelKeeper.provideVatKeeper(vatID); - vatKeeper.setSourceAndOptions({ bundleID }, vatOptions); - vatKeeper.initializeReapCountdown(reapInterval); + enablePipelining, + managerType, + }; + await optionRecorder.recordStatic(vatID, source, options); - await vatWarehouse.loadTestVat(vatID, setup, vatOptions); + await vatWarehouse.loadTestVat(vatID, setup); const vpCapData = kser(vatParameters); /** @type { RunQueueEventStartVat } */ diff --git a/packages/SwingSet/src/kernel/state/vatKeeper.js b/packages/SwingSet/src/kernel/state/vatKeeper.js index 3848894a1c6..daa704a9eac 100644 --- a/packages/SwingSet/src/kernel/state/vatKeeper.js +++ b/packages/SwingSet/src/kernel/state/vatKeeper.js @@ -485,7 +485,7 @@ export function makeVatKeeper( /** * Append an entry to the vat's transcript. * - * @param {object} entry The transcript entry to append. + * @param {TranscriptEntry} entry The transcript entry to append. */ function addToTranscript(entry) { transcriptStore.addItem(vatID, JSON.stringify(entry)); diff --git a/packages/SwingSet/src/kernel/vat-loader/manager-factory.js b/packages/SwingSet/src/kernel/vat-loader/manager-factory.js index 067bd8e3fac..a1612ff2dab 100644 --- a/packages/SwingSet/src/kernel/vat-loader/manager-factory.js +++ b/packages/SwingSet/src/kernel/vat-loader/manager-factory.js @@ -13,17 +13,14 @@ export function makeVatManagerFactory({ }) { const localFactory = makeLocalVatManagerFactory({ allVatPowers, - kernelKeeper, vatEndowments, gcTools, - kernelSlog, }); const xsWorkerFactory = makeXsSubprocessFactory({ startXSnap, kernelKeeper, kernelSlog, - allVatPowers, testLog: allVatPowers.testLog, }); @@ -32,6 +29,7 @@ export function makeVatManagerFactory({ 'enablePipelining', 'workerOptions', 'setup', + 'retainSyscall', 'bundle', 'metered', 'enableDisavow', @@ -41,7 +39,6 @@ export function makeVatManagerFactory({ 'reapInterval', 'sourcedConsole', 'name', - 'compareSyscalls', ]); const { setup, bundle, enableSetup = false } = managerOptions; assert(setup || bundle); @@ -59,7 +56,6 @@ export function makeVatManagerFactory({ * syscall. * * @param {import('../../types-internal.js').VatID} vatID - * @param {import('../vat-warehouse.js').VatSyscallHandler} vatSyscallHandler * @param {object} options * @param {import('../../types-internal.js').ManagerOptions} options.managerOptions * @param {import('@agoric/swingset-liveslots').LiveSlotsOptions} options.liveSlotsOptions @@ -67,7 +63,6 @@ export function makeVatManagerFactory({ */ async function vatManagerFactory( vatID, - vatSyscallHandler, { managerOptions, liveSlotsOptions }, ) { validateManagerOptions(managerOptions); @@ -79,19 +74,13 @@ export function makeVatManagerFactory({ } if (enableSetup) { if (managerOptions.setup) { - return localFactory.createFromSetup( - vatID, - managerOptions.setup, - managerOptions, - vatSyscallHandler, - ); + return localFactory.createFromSetup(vatID, managerOptions); } else { return localFactory.createFromBundle( vatID, managerOptions.bundle, managerOptions, liveSlotsOptions, - vatSyscallHandler, ); } } else if (type === 'local') { @@ -100,7 +89,6 @@ export function makeVatManagerFactory({ managerOptions.bundle, managerOptions, liveSlotsOptions, - vatSyscallHandler, ); } @@ -111,7 +99,6 @@ export function makeVatManagerFactory({ managerOptions.bundle, managerOptions, liveSlotsOptions, - vatSyscallHandler, ); } diff --git a/packages/SwingSet/src/kernel/vat-loader/manager-helper.js b/packages/SwingSet/src/kernel/vat-loader/manager-helper.js index fbe0d28b2cc..dbd49c0cd30 100644 --- a/packages/SwingSet/src/kernel/vat-loader/manager-helper.js +++ b/packages/SwingSet/src/kernel/vat-loader/manager-helper.js @@ -1,7 +1,9 @@ import { assert } from '@agoric/assert'; import '../../types-ambient.js'; -import { insistVatDeliveryResult } from '../../lib/message.js'; -import { makeTranscriptManager } from './transcript.js'; +import { + insistVatDeliveryResult, + insistVatSyscallResult, +} from '../../lib/message.js'; /** * @typedef {import('@agoric/swingset-liveslots').VatDeliveryObject} VatDeliveryObject @@ -62,6 +64,7 @@ import { makeTranscriptManager } from './transcript.js'; */ /** + * TODO: stale * This generic helper runs on the manager side. It handles transcript * record/replay, and errors in the manager-specific code. * @@ -104,36 +107,12 @@ import { makeTranscriptManager } from './transcript.js'; * The returned getManager() function will return a VatManager suitable for * handing to the kernel, which can use it to send deliveries to the vat. * - * @param {string} vatID - * @param {KernelKeeper} kernelKeeper - * @param {KernelSlog} kernelSlog - * @param {(vso: VatSyscallObject) => VatSyscallResult} vatSyscallHandler - * @param {boolean} workerCanBlock - * @param {import('./transcript.js').CompareSyscalls} [compareSyscalls] - * @param {boolean} [useTranscript] + * @param {boolean} retainSyscall for unit tests: allow syscalls between deliveries * @returns {ManagerKit} */ -function makeManagerKit( - vatID, - kernelSlog, - kernelKeeper, - vatSyscallHandler, - workerCanBlock, - compareSyscalls, - useTranscript, -) { - assert(kernelSlog); - const vatKeeper = kernelKeeper.provideVatKeeper(vatID); - /** @type {ReturnType | undefined} */ - let transcriptManager; - if (useTranscript) { - transcriptManager = makeTranscriptManager( - vatKeeper, - vatID, - compareSyscalls, - ); - } +function makeManagerKit(retainSyscall = false) { + let syscallHandler; // empty between deliveries /** @type { (delivery: VatDeliveryObject) => Promise } */ let deliverToWorker; @@ -149,12 +128,15 @@ function makeManagerKit( /** * * @param {VatDeliveryObject} delivery + * @param {(vso: VatSyscallObject) => VatSyscallResult} vatSyscallHandler * @returns {Promise} // or Error */ - async function deliver(delivery) { - if (transcriptManager) { - transcriptManager.startDispatch(delivery); + async function deliver(delivery, vatSyscallHandler) { + assert(vatSyscallHandler); + if (!retainSyscall) { + assert(!syscallHandler); } + syscallHandler = vatSyscallHandler; // metering faults (or other reasons why the vat should be // deterministically terminated) are reported with status= ['error', // err.message, null]. Any non-deterministic error (unexpected worker @@ -163,74 +145,12 @@ function makeManagerKit( /** @type { VatDeliveryResult } */ const status = await deliverToWorker(delivery); insistVatDeliveryResult(status); - // TODO: if the dispatch failed for whatever reason, and we choose to - // destroy the vat, change what we do with the transcript here. - if (transcriptManager) { - transcriptManager.finishDispatch(); + if (!retainSyscall) { + syscallHandler = undefined; } return status; } - async function replayOneDelivery(delivery, expectedSyscalls, deliveryNum) { - assert(transcriptManager, 'delivery replay with no transcript'); - transcriptManager.startReplay(); - transcriptManager.startReplayDelivery(expectedSyscalls); - - // we slog the replay just like the original, but some fields are missing - /** @type {any} */ - const newCrankNum = undefined; // TODO think of a way to correlate this - /** @type {any} */ - const kd = undefined; - const vd = delivery; - const replay = true; - const finish = kernelSlog.delivery( - vatID, - newCrankNum, - deliveryNum, - kd, - vd, - replay, - ); - const status = await deliver(delivery); - finish(status); - transcriptManager.finishReplayDelivery(deliveryNum); - transcriptManager.checkReplayError(); - transcriptManager.finishReplay(); - return status; - } - - /** - * @param {number | undefined} startPos - * @returns {Promise} number of deliveries, or null if !useTranscript - */ - async function replayTranscript(startPos) { - // console.log('replay from', { vatID, startPos }); - - if (transcriptManager) { - const total = vatKeeper.vatStats().transcriptCount; - kernelSlog.write({ type: 'start-replay', vatID, deliveries: total }); - // TODO glean deliveryNum better, make sure we get the post-snapshot - // transcript starting point right. getTranscript() should probably - // return [deliveryNum, t] pairs. Think about how to provide an - // accurate crankNum, because I'm not sure I want that in transcript. - let deliveryNum = startPos || 0; - for (const t of vatKeeper.getTranscript(startPos)) { - // if (deliveryNum % 100 === 0) { - // console.debug(`replay vatID:${vatID} deliveryNum:${deliveryNum} / ${total}`); - // } - // - // eslint-disable-next-line no-await-in-loop, @jessie.js/no-nested-await - await replayOneDelivery(t.d, t.syscalls, deliveryNum); - deliveryNum += 1; - } - transcriptManager.checkReplayError(); - kernelSlog.write({ type: 'finish-replay', vatID }); - return deliveryNum; - } - - return null; - } - /** * vatSyscallObject is an array that starts with the syscall name ('send', * 'subscribe', etc) followed by all the positional arguments of the @@ -241,27 +161,10 @@ function makeManagerKit( * @param {VatSyscallObject} vso */ function syscallFromWorker(vso) { - if (transcriptManager && transcriptManager.inReplay()) { - // We're replaying old messages to bring the vat's internal state - // up-to-date. It will make syscalls like a puppy chasing rabbits in - // its sleep. Gently prevent their twitching paws from doing anything. - - // but if the puppy deviates one inch from previous twitches, explode - kernelSlog.syscall(vatID, undefined, vso); - const vres = transcriptManager.simulateSyscall(vso); - return vres; - } - - const vres = vatSyscallHandler(vso); - // vres is ['error', reason] or ['ok', null] or ['ok', capdata] or ['ok', string] - const [successFlag, data] = vres; - if (successFlag === 'ok' && data && !workerCanBlock) { - console.log(`warning: syscall returns data, but worker cannot get it`); - } - if (transcriptManager) { - transcriptManager.addSyscall(vso, vres); - } - return vres; + const vsr = syscallHandler(vso); + // vsr is ['error', reason] or ['ok', null] or ['ok', capdata] or ['ok', string] + insistVatSyscallResult(vsr); + return vsr; } /** @@ -272,8 +175,6 @@ function makeManagerKit( */ function getManager(shutdown, makeSnapshot) { return harden({ - replayTranscript, - replayOneDelivery, deliver, shutdown, makeSnapshot, diff --git a/packages/SwingSet/src/kernel/vat-loader/manager-local.js b/packages/SwingSet/src/kernel/vat-loader/manager-local.js index 0de3db001b2..52fad693e73 100644 --- a/packages/SwingSet/src/kernel/vat-loader/manager-local.js +++ b/packages/SwingSet/src/kernel/vat-loader/manager-local.js @@ -10,25 +10,19 @@ import { makeVatConsole, } from '../../supervisors/supervisor-helper.js'; -export function makeLocalVatManagerFactory(tools) { - const { allVatPowers, kernelKeeper, vatEndowments, gcTools, kernelSlog } = - tools; - +export function makeLocalVatManagerFactory({ + allVatPowers, + vatEndowments, + gcTools, +}) { const baseVP = { makeMarshal: allVatPowers.makeMarshal, }; // testLog is also a vatPower, only for unit tests - function prepare(vatID, vatSyscallHandler, compareSyscalls, useTranscript) { - const mk = makeManagerKit( - vatID, - kernelSlog, - kernelKeeper, - vatSyscallHandler, - true, - compareSyscalls, - useTranscript, - ); + function prepare(managerOptions) { + const { retainSyscall = false } = managerOptions; + const mk = makeManagerKit(retainSyscall); function finish(dispatch) { assert.typeof(dispatch, 'function'); @@ -45,16 +39,11 @@ export function makeLocalVatManagerFactory(tools) { return { syscall, finish }; } - function createFromSetup(vatID, setup, managerOptions, vatSyscallHandler) { + function createFromSetup(vatID, managerOptions) { + const { setup } = managerOptions; assert.typeof(setup, 'function', 'setup is not an in-realm function'); - const { compareSyscalls, useTranscript } = managerOptions; - const { syscall, finish } = prepare( - vatID, - vatSyscallHandler, - compareSyscalls, - useTranscript, - ); + const { syscall, finish } = prepare(managerOptions); const { testLog } = allVatPowers; const helpers = harden({}); // DEPRECATED, todo remove from setup() const state = null; // TODO remove from setup() @@ -69,22 +58,11 @@ export function makeLocalVatManagerFactory(tools) { bundle, managerOptions, liveSlotsOptions, - vatSyscallHandler, ) { - const { - enableSetup = false, - sourcedConsole, - compareSyscalls, - useTranscript, - } = managerOptions; + const { enableSetup = false, sourcedConsole } = managerOptions; assert(sourcedConsole, 'vats need managerOptions.sourcedConsole'); - const { syscall, finish } = prepare( - vatID, - vatSyscallHandler, - compareSyscalls, - useTranscript, - ); + const { syscall, finish } = prepare(managerOptions); const vatPowers = harden({ ...baseVP, 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 5855db32d01..fb104cf740f 100644 --- a/packages/SwingSet/src/kernel/vat-loader/manager-subprocess-xsnap.js +++ b/packages/SwingSet/src/kernel/vat-loader/manager-subprocess-xsnap.js @@ -28,7 +28,6 @@ const decoder = new TextDecoder(); /** * @param {{ - * allVatPowers: VatPowers, * kernelKeeper: KernelKeeper, * kernelSlog: KernelSlog, * startXSnap: (vatID: string, name: string, @@ -55,20 +54,17 @@ export function makeXsSubprocessFactory({ * @param {unknown} bundle * @param {import('../../types-internal.js').ManagerOptions} managerOptions * @param {LiveSlotsOptions} liveSlotsOptions - * @param {(vso: VatSyscallObject) => VatSyscallResult} vatSyscallHandler */ async function createFromBundle( vatID, bundle, managerOptions, liveSlotsOptions, - vatSyscallHandler, ) { parentLog(vatID, 'createFromBundle', { vatID }); const { name: vatName, metered, - compareSyscalls, useTranscript, sourcedConsole, workerOptions, @@ -82,15 +78,7 @@ export function makeXsSubprocessFactory({ const { bundleIDs } = workerOptions; assert(bundleIDs, 'bundleIDs required for xsnap'); - const mk = makeManagerKit( - vatID, - kernelSlog, - kernelKeeper, - vatSyscallHandler, - true, - compareSyscalls, - useTranscript, - ); + const mk = makeManagerKit(); /** @type { (item: Tagged) => unknown } */ function handleUpstream([type, ...args]) { diff --git a/packages/SwingSet/src/kernel/vat-loader/transcript.js b/packages/SwingSet/src/kernel/vat-loader/transcript.js deleted file mode 100644 index 45346671ff7..00000000000 --- a/packages/SwingSet/src/kernel/vat-loader/transcript.js +++ /dev/null @@ -1,132 +0,0 @@ -// @ts-check - -import djson from '../../lib/djson.js'; - -/** @typedef {import('@agoric/swingset-liveslots').VatSyscallObject} VatSyscallObject */ -/** @typedef {import('@agoric/swingset-liveslots').VatSyscallResult} VatSyscallResult */ - -/** @typedef {Error | undefined} CompareSyscallsResult */ -/** - * @typedef {( - * vatId: any, - * originalSyscall: VatSyscallObject, - * newSyscall: VatSyscallObject, - * ) => CompareSyscallsResult - * } CompareSyscalls - */ - -/** - * @param {any} vatID - * @param {VatSyscallObject} originalSyscall - * @param {VatSyscallObject} newSyscall - */ -export function requireIdentical(vatID, originalSyscall, newSyscall) { - if (djson.stringify(originalSyscall) !== djson.stringify(newSyscall)) { - console.error(`anachrophobia strikes vat ${vatID}`); - console.error(`expected:`, djson.stringify(originalSyscall)); - console.error(`got :`, djson.stringify(newSyscall)); - return new Error(`historical inaccuracy in replay of ${vatID}`); - } - return undefined; -} - -/** - * @param {*} vatKeeper - * @param {*} vatID - * @param {CompareSyscalls} compareSyscalls - */ -export function makeTranscriptManager( - vatKeeper, - vatID, - compareSyscalls = requireIdentical, -) { - let weAreInReplay = false; - let playbackSyscalls; - let currentEntry; - - function startDispatch(d) { - currentEntry = { - d, - syscalls: [], - }; - } - - function addSyscall(d, response) { - if (currentEntry) { - currentEntry.syscalls.push({ d, response }); - } - } - - function finishDispatch() { - if (!weAreInReplay) { - vatKeeper.addToTranscript(currentEntry); - } - } - - // replay - - function startReplay() { - weAreInReplay = true; - } - - function startReplayDelivery(syscalls) { - playbackSyscalls = Array.from(syscalls); - } - - function inReplay() { - return weAreInReplay; - } - - function finishReplay() { - weAreInReplay = false; - } - - let replayError; - - /** @param {VatSyscallObject} newSyscall */ - function simulateSyscall(newSyscall) { - /** @type {{d: VatSyscallObject; response: VatSyscallResult}} */ - const s = playbackSyscalls.shift(); - const newReplayError = compareSyscalls(vatID, s.d, newSyscall); - if (newReplayError) { - replayError = newReplayError; - throw replayError; - } - return s.response; - } - - function finishReplayDelivery(dnum) { - if (playbackSyscalls.length !== 0) { - console.log(`anachrophobia strikes vat ${vatID} on delivery ${dnum}`); - console.log( - `delivery completed with ${playbackSyscalls.length} expected syscalls remaining`, - ); - for (const s of playbackSyscalls) { - console.log(`expected:`, djson.stringify(s.d)); - } - if (!replayError) { - replayError = new Error(`historical inaccuracy in replay of ${vatID}`); - } - throw replayError; - } - } - - function checkReplayError() { - if (replayError) { - throw replayError; - } - } - - return harden({ - startDispatch, - addSyscall, - finishDispatch, - startReplay, - startReplayDelivery, - finishReplay, - simulateSyscall, - finishReplayDelivery, - checkReplayError, - inReplay, - }); -} diff --git a/packages/SwingSet/src/kernel/vat-loader/vat-loader.js b/packages/SwingSet/src/kernel/vat-loader/vat-loader.js index 4e19d54173b..83b4a70caa4 100644 --- a/packages/SwingSet/src/kernel/vat-loader/vat-loader.js +++ b/packages/SwingSet/src/kernel/vat-loader/vat-loader.js @@ -13,10 +13,8 @@ export function makeVatLoader(stuff) { kernelSlog, makeSourcedConsole, kernelKeeper, - buildVatSyscallHandler, } = stuff; - /** @typedef {ReturnType} Translators */ /** @typedef {import('../../types-internal.js').VatManager} VatManager */ const allowedOptions = [ @@ -53,14 +51,12 @@ export function makeVatLoader(stuff) { * used, it must identify a bundle already known to the kernel (via the * `config.bundles` table) which satisfies these constraints. * - * @param {Translators} details.translators - * * @param {import('../../types-internal.js').RecordedVatOptions} details.options * * @returns {Promise} A Promise which fires when the * vat is ready for messages. */ - async function create(vatID, { isDynamic, source, translators, options }) { + async function create(vatID, { isDynamic, source, options }) { assert( 'bundle' in source || 'bundleName' in source || 'bundleID' in source, 'broken source', @@ -130,10 +126,8 @@ export function makeVatLoader(stuff) { relaxDurabilityRules: kernelKeeper.getRelaxDurabilityRules(), }; - const vatSyscallHandler = buildVatSyscallHandler(vatID, translators); - const finish = starting && kernelSlog.startup(vatID); - const manager = await vatManagerFactory(vatID, vatSyscallHandler, { + const manager = await vatManagerFactory(vatID, { managerOptions, liveSlotsOptions, }); @@ -141,17 +135,22 @@ export function makeVatLoader(stuff) { return manager; } - async function loadTestVat(vatID, setup, translators, creationOptions) { + async function loadTestVat(vatID, setup, options) { + const { name, workerOptions, enablePipelining, critical } = options; const managerOptions = { - ...creationOptions, + workerOptions, setup, + retainSyscall: true, // let unit test to drive syscall between deliveries + metered: false, enableSetup: true, + enablePipelining, useTranscript: true, + critical, + name, ...overrideVatManagerOptions, }; - const vatSyscallHandler = buildVatSyscallHandler(vatID, translators); const liveSlotsOptions = {}; - const manager = await vatManagerFactory(vatID, vatSyscallHandler, { + const manager = await vatManagerFactory(vatID, { managerOptions, liveSlotsOptions, }); diff --git a/packages/SwingSet/src/kernel/vat-warehouse.js b/packages/SwingSet/src/kernel/vat-warehouse.js index 08fa0cc6bfc..83d84a94e99 100644 --- a/packages/SwingSet/src/kernel/vat-warehouse.js +++ b/packages/SwingSet/src/kernel/vat-warehouse.js @@ -1,14 +1,153 @@ -/* eslint-disable no-await-in-loop,@jessie.js/no-nested-await */ import { assert, Fail, quote as q } from '@agoric/assert'; import { isNat } from '@endo/nat'; import { makeVatTranslators } from './vatTranslator.js'; import { insistVatDeliveryResult } from '../lib/message.js'; +import djson from '../lib/djson.js'; /** * @typedef {import('@agoric/swingset-liveslots').VatDeliveryObject} VatDeliveryObject + * @typedef {import('@agoric/swingset-liveslots').VatDeliveryResult} VatDeliveryResult + * @typedef {import('@agoric/swingset-liveslots').VatSyscallObject} VatSyscallObject + * @typedef {import('@agoric/swingset-liveslots').VatSyscallResult} VatSyscallResult + * @typedef {import('@agoric/swingset-liveslots').VatSyscallHandler} VatSyscallHandler * @typedef {import('../types-internal.js').VatManager} VatManager + * @typedef {{ body: string, slots: unknown[] }} Capdata + * @typedef { [unknown, ...unknown[]] } Tagged + * @typedef { { moduleFormat: string }} Bundle */ +/** + * @param {VatSyscallObject} originalSyscall + * @param {VatSyscallObject} newSyscall + * @returns {boolean} + */ +export function syscallsAreIdentical(originalSyscall, newSyscall) { + return djson.stringify(originalSyscall) === djson.stringify(newSyscall); +} + +/** + * @param {VatSyscallHandler} origHandler + */ +function recordSyscalls(origHandler) { + assert(origHandler); + const syscalls = []; + const syscallHandler = vso => { + const vres = origHandler(vso); + syscalls.push({ s: vso, r: vres }); + return vres; + }; + const getTranscriptSyscalls = () => syscalls; + return { syscallHandler, getTranscriptSyscalls }; +} + +/** + * Make a syscallHandler that returns results from a + * previously-recorded transcript, instead of executing them for + * real. The vat must perform exactly the same syscalls as before, + * else it gets a vat-fatal error. + * + * @param {*} kernelSlog + * @param {string} vatID + * @param {number} deliveryNum + * @param {import('../types-external.js').TranscriptEntry} transcriptEntry + * @returns { { + * syscallHandler: (vso: VatSyscallObject) => VatSyscallResult, + * finishSimulation: () => void, + * } } + */ +export function makeSyscallSimulator( + kernelSlog, + vatID, + deliveryNum, + transcriptEntry, +) { + const syscallsExpected = [...transcriptEntry.sc]; // copy + const syscallsMade = []; + // syscallStatus's length will be max(syscallsExpected, + // syscallsMade). We push a new status onto it each time the + // replaying vat makes a syscall, then when the delivery ends, + // finishSimulation will pad it out with 'missing' entries. + const syscallStatus = []; // array of 'ok'/'wrong'/'extra'/'missing' + let replayError; // sticky + + const explain = () => { + console.log(`anachrophobia strikes ${vatID} on delivery ${deliveryNum}`); + syscallStatus.forEach((status, idx) => { + const expected = syscallsExpected[idx]; + const got = syscallsMade[idx]; + switch (status) { + case 'ok': { + console.log(`sc[${idx}]: ok: ${djson.stringify(got)}`); + break; + } + case 'wrong': { + console.log(`sc[${idx}]: wrong`); + console.log(` expected: ${djson.stringify(expected.s)}`); + console.log(` got : ${djson.stringify(got)}`); + break; + } + case 'extra': { + console.log(`sc[${idx}]: extra: ${djson.stringify(got)}`); + break; + } + case 'missing': { + console.log(`sc[${idx}]: missing: ${djson.stringify(expected.s)}`); + break; + } + default: + Fail`bad ${status}`; + } + }); + }; + + const syscallHandler = vso => { + kernelSlog.syscall(vatID, undefined, vso); // TODO: finish()? + const expected = syscallsExpected[syscallsMade.length]; + syscallsMade.push(vso); + if (!expected) { + syscallStatus.push('extra'); + const error = Error(`anachrophobia in ${vatID}: extra syscall`); + replayError ||= error; + throw error; + } + if (!syscallsAreIdentical(expected.s, vso)) { + syscallStatus.push('wrong'); + const error = Error(`anachrophobia in ${vatID}: wrong syscall`); + replayError ||= error; + throw error; + } + syscallStatus.push('ok'); + return expected.r; + }; + + const finishSimulation = () => { + if (syscallsMade.length < syscallsExpected.length) { + const missing = syscallsExpected.length - syscallsMade.length; + for (let i = 0; i < missing; i += 1) { + syscallStatus.push('missing'); + } + const error = Error(`anachrophobia in ${vatID}: missing syscalls`); + replayError ||= error; + } + + if (replayError) { + explain(); + throw replayError; + } + }; + + return { syscallHandler, finishSimulation }; +} + +function slogReplay(kernelSlog, vatID, deliveryNum, te) { + /** @type {any} */ + const newCrankNum = undefined; // TODO think of a way to correlate this + /** @type {any} */ + const kd = undefined; + const vd = te.d; + return kernelSlog.delivery(vatID, newCrankNum, deliveryNum, kd, vd, true); +} + /** @param {number} max */ export const makeLRU = max => { /** @type { string[] } */ @@ -50,26 +189,23 @@ export const makeLRU = max => { }; /** - * @typedef {(syscall: import('@agoric/swingset-liveslots').VatSyscallObject) => ['error', string] | ['ok', null] | ['ok', Capdata]} VatSyscallHandler - * @typedef {{ body: string, slots: unknown[] }} Capdata - * @typedef { [unknown, ...unknown[]] } Tagged - * @typedef { { moduleFormat: string }} Bundle + * @param {object} details + * @param {KernelKeeper} details.kernelKeeper + * @param {ReturnType} details.vatLoader + * @param {(vatID: string, translators: VatTranslators) => VatSyscallHandler} details.buildVatSyscallHandler + * @param { import('../types-internal.js').KernelPanic } details.panic + * @param { import('../types-external.js').VatWarehousePolicy } details.warehousePolicy + * @param { import('../types-external.js').KernelSlog } details.kernelSlog */ - -/** - * @param {KernelKeeper} kernelKeeper - * @param {ReturnType} vatLoader - * @param { import('../types-internal.js').KernelPanic } panic - * @param {object} policyOptions - * @param {number} [policyOptions.maxVatsOnline] Limit the number of simultaneous workers - */ -export function makeVatWarehouse( +export function makeVatWarehouse({ + kernelSlog, kernelKeeper, vatLoader, + buildVatSyscallHandler, panic, - policyOptions, -) { - const { maxVatsOnline = 50 } = policyOptions || {}; + warehousePolicy, +}) { + const { maxVatsOnline = 50 } = warehousePolicy || {}; // Often a large contract evaluation is among the first few deliveries, // so let's do a snapshot after just a few deliveries. const snapshotInitial = kernelKeeper.getSnapshotInitial(); @@ -77,15 +213,17 @@ export function makeVatWarehouse( // Note: some measurements show 10 deliveries per sec on XS as of this writing. let snapshotInterval = kernelKeeper.getSnapshotInterval(); // Idea: snapshot based on delivery size: after deliveries >10Kb. - // console.debug('makeVatWarehouse', { policyOptions }); + // console.debug('makeVatWarehouse', { warehousePolicy }); /** + * @typedef { ReturnType } VatTranslators * @typedef {{ * manager: VatManager, + * translators: VatTranslators, + * syscallHandler: VatSyscallHandler, * enablePipelining: boolean, - * options: { name?: string, managerType?: ManagerType, meterID?: MeterID }, + * options: import('../types-internal.js').RecordedVatOptions, * }} VatInfo - * @typedef { ReturnType } VatTranslators */ const ephemeral = { /** @type {Map } key is vatID */ @@ -106,6 +244,39 @@ export function makeVatWarehouse( return translators; } + /** + * @param {string} vatID + * @param {import('../types-external.js').VatKeeper} vatKeeper + * @param {VatManager} manager + * @returns {Promise} + */ + async function replayTranscript(vatID, vatKeeper, manager) { + const snapshotInfo = vatKeeper.getSnapshotInfo(); + const startPos = snapshotInfo ? snapshotInfo.endPos : undefined; + // console.log('replay from', { vatID, startPos }); + + const total = vatKeeper.vatStats().transcriptCount; + kernelSlog.write({ type: 'start-replay', vatID, deliveries: total }); + // TODO glean deliveryNum better, make sure we get the post-snapshot + // transcript starting point right. getTranscript() should probably + // return [deliveryNum, t] pairs. + let deliveryNum = startPos || 0; + for await (const te of vatKeeper.getTranscript(startPos)) { + // if (deliveryNum % 100 === 0) { + // console.debug(`replay vatID:${vatID} deliveryNum:${deliveryNum} / ${total}`); + // } + // + // we slog the replay just like the original, but some fields are missing + const finishSlog = slogReplay(kernelSlog, vatID, deliveryNum, te); + const sim = makeSyscallSimulator(kernelSlog, vatID, deliveryNum, te); + const status = await manager.deliver(te.d, sim.syscallHandler); + finishSlog(status); + sim.finishSimulation(); // will throw if syscalls did not match + deliveryNum += 1; + } + kernelSlog.write({ type: 'finish-replay', vatID }); + } + /** * @param {string} vatID * @param {boolean} recreate @@ -114,17 +285,21 @@ export function makeVatWarehouse( async function ensureVatOnline(vatID, recreate) { const info = ephemeral.vats.get(vatID); if (info) return info; + kernelKeeper.vatIsAlive(vatID) || Fail`${q(vatID)}: not alive`; const vatKeeper = kernelKeeper.provideVatKeeper(vatID); const { source, options } = vatKeeper.getSourceAndOptions(); + // TODO: translators should be stored only in ephemeral.vats, + // share lifetime with worker, but might be needed earlier than + // ensureVatOnline?, make sure it gets deleted eventually const translators = provideTranslators(vatID); + const syscallHandler = buildVatSyscallHandler(vatID, translators); const isDynamic = kernelKeeper.getDynamicVats().includes(vatID); const managerP = vatLoader.create(vatID, { isDynamic, source, - translators, options, }); if (recreate) { @@ -135,14 +310,15 @@ export function makeVatWarehouse( // TODO(3218): persist this option; avoid spinning up a vat that isn't pipelined const { enablePipelining = false } = options; - const snapshotInfo = vatKeeper.getSnapshotInfo(); - await manager.replayTranscript( - snapshotInfo ? snapshotInfo.endPos : undefined, - ); + if (options.useTranscript) { + // eslint-disable-next-line @jessie.js/no-nested-await + await replayTranscript(vatID, vatKeeper, manager); + } const result = { manager, translators, + syscallHandler, enablePipelining, options, }; @@ -178,7 +354,7 @@ export function makeVatWarehouse( // instantiate all static vats, in lexicographic order, up to the // maxPreload limit - for (const [name, vatID] of kernelKeeper.getStaticVats()) { + for await (const [name, vatID] of kernelKeeper.getStaticVats()) { if (numPreloaded >= maxPreload) { break; } @@ -190,7 +366,7 @@ export function makeVatWarehouse( // then instantiate all dynamic vats, in creation order, also // subject to maxPreload - for (const vatID of kernelKeeper.getDynamicVats()) { + for await (const vatID of kernelKeeper.getDynamicVats()) { if (numPreloaded >= maxPreload) { break; } @@ -207,7 +383,7 @@ export function makeVatWarehouse( /** * @param {string} vatID - * @returns {{ enablePipelining?: boolean, meterID?: MeterID } + * @returns {{ enablePipelining: boolean, meterID?: MeterID } * | undefined // if the vat is dead or never initialized * } */ @@ -278,29 +454,51 @@ export function makeVatWarehouse( /** @type { string | undefined } */ let lastVatID; - /** @type {(vatID: string, kd: KernelDeliveryObject, d: VatDeliveryObject, vs: VatSlog) => Promise } */ + /** @type {(vatID: string, kd: KernelDeliveryObject, d: VatDeliveryObject, vs: VatSlog) => Promise } */ async function deliverToVat(vatID, kd, vd, vs) { await applyAvailabilityPolicy(vatID); lastVatID = vatID; const recreate = true; // PANIC in the failure case // create the worker and replay the transcript, if necessary - const { manager } = await ensureVatOnline(vatID, recreate); + const { + manager, + syscallHandler: origHandler, + options, + } = await ensureVatOnline(vatID, recreate); + assert(origHandler); // then log the delivery so it appears after transcript replay const vatKeeper = kernelKeeper.provideVatKeeper(vatID); const crankNum = kernelKeeper.getCrankNumber(); const deliveryNum = vatKeeper.nextDeliveryNum(); // increments /** @type { SlogFinishDelivery } */ - const slogFinish = vs.delivery(crankNum, deliveryNum, kd, vd); + const finishSlog = vs.delivery(crankNum, deliveryNum, kd, vd); + + // wrap the syscallHandler with a syscall recorder + const recorder = recordSyscalls(origHandler); + const { syscallHandler, getTranscriptSyscalls } = recorder; + assert(syscallHandler); // make the delivery - const deliveryResult = await manager.deliver(vd); + const deliveryResult = await manager.deliver(vd, syscallHandler); insistVatDeliveryResult(deliveryResult); + finishSlog(deliveryResult); // log the delivery results + + // TODO: if the dispatch failed for whatever reason, and we choose to + // destroy the vat, change what we do with the transcript here. + if (options.useTranscript) { + // record transcript entry + /** @type { import('../types-external.js').TranscriptDeliveryResults} */ + const tdr = { status: deliveryResult[0] }; + const transcriptEntry = { d: vd, sc: getTranscriptSyscalls(), r: tdr }; + vatKeeper.addToTranscript(transcriptEntry); + } + + // TODO: if per-vat policy decides it wants a BOYD or heap snapshot, + // now is the time to do it, or to ask the kernel to schedule it - // log the delivery results, and return to caller for evaluation - slogFinish(deliveryResult); - return deliveryResult; + return deliveryResult; // return delivery results to caller for evaluation } /** @@ -354,25 +552,21 @@ export function makeVatWarehouse( /** * @param {string} vatID * @param {unknown} setup - * @param {import('../types-internal.js').RecordedVatOptions} vatOptions */ - async function loadTestVat(vatID, setup, vatOptions) { + async function loadTestVat(vatID, setup) { + const vatKeeper = kernelKeeper.provideVatKeeper(vatID); + const options = vatKeeper.getOptions(); + const { enablePipelining } = options; const translators = provideTranslators(vatID); - - const manager = await vatLoader.loadTestVat( - vatID, - setup, - translators, - vatOptions, - ); - - const { enablePipelining = false } = vatOptions; + const syscallHandler = buildVatSyscallHandler(vatID, translators); + const manager = await vatLoader.loadTestVat(vatID, setup, options); const result = { manager, translators, + syscallHandler, enablePipelining, - options: {}, + options, }; ephemeral.vats.set(vatID, result); } @@ -399,6 +593,7 @@ export function makeVatWarehouse( // worker may or may not be online if (ephemeral.vats.has(vatID)) { try { + // eslint-disable-next-line @jessie.js/no-nested-await await evict(vatID); } catch (err) { console.debug('vat termination was already reported; ignoring:', err); diff --git a/packages/SwingSet/src/lib/recordVatOptions.js b/packages/SwingSet/src/lib/recordVatOptions.js new file mode 100644 index 00000000000..599c65db651 --- /dev/null +++ b/packages/SwingSet/src/lib/recordVatOptions.js @@ -0,0 +1,73 @@ +import { Fail } from '@agoric/assert'; +import { makeWorkerOptions } from './workerOptions.js'; + +export const makeVatOptionRecorder = (kernelKeeper, bundleHandler) => { + const record = async (vatID, source, options) => { + const { + name, + vatParameters = undefined, + enableSetup = false, + enablePipelining = false, + enableDisavow = false, + useTranscript = true, + reapInterval = kernelKeeper.getDefaultReapInterval(), + critical = false, + meterID = undefined, + managerType = kernelKeeper.getDefaultManagerType(), + ...leftover + } = options; + const unused = Object.keys(leftover); + if (unused.length) { + Fail`OptionRecorder: ${vatID} unused options ${unused.join(',')}`; + } + const workerOptions = await makeWorkerOptions(managerType, bundleHandler); + /** @type { import('../types-internal.js').RecordedVatOptions } */ + const vatOptions = harden({ + workerOptions, + name, + vatParameters, + enableSetup, + enablePipelining, + enableDisavow, + useTranscript, + reapInterval, + critical, + meterID, + }); + const vatKeeper = kernelKeeper.provideVatKeeper(vatID); + vatKeeper.setSourceAndOptions(source, vatOptions); + vatKeeper.initializeReapCountdown(vatOptions.reapInterval); + }; + + /** + * Convert an StaticVatOptions (from the config.vats definition) + * into a RecordedVatOptions, sampling and populating the current + * defaults. Store it. + * + * @param {string} vatID + * @param {*} source + * @param {import('../types-external.js').StaticVatOptions} staticOptions + * @returns {Promise} + */ + const recordStatic = (vatID, source, staticOptions) => { + const options = { ...staticOptions, meterID: undefined }; + return record(vatID, source, options); + }; + + /** + * Convert an InternalDynamicVatOptions (from the run-queue + * 'create-vat' event) into a RecordedVatOptions, sampling and + * populating the current defaults. Store it. + * + * @param {string} vatID + * @param {*} source + * @param {import('../types-internal.js').InternalDynamicVatOptions} dynamicOptions + * @returns {Promise} + */ + const recordDynamic = (vatID, source, dynamicOptions) => { + const options = { ...dynamicOptions, enableDisavow: false }; + return record(vatID, source, options); + }; + + return { recordStatic, recordDynamic }; +}; diff --git a/packages/SwingSet/src/supervisors/supervisor-helper.js b/packages/SwingSet/src/supervisors/supervisor-helper.js index 4057bfed3c8..5cfed5d6856 100644 --- a/packages/SwingSet/src/supervisors/supervisor-helper.js +++ b/packages/SwingSet/src/supervisors/supervisor-helper.js @@ -9,7 +9,7 @@ import '../types-ambient.js'; * @typedef {import('@agoric/swingset-liveslots').VatDeliveryObject} VatDeliveryObject * @typedef {import('@agoric/swingset-liveslots').VatDeliveryResult} VatDeliveryResult * @typedef {import('@agoric/swingset-liveslots').VatSyscallObject} VatSyscallObject - * @typedef {import('@agoric/swingset-liveslots').VatSyscaller} VatSyscaller + * @typedef {import('@agoric/swingset-liveslots').VatSyscallHandler} VatSyscallHandler * @typedef {import('@endo/marshal').CapData} SwingSetCapData * @typedef { (delivery: VatDeliveryObject) => (VatDeliveryResult | Promise) } VatDispatcherSyncAsync * @typedef { (delivery: VatDeliveryObject) => Promise } VatDispatcher @@ -59,7 +59,7 @@ export { makeSupervisorDispatch }; * I should be given a `syscallToManager` function that accepts a * VatSyscallObject and (synchronously) returns a VatSyscallResult. * - * @param {VatSyscaller} syscallToManager + * @param {VatSyscallHandler} syscallToManager * @param {boolean} workerCanBlock * @typedef { unknown } TheSyscallObjectWithMethodsThatLiveslotsWants * @returns {TheSyscallObjectWithMethodsThatLiveslotsWants} diff --git a/packages/SwingSet/src/types-external.js b/packages/SwingSet/src/types-external.js index 192fccd4606..14eb3826ff2 100644 --- a/packages/SwingSet/src/types-external.js +++ b/packages/SwingSet/src/types-external.js @@ -68,11 +68,9 @@ export {}; * @typedef {{ name: string, upgradeMessage: string, incarnationNumber: number }} DisconnectObject * * @typedef { import('@agoric/swingset-liveslots').VatDeliveryObject } VatDeliveryObject - * @typedef { import('@agoric/swingset-liveslots').VatOneResolution } VatOneResolution * @typedef { import('@agoric/swingset-liveslots').VatDeliveryResult } VatDeliveryResult * @typedef { import('@agoric/swingset-liveslots').VatSyscallObject } VatSyscallObject * @typedef { import('@agoric/swingset-liveslots').VatSyscallResult } VatSyscallResult - * @typedef { import('@agoric/swingset-liveslots').VatSyscaller } VatSyscaller * * @typedef { [tag: 'message', target: string, msg: Message]} KernelDeliveryMessage * @typedef { [kpid: string, kp: { state: string, data: SwingSetCapData }] } KernelDeliveryOneNotify @@ -123,7 +121,9 @@ export {}; * @typedef {[tag: 'error', problem: string]} DeviceInvocationResultError * @typedef { DeviceInvocationResultOk | DeviceInvocationResultError } DeviceInvocationResult * - * @typedef { { d: VatDeliveryObject, syscalls: VatSyscallObject[] } } TranscriptEntry + * @typedef { { s: VatSyscallObject, r: VatSyscallResult } } TranscriptSyscall + * @typedef { { status: string } } TranscriptDeliveryResults + * @typedef { { d: VatDeliveryObject, sc: TranscriptSyscall[], r: TranscriptDeliveryResults } } TranscriptEntry * @typedef { { transcriptCount: number } } VatStats * @typedef { ReturnType } VatKeeper * @typedef { ReturnType } KernelKeeper @@ -232,6 +232,9 @@ export {}; * crankFailed: (details: {}) => PolicyOutput, * emptyCrank: () => PolicyOutput, * } } RunPolicy + * + * @typedef {object} VatWarehousePolicy + * @property { number } [maxVatsOnline] Limit the number of simultaneous workers */ /** @@ -270,7 +273,6 @@ export {}; * * @typedef { object } BaseVatOptions * @property { string } name - * @property { string } [description] * @property { * } [vatParameters] * @property { boolean } [enableSetup] * If true, permits the vat to construct itself using the diff --git a/packages/SwingSet/src/types-internal.js b/packages/SwingSet/src/types-internal.js index 07a10204eee..ea91f5f53e6 100644 --- a/packages/SwingSet/src/types-internal.js +++ b/packages/SwingSet/src/types-internal.js @@ -16,17 +16,30 @@ export {}; * @typedef { { meterID?: MeterID } } OptMeterID * @typedef { import('./types-external.js').BaseVatOptions } BaseVatOptions * @typedef { import('./types-external.js').OptManagerType } OptManagerType - * @typedef { { workerOptions: WorkerOptions } } OptWorkerOptions - * @typedef { import('./types-external.js').OptEnableDisavow } OptEnableDisavow * @typedef { import('@agoric/swingset-liveslots').VatDeliveryObject } VatDeliveryObject * @typedef { import('@agoric/swingset-liveslots').VatDeliveryResult } VatDeliveryResult - * @typedef { import('@agoric/swingset-liveslots').VatSyscallObject } VatSyscallObject + * @typedef { import('@agoric/swingset-liveslots').VatSyscallHandler } VatSyscallHandler * * // used by vatKeeper.setSourceAndOptions(source, RecordedVatOptions) * * @typedef { BaseVatOptions & OptMeterID & OptManagerType } InternalDynamicVatOptions - * @typedef { BaseVatOptions & OptMeterID & OptWorkerOptions & OptEnableDisavow } RecordedVatOptions * + * RecordedVatOptions is fully-specified, no optional fields + * + * @typedef RecordedVatOptions + * @property { string } name + * @property { * } vatParameters + * @property { boolean } enableSetup + * @property { boolean } enablePipelining + * @property { boolean } useTranscript + * @property { number | 'never' } reapInterval + * @property { boolean } critical + * @property { MeterID } [meterID] // property must be present, but can be undefined + * @property { WorkerOptions } workerOptions + * @property { boolean } enableDisavow + */ + +/** * @typedef {{ * enablePipelining: boolean, * workerOptions: WorkerOptions, @@ -35,16 +48,15 @@ export {}; * enableDisavow: boolean, * useTranscript: boolean, * name: string, - * compareSyscalls?: import('./kernel/vat-loader/transcript.js').CompareSyscalls, * sourcedConsole: Pick, * enableSetup: boolean, * setup?: unknown, + * retainSyscall?: boolean * bundle?: Bundle, * }} ManagerOptions * - * @typedef { { deliver: (delivery: VatDeliveryObject) => Promise, - * replayOneDelivery: (delivery: VatDeliveryObject, expectedSyscalls: VatSyscallObject[], deliveryNum: number) => Promise, - * replayTranscript: (startPos: number | undefined) => Promise, + * @typedef { { deliver: (delivery: VatDeliveryObject, vatSyscallHandler: VatSyscallHandler) + * => Promise, * makeSnapshot?: undefined | ((endPos: number, ss: SnapStore) => Promise), * shutdown: () => Promise, * } } VatManager @@ -52,7 +64,7 @@ export {}; * bundle: Bundle, * managerOptions: ManagerOptions, * liveSlotsOptions: import('@agoric/swingset-liveslots').LiveSlotsOptions, - * vatSyscallHandler: unknown) => Promise, + * ) => Promise * } } VatManagerFactory * * @typedef {(problem: unknown, err?: Error) => void } KernelPanic diff --git a/packages/SwingSet/test/test-kernel.js b/packages/SwingSet/test/test-kernel.js index d5d16a5d58e..9bd2ee4b83e 100644 --- a/packages/SwingSet/test/test-kernel.js +++ b/packages/SwingSet/test/test-kernel.js @@ -38,7 +38,7 @@ async function makeKernel() { return buildKernel(endowments, {}, {}); } -const tsv = [{ d: ['startVat', kser({})], syscalls: [] }]; +const tsv = [{ d: ['startVat', kser({})], sc: [], r: { status: 'ok' } }]; test('build kernel', async t => { const kernel = await makeKernel(); @@ -150,10 +150,7 @@ test('vat store', async t => { ]); const data = kernel.dump(); // check that we're not sticking an undefined into the transcript - t.deepEqual(data.vatTables[0].state.transcript[1].syscalls[0].response, [ - 'ok', - null, - ]); + t.deepEqual(data.vatTables[0].state.transcript[1].sc[0].r, ['ok', null]); }); test('map inbound', async t => { @@ -1183,16 +1180,17 @@ test('transcript', async t => { result: 'p-60', }, ], - syscalls: [ + sc: [ { - d: [ + s: [ 'send', bobForAlice, { methargs: kser(['foo', ['fooarg']]), result: 'p+5' }, ], - response: ['ok', null], + r: ['ok', null], }, ], + r: { status: 'ok' }, }); }); diff --git a/packages/SwingSet/test/test-xsnap-errors.js b/packages/SwingSet/test/test-xsnap-errors.js index 82ffc051a4b..6c1aff2eccf 100644 --- a/packages/SwingSet/test/test-xsnap-errors.js +++ b/packages/SwingSet/test/test-xsnap-errors.js @@ -49,8 +49,6 @@ test('child termination distinguished from meter exhaustion', async t => { kernelKeeper, // @ts-expect-error kernelSlog is not used in this test kernelSlog: {}, - allVatPowers: undefined, - testLog: undefined, }); const fn = new URL('vat-xsnap-hang.js', import.meta.url).pathname; @@ -67,16 +65,15 @@ test('child termination distinguished from meter exhaustion', async t => { bundle, managerOptions, {}, - schandler, ); - await m.deliver(['startVat', kser()]); + await m.deliver(['startVat', kser()], schandler); const msg = { methargs: kser(['hang', []]) }; /** @type { VatDeliveryObject } */ const delivery = ['message', 'o+0', msg]; - const p = m.deliver(delivery); // won't resolve until child dies + const p = m.deliver(delivery, schandler); // won't resolve until child dies // please excuse ambient authority setTimeout( diff --git a/packages/swingset-liveslots/src/types.js b/packages/swingset-liveslots/src/types.js index da7027a27b0..115b3b5edd8 100644 --- a/packages/swingset-liveslots/src/types.js +++ b/packages/swingset-liveslots/src/types.js @@ -75,7 +75,7 @@ * @typedef { [tag: 'error', err: string ] } VatSyscallResultError * @typedef { VatSyscallResultOk | VatSyscallResultError } VatSyscallResult * - * @typedef { (vso: VatSyscallObject) => VatSyscallResult } VatSyscaller + * @typedef { (vso: VatSyscallObject) => VatSyscallResult } VatSyscallHandler * */ diff --git a/packages/swingset-xsnap-supervisor/lib/supervisor-helper.js b/packages/swingset-xsnap-supervisor/lib/supervisor-helper.js index 79926bbc0ca..27c7b6dddd8 100644 --- a/packages/swingset-xsnap-supervisor/lib/supervisor-helper.js +++ b/packages/swingset-xsnap-supervisor/lib/supervisor-helper.js @@ -8,7 +8,7 @@ import { * @typedef {import('@agoric/swingset-liveslots').VatDeliveryObject} VatDeliveryObject * @typedef {import('@agoric/swingset-liveslots').VatDeliveryResult} VatDeliveryResult * @typedef {import('@agoric/swingset-liveslots').VatSyscallObject} VatSyscallObject - * @typedef {import('@agoric/swingset-liveslots').VatSyscaller} VatSyscaller + * @typedef {import('@agoric/swingset-liveslots').VatSyscallHandler} VatSyscallHandler * @typedef {import('@endo/marshal').CapData} SwingSetCapData * @typedef { (delivery: VatDeliveryObject) => (VatDeliveryResult | Promise) } VatDispatcherSyncAsync * @typedef { (delivery: VatDeliveryObject) => Promise } VatDispatcher @@ -58,7 +58,7 @@ export { makeSupervisorDispatch }; * I should be given a `syscallToManager` function that accepts a * VatSyscallObject and (synchronously) returns a VatSyscallResult. * - * @param {VatSyscaller} syscallToManager + * @param {VatSyscallHandler} syscallToManager * @param {boolean} workerCanBlock * @typedef { unknown } TheSyscallObjectWithMethodsThatLiveslotsWants * @returns {TheSyscallObjectWithMethodsThatLiveslotsWants} diff --git a/packages/swingset-xsnap-supervisor/lib/supervisor-subprocess-xsnap.js b/packages/swingset-xsnap-supervisor/lib/supervisor-subprocess-xsnap.js index cc9f5663491..a86afaa7b34 100644 --- a/packages/swingset-xsnap-supervisor/lib/supervisor-subprocess-xsnap.js +++ b/packages/swingset-xsnap-supervisor/lib/supervisor-subprocess-xsnap.js @@ -23,7 +23,7 @@ import { * @typedef {import('@agoric/swingset-liveslots').VatDeliveryResult} VatDeliveryResult * @typedef {import('@agoric/swingset-liveslots').VatSyscallObject} VatSyscallObject * @typedef {import('@agoric/swingset-liveslots').VatSyscallResult} VatSyscallResult - * @typedef {import('@agoric/swingset-liveslots').VatSyscaller} VatSyscaller + * @typedef {import('@agoric/swingset-liveslots').VatSyscallHandler} VatSyscallHandler * @typedef {import('@agoric/swingset-liveslots').LiveSlotsOptions} LiveSlotsOptions * @typedef {import('@agoric/swingset-liveslots').MeterControl} MeterControl */ @@ -196,7 +196,7 @@ function makeWorker(port) { * @returns {Promise} */ async function setBundle(vatID, bundle, liveSlotsOptions) { - /** @type { VatSyscaller } */ + /** @type { VatSyscallHandler } */ function syscallToManager(vatSyscallObject) { workerLog('doSyscall', vatSyscallObject); const result = port.call(['syscall', vatSyscallObject]);