diff --git a/packages/react-native/React/Base/RCTUtils.h b/packages/react-native/React/Base/RCTUtils.h index d4459badcea009..2de1c02f223ad7 100644 --- a/packages/react-native/React/Base/RCTUtils.h +++ b/packages/react-native/React/Base/RCTUtils.h @@ -49,7 +49,7 @@ RCT_EXTERN void RCTExecuteOnMainQueue(dispatch_block_t block); // Legacy function to execute the specified block on the main queue synchronously. // Please do not use this unless you know what you're doing. RCT_EXTERN void RCTUnsafeExecuteOnMainQueueSync(dispatch_block_t block); -RCT_EXTERN void RCTUnsafeExecuteOnMainQueueSyncWithError(dispatch_block_t block, NSString *context); +RCT_EXTERN void RCTUnsafeExecuteOnMainQueueSyncWithError(dispatch_block_t block, NSString *_Nullable context); // Get screen metrics in a thread-safe way RCT_EXTERN CGFloat RCTScreenScale(void); diff --git a/packages/react-native/React/Base/RCTUtils.mm b/packages/react-native/React/Base/RCTUtils.mm index 97330a33caf176..241c0d3aaa2521 100644 --- a/packages/react-native/React/Base/RCTUtils.mm +++ b/packages/react-native/React/Base/RCTUtils.mm @@ -19,6 +19,7 @@ #import #import +#import #import "RCTAssert.h" #import "RCTLog.h" @@ -295,36 +296,41 @@ void RCTExecuteOnMainQueue(dispatch_block_t block) } } +static BOOL RCTIsJSThread() +{ + return [[NSThread currentThread].name containsString:@"JavaScript"]; +} + // Please do not use this method // unless you know what you are doing. void RCTUnsafeExecuteOnMainQueueSync(dispatch_block_t block) { - if (RCTIsMainQueue()) { - block(); - } else { - if (facebook::react::ReactNativeFeatureFlags::disableMainQueueSyncDispatchIOS()) { - RCTLogError(@"RCTUnsafeExecuteOnMainQueueSync: Sync dispatches to the main queue can deadlock React Native."); - } - dispatch_sync(dispatch_get_main_queue(), ^{ - block(); - }); - } + RCTUnsafeExecuteOnMainQueueSyncWithError(block, nil); } // Please do not use this method // unless you know what you are doing. -void RCTUnsafeExecuteOnMainQueueSyncWithError(dispatch_block_t block, NSString *context) +void RCTUnsafeExecuteOnMainQueueSyncWithError(dispatch_block_t block, NSString *_Nullable context) { if (RCTIsMainQueue()) { block(); - } else { - if (facebook::react::ReactNativeFeatureFlags::disableMainQueueSyncDispatchIOS()) { - RCTLogError(@"RCTUnsafeExecuteOnMainQueueSync: %@", context); + return; + } + + if (facebook::react::ReactNativeFeatureFlags::enableSaferMainQueueSyncDispatchOnIOS()) { + if (RCTIsJSThread()) { + facebook::react::unsafeExecuteOnMainThreadSync(block); + return; } - dispatch_sync(dispatch_get_main_queue(), ^{ - block(); - }); + } else if (facebook::react::ReactNativeFeatureFlags::disableMainQueueSyncDispatchIOS()) { + RCTLogError( + @"RCTUnsafeExecuteOnMainQueueSync: %@", + context ?: @"Sync dispatches to the main queue can deadlock React Native."); } + + dispatch_sync(dispatch_get_main_queue(), ^{ + block(); + }); } static void RCTUnsafeExecuteOnMainQueueOnceSync(dispatch_once_t *onceToken, dispatch_block_t block) @@ -332,19 +338,29 @@ static void RCTUnsafeExecuteOnMainQueueOnceSync(dispatch_once_t *onceToken, disp // The solution was borrowed from a post by Sophie Alpert: // https://sophiebits.com/2014/04/02/dispatch-once-initialization-on-the-main-thread // See also: https://www.mikeash.com/pyblog/friday-qa-2014-06-06-secrets-of-dispatch_once.html - if (RCTIsMainQueue()) { + auto executeOnce = ^{ dispatch_once(onceToken, block); - } else { - if (DISPATCH_EXPECT(*onceToken == 0L, NO)) { - if (facebook::react::ReactNativeFeatureFlags::disableMainQueueSyncDispatchIOS()) { - RCTLogError( - @"RCTUnsafeExecuteOnMainQueueOnceSync: Sync dispatches to the main queue can deadlock React Native."); - } - dispatch_sync(dispatch_get_main_queue(), ^{ - dispatch_once(onceToken, block); - }); + }; + + if (RCTIsMainQueue()) { + executeOnce(); + return; + } + + if (!DISPATCH_EXPECT(*onceToken == 0L, NO)) { + return; + } + + if (facebook::react::ReactNativeFeatureFlags::enableSaferMainQueueSyncDispatchOnIOS()) { + if (RCTIsJSThread()) { + facebook::react::unsafeExecuteOnMainThreadSync(block); + return; } + } else if (facebook::react::ReactNativeFeatureFlags::disableMainQueueSyncDispatchIOS()) { + RCTLogError(@"RCTUnsafeExecuteOnMainQueueOnceSync: Sync dispatches to the main queue can deadlock React Native."); } + + dispatch_sync(dispatch_get_main_queue(), executeOnce); } CGFloat RCTScreenScale(void) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt index 5fc4a09e63d59a..0cbd6e516cd936 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<24415ceea0dad0fec42af0b77ceec393>> */ /** @@ -198,6 +198,12 @@ public object ReactNativeFeatureFlags { @JvmStatic public fun enableResourceTimingAPI(): Boolean = accessor.enableResourceTimingAPI() + /** + * Make RCTUnsafeExecuteOnMainQueueSync less likely to deadlock, when used in conjuction with sync rendering/events. + */ + @JvmStatic + public fun enableSaferMainQueueSyncDispatchOnIOS(): Boolean = accessor.enableSaferMainQueueSyncDispatchOnIOS() + /** * Dispatches state updates synchronously in Fabric (e.g.: updates the scroll position in the shadow tree synchronously from the main thread). */ diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt index b93b0ad580d114..68cd02be397373 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<1a5cd689229d4e0c31070123045e84da>> + * @generated SignedSource<<07f351d4f8971f7bd7d2eb0115760565>> */ /** @@ -48,6 +48,7 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces private var enablePreparedTextLayoutCache: Boolean? = null private var enablePropsUpdateReconciliationAndroidCache: Boolean? = null private var enableResourceTimingAPICache: Boolean? = null + private var enableSaferMainQueueSyncDispatchOnIOSCache: Boolean? = null private var enableSynchronousStateUpdatesCache: Boolean? = null private var enableViewCullingCache: Boolean? = null private var enableViewRecyclingCache: Boolean? = null @@ -321,6 +322,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces return cached } + override fun enableSaferMainQueueSyncDispatchOnIOS(): Boolean { + var cached = enableSaferMainQueueSyncDispatchOnIOSCache + if (cached == null) { + cached = ReactNativeFeatureFlagsCxxInterop.enableSaferMainQueueSyncDispatchOnIOS() + enableSaferMainQueueSyncDispatchOnIOSCache = cached + } + return cached + } + override fun enableSynchronousStateUpdates(): Boolean { var cached = enableSynchronousStateUpdatesCache if (cached == null) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt index 2ec9c166d886c6..a29cc7c1e1efdb 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<> */ /** @@ -84,6 +84,8 @@ public object ReactNativeFeatureFlagsCxxInterop { @DoNotStrip @JvmStatic public external fun enableResourceTimingAPI(): Boolean + @DoNotStrip @JvmStatic public external fun enableSaferMainQueueSyncDispatchOnIOS(): Boolean + @DoNotStrip @JvmStatic public external fun enableSynchronousStateUpdates(): Boolean @DoNotStrip @JvmStatic public external fun enableViewCulling(): Boolean diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt index 86d3e2ace7d256..65361f962c0902 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<7f357475254104729cc7910c14e1c1fb>> + * @generated SignedSource<> */ /** @@ -79,6 +79,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi override fun enableResourceTimingAPI(): Boolean = false + override fun enableSaferMainQueueSyncDispatchOnIOS(): Boolean = false + override fun enableSynchronousStateUpdates(): Boolean = false override fun enableViewCulling(): Boolean = false diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt index 0ef3d9aae2039d..2761ab4fecf5d5 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<14cd1a58bd153dedda045a72c1494caa>> + * @generated SignedSource<<52f72deb1601f741295b22caa9755749>> */ /** @@ -52,6 +52,7 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc private var enablePreparedTextLayoutCache: Boolean? = null private var enablePropsUpdateReconciliationAndroidCache: Boolean? = null private var enableResourceTimingAPICache: Boolean? = null + private var enableSaferMainQueueSyncDispatchOnIOSCache: Boolean? = null private var enableSynchronousStateUpdatesCache: Boolean? = null private var enableViewCullingCache: Boolean? = null private var enableViewRecyclingCache: Boolean? = null @@ -353,6 +354,16 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc return cached } + override fun enableSaferMainQueueSyncDispatchOnIOS(): Boolean { + var cached = enableSaferMainQueueSyncDispatchOnIOSCache + if (cached == null) { + cached = currentProvider.enableSaferMainQueueSyncDispatchOnIOS() + accessedFeatureFlags.add("enableSaferMainQueueSyncDispatchOnIOS") + enableSaferMainQueueSyncDispatchOnIOSCache = cached + } + return cached + } + override fun enableSynchronousStateUpdates(): Boolean { var cached = enableSynchronousStateUpdatesCache if (cached == null) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt index 8de12fbab66e2d..eb0d5caa827f93 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<593b1d64dc31038140032a6b0a439700>> + * @generated SignedSource<<8c2ffe69841478213407e92e44519157>> */ /** @@ -79,6 +79,8 @@ public interface ReactNativeFeatureFlagsProvider { @DoNotStrip public fun enableResourceTimingAPI(): Boolean + @DoNotStrip public fun enableSaferMainQueueSyncDispatchOnIOS(): Boolean + @DoNotStrip public fun enableSynchronousStateUpdates(): Boolean @DoNotStrip public fun enableViewCulling(): Boolean diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp index a3da5669db5f42..6c2c241b872f4f 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<> */ /** @@ -207,6 +207,12 @@ class ReactNativeFeatureFlagsJavaProvider return method(javaProvider_); } + bool enableSaferMainQueueSyncDispatchOnIOS() override { + static const auto method = + getReactNativeFeatureFlagsProviderJavaClass()->getMethod("enableSaferMainQueueSyncDispatchOnIOS"); + return method(javaProvider_); + } + bool enableSynchronousStateUpdates() override { static const auto method = getReactNativeFeatureFlagsProviderJavaClass()->getMethod("enableSynchronousStateUpdates"); @@ -471,6 +477,11 @@ bool JReactNativeFeatureFlagsCxxInterop::enableResourceTimingAPI( return ReactNativeFeatureFlags::enableResourceTimingAPI(); } +bool JReactNativeFeatureFlagsCxxInterop::enableSaferMainQueueSyncDispatchOnIOS( + facebook::jni::alias_ref /*unused*/) { + return ReactNativeFeatureFlags::enableSaferMainQueueSyncDispatchOnIOS(); +} + bool JReactNativeFeatureFlagsCxxInterop::enableSynchronousStateUpdates( facebook::jni::alias_ref /*unused*/) { return ReactNativeFeatureFlags::enableSynchronousStateUpdates(); @@ -686,6 +697,9 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() { makeNativeMethod( "enableResourceTimingAPI", JReactNativeFeatureFlagsCxxInterop::enableResourceTimingAPI), + makeNativeMethod( + "enableSaferMainQueueSyncDispatchOnIOS", + JReactNativeFeatureFlagsCxxInterop::enableSaferMainQueueSyncDispatchOnIOS), makeNativeMethod( "enableSynchronousStateUpdates", JReactNativeFeatureFlagsCxxInterop::enableSynchronousStateUpdates), diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h index 5bf284c848ed16..e059075421742f 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h +++ b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<7f021510ca3f485b75e231d18cb58db9>> */ /** @@ -114,6 +114,9 @@ class JReactNativeFeatureFlagsCxxInterop static bool enableResourceTimingAPI( facebook::jni::alias_ref); + static bool enableSaferMainQueueSyncDispatchOnIOS( + facebook::jni::alias_ref); + static bool enableSynchronousStateUpdates( facebook::jni::alias_ref); diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp index 2e1048c0193730..26a6ae2847758d 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<88fdbea2f97f628187164a47a9737da0>> + * @generated SignedSource<<997a6c9e018988ae1e4b7f02bad8f433>> */ /** @@ -138,6 +138,10 @@ bool ReactNativeFeatureFlags::enableResourceTimingAPI() { return getAccessor().enableResourceTimingAPI(); } +bool ReactNativeFeatureFlags::enableSaferMainQueueSyncDispatchOnIOS() { + return getAccessor().enableSaferMainQueueSyncDispatchOnIOS(); +} + bool ReactNativeFeatureFlags::enableSynchronousStateUpdates() { return getAccessor().enableSynchronousStateUpdates(); } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h index 2426a0f371c944..d7ff98add7f631 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<> */ /** @@ -179,6 +179,11 @@ class ReactNativeFeatureFlags { */ RN_EXPORT static bool enableResourceTimingAPI(); + /** + * Make RCTUnsafeExecuteOnMainQueueSync less likely to deadlock, when used in conjuction with sync rendering/events. + */ + RN_EXPORT static bool enableSaferMainQueueSyncDispatchOnIOS(); + /** * Dispatches state updates synchronously in Fabric (e.g.: updates the scroll position in the shadow tree synchronously from the main thread). */ diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp index d57e49cc52f6c5..154c3c5786c29f 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<0c768a58bd488ed1f3061cb4c13ef78d>> + * @generated SignedSource<<0c1efaf32ef621c992aa8a3eb0ac0936>> */ /** @@ -533,6 +533,24 @@ bool ReactNativeFeatureFlagsAccessor::enableResourceTimingAPI() { return flagValue.value(); } +bool ReactNativeFeatureFlagsAccessor::enableSaferMainQueueSyncDispatchOnIOS() { + auto flagValue = enableSaferMainQueueSyncDispatchOnIOS_.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(28, "enableSaferMainQueueSyncDispatchOnIOS"); + + flagValue = currentProvider_->enableSaferMainQueueSyncDispatchOnIOS(); + enableSaferMainQueueSyncDispatchOnIOS_ = flagValue; + } + + return flagValue.value(); +} + bool ReactNativeFeatureFlagsAccessor::enableSynchronousStateUpdates() { auto flagValue = enableSynchronousStateUpdates_.load(); @@ -542,7 +560,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(28, "enableSynchronousStateUpdates"); + markFlagAsAccessed(29, "enableSynchronousStateUpdates"); flagValue = currentProvider_->enableSynchronousStateUpdates(); enableSynchronousStateUpdates_ = flagValue; @@ -560,7 +578,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(29, "enableViewCulling"); + markFlagAsAccessed(30, "enableViewCulling"); flagValue = currentProvider_->enableViewCulling(); enableViewCulling_ = flagValue; @@ -578,7 +596,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(30, "enableViewRecycling"); + markFlagAsAccessed(31, "enableViewRecycling"); flagValue = currentProvider_->enableViewRecycling(); enableViewRecycling_ = flagValue; @@ -596,7 +614,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(31, "enableViewRecyclingForText"); + markFlagAsAccessed(32, "enableViewRecyclingForText"); flagValue = currentProvider_->enableViewRecyclingForText(); enableViewRecyclingForText_ = flagValue; @@ -614,7 +632,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(32, "enableViewRecyclingForView"); + markFlagAsAccessed(33, "enableViewRecyclingForView"); flagValue = currentProvider_->enableViewRecyclingForView(); enableViewRecyclingForView_ = flagValue; @@ -632,7 +650,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(33, "fixMappingOfEventPrioritiesBetweenFabricAndReact"); + markFlagAsAccessed(34, "fixMappingOfEventPrioritiesBetweenFabricAndReact"); flagValue = currentProvider_->fixMappingOfEventPrioritiesBetweenFabricAndReact(); fixMappingOfEventPrioritiesBetweenFabricAndReact_ = flagValue; @@ -650,7 +668,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(34, "fuseboxEnabledRelease"); + markFlagAsAccessed(35, "fuseboxEnabledRelease"); flagValue = currentProvider_->fuseboxEnabledRelease(); fuseboxEnabledRelease_ = flagValue; @@ -668,7 +686,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(35, "fuseboxNetworkInspectionEnabled"); + markFlagAsAccessed(36, "fuseboxNetworkInspectionEnabled"); flagValue = currentProvider_->fuseboxNetworkInspectionEnabled(); fuseboxNetworkInspectionEnabled_ = flagValue; @@ -686,7 +704,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(36, "incorporateMaxLinesDuringAndroidLayout"); + markFlagAsAccessed(37, "incorporateMaxLinesDuringAndroidLayout"); flagValue = currentProvider_->incorporateMaxLinesDuringAndroidLayout(); incorporateMaxLinesDuringAndroidLayout_ = flagValue; @@ -704,7 +722,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(37, "traceTurboModulePromiseRejectionsOnAndroid"); + markFlagAsAccessed(38, "traceTurboModulePromiseRejectionsOnAndroid"); flagValue = currentProvider_->traceTurboModulePromiseRejectionsOnAndroid(); traceTurboModulePromiseRejectionsOnAndroid_ = flagValue; @@ -722,7 +740,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(38, "updateRuntimeShadowNodeReferencesOnCommit"); + markFlagAsAccessed(39, "updateRuntimeShadowNodeReferencesOnCommit"); flagValue = currentProvider_->updateRuntimeShadowNodeReferencesOnCommit(); updateRuntimeShadowNodeReferencesOnCommit_ = flagValue; @@ -740,7 +758,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(39, "useAlwaysAvailableJSErrorHandling"); + markFlagAsAccessed(40, "useAlwaysAvailableJSErrorHandling"); flagValue = currentProvider_->useAlwaysAvailableJSErrorHandling(); useAlwaysAvailableJSErrorHandling_ = flagValue; @@ -758,7 +776,7 @@ bool ReactNativeFeatureFlagsAccessor::useAndroidTextLayoutWidthDirectly() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(40, "useAndroidTextLayoutWidthDirectly"); + markFlagAsAccessed(41, "useAndroidTextLayoutWidthDirectly"); flagValue = currentProvider_->useAndroidTextLayoutWidthDirectly(); useAndroidTextLayoutWidthDirectly_ = flagValue; @@ -776,7 +794,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(41, "useFabricInterop"); + markFlagAsAccessed(42, "useFabricInterop"); flagValue = currentProvider_->useFabricInterop(); useFabricInterop_ = flagValue; @@ -794,7 +812,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(42, "useNativeViewConfigsInBridgelessMode"); + markFlagAsAccessed(43, "useNativeViewConfigsInBridgelessMode"); flagValue = currentProvider_->useNativeViewConfigsInBridgelessMode(); useNativeViewConfigsInBridgelessMode_ = flagValue; @@ -812,7 +830,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(43, "useOptimizedEventBatchingOnAndroid"); + markFlagAsAccessed(44, "useOptimizedEventBatchingOnAndroid"); flagValue = currentProvider_->useOptimizedEventBatchingOnAndroid(); useOptimizedEventBatchingOnAndroid_ = flagValue; @@ -830,7 +848,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(44, "useRawPropsJsiValue"); + markFlagAsAccessed(45, "useRawPropsJsiValue"); flagValue = currentProvider_->useRawPropsJsiValue(); useRawPropsJsiValue_ = flagValue; @@ -848,7 +866,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(45, "useShadowNodeStateOnClone"); + markFlagAsAccessed(46, "useShadowNodeStateOnClone"); flagValue = currentProvider_->useShadowNodeStateOnClone(); useShadowNodeStateOnClone_ = flagValue; @@ -866,7 +884,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(46, "useTurboModuleInterop"); + markFlagAsAccessed(47, "useTurboModuleInterop"); flagValue = currentProvider_->useTurboModuleInterop(); useTurboModuleInterop_ = flagValue; @@ -884,7 +902,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(47, "useTurboModules"); + markFlagAsAccessed(48, "useTurboModules"); flagValue = currentProvider_->useTurboModules(); useTurboModules_ = flagValue; diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h index bfabf21872d5b1..534ad89f8498ec 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<047302d6d44c8751f8da93e79d197a34>> */ /** @@ -60,6 +60,7 @@ class ReactNativeFeatureFlagsAccessor { bool enablePreparedTextLayout(); bool enablePropsUpdateReconciliationAndroid(); bool enableResourceTimingAPI(); + bool enableSaferMainQueueSyncDispatchOnIOS(); bool enableSynchronousStateUpdates(); bool enableViewCulling(); bool enableViewRecycling(); @@ -91,7 +92,7 @@ class ReactNativeFeatureFlagsAccessor { std::unique_ptr currentProvider_; bool wasOverridden_; - std::array, 48> accessedFeatureFlags_; + std::array, 49> accessedFeatureFlags_; std::atomic> commonTestFlag_; std::atomic> animatedShouldSignalBatch_; @@ -121,6 +122,7 @@ class ReactNativeFeatureFlagsAccessor { std::atomic> enablePreparedTextLayout_; std::atomic> enablePropsUpdateReconciliationAndroid_; std::atomic> enableResourceTimingAPI_; + std::atomic> enableSaferMainQueueSyncDispatchOnIOS_; std::atomic> enableSynchronousStateUpdates_; std::atomic> enableViewCulling_; std::atomic> enableViewRecycling_; diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h index 2e631fa15b0702..60782862f4810a 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<829c85b56fbeaf01ece2d1dd84ecde8e>> + * @generated SignedSource<> */ /** @@ -139,6 +139,10 @@ class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider { return false; } + bool enableSaferMainQueueSyncDispatchOnIOS() override { + return false; + } + bool enableSynchronousStateUpdates() override { return false; } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h index 032e8980c65f5f..86116f8f0af722 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<> */ /** @@ -297,6 +297,15 @@ class ReactNativeFeatureFlagsDynamicProvider : public ReactNativeFeatureFlagsDef return ReactNativeFeatureFlagsDefaults::enableResourceTimingAPI(); } + bool enableSaferMainQueueSyncDispatchOnIOS() override { + auto value = values_["enableSaferMainQueueSyncDispatchOnIOS"]; + if (!value.isNull()) { + return value.getBool(); + } + + return ReactNativeFeatureFlagsDefaults::enableSaferMainQueueSyncDispatchOnIOS(); + } + bool enableSynchronousStateUpdates() override { auto value = values_["enableSynchronousStateUpdates"]; if (!value.isNull()) { diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h index 6ebab81a832e05..29a71e2b9b11da 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<25e38ea9e85a704f2f5ab6c7b8cb3062>> + * @generated SignedSource<<98e9c20fe45093aa36cb2014354e8a80>> */ /** @@ -53,6 +53,7 @@ class ReactNativeFeatureFlagsProvider { virtual bool enablePreparedTextLayout() = 0; virtual bool enablePropsUpdateReconciliationAndroid() = 0; virtual bool enableResourceTimingAPI() = 0; + virtual bool enableSaferMainQueueSyncDispatchOnIOS() = 0; virtual bool enableSynchronousStateUpdates() = 0; virtual bool enableViewCulling() = 0; virtual bool enableViewRecycling() = 0; diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp index 1da8bd984875d4..0428d836706e2c 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<3c85cdc2dc3d2368d09a5c0aff4d083a>> + * @generated SignedSource<<47bf477394a93de7dc6996fd8620e5ae>> */ /** @@ -184,6 +184,11 @@ bool NativeReactNativeFeatureFlags::enableResourceTimingAPI( return ReactNativeFeatureFlags::enableResourceTimingAPI(); } +bool NativeReactNativeFeatureFlags::enableSaferMainQueueSyncDispatchOnIOS( + jsi::Runtime& /*runtime*/) { + return ReactNativeFeatureFlags::enableSaferMainQueueSyncDispatchOnIOS(); +} + bool NativeReactNativeFeatureFlags::enableSynchronousStateUpdates( jsi::Runtime& /*runtime*/) { return ReactNativeFeatureFlags::enableSynchronousStateUpdates(); diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h index 3870d136f337d0..fff36841cc9c13 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h +++ b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<8b44ca0cfe8178ad7fab4f9a879ae7ae>> */ /** @@ -93,6 +93,8 @@ class NativeReactNativeFeatureFlags bool enableResourceTimingAPI(jsi::Runtime& runtime); + bool enableSaferMainQueueSyncDispatchOnIOS(jsi::Runtime& runtime); + bool enableSynchronousStateUpdates(jsi::Runtime& runtime); bool enableViewCulling(jsi::Runtime& runtime); diff --git a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Legacy.cpp b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Legacy.cpp index 24b2ae0ae9f297..0b4a11a5d0e7c8 100644 --- a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Legacy.cpp +++ b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Legacy.cpp @@ -8,6 +8,7 @@ #include "RuntimeScheduler_Legacy.h" #include "SchedulerPriorityUtils.h" +#include #include #include #include diff --git a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Modern.cpp b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Modern.cpp index 9f90a6bb21ff07..d1e256ea967de6 100644 --- a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Modern.cpp +++ b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Modern.cpp @@ -8,6 +8,7 @@ #include "RuntimeScheduler_Modern.h" #include "SchedulerPriorityUtils.h" +#include #include #include #include diff --git a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/tests/RuntimeSchedulerTest.cpp b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/tests/RuntimeSchedulerTest.cpp index 0fdff1b072f8ee..88ad9ebe8d3996 100644 --- a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/tests/RuntimeSchedulerTest.cpp +++ b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/tests/RuntimeSchedulerTest.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include "StubClock.h" diff --git a/packages/react-native/ReactCommon/react/runtime/BufferedRuntimeExecutor.h b/packages/react-native/ReactCommon/react/runtime/BufferedRuntimeExecutor.h index c13e7aa459ea9a..6606e492ec5f8a 100644 --- a/packages/react-native/ReactCommon/react/runtime/BufferedRuntimeExecutor.h +++ b/packages/react-native/ReactCommon/react/runtime/BufferedRuntimeExecutor.h @@ -10,6 +10,7 @@ #include #include #include +#include #include namespace facebook::react { diff --git a/packages/react-native/ReactCommon/runtimeexecutor/CMakeLists.txt b/packages/react-native/ReactCommon/runtimeexecutor/CMakeLists.txt index ca883784338da9..dd4485956fa2ef 100644 --- a/packages/react-native/ReactCommon/runtimeexecutor/CMakeLists.txt +++ b/packages/react-native/ReactCommon/runtimeexecutor/CMakeLists.txt @@ -8,11 +8,11 @@ set(CMAKE_VERBOSE_MAKEFILE on) include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake) -file(GLOB_RECURSE runtimeexecutor_SRC CONFIGURE_DEPENDS *.cpp *.h) +file(GLOB_RECURSE runtimeexecutor_SRC CONFIGURE_DEPENDS *.cpp *.h ${CMAKE_CURRENT_SOURCE_DIR}/platform/default/*.cpp) add_library(runtimeexecutor OBJECT ${runtimeexecutor_SRC}) -target_include_directories(runtimeexecutor PUBLIC .) +target_include_directories(runtimeexecutor PUBLIC . ${CMAKE_CURRENT_SOURCE_DIR}/platform/default/) target_link_libraries(runtimeexecutor jsi) target_compile_reactnative_options(runtimeexecutor PRIVATE) diff --git a/packages/react-native/ReactCommon/runtimeexecutor/React-runtimeexecutor.podspec b/packages/react-native/ReactCommon/runtimeexecutor/React-runtimeexecutor.podspec index c6fb40df8de743..3288769244e376 100644 --- a/packages/react-native/ReactCommon/runtimeexecutor/React-runtimeexecutor.podspec +++ b/packages/react-native/ReactCommon/runtimeexecutor/React-runtimeexecutor.podspec @@ -17,6 +17,12 @@ else end Pod::Spec.new do |s| + source_files = "ReactCommon/*.{m,mm,cpp,h}", "platform/ios/**/*.{m,mm,cpp,h}" + header_search_paths = [ + "\"$(PODS_TARGET_SRCROOT)\"", + "\"$(PODS_TARGET_SRCROOT)/platform/ios\"" + ] + s.name = "React-runtimeexecutor" s.version = version s.summary = "-" # TODO @@ -25,8 +31,19 @@ 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}" + s.source_files = source_files s.header_dir = "ReactCommon" + if ENV['USE_FRAMEWORKS'] + s.module_name = "React-runtimeexecutor" + 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 end diff --git a/packages/react-native/ReactCommon/runtimeexecutor/ReactCommon/RuntimeExecutor.h b/packages/react-native/ReactCommon/runtimeexecutor/ReactCommon/RuntimeExecutor.h index 67dee4b592977f..f8f69b54343ee8 100644 --- a/packages/react-native/ReactCommon/runtimeexecutor/ReactCommon/RuntimeExecutor.h +++ b/packages/react-native/ReactCommon/runtimeexecutor/ReactCommon/RuntimeExecutor.h @@ -7,9 +7,6 @@ #pragma once -#include -#include - #include namespace facebook::react { @@ -25,73 +22,4 @@ namespace facebook::react { using RuntimeExecutor = std::function&& callback)>; -/* - * Executes a `callback` in a *synchronous* manner on the same thread using - * given `RuntimeExecutor`. - * Use this method when the caller needs to *be blocked* by executing the - * `callback` and requires that the callback will be executed on the same - * thread. - * Example order of events (when not a sync call in runtimeExecutor callback): - * - [UI thread] Lock all mutexes at start - * - [UI thread] runtimeCaptured.lock before callback - * - [JS thread] Set runtimePtr in runtimeExecutor callback - * - [JS thread] runtimeCaptured.unlock in runtimeExecutor callback - * - [UI thread] Call callback - * - [JS thread] callbackExecuted.lock in runtimeExecutor callback - * - [UI thread] callbackExecuted.unlock after callback - * - [UI thread] jsBlockExecuted.lock after callback - * - [JS thread] jsBlockExecuted.unlock in runtimeExecutor callback - */ -inline static void executeSynchronouslyOnSameThread_CAN_DEADLOCK( - const RuntimeExecutor& runtimeExecutor, - std::function&& callback) noexcept { - // Note: We need the third mutex to get back to the main thread before - // the lambda is finished (because all mutexes are allocated on the stack). - - std::mutex runtimeCaptured; - std::mutex callbackExecuted; - std::mutex jsBlockExecuted; - - runtimeCaptured.lock(); - callbackExecuted.lock(); - jsBlockExecuted.lock(); - - jsi::Runtime* runtimePtr; - - auto threadId = std::this_thread::get_id(); - - runtimeExecutor([&](jsi::Runtime& runtime) { - runtimePtr = &runtime; - - if (threadId == std::this_thread::get_id()) { - // In case of a synchronous call, we should unlock mutexes and return. - runtimeCaptured.unlock(); - jsBlockExecuted.unlock(); - return; - } - - runtimeCaptured.unlock(); - // `callback` is called somewhere here. - callbackExecuted.lock(); - jsBlockExecuted.unlock(); - }); - - runtimeCaptured.lock(); - callback(*runtimePtr); - callbackExecuted.unlock(); - jsBlockExecuted.lock(); -} - -template -inline static DataT executeSynchronouslyOnSameThread_CAN_DEADLOCK( - const RuntimeExecutor& runtimeExecutor, - std::function&& callback) noexcept { - DataT data; - - executeSynchronouslyOnSameThread_CAN_DEADLOCK( - runtimeExecutor, - [&](jsi::Runtime& runtime) { data = callback(runtime); }); - - return data; -} } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/runtimeexecutor/platform/default/ReactCommon/RuntimeExecutorSyncUIThreadUtils.cpp b/packages/react-native/ReactCommon/runtimeexecutor/platform/default/ReactCommon/RuntimeExecutorSyncUIThreadUtils.cpp new file mode 100644 index 00000000000000..d5dde44cd3b2ed --- /dev/null +++ b/packages/react-native/ReactCommon/runtimeexecutor/platform/default/ReactCommon/RuntimeExecutorSyncUIThreadUtils.cpp @@ -0,0 +1,69 @@ +/* + * 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 +#include +#include + +namespace facebook::react { +/** + * Example order of events (when not a sync call in runtimeExecutor + * jsWork): + * - [UI thread] Lock all mutexes at start + * - [UI thread] Schedule "runtime capture block" on js thread + * - [UI thread] Wait for runtime capture: runtimeCaptured.lock() + * - [JS thread] Capture runtime by setting runtimePtr + * - [JS thread] Signal runtime captured: runtimeCaptured.unlock() + * - [UI thread] Call jsWork using runtimePtr + * - [JS thread] Wait until jsWork done: jsWorkDone.lock() + * - [UI thread] Signal jsWork done: jsWorkDone.unlock() + * - [UI thread] Wait until runtime capture block finished: + * runtimeCaptureBlockDone.lock() + * - [JS thread] Signal runtime capture block is finished: + * runtimeCaptureBlockDone.unlock() + */ +void executeSynchronouslyOnSameThread_CAN_DEADLOCK( + const RuntimeExecutor& runtimeExecutor, + std::function&& jsWork) noexcept { + // Note: We need the third mutex to get back to the main thread before + // the lambda is finished (because all mutexes are allocated on the stack). + + std::mutex runtimeCaptured; + std::mutex jsWorkDone; + std::mutex runtimeCaptureBlockDone; + + runtimeCaptured.lock(); + jsWorkDone.lock(); + runtimeCaptureBlockDone.lock(); + + jsi::Runtime* runtimePtr; + + auto threadId = std::this_thread::get_id(); + auto runtimeCaptureBlock = [&](jsi::Runtime& runtime) { + runtimePtr = &runtime; + + if (threadId == std::this_thread::get_id()) { + // In case of a synchronous call, we should unlock mutexes and return. + runtimeCaptured.unlock(); + runtimeCaptureBlockDone.unlock(); + return; + } + + runtimeCaptured.unlock(); + // `jsWork` is called somewhere here. + jsWorkDone.lock(); + runtimeCaptureBlockDone.unlock(); + }; + runtimeExecutor(std::move(runtimeCaptureBlock)); + + runtimeCaptured.lock(); + jsWork(*runtimePtr); + jsWorkDone.unlock(); + runtimeCaptureBlockDone.lock(); +} + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/runtimeexecutor/platform/default/ReactCommon/RuntimeExecutorSyncUIThreadUtils.h b/packages/react-native/ReactCommon/runtimeexecutor/platform/default/ReactCommon/RuntimeExecutorSyncUIThreadUtils.h new file mode 100644 index 00000000000000..3345561f668f55 --- /dev/null +++ b/packages/react-native/ReactCommon/runtimeexecutor/platform/default/ReactCommon/RuntimeExecutorSyncUIThreadUtils.h @@ -0,0 +1,39 @@ +/* + * 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 + +#include + +namespace facebook::react { + +/* + * Executes a `callback` in a *synchronous* manner on the same thread using + * given `RuntimeExecutor`. + * Use this method when the caller needs to *be blocked* by executing the + * `callback` and requires that the callback will be executed on the same + * thread. + */ +void executeSynchronouslyOnSameThread_CAN_DEADLOCK( + const RuntimeExecutor& runtimeExecutor, + std::function&& callback) noexcept; + +template +inline static DataT executeSynchronouslyOnSameThread_CAN_DEADLOCK( + const RuntimeExecutor& runtimeExecutor, + std::function&& callback) noexcept { + DataT data; + + executeSynchronouslyOnSameThread_CAN_DEADLOCK( + runtimeExecutor, + [&](jsi::Runtime& runtime) { data = callback(runtime); }); + + return data; +} +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/runtimeexecutor/platform/ios/ReactCommon/RuntimeExecutorSyncUIThreadUtils.h b/packages/react-native/ReactCommon/runtimeexecutor/platform/ios/ReactCommon/RuntimeExecutorSyncUIThreadUtils.h new file mode 100644 index 00000000000000..3aea9bf9fa6f11 --- /dev/null +++ b/packages/react-native/ReactCommon/runtimeexecutor/platform/ios/ReactCommon/RuntimeExecutorSyncUIThreadUtils.h @@ -0,0 +1,42 @@ +/* + * 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 + +#include + +namespace facebook::react { + +/* + * Executes a `callback` in a *synchronous* manner on the same thread using + * given `RuntimeExecutor`. + * Use this method when the caller needs to *be blocked* by executing the + * `callback` and requires that the callback will be executed on the same + * thread. + */ +void executeSynchronouslyOnSameThread_CAN_DEADLOCK( + const RuntimeExecutor& runtimeExecutor, + std::function&& callback) noexcept; + +template +inline static DataT executeSynchronouslyOnSameThread_CAN_DEADLOCK( + const RuntimeExecutor& runtimeExecutor, + std::function&& callback) noexcept { + DataT data; + + executeSynchronouslyOnSameThread_CAN_DEADLOCK( + runtimeExecutor, + [&](jsi::Runtime& runtime) { data = callback(runtime); }); + + return data; +} + +void unsafeExecuteOnMainThreadSync(std::function runnable); + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/runtimeexecutor/platform/ios/ReactCommon/RuntimeExecutorSyncUIThreadUtils.mm b/packages/react-native/ReactCommon/runtimeexecutor/platform/ios/ReactCommon/RuntimeExecutorSyncUIThreadUtils.mm new file mode 100644 index 00000000000000..33f1428e48cff4 --- /dev/null +++ b/packages/react-native/ReactCommon/runtimeexecutor/platform/ios/ReactCommon/RuntimeExecutorSyncUIThreadUtils.mm @@ -0,0 +1,216 @@ +/* + * 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 + +#import +#import +#import +#import +#import +#import +#import + +namespace facebook::react { +namespace { +struct UITask { + std::promise _isDone; + std::mutex _mutex; + std::function _uiWork; + bool _isRunning; + std::atomic_bool _wasStarted; + + public: + UITask(std::function uiWork) : _uiWork(uiWork), _isRunning(false), _wasStarted(false) {} + + void run() + { + bool expected = false; + if (!_wasStarted.compare_exchange_strong(expected, true)) { + return; + } + OnScopeExit onScopeExit(^{ + _uiWork = nil; + _isRunning = false; + _isDone.set_value(); + }); + _isRunning = true; + _uiWork(); + } + + bool wasStarted() + { + return _wasStarted.load(); + } + + bool isRunning() + { + return _isRunning; + } + + std::future getFuture() + { + return _isDone.get_future(); + } +}; + +std::mutex &g_mutex() +{ + static std::mutex mutex; + return mutex; +} + +std::condition_variable &g_cv() +{ + static std::condition_variable cv; + return cv; +} + +std::shared_ptr &g_postedUITask() +{ + static std::shared_ptr postedUITask; + return postedUITask; +} + +void runPostedUITask() +{ + OnScopeExit onScopeExit([&]() { g_postedUITask() = nullptr; }); + g_postedUITask()->run(); +} + +bool isJSThread() +{ + return [[NSThread currentThread].name containsString:@"JavaScript"]; +} + +void saferExecuteSynchronouslyOnSameThread_CAN_DEADLOCK( + const RuntimeExecutor &runtimeExecutor, + std::function &&jsWork) noexcept +{ + react_native_assert([[NSThread currentThread] isMainThread] && (!g_postedUITask() || !g_postedUITask()->isRunning())); + + jsi::Runtime *runtime = nullptr; + std::mutex jsWorkDone; + + jsWorkDone.lock(); + OnScopeExit onScopeExit([&]() { jsWorkDone.unlock(); }); + + { + std::unique_lock lock(_mutex); + if (g_postedUITask()) { + runPostedUITask(); + } + + runtimeExecutor([&](jsi::Runtime &rt) { + runtime = &rt; + _cv.notify_one(); + + // Block the js thread until jsWork finishes on calling thread + jsWorkDone.lock(); + }); + + while (true) { + _cv.wait(lock, [&] { return runtime != nullptr || g_postedUITask() != nullptr; }); + + if (g_postedUITask() != nullptr) { + runPostedUITask(); + } else { + break; + } + } + } + + jsWork(*runtime); +} + +/** + * Example order of events (when not a sync call in runtimeExecutor + * jsWork): + * - [UI thread] Lock all mutexes at start + * - [UI thread] Schedule "runtime capture block" on js thread + * - [UI thread] Wait for runtime capture: runtimeCaptured.lock() + * - [JS thread] Capture runtime by setting runtimePtr + * - [JS thread] Signal runtime captured: runtimeCaptured.unlock() + * - [UI thread] Call jsWork using runtimePtr + * - [JS thread] Wait until jsWork done: jsWorkDone.lock() + * - [UI thread] Signal jsWork done: jsWorkDone.unlock() + * - [UI thread] Wait until runtime capture block finished: + * runtimeCaptureBlockDone.lock() + * - [JS thread] Signal runtime capture block is finished: + * runtimeCaptureBlockDone.unlock() + */ +void legacyExecuteSynchronouslyOnSameThread_CAN_DEADLOCK( + const RuntimeExecutor &runtimeExecutor, + std::function &&jsWork) noexcept +{ + // Note: We need the third mutex to get back to the main thread before + // the lambda is finished (because all mutexes are allocated on the stack). + + std::mutex runtimeCaptured; + std::mutex jsWorkDone; + std::mutex runtimeCaptureBlockDone; + + runtimeCaptured.lock(); + jsWorkDone.lock(); + runtimeCaptureBlockDone.lock(); + + jsi::Runtime *runtimePtr = nullptr; + + auto threadId = std::this_thread::get_id(); + auto runtimeCaptureBlock = [&](jsi::Runtime &runtime) { + runtimePtr = &runtime; + + if (threadId == std::this_thread::get_id()) { + // In case of a synchronous call, we should unlock mutexes and return. + runtimeCaptured.unlock(); + runtimeCaptureBlockDone.unlock(); + return; + } + + runtimeCaptured.unlock(); + // `jsWork` is called somewhere here. + jsWorkDone.lock(); + runtimeCaptureBlockDone.unlock(); + }; + runtimeExecutor(std::move(runtimeCaptureBlock)); + + runtimeCaptured.lock(); + jsWork(*runtimePtr); + jsWorkDone.unlock(); + runtimeCaptureBlockDone.lock(); +} + +} // namespace + +void executeSynchronouslyOnSameThread_CAN_DEADLOCK( + const RuntimeExecutor &runtimeExecutor, + std::function &&jsWork) noexcept +{ + if (ReactNativeFeatureFlags::enableSaferMainQueueSyncDispatchOnIOS()) { + saferExecuteSynchronouslyOnSameThread_CAN_DEADLOCK(runtimeExecutor, std::move(jsWork)); + } else { + legacyExecuteSynchronouslyOnSameThread_CAN_DEADLOCK(runtimeExecutor, std::move(jsWork)); + } +} + +void unsafeExecuteOnMainThreadSync(std::function work) +{ + react_native_assert(isJSThread()); + std::lock_guard lock(g_mutex()); + react_native_assert(!g_postedUITask() || g_postedUITask()->wasStarted()); + + auto uiTask = std::make_shared(work); + dispatch_async(dispatch_get_main_queue(), ^{ + uiTask->run(); + }); + + g_postedUITask() = uiTask; + g_cv().notify_one(); + uiTask->getFuture().wait(); +} + +} // namespace facebook::react diff --git a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js index 36b17fbcf436ab..adc3c32290a9a7 100644 --- a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js +++ b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js @@ -340,6 +340,17 @@ const definitions: FeatureFlagDefinitions = { }, ossReleaseStage: 'none', }, + enableSaferMainQueueSyncDispatchOnIOS: { + defaultValue: false, + metadata: { + dateAdded: '2025-05-17', + description: + 'Make RCTUnsafeExecuteOnMainQueueSync less likely to deadlock, when used in conjuction with sync rendering/events.', + expectedReleaseValue: true, + purpose: 'experimentation', + }, + ossReleaseStage: 'none', + }, enableSynchronousStateUpdates: { defaultValue: false, metadata: { diff --git a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js index 5bc924dec8fb67..60ca09af134ab4 100644 --- a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js +++ b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<29691b2062c240377834a01ced24c71c>> + * @generated SignedSource<<408472df13d804c9820ba14376853710>> * @flow strict * @noformat */ @@ -79,6 +79,7 @@ export type ReactNativeFeatureFlags = $ReadOnly<{ enablePreparedTextLayout: Getter, enablePropsUpdateReconciliationAndroid: Getter, enableResourceTimingAPI: Getter, + enableSaferMainQueueSyncDispatchOnIOS: Getter, enableSynchronousStateUpdates: Getter, enableViewCulling: Getter, enableViewRecycling: Getter, @@ -297,6 +298,10 @@ export const enablePropsUpdateReconciliationAndroid: Getter = createNat * Enables the reporting of network resource timings through `PerformanceObserver`. */ export const enableResourceTimingAPI: Getter = createNativeFlagGetter('enableResourceTimingAPI', false); +/** + * Make RCTUnsafeExecuteOnMainQueueSync less likely to deadlock, when used in conjuction with sync rendering/events. + */ +export const enableSaferMainQueueSyncDispatchOnIOS: Getter = createNativeFlagGetter('enableSaferMainQueueSyncDispatchOnIOS', false); /** * Dispatches state updates synchronously in Fabric (e.g.: updates the scroll position in the shadow tree synchronously from the main thread). */ diff --git a/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js index 48f06fd308b082..fee9e1b6c874ef 100644 --- a/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js +++ b/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<59ebee6019b99a8c6a29dfc1eb036194>> + * @generated SignedSource<<0b02bc02393f40b467ce1d70e31aeec3>> * @flow strict * @noformat */ @@ -53,6 +53,7 @@ export interface Spec extends TurboModule { +enablePreparedTextLayout?: () => boolean; +enablePropsUpdateReconciliationAndroid?: () => boolean; +enableResourceTimingAPI?: () => boolean; + +enableSaferMainQueueSyncDispatchOnIOS?: () => boolean; +enableSynchronousStateUpdates?: () => boolean; +enableViewCulling?: () => boolean; +enableViewRecycling?: () => boolean;