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

Race condition in NativeReanimatedModule::performOperations() #6245

Closed
yard opened this issue Jul 9, 2024 · 7 comments · Fixed by #6330
Closed

Race condition in NativeReanimatedModule::performOperations() #6245

yard opened this issue Jul 9, 2024 · 7 comments · Fixed by #6330
Labels
Missing info The user didn't precise the problem enough Missing repro This issue need minimum repro scenario Platform: iOS This issue is specific to iOS

Comments

@yard
Copy link

yard commented Jul 9, 2024

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, enabled shouldReanimatedSkipCommit_ 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

@github-actions github-actions bot added the Missing repro This issue need minimum repro scenario label Jul 9, 2024
Copy link

github-actions bot commented Jul 9, 2024

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?

@github-actions github-actions bot added the Missing info The user didn't precise the problem enough label Jul 9, 2024
Copy link

github-actions bot commented Jul 9, 2024

Hey! 👋

It looks like you've omitted a few important sections from the issue template.

Please complete Description section.

@github-actions github-actions bot added the Platform: iOS This issue is specific to iOS label Jul 9, 2024
@joe-sam
Copy link

joe-sam commented Jul 11, 2024

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.

@bartlomiejbloniarz
Copy link
Contributor

Hi @yard! I am currently working on resolving some issues related to performOperations, so I will take this problem into considerations. Your solution seems good, I think you can keep it as a patch, but since there will be some changes to this logic soon I don't think that a PR is necessary, I will handle this problem.

@yard
Copy link
Author

yard commented Jul 15, 2024

Hi @bartlomiejbloniarz ! Noted, do you want me to send you the diff or something or you are good for now?

@bartlomiejbloniarz
Copy link
Contributor

Thank you @yard. The diff could be useful so if you have time it would be cool if you could send it!

@yard
Copy link
Author

yard commented Jul 16, 2024

Sure thing, here you go:

react-native-reanimated-npm-3.8.1-d993815628.patch

The idea is pretty simple here: we added a flag propsUpdatedDuringMount_ which gets set whenever PropsRegistry receive an update with shouldReanimatedSkipCommit_ set and in ReanimatedMountHook we check if updates happenned during mount phase to re-apply the props. Could be tidied up etc, but does the job for us.

github-merge-queue bot pushed a commit that referenced this issue Jul 30, 2024
## 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Missing info The user didn't precise the problem enough Missing repro This issue need minimum repro scenario Platform: iOS This issue is specific to iOS
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants