Skip to content

Make the view manager prop setter caches thread-safe - #59245

Open
brownrice777 wants to merge 1 commit into
react:mainfrom
brownrice777:thread-safe-prop-setter-caches
Open

brownrice777 wants to merge 1 commit into
react:mainfrom
brownrice777:thread-safe-prop-setter-caches

Conversation

@brownrice777

@brownrice777 brownrice777 commented Oct 20, 2026 •

Copy link
Copy Markdown

Summary:

ViewManagersPropertyCache.CLASS_PROPS_CACHE and the two setter maps in ViewManagerPropertyUpdater are static HashMaps. Two threads write them:

  • the UI thread, when it updates the props of a view (ViewManagerPropertyUpdater.updateProps → findManagerSetter);
  • the JS thread, when it builds a view config (getConstantsForViewManager → ViewManager.nativeProps).

In bridgeless mode, JS gets the view config of a lazy view manager on first use, during render. At that time, the UI thread can mount other views. Concurrent writes to a HashMap can lose entries or break the map.

This change uses ConcurrentHashMap for the three maps. The get-then-put code does not change. If two threads compute the same entry, the values are equal, so the second write is harmless. I did not use computeIfAbsent, because getNativePropSettersForViewManagerClass calls itself for the superclass.

This came up in the review of #59173, which sends the eager view managers through the same on-demand path.

Changelog:

[ANDROID] [FIXED] - Make the view manager prop setter caches thread-safe

Test Plan:

Unit tests: ./gradlew :packages:react-native:ReactAndroid:testDebugUnitTest --tests "com.facebook.react.uimanager.*" passes (230 tests, 0 failures).

Device: Xiaomi Mi Note 2 (Snapdragon 821, Android 8.0). App: a production Expo app, release build, React Native 0.88.0, Hermes. I compiled this change and #59173 against 0.88.0 and replaced the same classes in react-android-0.88.0-release.aar. I measured from process start to the first frame with rows in the first list, 12 cold starts for each build.

Build Median Range getConstants()
Stock 0.88.0 2416 ms 2335–2477 ms 215 ms (45 keys)
#59173 + this change 2243 ms 2187–2406 ms 45 ms (5 keys)

To see the cost of this change alone, I also used a build whose JS does not call getConstants() at startup, so the first view configs come from getConstantsForViewManager() during render. Without this change: 2189 ms median (2140–2236 ms). With it: 2192 ms median (2156–2244 ms). I see no difference.

Same chat app with threads list and messages. With this change, I opened the chat list, a thread, settings, the theme list, the profile and the contacts, and scrolled. Logcat shows no crash and no view config errors.

ViewManagersPropertyCache.CLASS_PROPS_CACHE and the two setter maps in
ViewManagerPropertyUpdater are static HashMaps. Two threads write them:

- the UI thread, when it updates the props of a view
  (ViewManager.updateProperties -> ViewManagerPropertyUpdater.updateProps);
- the JS thread, when it builds a view config
  (getConstantsForViewManager -> ViewManager.nativeProps).

In bridgeless mode, the JS thread gets the view config of a lazy view
manager on first use, during render. At that time the UI thread can mount
other views. Concurrent writes to a HashMap can lose entries or break the
map.

This change uses ConcurrentHashMap for the three maps. The get-then-put
code does not change. If two threads compute the same entry, they make
equal values, so the second write is harmless. computeIfAbsent is not
used, because getNativePropSettersForViewManagerClass calls itself for
the superclass.
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Oct 20, 2026
@brownrice777
brownrice777 marked this pull request as ready for review October 20, 2026 20:22
@facebook-github-tools facebook-github-tools Bot added the Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team. label Oct 20, 2026
@javache

javache commented Oct 21, 2026

Copy link
Copy Markdown
Contributor

I'm trying to figure out why we haven't seen this internally. We use the generated viewmanager delegates exclusively, so do those take a different path here?

@javache

javache commented Oct 21, 2026

Copy link
Copy Markdown
Contributor

Ah, when using ViewManagerDelegates, we won't be using the prop setters at all, so there's no race on the UI thread. we've also disabled useNativeViewConfigsInBridgelessMode in release builds.

We also don't consume the NativeProps in JS when codegenNativeComponent is used, so a lot of these lookups are actually wasted.

@meta-codesync

meta-codesync Bot commented Oct 21, 2026

Copy link
Copy Markdown

@javache has imported this pull request. If you are a Meta employee, you can view this in D126631569.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants