From dd29ff35c5dc72efbbf7087849182aa7f04b2bb1 Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Wed, 8 Mar 2023 21:11:52 -0500 Subject: [PATCH] fix(swingset-liveslots): Move promise rejection responsibility into the kernel Fixes #6694 --- packages/SwingSet/src/kernel/kernelQueue.js | 4 +- packages/swingset-liveslots/src/liveslots.js | 24 +------ .../swingset-liveslots/src/watchedPromises.js | 65 ++++--------------- .../tools/fakeVirtualSupport.js | 4 ++ 4 files changed, 21 insertions(+), 76 deletions(-) diff --git a/packages/SwingSet/src/kernel/kernelQueue.js b/packages/SwingSet/src/kernel/kernelQueue.js index 7f3fc29b808..3a8672b74d8 100644 --- a/packages/SwingSet/src/kernel/kernelQueue.js +++ b/packages/SwingSet/src/kernel/kernelQueue.js @@ -45,9 +45,7 @@ export function makeKernelQueueHandler(tools) { const p = kernelKeeper.getResolveablePromise(kpid, vatID); const { subscribers } = p; for (const subscriber of subscribers) { - if (subscriber !== vatID) { - notify(subscriber, kpid); - } + notify(subscriber, kpid); } kernelKeeper.resolveKernelPromise(kpid, rejected, data); const tag = rejected ? 'rejected' : 'fulfilled'; diff --git a/packages/swingset-liveslots/src/liveslots.js b/packages/swingset-liveslots/src/liveslots.js index e903a3f228a..82cf235a2dd 100644 --- a/packages/swingset-liveslots/src/liveslots.js +++ b/packages/swingset-liveslots/src/liveslots.js @@ -552,7 +552,7 @@ function build( const knownResolutions = new WeakMap(); /** - * Determines if a vref from an outbound argument + * Determines if a vref from a watched promise or outbound argument * identifies a promise that should be exported, and if so then * adds it to exportedVPIDs and sets up handlers. * @@ -674,6 +674,7 @@ function build( // eslint-disable-next-line no-use-before-define convertValToSlot, convertSlotToVal: unmeteredConvertSlotToVal, + maybeExportPromise, }); function convertValToSlot(val) { @@ -1190,7 +1191,6 @@ function build( // upgrade, or if we acquire decider authority for a // previously-imported promise if (pRec) { - // TODO: insist that we do not have decider authority for promiseID meterControl.assertNotMetered(); const val = m.unserialize(data); if (rejected) { @@ -1431,10 +1431,7 @@ function build( Fail`buildRootObject() for vat ${forVatID} returned ${rootObject} with no interface`; // Need to load watched promises *after* buildRootObject() so that handler kindIDs // have a chance to be reassociated with their handlers. - watchedPromiseManager.loadWatchedPromiseTable( - unmeteredUnserialize, - unmeteredRevivePromise, - ); + watchedPromiseManager.loadWatchedPromiseTable(unmeteredRevivePromise); const rootSlot = makeVatSlot('object', true, BigInt(0)); valToSlot.set(rootObject, rootSlot); @@ -1526,26 +1523,11 @@ function build( assert(!didStopVat); didStopVat = true; - // all vpids are either "imported" (kernel knows about it and - // kernel decides), "exported" (kernel knows about it but we - // decide), or neither (local, we decide, kernel is unaware). TODO - // this could be cheaper if we tracked all three states (make a - // Set for "neither") instead of doing enumeration and set math. - try { - // mark "imported" plus "neither" for rejection at next startup - const importedVPIDsSet = new Set(importedVPIDs.keys()); - watchedPromiseManager.prepareShutdownRejections( - importedVPIDsSet, - disconnectObjectCapData, - ); - // reject all "exported" vpids now - const deciderVPIDs = Array.from(exportedVPIDs.keys()).sort(); // eslint-disable-next-line @jessie.js/no-nested-await await releaseOldState({ m, disconnectObjectCapData, - deciderVPIDs, syscall, exportedRemotables, addToPossiblyDeadSet, diff --git a/packages/swingset-liveslots/src/watchedPromises.js b/packages/swingset-liveslots/src/watchedPromises.js index 2edc26ac540..2f3ffc1851a 100644 --- a/packages/swingset-liveslots/src/watchedPromises.js +++ b/packages/swingset-liveslots/src/watchedPromises.js @@ -14,6 +14,7 @@ import { parseVatSlot } from './parseVatSlots.js'; * @param {*} options.collectionManager * @param {import('@endo/marshal').ConvertValToSlot} options.convertValToSlot * @param {import('@endo/marshal').ConvertSlotToVal} options.convertSlotToVal + * @param {(vref: any) => boolean} options.maybeExportPromise */ export function makeWatchedPromiseManager({ syscall, @@ -22,6 +23,7 @@ export function makeWatchedPromiseManager({ collectionManager, convertValToSlot, convertSlotToVal, + maybeExportPromise, }) { const { makeScalarBigMapStore } = collectionManager; const { defineDurableKind } = vom; @@ -103,41 +105,14 @@ export function makeWatchedPromiseManager({ /** * Revives watched promises. * - * @param {import('@endo/marshal').Unserialize} unserialize * @param {(vref: any) => Promise} revivePromise * @returns {void} */ - function loadWatchedPromiseTable(unserialize, revivePromise) { - const deadPromisesRaw = syscall.vatstoreGet('deadPromises'); - if (!deadPromisesRaw) { - return; - } - const disconnectObjectCapData = JSON.parse( - syscall.vatstoreGet('deadPromiseDO'), - ); - const disconnectObject = unserialize(disconnectObjectCapData); - syscall.vatstoreDelete('deadPromises'); - syscall.vatstoreDelete('deadPromiseDO'); - const deadPromises = new Set(deadPromisesRaw.split(',')); - - for (const [vpid, watches] of watchedPromiseTable.entries()) { - if (deadPromises.has(vpid)) { - watchedPromiseTable.delete(vpid); - for (const watch of watches) { - const [watcher, ...args] = watch; - void Promise.resolve().then(() => { - if (watcher.onRejected) { - watcher.onRejected(disconnectObject, ...args); - } else { - throw disconnectObject; - } - }); - } - } else { - const p = revivePromise(vpid); - promiseRegistrations.init(vpid, p); - pseudoThen(p, vpid); - } + function loadWatchedPromiseTable(revivePromise) { + for (const vpid of watchedPromiseTable.keys()) { + const p = revivePromise(vpid); + promiseRegistrations.init(vpid, p); + pseudoThen(p, vpid); } } @@ -178,7 +153,6 @@ export function makeWatchedPromiseManager({ // TODO: add vpid->p virtual table mapping, to keep registration alive // TODO: remove mapping upon resolution - // TODO: track watched but non-exported promises, add during prepareShutdownRejections // maybe check importedVPIDs here and add to table if !has void Promise.resolve().then(() => { const watcherVref = convertValToSlot(watcher); @@ -205,35 +179,22 @@ export function makeWatchedPromiseManager({ watchedPromiseTable.set(vpid, harden([...watches, [watcher, ...args]])); } else { watchedPromiseTable.init(vpid, harden([[watcher, ...args]])); + + // Ensure that this vat's promises are rejected at termination. + if (maybeExportPromise(vpid)) { + syscall.subscribe(vpid); + } + promiseRegistrations.init(vpid, p); pseudoThen(p, vpid); } }); } - function prepareShutdownRejections( - importedVPIDsSet, - disconnectObjectCapData, - ) { - const deadPromises = []; - for (const vpid of watchedPromiseTable.keys()) { - if (!importedVPIDsSet.has(vpid)) { - deadPromises.push(vpid); // "exported" plus "neither" vpids - } - } - deadPromises.sort(); // just in case - syscall.vatstoreSet('deadPromises', deadPromises.join(',')); - syscall.vatstoreSet( - 'deadPromiseDO', - JSON.stringify(disconnectObjectCapData), - ); - } - return harden({ preparePromiseWatcherTables, loadWatchedPromiseTable, providePromiseWatcher, watchPromise, - prepareShutdownRejections, }); } diff --git a/packages/swingset-liveslots/tools/fakeVirtualSupport.js b/packages/swingset-liveslots/tools/fakeVirtualSupport.js index 6e90a9e073e..89c91d0b541 100644 --- a/packages/swingset-liveslots/tools/fakeVirtualSupport.js +++ b/packages/swingset-liveslots/tools/fakeVirtualSupport.js @@ -230,6 +230,8 @@ export function makeFakeLiveSlotsStuff(options = {}) { function assertAcceptableSyscallCapdataSize(_capdatas) {} + const maybeExportPromise = _vref => false; + return { syscall, allocateExportID, @@ -250,6 +252,7 @@ export function makeFakeLiveSlotsStuff(options = {}) { dumpStore, setVrm, assertAcceptableSyscallCapdataSize, + maybeExportPromise, }; } @@ -281,6 +284,7 @@ export function makeFakeWatchedPromiseManager( collectionManager, convertValToSlot: fakeStuff.convertValToSlot, convertSlotToVal: fakeStuff.convertSlotToVal, + maybeExportPromise: fakeStuff.maybeExportPromise, }); } /**