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

Introduce executeOnUIRuntimeSync and use it to replace Sync Data Holder #4300

Merged
merged 14 commits into from
Jan 17, 2024

Conversation

kmagiera
Copy link
Member

Summary

This PR introduces a new concept of executing worklets on the UI runtime synchronously from the RN JS runtime.

It is implemented using runtime decorator that uses a lock to guard all the code that's being executed on that runtime. Thanks to this locking mechanism, we can now grab that lock on a different thread and safely execute some more methods without worrying that the runtime object will be accessed concurrently.

This new method makes opens up some new possibilities. The first one being the way we handle two-way shared values. The shared value rewrite introduced a concept of one-way shared values that are only accessed and modified from the ui runtime. We initially thought such values can be used in majority of the use cases, however, in practice it turned out that a lot of existing code already relies on reading from shared values on the RN thread. Despite the reads happening very infrequently (just a few times per shared value runtime), we still needed to serialize all the updates for a given shared value such that it can be read later on from the RN thread. With this change, we can leverage synchronous execution of the code on UI runtime and ask it to read the value on demand rather than synchronizing the value after each single update.

Test plan

Common/cpp/NativeModules/NativeReanimatedModule.cpp Outdated Show resolved Hide resolved
Common/cpp/ReanimatedRuntime/ReanimatedRuntime.cpp Outdated Show resolved Hide resolved
Common/cpp/ReanimatedRuntime/ReanimatedRuntime.h Outdated Show resolved Hide resolved
src/reanimated2/mutables.ts Outdated Show resolved Hide resolved
Example/src/AnimatedStyleUpdateExample.tsx Outdated Show resolved Hide resolved
Copy link
Collaborator

@tjzel tjzel left a comment

Choose a reason for hiding this comment

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

With the changes in #5513 and #5516 it seems LGTM.

However I would like someone to explain to me how does it protect us (the locking mechanism implemented here) and what from exactly.

Not approving since it's still a draft.

piaskowyk pushed a commit that referenced this pull request Dec 21, 2023
## Summary

Minor changes to #4300.

## Test plan

🚀
…Data Holder (#5513)

## Summary

This PR fixes some small issues from
#4300

---------

Co-authored-by: Tomasz Żelawski <[email protected]>
@tomekzaw tomekzaw self-requested a review December 22, 2023 09:18
@piaskowyk piaskowyk marked this pull request as ready for review December 22, 2023 10:10
@ranaavneet
Copy link

Hi @tomekzaw @tjzel We currently pass true in oneWayReads param to almost every useSharedValue initialisation (Although @tomekzaw already warned me that it could get deprecated in the future and thats why its not part of the public API yet). I have a couple of questions regarding this:

  1. Does this mean that post this PR, oneWayReads parameter would be deprecated?
  2. We started passing oneWayReads as true since it gave us performance improvements. Would this PR have performance gains as well ?
  3. Do we plan to release this with 3.7.0, and if yes, what are the tentative timelines for the same?

Apologies for bothering you guys during holidays! PS: Merry Christmas in advance! 🎄

@tjzel
Copy link
Collaborator

tjzel commented Dec 22, 2023

@ranaavneet Today @tomekzaw was explaining to me bits of this PR in regard to performance so here's what I know.

  1. Yes, oneWayReads will be deprecated (actually removed, but we might keep deprecated TS signature). This is because oneWayReads=true will kind-of be the default behavior of a shared value.

  2. From our initial benchmarks done by @piaskowyk this PR will give you an edge over the current default shared value. However I don't think he has tested it with oneWayReads=true yet. I don't want to say anything hasty here since @tomekzaw noticed that with this PR there should come some extra optimizations in other bits of our code.

  3. If we get enough testing I think it can go to 3.7.0. In regards to the timeline of that, my safe guess would be the end of January 2024, but I don't want to promise anything 😸.

Merry Christmas to you too!

Copy link
Member

@tomekzaw tomekzaw left a comment

Choose a reason for hiding this comment

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

I've left some minor suggestions in the comments.

Apart from this, I have some thoughts regarding the granularity of locks.

When we use the Reanimated runtime synchronously on the JS thread, we have one giant lock that wraps the call, which is nice.

On the other hand, when we use the Reanimated runtime on the UI thread, we call lock and unlock in before and after methods. This means that when we invoke the UI runtime using JSI from C++ code, we lock and unlock it multiple number of times.

Is it okay to have different levels of granularity in these two scenarios? Is there some way we can lock the Reanimated runtime once for the whole animation frame to avoid short-locking?

Common/cpp/NativeModules/NativeReanimatedModule.cpp Outdated Show resolved Hide resolved
Common/cpp/ReanimatedRuntime/WorkletRuntime.cpp Outdated Show resolved Hide resolved
Common/cpp/ReanimatedRuntime/WorkletRuntime.cpp Outdated Show resolved Hide resolved
Common/cpp/ReanimatedRuntime/WorkletRuntime.cpp Outdated Show resolved Hide resolved
Common/cpp/ReanimatedRuntime/WorkletRuntime.cpp Outdated Show resolved Hide resolved
src/reanimated2/mutables.ts Outdated Show resolved Hide resolved
src/reanimated2/initializers.ts Show resolved Hide resolved
Common/cpp/ReanimatedRuntime/WorkletRuntime.h Outdated Show resolved Hide resolved
Common/cpp/ReanimatedRuntime/WorkletRuntime.h Outdated Show resolved Hide resolved
Common/cpp/ReanimatedRuntime/WorkletRuntime.cpp Outdated Show resolved Hide resolved
@piaskowyk
Copy link
Member

Is it okay to have different levels of granularity in these two scenarios? Is there some way we can lock the Reanimated runtime once for the whole animation frame to avoid short-locking?

Based on my understanding, in this case, we require both levels of locking. The lock and unlock in the before and after functions ensure thread-safety at the single operation level, which is necessary. However, the lock in WorkletRuntime::executeSync exists to synchronize the batch of two single JS instructions: runGuarded and extractShareableOrThrow.

We can further discuss this during our meeting tomorrow with kmagiera.

src/reanimated2/core.ts Show resolved Hide resolved
@piaskowyk piaskowyk added this pull request to the merge queue Jan 17, 2024
Merged via the queue into main with commit d809364 Jan 17, 2024
15 checks passed
@piaskowyk piaskowyk deleted the sync-calls-on-ui-runtime branch January 17, 2024 10:31
github-merge-queue bot pushed a commit that referenced this pull request Mar 6, 2024
…ds (#5759)

## Summary

Fixes #5660, a regression introduced in #4300.

The aforementioned race condition happens this way:

1. An object using a `ShareableHandle` underneath (e.g. a shared value)
is created.
2. This object is accessed on UI thread.
3. The initializer of this `ShareableHandle` get called on the UI
thread.
4. At the same time, JS thread schedules an operation on this object
(e.g. setting a value of shared value).
5. Access on UI thread forces the initialization. The condition of the
if clause resolves to true and UI thread tries to access the runtime.
![Screenshot 2024-03-04 at 13 00
07](https://github.com/software-mansion/react-native-reanimated/assets/40713406/6c124599-3d60-4211-a1a9-3ccedd88f687)
6. However, access from JS thread has locked the runtime and causes the
UI runtime to wait.
7. Then, JS thread enters the same if clause body and initializes the
whole shareable.
8. After initializing and releasing the runtime, UI thread gets
unblocked.
9. However, now `initializer_` has been nulled and causes memory access
issues.

It's difficult to change the whole flow of locking to prevent such
scenarios. Therefore we won't null the `initializer_` object anymore.
However, this won't fix the problem of potential double initialization.
Luckily, the code of `valueUnpacker` already prevents that with its
shareable handle cache and the fact that runtime operations must be
sequential.

## Test plan

Run the following race condition reproduction made - you should re-run
the app several times (probably up to 20ish) since it's most likely to
happen when the app starts. It's also more likely to happen on Android
simulators (at least for me).

<details>
<summary>
Test code
</summary>

```tsx
import {useSharedValue} from 'react-native-reanimated';
import {useEffect, useState} from 'react';

const value = 666666;

const Screen = () => {
  const sv = useSharedValue(value);
  useSharedValue(1);
  useSharedValue(2);
  useSharedValue(3);
  sv.value = value;

  useEffect(() => {
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    const currentValue = sv.value;
  }, [sv]);

  return null;
};
const ReanimatedCrashReproduction = () => {
  const [render, setRender] = useState(false);

  useEffect(() => {
    const interval = setInterval(() => setRender(r => !r), 500);
    return () => clearInterval(interval);
  }, []);

  return render ? <Screen /> : null;
};

export default ReanimatedCrashReproduction;
```

</summary>

You can do the following to see that double initialization happens
still, but all is well 🚰.

![Screenshot 2024-03-06 at 10 06
50](https://github.com/software-mansion/react-native-reanimated/assets/40713406/6517cd3a-eea0-45ef-bd18-a15215272f13)

Big thanks to @piaskowyk for the debugging help 🚀
github-merge-queue bot pushed a commit that referenced this pull request Jul 15, 2024
## Summary

Fixes `useSharedValue` type definition in docs after changes in the
#4300 PR.

## Test plan

No tests :)
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.

5 participants