Skip to content

Commit 195a363

Browse files
jlobue10claude
andcommitted
Retry each metadata failure exactly once per gateway flip
The gateway-flip retry effect had two churn paths (Bugbot, PR Chia-Network#3029): retrying an errored entry re-inserts its key with a fresh in-flight promise synchronously, and Map.forEach revisits keys re-added during the pass - so the effect attached a rejection retry to the very fetch it had just started, double-fetching a failure. And rapid toggles could stack rejection handlers on one promise; when it rejected, each handler invalidated in turn, the later ones discarding the refetch the first had started - even a successful one. The effect now iterates a snapshot of the map, and a rejection handler retries only the failure it saw: the fetch's own catch stores its rejection as the entry's error, so an entry that has moved on - already retried by a stacked handler, or settled successfully - is left alone. Each failure is retried exactly once per flip and successful results are never dropped. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017NXWAjaHb9SFafLeguTHd8
1 parent e715a4d commit 195a363

1 file changed

Lines changed: 16 additions & 2 deletions

File tree

packages/gui/src/components/nfts/provider/hooks/useMetadataData.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,12 @@ export default function useMetadataData(props: UseMetadataDataProps) {
178178
// started under the old preference and may fail because of it — after
179179
// this effect has run, nothing else would retry that failure — so it is
180180
// retried on rejection; a result that arrives successfully is kept.
181-
metadatasOnDemand.forEach((metadataOnDemand, nftId) => {
181+
//
182+
// Iterate a snapshot: retrying an errored entry re-inserts its key with a
183+
// fresh in-flight promise synchronously, and Map.forEach revisits keys
184+
// re-added during the pass — the live map would attach a rejection retry
185+
// to the very fetch this effect just started, double-fetching a failure.
186+
Array.from(metadatasOnDemand.entries()).forEach(([nftId, metadataOnDemand]) => {
182187
const retry = () =>
183188
invalidate(nftId).catch((e) => {
184189
log(`Error retrying metadata for nftId: ${nftId}`, e);
@@ -187,7 +192,16 @@ export default function useMetadataData(props: UseMetadataDataProps) {
187192
if (metadataOnDemand.error) {
188193
retry();
189194
} else if (metadataOnDemand.promise) {
190-
metadataOnDemand.promise.catch(retry);
195+
metadataOnDemand.promise.catch((e) => {
196+
// Retry only the failure this handler saw. The fetch's own catch
197+
// stores its rejection as the entry's error, so anything else here
198+
// means the entry has moved on — a stacked handler from another
199+
// toggle already retried it, or a newer fetch succeeded — and a
200+
// retry would discard that state and fetch again for nothing.
201+
if (metadatasOnDemand.get(nftId)?.error === e) {
202+
retry();
203+
}
204+
});
191205
}
192206
});
193207
}, [ipfsGateway, invalidate /* immutable */, metadatasOnDemand /* immutable */]);

0 commit comments

Comments
 (0)