From 4ffb91147dbdae971111d7f2fa1e5c9cdc1ae578 Mon Sep 17 00:00:00 2001 From: Chip Morningstar Date: Thu, 10 Dec 2020 17:42:36 -0800 Subject: [PATCH] feat: promise resolution notification refactoring --- packages/SwingSet/src/kernel/cleanup.js | 51 ++++++++++-- packages/SwingSet/src/kernel/kernel.js | 5 +- packages/SwingSet/src/kernel/liveSlots.js | 81 ++++++------------- .../SwingSet/src/kernel/state/kernelKeeper.js | 3 +- .../SwingSet/src/kernel/vatManager/deliver.js | 29 +++---- .../kernel/vatManager/nodeWorkerSupervisor.js | 22 ++--- .../kernel/vatManager/subprocessSupervisor.js | 22 ++--- packages/SwingSet/src/kernel/vatTranslator.js | 56 +++++++++---- .../SwingSet/src/makeUndeliverableError.js | 2 +- .../SwingSet/src/vats/comms/clist-outbound.js | 2 +- packages/SwingSet/src/vats/comms/dispatch.js | 52 ++++++------ packages/SwingSet/test/message-patterns.js | 59 ++++++++++++++ packages/SwingSet/test/test-kernel.js | 36 +++++---- packages/SwingSet/test/test-liveslots.js | 15 ++-- packages/SwingSet/test/test-vpid-kernel.js | 24 +++--- packages/SwingSet/test/test-vpid-liveslots.js | 16 ++-- packages/SwingSet/test/util.js | 18 +---- packages/SwingSet/test/workers/vat-target.js | 6 +- packages/swingset-runner/src/slogulator.js | 10 ++- 19 files changed, 298 insertions(+), 211 deletions(-) diff --git a/packages/SwingSet/src/kernel/cleanup.js b/packages/SwingSet/src/kernel/cleanup.js index 976594d9160..1285cc2fbd5 100644 --- a/packages/SwingSet/src/kernel/cleanup.js +++ b/packages/SwingSet/src/kernel/cleanup.js @@ -1,23 +1,62 @@ import { kdebug } from './kdebug'; import { parseKernelSlot } from './parseKernelSlots'; +// XXX temporary flags to control features during development +const ENABLE_PROMISE_ANALYSIS = true; // flag to enable/disable check to see if delete clist entry is ok + export function deleteCListEntryIfEasy( vatID, vatKeeper, + kernelKeeper, kpid, vpid, kernelData, ) { - let idx = 0; - for (const slot of kernelData.slots) { - const { type } = parseKernelSlot(slot); - if (type === 'promise') { + if (ENABLE_PROMISE_ANALYSIS) { + const visited = new Set(); + let sawPromise; + + function scanKernelPromise(scanKPID, scanKernelData) { + visited.add(scanKPID); + kdebug(`@@@ scan ${scanKPID}`); + if (scanKernelData) { + for (const slot of scanKernelData.slots) { + const { type } = parseKernelSlot(slot); + if (type === 'promise') { + sawPromise = slot; + if (visited.has(slot)) { + kdebug(`@@@ ${slot} previously visited`); + return true; + } else { + const { data, state } = kernelKeeper.getKernelPromise(slot); + if (data) { + if (scanKernelPromise(slot, data)) { + kdebug(`@@@ scan ${slot} detects circularity`); + return true; + } + } else { + kdebug(`@@@ scan ${slot} state = ${state}`); + } + } + } + } + } + kdebug(`@@@ scan ${scanKPID} detects no circularity`); + return false; + } + + kdebug(`@@ checking ${vatID} ${kpid} for circularity`); + if (scanKernelPromise(kpid, kernelData)) { + kdebug( + `Unable to delete ${vatID} clist entry ${kpid}<=>${vpid} because it is indirectly self-referential`, + ); + return; + } else if (sawPromise) { kdebug( - `Unable to delete ${vatID} clist entry ${kpid}<=>${vpid} because slot[${idx}]===${slot} is a promise`, + `Unable to delete ${vatID} clist entry ${kpid}<=>${vpid} because there was a contained promise ${sawPromise}`, ); return; } - idx += 1; } vatKeeper.deleteCListEntry(kpid, vpid); } diff --git a/packages/SwingSet/src/kernel/kernel.js b/packages/SwingSet/src/kernel/kernel.js index 300d4233969..a0462e19bc1 100644 --- a/packages/SwingSet/src/kernel/kernel.js +++ b/packages/SwingSet/src/kernel/kernel.js @@ -157,8 +157,7 @@ export default function buildKernel( // runQueue entries are {type, vatID, more..}. 'more' depends on type: // * deliver: target, msg - // * notifyFulfillToData/notifyFulfillToPresence/notifyReject: - // kernelPromiseID + // * notify: kernelPromiseID // in the kernel table, promises and resolvers are both indexed by the same // value. kernelPromises[promiseID] = { decider, subscribers } @@ -454,7 +453,7 @@ export default function buildKernel( case 'fulfilledToData': return 'dispatchNotifyFulfillToData'; case 'rejected': - return 'dispatchReject'; + return 'dispatchNotifyReject'; default: throw Error(`unknown promise state ${state}`); } diff --git a/packages/SwingSet/src/kernel/liveSlots.js b/packages/SwingSet/src/kernel/liveSlots.js index 9e4f50e4b09..90a3be24e8b 100644 --- a/packages/SwingSet/src/kernel/liveSlots.js +++ b/packages/SwingSet/src/kernel/liveSlots.js @@ -123,11 +123,11 @@ function build(syscall, forVatID, cacheSize, vatPowers, vatParameters) { insistVatType('promise', vpid); lsdebug(`makeImportedPromise(${vpid})`); - // The Promise will we associated with a handler that converts p~.foo() - // into a syscall.send() that targets the vpid. When the Promise is - // resolved (during receipt of a dispatch.notifyFulfill* or - // notifyReject), this Promise's handler will be replaced by the handler - // of the resolution, which might be a Presence or a local object. + // The Promise will we associated with a handler that converts p~.foo() into + // a syscall.send() that targets the vpid. When the Promise is resolved + // (during receipt of a dispatch.notify), this Promise's handler will be + // replaced by the handler of the resolution, which might be a Presence or a + // local object. // for safety as we shake out bugs in HandledPromise, we guard against // this handler being used after it was supposed to be resolved @@ -382,7 +382,7 @@ function build(syscall, forVatID, cacheSize, vatPowers, vatParameters) { } // TODO: if we acquire new decision-making authority over a promise that // we already knew about ('result' is already in slotToVal), we should no - // longer accept dispatch.notifyFulfill from the kernel. We currently use + // longer accept dispatch.notify from the kernel. We currently use // importedPromisesByPromiseID to track a combination of "we care about // when this promise resolves" and "we are listening for the kernel to // resolve it". We should split that into two tables or something. And we @@ -435,14 +435,18 @@ function build(syscall, forVatID, cacheSize, vatPowers, vatParameters) { slotToVal.delete(promiseID); } + const ENABLE_PROMISE_ANALYSIS = true; + function retirePromiseIDIfEasy(promiseID, data) { - for (const slot of data.slots) { - const { type } = parseVatSlot(slot); - if (type === 'promise') { - lsdebug( - `Unable to retire ${promiseID} because slot ${slot} is a promise`, - ); - return; + if (ENABLE_PROMISE_ANALYSIS) { + for (const slot of data.slots) { + const { type } = parseVatSlot(slot); + if (type === 'promise') { + lsdebug( + `Unable to retire ${promiseID} because slot ${slot} is a promise`, + ); + return; + } } } retirePromiseID(promiseID); @@ -501,11 +505,11 @@ function build(syscall, forVatID, cacheSize, vatPowers, vatParameters) { }; } - function notifyFulfillToData(promiseID, data) { + function notify(promiseID, rejected, data) { assert(didRoot); insistCapData(data); lsdebug( - `ls.dispatch.notifyFulfillToData(${promiseID}, ${data.body}, ${data.slots})`, + `ls.dispatch.notify(${promiseID}, ${rejected}, ${data.body}, [${data.slots}])`, ); insistVatType('promise', promiseID); // TODO: insist that we do not have decider authority for promiseID @@ -514,46 +518,16 @@ function build(syscall, forVatID, cacheSize, vatPowers, vatParameters) { } const pRec = importedPromisesByPromiseID.get(promiseID); const val = m.unserialize(data); - pRec.resolve(val); - retirePromiseIDIfEasy(promiseID, data); - } - - function notifyFulfillToPresence(promiseID, slot) { - assert(didRoot); - lsdebug(`ls.dispatch.notifyFulfillToPresence(${promiseID}, ${slot})`); - insistVatType('promise', promiseID); - // TODO: insist that we do not have decider authority for promiseID - insistVatType('object', slot); - if (!importedPromisesByPromiseID.has(promiseID)) { - throw new Error(`unknown promiseID '${promiseID}'`); + if (rejected) { + pRec.reject(val); + } else { + pRec.resolve(val); } - const pRec = importedPromisesByPromiseID.get(promiseID); - const val = convertSlotToVal(slot); - // val is either a local pass-by-presence object, or a Presence (which - // points at some remote pass-by-presence object). - pRec.resolve(val); - retirePromiseID(promiseID); + retirePromiseIDIfEasy(promiseID, data); } // TODO: when we add notifyForward, guard against cycles - function notifyReject(promiseID, data) { - assert(didRoot); - insistCapData(data); - lsdebug( - `ls.dispatch.notifyReject(${promiseID}, ${data.body}, ${data.slots})`, - ); - insistVatType('promise', promiseID); - // TODO: insist that we do not have decider authority for promiseID - if (!importedPromisesByPromiseID.has(promiseID)) { - throw new Error(`unknown promiseID '${promiseID}'`); - } - const pRec = importedPromisesByPromiseID.get(promiseID); - const val = m.unserialize(data); - pRec.reject(val); - retirePromiseIDIfEasy(promiseID, data); - } - function exitVat(completion) { syscall.exit(false, m.serialize(harden(completion))); } @@ -586,12 +560,7 @@ function build(syscall, forVatID, cacheSize, vatPowers, vatParameters) { slotToVal.set(rootSlot, rootObject); } - const dispatch = harden({ - deliver, - notifyFulfillToData, - notifyFulfillToPresence, - notifyReject, - }); + const dispatch = harden({ deliver, notify }); return harden({ vatGlobals, setBuildRootObject, dispatch, m }); } diff --git a/packages/SwingSet/src/kernel/state/kernelKeeper.js b/packages/SwingSet/src/kernel/state/kernelKeeper.js index 5225fea9e2a..219b4a234b6 100644 --- a/packages/SwingSet/src/kernel/state/kernelKeeper.js +++ b/packages/SwingSet/src/kernel/state/kernelKeeper.js @@ -161,9 +161,10 @@ export default function makeKernelKeeper(storage, kernelSlog) { syscallCallNow: 0, dispatches: 0, dispatchDeliver: 0, + dispatchNotify: 0, dispatchNotifyFulfillToData: 0, dispatchNotifyFulfillToPresence: 0, - dispatchReject: 0, + dispatchNotifyReject: 0, clistEntries: 0, clistEntriesUp: 0, clistEntriesDown: 0, diff --git a/packages/SwingSet/src/kernel/vatManager/deliver.js b/packages/SwingSet/src/kernel/vatManager/deliver.js index 5014fb246e0..24193746edb 100644 --- a/packages/SwingSet/src/kernel/vatManager/deliver.js +++ b/packages/SwingSet/src/kernel/vatManager/deliver.js @@ -81,29 +81,26 @@ export function makeDeliver(tools, dispatch) { ); } - async function deliverOneNotification(vpid, vp) { - const errmsg = `vat[${vatID}].promise[${vpid}] ${vp.state} failed`; - switch (vp.state) { - case 'fulfilledToPresence': - return doProcess(['notifyFulfillToPresence', vpid, vp.slot], errmsg); - case 'redirected': - // TODO unimplemented - throw new Error('not implemented yet'); - case 'fulfilledToData': - return doProcess(['notifyFulfillToData', vpid, vp.data], errmsg); - case 'rejected': - return doProcess(['notifyReject', vpid, vp.data], errmsg); - default: - throw Error(`unknown promise state '${vp.state}'`); + async function deliverOneNotification(primaryVpid, resolutions) { + for (const vpid of Object.keys(resolutions)) { + // XXX return inside loop is wrong once `resolutions` has more than 1 element + const vp = resolutions[vpid]; + const errmsg = `vat[${vatID}].promise[${vpid}] ${vp.rejected} failed`; + return doProcess(['notify', vpid, vp.rejected, vp.data], errmsg); } + // XXX placeholder to make lint shut up until we're done implementing things + return ['error', 'incomplete code, this should never happen']; } // vatDeliverObject is: // ['message', target, msg] // target is vid // msg is: { method, args (capdata), result } - // ['notify', vpid, vp] - // vp is the current (final) promise data, rendered in vat form + // ['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 async function deliver(vatDeliverObject) { const [type, ...args] = vatDeliverObject; switch (type) { diff --git a/packages/SwingSet/src/kernel/vatManager/nodeWorkerSupervisor.js b/packages/SwingSet/src/kernel/vatManager/nodeWorkerSupervisor.js index a6a3f1bdb8a..ea9fa01d253 100644 --- a/packages/SwingSet/src/kernel/vatManager/nodeWorkerSupervisor.js +++ b/packages/SwingSet/src/kernel/vatManager/nodeWorkerSupervisor.js @@ -59,21 +59,15 @@ function doMessage(targetSlot, msg) { ); } -function doNotify(vpid, vp) { - const errmsg = `vat.promise[${vpid}] ${vp.state} failed`; - switch (vp.state) { - case 'fulfilledToPresence': - return doProcess(['notifyFulfillToPresence', vpid, vp.slot], errmsg); - case 'redirected': - // TODO unimplemented - throw new Error('not implemented yet'); - case 'fulfilledToData': - return doProcess(['notifyFulfillToData', vpid, vp.data], errmsg); - case 'rejected': - return doProcess(['notifyReject', vpid, vp.data], errmsg); - default: - throw Error(`unknown promise state '${vp.state}'`); +function doNotify(primaryVpid, resolutions) { + for (const vpid of Object.keys(resolutions)) { + // XXX return inside loop is wrong once `resolutions` has more than 1 element + const vp = resolutions[vpid]; + const errmsg = `vat.promise[${vpid}] ${vp.rejected} failed`; + return doProcess(['notify', vpid, vp.rejected, vp.data], errmsg); } + // XXX placeholder to make lint shut up until we're done implementing things + return ['error', 'incomplete code, this should never happen']; } parentPort.on('message', ([type, ...margs]) => { diff --git a/packages/SwingSet/src/kernel/vatManager/subprocessSupervisor.js b/packages/SwingSet/src/kernel/vatManager/subprocessSupervisor.js index f1eee8da70a..f6246d3aafa 100644 --- a/packages/SwingSet/src/kernel/vatManager/subprocessSupervisor.js +++ b/packages/SwingSet/src/kernel/vatManager/subprocessSupervisor.js @@ -59,21 +59,15 @@ function doMessage(targetSlot, msg) { ); } -function doNotify(vpid, vp) { - const errmsg = `vat.promise[${vpid}] ${vp.state} failed`; - switch (vp.state) { - case 'fulfilledToPresence': - return doProcess(['notifyFulfillToPresence', vpid, vp.slot], errmsg); - case 'redirected': - // TODO unimplemented - throw new Error('not implemented yet'); - case 'fulfilledToData': - return doProcess(['notifyFulfillToData', vpid, vp.data], errmsg); - case 'rejected': - return doProcess(['notifyReject', vpid, vp.data], errmsg); - default: - throw Error(`unknown promise state '${vp.state}'`); +function doNotify(primaryVpid, resolutions) { + for (const vpid of Object.keys(resolutions)) { + // XXX return inside loop is wrong once `resolutions` has more than 1 element + const vp = resolutions[vpid]; + const errmsg = `vat.promise[${vpid}] ${vp.rejected} failed`; + return doProcess(['notify', vpid, vp.rejected, vp.data], errmsg); } + // XXX placeholder to make lint shut up until we're done implementing things + return ['error', 'incomplete code, this should never happen']; } const toParent = arrayEncoderStream(); diff --git a/packages/SwingSet/src/kernel/vatTranslator.js b/packages/SwingSet/src/kernel/vatTranslator.js index 5915daa90ea..31895eb223f 100644 --- a/packages/SwingSet/src/kernel/vatTranslator.js +++ b/packages/SwingSet/src/kernel/vatTranslator.js @@ -52,26 +52,38 @@ function makeTranslateKernelDeliveryToVatDelivery(vatID, kernelKeeper) { return vatDelivery; } - function translateNotify(kpid, kp) { - assert(kp.state !== 'unresolved', details`spurious notification ${kpid}`); - const vpid = mapKernelSlotToVatSlot(kpid); - const vp = { state: kp.state }; + function translatePromiseDescriptor(kp) { if (kp.state === 'fulfilledToPresence') { - vp.slot = mapKernelSlotToVatSlot(kp.slot); - vatKeeper.deleteCListEntry(kpid, vpid); + return { + rejected: false, + data: { + body: '{"@qclass":"slot","index":0}', + slots: [mapKernelSlotToVatSlot(kp.slot)], + }, + }; + } else if (kp.state === 'fulfilledToData' || kp.state === 'rejected') { + return { + rejected: kp.state === 'rejected', + data: { + ...kp.data, + slots: kp.data.slots.map(slot => mapKernelSlotToVatSlot(slot)), + }, + }; } else if (kp.state === 'redirected') { // TODO unimplemented throw new Error('not implemented yet'); - } else if (kp.state === 'fulfilledToData' || kp.state === 'rejected') { - vp.data = { - ...kp.data, - slots: kp.data.slots.map(slot => mapKernelSlotToVatSlot(slot)), - }; - deleteCListEntryIfEasy(vatID, vatKeeper, kpid, vpid, kp.data); } else { throw new Error(`unknown kernelPromise state '${kp.state}'`); } - const vatDelivery = harden(['notify', vpid, vp]); + } + + function translateNotify(kpid, kp) { + assert(kp.state !== 'unresolved', details`spurious notification ${kpid}`); + const vpid = mapKernelSlotToVatSlot(kpid); + const resolutions = {}; + resolutions[vpid] = translatePromiseDescriptor(kp); + deleteCListEntryIfEasy(vatID, vatKeeper, kernelKeeper, kpid, vpid, kp.data); + const vatDelivery = harden(['notify', vpid, resolutions]); return vatDelivery; } @@ -225,7 +237,14 @@ function makeTranslateVatSyscallToKernelSyscall(vatID, kernelKeeper) { data.body } ${JSON.stringify(data.slots)}/${JSON.stringify(kernelSlots)}`, ); - deleteCListEntryIfEasy(vatID, vatKeeper, kpid, vpid, kernelData); + deleteCListEntryIfEasy( + vatID, + vatKeeper, + kernelKeeper, + kpid, + vpid, + kernelData, + ); return harden(['fulfillToData', vatID, kpid, kernelData]); } @@ -240,7 +259,14 @@ function makeTranslateVatSyscallToKernelSyscall(vatID, kernelKeeper) { data.body } ${JSON.stringify(data.slots)}/${JSON.stringify(kernelSlots)}`, ); - deleteCListEntryIfEasy(vatID, vatKeeper, kpid, vpid, kernelData); + deleteCListEntryIfEasy( + vatID, + vatKeeper, + kernelKeeper, + kpid, + vpid, + kernelData, + ); return harden(['reject', vatID, kpid, kernelData]); } diff --git a/packages/SwingSet/src/makeUndeliverableError.js b/packages/SwingSet/src/makeUndeliverableError.js index 0e758577ab7..ce830c022d6 100644 --- a/packages/SwingSet/src/makeUndeliverableError.js +++ b/packages/SwingSet/src/makeUndeliverableError.js @@ -2,7 +2,7 @@ // objects and non-callable data. Attempting to send a message to data will // throw an error. This function provides a consistent definition of this // error message, for the use of the kernel and the comms vat. The capdata it -// returns can be used in a `notifyReject` on the result promise. +// returns can be used in a `notify` on the rejection promise. // Within a vat, the native JavaScript behavior (e.g. `const obj = {}; // obj.foo()`) would be TypeError with a message like "obj.foo is not a diff --git a/packages/SwingSet/src/vats/comms/clist-outbound.js b/packages/SwingSet/src/vats/comms/clist-outbound.js index 96ce58e2819..88ac7c6612f 100644 --- a/packages/SwingSet/src/vats/comms/clist-outbound.js +++ b/packages/SwingSet/src/vats/comms/clist-outbound.js @@ -127,7 +127,7 @@ export function makeOutbound(state, stateKit) { // hear about it. If we then get the authority back again, we no longer // want to hear about its resolution (since we're the ones doing the // resolving), but the kernel still thinks of us as subscribing, so we'll - // get a bogus dispatch.notifyFulfill*. Currently we throw an error, which + // get a bogus dispatch.notify. Currently we throw an error, which // is currently ignored but might prompt a vat shutdown in the future. return rpid; diff --git a/packages/SwingSet/src/vats/comms/dispatch.js b/packages/SwingSet/src/vats/comms/dispatch.js index 1153a0db3fa..26bdf67bb21 100644 --- a/packages/SwingSet/src/vats/comms/dispatch.js +++ b/packages/SwingSet/src/vats/comms/dispatch.js @@ -1,5 +1,6 @@ import { assert, details } from '@agoric/assert'; -import { makeVatSlot } from '../../parseVatSlots'; +import { QCLASS } from '@agoric/marshal'; +import { insistVatType, makeVatSlot } from '../../parseVatSlots'; import { getRemote } from './remote'; import { makeState, makeStateKit } from './state'; import { deliverToController } from './controller'; @@ -72,9 +73,9 @@ export function buildCommsDispatch(syscall) { throw Error(`unknown target ${target}`); } - function notifyFulfillToData(promiseID, data) { + function notify(promiseID, rejected, data) { insistCapData(data); - // console.debug(`comms.notifyFulfillToData(${promiseID})`); + // console.debug(`comms.notify(${promiseID}, ${rejected}, ${data})`); // dumpState(state); // I *think* we should never get here for local promises, since the @@ -89,32 +90,35 @@ export function buildCommsDispatch(syscall) { // hear about it. If we then get the authority back again, we no longer // want to hear about its resolution (since we're the ones doing the // resolving), but the kernel still thinks of us as subscribing, so we'll - // get a bogus dispatch.notifyFulfill*. Currently we throw an error, which + // get a bogus dispatch.notify. Currently we throw an error, which // is currently ignored but might prompt a vat shutdown in the future. - const resolution = harden({ type: 'data', data }); - resolveFromKernel(promiseID, resolution); - } - - function notifyFulfillToPresence(promiseID, slot) { - // console.debug(`comms.notifyFulfillToPresence(${promiseID}) = ${slot}`); - const resolution = harden({ type: 'object', slot }); - resolveFromKernel(promiseID, resolution); - } - - function notifyReject(promiseID, data) { - insistCapData(data); - // console.debug(`comms.notifyReject(${promiseID})`); - const resolution = harden({ type: 'reject', data }); + // TODO: The following goofiness, namely taking apart the capdata object and + // looking at it to see if it's a single presence reference and then + // treating it in a special way if it is, is a consequence of an impedance + // mismatch that has grown up between the comms protocol and the + // evolutionary path that the kernel/vat interface has taken. In the future + // we should clean this up and unify presences and data in the comms + // protocol the way the kernel/vat interface has. + const unser = JSON.parse(data.body); + let resolution; + if (rejected) { + resolution = harden({ type: 'reject', data }); + } else if ( + Object(unser) === unser && + QCLASS in unser && + unser[QCLASS] === 'slot' + ) { + const slot = data.slots[unser.index]; + insistVatType('object', slot); + resolution = harden({ type: 'object', slot }); + } else { + resolution = harden({ type: 'data', data }); + } resolveFromKernel(promiseID, resolution); } - const dispatch = harden({ - deliver, - notifyFulfillToData, - notifyFulfillToPresence, - notifyReject, - }); + const dispatch = harden({ deliver, notify }); debugState.set(dispatch, { state, clistKit }); return dispatch; diff --git a/packages/SwingSet/test/message-patterns.js b/packages/SwingSet/test/message-patterns.js index 8dfa0c23412..023a68b36e3 100644 --- a/packages/SwingSet/test/message-patterns.js +++ b/packages/SwingSet/test/message-patterns.js @@ -218,6 +218,42 @@ export function buildPatterns(log) { out.a42 = ['a42 done, [Alleged: presence o-53] match true']; test('a42'); + // bob!x() -> // rejection + { + objA.a43 = async () => { + try { + const ret = await E(b.bob).b43(); + log(`a43 unexpectedly resolved to ${ret}`); + } catch (e) { + log(`a43 rejected with ${e}`); + } + }; + objB.b43 = async () => { + throw Error('nope'); + }; + } + out.a43 = ['a43 rejected with Error: nope']; + test('a43'); + + // bob!x() -> [] // rejection in result + { + objA.a44 = async () => { + try { + const ret = await E(b.bob).b44(); + log(`a44 got ret`); + const ret2 = await ret[0]; + log(`a44 ret[0] unexpectedly resolved to ${ret2}`); + } catch (e) { + log(`a44 ret[0] rejected with ${e}`); + } + }; + objB.b44 = async () => { + return harden([Promise.reject(Error('nope'))]); + }; + } + out.a44 = ['a44 got ret', 'a44 ret[0] rejected with Error: nope']; + test('a44'); + // bob!x() -> P(data) { // bob returns a (wrapped) promise, then resolves it to data. We wrap it @@ -411,6 +447,29 @@ export function buildPatterns(log) { out.a64 = ['true', 'true', 'true', 'true']; test('a64'); + // bob!x() -> P(bill) // reject after receipt + { + objA.a65 = async () => { + const p2 = await E(b.bob).b65_1(); + E(b.bob).b65_2(); + try { + const bill = await p2.promise; + log(`a65 unexpectedly resolved to ${bill}`); + } catch (e) { + log(`a65 rejected with ${e}`); + } + }; + const p1 = makePromiseKit(); + objB.b65_1 = async () => { + return { promise: p1.promise }; + }; + objB.b65_2 = async () => { + p1.reject(Error('nope')); + }; + } + out.a65 = ['a65 rejected with Error: nope']; + test('a65'); + // bob!pipe1()!pipe2()!pipe3() // pipelining { objA.a70 = async () => { diff --git a/packages/SwingSet/test/test-kernel.js b/packages/SwingSet/test/test-kernel.js index 2bec7a73110..900950b2a02 100644 --- a/packages/SwingSet/test/test-kernel.js +++ b/packages/SwingSet/test/test-kernel.js @@ -724,10 +724,10 @@ test('promise resolveToData', async t => { function setupA(s) { syscallA = s; function deliver() {} - function notifyFulfillToData(promiseID, data) { - log.push(['notify', promiseID, data]); + function notify(promiseID, rejected, data) { + log.push(['notify', promiseID, rejected, data]); } - return { deliver, notifyFulfillToData }; + return { deliver, notify }; } await kernel.createTestVat('vatA', setupA); @@ -765,7 +765,7 @@ test('promise resolveToData', async t => { ]); syscallB.fulfillToData(pForB, capdata('args', [aliceForA])); - // this causes a notifyFulfillToData message to be queued + // this causes a notify message to be queued t.deepEqual(log, []); // no other dispatch calls t.deepEqual(kernel.dump().runQueue, [ { @@ -777,7 +777,7 @@ test('promise resolveToData', async t => { await kernel.step(); // the kernelPromiseID gets mapped back to the vat PromiseID - t.deepEqual(log.shift(), ['notify', pForA, capdata('args', ['o-50'])]); + t.deepEqual(log.shift(), ['notify', pForA, false, capdata('args', ['o-50'])]); t.deepEqual(log, []); // no other dispatch calls if (!RETIRE_KPIDS) { t.deepEqual(kernel.dump().promises, [ @@ -801,10 +801,10 @@ test('promise resolveToPresence', async t => { function setupA(s) { syscallA = s; function deliver() {} - function notifyFulfillToPresence(promiseID, slot) { - log.push(['notify', promiseID, slot]); + function notify(promiseID, rejected, data) { + log.push(['notify', promiseID, rejected, data]); } - return { deliver, notifyFulfillToPresence }; + return { deliver, notify }; } await kernel.createTestVat('vatA', setupA); @@ -858,7 +858,15 @@ test('promise resolveToPresence', async t => { ]); await kernel.step(); - t.deepEqual(log.shift(), ['notify', pForA, bobForA]); + t.deepEqual(log.shift(), [ + 'notify', + pForA, + false, + { + body: '{"@qclass":"slot","index":0}', + slots: [bobForA], + }, + ]); t.deepEqual(log, []); // no other dispatch calls if (!RETIRE_KPIDS) { t.deepEqual(kernel.dump().promises, [ @@ -882,10 +890,10 @@ test('promise reject', async t => { function setupA(s) { syscallA = s; function deliver() {} - function notifyReject(promiseID, rejectData) { - log.push(['notify', promiseID, rejectData]); + function notify(promiseID, rejected, rejectData) { + log.push(['notify', promiseID, rejected, rejectData]); } - return { deliver, notifyReject }; + return { deliver, notify }; } await kernel.createTestVat('vatA', setupA); @@ -923,7 +931,7 @@ test('promise reject', async t => { ]); syscallB.reject(pForB, capdata('args', [aliceForA])); - // this causes a notifyFulfillToData message to be queued + // this causes a notify message to be queued t.deepEqual(log, []); // no other dispatch calls t.deepEqual(kernel.dump().runQueue, [ { @@ -935,7 +943,7 @@ test('promise reject', async t => { await kernel.step(); // the kernelPromiseID gets mapped back to the vat PromiseID - t.deepEqual(log.shift(), ['notify', pForA, capdata('args', ['o-50'])]); + t.deepEqual(log.shift(), ['notify', pForA, true, capdata('args', ['o-50'])]); t.deepEqual(log, []); // no other dispatch calls if (!RETIRE_KPIDS) { t.deepEqual(kernel.dump().promises, [ diff --git a/packages/SwingSet/test/test-liveslots.js b/packages/SwingSet/test/test-liveslots.js index 1eee852a10d..90bc7761473 100644 --- a/packages/SwingSet/test/test-liveslots.js +++ b/packages/SwingSet/test/test-liveslots.js @@ -89,7 +89,7 @@ test('calls', async t => { t.deepEqual(log.shift(), { type: 'subscribe', target: 'p-1' }); t.deepEqual(log.shift(), 'two true'); - dispatch.notifyFulfillToData('p-1', capargs('result')); + dispatch.notify('p-1', false, capargs('result')); await waitUntilQuiescent(); t.deepEqual(log.shift(), ['res', 'result']); @@ -107,7 +107,7 @@ test('calls', async t => { t.deepEqual(log.shift(), { type: 'subscribe', target: 'p-2' }); t.deepEqual(log.shift(), 'two true'); - dispatch.notifyReject('p-2', capargs('rejection')); + dispatch.notify('p-2', true, capargs('rejection')); await waitUntilQuiescent(); t.deepEqual(log.shift(), ['rej', 'rejection']); @@ -218,8 +218,7 @@ test('liveslots pipeline/non-pipeline calls', async t => { t.deepEqual(log, []); // now we tell it the promise has resolved, to object 'o2' - // function notifyFulfillToPresence(promiseID, slot) { - dispatch.notifyFulfillToPresence(p1, o2); + dispatch.notify(p1, false, capargs(slot0arg, [o2])); await waitUntilQuiescent(); // this allows E(o2).nonpipe2() to go out, which was not pipelined t.deepEqual(log.shift(), { @@ -427,11 +426,11 @@ async function doResultPromise(t, mode) { // resolve p1 first. The one() call was already pipelined, so this // should not trigger any new syscalls. if (mode === 'to presence') { - dispatch.notifyFulfillToPresence(expectedP1, target2); + dispatch.notify(expectedP1, false, capargs(slot0arg, [target2])); } else if (mode === 'to data') { - dispatch.notifyFulfillToData(expectedP1, capargs(4, [])); + dispatch.notify(expectedP1, false, capargs(4, [])); } else if (mode === 'reject') { - dispatch.notifyReject(expectedP1, capargs('error', [])); + dispatch.notify(expectedP1, true, capargs('error', [])); } else { throw Error(`unknown mode ${mode}`); } @@ -439,7 +438,7 @@ async function doResultPromise(t, mode) { t.deepEqual(log, []); // Now we resolve p2, allowing the second two() to proceed - dispatch.notifyFulfillToData(expectedP2, capargs(4, [])); + dispatch.notify(expectedP2, false, capargs(4, [])); await waitUntilQuiescent(); if (mode === 'to presence') { diff --git a/packages/SwingSet/test/test-vpid-kernel.js b/packages/SwingSet/test/test-vpid-kernel.js index fac2c5b0640..1fb3f3f7aa4 100644 --- a/packages/SwingSet/test/test-vpid-kernel.js +++ b/packages/SwingSet/test/test-vpid-kernel.js @@ -130,38 +130,44 @@ function resolutionOf(vpid, mode, targets) { switch (mode) { case 'presence': return { - type: 'notifyFulfillToPresence', + type: 'notify', promiseID: vpid, - slot: targets.target2, + rejected: false, + data: capargs(slot0arg, [targets.target2]), }; case 'local-object': return { - type: 'notifyFulfillToPresence', + type: 'notify', promiseID: vpid, - slot: targets.localTarget, + rejected: false, + data: capargs(slot0arg, [targets.localTarget]), }; case 'data': return { - type: 'notifyFulfillToData', + type: 'notify', promiseID: vpid, + rejected: false, data: capargs(4, []), }; case 'promise-data': return { - type: 'notifyFulfillToData', + type: 'notify', promiseID: vpid, + rejected: false, data: capargs([slot0arg], [targets.p1]), }; case 'reject': return { - type: 'notifyReject', + type: 'notify', promiseID: vpid, + rejected: true, data: capargs('error', []), }; case 'promise-reject': return { - type: 'notifyReject', + type: 'notify', promiseID: vpid, + rejected: true, data: capargs([slot0arg], [targets.p1]), }; default: @@ -535,7 +541,7 @@ async function doTest4567(t, which, mode) { // Now bob resolves it. We want to examine the kernel's c-lists at the // moment the notification is delivered to Alice. We only expect one - // dispatch: Alice.notifyFulfillToPresence() + // dispatch: Alice.notify() const targetsA = { target2: rootAvatA, localTarget: localTargetA, diff --git a/packages/SwingSet/test/test-vpid-liveslots.js b/packages/SwingSet/test/test-vpid-liveslots.js index 182960ed88e..01a838010d2 100644 --- a/packages/SwingSet/test/test-vpid-liveslots.js +++ b/packages/SwingSet/test/test-vpid-liveslots.js @@ -291,7 +291,7 @@ for (const mode of modes) { // about this act of resolution. // In the current code, the kernel then echoes the resolution back into the -// vat (by delivering a `dispatch.notifyFulfillTo..` message in a subsequent +// vat (by delivering a `dispatch.notify` message in a subsequent // crank). In the upcoming retire-promiseid branch, the kernel does not, and // liveslots must notice that we're also subscribed to this promise, and // exercise the resolver/rejector that would normally be waiting for a @@ -480,7 +480,7 @@ async function doVatResolveCase23(t, which, mode, stalls) { // remote subscribers that p1 has been resolved. Since the vat is also a // subscriber, thenResolve's callback must also invoke p1's resolver (which // was stashed in importedPromisesByPromiseID), as if the kernel had call - // the vat's dispatch.notifyFulfillToPresence. This causes the p1.then + // the vat's dispatch.notify. This causes the p1.then // callback to be pushed to the back of the promise queue, which will set // resolutionOfP1 after all the syscalls have been made. @@ -664,17 +664,17 @@ async function doVatResolveCase4(t, mode) { t.deepEqual(log, []); if (mode === 'presence') { - dispatch.notifyFulfillToPresence(p1, target2); + dispatch.notify(p1, false, capargs(slot0arg, [target2])); } else if (mode === 'local-object') { - dispatch.notifyFulfillToPresence(p1, rootA); + dispatch.notify(p1, false, capargs(slot0arg, [rootA])); } else if (mode === 'data') { - dispatch.notifyFulfillToData(p1, capargs(4, [])); + dispatch.notify(p1, false, capargs(4, [])); } else if (mode === 'promise-data') { - dispatch.notifyFulfillToData(p1, capargs([slot0arg], [p1])); + dispatch.notify(p1, false, capargs([slot0arg], [p1])); } else if (mode === 'reject') { - dispatch.notifyReject(p1, capargs('error', [])); + dispatch.notify(p1, true, capargs('error', [])); } else if (mode === 'promise-reject') { - dispatch.notifyReject(p1, capargs([slot0arg], [p1])); + dispatch.notify(p1, true, capargs([slot0arg], [p1])); } else { throw Error(`unknown mode ${mode}`); } diff --git a/packages/SwingSet/test/util.js b/packages/SwingSet/test/util.js index 63f7c3cf15c..3053fa7479d 100644 --- a/packages/SwingSet/test/util.js +++ b/packages/SwingSet/test/util.js @@ -44,22 +44,8 @@ export function buildDispatch(onDispatchCallback = undefined) { onDispatchCallback(d); } }, - notifyFulfillToPresence(promiseID, slot) { - const d = { type: 'notifyFulfillToPresence', promiseID, slot }; - log.push(d); - if (onDispatchCallback) { - onDispatchCallback(d); - } - }, - notifyFulfillToData(promiseID, data) { - const d = { type: 'notifyFulfillToData', promiseID, data }; - log.push(d); - if (onDispatchCallback) { - onDispatchCallback(d); - } - }, - notifyReject(promiseID, data) { - const d = { type: 'notifyReject', promiseID, data }; + notify(promiseID, rejected, data) { + const d = { type: 'notify', promiseID, rejected, data }; log.push(d); if (onDispatchCallback) { onDispatchCallback(d); diff --git a/packages/SwingSet/test/workers/vat-target.js b/packages/SwingSet/test/workers/vat-target.js index dc28dc030d5..b23d9f0b7f6 100644 --- a/packages/SwingSet/test/workers/vat-target.js +++ b/packages/SwingSet/test/workers/vat-target.js @@ -45,10 +45,10 @@ export function buildRootObject(vatPowers) { return 1; } - // two: dispatch.notifyFulfillToPresence(pD, callbackObj) - // three: dispatch.notifyReject(pE, Error('four')) + // two: dispatch.notify(pD, false, callbackObj) + // three: dispatch.notify(pE, true, Error('four')) - // four: dispatch.notifyFulfillToData(pF, ['data', callbackObj]) + // four: dispatch.notify(pF, false, ['data', callbackObj]) const target = harden({ zero, diff --git a/packages/swingset-runner/src/slogulator.js b/packages/swingset-runner/src/slogulator.js index 16b0d5a0dbf..df7cc736dac 100644 --- a/packages/swingset-runner/src/slogulator.js +++ b/packages/swingset-runner/src/slogulator.js @@ -29,7 +29,10 @@ export function main() { .boolean('vatspace') .describe('vatspace', "Show object refs in their vat's namespace") .boolean('kernelspace') - .describe('kernelspace', 'Show object refs in the kernel namespace (default)') + .describe( + 'kernelspace', + 'Show object refs in the kernel namespace (default)', + ) .conflicts('vatspace', 'kernelspace') .boolean('crankbreaks') .default('crankbreaks', true) @@ -38,7 +41,10 @@ export function main() { .default('clist', true) .describe('clist', 'Show C-list change events') .boolean('usesloggertags') - .describe('usesloggertags', 'Display slogger events using full slogger tags') + .describe( + 'usesloggertags', + 'Display slogger events using full slogger tags', + ) .number('bigwidth') .default('bigwidth', 200) .describe('bigwidth', 'Width above which large values display as ')