-
Notifications
You must be signed in to change notification settings - Fork 2.7k
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
Fixes race condition when changing back to initial variables before request finishes #11186
Conversation
🦋 Changeset detectedLatest commit: 2b1ce1d The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
size-limit report 📦
|
✅ Deploy Preview for apollo-client-docs ready!
To edit notification comments on pull requests, go to your Netlify site configuration. |
@@ -157,14 +157,14 @@ export class QueryInfo { | |||
this.dirty = false; | |||
} | |||
|
|||
getDiff(variables = this.variables): Cache.DiffResult<any> { | |||
const options = this.getDiffOptions(variables); | |||
getDiff(): Cache.DiffResult<any> { |
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.
Turns out there was only one area of the codebase that called getDiff
with variables, which was the readCache
function in QueryManager#fetchQueryByPolicy
. That function calls queryInfo.init(...)
before that readCache
function is called, therefore updating this.variables
before this function is executed.
This parameter was always confusing to me because it felt too easy to accidentally change the variables being looked at by this QueryInfo
instance. I prefer more intentional changes to this.variables
to try and reduce the surface area where variables
could change.
There is still lots more that could be refactored to make it more intentional, but that feels outside the scope of this PR and could open a big can of worms. For now, this change seemed reasonable as a step toward that future without breaking existing functionality.
@@ -1516,7 +1516,7 @@ export class QueryManager<TStore> { | |||
networkStatus, | |||
}); | |||
|
|||
const readCache = () => queryInfo.getDiff(variables); | |||
const readCache = () => queryInfo.getDiff(); |
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.
As alluded to in #11186 (comment) this was the only area of the codebase that passed variables
to getDiff
. Since queryInfo.init(...)
is called a few lines up, this argument felt unneeded since init
will set this.variables
.
src/core/QueryInfo.ts
Outdated
// In case variables have changed and there is a race condition where the | ||
// previous variables request finishes after the current variables | ||
// request, skip the cache update. | ||
equal(this.variables, variables) && |
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.
The one bummer about this particular change is that we miss a nice opportunity to write a fetched result to the cache, even though it isn't actively used right away. Since this change prevents the cache write when variables don't match, this means that switching back to the variables will re-execute the network request since the cache wasn't previously written.
Unfortunately I couldn't find a simpler solution to this bug. The cache write triggers a cache broadcast, but it broadcasts the wrong result (or rather, the result it broadcasts is associated to the wrong set of variables). I attempted to disable broadcasting entirely in the cache.writeQuery
call, but this did not seem to work.
False alarm! I was able to fix this in 20b2932
/release:pr |
A new release has been made for this PR. You can install it with |
/release:pr |
A new release has been made for this PR. You can install it with |
.changeset/odd-beers-perform.md
Outdated
"@apollo/client": patch | ||
--- | ||
|
||
Fixes an issue where triggering a query with different variables, then rapidly changing back to initial variables with a cached result would return the wrong result if the previous request finished after switching back to initial variables. |
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.
I don't love the way this is worded, so any suggestions here are welcome.
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.
I see what you mean after the reading the test, but yeah there's a lot to keep track of conceptually 😅 Maybe using VariablesA
and VariablesB
could be helpful? Something like:
If a user triggers a query with
VariablesA
, then triggers the same query withVariablesB
but switches back toVariablesA
before the response for the fetch made withVariablesB
is returned, the user will now correctly receive the cached data for the request made withVariablesA
. (Previously, the user would see the data forVariablesB
even after switching the variables back toVariablesA
once the request made withVariablesB
completed.)
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.
LGTM! And glad to have that this.variables
assignment expression out of the call to updateWatch
:)
.changeset/odd-beers-perform.md
Outdated
"@apollo/client": patch | ||
--- | ||
|
||
Fixes an issue where triggering a query with different variables, then rapidly changing back to initial variables with a cached result would return the wrong result if the previous request finished after switching back to initial variables. |
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.
I see what you mean after the reading the test, but yeah there's a lot to keep track of conceptually 😅 Maybe using VariablesA
and VariablesB
could be helpful? Something like:
If a user triggers a query with
VariablesA
, then triggers the same query withVariablesB
but switches back toVariablesA
before the response for the fetch made withVariablesB
is returned, the user will now correctly receive the cached data for the request made withVariablesA
. (Previously, the user would see the data forVariablesB
even after switching the variables back toVariablesA
once the request made withVariablesB
completed.)
@alessbell I like that a lot more. At least its easier to keep track of what "variables" the changelog is referring to. Thanks for the suggestion! |
Fixes #11184
Fixes an issue where the wrong result was returned when switching back to initial variables with a cached result before the request for previous changed variables finished.
Prior to v3.7.11, changing variables would abort a previous request, which meant there was no cache update for the previous variables. This behavior had some edge cases and resulted in some race condition bugs. This was fixed by #10597, but because #10597 no longer aborted requests, the previous request would complete and the cache would be written, but the broadcasted result would be incorrectly associated with the current variables.
Checklist: