Delete ShadowTreeRevision on background thread (#50997)

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

In some apps, we spend a non-trivial amount of time calling ShadowNode destructors on the UI thread.

A simple way to avoid stalling the UI thread is to move the `baseRevision_` instance to a data structure that is cleared on a background thread, so it's tree of ShadowNode shared_ptrs are released (and in most cases destroyed) on the background thread.

Rather than using std::thread, this change introduces the LowPriorityExecutor abstraction that should be supplied by host platforms. The implementation of this LowPriorityExecutor for each platform is as follows:
- iOS: uses dispatch_async to a low priority dispatch queue
- Android: uses a pthread with SCHED_OTHER and priority = 19

Moving the ShadowTreeRevision into a lambda capture and punting the lambda to the LowPriorityExecutor moves the destructor calls of the ShadowNodes to the host platform implementation of the LowPriorityExecutor.

This change is also guarded by a feature flag so we can keep an eye out for potential memory leaks.

## Changelog

[Internal]

Reviewed By: NickGerleman

Differential Revision: D73688009

fbshipit-source-id: 6a66da248e6fe5c38375bf026499346e8381e75a
This commit is contained in:
Eric Rozell
2025-05-05 07:44:02 -07:00
committed by Facebook GitHub Bot
parent 8d08845cda
commit d9823d80bb
29 changed files with 361 additions and 67 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<<b86b8bb1d53ca03240c25adb8b423040>>
* @generated SignedSource<<31638ef8ac6992a354785b74af8fbe0d>>
*/
/**
@@ -90,6 +90,12 @@ public object ReactNativeFeatureFlags {
@JvmStatic
public fun enableCustomFocusSearchOnClippedElementsAndroid(): Boolean = accessor.enableCustomFocusSearchOnClippedElementsAndroid()
/**
* Enables destructor calls for ShadowTreeRevision in the background to reduce UI thread work.
*/
@JvmStatic
public fun enableDestroyShadowTreeRevisionAsync(): Boolean = accessor.enableDestroyShadowTreeRevisionAsync()
/**
* When enabled a subset of components will avoid double measurement on Android.
*/
@@ -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<<9c1e0e7e87aead96e2554ef5ecf9e7af>>
* @generated SignedSource<<6b382661025db56592b44255f5a8694c>>
*/
/**
@@ -30,6 +30,7 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
private var enableBridgelessArchitectureCache: Boolean? = null
private var enableCppPropsIteratorSetterCache: Boolean? = null
private var enableCustomFocusSearchOnClippedElementsAndroidCache: Boolean? = null
private var enableDestroyShadowTreeRevisionAsyncCache: Boolean? = null
private var enableDoubleMeasurementFixAndroidCache: Boolean? = null
private var enableEagerRootViewAttachmentCache: Boolean? = null
private var enableFabricLogsCache: Boolean? = null
@@ -156,6 +157,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
return cached
}
override fun enableDestroyShadowTreeRevisionAsync(): Boolean {
var cached = enableDestroyShadowTreeRevisionAsyncCache
if (cached == null) {
cached = ReactNativeFeatureFlagsCxxInterop.enableDestroyShadowTreeRevisionAsync()
enableDestroyShadowTreeRevisionAsyncCache = cached
}
return cached
}
override fun enableDoubleMeasurementFixAndroid(): Boolean {
var cached = enableDoubleMeasurementFixAndroidCache
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<<b95c0d1f576b67fcf9d9df498ea30994>>
* @generated SignedSource<<8276fd1166cdd235f11a0b490bb7d924>>
*/
/**
@@ -48,6 +48,8 @@ public object ReactNativeFeatureFlagsCxxInterop {
@DoNotStrip @JvmStatic public external fun enableCustomFocusSearchOnClippedElementsAndroid(): Boolean
@DoNotStrip @JvmStatic public external fun enableDestroyShadowTreeRevisionAsync(): Boolean
@DoNotStrip @JvmStatic public external fun enableDoubleMeasurementFixAndroid(): Boolean
@DoNotStrip @JvmStatic public external fun enableEagerRootViewAttachment(): 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<<c7425e6212e4e9d70e64452e550bf638>>
* @generated SignedSource<<ffadd7912aed2d95b0c6199aa8902690>>
*/
/**
@@ -43,6 +43,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi
override fun enableCustomFocusSearchOnClippedElementsAndroid(): Boolean = true
override fun enableDestroyShadowTreeRevisionAsync(): Boolean = false
override fun enableDoubleMeasurementFixAndroid(): Boolean = false
override fun enableEagerRootViewAttachment(): 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<<5b145ee103cf6b40e13d33c88cd52777>>
* @generated SignedSource<<5dc41059d71d3a345be45a6a233b05a0>>
*/
/**
@@ -34,6 +34,7 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
private var enableBridgelessArchitectureCache: Boolean? = null
private var enableCppPropsIteratorSetterCache: Boolean? = null
private var enableCustomFocusSearchOnClippedElementsAndroidCache: Boolean? = null
private var enableDestroyShadowTreeRevisionAsyncCache: Boolean? = null
private var enableDoubleMeasurementFixAndroidCache: Boolean? = null
private var enableEagerRootViewAttachmentCache: Boolean? = null
private var enableFabricLogsCache: Boolean? = null
@@ -170,6 +171,16 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
return cached
}
override fun enableDestroyShadowTreeRevisionAsync(): Boolean {
var cached = enableDestroyShadowTreeRevisionAsyncCache
if (cached == null) {
cached = currentProvider.enableDestroyShadowTreeRevisionAsync()
accessedFeatureFlags.add("enableDestroyShadowTreeRevisionAsync")
enableDestroyShadowTreeRevisionAsyncCache = cached
}
return cached
}
override fun enableDoubleMeasurementFixAndroid(): Boolean {
var cached = enableDoubleMeasurementFixAndroidCache
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<<9cdb965a420d53cf36b6531684cb07ae>>
* @generated SignedSource<<35f6d9ae3c445c81fc4bab7509fd3179>>
*/
/**
@@ -43,6 +43,8 @@ public interface ReactNativeFeatureFlagsProvider {
@DoNotStrip public fun enableCustomFocusSearchOnClippedElementsAndroid(): Boolean
@DoNotStrip public fun enableDestroyShadowTreeRevisionAsync(): Boolean
@DoNotStrip public fun enableDoubleMeasurementFixAndroid(): Boolean
@DoNotStrip public fun enableEagerRootViewAttachment(): 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<<c89831646046b10c7b2a40e14deb3d1a>>
* @generated SignedSource<<d566c157b7db07e235bada071067f11d>>
*/
/**
@@ -99,6 +99,12 @@ class ReactNativeFeatureFlagsJavaProvider
return method(javaProvider_);
}
bool enableDestroyShadowTreeRevisionAsync() override {
static const auto method =
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("enableDestroyShadowTreeRevisionAsync");
return method(javaProvider_);
}
bool enableDoubleMeasurementFixAndroid() override {
static const auto method =
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("enableDoubleMeasurementFixAndroid");
@@ -363,6 +369,11 @@ bool JReactNativeFeatureFlagsCxxInterop::enableCustomFocusSearchOnClippedElement
return ReactNativeFeatureFlags::enableCustomFocusSearchOnClippedElementsAndroid();
}
bool JReactNativeFeatureFlagsCxxInterop::enableDestroyShadowTreeRevisionAsync(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
return ReactNativeFeatureFlags::enableDestroyShadowTreeRevisionAsync();
}
bool JReactNativeFeatureFlagsCxxInterop::enableDoubleMeasurementFixAndroid(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
return ReactNativeFeatureFlags::enableDoubleMeasurementFixAndroid();
@@ -599,6 +610,9 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() {
makeNativeMethod(
"enableCustomFocusSearchOnClippedElementsAndroid",
JReactNativeFeatureFlagsCxxInterop::enableCustomFocusSearchOnClippedElementsAndroid),
makeNativeMethod(
"enableDestroyShadowTreeRevisionAsync",
JReactNativeFeatureFlagsCxxInterop::enableDestroyShadowTreeRevisionAsync),
makeNativeMethod(
"enableDoubleMeasurementFixAndroid",
JReactNativeFeatureFlagsCxxInterop::enableDoubleMeasurementFixAndroid),
@@ -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<<2a5ad62d2cb4f1111391f1b566e10c1d>>
* @generated SignedSource<<270b461fa199f8e6365948cded0785ad>>
*/
/**
@@ -60,6 +60,9 @@ class JReactNativeFeatureFlagsCxxInterop
static bool enableCustomFocusSearchOnClippedElementsAndroid(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
static bool enableDestroyShadowTreeRevisionAsync(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
static bool enableDoubleMeasurementFixAndroid(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
@@ -46,12 +46,12 @@ Pod::Spec.new do |s|
s.dependency "React-Core"
s.dependency "React-debug"
s.dependency "React-featureflags"
s.dependency "React-utils"
s.dependency "React-runtimescheduler"
s.dependency "React-cxxreact"
add_dependency(s, "React-rendererdebug")
add_dependency(s, "React-graphics", :additional_framework_paths => ["react/renderer/graphics/platform/ios"])
add_dependency(s, "React-utils", :additional_framework_paths => ["react/utils/platform/ios"])
depend_on_js_engine(s)
add_rn_third_party_dependencies(s)
@@ -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<<0eb63ead191be88440cbb73fd760dec7>>
* @generated SignedSource<<74f22c6a0302a9923e99d2455d84e231>>
*/
/**
@@ -66,6 +66,10 @@ bool ReactNativeFeatureFlags::enableCustomFocusSearchOnClippedElementsAndroid()
return getAccessor().enableCustomFocusSearchOnClippedElementsAndroid();
}
bool ReactNativeFeatureFlags::enableDestroyShadowTreeRevisionAsync() {
return getAccessor().enableDestroyShadowTreeRevisionAsync();
}
bool ReactNativeFeatureFlags::enableDoubleMeasurementFixAndroid() {
return getAccessor().enableDoubleMeasurementFixAndroid();
}
@@ -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<<80dcd75591a392144c2fd2817cc4408a>>
* @generated SignedSource<<b941543bc6f12dbb0e12ac2ae0042186>>
*/
/**
@@ -89,6 +89,11 @@ class ReactNativeFeatureFlags {
*/
RN_EXPORT static bool enableCustomFocusSearchOnClippedElementsAndroid();
/**
* Enables destructor calls for ShadowTreeRevision in the background to reduce UI thread work.
*/
RN_EXPORT static bool enableDestroyShadowTreeRevisionAsync();
/**
* When enabled a subset of components will avoid double measurement on Android.
*/
@@ -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<<0c39183e7858453bfc18515be46eb17f>>
* @generated SignedSource<<4efbb3f094edbcc89c6ac4d586169b9a>>
*/
/**
@@ -209,6 +209,24 @@ bool ReactNativeFeatureFlagsAccessor::enableCustomFocusSearchOnClippedElementsAn
return flagValue.value();
}
bool ReactNativeFeatureFlagsAccessor::enableDestroyShadowTreeRevisionAsync() {
auto flagValue = enableDestroyShadowTreeRevisionAsync_.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(10, "enableDestroyShadowTreeRevisionAsync");
flagValue = currentProvider_->enableDestroyShadowTreeRevisionAsync();
enableDestroyShadowTreeRevisionAsync_ = flagValue;
}
return flagValue.value();
}
bool ReactNativeFeatureFlagsAccessor::enableDoubleMeasurementFixAndroid() {
auto flagValue = enableDoubleMeasurementFixAndroid_.load();
@@ -218,7 +236,7 @@ bool ReactNativeFeatureFlagsAccessor::enableDoubleMeasurementFixAndroid() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(10, "enableDoubleMeasurementFixAndroid");
markFlagAsAccessed(11, "enableDoubleMeasurementFixAndroid");
flagValue = currentProvider_->enableDoubleMeasurementFixAndroid();
enableDoubleMeasurementFixAndroid_ = flagValue;
@@ -236,7 +254,7 @@ bool ReactNativeFeatureFlagsAccessor::enableEagerRootViewAttachment() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(11, "enableEagerRootViewAttachment");
markFlagAsAccessed(12, "enableEagerRootViewAttachment");
flagValue = currentProvider_->enableEagerRootViewAttachment();
enableEagerRootViewAttachment_ = flagValue;
@@ -254,7 +272,7 @@ bool ReactNativeFeatureFlagsAccessor::enableFabricLogs() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(12, "enableFabricLogs");
markFlagAsAccessed(13, "enableFabricLogs");
flagValue = currentProvider_->enableFabricLogs();
enableFabricLogs_ = flagValue;
@@ -272,7 +290,7 @@ bool ReactNativeFeatureFlagsAccessor::enableFabricRenderer() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(13, "enableFabricRenderer");
markFlagAsAccessed(14, "enableFabricRenderer");
flagValue = currentProvider_->enableFabricRenderer();
enableFabricRenderer_ = flagValue;
@@ -290,7 +308,7 @@ bool ReactNativeFeatureFlagsAccessor::enableFixForParentTagDuringReparenting() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(14, "enableFixForParentTagDuringReparenting");
markFlagAsAccessed(15, "enableFixForParentTagDuringReparenting");
flagValue = currentProvider_->enableFixForParentTagDuringReparenting();
enableFixForParentTagDuringReparenting_ = flagValue;
@@ -308,7 +326,7 @@ bool ReactNativeFeatureFlagsAccessor::enableFontScaleChangesUpdatingLayout() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(15, "enableFontScaleChangesUpdatingLayout");
markFlagAsAccessed(16, "enableFontScaleChangesUpdatingLayout");
flagValue = currentProvider_->enableFontScaleChangesUpdatingLayout();
enableFontScaleChangesUpdatingLayout_ = flagValue;
@@ -326,7 +344,7 @@ bool ReactNativeFeatureFlagsAccessor::enableIOSViewClipToPaddingBox() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(16, "enableIOSViewClipToPaddingBox");
markFlagAsAccessed(17, "enableIOSViewClipToPaddingBox");
flagValue = currentProvider_->enableIOSViewClipToPaddingBox();
enableIOSViewClipToPaddingBox_ = flagValue;
@@ -344,7 +362,7 @@ bool ReactNativeFeatureFlagsAccessor::enableJSRuntimeGCOnMemoryPressureOnIOS() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(17, "enableJSRuntimeGCOnMemoryPressureOnIOS");
markFlagAsAccessed(18, "enableJSRuntimeGCOnMemoryPressureOnIOS");
flagValue = currentProvider_->enableJSRuntimeGCOnMemoryPressureOnIOS();
enableJSRuntimeGCOnMemoryPressureOnIOS_ = flagValue;
@@ -362,7 +380,7 @@ bool ReactNativeFeatureFlagsAccessor::enableLayoutAnimationsOnAndroid() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(18, "enableLayoutAnimationsOnAndroid");
markFlagAsAccessed(19, "enableLayoutAnimationsOnAndroid");
flagValue = currentProvider_->enableLayoutAnimationsOnAndroid();
enableLayoutAnimationsOnAndroid_ = flagValue;
@@ -380,7 +398,7 @@ bool ReactNativeFeatureFlagsAccessor::enableLayoutAnimationsOnIOS() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(19, "enableLayoutAnimationsOnIOS");
markFlagAsAccessed(20, "enableLayoutAnimationsOnIOS");
flagValue = currentProvider_->enableLayoutAnimationsOnIOS();
enableLayoutAnimationsOnIOS_ = flagValue;
@@ -398,7 +416,7 @@ bool ReactNativeFeatureFlagsAccessor::enableMainQueueModulesOnIOS() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(20, "enableMainQueueModulesOnIOS");
markFlagAsAccessed(21, "enableMainQueueModulesOnIOS");
flagValue = currentProvider_->enableMainQueueModulesOnIOS();
enableMainQueueModulesOnIOS_ = flagValue;
@@ -416,7 +434,7 @@ bool ReactNativeFeatureFlagsAccessor::enableNativeCSSParsing() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(21, "enableNativeCSSParsing");
markFlagAsAccessed(22, "enableNativeCSSParsing");
flagValue = currentProvider_->enableNativeCSSParsing();
enableNativeCSSParsing_ = flagValue;
@@ -434,7 +452,7 @@ bool ReactNativeFeatureFlagsAccessor::enableNetworkEventReporting() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(22, "enableNetworkEventReporting");
markFlagAsAccessed(23, "enableNetworkEventReporting");
flagValue = currentProvider_->enableNetworkEventReporting();
enableNetworkEventReporting_ = flagValue;
@@ -452,7 +470,7 @@ bool ReactNativeFeatureFlagsAccessor::enableNewBackgroundAndBorderDrawables() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(23, "enableNewBackgroundAndBorderDrawables");
markFlagAsAccessed(24, "enableNewBackgroundAndBorderDrawables");
flagValue = currentProvider_->enableNewBackgroundAndBorderDrawables();
enableNewBackgroundAndBorderDrawables_ = flagValue;
@@ -470,7 +488,7 @@ bool ReactNativeFeatureFlagsAccessor::enablePropsUpdateReconciliationAndroid() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(24, "enablePropsUpdateReconciliationAndroid");
markFlagAsAccessed(25, "enablePropsUpdateReconciliationAndroid");
flagValue = currentProvider_->enablePropsUpdateReconciliationAndroid();
enablePropsUpdateReconciliationAndroid_ = flagValue;
@@ -488,7 +506,7 @@ bool ReactNativeFeatureFlagsAccessor::enableResourceTimingAPI() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(25, "enableResourceTimingAPI");
markFlagAsAccessed(26, "enableResourceTimingAPI");
flagValue = currentProvider_->enableResourceTimingAPI();
enableResourceTimingAPI_ = flagValue;
@@ -506,7 +524,7 @@ bool ReactNativeFeatureFlagsAccessor::enableSynchronousStateUpdates() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(26, "enableSynchronousStateUpdates");
markFlagAsAccessed(27, "enableSynchronousStateUpdates");
flagValue = currentProvider_->enableSynchronousStateUpdates();
enableSynchronousStateUpdates_ = flagValue;
@@ -524,7 +542,7 @@ bool ReactNativeFeatureFlagsAccessor::enableViewCulling() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(27, "enableViewCulling");
markFlagAsAccessed(28, "enableViewCulling");
flagValue = currentProvider_->enableViewCulling();
enableViewCulling_ = flagValue;
@@ -542,7 +560,7 @@ bool ReactNativeFeatureFlagsAccessor::enableViewRecycling() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(28, "enableViewRecycling");
markFlagAsAccessed(29, "enableViewRecycling");
flagValue = currentProvider_->enableViewRecycling();
enableViewRecycling_ = flagValue;
@@ -560,7 +578,7 @@ bool ReactNativeFeatureFlagsAccessor::enableViewRecyclingForText() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(29, "enableViewRecyclingForText");
markFlagAsAccessed(30, "enableViewRecyclingForText");
flagValue = currentProvider_->enableViewRecyclingForText();
enableViewRecyclingForText_ = flagValue;
@@ -578,7 +596,7 @@ bool ReactNativeFeatureFlagsAccessor::enableViewRecyclingForView() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(30, "enableViewRecyclingForView");
markFlagAsAccessed(31, "enableViewRecyclingForView");
flagValue = currentProvider_->enableViewRecyclingForView();
enableViewRecyclingForView_ = flagValue;
@@ -596,7 +614,7 @@ bool ReactNativeFeatureFlagsAccessor::fixMappingOfEventPrioritiesBetweenFabricAn
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(31, "fixMappingOfEventPrioritiesBetweenFabricAndReact");
markFlagAsAccessed(32, "fixMappingOfEventPrioritiesBetweenFabricAndReact");
flagValue = currentProvider_->fixMappingOfEventPrioritiesBetweenFabricAndReact();
fixMappingOfEventPrioritiesBetweenFabricAndReact_ = flagValue;
@@ -614,7 +632,7 @@ bool ReactNativeFeatureFlagsAccessor::fuseboxEnabledRelease() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(32, "fuseboxEnabledRelease");
markFlagAsAccessed(33, "fuseboxEnabledRelease");
flagValue = currentProvider_->fuseboxEnabledRelease();
fuseboxEnabledRelease_ = flagValue;
@@ -632,7 +650,7 @@ bool ReactNativeFeatureFlagsAccessor::fuseboxNetworkInspectionEnabled() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(33, "fuseboxNetworkInspectionEnabled");
markFlagAsAccessed(34, "fuseboxNetworkInspectionEnabled");
flagValue = currentProvider_->fuseboxNetworkInspectionEnabled();
fuseboxNetworkInspectionEnabled_ = flagValue;
@@ -650,7 +668,7 @@ bool ReactNativeFeatureFlagsAccessor::incorporateMaxLinesDuringAndroidLayout() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(34, "incorporateMaxLinesDuringAndroidLayout");
markFlagAsAccessed(35, "incorporateMaxLinesDuringAndroidLayout");
flagValue = currentProvider_->incorporateMaxLinesDuringAndroidLayout();
incorporateMaxLinesDuringAndroidLayout_ = flagValue;
@@ -668,7 +686,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(35, "traceTurboModulePromiseRejectionsOnAndroid");
markFlagAsAccessed(36, "traceTurboModulePromiseRejectionsOnAndroid");
flagValue = currentProvider_->traceTurboModulePromiseRejectionsOnAndroid();
traceTurboModulePromiseRejectionsOnAndroid_ = flagValue;
@@ -686,7 +704,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(36, "updateRuntimeShadowNodeReferencesOnCommit");
markFlagAsAccessed(37, "updateRuntimeShadowNodeReferencesOnCommit");
flagValue = currentProvider_->updateRuntimeShadowNodeReferencesOnCommit();
updateRuntimeShadowNodeReferencesOnCommit_ = flagValue;
@@ -704,7 +722,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(37, "useAlwaysAvailableJSErrorHandling");
markFlagAsAccessed(38, "useAlwaysAvailableJSErrorHandling");
flagValue = currentProvider_->useAlwaysAvailableJSErrorHandling();
useAlwaysAvailableJSErrorHandling_ = flagValue;
@@ -722,7 +740,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(38, "useFabricInterop");
markFlagAsAccessed(39, "useFabricInterop");
flagValue = currentProvider_->useFabricInterop();
useFabricInterop_ = flagValue;
@@ -740,7 +758,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(39, "useNativeViewConfigsInBridgelessMode");
markFlagAsAccessed(40, "useNativeViewConfigsInBridgelessMode");
flagValue = currentProvider_->useNativeViewConfigsInBridgelessMode();
useNativeViewConfigsInBridgelessMode_ = flagValue;
@@ -758,7 +776,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(40, "useOptimizedEventBatchingOnAndroid");
markFlagAsAccessed(41, "useOptimizedEventBatchingOnAndroid");
flagValue = currentProvider_->useOptimizedEventBatchingOnAndroid();
useOptimizedEventBatchingOnAndroid_ = flagValue;
@@ -776,7 +794,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(41, "useRawPropsJsiValue");
markFlagAsAccessed(42, "useRawPropsJsiValue");
flagValue = currentProvider_->useRawPropsJsiValue();
useRawPropsJsiValue_ = flagValue;
@@ -794,7 +812,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(42, "useShadowNodeStateOnClone");
markFlagAsAccessed(43, "useShadowNodeStateOnClone");
flagValue = currentProvider_->useShadowNodeStateOnClone();
useShadowNodeStateOnClone_ = flagValue;
@@ -812,7 +830,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(43, "useTurboModuleInterop");
markFlagAsAccessed(44, "useTurboModuleInterop");
flagValue = currentProvider_->useTurboModuleInterop();
useTurboModuleInterop_ = flagValue;
@@ -830,7 +848,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(44, "useTurboModules");
markFlagAsAccessed(45, "useTurboModules");
flagValue = currentProvider_->useTurboModules();
useTurboModules_ = 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<<336504fac084993c9aff86f274d45342>>
* @generated SignedSource<<31b5c2776ad91fd0d3bd2cfc3672575b>>
*/
/**
@@ -42,6 +42,7 @@ class ReactNativeFeatureFlagsAccessor {
bool enableBridgelessArchitecture();
bool enableCppPropsIteratorSetter();
bool enableCustomFocusSearchOnClippedElementsAndroid();
bool enableDestroyShadowTreeRevisionAsync();
bool enableDoubleMeasurementFixAndroid();
bool enableEagerRootViewAttachment();
bool enableFabricLogs();
@@ -88,7 +89,7 @@ class ReactNativeFeatureFlagsAccessor {
std::unique_ptr<ReactNativeFeatureFlagsProvider> currentProvider_;
bool wasOverridden_;
std::array<std::atomic<const char*>, 45> accessedFeatureFlags_;
std::array<std::atomic<const char*>, 46> accessedFeatureFlags_;
std::atomic<std::optional<bool>> commonTestFlag_;
std::atomic<std::optional<bool>> animatedShouldSignalBatch_;
@@ -100,6 +101,7 @@ class ReactNativeFeatureFlagsAccessor {
std::atomic<std::optional<bool>> enableBridgelessArchitecture_;
std::atomic<std::optional<bool>> enableCppPropsIteratorSetter_;
std::atomic<std::optional<bool>> enableCustomFocusSearchOnClippedElementsAndroid_;
std::atomic<std::optional<bool>> enableDestroyShadowTreeRevisionAsync_;
std::atomic<std::optional<bool>> enableDoubleMeasurementFixAndroid_;
std::atomic<std::optional<bool>> enableEagerRootViewAttachment_;
std::atomic<std::optional<bool>> enableFabricLogs_;
@@ -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<<825294594363fb672b6cd64eaa954dd4>>
* @generated SignedSource<<9cc33ba9f5ef67d6c98700d772bbc0de>>
*/
/**
@@ -67,6 +67,10 @@ class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider {
return true;
}
bool enableDestroyShadowTreeRevisionAsync() override {
return false;
}
bool enableDoubleMeasurementFixAndroid() 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<<d9dc055d9cda1ac17b7039aa70691a6b>>
* @generated SignedSource<<d336ecdd8ce51394df6d7baf6ea6457a>>
*/
/**
@@ -135,6 +135,15 @@ class ReactNativeFeatureFlagsDynamicProvider : public ReactNativeFeatureFlagsDef
return ReactNativeFeatureFlagsDefaults::enableCustomFocusSearchOnClippedElementsAndroid();
}
bool enableDestroyShadowTreeRevisionAsync() override {
auto value = values_["enableDestroyShadowTreeRevisionAsync"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::enableDestroyShadowTreeRevisionAsync();
}
bool enableDoubleMeasurementFixAndroid() override {
auto value = values_["enableDoubleMeasurementFixAndroid"];
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<<905f26c85dc731205b1a6f44c4e32468>>
* @generated SignedSource<<ac9ae2b709bdc6358523ebf91fc2f78e>>
*/
/**
@@ -35,6 +35,7 @@ class ReactNativeFeatureFlagsProvider {
virtual bool enableBridgelessArchitecture() = 0;
virtual bool enableCppPropsIteratorSetter() = 0;
virtual bool enableCustomFocusSearchOnClippedElementsAndroid() = 0;
virtual bool enableDestroyShadowTreeRevisionAsync() = 0;
virtual bool enableDoubleMeasurementFixAndroid() = 0;
virtual bool enableEagerRootViewAttachment() = 0;
virtual bool enableFabricLogs() = 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<<614814062283858cf70138a2ac3d6304>>
* @generated SignedSource<<82e2e7118453787377248101925b56d1>>
*/
/**
@@ -94,6 +94,11 @@ bool NativeReactNativeFeatureFlags::enableCustomFocusSearchOnClippedElementsAndr
return ReactNativeFeatureFlags::enableCustomFocusSearchOnClippedElementsAndroid();
}
bool NativeReactNativeFeatureFlags::enableDestroyShadowTreeRevisionAsync(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::enableDestroyShadowTreeRevisionAsync();
}
bool NativeReactNativeFeatureFlags::enableDoubleMeasurementFixAndroid(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::enableDoubleMeasurementFixAndroid();
@@ -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<<1c0248c3eda43cc89a61b3e3ff8b4345>>
* @generated SignedSource<<d0d056652d60269b736d4fb0bc71ab4f>>
*/
/**
@@ -57,6 +57,8 @@ class NativeReactNativeFeatureFlags
bool enableCustomFocusSearchOnClippedElementsAndroid(jsi::Runtime& runtime);
bool enableDestroyShadowTreeRevisionAsync(jsi::Runtime& runtime);
bool enableDoubleMeasurementFixAndroid(jsi::Runtime& runtime);
bool enableEagerRootViewAttachment(jsi::Runtime& runtime);
@@ -9,7 +9,9 @@
#include <cxxreact/TraceSection.h>
#include <react/debug/react_native_assert.h>
#include <react/featureflags/ReactNativeFeatureFlags.h>
#include <react/renderer/mounting/ShadowViewMutation.h>
#include <react/utils/LowPriorityExecutor.h>
#include <condition_variable>
#include "updateMountedFlag.h"
@@ -186,6 +188,9 @@ std::optional<MountingTransaction> MountingCoordinator::pullTransaction(
#endif
if (lastRevision_.has_value()) {
if (ReactNativeFeatureFlags::enableDestroyShadowTreeRevisionAsync()) {
LowPriorityExecutor::execute([toDelete = std::move(baseRevision_)]() {});
}
baseRevision_ = std::move(*lastRevision_);
lastRevision_.reset();
@@ -8,10 +8,14 @@ set(CMAKE_VERBOSE_MAKEFILE on)
include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake)
file(GLOB react_utils_SRC CONFIGURE_DEPENDS *.cpp *.mm)
file(GLOB react_utils_SRC CONFIGURE_DEPENDS *.cpp platform/android/react/utils/*.cpp)
add_library(react_utils OBJECT ${react_utils_SRC})
target_include_directories(react_utils PUBLIC ${REACT_COMMON_DIR})
target_include_directories(react_utils
PUBLIC
${REACT_COMMON_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/platform/android/
)
target_link_libraries(react_utils
glog
@@ -16,12 +16,12 @@ else
source[:tag] = "v#{version}"
end
header_search_paths = [
"\"$(PODS_TARGET_SRCROOT)\"",
"\"$(PODS_TARGET_SRCROOT)/ReactCommon\"",
]
Pod::Spec.new do |s|
source_files = "*.{m,mm,cpp,h}", "platform/ios/**/*.{m,mm,cpp,h}"
header_search_paths = [
"\"$(PODS_TARGET_SRCROOT)/../../\"",
]
s.name = "React-utils"
s.version = version
s.summary = "-" # TODO
@@ -30,18 +30,21 @@ Pod::Spec.new do |s|
s.author = "Meta Platforms, Inc. and its affiliates"
s.platforms = min_supported_versions
s.source = source
s.source_files = "**/*.{cpp,h,mm}"
s.source_files = source_files
s.header_dir = "react/utils"
s.exclude_files = "tests"
s.pod_target_xcconfig = { "CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(),
"HEADER_SEARCH_PATHS" => header_search_paths.join(' '),
"DEFINES_MODULE" => "YES" }
if ENV['USE_FRAMEWORKS']
s.module_name = "React_utils"
s.header_mappings_dir = "../.."
header_search_paths = header_search_paths + ["\"$(PODS_TARGET_SRCROOT)/platform/ios\""]
end
s.pod_target_xcconfig = { "USE_HEADERMAP" => "NO",
"CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(),
"HEADER_SEARCH_PATHS" => header_search_paths.join(' '),
"DEFINES_MODULE" => "YES" }
s.dependency "React-jsi", version
depend_on_js_engine(s)
@@ -0,0 +1,95 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include <pthread.h>
#include <sched.h>
#include <condition_variable>
#include <mutex>
#include <queue>
#include <thread>
#include <utility>
namespace facebook::react::LowPriorityExecutor {
struct LowPriorityExecutorThread {
LowPriorityExecutorThread() : thread_{std::thread([this] { run(); })} {
pthread_t hThread = thread_.native_handle();
struct sched_param param {};
param.sched_priority = 19; // Higher value means lower priority
pthread_setschedparam(hThread, SCHED_OTHER, &param);
}
// Deleted constructors
LowPriorityExecutorThread(const LowPriorityExecutorThread& other) = delete;
LowPriorityExecutorThread(LowPriorityExecutorThread&& other) = delete;
LowPriorityExecutorThread& operator=(const LowPriorityExecutorThread& other) =
delete;
LowPriorityExecutorThread& operator=(LowPriorityExecutorThread&& other) =
delete;
~LowPriorityExecutorThread() {
// Stop the thread
{
std::lock_guard<std::mutex> lock(mutex_);
running_ = false;
}
// Unblock the thread to check the running_ flag and terminate.
cv_.notify_one();
// Wait for thread completion to avoid use-after-free on background thread.
thread_.join();
}
void post(std::function<void()>&& workItem) {
// Move the object to the queue.
{
std::lock_guard<std::mutex> lock(mutex_);
queue_.emplace(std::move(workItem));
}
// Notify the background thread.
cv_.notify_one();
}
private:
void run() {
pthread_setname_np(pthread_self(), "LowPriorityExecutorThread");
while (true) {
std::unique_lock<std::mutex> lock(mutex_);
// Wait until an object is in the queue or the thread is stopped.
cv_.wait(lock, [this] { return !queue_.empty() || !running_; });
// Empty the queue.
while (!queue_.empty()) {
queue_.front()();
queue_.pop();
}
// Check if the thread is stopping.
if (!running_) {
break;
}
}
}
std::string threadName_;
std::thread thread_;
std::queue<std::function<void()>> queue_;
std::mutex mutex_;
std::condition_variable cv_;
bool running_{true};
};
void execute(std::function<void()>&& workItem) {
static LowPriorityExecutorThread thread{};
thread.post(std::move(workItem));
}
} // namespace facebook::react::LowPriorityExecutor
@@ -0,0 +1,14 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
namespace facebook::react::LowPriorityExecutor {
void execute(std::function<void()>&& workItem);
} // namespace facebook::react::LowPriorityExecutor
@@ -0,0 +1,18 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <functional>
namespace facebook::react::LowPriorityExecutor {
inline void execute(std::function<void()>&& workItem) {
workItem();
}
} // namespace facebook::react::LowPriorityExecutor
@@ -0,0 +1,16 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <functional>
namespace facebook::react::LowPriorityExecutor {
void execute(std::function<void()>&& workItem);
} // namespace facebook::react::LowPriorityExecutor
@@ -0,0 +1,22 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "LowPriorityExecutor.h"
#import <Foundation/Foundation.h>
namespace facebook::react::LowPriorityExecutor {
void execute(std::function<void()> &&workItem)
{
std::function<void()> localWorkItem = std::move(workItem);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
localWorkItem();
});
}
} // namespace facebook::react::LowPriorityExecutor
@@ -155,6 +155,17 @@ const definitions: FeatureFlagDefinitions = {
},
ossReleaseStage: 'none',
},
enableDestroyShadowTreeRevisionAsync: {
defaultValue: false,
metadata: {
dateAdded: '2025-04-29',
description:
'Enables destructor calls for ShadowTreeRevision in the background to reduce UI thread work.',
expectedReleaseValue: true,
purpose: 'experimentation',
},
ossReleaseStage: 'none',
},
enableDoubleMeasurementFixAndroid: {
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<<fab0bb6a5cd8a2b8366e8184659188c0>>
* @generated SignedSource<<fc8743f0fb9cb153412a1e81f585f07d>>
* @flow strict
*/
@@ -56,6 +56,7 @@ export type ReactNativeFeatureFlags = $ReadOnly<{
enableBridgelessArchitecture: Getter<boolean>,
enableCppPropsIteratorSetter: Getter<boolean>,
enableCustomFocusSearchOnClippedElementsAndroid: Getter<boolean>,
enableDestroyShadowTreeRevisionAsync: Getter<boolean>,
enableDoubleMeasurementFixAndroid: Getter<boolean>,
enableEagerRootViewAttachment: Getter<boolean>,
enableFabricLogs: Getter<boolean>,
@@ -197,6 +198,10 @@ export const enableCppPropsIteratorSetter: Getter<boolean> = createNativeFlagGet
* This enables the fabric implementation of focus search so that we can focus clipped elements
*/
export const enableCustomFocusSearchOnClippedElementsAndroid: Getter<boolean> = createNativeFlagGetter('enableCustomFocusSearchOnClippedElementsAndroid', true);
/**
* Enables destructor calls for ShadowTreeRevision in the background to reduce UI thread work.
*/
export const enableDestroyShadowTreeRevisionAsync: Getter<boolean> = createNativeFlagGetter('enableDestroyShadowTreeRevisionAsync', false);
/**
* When enabled a subset of components will avoid double measurement on Android.
*/
@@ -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<<715d2a137ded85479120f5090c05b3b6>>
* @generated SignedSource<<995633c3b12e26369518ff579f006f68>>
* @flow strict
*/
@@ -34,6 +34,7 @@ export interface Spec extends TurboModule {
+enableBridgelessArchitecture?: () => boolean;
+enableCppPropsIteratorSetter?: () => boolean;
+enableCustomFocusSearchOnClippedElementsAndroid?: () => boolean;
+enableDestroyShadowTreeRevisionAsync?: () => boolean;
+enableDoubleMeasurementFixAndroid?: () => boolean;
+enableEagerRootViewAttachment?: () => boolean;
+enableFabricLogs?: () => boolean;