Skip to content

Commit

Permalink
fix(swingset-liveslots): Move promise rejection responsibility into t…
Browse files Browse the repository at this point in the history
…he kernel

Fixes #6694
  • Loading branch information
gibson042 committed Mar 16, 2023
1 parent 5d84157 commit dd29ff3
Show file tree
Hide file tree
Showing 4 changed files with 21 additions and 76 deletions.
4 changes: 1 addition & 3 deletions packages/SwingSet/src/kernel/kernelQueue.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
24 changes: 3 additions & 21 deletions packages/swingset-liveslots/src/liveslots.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down Expand Up @@ -674,6 +674,7 @@ function build(
// eslint-disable-next-line no-use-before-define
convertValToSlot,
convertSlotToVal: unmeteredConvertSlotToVal,
maybeExportPromise,
});

function convertValToSlot(val) {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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,
Expand Down
65 changes: 13 additions & 52 deletions packages/swingset-liveslots/src/watchedPromises.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { parseVatSlot } from './parseVatSlots.js';
* @param {*} options.collectionManager
* @param {import('@endo/marshal').ConvertValToSlot<any>} options.convertValToSlot
* @param {import('@endo/marshal').ConvertSlotToVal<any>} options.convertSlotToVal
* @param {(vref: any) => boolean} options.maybeExportPromise
*/
export function makeWatchedPromiseManager({
syscall,
Expand All @@ -22,6 +23,7 @@ export function makeWatchedPromiseManager({
collectionManager,
convertValToSlot,
convertSlotToVal,
maybeExportPromise,
}) {
const { makeScalarBigMapStore } = collectionManager;
const { defineDurableKind } = vom;
Expand Down Expand Up @@ -103,41 +105,14 @@ export function makeWatchedPromiseManager({
/**
* Revives watched promises.
*
* @param {import('@endo/marshal').Unserialize<unknown>} unserialize
* @param {(vref: any) => Promise<any>} 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);
}
}

Expand Down Expand Up @@ -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);
Expand All @@ -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,
});
}
4 changes: 4 additions & 0 deletions packages/swingset-liveslots/tools/fakeVirtualSupport.js
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,8 @@ export function makeFakeLiveSlotsStuff(options = {}) {

function assertAcceptableSyscallCapdataSize(_capdatas) {}

const maybeExportPromise = _vref => false;

return {
syscall,
allocateExportID,
Expand All @@ -250,6 +252,7 @@ export function makeFakeLiveSlotsStuff(options = {}) {
dumpStore,
setVrm,
assertAcceptableSyscallCapdataSize,
maybeExportPromise,
};
}

Expand Down Expand Up @@ -281,6 +284,7 @@ export function makeFakeWatchedPromiseManager(
collectionManager,
convertValToSlot: fakeStuff.convertValToSlot,
convertSlotToVal: fakeStuff.convertSlotToVal,
maybeExportPromise: fakeStuff.maybeExportPromise,
});
}
/**
Expand Down

0 comments on commit dd29ff3

Please sign in to comment.