diff --git a/packages/SwingSet/src/kernel/kernelSyscall.js b/packages/SwingSet/src/kernel/kernelSyscall.js index 846e6f167cc..a592cd83899 100644 --- a/packages/SwingSet/src/kernel/kernelSyscall.js +++ b/packages/SwingSet/src/kernel/kernelSyscall.js @@ -119,35 +119,38 @@ export function makeKernelSyscallHandler(tools) { return null; } - function resolve(vatID, kpid, rejected, data) { + function resolve(vatID, resolutions) { insistVatID(vatID); - insistKernelType('promise', kpid); - insistCapData(data); kernelKeeper.incStat('syscalls'); kernelKeeper.incStat('syscallResolve'); - if (rejected) { - resolveToError(kpid, data, vatID); - } else { - const p = kernelKeeper.getResolveablePromise(kpid, vatID); - const { subscribers, queue } = p; - let idx = 0; - for (const dataSlot of data.slots) { - kernelKeeper.incrementRefCount(dataSlot, `resolve|s${idx}`); - idx += 1; - } - const presence = extractPresenceIfPresent(data); - if (presence) { - kernelKeeper.fulfillKernelPromiseToPresence(kpid, presence); - } else if (rejected) { - kernelKeeper.rejectKernelPromise(kpid, data); + for (const resolution of resolutions) { + const [kpid, rejected, data] = resolution; + insistKernelType('promise', kpid); + insistCapData(data); + if (rejected) { + resolveToError(kpid, data, vatID); } else { - kernelKeeper.fulfillKernelPromiseToData(kpid, data); - } - notifySubscribersAndQueue(kpid, vatID, subscribers, queue); - if (p.policy === 'logAlways') { - console.log( - `${kpid}.policy logAlways: resolve ${JSON.stringify(data)}`, - ); + const p = kernelKeeper.getResolveablePromise(kpid, vatID); + const { subscribers, queue } = p; + let idx = 0; + for (const dataSlot of data.slots) { + kernelKeeper.incrementRefCount(dataSlot, `resolve|s${idx}`); + idx += 1; + } + const presence = extractPresenceIfPresent(data); + if (presence) { + kernelKeeper.fulfillKernelPromiseToPresence(kpid, presence); + } else if (rejected) { + kernelKeeper.rejectKernelPromise(kpid, data); + } else { + kernelKeeper.fulfillKernelPromiseToData(kpid, data); + } + notifySubscribersAndQueue(kpid, vatID, subscribers, queue); + if (p.policy === 'logAlways') { + console.log( + `${kpid}.policy logAlways: resolve ${JSON.stringify(data)}`, + ); + } } } return OKNULL; diff --git a/packages/SwingSet/src/kernel/liveSlots.js b/packages/SwingSet/src/kernel/liveSlots.js index 0a54de699d5..48a3dca7ba0 100644 --- a/packages/SwingSet/src/kernel/liveSlots.js +++ b/packages/SwingSet/src/kernel/liveSlots.js @@ -476,7 +476,7 @@ function build(syscall, forVatID, cacheSize, vatPowers, vatParameters) { harden(res); lsdebug(`ls.thenResolve fired`, res); const ser = m.serialize(res); - syscall.resolve(promiseID, false, ser); + syscall.resolve([[promiseID, false, ser]]); const pRec = importedPromisesByPromiseID.get(promiseID); if (pRec) { pRec.resolve(res); @@ -490,7 +490,7 @@ function build(syscall, forVatID, cacheSize, vatPowers, vatParameters) { harden(rej); lsdebug(`ls thenReject fired`, rej); const ser = m.serialize(rej); - syscall.resolve(promiseID, true, ser); + syscall.resolve([[promiseID, true, ser]]); const pRec = importedPromisesByPromiseID.get(promiseID); if (pRec) { pRec.reject(rej); diff --git a/packages/SwingSet/src/kernel/vatTranslator.js b/packages/SwingSet/src/kernel/vatTranslator.js index e4d3d161907..eed3d2defda 100644 --- a/packages/SwingSet/src/kernel/vatTranslator.js +++ b/packages/SwingSet/src/kernel/vatTranslator.js @@ -221,26 +221,37 @@ function makeTranslateVatSyscallToKernelSyscall(vatID, kernelKeeper) { return ks; } - function translateResolve(vpid, rejected, data) { - insistVatType('promise', vpid); - insistCapData(data); - const kpid = mapVatSlotToKernelSlot(vpid); - const kernelSlots = data.slots.map(slot => mapVatSlotToKernelSlot(slot)); - const kernelData = harden({ ...data, slots: kernelSlots }); - kdebug( - `syscall[${vatID}].resolve(${vpid}/${kpid}, ${rejected}) = ${ - data.body - } ${JSON.stringify(data.slots)}/${JSON.stringify(kernelSlots)}`, - ); - deleteCListEntryIfEasy( - vatID, - vatKeeper, - kernelKeeper, - kpid, - vpid, - kernelData, - ); - return harden(['resolve', vatID, kpid, rejected, kernelData]); + function translateResolve(vresolutions) { + const kresolutions = []; + let idx = 0; + for (const resolution of vresolutions) { + const [vpid, rejected, data] = resolution; + insistVatType('promise', vpid); + insistCapData(data); + const kpid = mapVatSlotToKernelSlot(vpid); + const kernelSlots = data.slots.map(slot => mapVatSlotToKernelSlot(slot)); + const kernelData = harden({ ...data, slots: kernelSlots }); + kdebug( + `syscall[${vatID}].resolve[${idx}](${vpid}/${kpid}, ${rejected}) = ${ + data.body + } ${JSON.stringify(data.slots)}/${JSON.stringify(kernelSlots)}`, + ); + idx += 1; + kresolutions.push([kpid, rejected, kernelData]); + deleteCListEntryIfEasy( + vatID, + vatKeeper, + kernelKeeper, + kpid, + vpid, + kernelData, + ); + } + // XXX TODO Once we get rid of the "if easy" logic, the above deletions + // should be collected and then processed in a batch here after all the + // translation is done, e.g., something like: + // vatKeeper.deleteCListEntriesForKernelSlots(targets); + return harden(['resolve', vatID, kresolutions]); } // vsc is [type, ...args] diff --git a/packages/SwingSet/src/vats/comms/clist-kernel.js b/packages/SwingSet/src/vats/comms/clist-kernel.js index 19e431bfc09..4859b33d780 100644 --- a/packages/SwingSet/src/vats/comms/clist-kernel.js +++ b/packages/SwingSet/src/vats/comms/clist-kernel.js @@ -62,7 +62,7 @@ export function makeKernel(state, syscall, stateKit) { // console.log(`fresh: ${fresh} for ${vpid}`, p.resolution); // we must tell the kernel about the resolution *after* the message // which introduces it - Promise.resolve().then(() => resolveToKernel(fresh, p.resolution)); + Promise.resolve().then(() => resolveToKernel([[fresh, p.resolution]])); return fresh; } diff --git a/packages/SwingSet/src/vats/comms/clist-outbound.js b/packages/SwingSet/src/vats/comms/clist-outbound.js index 88ac7c6612f..92a78f1ce34 100644 --- a/packages/SwingSet/src/vats/comms/clist-outbound.js +++ b/packages/SwingSet/src/vats/comms/clist-outbound.js @@ -78,7 +78,7 @@ export function makeOutbound(state, stateKit) { // we must send the resolution *after* the message which introduces it const { remoteID } = remote; Promise.resolve().then(() => - resolveToRemote(remoteID, vpid, p.resolution), + resolveToRemote(remoteID, [[vpid, p.resolution]]), ); } else { // or arrange to send it later, once it resolves diff --git a/packages/SwingSet/src/vats/comms/controller.js b/packages/SwingSet/src/vats/comms/controller.js index 7aa5a0b51e5..04604be69b3 100644 --- a/packages/SwingSet/src/vats/comms/controller.js +++ b/packages/SwingSet/src/vats/comms/controller.js @@ -60,7 +60,7 @@ export function deliverToController( // todo: consider, this leaves one message (setReceiver) on the queue, // rather than giving the caller of comms!addRemote() something to // synchronize upon. I don't think it hurts, but might affect debugging. - syscall.resolve(result, false, UNDEFINED); + syscall.resolve([[result, false, UNDEFINED]]); } function doAddEgress() { @@ -80,7 +80,7 @@ export function deliverToController( } const localRef = slots[args[2].index]; addEgress(remoteID, remoteRefID, localRef); - syscall.resolve(result, false, UNDEFINED); + syscall.resolve([[result, false, UNDEFINED]]); } function doAddIngress() { @@ -99,7 +99,7 @@ export function deliverToController( body: '{"@qclass":"slot","index":0}', slots: [localRef], }; - syscall.resolve(result, false, data); + syscall.resolve([[result, false, data]]); } switch (method) { diff --git a/packages/SwingSet/src/vats/comms/delivery.js b/packages/SwingSet/src/vats/comms/delivery.js index 0b5aa3d333d..668ea244775 100644 --- a/packages/SwingSet/src/vats/comms/delivery.js +++ b/packages/SwingSet/src/vats/comms/delivery.js @@ -66,22 +66,45 @@ export function makeDeliveryKit(state, syscall, transmit, clistKit, stateKit) { }); } - // dispatch.notifyResolve* from kernel lands here (local vat resolving some + // dispatch.notify from kernel lands here (local vat resolving some // Promise, we need to notify remote machines): translate to local, join - // with handleResolution - function resolveFromKernel(vpid, resolution, doNotSubscribeSet) { - insistPromiseIsUnresolved(vpid); - insistDeciderIsKernel(vpid); - changeDeciderFromKernelToComms(vpid); - handleResolution( - vpid, - mapResolutionFromKernel(resolution, doNotSubscribeSet), - ); + // with handleResolutions + function resolveFromKernel(resolutions, doNotSubscribeSet) { + const localResolutions = []; + for (const resolution of resolutions) { + const [vpid, value] = resolution; + + // I *think* we should never get here for local promises, since the + // controller only does sendOnly. But if we change that, we need to catch + // locally-generated promises and deal with them. + // if (promiseID in localPromises) { + // resolveLocal(promiseID, { rejected: false, data }); + // } + + // todo: if we previously held resolution authority for this promise, then + // transferred it to some local vat, we'll have subscribed to the kernel + // to 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.notify. Currently we throw an error, which is + // currently ignored but might prompt a vat shutdown in the future. + + insistPromiseIsUnresolved(vpid); + insistDeciderIsKernel(vpid); + changeDeciderFromKernelToComms(vpid); + localResolutions.push([ + vpid, + mapResolutionFromKernel(value, doNotSubscribeSet), + ]); + } + handleResolutions(localResolutions); + // XXX question: do we need to call retirePromiseIDIfEasy (or some special + // comms vat version of it) here? } // dispatch.deliver with msg from vattp lands here, containing a message // from some remote machine. figure out whether it's a deliver or a - // resolve, parse, merge with handleSend/handleResolution + // resolve, parse, merge with handleSend/handleResolutions function messageFromRemote(remoteID, message) { const command = message.split(':', 1)[0]; if (command === 'deliver') { @@ -118,30 +141,37 @@ export function makeDeliveryKit(state, syscall, transmit, clistKit, stateKit) { } function resolveFromRemote(remoteID, message) { - const sci = message.indexOf(';'); - assert(sci !== -1, details`missing semicolon in resolve ${message}`); - // message is created by resolveToRemote, taking the form: - // `resolve:${rejected}:${target}${rmss};${resolution.body}` - - const pieces = message.slice(0, sci).split(':'); - // pieces[0] is 'resolve' - const rejected = pieces[1] === 'reject'; - assert(rejected || pieces[1] === 'fulfill'); - const remoteTarget = pieces[2]; - const remoteSlots = pieces.slice(3); // length=1 for resolve:object - insistRemoteType('promise', remoteTarget); // slots[0] is 'rp+NN`. - const vpid = getLocalForRemote(remoteID, remoteTarget); - // rp+NN maps to target=p-+NN and we look at the promiseTable to make - // sure it's in the right state. - insistPromiseIsUnresolved(vpid); - insistDeciderIsRemote(vpid, remoteID); - - const slots = remoteSlots.map(s => provideLocalForRemote(remoteID, s)); - const body = message.slice(sci + 1); - const data = harden({ body, slots }); - changeDeciderFromRemoteToComms(vpid, remoteID); - - handleResolution(vpid, harden({ rejected, data })); + // message is created by resolveToRemote. It consists of 1 or more + // resolutions, separated by newlines, each taking the form of either: + // `resolve:fulfill:${target}${rmss};${resolution.body}` + // or + // `resolve:reject:${target}${rmss};${resolution.body}` + const subMessages = message.split('\n'); + const resolutions = []; + for (const submsg of subMessages) { + const sci = submsg.indexOf(';'); + assert(sci !== -1, details`missing semicolon in resolve ${submsg}`); + + const pieces = submsg.slice(0, sci).split(':'); + assert(pieces[0] === 'resolve'); + const rejected = pieces[1] === 'reject'; + assert(rejected || pieces[1] === 'fulfill'); + const remoteTarget = pieces[2]; + const remoteSlots = pieces.slice(3); + insistRemoteType('promise', remoteTarget); // slots[0] is 'rp+NN`. + const vpid = getLocalForRemote(remoteID, remoteTarget); + // rp+NN maps to target=p-+NN and we look at the promiseTable to make + // sure it's in the right state. + insistPromiseIsUnresolved(vpid); + insistDeciderIsRemote(vpid, remoteID); + + const slots = remoteSlots.map(s => provideLocalForRemote(remoteID, s)); + const body = submsg.slice(sci + 1); + const data = harden({ body, slots }); + changeDeciderFromRemoteToComms(vpid, remoteID); + resolutions.push([vpid, { rejected, data }]); + } + handleResolutions(harden(resolutions)); } function extractPresenceIfPresent(data) { @@ -229,8 +259,10 @@ export function makeDeliveryKit(state, syscall, transmit, clistKit, stateKit) { if (!localDelivery.result) { return; // sendOnly, nowhere to send the rejection } - const resolution = harden({ rejected: true, data: where.reject }); - handleResolution(localDelivery.result, resolution); + const resolutions = harden([ + [localDelivery.result, { rejected: true, data: where.reject }], + ]); + handleResolutions(resolutions); return; } @@ -275,64 +307,73 @@ export function makeDeliveryKit(state, syscall, transmit, clistKit, stateKit) { transmit(remoteID, msg); } - function handleResolution(vpid, resolution) { - // resolution: { rejected: boolean, data: capdata } - insistCapData(resolution.data); - insistVatType('promise', vpid); - insistPromiseIsUnresolved(vpid); - insistDeciderIsComms(vpid); - - const { subscribers, kernelIsSubscribed } = getPromiseSubscribers(vpid); - - // mark it as resolved in the promise table, so later messages to it will - // be handled properly - markPromiseAsResolved(vpid, resolution); + function handleResolutions(resolutions) { + const [[primaryVpid]] = resolutions; + const { subscribers, kernelIsSubscribed } = getPromiseSubscribers( + primaryVpid, + ); + for (const resolution of resolutions) { + const [vpid, value] = resolution; + // value: { rejected: boolean, data: capdata } + insistCapData(value.data); + insistVatType('promise', vpid); + insistPromiseIsUnresolved(vpid); + insistDeciderIsComms(vpid); + + // mark it as resolved in the promise table, so later messages to it will + // be handled properly + markPromiseAsResolved(vpid, value); + } // what remotes need to know? for (const remoteID of subscribers) { insistRemoteID(remoteID); - resolveToRemote(remoteID, vpid, resolution); + resolveToRemote(remoteID, resolutions); // TODO: what happens when we tell them about the promise again someday? // do we need to remember who we've notified, and never notify them // again? } if (kernelIsSubscribed) { - resolveToKernel(vpid, resolution); + resolveToKernel(resolutions); // the kernel now forgets this vpid: the p.resolved flag in // promiseTable reminds provideKernelForLocal to use a fresh VPID if we // ever reference it again in the future } } - function resolveToRemote(remoteID, vpid, resolution) { - const rpid = getRemoteForLocal(remoteID, vpid); - // rpid should be rp+NN - insistRemoteType('promise', rpid); - // assert(parseRemoteSlot(rpid).allocatedByRecipient, rpid); // rp+NN for them - function mapSlots() { - const { slots } = resolution.data; - let ss = slots.map(s => provideRemoteForLocal(remoteID, s)).join(':'); - if (ss) { - ss = `:${ss}`; + function resolveToRemote(remoteID, resolutions) { + const msgs = []; + for (const resolution of resolutions) { + const [vpid, value] = resolution; + + const rpid = getRemoteForLocal(remoteID, vpid); + // rpid should be rp+NN + insistRemoteType('promise', rpid); + // assert(parseRemoteSlot(rpid).allocatedByRecipient, rpid); // rp+NN for them + function mapSlots() { + const { slots } = value.data; + let ss = slots.map(s => provideRemoteForLocal(remoteID, s)).join(':'); + if (ss) { + ss = `:${ss}`; + } + return ss; } - return ss; - } - - const rejected = resolution.rejected ? 'reject' : 'fulfill'; - // prettier-ignore - const msg = - `resolve:${rejected}:${rpid}${mapSlots()};${resolution.data.body}`; - transmit(remoteID, msg); + const rejected = value.rejected ? 'reject' : 'fulfill'; + // prettier-ignore + msgs.push(`resolve:${rejected}:${rpid}${mapSlots()};${value.data.body}`); + } + transmit(remoteID, msgs.join('\n')); } - function resolveToKernel(vpid, resolution) { - syscall.resolve( - vpid, - resolution.rejected, - mapDataToKernel(resolution.data), - ); + function resolveToKernel(localResolutions) { + const resolutions = []; + for (const localResolution of localResolutions) { + const [vpid, value] = localResolution; + resolutions.push([vpid, value.rejected, mapDataToKernel(value.data)]); + } + syscall.resolve(resolutions); } return harden({ diff --git a/packages/SwingSet/src/vats/comms/dispatch.js b/packages/SwingSet/src/vats/comms/dispatch.js index 6b6d58b7c30..1058e3531ba 100644 --- a/packages/SwingSet/src/vats/comms/dispatch.js +++ b/packages/SwingSet/src/vats/comms/dispatch.js @@ -72,47 +72,20 @@ export function buildCommsDispatch(syscall) { throw Error(`unknown target ${target}`); } - function notify(notifications) { + function notify(resolutions) { const willBeResolved = new Set(); - for (const notification of notifications) { - const [vpid] = notification; + for (const resolution of resolutions) { + const [vpid, value] = resolution; willBeResolved.add(vpid); - } - - for (const notification of notifications) { - const [vpid, resolution] = notification; - - assert(typeof resolution === 'object'); - insistCapData(resolution.data); - // console.debug(`comms.notify(${promiseID}, ${resoluion})`); + assert(typeof value === 'object'); + insistCapData(value.data); + // console.debug(`comms.notify(${vpid}, ${value})`); // dumpState(state); - - // I *think* we should never get here for local promises, since the - // controller only does sendOnly. But if we change that, we need to catch - // locally-generated promises and deal with them. - // if (promiseID in localPromises) { - // resolveLocal(promiseID, { rejected: false, data }); - // } - - // todo: if we previously held resolution authority for this promise, then - // transferred it to some local vat, we'll have subscribed to the kernel - // to 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.notify. Currently we throw an error, which is - // currently ignored but might prompt a vat shutdown in the future. - - // 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. - resolveFromKernel(vpid, resolution, willBeResolved); - // XXX question: do we need to call retirePromiseIDIfEasy (or some special - // comms vat version of it) here? } + resolveFromKernel(resolutions, willBeResolved); + + // XXX question: do we need to call retirePromiseIDIfEasy (or some special + // comms vat version of it) here? } const dispatch = harden({ deliver, notify }); diff --git a/packages/SwingSet/test/test-kernel.js b/packages/SwingSet/test/test-kernel.js index 9f637bc0b00..67948dd643b 100644 --- a/packages/SwingSet/test/test-kernel.js +++ b/packages/SwingSet/test/test-kernel.js @@ -774,7 +774,7 @@ test('promise resolveToData', async t => { }, ]); - syscallB.resolve(pForB, false, capdata('"args"', [aliceForA])); + syscallB.resolve([[pForB, false, capdata('"args"', [aliceForA])]]); // this causes a notify message to be queued t.deepEqual(log, []); // no other dispatch calls t.deepEqual(kernel.dump().runQueue, [ @@ -860,7 +860,7 @@ test('promise resolveToPresence', async t => { }, ]); - syscallB.resolve(pForB, false, capargs(slot0arg, [bobForB])); + syscallB.resolve([[pForB, false, capargs(slot0arg, [bobForB])]]); t.deepEqual(log, []); // no other dispatch calls t.deepEqual(kernel.dump().runQueue, [ { @@ -941,7 +941,7 @@ test('promise reject', async t => { }, ]); - syscallB.resolve(pForB, true, capdata('args', [aliceForA])); + syscallB.resolve([[pForB, true, capdata('args', [aliceForA])]]); // this causes a notify message to be queued t.deepEqual(log, []); // no other dispatch calls t.deepEqual(kernel.dump().runQueue, [ diff --git a/packages/SwingSet/test/test-liveslots.js b/packages/SwingSet/test/test-liveslots.js index 45c99394505..4f0d4b4113b 100644 --- a/packages/SwingSet/test/test-liveslots.js +++ b/packages/SwingSet/test/test-liveslots.js @@ -28,8 +28,8 @@ function buildSyscall() { subscribe(target) { log.push({ type: 'subscribe', target }); }, - resolve(promiseID, rejected, data) { - log.push({ type: 'resolve', promiseID, rejected, data }); + resolve(resolutions) { + log.push({ type: 'resolve', resolutions }); }, }; @@ -285,8 +285,7 @@ async function doOutboundPromise(t, mode) { let resolution; const resolveSyscall = { type: 'resolve', - promiseID: expectedP1, - rejected: false, + resolutions: [[expectedP1, false]], }; if (mode === 'to presence') { // n.b.: because the `body` object gets stringified and THEN compared to the @@ -298,14 +297,14 @@ async function doOutboundPromise(t, mode) { index: 0, }; resolution = slot0arg; - resolveSyscall.data = capargs(body, [target]); + resolveSyscall.resolutions[0][2] = capargs(body, [target]); } else if (mode === 'to data') { resolution = 4; - resolveSyscall.data = capargs(4, []); + resolveSyscall.resolutions[0][2] = capargs(4, []); } else if (mode === 'reject') { resolution = 'reject'; - resolveSyscall.rejected = true; - resolveSyscall.data = capargs('reject', []); + resolveSyscall.resolutions[0][1] = true; + resolveSyscall.resolutions[0][2] = capargs('reject', []); } else { throw Error(`unknown mode ${mode}`); } @@ -341,7 +340,7 @@ async function doOutboundPromise(t, mode) { // and again it subscribes to the result promise t.deepEqual(log.shift(), { type: 'subscribe', target: expectedResultP2 }); - resolveSyscall.promiseID = expectedP2; + resolveSyscall.resolutions[0][0] = expectedP2; t.deepEqual(log.shift(), resolveSyscall); t.deepEqual(log, []); diff --git a/packages/SwingSet/test/test-marshal.js b/packages/SwingSet/test/test-marshal.js index 5efb97829ce..66b96873a29 100644 --- a/packages/SwingSet/test/test-marshal.js +++ b/packages/SwingSet/test/test-marshal.js @@ -95,8 +95,8 @@ test('serialize imports', async t => { test('serialize promise', async t => { const log = []; const syscall = { - resolve(result, rejected, data) { - log.push({ result, data }); + resolve(resolutions) { + log.push(resolutions); }, }; @@ -124,7 +124,7 @@ test('serialize promise', async t => { const { promise: pauseP, resolve: pauseRes } = makePromiseKit(); setImmediate(() => pauseRes()); await pauseP; - t.deepEqual(log, [{ result: 'p+5', data: { body: '5', slots: [] } }]); + t.deepEqual(log, [[['p+5', false, { body: '5', slots: [] }]]]); }); test('unserialize promise', async t => { diff --git a/packages/SwingSet/test/test-vpid-kernel.js b/packages/SwingSet/test/test-vpid-kernel.js index f7861bbb440..38dd6c96704 100644 --- a/packages/SwingSet/test/test-vpid-kernel.js +++ b/packages/SwingSet/test/test-vpid-kernel.js @@ -108,22 +108,24 @@ const slot0arg = { '@qclass': 'slot', index: 0 }; function doResolveSyscall(syscallA, vpid, mode, targets) { switch (mode) { case 'presence': - syscallA.resolve(vpid, false, capargs(slot0arg, [targets.target2])); + syscallA.resolve([[vpid, false, capargs(slot0arg, [targets.target2])]]); break; case 'local-object': - syscallA.resolve(vpid, false, capargs(slot0arg, [targets.localTarget])); + syscallA.resolve([ + [vpid, false, capargs(slot0arg, [targets.localTarget])], + ]); break; case 'data': - syscallA.resolve(vpid, false, capargs(4, [])); + syscallA.resolve([[vpid, false, capargs(4, [])]]); break; case 'promise-data': - syscallA.resolve(vpid, false, capargs([slot0arg], [targets.p1])); + syscallA.resolve([[vpid, false, capargs([slot0arg], [targets.p1])]]); break; case 'reject': - syscallA.resolve(vpid, true, capargs('error', [])); + syscallA.resolve([[vpid, true, capargs('error', [])]]); break; case 'promise-reject': - syscallA.resolve(vpid, true, capargs([slot0arg], [targets.p1])); + syscallA.resolve([[vpid, true, capargs([slot0arg], [targets.p1])]]); break; default: throw Error(`unknown mode ${mode}`); @@ -743,11 +745,13 @@ test(`kernel vpid handling crossing resolutions`, async t => { // **** begin Crank 4 (A) **** // usePromise(b) delivered to A syscallA.subscribe(importedGenResultBvatA); - syscallA.resolve( - importedGenResultAvatA, - false, - capargs([slot0arg], [importedGenResultBvatA]), - ); + syscallA.resolve([ + [ + importedGenResultAvatA, + false, + capargs([slot0arg], [importedGenResultBvatA]), + ], + ]); await kernel.run(); t.deepEqual(logX.shift(), { type: 'notify', @@ -777,11 +781,13 @@ test(`kernel vpid handling crossing resolutions`, async t => { // **** begin Crank 5 (B) **** // usePromise(a) delivered to B syscallB.subscribe(importedGenResultAvatB); - syscallB.resolve( - importedGenResultBvatB, - false, - capargs([slot0arg], [importedGenResultAvatB]), - ); + syscallB.resolve([ + [ + importedGenResultBvatB, + false, + capargs([slot0arg], [importedGenResultAvatB]), + ], + ]); await kernel.run(); t.deepEqual(logB.shift(), { diff --git a/packages/SwingSet/test/test-vpid-liveslots.js b/packages/SwingSet/test/test-vpid-liveslots.js index 071f976f97a..b53bbe03ff0 100644 --- a/packages/SwingSet/test/test-vpid-liveslots.js +++ b/packages/SwingSet/test/test-vpid-liveslots.js @@ -3,7 +3,6 @@ import '@agoric/install-ses'; import test from 'ava'; -import { Far } from '@agoric/marshal'; import { E } from '@agoric/eventual-send'; import { makePromiseKit } from '@agoric/promise-kit'; @@ -34,8 +33,8 @@ function buildSyscall() { subscribe(target) { log.push({ type: 'subscribe', target }); }, - resolve(promiseID, rejected, data) { - log.push({ type: 'resolve', promiseID, rejected, data }); + resolve(resolutions) { + log.push({ type: 'resolve', resolutions }); }, }; @@ -115,16 +114,14 @@ function resolvePR(pr, mode, targets) { pr.resolve(targets.target2); break; case 'local-object': - pr.resolve( - Far('vpid resolve test', { - two() { - /* console.log(`local two() called`); */ - }, - four() { - /* console.log(`local four() called`); */ - }, - }), - ); + pr.resolve({ + two() { + /* console.log(`local two() called`); */ + }, + four() { + /* console.log(`local four() called`); */ + }, + }); break; case 'data': pr.resolve(4); @@ -149,8 +146,7 @@ const slot1arg = { '@qclass': 'slot', index: 1 }; function resolutionOf(vpid, mode, targets) { const resolution = { type: 'resolve', - promiseID: vpid, - rejected: false, + resolutions: [[vpid, false]], }; switch (mode) { case 'presence': { @@ -159,25 +155,25 @@ function resolutionOf(vpid, mode, targets) { iface: `Alleged: presence ${targets.target2}`, index: 0, }; - resolution.data = capargs(presenceBody, [targets.target2]); + resolution.resolutions[0][2] = capargs(presenceBody, [targets.target2]); break; } case 'local-object': - resolution.data = capargs(slot0arg, [targets.localTarget]); + resolution.resolutions[0][2] = capargs(slot0arg, [targets.localTarget]); break; case 'data': - resolution.data = capargs(4, []); + resolution.resolutions[0][2] = capargs(4, []); break; case 'promise-data': - resolution.data = capargs([slot0arg], [targets.p1]); + resolution.resolutions[0][2] = capargs([slot0arg], [targets.p1]); break; case 'reject': - resolution.rejected = true; - resolution.data = capargs('error', []); + resolution.resolutions[0][1] = true; + resolution.resolutions[0][2] = capargs('error', []); break; case 'promise-reject': - resolution.rejected = true; - resolution.data = capargs([slot0arg], [targets.p1]); + resolution.resolutions[0][1] = true; + resolution.resolutions[0][2] = capargs([slot0arg], [targets.p1]); break; default: throw Error(`unknown mode ${mode}`); @@ -589,7 +585,7 @@ async function doVatResolveCase4(t, mode) { function build(_vatPowers) { let p1; - return Far('test vpid', { + return harden({ async get(p) { p1 = p; // if we don't add this, node will complain when the kernel notifies @@ -768,9 +764,7 @@ test('inter-vat circular promise references', async t => { t.deepEqual(log.shift(), { type: 'subscribe', target: pbA }); t.deepEqual(log.shift(), { type: 'resolve', - promiseID: paA, - rejected: false, - data: capargs([slot0arg], [pbA]), + resolutions: [[paA, false, capargs([slot0arg], [pbA])]], }); t.deepEqual(log, []); @@ -779,9 +773,7 @@ test('inter-vat circular promise references', async t => { // t.deepEqual(log.shift(), { type: 'subscribe', target: paB }); // t.deepEqual(log.shift(), { // type: 'resolve', - // promiseID: pbB, - // rejected: false, - // data: capargs([slot0arg], [paB]), + // resoutions: [[pbB, false, capargs([slot0arg], [paB])]], // }); // t.deepEqual(log, []); }); diff --git a/packages/swingset-runner/src/slogulator.js b/packages/swingset-runner/src/slogulator.js index a1ab8073b0d..83c78db47d8 100644 --- a/packages/swingset-runner/src/slogulator.js +++ b/packages/swingset-runner/src/slogulator.js @@ -354,11 +354,14 @@ export function main() { } function doSyscallResolve(tag, entry) { - const target = kernelSpace ? entry[2] : entry[1]; - const rejected = kernelSpace ? entry[3] : entry[2]; - const rejTag = rejected ? 'reject' : 'fulfill'; - const value = kernelSpace ? entry[4] : entry[3]; - p(`${tag} ${rejTag}: ${pref(target)} = ${pdata(value)}`); + let idx = 0; + const resolutions = kernelSpace ? entry[2] : entry[1]; + for (const resolution of resolutions) { + const [target, rejected, value] = resolution; + const rejTag = rejected ? 'reject' : 'fulfill'; + p(`${tag} ${idx} ${rejTag}: ${pref(target)} = ${pdata(value)}`); + idx += 1; + } } function doSyscallInvoke(tag, entry) {