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

Configure Feed

Select the types of activity you want to include in your feed.

fix(graphcache): Fix deadlocked layers between deferred and optimistic layers (#2861)

authored by

Phil Pluckthun and committed by
GitHub
fbd254ba 8b72ff31

+30 -55
+5
.changeset/poor-items-shake.md
··· 1 + --- 2 + '@urql/exchange-graphcache': patch 3 + --- 4 + 5 + Fix a deadlock condition in Graphcache's layers, which is caused by subscriptions (or other deferred layers) starting before one-off mutation layers. This causes the mutation to not be completed, which keeps its data preferred above the deferred layer. That in turn means that layers stop squashing, which causes new results to be missing indefinitely, when they overlap.
-23
exchanges/graphcache/src/cacheExchange.test.ts
··· 1811 1811 variables: undefined, 1812 1812 }); 1813 1813 1814 - const queryOpB = client.createRequestOperation('query', { 1815 - key: 3, 1816 - query, 1817 - variables: undefined, 1818 - }); 1819 - 1820 1814 expect(data).toBe(undefined); 1821 1815 1822 1816 nextOp(queryOpA); ··· 1838 1832 nextOp(mutationOp); 1839 1833 expect(reexec).toHaveBeenCalledTimes(1); 1840 1834 expect(data).toHaveProperty('node.name', 'optimistic'); 1841 - 1842 - // NOTE: We purposefully skip the following: 1843 - // nextOp(queryOpB); 1844 - 1845 - nextRes({ 1846 - operation: queryOpB, 1847 - data: { 1848 - __typename: 'Query', 1849 - node: { 1850 - __typename: 'Node', 1851 - id: 'node', 1852 - name: 'query b', 1853 - }, 1854 - }, 1855 - }); 1856 - 1857 - expect(data).toHaveProperty('node.name', 'query b'); 1858 1835 }); 1859 1836 1860 1837 it('applies mutation results on top of commutative queries', () => {
+2 -5
exchanges/graphcache/src/cacheExchange.ts
··· 213 213 optimisticKeysToDependencies.delete(operation.key); 214 214 } 215 215 216 - reserveLayer( 217 - store.data, 218 - operation.key, 219 - operation.kind === 'subscription' || result.hasNext 220 - ); 216 + if (operation.kind === 'subscription' || result.hasNext) 217 + reserveLayer(store.data, operation.key, true); 221 218 222 219 let queryDependencies: void | Dependencies; 223 220 let data: Data | null = result.data;
+23 -27
exchanges/graphcache/src/store/data.ts
··· 161 161 while ( 162 162 --i >= 0 && 163 163 data.refLock.has(data.optimisticOrder[i]) && 164 - data.commutativeKeys.has(data.optimisticOrder[i]) && 165 - !data.deferredKeys.has(data.optimisticOrder[i]) 166 - ) { 164 + data.commutativeKeys.has(data.optimisticOrder[i]) 165 + ) 167 166 squashLayer(data.optimisticOrder[i]); 168 - } 169 167 } 170 168 171 169 currentOwnership = null; ··· 527 525 layerKey: number, 528 526 hasNext?: boolean 529 527 ) => { 528 + // Find the current index for the layer, and remove it from 529 + // the order if it exists already 530 + let index = data.optimisticOrder.indexOf(layerKey); 531 + if (index > -1) data.optimisticOrder.splice(index, 1); 532 + 530 533 if (hasNext) { 531 534 data.deferredKeys.add(layerKey); 535 + // If the layer has future results then we'll move it past any layer that's 536 + // still empty, so currently pending operations will take precedence over it 537 + for ( 538 + index = index > -1 ? index : 0; 539 + index < data.optimisticOrder.length && 540 + !data.deferredKeys.has(data.optimisticOrder[index]) && 541 + (!data.refLock.has(data.optimisticOrder[index]) || 542 + !data.commutativeKeys.has(data.optimisticOrder[index])); 543 + index++ 544 + ); 532 545 } else { 533 546 data.deferredKeys.delete(layerKey); 534 - } 535 - 536 - let index = data.optimisticOrder.indexOf(layerKey); 537 - if (index > -1) { 538 - if (!data.commutativeKeys.has(layerKey) && !hasNext) { 539 - data.optimisticOrder.splice(index, 1); 540 - // Protect optimistic layers from being turned into non-optimistic layers 541 - // while preserving optimistic data 547 + // Protect optimistic layers from being turned into non-optimistic layers 548 + // while preserving optimistic data 549 + if (index > -1 && !data.commutativeKeys.has(layerKey)) 542 550 clearLayer(data, layerKey); 543 - } else { 544 - return; 545 - } 546 - } 547 - 548 - // If the layer has future results then we'll move it past any layer that's 549 - // still empty, so currently pending operations will take precedence over it 550 - for ( 551 551 index = 0; 552 - hasNext && 553 - index < data.optimisticOrder.length && 554 - !data.deferredKeys.has(data.optimisticOrder[index]) && 555 - (!data.refLock.has(data.optimisticOrder[index]) || 556 - !data.commutativeKeys.has(data.optimisticOrder[index])); 557 - index++ 558 - ); 552 + } 559 553 554 + // Register the layer with the deferred or "top" index and 555 + // mark it as commutative 560 556 data.optimisticOrder.splice(index, 0, layerKey); 561 557 data.commutativeKeys.add(layerKey); 562 558 };