[0.81] Clean up feature flag preventShadowTreeCommitExhaustionWithLocking (#52862)

* Clean up feature flag preventShadowTreeCommitExhaustionWithLocking (#52791)

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

Changelog: [internal]

This cleans up this feature flag as it doesn't work as intended. We'll try another approach with a different flag instead.

Reviewed By: sammy-SC

Differential Revision: D78815892

fbshipit-source-id: 4c651a3a225de9cfb54d00346343c7f2e3bea1d5

* Implement solution for ShadowTree commmit exhaustion using recursive locks (behind a flag) (#52795)

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

Changelog: [internal]

This is another attempt to fix https://github.com/facebook/react-native/issues/51870, inspired by https://github.com/facebook/react-native/pull/52314 but gated behind a feature flag until we've tested it carefully.

Reviewed By: sammy-SC

Differential Revision: D78817100

fbshipit-source-id: 45e6cae019b212528f2b2e74b9f52fe43d07f537

* [LOCAL] Correctly sort preventShadowTreeCommitExhaustion after merge conflict

---------

Co-authored-by: Rubén Norte <rubennorte@meta.com>
This commit is contained in:
Nicola Corti
2025-07-28 14:38:54 +01:00
committed by GitHub
co-authored by Rubén Norte
parent 5f3d297eec
commit cb20a1cd32
24 changed files with 112 additions and 90 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<<f5f8c15a68610c9453d4085626effee2>>
* @generated SignedSource<<3e4d74a17c15742d35db9e4247f3e1c1>>
*/
/**
@@ -316,7 +316,7 @@ public object ReactNativeFeatureFlags {
* 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()
public fun preventShadowTreeCommitExhaustion(): Boolean = accessor.preventShadowTreeCommitExhaustion()
/**
* Enables storing js caller stack when creating promise in native module. This is useful in case of Promise rejection and tracing the cause.
@@ -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<<773ddcede573164ba82db671341ddc3f>>
* @generated SignedSource<<e7c1c6d184681d98320aac2a23c06288>>
*/
/**
@@ -67,7 +67,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 preventShadowTreeCommitExhaustionCache: Boolean? = null
private var traceTurboModulePromiseRejectionsOnAndroidCache: Boolean? = null
private var updateRuntimeShadowNodeReferencesOnCommitCache: Boolean? = null
private var useAlwaysAvailableJSErrorHandlingCache: Boolean? = null
@@ -503,11 +503,11 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
return cached
}
override fun preventShadowTreeCommitExhaustionWithLocking(): Boolean {
var cached = preventShadowTreeCommitExhaustionWithLockingCache
override fun preventShadowTreeCommitExhaustion(): Boolean {
var cached = preventShadowTreeCommitExhaustionCache
if (cached == null) {
cached = ReactNativeFeatureFlagsCxxInterop.preventShadowTreeCommitExhaustionWithLocking()
preventShadowTreeCommitExhaustionWithLockingCache = cached
cached = ReactNativeFeatureFlagsCxxInterop.preventShadowTreeCommitExhaustion()
preventShadowTreeCommitExhaustionCache = cached
}
return cached
}
@@ -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<<96fca46813d841eb7f4d043010513999>>
* @generated SignedSource<<ba62d616188ed439c85c66cfd055810d>>
*/
/**
@@ -122,7 +122,7 @@ public object ReactNativeFeatureFlagsCxxInterop {
@DoNotStrip @JvmStatic public external fun preparedTextCacheSize(): Double
@DoNotStrip @JvmStatic public external fun preventShadowTreeCommitExhaustionWithLocking(): Boolean
@DoNotStrip @JvmStatic public external fun preventShadowTreeCommitExhaustion(): Boolean
@DoNotStrip @JvmStatic public external fun traceTurboModulePromiseRejectionsOnAndroid(): 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<<8ebd61411e0e0ac8c8b307cf803f1206>>
* @generated SignedSource<<12c2727291b635ef7c3163d153669c2c>>
*/
/**
@@ -117,7 +117,7 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi
override fun preparedTextCacheSize(): Double = 200.0
override fun preventShadowTreeCommitExhaustionWithLocking(): Boolean = false
override fun preventShadowTreeCommitExhaustion(): Boolean = false
override fun traceTurboModulePromiseRejectionsOnAndroid(): 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<<a7e62fa950e2716e664e7f6d30d4c941>>
* @generated SignedSource<<3ea9946ef21c8ac8bb9bb63712636e89>>
*/
/**
@@ -71,7 +71,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 preventShadowTreeCommitExhaustionCache: Boolean? = null
private var traceTurboModulePromiseRejectionsOnAndroidCache: Boolean? = null
private var updateRuntimeShadowNodeReferencesOnCommitCache: Boolean? = null
private var useAlwaysAvailableJSErrorHandlingCache: Boolean? = null
@@ -554,12 +554,12 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
return cached
}
override fun preventShadowTreeCommitExhaustionWithLocking(): Boolean {
var cached = preventShadowTreeCommitExhaustionWithLockingCache
override fun preventShadowTreeCommitExhaustion(): Boolean {
var cached = preventShadowTreeCommitExhaustionCache
if (cached == null) {
cached = currentProvider.preventShadowTreeCommitExhaustionWithLocking()
accessedFeatureFlags.add("preventShadowTreeCommitExhaustionWithLocking")
preventShadowTreeCommitExhaustionWithLockingCache = cached
cached = currentProvider.preventShadowTreeCommitExhaustion()
accessedFeatureFlags.add("preventShadowTreeCommitExhaustion")
preventShadowTreeCommitExhaustionCache = cached
}
return cached
}
@@ -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<<93aab733661b558c1701b728c18b3d00>>
* @generated SignedSource<<58da46268043f086730132430735b720>>
*/
/**
@@ -23,5 +23,5 @@ 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 preventShadowTreeCommitExhaustionWithLocking(): Boolean = true
override fun preventShadowTreeCommitExhaustion(): Boolean = true
}
@@ -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<<f3b9aa1ed32aa3e013e16d4abb10b9cf>>
* @generated SignedSource<<1e81de36735c6c9286b228c75c9a0228>>
*/
/**
@@ -117,7 +117,7 @@ public interface ReactNativeFeatureFlagsProvider {
@DoNotStrip public fun preparedTextCacheSize(): Double
@DoNotStrip public fun preventShadowTreeCommitExhaustionWithLocking(): Boolean
@DoNotStrip public fun preventShadowTreeCommitExhaustion(): Boolean
@DoNotStrip public fun traceTurboModulePromiseRejectionsOnAndroid(): 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<<7fac1c2c0c3ce131442319925e4231dc>>
* @generated SignedSource<<cf7b6ff66c614ca2acc6667a80c5590d>>
*/
/**
@@ -321,9 +321,9 @@ class ReactNativeFeatureFlagsJavaProvider
return method(javaProvider_);
}
bool preventShadowTreeCommitExhaustionWithLocking() override {
bool preventShadowTreeCommitExhaustion() override {
static const auto method =
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("preventShadowTreeCommitExhaustionWithLocking");
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("preventShadowTreeCommitExhaustion");
return method(javaProvider_);
}
@@ -632,9 +632,9 @@ double JReactNativeFeatureFlagsCxxInterop::preparedTextCacheSize(
return ReactNativeFeatureFlags::preparedTextCacheSize();
}
bool JReactNativeFeatureFlagsCxxInterop::preventShadowTreeCommitExhaustionWithLocking(
bool JReactNativeFeatureFlagsCxxInterop::preventShadowTreeCommitExhaustion(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
return ReactNativeFeatureFlags::preventShadowTreeCommitExhaustionWithLocking();
return ReactNativeFeatureFlags::preventShadowTreeCommitExhaustion();
}
bool JReactNativeFeatureFlagsCxxInterop::traceTurboModulePromiseRejectionsOnAndroid(
@@ -865,8 +865,8 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() {
"preparedTextCacheSize",
JReactNativeFeatureFlagsCxxInterop::preparedTextCacheSize),
makeNativeMethod(
"preventShadowTreeCommitExhaustionWithLocking",
JReactNativeFeatureFlagsCxxInterop::preventShadowTreeCommitExhaustionWithLocking),
"preventShadowTreeCommitExhaustion",
JReactNativeFeatureFlagsCxxInterop::preventShadowTreeCommitExhaustion),
makeNativeMethod(
"traceTurboModulePromiseRejectionsOnAndroid",
JReactNativeFeatureFlagsCxxInterop::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<<07daae0284829d56b7eaa330b1973e02>>
* @generated SignedSource<<dae981c66bf0751fd2863937ecf255d8>>
*/
/**
@@ -171,7 +171,7 @@ class JReactNativeFeatureFlagsCxxInterop
static double preparedTextCacheSize(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
static bool preventShadowTreeCommitExhaustionWithLocking(
static bool preventShadowTreeCommitExhaustion(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
static 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<<0179ba45718903d6fec6dcc19b0e1aaa>>
* @generated SignedSource<<f73bbcd926a835c09b70d814c6662dbb>>
*/
/**
@@ -214,8 +214,8 @@ double ReactNativeFeatureFlags::preparedTextCacheSize() {
return getAccessor().preparedTextCacheSize();
}
bool ReactNativeFeatureFlags::preventShadowTreeCommitExhaustionWithLocking() {
return getAccessor().preventShadowTreeCommitExhaustionWithLocking();
bool ReactNativeFeatureFlags::preventShadowTreeCommitExhaustion() {
return getAccessor().preventShadowTreeCommitExhaustion();
}
bool ReactNativeFeatureFlags::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<<5055890d2cb2fb46a940f8308f014f0b>>
* @generated SignedSource<<da14545268455bfd4cd35e5c2ecf81ee>>
*/
/**
@@ -277,7 +277,7 @@ class ReactNativeFeatureFlags {
/**
* 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();
RN_EXPORT static bool preventShadowTreeCommitExhaustion();
/**
* Enables storing js caller stack when creating promise in native module. This is useful in case of Promise rejection and tracing the cause.
@@ -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<<ea72f787f16b0c96ad745e82067591a9>>
* @generated SignedSource<<681bff71eb87886a108f67b3162b030c>>
*/
/**
@@ -875,8 +875,8 @@ double ReactNativeFeatureFlagsAccessor::preparedTextCacheSize() {
return flagValue.value();
}
bool ReactNativeFeatureFlagsAccessor::preventShadowTreeCommitExhaustionWithLocking() {
auto flagValue = preventShadowTreeCommitExhaustionWithLocking_.load();
bool ReactNativeFeatureFlagsAccessor::preventShadowTreeCommitExhaustion() {
auto flagValue = preventShadowTreeCommitExhaustion_.load();
if (!flagValue.has_value()) {
// This block is not exclusive but it is not necessary.
@@ -884,10 +884,10 @@ bool ReactNativeFeatureFlagsAccessor::preventShadowTreeCommitExhaustionWithLocki
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(47, "preventShadowTreeCommitExhaustionWithLocking");
markFlagAsAccessed(47, "preventShadowTreeCommitExhaustion");
flagValue = currentProvider_->preventShadowTreeCommitExhaustionWithLocking();
preventShadowTreeCommitExhaustionWithLocking_ = flagValue;
flagValue = currentProvider_->preventShadowTreeCommitExhaustion();
preventShadowTreeCommitExhaustion_ = flagValue;
}
return flagValue.value();
@@ -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<<5ed90d5ed1a03a16f551cd1cf6cbdeb3>>
* @generated SignedSource<<e5a8a196b35c010d92d3f616979891a9>>
*/
/**
@@ -79,7 +79,7 @@ class ReactNativeFeatureFlagsAccessor {
bool fuseboxNetworkInspectionEnabled();
bool hideOffscreenVirtualViewsOnIOS();
double preparedTextCacheSize();
bool preventShadowTreeCommitExhaustionWithLocking();
bool preventShadowTreeCommitExhaustion();
bool traceTurboModulePromiseRejectionsOnAndroid();
bool updateRuntimeShadowNodeReferencesOnCommit();
bool useAlwaysAvailableJSErrorHandling();
@@ -151,7 +151,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>> preventShadowTreeCommitExhaustion_;
std::atomic<std::optional<bool>> traceTurboModulePromiseRejectionsOnAndroid_;
std::atomic<std::optional<bool>> updateRuntimeShadowNodeReferencesOnCommit_;
std::atomic<std::optional<bool>> useAlwaysAvailableJSErrorHandling_;
@@ -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<<d9fdce0b92313eff37d7146d25b80d4a>>
* @generated SignedSource<<9832c18e4c7ccf232b7222e2356f99d9>>
*/
/**
@@ -215,7 +215,7 @@ class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider {
return 200.0;
}
bool preventShadowTreeCommitExhaustionWithLocking() override {
bool preventShadowTreeCommitExhaustion() 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<<ae01d3b37b41ff2ef7fdc26977954b7e>>
* @generated SignedSource<<19f0a48bcfa8f8ffaf634e85301adc7e>>
*/
/**
@@ -468,13 +468,13 @@ class ReactNativeFeatureFlagsDynamicProvider : public ReactNativeFeatureFlagsDef
return ReactNativeFeatureFlagsDefaults::preparedTextCacheSize();
}
bool preventShadowTreeCommitExhaustionWithLocking() override {
auto value = values_["preventShadowTreeCommitExhaustionWithLocking"];
bool preventShadowTreeCommitExhaustion() override {
auto value = values_["preventShadowTreeCommitExhaustion"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::preventShadowTreeCommitExhaustionWithLocking();
return ReactNativeFeatureFlagsDefaults::preventShadowTreeCommitExhaustion();
}
bool traceTurboModulePromiseRejectionsOnAndroid() override {
@@ -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<<16c5fdf431579bbfd454a28c06f28c41>>
* @generated SignedSource<<f33ea0f19a27ec6124d8dbf1a043b4ce>>
*/
/**
@@ -27,7 +27,7 @@ class ReactNativeFeatureFlagsOverridesOSSExperimental : public ReactNativeFeatur
public:
ReactNativeFeatureFlagsOverridesOSSExperimental() = default;
bool preventShadowTreeCommitExhaustionWithLocking() override {
bool preventShadowTreeCommitExhaustion() override {
return true;
}
};
@@ -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<<bf06b42a9dfc43a3bfe4e8e59587ea71>>
* @generated SignedSource<<815769cc8d08e19b2598dd1862ed5060>>
*/
/**
@@ -72,7 +72,7 @@ class ReactNativeFeatureFlagsProvider {
virtual bool fuseboxNetworkInspectionEnabled() = 0;
virtual bool hideOffscreenVirtualViewsOnIOS() = 0;
virtual double preparedTextCacheSize() = 0;
virtual bool preventShadowTreeCommitExhaustionWithLocking() = 0;
virtual bool preventShadowTreeCommitExhaustion() = 0;
virtual bool traceTurboModulePromiseRejectionsOnAndroid() = 0;
virtual bool updateRuntimeShadowNodeReferencesOnCommit() = 0;
virtual bool useAlwaysAvailableJSErrorHandling() = 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<<b6c1cb535484fe2a5ff839ca2a9ece46>>
* @generated SignedSource<<474a64af739969acebb4bb9bb1005168>>
*/
/**
@@ -279,9 +279,9 @@ double NativeReactNativeFeatureFlags::preparedTextCacheSize(
return ReactNativeFeatureFlags::preparedTextCacheSize();
}
bool NativeReactNativeFeatureFlags::preventShadowTreeCommitExhaustionWithLocking(
bool NativeReactNativeFeatureFlags::preventShadowTreeCommitExhaustion(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::preventShadowTreeCommitExhaustionWithLocking();
return ReactNativeFeatureFlags::preventShadowTreeCommitExhaustion();
}
bool NativeReactNativeFeatureFlags::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<<03ab35c55476b69046e67235b66533a0>>
* @generated SignedSource<<bfaa3cc7ab3eeff306337b06b7ae978a>>
*/
/**
@@ -130,7 +130,7 @@ class NativeReactNativeFeatureFlags
double preparedTextCacheSize(jsi::Runtime& runtime);
bool preventShadowTreeCommitExhaustionWithLocking(jsi::Runtime& runtime);
bool preventShadowTreeCommitExhaustion(jsi::Runtime& runtime);
bool traceTurboModulePromiseRejectionsOnAndroid(jsi::Runtime& runtime);
@@ -22,12 +22,12 @@
namespace facebook::react {
using CommitStatus = ShadowTree::CommitStatus;
using CommitMode = ShadowTree::CommitMode;
namespace {
const int MAX_COMMIT_ATTEMPTS_BEFORE_LOCKING = 3;
}
} // namespace
using CommitStatus = ShadowTree::CommitStatus;
using CommitMode = ShadowTree::CommitMode;
/*
* Generates (possibly) a new tree where all nodes with non-obsolete `State`
@@ -214,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;
}
@@ -231,7 +232,7 @@ void ShadowTree::setCommitMode(CommitMode commitMode) const {
}
CommitMode ShadowTree::getCommitMode() const {
std::shared_lock lock(commitMutex_);
SharedLock lock = sharedCommitLock();
return commitMode_;
}
@@ -245,7 +246,7 @@ CommitStatus ShadowTree::commit(
const CommitOptions& commitOptions) const {
[[maybe_unused]] int attempts = 0;
if (ReactNativeFeatureFlags::preventShadowTreeCommitExhaustionWithLocking()) {
if (ReactNativeFeatureFlags::preventShadowTreeCommitExhaustion()) {
while (attempts < MAX_COMMIT_ATTEMPTS_BEFORE_LOCKING) {
auto status = tryCommit(transaction, commitOptions);
if (status != CommitStatus::Failed) {
@@ -255,8 +256,8 @@ CommitStatus ShadowTree::commit(
}
{
std::unique_lock lock(commitMutex_);
return tryCommit(transaction, commitOptions, true);
std::unique_lock lock(commitMutexRecursive_);
return tryCommit(transaction, commitOptions);
}
} else {
while (true) {
@@ -276,8 +277,7 @@ CommitStatus ShadowTree::commit(
CommitStatus ShadowTree::tryCommit(
const ShadowTreeCommitTransaction& transaction,
const CommitOptions& commitOptions,
bool hasLocked) const {
const CommitOptions& commitOptions) const {
TraceSection s("ShadowTree::commit");
auto telemetry = TransactionTelemetry{};
@@ -289,10 +289,7 @@ CommitStatus ShadowTree::tryCommit(
{
// Reading `currentRevision_` in shared manner.
std::shared_lock lock(commitMutex_, std::defer_lock);
if (!hasLocked) {
lock.lock();
}
SharedLock lock = sharedCommitLock();
commitMode = commitMode_;
oldRevision = currentRevision_;
}
@@ -333,10 +330,7 @@ CommitStatus ShadowTree::tryCommit(
{
// Updating `currentRevision_` in unique manner if it hasn't changed.
std::unique_lock lock(commitMutex_, std::defer_lock);
if (!hasLocked) {
lock.lock();
}
UniqueLock lock = uniqueCommitLock();
if (currentRevision_.number != oldRevision.number) {
return CommitStatus::Failed;
@@ -375,7 +369,7 @@ CommitStatus ShadowTree::tryCommit(
}
ShadowTreeRevision ShadowTree::getCurrentRevision() const {
std::shared_lock lock(commitMutex_);
SharedLock lock = sharedCommitLock();
return currentRevision_;
}
@@ -423,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
@@ -8,6 +8,8 @@
#pragma once
#include <memory>
#include <mutex>
#include <shared_mutex>
#include <react/renderer/components/root/RootShadowNode.h>
#include <react/renderer/core/LayoutConstraints.h>
@@ -111,8 +113,7 @@ class ShadowTree final {
*/
CommitStatus tryCommit(
const ShadowTreeCommitTransaction& transaction,
const CommitOptions& commitOptions,
bool hasLocked = false) const;
const CommitOptions& commitOptions) const;
/*
* Calls `tryCommit` in a loop until it finishes successfully.
@@ -151,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<const MountingCoordinator> mountingCoordinator_;
using UniqueLock = std::variant<
std::unique_lock<std::shared_mutex>,
std::unique_lock<std::recursive_mutex>>;
using SharedLock = std::variant<
std::shared_lock<std::shared_mutex>,
std::unique_lock<std::recursive_mutex>>;
inline UniqueLock uniqueCommitLock() const;
inline SharedLock sharedCommitLock() const;
};
} // namespace facebook::react
@@ -543,10 +543,10 @@ const definitions: FeatureFlagDefinitions = {
},
ossReleaseStage: 'none',
},
preventShadowTreeCommitExhaustionWithLocking: {
preventShadowTreeCommitExhaustion: {
defaultValue: false,
metadata: {
dateAdded: '2025-07-14',
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,
@@ -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<<595a51e39658c12aab12032f7b928615>>
* @generated SignedSource<<999a8d329cdab258ac64c03b24f1a516>>
* @flow strict
* @noformat
*/
@@ -98,7 +98,7 @@ export type ReactNativeFeatureFlags = $ReadOnly<{
fuseboxNetworkInspectionEnabled: Getter<boolean>,
hideOffscreenVirtualViewsOnIOS: Getter<boolean>,
preparedTextCacheSize: Getter<number>,
preventShadowTreeCommitExhaustionWithLocking: Getter<boolean>,
preventShadowTreeCommitExhaustion: Getter<boolean>,
traceTurboModulePromiseRejectionsOnAndroid: Getter<boolean>,
updateRuntimeShadowNodeReferencesOnCommit: Getter<boolean>,
useAlwaysAvailableJSErrorHandling: Getter<boolean>,
@@ -387,7 +387,7 @@ export const preparedTextCacheSize: Getter<number> = createNativeFlagGetter('pre
/**
* 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);
export const preventShadowTreeCommitExhaustion: Getter<boolean> = createNativeFlagGetter('preventShadowTreeCommitExhaustion', false);
/**
* Enables storing js caller stack when creating promise in native module. This is useful in case of Promise rejection and tracing the cause.
*/
@@ -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<<9d6574da819c190bed0458559c66a089>>
* @generated SignedSource<<1b84b6e04c214f6c2798010372937990>>
* @flow strict
* @noformat
*/
@@ -72,7 +72,7 @@ export interface Spec extends TurboModule {
+fuseboxNetworkInspectionEnabled?: () => boolean;
+hideOffscreenVirtualViewsOnIOS?: () => boolean;
+preparedTextCacheSize?: () => number;
+preventShadowTreeCommitExhaustionWithLocking?: () => boolean;
+preventShadowTreeCommitExhaustion?: () => boolean;
+traceTurboModulePromiseRejectionsOnAndroid?: () => boolean;
+updateRuntimeShadowNodeReferencesOnCommit?: () => boolean;
+useAlwaysAvailableJSErrorHandling?: () => boolean;