Skip to content

Commit 4d4ab42

Browse files
javachemeta-codesync[bot]
authored andcommitted
Buffer async CallInvoker work with module calls (#58313)
Summary: Pull Request resolved: #58313 In bridgeless, native reaches JS by two routes that end in the same `RuntimeScheduler` queue but get there differently. `callFunctionOnModule` goes through the instance's `BufferedRuntimeExecutor`; the `CallInvoker` goes straight to `scheduleTask`. The CallInvoker therefore skips the buffer entirely and can reach the runtime while a module call issued earlier is still parked, unflushed, because the bundle is mid-evaluation. Native code that issues both cannot rely on the order it issued them in, and `Task` is a min-heap on `now() + timeout(priority)` with no insertion tiebreak, so equal priorities do not settle it either. Gives the two channels the same buffering. `BufferedRuntimeExecutor` gains a priority-carrying `execute`, so work routed through it keeps the scheduler priority it was submitted with instead of collapsing to the executor default, and buffered work from both overloads stays in one submission-ordered stream. `BufferedCallInvoker` sits on that executor and becomes the bridgeless `jsCallInvoker` on Android, iOS and macOS. `invokeSync` deliberately keeps going straight to the scheduler: a synchronous call cannot wait for a flush that only happens once the bundle has run. Behind `enableBufferedCallInvoker`, default true. `ReactInstance` picks between the buffered invoker and the existing `RuntimeSchedulerCallInvoker` in one place, so the platform call sites are identical either way and the change is revertible at runtime — it moves when native-issued async work first reaches JS during startup, which is the intended contract but affects every native module. One lifetime hazard this surfaces, worth knowing about beyond this diff: `BufferedRuntimeExecutor` reaches the scheduler through a raw pointer captured at construction, which is safe only while the owning instance is alive. A CallInvoker is routinely held across instance teardown, so `BufferedCallInvoker` guards every async dispatch on a weak reference to the scheduler and drops the work when it has expired — the same contract `RuntimeSchedulerCallInvoker` has. Without that guard this reliably segfaults on a reload. Changelog: [General][Changed] - Async `CallInvoker` work is now buffered alongside callable module calls, so it no longer runs before the JS bundle has finished evaluating Reviewed By: rubennorte Differential Revision: D118456662
1 parent c300f84 commit 4d4ab42

41 files changed

Lines changed: 610 additions & 130 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

packages/react-native/Libraries/AppDelegate/RCTRootViewFactory.mm

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
#import <ReactCommon/RCTHost.h>
2828
#import <ReactCommon/RCTTurboModuleManager.h>
2929
#import <react/renderer/runtimescheduler/RuntimeScheduler.h>
30-
#import <react/renderer/runtimescheduler/RuntimeSchedulerCallInvoker.h>
3130
#import <react/runtime/JSRuntimeFactory.h>
3231
#import <react/runtime/JSRuntimeFactoryCAPI.h>
3332

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<ffaf7abad8f9b217cc16e28ee9270b40>>
7+
* @generated SignedSource<<b7ef80c2c39c734ae511fe6457b89ce7>>
88
*/
99

1010
/**
@@ -114,6 +114,12 @@ public object ReactNativeFeatureFlags {
114114
@JvmStatic
115115
public fun enableBridgelessArchitecture(): Boolean = accessor.enableBridgelessArchitecture()
116116

117+
/**
118+
* Route async CallInvoker work through the ReactInstance buffered runtime executor, so it is ordered against callable module calls and cannot run before the JS bundle has finished evaluating. invokeSync is unaffected.
119+
*/
120+
@JvmStatic
121+
public fun enableBufferedCallInvoker(): Boolean = accessor.enableBufferedCallInvoker()
122+
117123
/**
118124
* Enable prop iterator setter-style construction of Props in C++ (this flag is not used in Java).
119125
*/

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<0bfeba3d07af6fade47b24974a80a592>>
7+
* @generated SignedSource<<68aefd0293540d56f57e8badc0de04c8>>
88
*/
99

1010
/**
@@ -34,6 +34,7 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
3434
private var enableAccumulatedUpdatesInRawPropsAndroidCache: Boolean? = null
3535
private var enableAndroidTextMeasurementOptimizationsCache: Boolean? = null
3636
private var enableBridgelessArchitectureCache: Boolean? = null
37+
private var enableBufferedCallInvokerCache: Boolean? = null
3738
private var enableCppPropsIteratorSetterCache: Boolean? = null
3839
private var enableCustomFocusSearchOnClippedElementsAndroidCache: Boolean? = null
3940
private var enableDestroyShadowTreeRevisionAsyncCache: Boolean? = null
@@ -232,6 +233,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
232233
return cached
233234
}
234235

236+
override fun enableBufferedCallInvoker(): Boolean {
237+
var cached = enableBufferedCallInvokerCache
238+
if (cached == null) {
239+
cached = ReactNativeFeatureFlagsCxxInterop.enableBufferedCallInvoker()
240+
enableBufferedCallInvokerCache = cached
241+
}
242+
return cached
243+
}
244+
235245
override fun enableCppPropsIteratorSetter(): Boolean {
236246
var cached = enableCppPropsIteratorSetterCache
237247
if (cached == null) {

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<b79fbf8335bf2b480743c3cd93d4e6bd>>
7+
* @generated SignedSource<<247f721796621af8615014477518bcd9>>
88
*/
99

1010
/**
@@ -56,6 +56,8 @@ public object ReactNativeFeatureFlagsCxxInterop {
5656

5757
@DoNotStrip @JvmStatic public external fun enableBridgelessArchitecture(): Boolean
5858

59+
@DoNotStrip @JvmStatic public external fun enableBufferedCallInvoker(): Boolean
60+
5961
@DoNotStrip @JvmStatic public external fun enableCppPropsIteratorSetter(): Boolean
6062

6163
@DoNotStrip @JvmStatic public external fun enableCustomFocusSearchOnClippedElementsAndroid(): Boolean

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<bc2a060e5718ae7afd6d22199c8e9d38>>
7+
* @generated SignedSource<<33071257f9c96a8664c9af429e387061>>
88
*/
99

1010
/**
@@ -51,6 +51,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi
5151

5252
override fun enableBridgelessArchitecture(): Boolean = true
5353

54+
override fun enableBufferedCallInvoker(): Boolean = true
55+
5456
override fun enableCppPropsIteratorSetter(): Boolean = false
5557

5658
override fun enableCustomFocusSearchOnClippedElementsAndroid(): Boolean = true

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<3605df96fad767e3ec3957d7305ef926>>
7+
* @generated SignedSource<<f218220c66b8367211cae49adba46afc>>
88
*/
99

1010
/**
@@ -38,6 +38,7 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
3838
private var enableAccumulatedUpdatesInRawPropsAndroidCache: Boolean? = null
3939
private var enableAndroidTextMeasurementOptimizationsCache: Boolean? = null
4040
private var enableBridgelessArchitectureCache: Boolean? = null
41+
private var enableBufferedCallInvokerCache: Boolean? = null
4142
private var enableCppPropsIteratorSetterCache: Boolean? = null
4243
private var enableCustomFocusSearchOnClippedElementsAndroidCache: Boolean? = null
4344
private var enableDestroyShadowTreeRevisionAsyncCache: Boolean? = null
@@ -250,6 +251,16 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
250251
return cached
251252
}
252253

254+
override fun enableBufferedCallInvoker(): Boolean {
255+
var cached = enableBufferedCallInvokerCache
256+
if (cached == null) {
257+
cached = currentProvider.enableBufferedCallInvoker()
258+
accessedFeatureFlags.add("enableBufferedCallInvoker")
259+
enableBufferedCallInvokerCache = cached
260+
}
261+
return cached
262+
}
263+
253264
override fun enableCppPropsIteratorSetter(): Boolean {
254265
var cached = enableCppPropsIteratorSetterCache
255266
if (cached == null) {

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<100e31bd98a30aba4abef10f168638fc>>
7+
* @generated SignedSource<<915bf918212b9898319de61d4cadaa13>>
88
*/
99

1010
/**
@@ -51,6 +51,8 @@ public interface ReactNativeFeatureFlagsProvider {
5151

5252
@DoNotStrip public fun enableBridgelessArchitecture(): Boolean
5353

54+
@DoNotStrip public fun enableBufferedCallInvoker(): Boolean
55+
5456
@DoNotStrip public fun enableCppPropsIteratorSetter(): Boolean
5557

5658
@DoNotStrip public fun enableCustomFocusSearchOnClippedElementsAndroid(): Boolean

packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<ce8ba846a2e567547c4ba7370b9f4e89>>
7+
* @generated SignedSource<<177c5cc7f6e970a2d4454c32d7f777ef>>
88
*/
99

1010
/**
@@ -123,6 +123,12 @@ class ReactNativeFeatureFlagsJavaProvider
123123
return method(javaProvider_);
124124
}
125125

126+
bool enableBufferedCallInvoker() override {
127+
static const auto method =
128+
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("enableBufferedCallInvoker");
129+
return method(javaProvider_);
130+
}
131+
126132
bool enableCppPropsIteratorSetter() override {
127133
static const auto method =
128134
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("enableCppPropsIteratorSetter");
@@ -623,6 +629,11 @@ bool JReactNativeFeatureFlagsCxxInterop::enableBridgelessArchitecture(
623629
return ReactNativeFeatureFlags::enableBridgelessArchitecture();
624630
}
625631

632+
bool JReactNativeFeatureFlagsCxxInterop::enableBufferedCallInvoker(
633+
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
634+
return ReactNativeFeatureFlags::enableBufferedCallInvoker();
635+
}
636+
626637
bool JReactNativeFeatureFlagsCxxInterop::enableCppPropsIteratorSetter(
627638
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
628639
return ReactNativeFeatureFlags::enableCppPropsIteratorSetter();
@@ -1051,6 +1062,9 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() {
10511062
makeNativeMethod(
10521063
"enableBridgelessArchitecture",
10531064
JReactNativeFeatureFlagsCxxInterop::enableBridgelessArchitecture),
1065+
makeNativeMethod(
1066+
"enableBufferedCallInvoker",
1067+
JReactNativeFeatureFlagsCxxInterop::enableBufferedCallInvoker),
10541068
makeNativeMethod(
10551069
"enableCppPropsIteratorSetter",
10561070
JReactNativeFeatureFlagsCxxInterop::enableCppPropsIteratorSetter),

packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<145b12c4a208db86c7bfdfed56cf433f>>
7+
* @generated SignedSource<<eab10e240a16c8ef3f659bd8ded290f0>>
88
*/
99

1010
/**
@@ -72,6 +72,9 @@ class JReactNativeFeatureFlagsCxxInterop
7272
static bool enableBridgelessArchitecture(
7373
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
7474

75+
static bool enableBufferedCallInvoker(
76+
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
77+
7578
static bool enableCppPropsIteratorSetter(
7679
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
7780

packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/JReactInstance.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
#include <jsi/jsi.h>
1616
#include <react/jni/JRuntimeExecutor.h>
1717
#include <react/jni/JSLogging.h>
18-
#include <react/renderer/runtimescheduler/RuntimeSchedulerCallInvoker.h>
1918
#include <react/runtime/BridgelessNativeMethodCallInvoker.h>
2019
#include <react/runtime/JSRuntimeBindings.h>
2120

@@ -89,10 +88,11 @@ JReactInstance::JReactInstance(
8988

9089
auto unbufferedRuntimeExecutor = instance_->getUnbufferedRuntimeExecutor();
9190
// Set up the JS and native modules call invokers (for TurboModules)
92-
auto jsInvoker = std::make_unique<RuntimeSchedulerCallInvoker>(
93-
instance_->getRuntimeScheduler());
91+
// Shares the instance's BufferedRuntimeExecutor so async calls from native
92+
// are ordered against callFunctionOnModule and cannot run before the bundle
93+
// has evaluated. invokeSync still goes straight to the scheduler.
9494
jsCallInvokerHolder_ = jni::make_global(
95-
CallInvokerHolder::newObjectCxxArgs(std::move(jsInvoker)));
95+
CallInvokerHolder::newObjectCxxArgs(instance_->createJSCallInvoker()));
9696
auto nativeMethodCallInvoker =
9797
std::make_unique<BridgelessNativeMethodCallInvoker>(
9898
sharedNativeMessageQueueThread);

0 commit comments

Comments
 (0)