-
Notifications
You must be signed in to change notification settings - Fork 462
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(graphcache): Fix deadlocked layers between deferred and optimistic layers #2861
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'@urql/exchange-graphcache': patch | ||
--- | ||
|
||
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. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -213,11 +213,8 @@ export const cacheExchange = <C extends Partial<CacheExchangeOpts>>( | |
optimisticKeysToDependencies.delete(operation.key); | ||
} | ||
|
||
reserveLayer( | ||
store.data, | ||
operation.key, | ||
operation.kind === 'subscription' || result.hasNext | ||
); | ||
if (operation.kind === 'subscription' || result.hasNext) | ||
reserveLayer(store.data, operation.key, true); | ||
Comment on lines
+216
to
+217
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This meant that we were:
|
||
|
||
let queryDependencies: void | Dependencies; | ||
let data: Data | null = result.data; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -161,11 +161,9 @@ export const clearDataState = () => { | |
while ( | ||
--i >= 0 && | ||
data.refLock.has(data.optimisticOrder[i]) && | ||
data.commutativeKeys.has(data.optimisticOrder[i]) && | ||
!data.deferredKeys.has(data.optimisticOrder[i]) | ||
) { | ||
data.commutativeKeys.has(data.optimisticOrder[i]) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here, as discussed, we realised that deferred layers should 100% be squasheable. We basially put deferred layers in the right place, write to them, and then ... we assume that they're never squasheable. This means that for subscriptions (or other neverending result streams) this layer never goes away, and pending layers that then were on top can never go away either, and have the potential to infinitely be "in the way" of old deferred layers. |
||
) | ||
squashLayer(data.optimisticOrder[i]); | ||
} | ||
} | ||
|
||
currentOwnership = null; | ||
|
@@ -527,36 +525,34 @@ export const reserveLayer = ( | |
layerKey: number, | ||
hasNext?: boolean | ||
) => { | ||
// Find the current index for the layer, and remove it from | ||
// the order if it exists already | ||
let index = data.optimisticOrder.indexOf(layerKey); | ||
if (index > -1) data.optimisticOrder.splice(index, 1); | ||
|
||
if (hasNext) { | ||
data.deferredKeys.add(layerKey); | ||
// If the layer has future results then we'll move it past any layer that's | ||
// still empty, so currently pending operations will take precedence over it | ||
for ( | ||
index = index > -1 ? index : 0; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the relevant changed line. We shift the layer based on where it was before if it's still around, rather than letting it "hop back in front" |
||
index < data.optimisticOrder.length && | ||
!data.deferredKeys.has(data.optimisticOrder[index]) && | ||
(!data.refLock.has(data.optimisticOrder[index]) || | ||
!data.commutativeKeys.has(data.optimisticOrder[index])); | ||
index++ | ||
); | ||
} else { | ||
data.deferredKeys.delete(layerKey); | ||
} | ||
|
||
let index = data.optimisticOrder.indexOf(layerKey); | ||
if (index > -1) { | ||
if (!data.commutativeKeys.has(layerKey) && !hasNext) { | ||
data.optimisticOrder.splice(index, 1); | ||
// Protect optimistic layers from being turned into non-optimistic layers | ||
// while preserving optimistic data | ||
// Protect optimistic layers from being turned into non-optimistic layers | ||
// while preserving optimistic data | ||
if (index > -1 && !data.commutativeKeys.has(layerKey)) | ||
clearLayer(data, layerKey); | ||
} else { | ||
return; | ||
} | ||
} | ||
|
||
// If the layer has future results then we'll move it past any layer that's | ||
// still empty, so currently pending operations will take precedence over it | ||
for ( | ||
index = 0; | ||
hasNext && | ||
index < data.optimisticOrder.length && | ||
!data.deferredKeys.has(data.optimisticOrder[index]) && | ||
(!data.refLock.has(data.optimisticOrder[index]) || | ||
!data.commutativeKeys.has(data.optimisticOrder[index])); | ||
index++ | ||
); | ||
} | ||
|
||
// Register the layer with the deferred or "top" index and | ||
// mark it as commutative | ||
data.optimisticOrder.splice(index, 0, layerKey); | ||
data.commutativeKeys.add(layerKey); | ||
}; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This tested for something that shouldn't ever happen. A result coming in without the layer being registered. This will now write straight to the "base" layer in most cases. Most importantly, this is undefined behaviour and we shouldn't drive-by test for it