From 122cf8bfbbf48f2d6efdb99df11005b1392cf3cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Norte?= Date: Wed, 16 Jul 2025 09:45:02 -0700 Subject: [PATCH] Implement mechanism to prevent ShadowTree commit exhaustion Summary: Changelog: [internal] This add a new feature flag to test a fix for https://github.com/facebook/react-native/issues/51870 Reviewed By: cortinico, sammy-SC Differential Revision: D78418504 --- .../featureflags/ReactNativeFeatureFlags.kt | 8 ++- .../ReactNativeFeatureFlagsCxxAccessor.kt | 12 ++++- .../ReactNativeFeatureFlagsCxxInterop.kt | 4 +- .../ReactNativeFeatureFlagsDefaults.kt | 4 +- .../ReactNativeFeatureFlagsLocalAccessor.kt | 13 ++++- .../ReactNativeFeatureFlagsProvider.kt | 4 +- .../JReactNativeFeatureFlagsCxxInterop.cpp | 16 +++++- .../JReactNativeFeatureFlagsCxxInterop.h | 5 +- .../featureflags/ReactNativeFeatureFlags.cpp | 6 ++- .../featureflags/ReactNativeFeatureFlags.h | 7 ++- .../ReactNativeFeatureFlagsAccessor.cpp | 50 +++++++++++++------ .../ReactNativeFeatureFlagsAccessor.h | 6 ++- .../ReactNativeFeatureFlagsDefaults.h | 6 ++- .../ReactNativeFeatureFlagsDynamicProvider.h | 11 +++- .../ReactNativeFeatureFlagsProvider.h | 3 +- .../NativeReactNativeFeatureFlags.cpp | 7 ++- .../NativeReactNativeFeatureFlags.h | 4 +- .../react/renderer/mounting/ShadowTree.cpp | 34 ++++++++++--- .../ReactNativeFeatureFlags.config.js | 11 ++++ .../featureflags/ReactNativeFeatureFlags.js | 7 ++- .../specs/NativeReactNativeFeatureFlags.js | 3 +- 21 files changed, 178 insertions(+), 43 deletions(-) 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 796e8735f189b6..7f3d123ba5156a 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<<3212fc0e52bffab8e56359d207a22357>> + * @generated SignedSource<> */ /** @@ -306,6 +306,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 preventShadowTreeCommitExhaustionWithLocking(): Boolean = accessor.preventShadowTreeCommitExhaustionWithLocking() + /** * 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 9eb164d5cea2fa..6c26467ce68175 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<<889f905c29a3558eb172782cccd521c4>> + * @generated SignedSource<> */ /** @@ -66,6 +66,7 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces private var fuseboxNetworkInspectionEnabledCache: Boolean? = null private var hideOffscreenVirtualViewsOnIOSCache: Boolean? = null private var preparedTextCacheSizeCache: Double? = null + private var preventShadowTreeCommitExhaustionWithLockingCache: Boolean? = null private var releaseImageDataWhenConsumedCache: Boolean? = null private var skipActivityIdentityAssertionOnHostPauseCache: Boolean? = null private var traceTurboModulePromiseRejectionsOnAndroidCache: Boolean? = null @@ -496,6 +497,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces return cached } + override fun preventShadowTreeCommitExhaustionWithLocking(): Boolean { + var cached = preventShadowTreeCommitExhaustionWithLockingCache + if (cached == null) { + cached = ReactNativeFeatureFlagsCxxInterop.preventShadowTreeCommitExhaustionWithLocking() + preventShadowTreeCommitExhaustionWithLockingCache = 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 d49b27b5534502..be7cbb2a3ae137 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<<69a932d66b50ac8c059b0dab68313616>> + * @generated SignedSource<> */ /** @@ -120,6 +120,8 @@ public object ReactNativeFeatureFlagsCxxInterop { @DoNotStrip @JvmStatic public external fun preparedTextCacheSize(): Double + @DoNotStrip @JvmStatic public external fun preventShadowTreeCommitExhaustionWithLocking(): Boolean + @DoNotStrip @JvmStatic public external fun releaseImageDataWhenConsumed(): Boolean @DoNotStrip @JvmStatic public external fun skipActivityIdentityAssertionOnHostPause(): 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 35cfc5c48348bd..b114b19fca255e 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<<90dadccf847e0f5fc24d3b1090bba82f>> + * @generated SignedSource<<99019349a8f3642c42e6dac5f1476e7f>> */ /** @@ -115,6 +115,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi override fun preparedTextCacheSize(): Double = 200.0 + override fun preventShadowTreeCommitExhaustionWithLocking(): Boolean = false + override fun releaseImageDataWhenConsumed(): Boolean = false override fun skipActivityIdentityAssertionOnHostPause(): 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 2f9ed61b607b55..c3a78715307115 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<<8663dc63e60ec71e7bf1d5645c39c77f>> + * @generated SignedSource<<23605f090bfbebe911caa9d3d834d3e8>> */ /** @@ -70,6 +70,7 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc private var fuseboxNetworkInspectionEnabledCache: Boolean? = null private var hideOffscreenVirtualViewsOnIOSCache: Boolean? = null private var preparedTextCacheSizeCache: Double? = null + private var preventShadowTreeCommitExhaustionWithLockingCache: Boolean? = null private var releaseImageDataWhenConsumedCache: Boolean? = null private var skipActivityIdentityAssertionOnHostPauseCache: Boolean? = null private var traceTurboModulePromiseRejectionsOnAndroidCache: Boolean? = null @@ -546,6 +547,16 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc return cached } + override fun preventShadowTreeCommitExhaustionWithLocking(): Boolean { + var cached = preventShadowTreeCommitExhaustionWithLockingCache + if (cached == null) { + cached = currentProvider.preventShadowTreeCommitExhaustionWithLocking() + accessedFeatureFlags.add("preventShadowTreeCommitExhaustionWithLocking") + preventShadowTreeCommitExhaustionWithLockingCache = 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/ReactNativeFeatureFlagsProvider.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt index 83c0265be7e037..626f532a496656 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<<07caa03ff4d6e1e624a9a71d5aeceebe>> + * @generated SignedSource<<780793412b76f101be1569d7a866c435>> */ /** @@ -115,6 +115,8 @@ public interface ReactNativeFeatureFlagsProvider { @DoNotStrip public fun preparedTextCacheSize(): Double + @DoNotStrip public fun preventShadowTreeCommitExhaustionWithLocking(): Boolean + @DoNotStrip public fun releaseImageDataWhenConsumed(): Boolean @DoNotStrip public fun skipActivityIdentityAssertionOnHostPause(): 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 0c8ae40ce11c1f..c6b7230a902c2b 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<<8121b9fc632bb1db16a1554ea5dd0b87>> + * @generated SignedSource<> */ /** @@ -315,6 +315,12 @@ class ReactNativeFeatureFlagsJavaProvider return method(javaProvider_); } + bool preventShadowTreeCommitExhaustionWithLocking() override { + static const auto method = + getReactNativeFeatureFlagsProviderJavaClass()->getMethod("preventShadowTreeCommitExhaustionWithLocking"); + return method(javaProvider_); + } + bool releaseImageDataWhenConsumed() override { static const auto method = getReactNativeFeatureFlagsProviderJavaClass()->getMethod("releaseImageDataWhenConsumed"); @@ -639,6 +645,11 @@ double JReactNativeFeatureFlagsCxxInterop::preparedTextCacheSize( return ReactNativeFeatureFlags::preparedTextCacheSize(); } +bool JReactNativeFeatureFlagsCxxInterop::preventShadowTreeCommitExhaustionWithLocking( + facebook::jni::alias_ref /*unused*/) { + return ReactNativeFeatureFlags::preventShadowTreeCommitExhaustionWithLocking(); +} + bool JReactNativeFeatureFlagsCxxInterop::releaseImageDataWhenConsumed( facebook::jni::alias_ref /*unused*/) { return ReactNativeFeatureFlags::releaseImageDataWhenConsumed(); @@ -883,6 +894,9 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() { makeNativeMethod( "preparedTextCacheSize", JReactNativeFeatureFlagsCxxInterop::preparedTextCacheSize), + makeNativeMethod( + "preventShadowTreeCommitExhaustionWithLocking", + JReactNativeFeatureFlagsCxxInterop::preventShadowTreeCommitExhaustionWithLocking), 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 e14fbdc8a4b4ad..40d1f051cb1203 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<<1cfdc8abd4434d8b40d685b9beb79e3c>> + * @generated SignedSource<<8c1da07c0b7d2053f7fdaac4326c3ac1>> */ /** @@ -168,6 +168,9 @@ class JReactNativeFeatureFlagsCxxInterop static double preparedTextCacheSize( facebook::jni::alias_ref); + static bool preventShadowTreeCommitExhaustionWithLocking( + 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 2c3d5e3f57635d..42b02f369b290d 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<<3e49ef5b83d57ba597a2675693046ec2>> + * @generated SignedSource<<355265aa8f13e3f307d6e30db6b80d41>> */ /** @@ -210,6 +210,10 @@ double ReactNativeFeatureFlags::preparedTextCacheSize() { return getAccessor().preparedTextCacheSize(); } +bool ReactNativeFeatureFlags::preventShadowTreeCommitExhaustionWithLocking() { + return getAccessor().preventShadowTreeCommitExhaustionWithLocking(); +} + 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 2e7bdb156ba371..a9c4098a9a31d8 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<<191db129f17bcf82d2d133484124867b>> + * @generated SignedSource<> */ /** @@ -269,6 +269,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 preventShadowTreeCommitExhaustionWithLocking(); + /** * 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 6d60580b07160a..bd652a756da86f 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<<26cc2b59099e4eb6bc97b0ea048d2e74>> + * @generated SignedSource<> */ /** @@ -857,6 +857,24 @@ double ReactNativeFeatureFlagsAccessor::preparedTextCacheSize() { return flagValue.value(); } +bool ReactNativeFeatureFlagsAccessor::preventShadowTreeCommitExhaustionWithLocking() { + auto flagValue = preventShadowTreeCommitExhaustionWithLocking_.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(46, "preventShadowTreeCommitExhaustionWithLocking"); + + flagValue = currentProvider_->preventShadowTreeCommitExhaustionWithLocking(); + preventShadowTreeCommitExhaustionWithLocking_ = flagValue; + } + + return flagValue.value(); +} + bool ReactNativeFeatureFlagsAccessor::releaseImageDataWhenConsumed() { auto flagValue = releaseImageDataWhenConsumed_.load(); @@ -866,7 +884,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(46, "releaseImageDataWhenConsumed"); + markFlagAsAccessed(47, "releaseImageDataWhenConsumed"); flagValue = currentProvider_->releaseImageDataWhenConsumed(); releaseImageDataWhenConsumed_ = flagValue; @@ -884,7 +902,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(47, "skipActivityIdentityAssertionOnHostPause"); + markFlagAsAccessed(48, "skipActivityIdentityAssertionOnHostPause"); flagValue = currentProvider_->skipActivityIdentityAssertionOnHostPause(); skipActivityIdentityAssertionOnHostPause_ = flagValue; @@ -902,7 +920,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(48, "traceTurboModulePromiseRejectionsOnAndroid"); + markFlagAsAccessed(49, "traceTurboModulePromiseRejectionsOnAndroid"); flagValue = currentProvider_->traceTurboModulePromiseRejectionsOnAndroid(); traceTurboModulePromiseRejectionsOnAndroid_ = flagValue; @@ -920,7 +938,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(49, "updateRuntimeShadowNodeReferencesOnCommit"); + markFlagAsAccessed(50, "updateRuntimeShadowNodeReferencesOnCommit"); flagValue = currentProvider_->updateRuntimeShadowNodeReferencesOnCommit(); updateRuntimeShadowNodeReferencesOnCommit_ = flagValue; @@ -938,7 +956,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(50, "useAlwaysAvailableJSErrorHandling"); + markFlagAsAccessed(51, "useAlwaysAvailableJSErrorHandling"); flagValue = currentProvider_->useAlwaysAvailableJSErrorHandling(); useAlwaysAvailableJSErrorHandling_ = flagValue; @@ -956,7 +974,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(51, "useFabricInterop"); + markFlagAsAccessed(52, "useFabricInterop"); flagValue = currentProvider_->useFabricInterop(); useFabricInterop_ = flagValue; @@ -974,7 +992,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(52, "useNativeEqualsInNativeReadableArrayAndroid"); + markFlagAsAccessed(53, "useNativeEqualsInNativeReadableArrayAndroid"); flagValue = currentProvider_->useNativeEqualsInNativeReadableArrayAndroid(); useNativeEqualsInNativeReadableArrayAndroid_ = flagValue; @@ -992,7 +1010,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(53, "useNativeTransformHelperAndroid"); + markFlagAsAccessed(54, "useNativeTransformHelperAndroid"); flagValue = currentProvider_->useNativeTransformHelperAndroid(); useNativeTransformHelperAndroid_ = flagValue; @@ -1010,7 +1028,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(54, "useNativeViewConfigsInBridgelessMode"); + markFlagAsAccessed(55, "useNativeViewConfigsInBridgelessMode"); flagValue = currentProvider_->useNativeViewConfigsInBridgelessMode(); useNativeViewConfigsInBridgelessMode_ = flagValue; @@ -1028,7 +1046,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(55, "useOptimizedEventBatchingOnAndroid"); + markFlagAsAccessed(56, "useOptimizedEventBatchingOnAndroid"); flagValue = currentProvider_->useOptimizedEventBatchingOnAndroid(); useOptimizedEventBatchingOnAndroid_ = flagValue; @@ -1046,7 +1064,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(56, "useRawPropsJsiValue"); + markFlagAsAccessed(57, "useRawPropsJsiValue"); flagValue = currentProvider_->useRawPropsJsiValue(); useRawPropsJsiValue_ = flagValue; @@ -1064,7 +1082,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(57, "useShadowNodeStateOnClone"); + markFlagAsAccessed(58, "useShadowNodeStateOnClone"); flagValue = currentProvider_->useShadowNodeStateOnClone(); useShadowNodeStateOnClone_ = flagValue; @@ -1082,7 +1100,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(58, "useTurboModuleInterop"); + markFlagAsAccessed(59, "useTurboModuleInterop"); flagValue = currentProvider_->useTurboModuleInterop(); useTurboModuleInterop_ = flagValue; @@ -1100,7 +1118,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(59, "useTurboModules"); + markFlagAsAccessed(60, "useTurboModules"); flagValue = currentProvider_->useTurboModules(); useTurboModules_ = flagValue; @@ -1118,7 +1136,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(60, "virtualViewPrerenderRatio"); + markFlagAsAccessed(61, "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 8c9190bbbef2ca..fb5de307bf717c 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<<822091facf02e7a8e4d34e62881a1a7b>> + * @generated SignedSource<> */ /** @@ -78,6 +78,7 @@ class ReactNativeFeatureFlagsAccessor { bool fuseboxNetworkInspectionEnabled(); bool hideOffscreenVirtualViewsOnIOS(); double preparedTextCacheSize(); + bool preventShadowTreeCommitExhaustionWithLocking(); bool releaseImageDataWhenConsumed(); bool skipActivityIdentityAssertionOnHostPause(); bool traceTurboModulePromiseRejectionsOnAndroid(); @@ -104,7 +105,7 @@ class ReactNativeFeatureFlagsAccessor { std::unique_ptr currentProvider_; bool wasOverridden_; - std::array, 61> accessedFeatureFlags_; + std::array, 62> accessedFeatureFlags_; std::atomic> commonTestFlag_; std::atomic> cxxNativeAnimatedEnabled_; @@ -152,6 +153,7 @@ class ReactNativeFeatureFlagsAccessor { std::atomic> fuseboxNetworkInspectionEnabled_; std::atomic> hideOffscreenVirtualViewsOnIOS_; std::atomic> preparedTextCacheSize_; + std::atomic> preventShadowTreeCommitExhaustionWithLocking_; std::atomic> releaseImageDataWhenConsumed_; std::atomic> skipActivityIdentityAssertionOnHostPause_; std::atomic> traceTurboModulePromiseRejectionsOnAndroid_; diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h index 04fc65f3cd1fa4..02bc59418b3f47 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<> + * @generated SignedSource<> */ /** @@ -211,6 +211,10 @@ class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider { return 200.0; } + bool preventShadowTreeCommitExhaustionWithLocking() 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 62d109348fb4f8..7e3a4b7acfbd00 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<> + * @generated SignedSource<<6502c090532e1d7d2ac67cf6e711d9f4>> */ /** @@ -459,6 +459,15 @@ class ReactNativeFeatureFlagsDynamicProvider : public ReactNativeFeatureFlagsDef return ReactNativeFeatureFlagsDefaults::preparedTextCacheSize(); } + bool preventShadowTreeCommitExhaustionWithLocking() override { + auto value = values_["preventShadowTreeCommitExhaustionWithLocking"]; + if (!value.isNull()) { + return value.getBool(); + } + + return ReactNativeFeatureFlagsDefaults::preventShadowTreeCommitExhaustionWithLocking(); + } + bool releaseImageDataWhenConsumed() override { auto value = values_["releaseImageDataWhenConsumed"]; if (!value.isNull()) { diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h index adf0a9b1d201bc..a17afc70ea90b4 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<<094495d25071a77cbe2ee91d13b0415d>> + * @generated SignedSource<> */ /** @@ -71,6 +71,7 @@ class ReactNativeFeatureFlagsProvider { virtual bool fuseboxNetworkInspectionEnabled() = 0; virtual bool hideOffscreenVirtualViewsOnIOS() = 0; virtual double preparedTextCacheSize() = 0; + virtual bool preventShadowTreeCommitExhaustionWithLocking() = 0; virtual bool releaseImageDataWhenConsumed() = 0; virtual bool skipActivityIdentityAssertionOnHostPause() = 0; virtual bool traceTurboModulePromiseRejectionsOnAndroid() = 0; diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp index 5c9cefa2dbb852..baf92be1b71497 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<> */ /** @@ -274,6 +274,11 @@ double NativeReactNativeFeatureFlags::preparedTextCacheSize( return ReactNativeFeatureFlags::preparedTextCacheSize(); } +bool NativeReactNativeFeatureFlags::preventShadowTreeCommitExhaustionWithLocking( + jsi::Runtime& /*runtime*/) { + return ReactNativeFeatureFlags::preventShadowTreeCommitExhaustionWithLocking(); +} + 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 176d2f175f32a7..23df7ab93f0d9f 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<> */ /** @@ -128,6 +128,8 @@ class NativeReactNativeFeatureFlags double preparedTextCacheSize(jsi::Runtime& runtime); + bool preventShadowTreeCommitExhaustionWithLocking(jsi::Runtime& runtime); + bool releaseImageDataWhenConsumed(jsi::Runtime& runtime); bool skipActivityIdentityAssertionOnHostPause(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 e91e643ccd22ec..079b55c171f2cb 100644 --- a/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTree.cpp +++ b/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTree.cpp @@ -25,6 +25,10 @@ namespace facebook::react { using CommitStatus = ShadowTree::CommitStatus; using CommitMode = ShadowTree::CommitMode; +namespace { +const int MAX_COMMIT_ATTEMPTS_BEFORE_LOCKING = 3; +} + /* * Generates (possibly) a new tree where all nodes with non-obsolete `State` * objects. If all `State` objects in the tree are not obsolete for the moment @@ -241,17 +245,31 @@ CommitStatus ShadowTree::commit( const CommitOptions& commitOptions) const { [[maybe_unused]] int attempts = 0; - while (true) { - attempts++; + if (ReactNativeFeatureFlags::preventShadowTreeCommitExhaustionWithLocking()) { + while (attempts < MAX_COMMIT_ATTEMPTS_BEFORE_LOCKING) { + auto status = tryCommit(transaction, commitOptions); + if (status != CommitStatus::Failed) { + return status; + } + } - auto status = tryCommit(transaction, commitOptions); - if (status != CommitStatus::Failed) { - return status; + { + std::unique_lock lock(commitMutex_); + 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); + // After multiple attempts, we failed to commit the transaction. + // Something internally went terribly wrong. + react_native_assert(attempts < 1024); + } } } diff --git a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js index 9cabe0015fcc83..03fa8cb884e90b 100644 --- a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js +++ b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js @@ -534,6 +534,17 @@ const definitions: FeatureFlagDefinitions = { }, ossReleaseStage: 'none', }, + preventShadowTreeCommitExhaustionWithLocking: { + defaultValue: false, + metadata: { + dateAdded: '2025-07-14', + 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: 'none', + }, 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 b13f1b3d83a321..7e652e4a318512 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<<9a7737303df151e91a4a6fdbec353e76>> + * @generated SignedSource<> * @flow strict * @noformat */ @@ -97,6 +97,7 @@ export type ReactNativeFeatureFlags = $ReadOnly<{ fuseboxNetworkInspectionEnabled: Getter, hideOffscreenVirtualViewsOnIOS: Getter, preparedTextCacheSize: Getter, + preventShadowTreeCommitExhaustionWithLocking: Getter, releaseImageDataWhenConsumed: Getter, skipActivityIdentityAssertionOnHostPause: Getter, traceTurboModulePromiseRejectionsOnAndroid: Getter, @@ -382,6 +383,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 preventShadowTreeCommitExhaustionWithLocking: Getter = createNativeFlagGetter('preventShadowTreeCommitExhaustionWithLocking', 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 f8d9a4276bb556..9a95c16f32a337 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<> + * @generated SignedSource<<45c98e5f4b5f3d3a8a0ded4c5148b549>> * @flow strict * @noformat */ @@ -71,6 +71,7 @@ export interface Spec extends TurboModule { +fuseboxNetworkInspectionEnabled?: () => boolean; +hideOffscreenVirtualViewsOnIOS?: () => boolean; +preparedTextCacheSize?: () => number; + +preventShadowTreeCommitExhaustionWithLocking?: () => boolean; +releaseImageDataWhenConsumed?: () => boolean; +skipActivityIdentityAssertionOnHostPause?: () => boolean; +traceTurboModulePromiseRejectionsOnAndroid?: () => boolean;