Implement mechanism to prevent ShadowTree commit exhaustion (#52645)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52645

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

fbshipit-source-id: 2792026b6936393d196fd1e3162f8b2c61a38ed6
This commit is contained in:
Rubén Norte
2025-07-16 12:02:07 -07:00
committed by Facebook GitHub Bot
parent d00de31aef
commit 21cd09d4c0
21 changed files with 179 additions and 44 deletions
@@ -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<<a90efac589511beb130c499e51150de8>>
*/
/**
@@ -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.
*/
@@ -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<<b32f66fb09971e786dd1380bbf417720>>
*/
/**
@@ -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) {
@@ -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<<d1da48f826bc6a1793d1630cb89cb5c1>>
*/
/**
@@ -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
@@ -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
@@ -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) {
@@ -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
@@ -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<<f310a5dc27fd655eab4445cb25d4c85c>>
*/
/**
@@ -315,6 +315,12 @@ class ReactNativeFeatureFlagsJavaProvider
return method(javaProvider_);
}
bool preventShadowTreeCommitExhaustionWithLocking() override {
static const auto method =
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("preventShadowTreeCommitExhaustionWithLocking");
return method(javaProvider_);
}
bool releaseImageDataWhenConsumed() override {
static const auto method =
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("releaseImageDataWhenConsumed");
@@ -639,6 +645,11 @@ double JReactNativeFeatureFlagsCxxInterop::preparedTextCacheSize(
return ReactNativeFeatureFlags::preparedTextCacheSize();
}
bool JReactNativeFeatureFlagsCxxInterop::preventShadowTreeCommitExhaustionWithLocking(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
return ReactNativeFeatureFlags::preventShadowTreeCommitExhaustionWithLocking();
}
bool JReactNativeFeatureFlagsCxxInterop::releaseImageDataWhenConsumed(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
return ReactNativeFeatureFlags::releaseImageDataWhenConsumed();
@@ -883,6 +894,9 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() {
makeNativeMethod(
"preparedTextCacheSize",
JReactNativeFeatureFlagsCxxInterop::preparedTextCacheSize),
makeNativeMethod(
"preventShadowTreeCommitExhaustionWithLocking",
JReactNativeFeatureFlagsCxxInterop::preventShadowTreeCommitExhaustionWithLocking),
makeNativeMethod(
"releaseImageDataWhenConsumed",
JReactNativeFeatureFlagsCxxInterop::releaseImageDataWhenConsumed),
@@ -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<JReactNativeFeatureFlagsCxxInterop>);
static bool preventShadowTreeCommitExhaustionWithLocking(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
static bool releaseImageDataWhenConsumed(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
@@ -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();
}
@@ -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<<c584290ce61ee78bdf843c19dd40c40d>>
*/
/**
@@ -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.
*/
@@ -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<<dc5c6b3273ef312426c4ac366cc13772>>
*/
/**
@@ -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;
@@ -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<<edfd880ca5034a683053dc95e177a7c2>>
*/
/**
@@ -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<ReactNativeFeatureFlagsProvider> currentProvider_;
bool wasOverridden_;
std::array<std::atomic<const char*>, 61> accessedFeatureFlags_;
std::array<std::atomic<const char*>, 62> accessedFeatureFlags_;
std::atomic<std::optional<bool>> commonTestFlag_;
std::atomic<std::optional<bool>> cxxNativeAnimatedEnabled_;
@@ -152,6 +153,7 @@ class ReactNativeFeatureFlagsAccessor {
std::atomic<std::optional<bool>> fuseboxNetworkInspectionEnabled_;
std::atomic<std::optional<bool>> hideOffscreenVirtualViewsOnIOS_;
std::atomic<std::optional<double>> preparedTextCacheSize_;
std::atomic<std::optional<bool>> preventShadowTreeCommitExhaustionWithLocking_;
std::atomic<std::optional<bool>> releaseImageDataWhenConsumed_;
std::atomic<std::optional<bool>> skipActivityIdentityAssertionOnHostPause_;
std::atomic<std::optional<bool>> traceTurboModulePromiseRejectionsOnAndroid_;
@@ -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<<efd96785ae3ffd19a4730e5f3eef76b2>>
* @generated SignedSource<<d7534fe6e3b398e56c179da8233e3b0a>>
*/
/**
@@ -211,6 +211,10 @@ class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider {
return 200.0;
}
bool preventShadowTreeCommitExhaustionWithLocking() override {
return false;
}
bool releaseImageDataWhenConsumed() override {
return false;
}
@@ -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<<d8f58c2014535394a14199a38313c1d5>>
* @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()) {
@@ -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<<a26af781141939d35cff83bacf522bb0>>
*/
/**
@@ -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;
@@ -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<<b553a8bf75d7047328c26c4b9029d509>>
* @generated SignedSource<<cadb1f2c1c10a03593dc7c9a9a94747b>>
*/
/**
@@ -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();
@@ -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<<d2506af091e796331f473dbb38aae518>>
* @generated SignedSource<<de04d72490f2c09abdcea51e6f02796c>>
*/
/**
@@ -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);
@@ -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++;
auto status = tryCommit(transaction, commitOptions);
if (status != CommitStatus::Failed) {
return status;
if (ReactNativeFeatureFlags::preventShadowTreeCommitExhaustionWithLocking()) {
while (attempts < MAX_COMMIT_ATTEMPTS_BEFORE_LOCKING) {
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);
{
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);
}
}
}
@@ -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: {
@@ -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<<d30fc4e36ad600353c9b9098fd71430a>>
* @flow strict
* @noformat
*/
@@ -97,6 +97,7 @@ export type ReactNativeFeatureFlags = $ReadOnly<{
fuseboxNetworkInspectionEnabled: Getter<boolean>,
hideOffscreenVirtualViewsOnIOS: Getter<boolean>,
preparedTextCacheSize: Getter<number>,
preventShadowTreeCommitExhaustionWithLocking: Getter<boolean>,
releaseImageDataWhenConsumed: Getter<boolean>,
skipActivityIdentityAssertionOnHostPause: Getter<boolean>,
traceTurboModulePromiseRejectionsOnAndroid: Getter<boolean>,
@@ -382,6 +383,10 @@ export const hideOffscreenVirtualViewsOnIOS: Getter<boolean> = createNativeFlagG
* Number cached PreparedLayouts in TextLayoutManager cache
*/
export const preparedTextCacheSize: Getter<number> = 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<boolean> = createNativeFlagGetter('preventShadowTreeCommitExhaustionWithLocking', false);
/**
* Releases the cached image data when it is consumed by the observers.
*/
@@ -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<<f0cb1821d25df02c6026a815eac4c076>>
* @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;