Mirror: The highly customizable and versatile GraphQL client with which you add on features like normalized caching as you grow.

fix(graphcache): Prevent refetch cycles when handling cache misses (#2737)

* Prevent infinite loops when handling cache misses

* Add changeset

authored by kitten.sh and committed by

GitHub ecfbf8bf 11558486

+18 -1
+5
.changeset/smart-pears-sing.md
··· 1 + --- 2 + '@urql/exchange-graphcache': patch 3 + --- 4 + 5 + Prevent cache misses from causing infinite network requests from being issued, when two operations manipulate each other while experiencing cache misses or are partially uncacheable.
+13 -1
exchanges/graphcache/src/cacheExchange.ts
··· 56 56 const requestedRefetch: Operations = new Set(); 57 57 const deps: DependentOperations = new Map(); 58 58 59 + let reexecutingOperations: Operations = new Set(); 60 + let dependentOperations: Operations = new Set(); 61 + 59 62 const isBlockedByOptimisticUpdate = (dependencies: Dependencies): boolean => { 60 63 for (const dep of dependencies.values()) 61 64 if (blockedDependencies.has(dep)) return true; ··· 84 87 if (key !== operation.key) { 85 88 const op = operations.get(key); 86 89 if (op) { 90 + // Collect all dependent operations if the reexecuting operation is a query 91 + if (operation.kind === 'query') dependentOperations.add(key); 87 92 operations.delete(key); 88 93 let policy: RequestPolicy = 'cache-first'; 89 94 if (requestedRefetch.has(key)) { ··· 94 99 } 95 100 } 96 101 } 102 + 103 + // Upon completion, all dependent operations become reexecuting operations, preventing 104 + // them from reexecuting prior operations again, causing infinite loops 105 + const _reexecutingOperations = reexecutingOperations; 106 + (reexecutingOperations = dependentOperations).add(operation.key); 107 + (dependentOperations = _reexecutingOperations).clear(); 97 108 }; 98 109 99 110 // This registers queries with the data layer to ensure commutativity ··· 278 289 return ( 279 290 res.outcome === 'miss' && 280 291 res.operation.context.requestPolicy !== 'cache-only' && 281 - !isBlockedByOptimisticUpdate(res.dependencies) 292 + !isBlockedByOptimisticUpdate(res.dependencies) && 293 + !reexecutingOperations.has(res.operation.key) 282 294 ); 283 295 }), 284 296 map(res => {