-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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 race condition when calling mutate synchronously #735
Conversation
Great feedback @huozhi, thanks! |
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! awesome work and explanation there 👍
|
||
if (_data && typeof _data === 'function') { | ||
// `_data` is a function, call it passing current cache value | ||
try { | ||
data = await _data(cache.get(key)) | ||
_data = _data(cache.get(key)) |
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.
we can keep the previous 'else if' flow here instead of reassign _data here ?
if (_data && typeof _data === 'function') {
// `_data` is a function, call it passing current cache value
try {
data = _data(cache.get(key))
} catch (err) {
error = err
}
} else if (_data && typeof _data.then === 'function') {
// `_data` is a promise
isAsyncMutation = true
try {
data = await _data
} catch (err) {
error = err
}
} else {
data = _data
}
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.
We can’t. If _data
is an async function, calling it without await
will get a promise returned, and we have to resolve it again.
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.
On i see now 😅. I think i can add a test for mutate with async function. we don't have one now.
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.
@promer94 that would be great, thanks!
Tested a bit, should be fine to ship this :) |
* 0.3.8 * replace rIC with rAF (#744) * Fix race condition when calling mutate synchronously (#735) * fix race condition when calling mutate synchronously * fix test * add comment * fix code reviews * refactor: support SSR in Deno (#754) * refactor: support SSR in Deno * refactor: improve Deno determining * Add @ts-ignore Co-authored-by: Shu Ding <g@shud.in> Co-authored-by: Shu Ding <g@shud.in> * update useSWR-loading and useSWR refresh * fix eslint error (#768) Co-authored-by: Shu Ding <g@shud.in> Co-authored-by: X <git@iamje.com>
* 0.3.8 * replace rIC with rAF (#744) * Fix race condition when calling mutate synchronously (#735) * fix race condition when calling mutate synchronously * fix test * add comment * fix code reviews * refactor: support SSR in Deno (#754) * refactor: support SSR in Deno * refactor: improve Deno determining * Add @ts-ignore Co-authored-by: Shu Ding <g@shud.in> Co-authored-by: Shu Ding <g@shud.in> * fix eslint error (#768) * Fix `mutateCallback` types (#745) * Fix `mutateCallback` types * WIP * Add CodeSandbox CI (#769) * add CodeSandbox CI * add new line * fix install cmd Co-authored-by: Paco <34928425+pacocoursey@users.noreply.github.com> * dispatch's payload type is actionType and run lint (#772) * chore: payload is actionType * chore: move a ts-ignore comment * Fix suspense (#777) * fix #494 * add comment * rename to initialMountedRef * 0.3.9 * fix: mark isValidating as false when key is falsy (#757) * fix: tear down when key turns to empty * use false for empty key * Fix README.md typo (#783) '/api/data' => '/api/user' in "Multiple Arguments" * fix: do mount check in config callback (#787) * Update api-hooks example README.md (#790) Updated the Vercel deploy link to the correct directory * Return '@null' if args is null ASAP (#767) * chore: return 'null' if arg[i] is null ASAP * chore: update comment * chore: use continue * Bump ini from 1.3.5 to 1.3.8 (#806) Bumps [ini](https://github.com/isaacs/ini) from 1.3.5 to 1.3.8. - [Release notes](https://github.com/isaacs/ini/releases) - [Commits](npm/ini@v1.3.5...v1.3.8) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * update test Co-authored-by: Shu Ding <g@shud.in> Co-authored-by: X <git@iamje.com> Co-authored-by: Umidbek Karimov <uma.karimov@gmail.com> Co-authored-by: Paco <34928425+pacocoursey@users.noreply.github.com> Co-authored-by: matamatanot <39780486+matamatanot@users.noreply.github.com> Co-authored-by: Jiachi Liu <inbox@huozhi.im> Co-authored-by: sAy <47605337+mingcenwei@users.noreply.github.com> Co-authored-by: William Crutchfield <william.r.crutchfield@gmail.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bug
We have this inconsistency issue in current version:
The data should be
2
after these 2 synchronous mutation calls. However if you test, it is actually1
(here's a great bug reproduction from @derindutz).The cause is that we are always updating the cache in async (as #619 tries to fix), so the argument
v
passed to the second mutation callback is still0
. To make it clear, here's a diagram addressing this bug:Solution
We at first tried to make the mutate process synchronous (if the callback is not a promise or async function), but that brings another issue which is intermediate state:
Now it correctly renders
2
at the end, but there's a moment that1
was rendered before that. Instead we should "batch"synchronous mutations. It is because that the first mutation can't get deduplicated by the second one anymore. They both render the data.
To ensure the correctness of
mutate
, we need to make state broadcasting always asynchronous (kinda like React), but cache needs to be updated ASAP. So the latter mutations will always have the result from the former, but only the last mutation will get rendered:The consequence of this fix is, if you mutate something and immediately check the result, it won't be updated. So in our test I added
await 0
to delay the check in the next tick:Fixes #566. Related to #619.