diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt index db9de4e2ab8..7b91d36046e 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<92170b32c13dc405a81b4a5cf5677afb>> */ /** @@ -312,6 +312,12 @@ public object ReactNativeFeatureFlags { @JvmStatic public fun preparedTextCacheSize(): Double = accessor.preparedTextCacheSize() + /** + * Enables a new mechanism in ShadowTree to prevent problems caused by multiple threads trying to commit concurrently. If a thread tries to commit a few times unsuccessfully, it will acquire a lock and try again. + */ + @JvmStatic + public fun preventShadowTreeCommitExhaustion(): Boolean = accessor.preventShadowTreeCommitExhaustion() + /** * Releases the cached image data when it is consumed by the observers. */ diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt index 4fda8e3b7a4..501a5128b3e 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<34c0a7def872e270cd542785d9bfde40>> */ /** @@ -67,6 +67,7 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces private var fuseboxNetworkInspectionEnabledCache: Boolean? = null private var hideOffscreenVirtualViewsOnIOSCache: Boolean? = null private var preparedTextCacheSizeCache: Double? = null + private var preventShadowTreeCommitExhaustionCache: Boolean? = null private var releaseImageDataWhenConsumedCache: Boolean? = null private var shouldPressibilityUseW3CPointerEventsForHoverCache: Boolean? = null private var skipActivityIdentityAssertionOnHostPauseCache: Boolean? = null @@ -507,6 +508,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces return cached } + override fun preventShadowTreeCommitExhaustion(): Boolean { + var cached = preventShadowTreeCommitExhaustionCache + if (cached == null) { + cached = ReactNativeFeatureFlagsCxxInterop.preventShadowTreeCommitExhaustion() + preventShadowTreeCommitExhaustionCache = cached + } + return cached + } + override fun releaseImageDataWhenConsumed(): Boolean { var cached = releaseImageDataWhenConsumedCache if (cached == null) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt index d345dd41d1b..5d7d4f61963 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<568d3a6628dd6cc9b32a77c590210730>> + * @generated SignedSource<<3c9a5f37b4d141ffcf9a2ec152f70563>> */ /** @@ -122,6 +122,8 @@ public object ReactNativeFeatureFlagsCxxInterop { @DoNotStrip @JvmStatic public external fun preparedTextCacheSize(): Double + @DoNotStrip @JvmStatic public external fun preventShadowTreeCommitExhaustion(): Boolean + @DoNotStrip @JvmStatic public external fun releaseImageDataWhenConsumed(): Boolean @DoNotStrip @JvmStatic public external fun shouldPressibilityUseW3CPointerEventsForHover(): Boolean diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt index 5b92f4548af..8d7f4fb42d2 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<276706dd4eef0370e61fc36e3420f0aa>> + * @generated SignedSource<<728ae563ec31340ccc8c4c593d566c64>> */ /** @@ -117,6 +117,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi override fun preparedTextCacheSize(): Double = 200.0 + override fun preventShadowTreeCommitExhaustion(): Boolean = false + override fun releaseImageDataWhenConsumed(): Boolean = false override fun shouldPressibilityUseW3CPointerEventsForHover(): Boolean = false diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt index bcc15a8fb1d..a3c37c3bc51 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<9a5b0bdc0bd9ab0889e548b25cfe5371>> */ /** @@ -71,6 +71,7 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc private var fuseboxNetworkInspectionEnabledCache: Boolean? = null private var hideOffscreenVirtualViewsOnIOSCache: Boolean? = null private var preparedTextCacheSizeCache: Double? = null + private var preventShadowTreeCommitExhaustionCache: Boolean? = null private var releaseImageDataWhenConsumedCache: Boolean? = null private var shouldPressibilityUseW3CPointerEventsForHoverCache: Boolean? = null private var skipActivityIdentityAssertionOnHostPauseCache: Boolean? = null @@ -558,6 +559,16 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc return cached } + override fun preventShadowTreeCommitExhaustion(): Boolean { + var cached = preventShadowTreeCommitExhaustionCache + if (cached == null) { + cached = currentProvider.preventShadowTreeCommitExhaustion() + accessedFeatureFlags.add("preventShadowTreeCommitExhaustion") + preventShadowTreeCommitExhaustionCache = cached + } + return cached + } + override fun releaseImageDataWhenConsumed(): Boolean { var cached = releaseImageDataWhenConsumedCache if (cached == null) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsOverrides_RNOSS_Experimental_Android.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsOverrides_RNOSS_Experimental_Android.kt index 9e8c6e5389a..1b8ad3bdc5c 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsOverrides_RNOSS_Experimental_Android.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsOverrides_RNOSS_Experimental_Android.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<636ec2163060c2ef6fdbc5e27ef41716>> + * @generated SignedSource<<0bafb0a2fb79c4220d21f1736894af14>> */ /** @@ -23,6 +23,8 @@ public open class ReactNativeFeatureFlagsOverrides_RNOSS_Experimental_Android : // We could use JNI to get the defaults from C++, // but that is more expensive than just duplicating the defaults here. + override fun preventShadowTreeCommitExhaustion(): Boolean = true + override fun useNativeEqualsInNativeReadableArrayAndroid(): Boolean = true override fun useNativeTransformHelperAndroid(): Boolean = true diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt index a9e9f318569..27a0d06de6a 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<68ec2f18b3554bcd6e3e10c530d8cca0>> + * @generated SignedSource<> */ /** @@ -117,6 +117,8 @@ public interface ReactNativeFeatureFlagsProvider { @DoNotStrip public fun preparedTextCacheSize(): Double + @DoNotStrip public fun preventShadowTreeCommitExhaustion(): Boolean + @DoNotStrip public fun releaseImageDataWhenConsumed(): Boolean @DoNotStrip public fun shouldPressibilityUseW3CPointerEventsForHover(): Boolean diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp index 4859040a2ed..456ec223eec 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<> */ /** @@ -321,6 +321,12 @@ class ReactNativeFeatureFlagsJavaProvider return method(javaProvider_); } + bool preventShadowTreeCommitExhaustion() override { + static const auto method = + getReactNativeFeatureFlagsProviderJavaClass()->getMethod("preventShadowTreeCommitExhaustion"); + return method(javaProvider_); + } + bool releaseImageDataWhenConsumed() override { static const auto method = getReactNativeFeatureFlagsProviderJavaClass()->getMethod("releaseImageDataWhenConsumed"); @@ -656,6 +662,11 @@ double JReactNativeFeatureFlagsCxxInterop::preparedTextCacheSize( return ReactNativeFeatureFlags::preparedTextCacheSize(); } +bool JReactNativeFeatureFlagsCxxInterop::preventShadowTreeCommitExhaustion( + facebook::jni::alias_ref /*unused*/) { + return ReactNativeFeatureFlags::preventShadowTreeCommitExhaustion(); +} + bool JReactNativeFeatureFlagsCxxInterop::releaseImageDataWhenConsumed( facebook::jni::alias_ref /*unused*/) { return ReactNativeFeatureFlags::releaseImageDataWhenConsumed(); @@ -908,6 +919,9 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() { makeNativeMethod( "preparedTextCacheSize", JReactNativeFeatureFlagsCxxInterop::preparedTextCacheSize), + makeNativeMethod( + "preventShadowTreeCommitExhaustion", + JReactNativeFeatureFlagsCxxInterop::preventShadowTreeCommitExhaustion), makeNativeMethod( "releaseImageDataWhenConsumed", JReactNativeFeatureFlagsCxxInterop::releaseImageDataWhenConsumed), diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h index c11e3651ad3..657a6aee911 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h +++ b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<9ad2d870c21a193730e937d566aae5dc>> + * @generated SignedSource<<3e83546899ed1194fe03f143865ba97a>> */ /** @@ -171,6 +171,9 @@ class JReactNativeFeatureFlagsCxxInterop static double preparedTextCacheSize( facebook::jni::alias_ref); + static bool preventShadowTreeCommitExhaustion( + facebook::jni::alias_ref); + static bool releaseImageDataWhenConsumed( facebook::jni::alias_ref); diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp index e3f0e044b94..e4afdef1bcc 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<4ee4c8163356e7777ab080a9364a5492>> + * @generated SignedSource<<21d92927b8b282b29ed9ff5795c60f8a>> */ /** @@ -214,6 +214,10 @@ double ReactNativeFeatureFlags::preparedTextCacheSize() { return getAccessor().preparedTextCacheSize(); } +bool ReactNativeFeatureFlags::preventShadowTreeCommitExhaustion() { + return getAccessor().preventShadowTreeCommitExhaustion(); +} + bool ReactNativeFeatureFlags::releaseImageDataWhenConsumed() { return getAccessor().releaseImageDataWhenConsumed(); } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h index fe3637bcb27..f959eaa7f8d 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<0bbb98a247e0cb3d1a54228507f98b49>> + * @generated SignedSource<<4e6e5bfa892fcfc1ad67d625a6b58ce6>> */ /** @@ -274,6 +274,11 @@ class ReactNativeFeatureFlags { */ RN_EXPORT static double preparedTextCacheSize(); + /** + * Enables a new mechanism in ShadowTree to prevent problems caused by multiple threads trying to commit concurrently. If a thread tries to commit a few times unsuccessfully, it will acquire a lock and try again. + */ + RN_EXPORT static bool preventShadowTreeCommitExhaustion(); + /** * Releases the cached image data when it is consumed by the observers. */ diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp index f4f5ca486be..de1e499d7af 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<> */ /** @@ -875,6 +875,24 @@ double ReactNativeFeatureFlagsAccessor::preparedTextCacheSize() { return flagValue.value(); } +bool ReactNativeFeatureFlagsAccessor::preventShadowTreeCommitExhaustion() { + auto flagValue = preventShadowTreeCommitExhaustion_.load(); + + if (!flagValue.has_value()) { + // This block is not exclusive but it is not necessary. + // If multiple threads try to initialize the feature flag, we would only + // be accessing the provider multiple times but the end state of this + // instance and the returned flag value would be the same. + + markFlagAsAccessed(47, "preventShadowTreeCommitExhaustion"); + + flagValue = currentProvider_->preventShadowTreeCommitExhaustion(); + preventShadowTreeCommitExhaustion_ = flagValue; + } + + return flagValue.value(); +} + bool ReactNativeFeatureFlagsAccessor::releaseImageDataWhenConsumed() { auto flagValue = releaseImageDataWhenConsumed_.load(); @@ -884,7 +902,7 @@ bool ReactNativeFeatureFlagsAccessor::releaseImageDataWhenConsumed() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(47, "releaseImageDataWhenConsumed"); + markFlagAsAccessed(48, "releaseImageDataWhenConsumed"); flagValue = currentProvider_->releaseImageDataWhenConsumed(); releaseImageDataWhenConsumed_ = flagValue; @@ -902,7 +920,7 @@ bool ReactNativeFeatureFlagsAccessor::shouldPressibilityUseW3CPointerEventsForHo // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(48, "shouldPressibilityUseW3CPointerEventsForHover"); + markFlagAsAccessed(49, "shouldPressibilityUseW3CPointerEventsForHover"); flagValue = currentProvider_->shouldPressibilityUseW3CPointerEventsForHover(); shouldPressibilityUseW3CPointerEventsForHover_ = flagValue; @@ -920,7 +938,7 @@ bool ReactNativeFeatureFlagsAccessor::skipActivityIdentityAssertionOnHostPause() // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(49, "skipActivityIdentityAssertionOnHostPause"); + markFlagAsAccessed(50, "skipActivityIdentityAssertionOnHostPause"); flagValue = currentProvider_->skipActivityIdentityAssertionOnHostPause(); skipActivityIdentityAssertionOnHostPause_ = flagValue; @@ -938,7 +956,7 @@ bool ReactNativeFeatureFlagsAccessor::traceTurboModulePromiseRejectionsOnAndroid // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(50, "traceTurboModulePromiseRejectionsOnAndroid"); + markFlagAsAccessed(51, "traceTurboModulePromiseRejectionsOnAndroid"); flagValue = currentProvider_->traceTurboModulePromiseRejectionsOnAndroid(); traceTurboModulePromiseRejectionsOnAndroid_ = flagValue; @@ -956,7 +974,7 @@ bool ReactNativeFeatureFlagsAccessor::updateRuntimeShadowNodeReferencesOnCommit( // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(51, "updateRuntimeShadowNodeReferencesOnCommit"); + markFlagAsAccessed(52, "updateRuntimeShadowNodeReferencesOnCommit"); flagValue = currentProvider_->updateRuntimeShadowNodeReferencesOnCommit(); updateRuntimeShadowNodeReferencesOnCommit_ = flagValue; @@ -974,7 +992,7 @@ bool ReactNativeFeatureFlagsAccessor::useAlwaysAvailableJSErrorHandling() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(52, "useAlwaysAvailableJSErrorHandling"); + markFlagAsAccessed(53, "useAlwaysAvailableJSErrorHandling"); flagValue = currentProvider_->useAlwaysAvailableJSErrorHandling(); useAlwaysAvailableJSErrorHandling_ = flagValue; @@ -992,7 +1010,7 @@ bool ReactNativeFeatureFlagsAccessor::useFabricInterop() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(53, "useFabricInterop"); + markFlagAsAccessed(54, "useFabricInterop"); flagValue = currentProvider_->useFabricInterop(); useFabricInterop_ = flagValue; @@ -1010,7 +1028,7 @@ bool ReactNativeFeatureFlagsAccessor::useNativeEqualsInNativeReadableArrayAndroi // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(54, "useNativeEqualsInNativeReadableArrayAndroid"); + markFlagAsAccessed(55, "useNativeEqualsInNativeReadableArrayAndroid"); flagValue = currentProvider_->useNativeEqualsInNativeReadableArrayAndroid(); useNativeEqualsInNativeReadableArrayAndroid_ = flagValue; @@ -1028,7 +1046,7 @@ bool ReactNativeFeatureFlagsAccessor::useNativeTransformHelperAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(55, "useNativeTransformHelperAndroid"); + markFlagAsAccessed(56, "useNativeTransformHelperAndroid"); flagValue = currentProvider_->useNativeTransformHelperAndroid(); useNativeTransformHelperAndroid_ = flagValue; @@ -1046,7 +1064,7 @@ bool ReactNativeFeatureFlagsAccessor::useNativeViewConfigsInBridgelessMode() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(56, "useNativeViewConfigsInBridgelessMode"); + markFlagAsAccessed(57, "useNativeViewConfigsInBridgelessMode"); flagValue = currentProvider_->useNativeViewConfigsInBridgelessMode(); useNativeViewConfigsInBridgelessMode_ = flagValue; @@ -1064,7 +1082,7 @@ bool ReactNativeFeatureFlagsAccessor::useOptimizedEventBatchingOnAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(57, "useOptimizedEventBatchingOnAndroid"); + markFlagAsAccessed(58, "useOptimizedEventBatchingOnAndroid"); flagValue = currentProvider_->useOptimizedEventBatchingOnAndroid(); useOptimizedEventBatchingOnAndroid_ = flagValue; @@ -1082,7 +1100,7 @@ bool ReactNativeFeatureFlagsAccessor::useRawPropsJsiValue() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(58, "useRawPropsJsiValue"); + markFlagAsAccessed(59, "useRawPropsJsiValue"); flagValue = currentProvider_->useRawPropsJsiValue(); useRawPropsJsiValue_ = flagValue; @@ -1100,7 +1118,7 @@ bool ReactNativeFeatureFlagsAccessor::useShadowNodeStateOnClone() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(59, "useShadowNodeStateOnClone"); + markFlagAsAccessed(60, "useShadowNodeStateOnClone"); flagValue = currentProvider_->useShadowNodeStateOnClone(); useShadowNodeStateOnClone_ = flagValue; @@ -1118,7 +1136,7 @@ bool ReactNativeFeatureFlagsAccessor::useTurboModuleInterop() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(60, "useTurboModuleInterop"); + markFlagAsAccessed(61, "useTurboModuleInterop"); flagValue = currentProvider_->useTurboModuleInterop(); useTurboModuleInterop_ = flagValue; @@ -1136,7 +1154,7 @@ bool ReactNativeFeatureFlagsAccessor::useTurboModules() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(61, "useTurboModules"); + markFlagAsAccessed(62, "useTurboModules"); flagValue = currentProvider_->useTurboModules(); useTurboModules_ = flagValue; @@ -1154,7 +1172,7 @@ double ReactNativeFeatureFlagsAccessor::virtualViewPrerenderRatio() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(62, "virtualViewPrerenderRatio"); + markFlagAsAccessed(63, "virtualViewPrerenderRatio"); flagValue = currentProvider_->virtualViewPrerenderRatio(); virtualViewPrerenderRatio_ = flagValue; diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h index c6cd574a247..aa62c97195d 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<84fc9cb4ade1c9a321e7d739b403fa72>> + * @generated SignedSource<> */ /** @@ -79,6 +79,7 @@ class ReactNativeFeatureFlagsAccessor { bool fuseboxNetworkInspectionEnabled(); bool hideOffscreenVirtualViewsOnIOS(); double preparedTextCacheSize(); + bool preventShadowTreeCommitExhaustion(); bool releaseImageDataWhenConsumed(); bool shouldPressibilityUseW3CPointerEventsForHover(); bool skipActivityIdentityAssertionOnHostPause(); @@ -106,7 +107,7 @@ class ReactNativeFeatureFlagsAccessor { std::unique_ptr currentProvider_; bool wasOverridden_; - std::array, 63> accessedFeatureFlags_; + std::array, 64> accessedFeatureFlags_; std::atomic> commonTestFlag_; std::atomic> cxxNativeAnimatedEnabled_; @@ -155,6 +156,7 @@ class ReactNativeFeatureFlagsAccessor { std::atomic> fuseboxNetworkInspectionEnabled_; std::atomic> hideOffscreenVirtualViewsOnIOS_; std::atomic> preparedTextCacheSize_; + std::atomic> preventShadowTreeCommitExhaustion_; std::atomic> releaseImageDataWhenConsumed_; std::atomic> shouldPressibilityUseW3CPointerEventsForHover_; std::atomic> skipActivityIdentityAssertionOnHostPause_; diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h index 8571d8a512a..e423546d6c1 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<4933ca80972665b1ac6e358e626d57ac>> + * @generated SignedSource<<4c386a379665e7c983db3da799f7c902>> */ /** @@ -215,6 +215,10 @@ class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider { return 200.0; } + bool preventShadowTreeCommitExhaustion() override { + return false; + } + bool releaseImageDataWhenConsumed() override { return false; } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h index 18b83bccd3f..8ff27b27de0 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<72907a7058f38e80b54947d321b0062d>> + * @generated SignedSource<> */ /** @@ -468,6 +468,15 @@ class ReactNativeFeatureFlagsDynamicProvider : public ReactNativeFeatureFlagsDef return ReactNativeFeatureFlagsDefaults::preparedTextCacheSize(); } + bool preventShadowTreeCommitExhaustion() override { + auto value = values_["preventShadowTreeCommitExhaustion"]; + if (!value.isNull()) { + return value.getBool(); + } + + return ReactNativeFeatureFlagsDefaults::preventShadowTreeCommitExhaustion(); + } + bool releaseImageDataWhenConsumed() override { auto value = values_["releaseImageDataWhenConsumed"]; if (!value.isNull()) { diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsOverridesOSSExperimental.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsOverridesOSSExperimental.h index 63d613ccb4f..230dc476cef 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsOverridesOSSExperimental.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsOverridesOSSExperimental.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<> */ /** @@ -27,6 +27,10 @@ class ReactNativeFeatureFlagsOverridesOSSExperimental : public ReactNativeFeatur public: ReactNativeFeatureFlagsOverridesOSSExperimental() = default; + bool preventShadowTreeCommitExhaustion() override { + return true; + } + bool useNativeEqualsInNativeReadableArrayAndroid() override { return true; } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h index 1f0c1e8921f..cbb61a26cdb 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<87afa2a91688f00bc5843cc13a8b72f1>> + * @generated SignedSource<<96ec4b7e248bbde00b52f09779e8a051>> */ /** @@ -72,6 +72,7 @@ class ReactNativeFeatureFlagsProvider { virtual bool fuseboxNetworkInspectionEnabled() = 0; virtual bool hideOffscreenVirtualViewsOnIOS() = 0; virtual double preparedTextCacheSize() = 0; + virtual bool preventShadowTreeCommitExhaustion() = 0; virtual bool releaseImageDataWhenConsumed() = 0; virtual bool shouldPressibilityUseW3CPointerEventsForHover() = 0; virtual bool skipActivityIdentityAssertionOnHostPause() = 0; diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp index 34b218f7149..50c1ff6a1c5 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<> */ /** @@ -279,6 +279,11 @@ double NativeReactNativeFeatureFlags::preparedTextCacheSize( return ReactNativeFeatureFlags::preparedTextCacheSize(); } +bool NativeReactNativeFeatureFlags::preventShadowTreeCommitExhaustion( + jsi::Runtime& /*runtime*/) { + return ReactNativeFeatureFlags::preventShadowTreeCommitExhaustion(); +} + bool NativeReactNativeFeatureFlags::releaseImageDataWhenConsumed( jsi::Runtime& /*runtime*/) { return ReactNativeFeatureFlags::releaseImageDataWhenConsumed(); diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h index 94f4282cf47..f4c941d3174 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h +++ b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<008cdb8915d0252d2c714529fb177245>> */ /** @@ -130,6 +130,8 @@ class NativeReactNativeFeatureFlags double preparedTextCacheSize(jsi::Runtime& runtime); + bool preventShadowTreeCommitExhaustion(jsi::Runtime& runtime); + bool releaseImageDataWhenConsumed(jsi::Runtime& runtime); bool shouldPressibilityUseW3CPointerEventsForHover(jsi::Runtime& runtime); diff --git a/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTree.cpp b/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTree.cpp index e91e643ccd2..483a1915fd6 100644 --- a/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTree.cpp +++ b/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTree.cpp @@ -22,6 +22,10 @@ namespace facebook::react { +namespace { +const int MAX_COMMIT_ATTEMPTS_BEFORE_LOCKING = 3; +} // namespace + using CommitStatus = ShadowTree::CommitStatus; using CommitMode = ShadowTree::CommitMode; @@ -210,7 +214,8 @@ void ShadowTree::setCommitMode(CommitMode commitMode) const { auto revision = ShadowTreeRevision{}; { - std::unique_lock lock(commitMutex_); + ShadowTree::UniqueLock lock = uniqueCommitLock(); + if (commitMode_ == commitMode) { return; } @@ -227,7 +232,7 @@ void ShadowTree::setCommitMode(CommitMode commitMode) const { } CommitMode ShadowTree::getCommitMode() const { - std::shared_lock lock(commitMutex_); + SharedLock lock = sharedCommitLock(); return commitMode_; } @@ -241,17 +246,32 @@ CommitStatus ShadowTree::commit( const CommitOptions& commitOptions) const { [[maybe_unused]] int attempts = 0; - while (true) { - attempts++; - - auto status = tryCommit(transaction, commitOptions); - if (status != CommitStatus::Failed) { - return status; + if (ReactNativeFeatureFlags::preventShadowTreeCommitExhaustion()) { + while (attempts < MAX_COMMIT_ATTEMPTS_BEFORE_LOCKING) { + auto status = tryCommit(transaction, commitOptions); + if (status != CommitStatus::Failed) { + return status; + } + attempts++; } - // After multiple attempts, we failed to commit the transaction. - // Something internally went terribly wrong. - react_native_assert(attempts < 1024); + { + std::unique_lock lock(commitMutexRecursive_); + return tryCommit(transaction, commitOptions); + } + } else { + while (true) { + attempts++; + + auto status = tryCommit(transaction, commitOptions); + if (status != CommitStatus::Failed) { + return status; + } + + // After multiple attempts, we failed to commit the transaction. + // Something internally went terribly wrong. + react_native_assert(attempts < 1024); + } } } @@ -269,7 +289,7 @@ CommitStatus ShadowTree::tryCommit( { // Reading `currentRevision_` in shared manner. - std::shared_lock lock(commitMutex_); + SharedLock lock = sharedCommitLock(); commitMode = commitMode_; oldRevision = currentRevision_; } @@ -310,7 +330,7 @@ CommitStatus ShadowTree::tryCommit( { // Updating `currentRevision_` in unique manner if it hasn't changed. - std::unique_lock lock(commitMutex_); + UniqueLock lock = uniqueCommitLock(); if (currentRevision_.number != oldRevision.number) { return CommitStatus::Failed; @@ -349,7 +369,7 @@ CommitStatus ShadowTree::tryCommit( } ShadowTreeRevision ShadowTree::getCurrentRevision() const { - std::shared_lock lock(commitMutex_); + SharedLock lock = sharedCommitLock(); return currentRevision_; } @@ -397,4 +417,20 @@ void ShadowTree::notifyDelegatesOfUpdates() const { delegate_.shadowTreeDidFinishTransaction(mountingCoordinator_, true); } +inline ShadowTree::UniqueLock ShadowTree::uniqueCommitLock() const { + if (ReactNativeFeatureFlags::preventShadowTreeCommitExhaustion()) { + return std::unique_lock{commitMutexRecursive_}; + } else { + return std::unique_lock{commitMutex_}; + } +} + +inline ShadowTree::SharedLock ShadowTree::sharedCommitLock() const { + if (ReactNativeFeatureFlags::preventShadowTreeCommitExhaustion()) { + return std::unique_lock{commitMutexRecursive_}; + } else { + return std::shared_lock{commitMutex_}; + } +} + } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTree.h b/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTree.h index 87b6a7afa47..33e10e102a8 100644 --- a/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTree.h +++ b/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTree.h @@ -8,6 +8,8 @@ #pragma once #include +#include +#include #include #include @@ -150,10 +152,21 @@ class ShadowTree final { const SurfaceId surfaceId_; const ShadowTreeDelegate& delegate_; mutable std::shared_mutex commitMutex_; + mutable std::recursive_mutex commitMutexRecursive_; mutable CommitMode commitMode_{ CommitMode::Normal}; // Protected by `commitMutex_`. mutable ShadowTreeRevision currentRevision_; // Protected by `commitMutex_`. std::shared_ptr mountingCoordinator_; + + using UniqueLock = std::variant< + std::unique_lock, + std::unique_lock>; + using SharedLock = std::variant< + std::shared_lock, + std::unique_lock>; + + inline UniqueLock uniqueCommitLock() const; + inline SharedLock sharedCommitLock() const; }; } // namespace facebook::react diff --git a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js index ed6184a7138..c76063d095a 100644 --- a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js +++ b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js @@ -547,6 +547,17 @@ const definitions: FeatureFlagDefinitions = { }, ossReleaseStage: 'none', }, + preventShadowTreeCommitExhaustion: { + defaultValue: false, + metadata: { + dateAdded: '2025-07-23', + description: + 'Enables a new mechanism in ShadowTree to prevent problems caused by multiple threads trying to commit concurrently. If a thread tries to commit a few times unsuccessfully, it will acquire a lock and try again.', + expectedReleaseValue: true, + purpose: 'experimentation', + }, + ossReleaseStage: 'experimental', + }, releaseImageDataWhenConsumed: { defaultValue: false, metadata: { diff --git a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js index 01b984c5c2c..3693d8ee6ca 100644 --- a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js +++ b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<4b5f7535318d03c648dd81ca9a81277c>> + * @generated SignedSource<<6a5b796b26a689b4b8ebaa101c60b5c8>> * @flow strict * @noformat */ @@ -93,6 +93,7 @@ export type ReactNativeFeatureFlags = $ReadOnly<{ fuseboxNetworkInspectionEnabled: Getter, hideOffscreenVirtualViewsOnIOS: Getter, preparedTextCacheSize: Getter, + preventShadowTreeCommitExhaustion: Getter, releaseImageDataWhenConsumed: Getter, shouldPressibilityUseW3CPointerEventsForHover: Getter, skipActivityIdentityAssertionOnHostPause: Getter, @@ -358,6 +359,10 @@ export const hideOffscreenVirtualViewsOnIOS: Getter = createNativeFlagG * Number cached PreparedLayouts in TextLayoutManager cache */ export const preparedTextCacheSize: Getter = createNativeFlagGetter('preparedTextCacheSize', 200); +/** + * Enables a new mechanism in ShadowTree to prevent problems caused by multiple threads trying to commit concurrently. If a thread tries to commit a few times unsuccessfully, it will acquire a lock and try again. + */ +export const preventShadowTreeCommitExhaustion: Getter = createNativeFlagGetter('preventShadowTreeCommitExhaustion', false); /** * Releases the cached image data when it is consumed by the observers. */ diff --git a/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js index 75722950db1..b750a7fbeb0 100644 --- a/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js +++ b/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<2b9624673a7fd8b4f820921ba0a088d6>> + * @generated SignedSource<<119985fbbfbc9ace52b26a7fb3b91f03>> * @flow strict * @noformat */ @@ -72,6 +72,7 @@ export interface Spec extends TurboModule { +fuseboxNetworkInspectionEnabled?: () => boolean; +hideOffscreenVirtualViewsOnIOS?: () => boolean; +preparedTextCacheSize?: () => number; + +preventShadowTreeCommitExhaustion?: () => boolean; +releaseImageDataWhenConsumed?: () => boolean; +shouldPressibilityUseW3CPointerEventsForHover?: () => boolean; +skipActivityIdentityAssertionOnHostPause?: () => boolean;