Skip to content
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

Merged
merged 3 commits into from
Dec 7, 2022

Conversation

kitten
Copy link
Member

@kitten kitten commented Dec 6, 2022

Resolve #2853

Summary

This fixes a deadlock that could occur in the layers of Graphcache, when a deferred layer (subscriptions or otherwise hasNext layers) would be moved past previously optimistic layers (like mutations). In such cases, it would become impossible for preceding layers (i.e. mutations) to ever squash and move "out of the way" causing a deadlock that'd make it impossible for the deferred layer's data to show up when the two layers had overlaps.

Instead, we now will try to clear out deferred layers more aggressively. This means that we will squash its data even when it's deferred, when data is written.

Furthermore, we then move the layer from its current position (or recreate it) when it's re-registered, i.e. when a new message/update comes in for the deferred layer.

This should keep the deferred layers data in the cache but still bump it as needed.

Set of changes

  • Allow deferred layers to be squashed
  • Allow layers to only move past empty ones from its current position in the optimisticOrder list

@kitten kitten force-pushed the fix/deferred-layer-deadlock branch from 4ca5b4b to 323b803 Compare December 6, 2022 17:17
@kitten kitten marked this pull request as ready for review December 6, 2022 17:36
@kitten kitten requested a review from JoviDeCroock December 6, 2022 17:36
Comment on lines +216 to +217
if (operation.kind === 'subscription' || result.hasNext)
reserveLayer(store.data, operation.key, true);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This meant that we were:

  • unnecessarily adding layers to optimisticOrder even when that wasn't needed
  • where shifting non-deferred layers that already were registered before, which is unexpected. Our tests are relying on this though, so we can "cheat" here

Comment on lines -1845 to -1857
nextRes({
operation: queryOpB,
data: {
__typename: 'Query',
node: {
__typename: 'Node',
id: 'node',
name: 'query b',
},
},
});

expect(data).toHaveProperty('node.name', 'query b');
Copy link
Member Author

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

// 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;
Copy link
Member Author

Choose a reason for hiding this comment

The 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"

data.commutativeKeys.has(data.optimisticOrder[i]) &&
!data.deferredKeys.has(data.optimisticOrder[i])
) {
data.commutativeKeys.has(data.optimisticOrder[i])
Copy link
Member Author

Choose a reason for hiding this comment

The 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.

@JoviDeCroock
Copy link
Collaborator

As a follow up we could create an e2e test but wouldn't call it blocking for this PR as we can publish without

@kitten kitten merged commit fbd254b into main Dec 7, 2022
@kitten kitten deleted the fix/deferred-layer-deadlock branch December 7, 2022 12:32
@github-actions github-actions bot mentioned this pull request Dec 7, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Subscription doesn't update records after mutation
2 participants