From 353c59f6f7643ac61987eed151664180ba4753d4 Mon Sep 17 00:00:00 2001 From: Illia Gerasymov Date: Tue, 20 Oct 2026 21:08:12 +0300 Subject: [PATCH] Make the view manager prop setter caches thread-safe 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. --- .../react/uimanager/ViewManagerPropertyUpdater.kt | 8 ++++++-- .../facebook/react/uimanager/ViewManagersPropertyCache.kt | 5 ++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagerPropertyUpdater.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagerPropertyUpdater.kt index 7a33df4ae121..6ed45e57e0e4 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagerPropertyUpdater.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagerPropertyUpdater.kt @@ -12,6 +12,7 @@ import com.facebook.common.logging.FLog import com.facebook.react.bridge.ReadableArray import com.facebook.react.uimanager.ViewManagersPropertyCache.PropSetter import java.util.HashMap +import java.util.concurrent.ConcurrentHashMap @Suppress("DEPRECATION") public object ViewManagerPropertyUpdater { @@ -31,8 +32,11 @@ public object ViewManagerPropertyUpdater { private const val TAG = "ViewManagerPropertyUpdater" - private val VIEW_MANAGER_SETTER_MAP: MutableMap, ViewManagerSetter<*, *>> = HashMap() - private val SHADOW_NODE_SETTER_MAP: MutableMap, ShadowNodeSetter<*>> = HashMap() + // Used from the JS thread (view configs) and the UI thread (prop updates). + private val VIEW_MANAGER_SETTER_MAP: MutableMap, ViewManagerSetter<*, *>> = + ConcurrentHashMap() + private val SHADOW_NODE_SETTER_MAP: MutableMap, ShadowNodeSetter<*>> = + ConcurrentHashMap() @JvmStatic public fun clear() { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagersPropertyCache.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagersPropertyCache.kt index 2c4f6c702b83..1cad6c414322 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagersPropertyCache.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagersPropertyCache.kt @@ -21,6 +21,7 @@ import com.facebook.react.uimanager.annotations.ReactProp import com.facebook.react.uimanager.annotations.ReactPropGroup import com.facebook.yoga.YogaValue import java.lang.reflect.Method +import java.util.concurrent.ConcurrentHashMap /** * This class is responsible for holding view manager property setters and is used in a process of @@ -29,7 +30,9 @@ import java.lang.reflect.Method @Suppress("DEPRECATION") // ReactShadowNode is @Deprecated but still required by this cache. internal object ViewManagersPropertyCache { - private val CLASS_PROPS_CACHE: MutableMap, Map> = HashMap() + // Used from the JS thread (view configs) and the UI thread (prop updates). + private val CLASS_PROPS_CACHE: MutableMap, Map> = + ConcurrentHashMap() private val EMPTY_PROPS_MAP: MutableMap = HashMap() @JvmStatic