From a031034a373b23d199542880342d3c405fd4a87e Mon Sep 17 00:00:00 2001 From: Chip Morningstar Date: Thu, 7 Jan 2021 18:45:08 -0800 Subject: [PATCH] feat: refactor notification and subscription --- packages/SwingSet/src/kernel/cleanup.js | 55 +++++------ packages/SwingSet/src/kernel/kernel.js | 38 +++++--- packages/SwingSet/src/kernel/liveSlots.js | 30 +++++- .../SwingSet/src/kernel/state/vatKeeper.js | 8 ++ .../SwingSet/src/kernel/vatManager/deliver.js | 13 ++- .../SwingSet/src/kernel/vatManager/syscall.js | 4 +- packages/SwingSet/src/kernel/vatTranslator.js | 37 +++----- .../SwingSet/src/vats/comms/clist-kernel.js | 6 +- packages/SwingSet/src/vats/comms/delivery.js | 23 +++-- packages/SwingSet/src/vats/comms/dispatch.js | 16 +++- packages/SwingSet/test/test-kernel.js | 2 +- packages/SwingSet/test/test-liveslots.js | 16 +--- packages/SwingSet/test/test-vpid-kernel.js | 95 ++++++++++++------- packages/SwingSet/test/test-vpid-liveslots.js | 2 +- .../demo/resolveCircular2/bootstrap.js | 14 +++ .../demo/resolveCircular2/vat-bob.js | 32 +++++++ .../demo/resolveCircular3/bootstrap.js | 12 +++ .../demo/resolveCircular3/vat-alice.js | 7 ++ .../demo/resolveCircular3/vat-bob.js | 28 ++++++ .../demo/resolveSimpleCircular2/bootstrap.js | 13 +++ .../demo/resolveSimpleCircular2/vat-bob.js | 24 +++++ packages/swingset-runner/src/dumpstore.js | 40 +++++--- packages/swingset-runner/src/slogulator.js | 32 ++++--- 23 files changed, 375 insertions(+), 172 deletions(-) create mode 100644 packages/swingset-runner/demo/resolveCircular2/bootstrap.js create mode 100644 packages/swingset-runner/demo/resolveCircular2/vat-bob.js create mode 100644 packages/swingset-runner/demo/resolveCircular3/bootstrap.js create mode 100644 packages/swingset-runner/demo/resolveCircular3/vat-alice.js create mode 100644 packages/swingset-runner/demo/resolveCircular3/vat-bob.js create mode 100644 packages/swingset-runner/demo/resolveSimpleCircular2/bootstrap.js create mode 100644 packages/swingset-runner/demo/resolveSimpleCircular2/vat-bob.js diff --git a/packages/SwingSet/src/kernel/cleanup.js b/packages/SwingSet/src/kernel/cleanup.js index 4ff0d81646c5..4cd712e1a7e9 100644 --- a/packages/SwingSet/src/kernel/cleanup.js +++ b/packages/SwingSet/src/kernel/cleanup.js @@ -1,4 +1,4 @@ -// import { kdebug } from './kdebug'; +import { kdebug } from './kdebug'; import { parseKernelSlot } from './parseKernelSlots'; // XXX temporary flags to control features during development @@ -62,50 +62,39 @@ export function deleteCListEntryIfEasy( vatKeeper.deleteCListEntry(kpid, vpid); } -export function getKpidsToRetire( - vatID, - vatKeeper, - kernelKeeper, - rootKPID, - rootKernelData, -) { +export function getKpidsToRetire(kernelKeeper, rootKPID, rootKernelData) { const seen = new Set(); function scanKernelPromise(kpid, kernelData) { - // kdebug(`### scanning ${kpid} ${JSON.stringify(kernelData)}`); - if (vatKeeper.hasCListEntry(kpid)) { - // kdebug(`## adding ${kpid} to scan results`); - seen.add(kpid); - if (kernelData) { - for (const slot of kernelData.slots) { - const { type } = parseKernelSlot(slot); - // kdebug(`## examine ${kpid} slot ${slot}`); - if (type === 'promise') { - if (!seen.has(slot)) { - const kp = kernelKeeper.getKernelPromise(slot); - const { data, state } = kp; - // kdebug(`## state of ${slot} is: ${JSON.stringify(kp)}`); - if (state !== 'unresolved') { - if (data) { - scanKernelPromise(slot, data); - } - } else { - // kdebug(`## ${slot} is still unresolved`); + kdebug(`### scanning ${kpid} ${JSON.stringify(kernelData)}`); + seen.add(kpid); + if (kernelData) { + for (const slot of kernelData.slots) { + const { type } = parseKernelSlot(slot); + kdebug(`## examine ${kpid} slot ${slot}`); + if (type === 'promise') { + if (!seen.has(slot)) { + const kp = kernelKeeper.getKernelPromise(slot); + const { data, state } = kp; + kdebug(`## state of ${slot} is: ${JSON.stringify(kp)}`); + if (state !== 'unresolved') { + if (data) { + scanKernelPromise(slot, data); } } else { - // kdebug(`## ${slot} previously seen`); + kdebug(`## ${slot} is still unresolved`); } } else { - // kdebug(`## ${slot} is not a promise`); + kdebug(`## ${slot} previously seen`); } + } else { + kdebug(`## ${slot} is not a promise`); } - } else { - // kdebug(`## ${kpid} has no data`); } } else { - // kdebug(`## ${kpid} has no c-list entry for ${vatID}`); + kdebug(`## ${kpid} has no data`); } } - + kdebug(`## scanning ${rootKPID}`); scanKernelPromise(rootKPID, rootKernelData); return Array.from(seen); } diff --git a/packages/SwingSet/src/kernel/kernel.js b/packages/SwingSet/src/kernel/kernel.js index 7d55deb8e394..45f61196821c 100644 --- a/packages/SwingSet/src/kernel/kernel.js +++ b/packages/SwingSet/src/kernel/kernel.js @@ -1,5 +1,5 @@ import { Remotable, getInterfaceOf } from '@agoric/marshal'; -import { assert } from '@agoric/assert'; +import { assert, details } from '@agoric/assert'; import { importBundle } from '@agoric/import-bundle'; import { assertKnownOptions } from '../assertOptions'; import { makeVatManagerFactory } from './vatManager/factory'; @@ -16,6 +16,7 @@ import { insistDeviceID, insistVatID } from './id'; import { makeMeterManager } from './metering'; import { makeKernelSyscallHandler, doSend } from './kernelSyscall'; import { makeSlogger, makeDummySlogger } from './slogger'; +import { getKpidsToRetire } from './cleanup'; import { makeVatLoader } from './loadVat'; import { makeVatTranslators } from './vatTranslator'; @@ -470,18 +471,28 @@ export default function buildKernel( } else { const p = kernelKeeper.getKernelPromise(kpid); kernelKeeper.incStat(statNameForNotify(p.state)); - const kd = harden(['notify', kpid, p]); - const vd = vat.translators.kernelDeliveryToVatDelivery(kd); - if (vd) { - await deliverAndLogToVat(vatID, kd, vd); - - const resolutions = vd[1]; - const vatKeeper = kernelKeeper.getVatKeeper(vatID); - for (const vpid of Object.keys(resolutions)) { - const kpidToDelete = vatKeeper.mapVatSlotToKernelSlot(vpid); - vatKeeper.deleteCListEntry(kpidToDelete, vpid); - } + const vatKeeper = kernelKeeper.getVatKeeper(vatID); + + assert(p.state !== 'unresolved', details`spurious notification ${kpid}`); + const resolutions = []; + if (!vatKeeper.hasCListEntry(kpid)) { + kdebug(`vat ${vatID} has no c-list entry for ${kpid}`); + kdebug(`skipping notify of ${kpid} because it's already been done`); + return; + } + const targets = getKpidsToRetire(kernelKeeper, kpid, p.data); + if (targets.length === 0) { + kdebug(`no kpids to retire`); + kdebug(`skipping notify of ${kpid} because it's already been done`); + return; } + for (const toResolve of targets) { + resolutions.push([toResolve, kernelKeeper.getKernelPromise(toResolve)]); + } + const kd = harden(['notify', resolutions]); + const vd = vat.translators.kernelDeliveryToVatDelivery(kd); + vatKeeper.deleteCListEntriesForKernelSlots(targets); + await deliverAndLogToVat(vatID, kd, vd); } } @@ -589,7 +600,8 @@ export default function buildKernel( // which is fatal to the vat ksc = translators.vatSyscallToKernelSyscall(vatSyscallObject); } catch (vaterr) { - kdebug(`vat ${vatID} terminated: error during translation: ${vaterr}`); + // prettier-ignore + kdebug(`vat ${vatID} terminated: error during translation: ${vaterr} ${JSON.stringify(vatSyscallObject)}`); const problem = 'clist violation: prepare to die'; setTerminationTrigger(vatID, true, true, makeError(problem)); return harden(['error', problem]); diff --git a/packages/SwingSet/src/kernel/liveSlots.js b/packages/SwingSet/src/kernel/liveSlots.js index 07c5097e0a41..65290ea98d78 100644 --- a/packages/SwingSet/src/kernel/liveSlots.js +++ b/packages/SwingSet/src/kernel/liveSlots.js @@ -254,6 +254,16 @@ function build(syscall, forVatID, cacheSize, vatPowers, vatParameters) { return valToSlot.get(val); } + let importedPromises = null; + function beginCollectingPromiseImports() { + importedPromises = new Set(); + } + function finishCollectingPromiseImports() { + const result = importedPromises; + importedPromises = null; + return result; + } + function convertSlotToVal(slot, iface = undefined) { let val = slotToVal.get(slot); if (val) { @@ -285,7 +295,11 @@ function build(syscall, forVatID, cacheSize, vatPowers, vatParameters) { // but the current Promise API doesn't give us a way to discover // this, so we must subscribe right away. If we were using Vows or // some other then-able, we could just hook then() to notify us. - syscall.subscribe(slot); + if (importedPromises) { + importedPromises.add(slot); + } else { + syscall.subscribe(slot); + } } else if (type === 'device') { val = makeDeviceNode(slot, iface); } else { @@ -530,13 +544,21 @@ function build(syscall, forVatID, cacheSize, vatPowers, vatParameters) { function notify(resolutions) { assert(didRoot); - for (const vpid of Object.keys(resolutions)) { - const vp = resolutions[vpid]; + beginCollectingPromiseImports(); + for (const resolution of resolutions) { + const [vpid, vp] = resolution; notifyOnePromise(vpid, vp.rejected, vp.data); } - for (const vpid of Object.keys(resolutions)) { + for (const resolution of resolutions) { + const [vpid] = resolution; retirePromiseID(vpid); } + const imports = finishCollectingPromiseImports(); + for (const slot of imports) { + if (slotToVal.get(slot)) { + syscall.subscribe(slot); + } + } } // TODO: when we add notifyForward, guard against cycles diff --git a/packages/SwingSet/src/kernel/state/vatKeeper.js b/packages/SwingSet/src/kernel/state/vatKeeper.js index cff8737fd1b6..955b25bd1a9a 100644 --- a/packages/SwingSet/src/kernel/state/vatKeeper.js +++ b/packages/SwingSet/src/kernel/state/vatKeeper.js @@ -218,6 +218,13 @@ export function makeVatKeeper( } } + function deleteCListEntriesForKernelSlots(kernelSlots) { + for (const kernelSlot of kernelSlots) { + const vatSlot = mapKernelSlotToVatSlot(kernelSlot); + deleteCListEntry(kernelSlot, vatSlot); + } + } + /** * Generator function to return the vat's transcript, one entry at a time. */ @@ -284,6 +291,7 @@ export function makeVatKeeper( mapKernelSlotToVatSlot, hasCListEntry, deleteCListEntry, + deleteCListEntriesForKernelSlots, getTranscript, addToTranscript, vatStats, diff --git a/packages/SwingSet/src/kernel/vatManager/deliver.js b/packages/SwingSet/src/kernel/vatManager/deliver.js index e3b9c4296ca3..6c7b679d8a26 100644 --- a/packages/SwingSet/src/kernel/vatManager/deliver.js +++ b/packages/SwingSet/src/kernel/vatManager/deliver.js @@ -90,11 +90,14 @@ export function makeDeliver(tools, dispatch) { // ['message', target, msg] // target is vid // msg is: { method, args (capdata), result } - // ['notify', vpid, resolutions] - // vpid is the id of the primary promise being resolved - // resolutions is an object mapping vpid's to the final promise data, - // rendered in vat form, for both the primary promise and any collateral - // promises it references whose resolution has been newly discovered + // ['notify', resolutions] + // resolutions is an array of pairs: [vpid, resolution] + // vpid is the id of the primary promise being resolved + // resolution is the data for the promise being resolved + // There is an entry in the resolutions array both for the primary promise + // and for any collateral promises it references whose resolution was + // newly discovered at the time the notification delivery was being + // generated async function deliver(vatDeliverObject) { const [type, ...args] = vatDeliverObject; switch (type) { diff --git a/packages/SwingSet/src/kernel/vatManager/syscall.js b/packages/SwingSet/src/kernel/vatManager/syscall.js index 560b81caa526..380c55163916 100644 --- a/packages/SwingSet/src/kernel/vatManager/syscall.js +++ b/packages/SwingSet/src/kernel/vatManager/syscall.js @@ -85,7 +85,9 @@ export function createSyscall(transcriptManager) { // (and we'll be terminated, but the kernel and all other vats will // continue). Emit enough of an error message to explain the errors // that are about to ensue on our way down. - throw Error(`syscall suffered error, shutdown commencing`); + throw Error( + `syscall ${vatSyscallObject[0]} suffered error, shutdown commencing`, + ); } // otherwise vres is ['ok', null] or ['ok', capdata] transcriptManager.addSyscall(vatSyscallObject, data); diff --git a/packages/SwingSet/src/kernel/vatTranslator.js b/packages/SwingSet/src/kernel/vatTranslator.js index 84ed36e55cf1..89895ee9294c 100644 --- a/packages/SwingSet/src/kernel/vatTranslator.js +++ b/packages/SwingSet/src/kernel/vatTranslator.js @@ -4,7 +4,7 @@ import { insistKernelType, parseKernelSlot } from './parseKernelSlots'; import { insistVatType, parseVatSlot } from '../parseVatSlots'; import { insistCapData } from '../capdata'; import { kdebug, legibilizeMessageArgs, legibilizeValue } from './kdebug'; -import { deleteCListEntryIfEasy, getKpidsToRetire } from './cleanup'; +import { deleteCListEntryIfEasy } from './cleanup'; /* * Return a function that converts KernelDelivery objects into VatDelivery @@ -77,29 +77,20 @@ function makeTranslateKernelDeliveryToVatDelivery(vatID, kernelKeeper) { } } - function translateNotify(kpid, kp) { - assert(kp.state !== 'unresolved', details`spurious notification ${kpid}`); - const resolutions = {}; - kdebug(`notify ${kpid} ${JSON.stringify(kp)}`); - const targets = getKpidsToRetire( - vatID, - vatKeeper, - kernelKeeper, - kpid, - kp.data, - ); - if (targets.length > 0) { - for (const toResolve of targets) { - const p = kernelKeeper.getKernelPromise(toResolve); - const vpid = mapKernelSlotToVatSlot(toResolve); - resolutions[vpid] = translatePromiseDescriptor(p); - } - const vatDelivery = harden(['notify', resolutions]); - return vatDelivery; - } else { - kdebug(`skipping notify of ${kpid} because it's already been done`); - return null; + function translateNotify(kResolutions) { + const vResolutions = []; + let idx = 0; + for (const resolution of kResolutions) { + const [kpid, p] = resolution; + assert(p.state !== 'unresolved', details`spurious notification ${kpid}`); + const vpid = mapKernelSlotToVatSlot(kpid); + const vres = translatePromiseDescriptor(p); + vResolutions.push([vpid, vres]); + kdebug(`notify ${idx} ${kpid}/${vpid} ${JSON.stringify(vres)}`); + idx += 1; } + const vatDelivery = harden(['notify', vResolutions]); + return vatDelivery; } function kernelDeliveryToVatDelivery(kd) { diff --git a/packages/SwingSet/src/vats/comms/clist-kernel.js b/packages/SwingSet/src/vats/comms/clist-kernel.js index 3d2f0d467612..19e431bfc094 100644 --- a/packages/SwingSet/src/vats/comms/clist-kernel.js +++ b/packages/SwingSet/src/vats/comms/clist-kernel.js @@ -94,7 +94,7 @@ export function makeKernel(state, syscall, stateKit) { // *-LocalForKernel: kernel sending in to comms vat - function provideLocalForKernel(kref) { + function provideLocalForKernel(kref, doNotSubscribeSet) { const { type, allocatedByVat } = parseVatSlot(kref); if (type !== 'object' && type !== 'promise') { // TODO: reject the message rather than crashing weirdly, we @@ -125,7 +125,9 @@ export function makeKernel(state, syscall, stateKit) { // the kernel is telling us about a new promise, so it's the decider trackUnresolvedPromise(vpid); changeDeciderToKernel(vpid); - syscall.subscribe(vpid); + if (!doNotSubscribeSet || !doNotSubscribeSet.has(vpid)) { + syscall.subscribe(vpid); + } } } } diff --git a/packages/SwingSet/src/vats/comms/delivery.js b/packages/SwingSet/src/vats/comms/delivery.js index f99f6c91b0f3..8414be89a8c7 100644 --- a/packages/SwingSet/src/vats/comms/delivery.js +++ b/packages/SwingSet/src/vats/comms/delivery.js @@ -42,9 +42,11 @@ export function makeDeliveryKit(state, syscall, transmit, clistKit, stateKit) { return kernelData; } - function mapDataFromKernel(kdata) { + function mapDataFromKernel(kdata, doNotSubscribeSet) { insistCapData(kdata); - const slots = kdata.slots.map(provideLocalForKernel); + const slots = kdata.slots.map(slot => + provideLocalForKernel(slot, doNotSubscribeSet), + ); return harden({ body: kdata.body, slots }); } @@ -52,33 +54,36 @@ export function makeDeliveryKit(state, syscall, transmit, clistKit, stateKit) { // remote machine): translate to local, join with handleSend function sendFromKernel(target, method, kargs, kresult) { const result = provideLocalForKernelResult(kresult); - const args = mapDataFromKernel(kargs); + const args = mapDataFromKernel(kargs, null); const localDelivery = harden({ target, method, result, args }); handleSend(localDelivery); } - function mapResolutionFromKernel(resolution) { + function mapResolutionFromKernel(resolution, doNotSubscribeSet) { if (resolution.type === 'object') { - const slot = provideLocalForKernel(resolution.slot); + const slot = provideLocalForKernel(resolution.slot, null); return harden({ ...resolution, slot }); } if (resolution.type === 'data' || resolution.type === 'reject') { return harden({ ...resolution, - data: mapDataFromKernel(resolution.data), + data: mapDataFromKernel(resolution.data, doNotSubscribeSet), }); } throw Error(`unknown resolution type ${resolution.type}`); } // dispatch.notifyResolve* from kernel lands here (local vat resolving some - // Promise, we need to notify remove machines): translate to local, join + // Promise, we need to notify remote machines): translate to local, join // with handleResolution - function resolveFromKernel(vpid, resolution) { + function resolveFromKernel(vpid, resolution, doNotSubscribeSet) { insistPromiseIsUnresolved(vpid); insistDeciderIsKernel(vpid); changeDeciderFromKernelToComms(vpid); - handleResolution(vpid, mapResolutionFromKernel(resolution)); + handleResolution( + vpid, + mapResolutionFromKernel(resolution, doNotSubscribeSet), + ); } // dispatch.deliver with msg from vattp lands here, containing a message diff --git a/packages/SwingSet/src/vats/comms/dispatch.js b/packages/SwingSet/src/vats/comms/dispatch.js index f2f6de62ceaf..01f6d43af76f 100644 --- a/packages/SwingSet/src/vats/comms/dispatch.js +++ b/packages/SwingSet/src/vats/comms/dispatch.js @@ -73,7 +73,7 @@ export function buildCommsDispatch(syscall) { throw Error(`unknown target ${target}`); } - function notifyOnePromise(promiseID, rejected, data) { + function notifyOnePromise(promiseID, rejected, data, doNotSubscribeSet) { insistCapData(data); // console.debug(`comms.notifyOnePromise(${promiseID}, ${rejected}, ${data})`); // dumpState(state); @@ -115,15 +115,21 @@ export function buildCommsDispatch(syscall) { } else { resolution = harden({ type: 'data', data }); } - resolveFromKernel(promiseID, resolution); + resolveFromKernel(promiseID, resolution, doNotSubscribeSet); // XXX question: do we need to call retirePromiseIDIfEasy (or some special // comms vat version of it) here? } function notify(resolutions) { - for (const vpid of Object.keys(resolutions)) { - const vp = resolutions[vpid]; - notifyOnePromise(vpid, vp.rejected, vp.data); + const willBeResolved = new Set(); + for (const resolution of resolutions) { + const [vpid] = resolution; + willBeResolved.add(vpid); + } + + for (const resolution of resolutions) { + const [vpid, vp] = resolution; + notifyOnePromise(vpid, vp.rejected, vp.data, willBeResolved); } } diff --git a/packages/SwingSet/test/test-kernel.js b/packages/SwingSet/test/test-kernel.js index 5b212fb982c0..382684481ebc 100644 --- a/packages/SwingSet/test/test-kernel.js +++ b/packages/SwingSet/test/test-kernel.js @@ -17,7 +17,7 @@ function capdata(body, slots = []) { } function oneResolution(promiseID, rejected, data) { - return { [promiseID]: { rejected, data } }; + return [[promiseID, { rejected, data }]]; } function checkPromises(t, kernel, expected) { diff --git a/packages/SwingSet/test/test-liveslots.js b/packages/SwingSet/test/test-liveslots.js index 5ca29b8b94c6..a94650f3308b 100644 --- a/packages/SwingSet/test/test-liveslots.js +++ b/packages/SwingSet/test/test-liveslots.js @@ -14,7 +14,7 @@ function capargs(args, slots = []) { } function oneResolution(promiseID, rejected, data) { - return { [promiseID]: { rejected, data } }; + return [[promiseID, { rejected, data }]]; } function buildSyscall() { @@ -93,12 +93,7 @@ test('calls', async t => { t.deepEqual(log.shift(), { type: 'subscribe', target: 'p-1' }); t.deepEqual(log.shift(), 'two true'); - dispatch.notify({ - 'p-1': { - rejected: false, - data: capargs('result'), - }, - }); + dispatch.notify(oneResolution('p-1', false, capargs('result'))); await waitUntilQuiescent(); t.deepEqual(log.shift(), ['res', 'result']); @@ -116,12 +111,7 @@ test('calls', async t => { t.deepEqual(log.shift(), { type: 'subscribe', target: 'p-2' }); t.deepEqual(log.shift(), 'two true'); - dispatch.notify({ - 'p-2': { - rejected: true, - data: capargs('rejection'), - }, - }); + dispatch.notify(oneResolution('p-2', true, capargs('rejection'))); await waitUntilQuiescent(); t.deepEqual(log.shift(), ['rej', 'rejection']); diff --git a/packages/SwingSet/test/test-vpid-kernel.js b/packages/SwingSet/test/test-vpid-kernel.js index 2e46fa91efca..99378002c649 100644 --- a/packages/SwingSet/test/test-vpid-kernel.js +++ b/packages/SwingSet/test/test-vpid-kernel.js @@ -21,7 +21,7 @@ function capargs(args, slots = []) { } function oneResolution(promiseID, rejected, data) { - return { [promiseID]: { rejected, data } }; + return [[promiseID, { rejected, data }]]; } function makeConsole(tag) { @@ -558,7 +558,7 @@ async function doTest4567(t, which, mode) { }; onDispatchCallback = function odc1(d) { t.deepEqual(d, resolutionOf(p1VatA, mode, targetsA)); - t.is(inCList(kernel, vatA, p1kernel, p1VatA), expectRetirement); + t.is(inCList(kernel, vatA, p1kernel, p1VatA), !expectRetirement); }; const targetsB = { target2: rootAvatB, @@ -752,12 +752,15 @@ test(`kernel vpid handling crossing resolutions`, async t => { await kernel.run(); t.deepEqual(logX.shift(), { type: 'notify', - resolutions: { - [exportedGenResultAvatX]: { - rejected: false, - data: capargs([slot0arg], [exportedGenResultBvatX]), - }, - }, + resolutions: [ + [ + exportedGenResultAvatX, + { + rejected: false, + data: capargs([slot0arg], [exportedGenResultBvatX]), + }, + ], + ], }); t.deepEqual(logX, []); t.deepEqual(logA, []); @@ -783,45 +786,67 @@ test(`kernel vpid handling crossing resolutions`, async t => { await kernel.run(); t.deepEqual(logB.shift(), { type: 'notify', - resolutions: { - [importedGenResultAvatB]: { - rejected: false, - data: capargs([slot0arg], [importedGenResultBvatB]), - }, - [importedGenResultBvatB]: { - rejected: false, - data: capargs([slot0arg], [importedGenResultAvatB]), - }, - }, + resolutions: [ + [ + importedGenResultAvatB, + { + rejected: false, + data: capargs([slot0arg], [importedGenResultBvatB]), + }, + ], + [ + importedGenResultBvatB, + { + rejected: false, + data: capargs([slot0arg], [importedGenResultAvatB]), + }, + ], + ], }); t.deepEqual(logB, []); t.deepEqual(logX.shift(), { type: 'notify', - resolutions: { - [exportedGenResultBvatX]: { - rejected: false, - data: capargs([slot0arg], [importedGenResultAvatX]), - }, - }, + resolutions: [ + [ + exportedGenResultBvatX, + { + rejected: false, + data: capargs([slot0arg], [importedGenResultAvatX]), + }, + ], + [ + importedGenResultAvatX, + { + rejected: false, + data: capargs([slot0arg], [exportedGenResultBvatX]), + }, + ], + ], }); t.deepEqual(logX, []); t.deepEqual(logA.shift(), { type: 'notify', - resolutions: { - [importedGenResultAvatA]: { - rejected: false, - data: capargs([slot0arg], [importedGenResultBvatA]), - }, - [importedGenResultBvatA]: { - rejected: false, - data: capargs([slot0arg], [importedGenResultAvatA]), - }, - }, + resolutions: [ + [ + importedGenResultBvatA, + { + rejected: false, + data: capargs([slot0arg], [importedGenResultAvatA]), + }, + ], + [ + importedGenResultAvatA, + { + rejected: false, + data: capargs([slot0arg], [importedGenResultBvatA]), + }, + ], + ], }); t.deepEqual(logA, []); t.is(inCList(kernel, vatA, genResultAkernel, importedGenResultAvatA), false); t.is(inCList(kernel, vatB, genResultAkernel, importedGenResultAvatB), false); - t.is(inCList(kernel, vatX, genResultAkernel, importedGenResultAvatX), true); + t.is(inCList(kernel, vatX, genResultAkernel, importedGenResultAvatX), false); t.is(inCList(kernel, vatA, genResultBkernel, importedGenResultBvatA), false); t.is(inCList(kernel, vatB, genResultBkernel, importedGenResultBvatB), false); diff --git a/packages/SwingSet/test/test-vpid-liveslots.js b/packages/SwingSet/test/test-vpid-liveslots.js index 19a45fe88b32..c252342a7801 100644 --- a/packages/SwingSet/test/test-vpid-liveslots.js +++ b/packages/SwingSet/test/test-vpid-liveslots.js @@ -20,7 +20,7 @@ function capargs(args, slots = []) { } function oneResolution(promiseID, rejected, data) { - return { [promiseID]: { rejected, data } }; + return [[promiseID, { rejected, data }]]; } function buildSyscall() { diff --git a/packages/swingset-runner/demo/resolveCircular2/bootstrap.js b/packages/swingset-runner/demo/resolveCircular2/bootstrap.js new file mode 100644 index 000000000000..1370bcce0396 --- /dev/null +++ b/packages/swingset-runner/demo/resolveCircular2/bootstrap.js @@ -0,0 +1,14 @@ +import { E } from '@agoric/eventual-send'; + +console.log(`=> loading bootstrap.js`); + +export function buildRootObject(_vatPowers) { + return harden({ + bootstrap(vats) { + const pa = E(vats.bob).genPromise1(); + const pb = E(vats.bob).genPromise2(); + E(vats.bob).usePromises([pa], [pb]); + E(vats.bob).finish(); + }, + }); +} diff --git a/packages/swingset-runner/demo/resolveCircular2/vat-bob.js b/packages/swingset-runner/demo/resolveCircular2/vat-bob.js new file mode 100644 index 000000000000..b65ee8e8ebad --- /dev/null +++ b/packages/swingset-runner/demo/resolveCircular2/vat-bob.js @@ -0,0 +1,32 @@ +function makePR() { + let r; + const p = new Promise((resolve, _reject) => { + r = resolve; + }); + return [p, r]; +} + +export function buildRootObject(_vatPowers) { + let p1; + let r1; + let p2; + let r2; + let savepa; + return harden({ + genPromise1() { + [p1, r1] = makePR(); + return p1; + }, + genPromise2() { + [p2, r2] = makePR(); + return p2; + }, + usePromises(pa, pb) { + r1(pb); + savepa = pa; + }, + finish() { + r2(savepa); + }, + }); +} diff --git a/packages/swingset-runner/demo/resolveCircular3/bootstrap.js b/packages/swingset-runner/demo/resolveCircular3/bootstrap.js new file mode 100644 index 000000000000..a3040f415882 --- /dev/null +++ b/packages/swingset-runner/demo/resolveCircular3/bootstrap.js @@ -0,0 +1,12 @@ +import { E } from '@agoric/eventual-send'; + +export function buildRootObject(_vatPowers) { + return harden({ + async bootstrap(vats) { + const pa = E(vats.bob).genPromise1(); + const pb = E(vats.bob).genPromise2(); + E(vats.bob).usePromises([pa], [pb]); + E(vats.alice).acceptPromise(pa); + }, + }); +} diff --git a/packages/swingset-runner/demo/resolveCircular3/vat-alice.js b/packages/swingset-runner/demo/resolveCircular3/vat-alice.js new file mode 100644 index 000000000000..58d50a58e38a --- /dev/null +++ b/packages/swingset-runner/demo/resolveCircular3/vat-alice.js @@ -0,0 +1,7 @@ +export function buildRootObject(_vatPowers) { + return harden({ + acceptPromise(_p) { + console.log('Alice got p'); + }, + }); +} diff --git a/packages/swingset-runner/demo/resolveCircular3/vat-bob.js b/packages/swingset-runner/demo/resolveCircular3/vat-bob.js new file mode 100644 index 000000000000..5b115289da86 --- /dev/null +++ b/packages/swingset-runner/demo/resolveCircular3/vat-bob.js @@ -0,0 +1,28 @@ +function makePR() { + let r; + const p = new Promise((resolve, _reject) => { + r = resolve; + }); + return [p, r]; +} + +export function buildRootObject(_vatPowers) { + let p1; + let r1; + let p2; + let r2; + return harden({ + genPromise1() { + [p1, r1] = makePR(); + return p1; + }, + genPromise2() { + [p2, r2] = makePR(); + return p2; + }, + usePromises(pa, pb) { + r1(pb); + r2(pa); + }, + }); +} diff --git a/packages/swingset-runner/demo/resolveSimpleCircular2/bootstrap.js b/packages/swingset-runner/demo/resolveSimpleCircular2/bootstrap.js new file mode 100644 index 000000000000..baef1b4bdfdd --- /dev/null +++ b/packages/swingset-runner/demo/resolveSimpleCircular2/bootstrap.js @@ -0,0 +1,13 @@ +import { E } from '@agoric/eventual-send'; + +console.log(`=> loading bootstrap.js`); + +export function buildRootObject(_vatPowers) { + return harden({ + bootstrap(vats) { + const pa = E(vats.bob).genPromise(); + E(vats.bob).usePromise([pa]); + E(vats.bob).getThing(); + }, + }); +} diff --git a/packages/swingset-runner/demo/resolveSimpleCircular2/vat-bob.js b/packages/swingset-runner/demo/resolveSimpleCircular2/vat-bob.js new file mode 100644 index 000000000000..186b207675d9 --- /dev/null +++ b/packages/swingset-runner/demo/resolveSimpleCircular2/vat-bob.js @@ -0,0 +1,24 @@ +function makePR() { + let r; + const p = new Promise((resolve, _reject) => { + r = resolve; + }); + return [p, r]; +} + +export function buildRootObject(_vatPowers) { + let p; + let r; + return harden({ + genPromise() { + [p, r] = makePR(); + return p; + }, + usePromise(pa) { + r(pa); + }, + getThing() { + return p; + }, + }); +} diff --git a/packages/swingset-runner/src/dumpstore.js b/packages/swingset-runner/src/dumpstore.js index 6c3ffd79bf41..c3e772ee621d 100644 --- a/packages/swingset-runner/src/dumpstore.js +++ b/packages/swingset-runner/src/dumpstore.js @@ -28,7 +28,20 @@ export function dumpStore(store, outfile, rawMode) { gap(); } - popt('runQueue'); + const runQueue = JSON.parse(eat('runQueue')); + if (runQueue.length === 0) { + p('runQueue :: []'); + } else { + p('runQueue :: ['); + let idx = 1; + for (const entry of runQueue) { + const comma = idx === runQueue.length ? '' : ','; + idx += 1; + p(` ${JSON.stringify(entry)}${comma}`); + } + p(']'); + } + popt('crankNumber'); popt('kernelStats'); gap(); @@ -132,8 +145,7 @@ export function dumpStore(store, outfile, rawMode) { popt(key); } for (const key of groupKeys(`${v}.t.`)) { - transcript.push([key, state.get(key)]); - state.delete(key); + transcript.push([key, eat(key)]); } } @@ -186,8 +198,7 @@ export function dumpStore(store, outfile, rawMode) { function pgroup(baseKey) { const toSort = []; for (const key of groupKeys(baseKey)) { - toSort.push([key, state.get(key)]); - state.delete(key); + toSort.push([key, eat(key)]); } // sort similar keys by their numeric portions if possible, e.g., // "ko7.owner" should be less than "ko43.owner" even though it would be the @@ -227,10 +238,9 @@ export function dumpStore(store, outfile, rawMode) { } } - function popt(key) { + function eat(key) { if (state.has(key)) { const value = state.get(key); - pkv(key, value); state.delete(key); return value; } else { @@ -238,18 +248,22 @@ export function dumpStore(store, outfile, rawMode) { } } + function popt(key) { + const value = eat(key); + if (value) { + pkv(key, value); + } + return value; + } + function poptBig(tag, key) { - if (state.has(key)) { - const value = state.get(key); + const value = eat(key); + if (value) { if (value.length > 50) { pkv(key, `<<${tag} ${value.length}>>`); } else { pkv(key, value); } - state.delete(key); - return value; - } else { - return undefined; } } } diff --git a/packages/swingset-runner/src/slogulator.js b/packages/swingset-runner/src/slogulator.js index 2128c30a5499..a10ed120eb03 100644 --- a/packages/swingset-runner/src/slogulator.js +++ b/packages/swingset-runner/src/slogulator.js @@ -289,20 +289,24 @@ export function main() { } function doDeliverNotify(delivery) { - const target = delivery[1]; - const value = delivery[2]; - const tag = terse ? value.state : `notify ${value.state}`; - switch (value.state) { - case 'fulfilledToPresence': - p(`${tag}: ${pref(target)} := ${pref(value.slot)}`); - break; - case 'fulfilledToData': - case 'rejected': - p(`${tag}: ${pref(target)} := ${pdata(value.data)}`); - break; - default: - p(`notify: unknown state "${value.state}"`); - break; + const resolutions = delivery[1]; + let idx = 0; + for (const resolution of resolutions) { + const [target, value] = resolution; + const tag = terse ? value.state : `notify ${value.state}`; + switch (value.state) { + case 'fulfilledToPresence': + p(`${tag}: ${idx} ${pref(target)} := ${pref(value.slot)}`); + break; + case 'fulfilledToData': + case 'rejected': + p(`${tag}: ${idx} ${pref(target)} := ${pdata(value.data)}`); + break; + default: + p(`notify: unknown state "${value.state}"`); + break; + } + idx += 1; } }