RuntimeExecutor: Create an ios fork of sync ui thread utils

Summary:
RuntimeExecutor.h has sync ui thread utils:
* executeSynchronouslyOnSameThread_CAN_DEADLOCK

The ios platform has js -> ui sync calls. This util, when it executes concurrently with those sync calls, deadlocks react native.

On ios, we're going to resolve these deadlocks, which'll involve customizing this util: D74769326. Therefore, this diff forks an implementation of these sync ui thread utils for ios.

Changelog: [Internal]

Reviewed By: mdvacca

Differential Revision: D74901907

fbshipit-source-id: f502df4216e9ba57f458435c696a2f086becf24f
This commit is contained in:
Ramanpreet Nara
2025-05-28 00:25:46 -07:00
committed by Facebook GitHub Bot
parent 8f2e981ab7
commit b8ce7b837f
29 changed files with 235 additions and 72 deletions
@@ -68,6 +68,7 @@ Pod::Spec.new do |s|
s.dependency "React-defaultsnativemodule"
s.dependency 'React-hermes'
add_dependency(s, "React-runtimeexecutor", :additional_framework_paths => ["platform/ios"])
add_dependency(s, "ReactCommon", :subspec => "turbomodule/core", :additional_framework_paths => ["react/nativemodule/core"])
add_dependency(s, "React-NativeModulesApple")
add_dependency(s, "React-runtimescheduler")
+1
View File
@@ -126,6 +126,7 @@ Pod::Spec.new do |s|
s.resource_bundles = {'React-Core_privacy' => 'React/Resources/PrivacyInfo.xcprivacy'}
add_dependency(s, "React-runtimeexecutor", :additional_framework_paths => ["platform/ios"])
add_dependency(s, "React-jsinspector", :framework_name => 'jsinspector_modern')
add_dependency(s, "React-jsinspectorcdp", :framework_name => 'jsinspector_moderncdp')
add_dependency(s, "React-jsitooling", :framework_name => "JSITooling")
@@ -52,6 +52,8 @@ Pod::Spec.new do |s|
s.dependency "React-RCTImage", version
s.dependency "React-jsi", version
s.dependency 'React-RCTBlob'
add_dependency(s, "React-runtimeexecutor", :additional_framework_paths => ["platform/ios"])
add_dependency(s, "React-jsinspector", :framework_name => 'jsinspector_modern')
add_dependency(s, "React-jsinspectorcdp", :framework_name => 'jsinspector_moderncdp')
add_dependency(s, "React-jsinspectortracing", :framework_name => 'jsinspector_moderntracing')
@@ -85,6 +85,7 @@ Pod::Spec.new do |s|
add_dependency(s, "React-performancetimeline")
add_dependency(s, "React-rendererdebug")
add_dependency(s, "React-rendererconsistency")
add_dependency(s, "React-runtimeexecutor", :additional_framework_paths => ["platform/ios"])
add_dependency(s, "React-runtimescheduler")
add_dependency(s, "React-RCTAnimation", :framework_name => 'RCTAnimation')
add_dependency(s, "React-jsinspector", :framework_name => 'jsinspector_modern')
@@ -49,6 +49,7 @@ Pod::Spec.new do |s|
s.dependency "React-Core"
s.dependency "React-jsi"
add_dependency(s, "React-runtimeexecutor", :additional_framework_paths => ["platform/ios"])
add_dependency(s, "React-jsitooling", :framework_name => "JSITooling")
add_dependency(s, "React-jsinspector", :framework_name => 'jsinspector_modern')
add_dependency(s, "React-jsinspectorcdp", :framework_name => 'jsinspector_moderncdp')
@@ -49,6 +49,7 @@ Pod::Spec.new do |s|
s.dependency "React-runtimescheduler"
s.dependency "React-cxxreact"
add_dependency(s, "React-runtimeexecutor", :additional_framework_paths => ["platform/ios"])
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"])
@@ -39,7 +39,7 @@ Pod::Spec.new do |s|
add_dependency(s, "React-jsinspectorcdp", :framework_name => 'jsinspector_moderncdp')
add_dependency(s, "React-jsinspectortracing", :framework_name => 'jsinspector_moderntracing')
s.dependency "React-callinvoker", version
s.dependency "React-runtimeexecutor", version
add_dependency(s, "React-runtimeexecutor", :additional_framework_paths => ["platform/ios"])
s.dependency "React-perflogger", version
s.dependency "React-jsi", version
s.dependency "React-logger", version
@@ -43,7 +43,7 @@ Pod::Spec.new do |s|
s.dependency "React-perflogger", version
s.dependency "hermes-engine"
s.dependency "React-jsi"
s.dependency "React-runtimeexecutor"
add_dependency(s, "React-runtimeexecutor", :additional_framework_paths => ["platform/ios"])
add_rn_third_party_dependencies(s)
end
@@ -32,6 +32,7 @@ Pod::Spec.new do |s|
s.dependency "React-cxxreact", version
s.dependency "React-jsi", version
s.dependency "React-perflogger", version
add_dependency(s, "React-runtimeexecutor", :additional_framework_paths => ["platform/ios"])
add_dependency(s, "React-jsinspector", :framework_name => 'jsinspector_modern')
add_dependency(s, "React-jsinspectorcdp", :framework_name => 'jsinspector_moderncdp')
add_dependency(s, "React-jsinspectortracing", :framework_name => 'jsinspector_moderntracing')
@@ -49,7 +49,7 @@ Pod::Spec.new do |s|
end
s.dependency "React-featureflags"
s.dependency "React-runtimeexecutor", version
add_dependency(s, "React-runtimeexecutor", :additional_framework_paths => ["platform/ios"])
s.dependency "React-jsi"
add_dependency(s, "React-jsinspectorcdp", :framework_name => 'jsinspector_moderncdp')
add_dependency(s, "React-jsinspectornetwork", :framework_name => 'jsinspector_modernnetwork')
@@ -40,6 +40,7 @@ Pod::Spec.new do |s|
s.dependency "React-cxxreact", version
s.dependency "React-jsi", version
add_dependency(s, "React-runtimeexecutor", :additional_framework_paths => ["platform/ios"])
add_dependency(s, "React-jsinspector", :framework_name => 'jsinspector_modern')
add_dependency(s, "React-jsinspectorcdp", :framework_name => 'jsinspector_moderncdp')
add_dependency(s, "React-jsinspectortracing", :framework_name => 'jsinspector_moderntracing')
@@ -44,7 +44,7 @@ Pod::Spec.new do |s|
s.dependency "React-cxxreact"
s.dependency "React-jsi"
s.dependency "React-featureflags"
s.dependency "React-runtimeexecutor"
add_dependency(s, "React-runtimeexecutor", :additional_framework_paths => ["platform/ios"])
add_dependency(s, "React-featureflags")
add_dependency(s, "React-jsinspector", :framework_name => 'jsinspector_modern')
add_dependency(s, "React-jsinspectorcdp", :framework_name => 'jsinspector_moderncdp')
@@ -55,6 +55,7 @@ Pod::Spec.new do |s|
s.dependency "ReactCommon/turbomodule/core"
s.dependency "React-Fabric"
s.dependency "React-FabricComponents"
add_dependency(s, "React-runtimeexecutor", :additional_framework_paths => ["platform/ios"])
add_dependency(s, "React-graphics", :additional_framework_paths => ["react/renderer/graphics/platform/ios"])
add_dependency(s, "React-RCTFBReactNativeSpec")
end
@@ -52,5 +52,6 @@ Pod::Spec.new do |s|
s.dependency "ReactCommon/turbomodule/core"
s.dependency "React-runtimescheduler"
add_dependency(s, "React-RCTFBReactNativeSpec")
add_dependency(s, "React-runtimeexecutor", :additional_framework_paths => ["platform/ios"])
end
@@ -43,7 +43,7 @@ Pod::Spec.new do |s|
s.header_mappings_dir = "../../.."
end
s.dependency "React-runtimeexecutor"
add_dependency(s, "React-runtimeexecutor", :additional_framework_paths => ["platform/ios"])
s.dependency "React-callinvoker"
s.dependency "React-cxxreact"
s.dependency "React-rendererdebug"
@@ -8,6 +8,7 @@
#include "RuntimeScheduler_Legacy.h"
#include "SchedulerPriorityUtils.h"
#include <ReactCommon/RuntimeExecutorSyncUIThreadUtils.h>
#include <cxxreact/TraceSection.h>
#include <react/renderer/consistency/ScopedShadowTreeRevisionLock.h>
#include <utility>
@@ -8,6 +8,7 @@
#include "RuntimeScheduler_Modern.h"
#include "SchedulerPriorityUtils.h"
#include <ReactCommon/RuntimeExecutorSyncUIThreadUtils.h>
#include <cxxreact/TraceSection.h>
#include <jsinspector-modern/tracing/EventLoopReporter.h>
#include <react/featureflags/ReactNativeFeatureFlags.h>
@@ -15,6 +15,7 @@
#include <chrono>
#include <memory>
#include <semaphore>
#include <thread>
#include <variant>
#include "StubClock.h"
@@ -10,6 +10,7 @@
#include <ReactCommon/RuntimeExecutor.h>
#include <jsi/jsi.h>
#include <atomic>
#include <mutex>
#include <queue>
namespace facebook::react {
@@ -40,7 +40,7 @@ Pod::Spec.new do |s|
s.dependency "React-jsiexecutor"
s.dependency "React-cxxreact"
s.dependency "React-runtimeexecutor"
add_dependency(s, "React-runtimeexecutor", :additional_framework_paths => ["platform/ios"])
s.dependency "React-jsi"
s.dependency "React-jserrorhandler"
s.dependency "React-performancetimeline"
@@ -48,6 +48,7 @@ Pod::Spec.new do |s|
s.dependency "React-hermes"
s.dependency "hermes-engine"
add_dependency(s, "React-runtimeexecutor", :additional_framework_paths => ["platform/ios"])
add_dependency(s, "React-jsitooling", :framework_name => "JSITooling")
add_rn_third_party_dependencies(s)
@@ -46,7 +46,7 @@ Pod::Spec.new do |s|
s.dependency "React-jsiexecutor"
s.dependency "React-cxxreact"
s.dependency "React-callinvoker"
s.dependency "React-runtimeexecutor"
add_dependency(s, "React-runtimeexecutor", :additional_framework_paths => ["platform/ios"])
s.dependency "React-runtimescheduler"
s.dependency "React-jsi"
s.dependency "React-Core/Default"
@@ -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 ${CMAKE_CURRENT_SOURCE_DIR}/ReactCommon/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/platform/cxx/*.cpp)
add_library(runtimeexecutor OBJECT ${runtimeexecutor_SRC})
target_include_directories(runtimeexecutor PUBLIC .)
target_include_directories(runtimeexecutor PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/platform/cxx)
target_link_libraries(runtimeexecutor jsi)
target_compile_reactnative_options(runtimeexecutor PRIVATE)
@@ -17,7 +17,12 @@ else
end
Pod::Spec.new do |s|
header_search_paths = [
"\"$(PODS_TARGET_SRCROOT)\"",
]
s.name = "React-runtimeexecutor"
s.module_name = "React_runtimeexecutor"
s.version = version
s.summary = "-" # TODO
s.homepage = "https://reactnative.dev/"
@@ -25,8 +30,18 @@ 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 = "ReactCommon/*.{m,mm,cpp,h}", "platform/ios/**/*.{m,mm,cpp,h}"
s.header_dir = "ReactCommon"
if ENV['USE_FRAMEWORKS']
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
end
@@ -7,9 +7,6 @@
#pragma once
#include <future>
#include <thread>
#include <jsi/jsi.h>
namespace facebook::react {
@@ -25,63 +22,4 @@ namespace facebook::react {
using RuntimeExecutor =
std::function<void(std::function<void(jsi::Runtime& runtime)>&& callback)>;
/*
* Schedules `runtimeWork` to be executed on the same thread using the
* `RuntimeExecutor`, and blocks on its completion.
*
* Example:
* - [UI thread] Schedule `runtimeCaptureBlock` on js thread
* - [UI thread] Wait for runtime capture: await(runtime)
* - [JS thread] Capture runtime for ui thread: resolve(runtime, &rt);
* - [JS thread] Wait until runtimeWork done: await(runtimeWorkDone)
* - [UI thread] Call runtimeWork: runtimeWork(*runtimePrt);
* - [UI thread] Signal runtimeWork done: resolve(runtimeWorkDone)
* - [UI thread] Wait until runtime capture block finished:
* await(runtimeCaptureBlockDone);
* - [JS thread] Signal runtime capture block is finished:
* resolve(runtimeCaptureBlockDone);
*/
inline static void executeSynchronouslyOnSameThread_CAN_DEADLOCK(
const RuntimeExecutor& runtimeExecutor,
std::function<void(jsi::Runtime&)>&& runtimeWork) {
std::promise<jsi::Runtime*> runtime;
std::promise<void> runtimeCaptureBlockDone;
std::promise<void> runtimeWorkDone;
auto callingThread = std::this_thread::get_id();
auto runtimeCaptureBlock = [&](jsi::Runtime& rt) {
runtime.set_value(&rt);
auto runtimeThread = std::this_thread::get_id();
if (callingThread != runtimeThread) {
// Block `runtimeThread` on execution of `runtimeWork` on `callingThread`.
runtimeWorkDone.get_future().wait();
}
// TODO(T225331233): This is likely unnecessary. Remove it.
runtimeCaptureBlockDone.set_value();
};
runtimeExecutor(std::move(runtimeCaptureBlock));
jsi::Runtime* runtimePtr = runtime.get_future().get();
runtimeWork(*runtimePtr);
runtimeWorkDone.set_value();
// TODO(T225331233): This is likely unnecessary. Remove it.
runtimeCaptureBlockDone.get_future().wait();
}
template <typename DataT>
inline static DataT executeSynchronouslyOnSameThread_CAN_DEADLOCK(
const RuntimeExecutor& runtimeExecutor,
std::function<DataT(jsi::Runtime& runtime)>&& runtimeWork) {
DataT data;
executeSynchronouslyOnSameThread_CAN_DEADLOCK(
runtimeExecutor,
[&](jsi::Runtime& runtime) { data = runtimeWork(runtime); });
return data;
}
} // namespace facebook::react
@@ -0,0 +1,61 @@
/*
* 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 <ReactCommon/RuntimeExecutorSyncUIThreadUtils.h>
#include <future>
#include <thread>
namespace facebook::react {
/*
* Schedules `runtimeWork` to be executed on the same thread using the
* `RuntimeExecutor`, and blocks on its completion.
*
* Example:
* - [UI thread] Schedule `runtimeCaptureBlock` on js thread
* - [UI thread] Wait for runtime capture: await(runtime)
* - [JS thread] Capture runtime for ui thread: resolve(runtime, &rt);
* - [JS thread] Wait until runtimeWork done: await(runtimeWorkDone)
* - [UI thread] Call runtimeWork: runtimeWork(*runtimePrt);
* - [UI thread] Signal runtimeWork done: resolve(runtimeWorkDone)
* - [UI thread] Wait until runtime capture block finished:
* await(runtimeCaptureBlockDone);
* - [JS thread] Signal runtime capture block is finished:
* resolve(runtimeCaptureBlockDone);
*/
void executeSynchronouslyOnSameThread_CAN_DEADLOCK(
const RuntimeExecutor& runtimeExecutor,
std::function<void(jsi::Runtime&)>&& runtimeWork) {
std::promise<jsi::Runtime*> runtime;
std::promise<void> runtimeCaptureBlockDone;
std::promise<void> runtimeWorkDone;
auto callingThread = std::this_thread::get_id();
auto runtimeCaptureBlock = [&](jsi::Runtime& rt) {
runtime.set_value(&rt);
auto runtimeThread = std::this_thread::get_id();
if (callingThread != runtimeThread) {
// Block `runtimeThread` on execution of `runtimeWork` on `callingThread`.
runtimeWorkDone.get_future().wait();
}
// TODO(T225331233): This is likely unnecessary. Remove it.
runtimeCaptureBlockDone.set_value();
};
runtimeExecutor(std::move(runtimeCaptureBlock));
jsi::Runtime* runtimePtr = runtime.get_future().get();
runtimeWork(*runtimePtr);
runtimeWorkDone.set_value();
// TODO(T225331233): This is likely unnecessary. Remove it.
runtimeCaptureBlockDone.get_future().wait();
}
} // namespace facebook::react
@@ -0,0 +1,36 @@
/*
* 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 <ReactCommon/RuntimeExecutor.h>
#include <jsi/jsi.h>
namespace facebook::react {
/*
* Schedules `runtimeWork` to be executed on the same thread using the
* `RuntimeExecutor`, and blocks on its completion.
*/
void executeSynchronouslyOnSameThread_CAN_DEADLOCK(
const RuntimeExecutor& runtimeExecutor,
std::function<void(jsi::Runtime&)>&& runtimeWork);
template <typename DataT>
inline static DataT executeSynchronouslyOnSameThread_CAN_DEADLOCK(
const RuntimeExecutor& runtimeExecutor,
std::function<DataT(jsi::Runtime& runtime)>&& runtimeWork) {
DataT data;
executeSynchronouslyOnSameThread_CAN_DEADLOCK(
runtimeExecutor,
[&](jsi::Runtime& runtime) { data = runtimeWork(runtime); });
return data;
}
} // namespace facebook::react
@@ -0,0 +1,36 @@
/*
* 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 <ReactCommon/RuntimeExecutor.h>
#include <jsi/jsi.h>
namespace facebook::react {
/*
* Schedules `runtimeWork` to be executed on the same thread using the
* `RuntimeExecutor`, and blocks on its completion.
*/
void executeSynchronouslyOnSameThread_CAN_DEADLOCK(
const RuntimeExecutor& runtimeExecutor,
std::function<void(jsi::Runtime&)>&& runtimeWork);
template <typename DataT>
inline static DataT executeSynchronouslyOnSameThread_CAN_DEADLOCK(
const RuntimeExecutor& runtimeExecutor,
std::function<DataT(jsi::Runtime& runtime)>&& runtimeWork) {
DataT data;
executeSynchronouslyOnSameThread_CAN_DEADLOCK(
runtimeExecutor,
[&](jsi::Runtime& runtime) { data = runtimeWork(runtime); });
return data;
}
} // namespace facebook::react
@@ -0,0 +1,61 @@
/*
* 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 <ReactCommon/RuntimeExecutorSyncUIThreadUtils.h>
#include <future>
#include <thread>
namespace facebook::react {
/*
* Schedules `runtimeWork` to be executed on the same thread using the
* `RuntimeExecutor`, and blocks on its completion.
*
* Example:
* - [UI thread] Schedule `runtimeCaptureBlock` on js thread
* - [UI thread] Wait for runtime capture: await(runtime)
* - [JS thread] Capture runtime for ui thread: resolve(runtime, &rt);
* - [JS thread] Wait until runtimeWork done: await(runtimeWorkDone)
* - [UI thread] Call runtimeWork: runtimeWork(*runtimePrt);
* - [UI thread] Signal runtimeWork done: resolve(runtimeWorkDone)
* - [UI thread] Wait until runtime capture block finished:
* await(runtimeCaptureBlockDone);
* - [JS thread] Signal runtime capture block is finished:
* resolve(runtimeCaptureBlockDone);
*/
void executeSynchronouslyOnSameThread_CAN_DEADLOCK(
const RuntimeExecutor &runtimeExecutor,
std::function<void(jsi::Runtime &)> &&runtimeWork)
{
std::promise<jsi::Runtime *> runtime;
std::promise<void> runtimeCaptureBlockDone;
std::promise<void> runtimeWorkDone;
auto callingThread = std::this_thread::get_id();
auto runtimeCaptureBlock = [&](jsi::Runtime &rt) {
runtime.set_value(&rt);
auto runtimeThread = std::this_thread::get_id();
if (callingThread != runtimeThread) {
// Block `runtimeThread` on execution of `runtimeWork` on `callingThread`.
runtimeWorkDone.get_future().wait();
}
// TODO(T225331233): This is likely unnecessary. Remove it.
runtimeCaptureBlockDone.set_value();
};
runtimeExecutor(std::move(runtimeCaptureBlock));
jsi::Runtime *runtimePtr = runtime.get_future().get();
runtimeWork(*runtimePtr);
runtimeWorkDone.set_value();
// TODO(T225331233): This is likely unnecessary. Remove it.
runtimeCaptureBlockDone.get_future().wait();
}
} // namespace facebook::react