-
-
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
Race condition in NativeReanimatedModule::performOperations() #6245
Comments
Hey! 👋 The issue doesn't seem to contain a minimal reproduction. Could you provide a snack or a link to a GitHub repository under your username that reproduces the problem? |
Hey! 👋 It looks like you've omitted a few important sections from the issue template. Please complete Description section. |
By definition all race conditions are nearly impossible to reproduce! So kindly please make a PR fix and include a before and after video capturing/evading the glitch respectively. |
Hi @yard! I am currently working on resolving some issues related to |
Hi @bartlomiejbloniarz ! Noted, do you want me to send you the diff or something or you are good for now? |
Thank you @yard. The diff could be useful so if you have time it would be cool if you could send it! |
Sure thing, here you go: react-native-reanimated-npm-3.8.1-d993815628.patch The idea is pretty simple here: we added a flag |
## Summary This PR changes the way `performOperations` and `ReanimatedCommitHook` are synchronized. The current implementations faces 2 problems: 1. Updates that come through `performOperations` after `pleaseSkipReanimatedCommit` is called in the commit hook will be deferred until the next commit. Usually it's fine since the next commit will be triggered by the next animation frame. But if there was a singular update scheduled through Reanimated, we might not see the change for a long time. This issue is more thoroughly explained in [this issue](#6245). 2. In `ReanimatedCommitMarker` there is an assumption that there can be at most one commit happening on a given thread (i.e. there can't be nested commits). This isn't true, since there can be an event listener that commits some changes in a reaction to a native mount/unmount (which is a part of the commit function). [This exact scenario was observed in the New Expensify App with `react-native-keyboard-controller`](Expensify/App#44437 (comment)). At first I thought this is a mistake, but [this PR in RN](facebook/react-native@5f0435f) seems to allow for scenarios like that. Applied fixes: ad 1. Now instead of resetting the skip flag in reanimated after a transaction is mounted (via MountHook), we reset the flag whenever a non-empty batch is read in `performOperations`. This ensures that we don't make an unnecessary commit, but never skip any updates. ad 2. Now we mark reanimated commits through `ReanimatedCommitShadowNode`. This is a class derived from ShadowNode that allows us to modify the root nodes traits_ (and add our custom trait). This ensures that even if the root node gets cloned it will retain the information. We couldn't derive from `RootShadowNode` since it is a final class. ## Test plan
Description
Background
We have been debugging a weird issue where sometimes the app would incorrectly account for keyboard state. To spare you from all the steps on that journey, we were able to confirm that the styles computed by
useAnimatedStyle
hook will not apply (although debug logs confirmed they did re-compute and did that correctly). After carefully studying the issue I think we have found the root cause for that behavior.Issue description
performOperations() method of
NativeReanimatedModule
can submit updated props to the registry while React is committing a new shadow tree. Specifically, it can happen so that ReanimatedCommitHook has already gathered the props from the registry and returned the updated tree to React, enabledshouldReanimatedSkipCommit_
flag and React is now reconciling the tree; at this very moment updates applied to props registry will effectively be lost:performOperations()
would bail out (as the flag is set). Until next re-render happens, the UI would be presented with the old props.Surely, this is a hard thing to reproduce – sorry for not providing a snack/repo, I am trying to think of a way of how to demonstrate that without too many external dependencies, but I hope that explanation is clear enough to at least confirm the issue is present and valid.
Proposed fix
We have applied a patch that adds another flag to props registry, which gets set whenever any prop update happens during mount phase. This way, ReanimatedMountHook can check if anything was changed during mount phase and trigger a re-render. I am not exactly sure if that's the best way to fix the problem, but it does seem to work and does so reliably and without causing excessive renders.
If someone could confirm this is a legit way of resolving the problem, I am happy to prepare a PR with the fix.
Steps to reproduce
N/A
Snack or a link to a repository
N/A
Reanimated version
3.8.1
React Native version
0.73.1
Platforms
iOS
JavaScript runtime
Hermes
Workflow
React Native
Architecture
Fabric (New Architecture)
Build type
Debug app & dev bundle
Device
Real device
Device model
Any iOS device
Acknowledgements
Yes
The text was updated successfully, but these errors were encountered: