Skip to content

Commit

Permalink
fixup! fix(vow): handle resolution loops in vows
Browse files Browse the repository at this point in the history
make watch cycle detection ephemeral
  • Loading branch information
mhofman committed Jun 21, 2024
1 parent a6c378f commit 5bdad2c
Showing 1 changed file with 24 additions and 6 deletions.
30 changes: 24 additions & 6 deletions packages/vow/src/watch.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,23 @@ const settle = (resolver, watcher, wcb, value, watcherArgs = []) => {
* @param {IsRetryableReason} isRetryableReason
* @param {ReturnType<typeof makeWatchNextStep>} watchNextStep
*/
const preparePromiseWatcher = (zone, isRetryableReason, watchNextStep) =>
zone.exoClass(
const preparePromiseWatcher = (zone, isRetryableReason, watchNextStep) => {
// We use an ephemeral WeakSet for the previously seen vows in a watch operation
// While watch is durable, it suffices to detect the cycle in a single incarnation
/** @type {WeakMap<PromiseWatcher, WeakSet<any>>} */
const watcherSeenPayloads = new WeakMap();

/** @param {PromiseWatcher} watcher */
const getSeenPayloads = watcher => {
let seenPayloads = watcherSeenPayloads.get(watcher);
if (!seenPayloads) {
seenPayloads = new WeakSet();
watcherSeenPayloads.set(watcher, seenPayloads);
}
return seenPayloads;
};

return zone.exoClass(
'PromiseWatcher',
PromiseWatcherI,
/**
Expand All @@ -81,7 +96,6 @@ const preparePromiseWatcher = (zone, isRetryableReason, watchNextStep) =>
const state = {
vow: /** @type {unknown} */ (undefined),
priorRetryValue: /** @type {any} */ (undefined),
seenPayloads: zone.detached().weakSetStore('seenPayloads'),
resolver,
watcher,
watcherArgs: harden(watcherArgs),
Expand All @@ -91,19 +105,21 @@ const preparePromiseWatcher = (zone, isRetryableReason, watchNextStep) =>
{
/** @type {Required<PromiseWatcher>['onFulfilled']} */
onFulfilled(value) {
const { watcher, watcherArgs, resolver, seenPayloads } = this.state;
const { watcher, watcherArgs, resolver } = this.state;
const payload = getVowPayload(value);
if (payload) {
const seenPayloads = getSeenPayloads(this.self);
// TODO: rely on endowed helper to get storable cap from payload
if (seenPayloads?.has(payload.vowV0)) {
if (seenPayloads.has(payload.vowV0)) {
return this.self.onRejected(Error('Vow resolution cycle detected'));
}
seenPayloads?.add(payload.vowV0);
seenPayloads.add(payload.vowV0);
// We've been shortened, so reflect our state accordingly, and go again.
this.state.vow = value;
watchNextStep(value, this.self);
return;
}
watcherSeenPayloads.delete(this.self);
this.state.priorRetryValue = undefined;
this.state.watcher = undefined;
this.state.resolver = undefined;
Expand All @@ -122,13 +138,15 @@ const preparePromiseWatcher = (zone, isRetryableReason, watchNextStep) =>
return;
}
}
watcherSeenPayloads.delete(this.self);
this.state.priorRetryValue = undefined;
this.state.resolver = undefined;
this.state.watcher = undefined;
settle(resolver, watcher, 'onRejected', reason, watcherArgs);
},
},
);
};

/**
* @param {Zone} zone
Expand Down

0 comments on commit 5bdad2c

Please sign in to comment.