Throw exception with name of TurboModule instead of deadlock during sync rendering (#49509)

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

Currently if we hit a deadlock in sync rendering due to a TurboModule initialization that requires main queue setup we don't get any information about which TurboModule caused the issue.

To help us know which TurboModules we need to fix, this instead will crash with the name of the TurboModule.

Changelog: [Internal]

Reviewed By: sammy-SC

Differential Revision: D69805065

fbshipit-source-id: f75df44f9a603a5f53a008382d32b2b5285c1162
This commit is contained in:
Liron Yahdav
2025-02-19 18:45:23 -08:00
committed by Facebook GitHub Bot
parent 778382ad3d
commit 5e63f34ab0
24 changed files with 210 additions and 31 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<<6e8144a48ab65f3b5ad8dd601cee2114>>
* @generated SignedSource<<99417ea22d1bf969d71388c600d42e85>>
*/
/**
@@ -226,6 +226,12 @@ public object ReactNativeFeatureFlags {
@JvmStatic
public fun lazyAnimationCallbacks(): Boolean = accessor.lazyAnimationCallbacks()
/**
* Throw an exception instead of deadlocking when a TurboModule that requires main queue setup is initialized during a synchronous render on iOS.
*/
@JvmStatic
public fun throwExceptionInsteadOfDeadlockOnTurboModuleSetupDuringSyncRenderIOS(): Boolean = accessor.throwExceptionInsteadOfDeadlockOnTurboModuleSetupDuringSyncRenderIOS()
/**
* Enables storing js caller stack when creating promise in native module. This is useful in case of Promise rejection and tracing the cause.
*/
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<dd2fa7d4fb722350d77094f8bcc38872>>
* @generated SignedSource<<3ede8912be1a2874050bab4c88a0a416>>
*/
/**
@@ -53,6 +53,7 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
private var fuseboxEnabledReleaseCache: Boolean? = null
private var fuseboxNetworkInspectionEnabledCache: Boolean? = null
private var lazyAnimationCallbacksCache: Boolean? = null
private var throwExceptionInsteadOfDeadlockOnTurboModuleSetupDuringSyncRenderIOSCache: Boolean? = null
private var traceTurboModulePromiseRejectionsOnAndroidCache: Boolean? = null
private var useAlwaysAvailableJSErrorHandlingCache: Boolean? = null
private var useEditTextStockAndroidFocusBehaviorCache: Boolean? = null
@@ -360,6 +361,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
return cached
}
override fun throwExceptionInsteadOfDeadlockOnTurboModuleSetupDuringSyncRenderIOS(): Boolean {
var cached = throwExceptionInsteadOfDeadlockOnTurboModuleSetupDuringSyncRenderIOSCache
if (cached == null) {
cached = ReactNativeFeatureFlagsCxxInterop.throwExceptionInsteadOfDeadlockOnTurboModuleSetupDuringSyncRenderIOS()
throwExceptionInsteadOfDeadlockOnTurboModuleSetupDuringSyncRenderIOSCache = cached
}
return cached
}
override fun traceTurboModulePromiseRejectionsOnAndroid(): Boolean {
var cached = traceTurboModulePromiseRejectionsOnAndroidCache
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<<eb52a136ba489460c2131105f4cc5b3f>>
* @generated SignedSource<<33adb23bfdb99c531c674f4c6d09ddbd>>
*/
/**
@@ -94,6 +94,8 @@ public object ReactNativeFeatureFlagsCxxInterop {
@DoNotStrip @JvmStatic public external fun lazyAnimationCallbacks(): Boolean
@DoNotStrip @JvmStatic public external fun throwExceptionInsteadOfDeadlockOnTurboModuleSetupDuringSyncRenderIOS(): Boolean
@DoNotStrip @JvmStatic public external fun traceTurboModulePromiseRejectionsOnAndroid(): Boolean
@DoNotStrip @JvmStatic public external fun useAlwaysAvailableJSErrorHandling(): 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<<5315bd93234b8c00b7d029081231eeeb>>
* @generated SignedSource<<94ffd31d788dae979b92c7a347f2ad40>>
*/
/**
@@ -89,6 +89,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi
override fun lazyAnimationCallbacks(): Boolean = false
override fun throwExceptionInsteadOfDeadlockOnTurboModuleSetupDuringSyncRenderIOS(): Boolean = false
override fun traceTurboModulePromiseRejectionsOnAndroid(): Boolean = false
override fun useAlwaysAvailableJSErrorHandling(): 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<<a97afc0515bcfc2e4ed60565af08ce8a>>
* @generated SignedSource<<15176fdf014740aa34039cfd82943ae2>>
*/
/**
@@ -57,6 +57,7 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
private var fuseboxEnabledReleaseCache: Boolean? = null
private var fuseboxNetworkInspectionEnabledCache: Boolean? = null
private var lazyAnimationCallbacksCache: Boolean? = null
private var throwExceptionInsteadOfDeadlockOnTurboModuleSetupDuringSyncRenderIOSCache: Boolean? = null
private var traceTurboModulePromiseRejectionsOnAndroidCache: Boolean? = null
private var useAlwaysAvailableJSErrorHandlingCache: Boolean? = null
private var useEditTextStockAndroidFocusBehaviorCache: Boolean? = null
@@ -397,6 +398,16 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
return cached
}
override fun throwExceptionInsteadOfDeadlockOnTurboModuleSetupDuringSyncRenderIOS(): Boolean {
var cached = throwExceptionInsteadOfDeadlockOnTurboModuleSetupDuringSyncRenderIOSCache
if (cached == null) {
cached = currentProvider.throwExceptionInsteadOfDeadlockOnTurboModuleSetupDuringSyncRenderIOS()
accessedFeatureFlags.add("throwExceptionInsteadOfDeadlockOnTurboModuleSetupDuringSyncRenderIOS")
throwExceptionInsteadOfDeadlockOnTurboModuleSetupDuringSyncRenderIOSCache = cached
}
return cached
}
override fun traceTurboModulePromiseRejectionsOnAndroid(): Boolean {
var cached = traceTurboModulePromiseRejectionsOnAndroidCache
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<<51121a77716b7748f993496eff5f6bfb>>
* @generated SignedSource<<98ad5281b51418af43620a89a40a3c45>>
*/
/**
@@ -89,6 +89,8 @@ public interface ReactNativeFeatureFlagsProvider {
@DoNotStrip public fun lazyAnimationCallbacks(): Boolean
@DoNotStrip public fun throwExceptionInsteadOfDeadlockOnTurboModuleSetupDuringSyncRenderIOS(): Boolean
@DoNotStrip public fun traceTurboModulePromiseRejectionsOnAndroid(): Boolean
@DoNotStrip public fun useAlwaysAvailableJSErrorHandling(): 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<<b0dab4690bff769baf4e83f508a6e514>>
* @generated SignedSource<<b02f04930c4c998668d6ad522717f914>>
*/
/**
@@ -237,6 +237,12 @@ class ReactNativeFeatureFlagsProviderHolder
return method(javaProvider_);
}
bool throwExceptionInsteadOfDeadlockOnTurboModuleSetupDuringSyncRenderIOS() override {
static const auto method =
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("throwExceptionInsteadOfDeadlockOnTurboModuleSetupDuringSyncRenderIOS");
return method(javaProvider_);
}
bool traceTurboModulePromiseRejectionsOnAndroid() override {
static const auto method =
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("traceTurboModulePromiseRejectionsOnAndroid");
@@ -460,6 +466,11 @@ bool JReactNativeFeatureFlagsCxxInterop::lazyAnimationCallbacks(
return ReactNativeFeatureFlags::lazyAnimationCallbacks();
}
bool JReactNativeFeatureFlagsCxxInterop::throwExceptionInsteadOfDeadlockOnTurboModuleSetupDuringSyncRenderIOS(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
return ReactNativeFeatureFlags::throwExceptionInsteadOfDeadlockOnTurboModuleSetupDuringSyncRenderIOS();
}
bool JReactNativeFeatureFlagsCxxInterop::traceTurboModulePromiseRejectionsOnAndroid(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
return ReactNativeFeatureFlags::traceTurboModulePromiseRejectionsOnAndroid();
@@ -635,6 +646,9 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() {
makeNativeMethod(
"lazyAnimationCallbacks",
JReactNativeFeatureFlagsCxxInterop::lazyAnimationCallbacks),
makeNativeMethod(
"throwExceptionInsteadOfDeadlockOnTurboModuleSetupDuringSyncRenderIOS",
JReactNativeFeatureFlagsCxxInterop::throwExceptionInsteadOfDeadlockOnTurboModuleSetupDuringSyncRenderIOS),
makeNativeMethod(
"traceTurboModulePromiseRejectionsOnAndroid",
JReactNativeFeatureFlagsCxxInterop::traceTurboModulePromiseRejectionsOnAndroid),
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<0bf1c23eda1c41a60eaae19e87c541e9>>
* @generated SignedSource<<a34eb273660e3fecbc6d9fba66176462>>
*/
/**
@@ -129,6 +129,9 @@ class JReactNativeFeatureFlagsCxxInterop
static bool lazyAnimationCallbacks(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
static bool throwExceptionInsteadOfDeadlockOnTurboModuleSetupDuringSyncRenderIOS(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
static bool traceTurboModulePromiseRejectionsOnAndroid(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<0a3119b99e455127d8a404963a23eb96>>
* @generated SignedSource<<5ea68e0bf3cbf7822d7a3e80f7dffc72>>
*/
/**
@@ -158,6 +158,10 @@ bool ReactNativeFeatureFlags::lazyAnimationCallbacks() {
return getAccessor().lazyAnimationCallbacks();
}
bool ReactNativeFeatureFlags::throwExceptionInsteadOfDeadlockOnTurboModuleSetupDuringSyncRenderIOS() {
return getAccessor().throwExceptionInsteadOfDeadlockOnTurboModuleSetupDuringSyncRenderIOS();
}
bool ReactNativeFeatureFlags::traceTurboModulePromiseRejectionsOnAndroid() {
return getAccessor().traceTurboModulePromiseRejectionsOnAndroid();
}
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<b354e1b9ffc58e74ffff200c14728d1d>>
* @generated SignedSource<<9be3692ca63703f32de54e24d4ceb6e9>>
*/
/**
@@ -204,6 +204,11 @@ class ReactNativeFeatureFlags {
*/
RN_EXPORT static bool lazyAnimationCallbacks();
/**
* Throw an exception instead of deadlocking when a TurboModule that requires main queue setup is initialized during a synchronous render on iOS.
*/
RN_EXPORT static bool throwExceptionInsteadOfDeadlockOnTurboModuleSetupDuringSyncRenderIOS();
/**
* Enables storing js caller stack when creating promise in native module. This is useful in case of Promise rejection and tracing the cause.
*/
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<41f3b407b98e268ba80f8cd8bb960321>>
* @generated SignedSource<<12be545f9fb06b76cb6c521e1d381bf1>>
*/
/**
@@ -623,6 +623,24 @@ bool ReactNativeFeatureFlagsAccessor::lazyAnimationCallbacks() {
return flagValue.value();
}
bool ReactNativeFeatureFlagsAccessor::throwExceptionInsteadOfDeadlockOnTurboModuleSetupDuringSyncRenderIOS() {
auto flagValue = throwExceptionInsteadOfDeadlockOnTurboModuleSetupDuringSyncRenderIOS_.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(33, "throwExceptionInsteadOfDeadlockOnTurboModuleSetupDuringSyncRenderIOS");
flagValue = currentProvider_->throwExceptionInsteadOfDeadlockOnTurboModuleSetupDuringSyncRenderIOS();
throwExceptionInsteadOfDeadlockOnTurboModuleSetupDuringSyncRenderIOS_ = flagValue;
}
return flagValue.value();
}
bool ReactNativeFeatureFlagsAccessor::traceTurboModulePromiseRejectionsOnAndroid() {
auto flagValue = traceTurboModulePromiseRejectionsOnAndroid_.load();
@@ -632,7 +650,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(33, "traceTurboModulePromiseRejectionsOnAndroid");
markFlagAsAccessed(34, "traceTurboModulePromiseRejectionsOnAndroid");
flagValue = currentProvider_->traceTurboModulePromiseRejectionsOnAndroid();
traceTurboModulePromiseRejectionsOnAndroid_ = flagValue;
@@ -650,7 +668,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(34, "useAlwaysAvailableJSErrorHandling");
markFlagAsAccessed(35, "useAlwaysAvailableJSErrorHandling");
flagValue = currentProvider_->useAlwaysAvailableJSErrorHandling();
useAlwaysAvailableJSErrorHandling_ = flagValue;
@@ -668,7 +686,7 @@ bool ReactNativeFeatureFlagsAccessor::useEditTextStockAndroidFocusBehavior() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(35, "useEditTextStockAndroidFocusBehavior");
markFlagAsAccessed(36, "useEditTextStockAndroidFocusBehavior");
flagValue = currentProvider_->useEditTextStockAndroidFocusBehavior();
useEditTextStockAndroidFocusBehavior_ = flagValue;
@@ -686,7 +704,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(36, "useFabricInterop");
markFlagAsAccessed(37, "useFabricInterop");
flagValue = currentProvider_->useFabricInterop();
useFabricInterop_ = flagValue;
@@ -704,7 +722,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(37, "useNativeViewConfigsInBridgelessMode");
markFlagAsAccessed(38, "useNativeViewConfigsInBridgelessMode");
flagValue = currentProvider_->useNativeViewConfigsInBridgelessMode();
useNativeViewConfigsInBridgelessMode_ = flagValue;
@@ -722,7 +740,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(38, "useOptimizedEventBatchingOnAndroid");
markFlagAsAccessed(39, "useOptimizedEventBatchingOnAndroid");
flagValue = currentProvider_->useOptimizedEventBatchingOnAndroid();
useOptimizedEventBatchingOnAndroid_ = flagValue;
@@ -740,7 +758,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(39, "useRawPropsJsiValue");
markFlagAsAccessed(40, "useRawPropsJsiValue");
flagValue = currentProvider_->useRawPropsJsiValue();
useRawPropsJsiValue_ = flagValue;
@@ -758,7 +776,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(40, "useTurboModuleInterop");
markFlagAsAccessed(41, "useTurboModuleInterop");
flagValue = currentProvider_->useTurboModuleInterop();
useTurboModuleInterop_ = flagValue;
@@ -776,7 +794,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(41, "useTurboModules");
markFlagAsAccessed(42, "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<<cf03a6e938df9988c00249dc78576cbf>>
* @generated SignedSource<<65caac2e749261ca919780dff302ef57>>
*/
/**
@@ -65,6 +65,7 @@ class ReactNativeFeatureFlagsAccessor {
bool fuseboxEnabledRelease();
bool fuseboxNetworkInspectionEnabled();
bool lazyAnimationCallbacks();
bool throwExceptionInsteadOfDeadlockOnTurboModuleSetupDuringSyncRenderIOS();
bool traceTurboModulePromiseRejectionsOnAndroid();
bool useAlwaysAvailableJSErrorHandling();
bool useEditTextStockAndroidFocusBehavior();
@@ -85,7 +86,7 @@ class ReactNativeFeatureFlagsAccessor {
std::unique_ptr<ReactNativeFeatureFlagsProvider> currentProvider_;
bool wasOverridden_;
std::array<std::atomic<const char*>, 42> accessedFeatureFlags_;
std::array<std::atomic<const char*>, 43> accessedFeatureFlags_;
std::atomic<std::optional<bool>> commonTestFlag_;
std::atomic<std::optional<bool>> disableMountItemReorderingAndroid_;
@@ -120,6 +121,7 @@ class ReactNativeFeatureFlagsAccessor {
std::atomic<std::optional<bool>> fuseboxEnabledRelease_;
std::atomic<std::optional<bool>> fuseboxNetworkInspectionEnabled_;
std::atomic<std::optional<bool>> lazyAnimationCallbacks_;
std::atomic<std::optional<bool>> throwExceptionInsteadOfDeadlockOnTurboModuleSetupDuringSyncRenderIOS_;
std::atomic<std::optional<bool>> traceTurboModulePromiseRejectionsOnAndroid_;
std::atomic<std::optional<bool>> useAlwaysAvailableJSErrorHandling_;
std::atomic<std::optional<bool>> useEditTextStockAndroidFocusBehavior_;
@@ -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<<1ade51e6e5c82275c5c01b72df630eee>>
* @generated SignedSource<<acc0b73730fe5e0a8e329735481bc579>>
*/
/**
@@ -159,6 +159,10 @@ class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider {
return false;
}
bool throwExceptionInsteadOfDeadlockOnTurboModuleSetupDuringSyncRenderIOS() override {
return false;
}
bool traceTurboModulePromiseRejectionsOnAndroid() 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<<95aa462c1a48b2299c2a82adf74c58df>>
* @generated SignedSource<<c36182568c56dd82ef777bc477a9a4fe>>
*/
/**
@@ -342,6 +342,15 @@ class ReactNativeFeatureFlagsDynamicProvider : public ReactNativeFeatureFlagsDef
return ReactNativeFeatureFlagsDefaults::lazyAnimationCallbacks();
}
bool throwExceptionInsteadOfDeadlockOnTurboModuleSetupDuringSyncRenderIOS() override {
auto value = values_["throwExceptionInsteadOfDeadlockOnTurboModuleSetupDuringSyncRenderIOS"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::throwExceptionInsteadOfDeadlockOnTurboModuleSetupDuringSyncRenderIOS();
}
bool traceTurboModulePromiseRejectionsOnAndroid() override {
auto value = values_["traceTurboModulePromiseRejectionsOnAndroid"];
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<<f3241de0ced5e6ea54e6debf2a453c36>>
* @generated SignedSource<<716d5779133c4ee8c9d20e4a71065d59>>
*/
/**
@@ -58,6 +58,7 @@ class ReactNativeFeatureFlagsProvider {
virtual bool fuseboxEnabledRelease() = 0;
virtual bool fuseboxNetworkInspectionEnabled() = 0;
virtual bool lazyAnimationCallbacks() = 0;
virtual bool throwExceptionInsteadOfDeadlockOnTurboModuleSetupDuringSyncRenderIOS() = 0;
virtual bool traceTurboModulePromiseRejectionsOnAndroid() = 0;
virtual bool useAlwaysAvailableJSErrorHandling() = 0;
virtual bool useEditTextStockAndroidFocusBehavior() = 0;
@@ -34,7 +34,7 @@ Pod::Spec.new do |s|
s.platforms = min_supported_versions
s.source = source
s.compiler_flags = folly_compiler_flags + ' ' + boost_compiler_flags
s.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\" \"$(PODS_ROOT)/RCT-Folly\" \"$(PODS_ROOT)/DoubleConversion\" \"$(PODS_ROOT)/fast_float/include\" \"$(PODS_ROOT)/fmt/include\" \"$(PODS_ROOT)/Headers/Private/React-Core\"",
s.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\" \"$(PODS_ROOT)/RCT-Folly\" \"$(PODS_ROOT)/DoubleConversion\" \"$(PODS_ROOT)/fast_float/include\" \"$(PODS_ROOT)/fmt/include\" \"$(PODS_ROOT)/Headers/Private/React-Core\" \"$(PODS_CONFIGURATION_BUILD_DIR)/React-debug/React_featureflags.framework/Headers\"",
"USE_HEADERMAP" => "YES",
"CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(),
"GCC_WARN_PEDANTIC" => "YES" }
@@ -51,6 +51,7 @@ Pod::Spec.new do |s|
s.dependency "React-Core"
s.dependency "React-cxxreact"
s.dependency "React-jsi"
s.dependency "React-featureflags"
s.dependency "React-runtimeexecutor"
add_dependency(s, "React-jsinspector", :framework_name => 'jsinspector_modern')
@@ -29,9 +29,11 @@
#import <React/RCTUtils.h>
#import <ReactCommon/CxxTurboModuleUtils.h>
#import <ReactCommon/RCTTurboModuleWithJSIBindings.h>
#import <ReactCommon/RuntimeExecutor.h>
#import <ReactCommon/TurboCxxModule.h>
#import <ReactCommon/TurboModulePerfLogger.h>
#import <ReactCommon/TurboModuleUtils.h>
#import <react/featureflags/ReactNativeFeatureFlags.h>
using namespace facebook;
using namespace facebook::react;
@@ -574,7 +576,35 @@ typedef struct {
};
if ([self _requiresMainQueueSetup:moduleClass]) {
RCTUnsafeExecuteOnMainQueueSync(work);
if (ReactNativeFeatureFlags::throwExceptionInsteadOfDeadlockOnTurboModuleSetupDuringSyncRenderIOS()) {
static int32_t modulesSettingUpOnMainQueueCount = 0;
bool needsUnlock = NO;
// This function can be recursive. If it's called recursively, we need to skip the lock.
// We can't use a recursive mutex instead because the lock is needed on multiple threads.
if (modulesSettingUpOnMainQueueCount == 0) {
if (!facebook::react::getMainThreadMutex()->try_lock()) {
NSString *reason = [NSString
stringWithFormat:
@"TurboModule %@ which requires main queue setup is initializing during sync rendering. This would have caused a deadlock. Please fix this by avoiding main queue setup or eager initializing this TurboModule.",
NSStringFromClass(moduleClass)];
NSException *exception = [NSException exceptionWithName:@"UnsafeTurboModuleException"
reason:reason
userInfo:nil];
@throw exception;
}
needsUnlock = true;
}
modulesSettingUpOnMainQueueCount++;
RCTUnsafeExecuteOnMainQueueSync(work);
modulesSettingUpOnMainQueueCount--;
if (needsUnlock) {
facebook::react::getMainThreadMutex()->unlock();
}
} else {
RCTUnsafeExecuteOnMainQueueSync(work);
}
} else {
work();
}
@@ -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<<9a4f0e72b7e6518347f65253c018220a>>
* @generated SignedSource<<f6c8f26ac2c00989c489cde342384a99>>
*/
/**
@@ -209,6 +209,11 @@ bool NativeReactNativeFeatureFlags::lazyAnimationCallbacks(
return ReactNativeFeatureFlags::lazyAnimationCallbacks();
}
bool NativeReactNativeFeatureFlags::throwExceptionInsteadOfDeadlockOnTurboModuleSetupDuringSyncRenderIOS(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::throwExceptionInsteadOfDeadlockOnTurboModuleSetupDuringSyncRenderIOS();
}
bool NativeReactNativeFeatureFlags::traceTurboModulePromiseRejectionsOnAndroid(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::traceTurboModulePromiseRejectionsOnAndroid();
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<16deece14930c9b259f6f7594127fe55>>
* @generated SignedSource<<805e70470028c1a98ef49e6aaf18fd03>>
*/
/**
@@ -103,6 +103,8 @@ class NativeReactNativeFeatureFlags
bool lazyAnimationCallbacks(jsi::Runtime& runtime);
bool throwExceptionInsteadOfDeadlockOnTurboModuleSetupDuringSyncRenderIOS(jsi::Runtime& runtime);
bool traceTurboModulePromiseRejectionsOnAndroid(jsi::Runtime& runtime);
bool useAlwaysAvailableJSErrorHandling(jsi::Runtime& runtime);
@@ -0,0 +1,17 @@
/*
* 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 "RuntimeExecutor.h"
namespace facebook::react {
std::mutex* getMainThreadMutex() {
static std::mutex mainThreadMutex;
return &mainThreadMutex;
}
} // namespace facebook::react
@@ -14,6 +14,8 @@
namespace facebook::react {
std::mutex* getMainThreadMutex();
/*
* Takes a function and calls it with a reference to a Runtime. The function
* will be called when it is safe to do so (i.e. it ensures non-concurrent
@@ -31,6 +33,16 @@ using 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] mutex1.lock before callback
* - [JS thread] Set runtimePtr in runtimeExecutor callback
* - [JS thread] mutex1.unlock in runtimeExecutor callback
* - [UI thread] Call callback
* - [JS thread] mutex2.lock in runtimeExecutor callback
* - [UI thread] mutex2.unlock after callback
* - [UI thread] mutex3.lock after callback
* - [JS thread] mutex3.unlock in runtimeExecutor callback
*/
inline static void executeSynchronouslyOnSameThread_CAN_DEADLOCK(
const RuntimeExecutor& runtimeExecutor,
@@ -42,6 +54,7 @@ inline static void executeSynchronouslyOnSameThread_CAN_DEADLOCK(
std::mutex mutex2;
std::mutex mutex3;
getMainThreadMutex()->lock();
mutex1.lock();
mutex2.lock();
mutex3.lock();
@@ -70,6 +83,7 @@ inline static void executeSynchronouslyOnSameThread_CAN_DEADLOCK(
callback(*runtimePtr);
mutex2.unlock();
mutex3.lock();
getMainThreadMutex()->unlock();
}
template <typename DataT>
@@ -398,6 +398,17 @@ const definitions: FeatureFlagDefinitions = {
},
ossReleaseStage: 'none',
},
throwExceptionInsteadOfDeadlockOnTurboModuleSetupDuringSyncRenderIOS: {
defaultValue: false,
metadata: {
dateAdded: '2025-02-18',
description:
'Throw an exception instead of deadlocking when a TurboModule that requires main queue setup is initialized during a synchronous render on iOS.',
expectedReleaseValue: true,
purpose: 'experimentation',
},
ossReleaseStage: 'none',
},
traceTurboModulePromiseRejectionsOnAndroid: {
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<<4bdedb6bbe2199cb99e72e86bfffe372>>
* @generated SignedSource<<a8dd33d3160b800bc7afa18045e3ac7a>>
* @flow strict
*/
@@ -82,6 +82,7 @@ export type ReactNativeFeatureFlags = $ReadOnly<{
fuseboxEnabledRelease: Getter<boolean>,
fuseboxNetworkInspectionEnabled: Getter<boolean>,
lazyAnimationCallbacks: Getter<boolean>,
throwExceptionInsteadOfDeadlockOnTurboModuleSetupDuringSyncRenderIOS: Getter<boolean>,
traceTurboModulePromiseRejectionsOnAndroid: Getter<boolean>,
useAlwaysAvailableJSErrorHandling: Getter<boolean>,
useEditTextStockAndroidFocusBehavior: Getter<boolean>,
@@ -304,6 +305,10 @@ export const fuseboxNetworkInspectionEnabled: Getter<boolean> = createNativeFlag
* Only enqueue Choreographer calls if there is an ongoing animation, instead of enqueueing every frame.
*/
export const lazyAnimationCallbacks: Getter<boolean> = createNativeFlagGetter('lazyAnimationCallbacks', false);
/**
* Throw an exception instead of deadlocking when a TurboModule that requires main queue setup is initialized during a synchronous render on iOS.
*/
export const throwExceptionInsteadOfDeadlockOnTurboModuleSetupDuringSyncRenderIOS: Getter<boolean> = createNativeFlagGetter('throwExceptionInsteadOfDeadlockOnTurboModuleSetupDuringSyncRenderIOS', false);
/**
* Enables storing js caller stack when creating promise in native module. This is useful in case of Promise rejection and tracing the cause.
*/
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<06a03507366a38dfc43868e94d75fcf1>>
* @generated SignedSource<<f6bbbe19810cafad5142bf528f04165a>>
* @flow strict
*/
@@ -57,6 +57,7 @@ export interface Spec extends TurboModule {
+fuseboxEnabledRelease?: () => boolean;
+fuseboxNetworkInspectionEnabled?: () => boolean;
+lazyAnimationCallbacks?: () => boolean;
+throwExceptionInsteadOfDeadlockOnTurboModuleSetupDuringSyncRenderIOS?: () => boolean;
+traceTurboModulePromiseRejectionsOnAndroid?: () => boolean;
+useAlwaysAvailableJSErrorHandling?: () => boolean;
+useEditTextStockAndroidFocusBehavior?: () => boolean;