Skip to content

Commit 8cc9bbc

Browse files
rozelemeta-codesync[bot]
authored andcommitted
Execute commands in order (#54074)
Summary: Pull Request resolved: #54074 Currently, we dispatch view commands eagerly at the beginning of a batch. This behavior originated with b54257c. However, this can make it difficult to allow developers to control mount item execution order. For example, someone may want to make sure a view command is processed *after* a particular mount item, and not exclusively rely on retryable command exceptions. This change sets up a feature flag to enable in-order execution of view command mount items. ## Changelog [Internal] Reviewed By: mdvacca, sammy-SC Differential Revision: D84061054 fbshipit-source-id: 7cb68c43823ed67f541f4e8f2d7471629609a905
1 parent e3a5af6 commit 8cc9bbc

21 files changed

Lines changed: 257 additions & 126 deletions

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountItemDispatcher.kt

Lines changed: 47 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,11 @@ internal class MountItemDispatcher(
4343
private var lastFrameTimeNanos: Long = 0L
4444

4545
fun addViewCommandMountItem(mountItem: DispatchCommandMountItem) {
46-
viewCommandMountItems.add(mountItem)
46+
if (!ReactNativeFeatureFlags.disableEarlyViewCommandExecution()) {
47+
viewCommandMountItems.add(mountItem)
48+
} else {
49+
mountItems.add(mountItem)
50+
}
4751
}
4852

4953
fun addMountItem(mountItem: MountItem) {
@@ -147,13 +151,47 @@ internal class MountItemDispatcher(
147151

148152
itemDispatchListener.willMountItems(mountItemsToDispatch)
149153

154+
val dispatchViewCommand: (command: DispatchCommandMountItem) -> Unit = { command ->
155+
if (ReactNativeFeatureFlags.enableFabricLogs()) {
156+
printMountItem(command, "dispatchMountItems: Executing viewCommandMountItem")
157+
}
158+
try {
159+
executeOrEnqueue(command)
160+
} catch (e: RetryableMountingLayerException) {
161+
// If the exception is marked as Retryable, we retry the viewcommand exactly once, after
162+
// the current batch of mount items has finished executing.
163+
if (command.getRetries() == 0) {
164+
command.incrementRetries()
165+
addViewCommandMountItem(command)
166+
} else {
167+
// It's very common for commands to be executed on views that no longer exist - for
168+
// example, a blur event on TextInput being fired because of a navigation event away
169+
// from the current screen. If the exception is marked as Retryable, we log a soft
170+
// exception but never crash in debug.
171+
// It's not clear that logging this is even useful, because these events are very
172+
// common, mundane, and there's not much we can do about them currently.
173+
ReactSoftExceptionLogger.logSoftException(
174+
TAG,
175+
ReactNoCrashSoftException("Caught exception executing ViewCommand: $command", e),
176+
)
177+
}
178+
} catch (e: Throwable) {
179+
// Non-retryable exceptions are logged as soft exceptions in prod, but crash in Debug.
180+
ReactSoftExceptionLogger.logSoftException(
181+
TAG,
182+
RuntimeException("Caught exception executing ViewCommand: $command", e),
183+
)
184+
}
185+
}
186+
150187
// As an optimization, execute all ViewCommands first
151188
// This should be:
152189
// 1) Performant: ViewCommands are often a replacement for SetNativeProps, which we've always
153190
// wanted to be as "synchronous" as possible.
154191
// 2) Safer: ViewCommands are inherently disconnected from the tree commit/diff/mount process.
155192
// JS imperatively queues these commands.
156-
// If JS has queued a command, it's reasonable to assume that the more time passes, the more
193+
// If JS has queued a command, it's reasonable to assume that the more time passes, the
194+
// more
157195
// likely it is that the view disappears.
158196
// Thus, by executing ViewCommands early, we should actually avoid a category of
159197
// errors/glitches.
@@ -164,36 +202,7 @@ internal class MountItemDispatcher(
164202
)
165203

166204
for (command in commands) {
167-
if (ReactNativeFeatureFlags.enableFabricLogs()) {
168-
printMountItem(command, "dispatchMountItems: Executing viewCommandMountItem")
169-
}
170-
try {
171-
executeOrEnqueue(command)
172-
} catch (e: RetryableMountingLayerException) {
173-
// If the exception is marked as Retryable, we retry the viewcommand exactly once, after
174-
// the current batch of mount items has finished executing.
175-
if (command.getRetries() == 0) {
176-
command.incrementRetries()
177-
addViewCommandMountItem(command)
178-
} else {
179-
// It's very common for commands to be executed on views that no longer exist - for
180-
// example, a blur event on TextInput being fired because of a navigation event away
181-
// from the current screen. If the exception is marked as Retryable, we log a soft
182-
// exception but never crash in debug.
183-
// It's not clear that logging this is even useful, because these events are very
184-
// common, mundane, and there's not much we can do about them currently.
185-
ReactSoftExceptionLogger.logSoftException(
186-
TAG,
187-
ReactNoCrashSoftException("Caught exception executing ViewCommand: $command", e),
188-
)
189-
}
190-
} catch (e: Throwable) {
191-
// Non-retryable exceptions are logged as soft exceptions in prod, but crash in Debug.
192-
ReactSoftExceptionLogger.logSoftException(
193-
TAG,
194-
RuntimeException("Caught exception executing ViewCommand: $command", e),
195-
)
196-
}
205+
dispatchViewCommand(command)
197206
}
198207

199208
Systrace.endSection(Systrace.TRACE_TAG_REACT)
@@ -227,6 +236,12 @@ internal class MountItemDispatcher(
227236
printMountItem(mountItem, "dispatchMountItems: Executing mountItem")
228237
}
229238

239+
val command = mountItem as? DispatchCommandMountItem
240+
if (command != null) {
241+
dispatchViewCommand(command)
242+
continue
243+
}
244+
230245
try {
231246
executeOrEnqueue(mountItem)
232247
} catch (e: Throwable) {

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<<4f9777b3b50f714214f9f0f86d5176f1>>
7+
* @generated SignedSource<<3fee7079eaa30dca86e3cb366cdd261e>>
88
*/
99

1010
/**
@@ -54,6 +54,12 @@ public object ReactNativeFeatureFlags {
5454
@JvmStatic
5555
public fun cxxNativeAnimatedRemoveJsSync(): Boolean = accessor.cxxNativeAnimatedRemoveJsSync()
5656

57+
/**
58+
* Dispatch view commands in mount item order.
59+
*/
60+
@JvmStatic
61+
public fun disableEarlyViewCommandExecution(): Boolean = accessor.disableEarlyViewCommandExecution()
62+
5763
/**
5864
* Prevents use of Fabric commit in C++ Animated implementation
5965
*/

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<<7e45b713bab183402e93ee058ea7cb36>>
7+
* @generated SignedSource<<7ea729886da71d6d117442c83736fc5f>>
88
*/
99

1010
/**
@@ -24,6 +24,7 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
2424
private var cdpInteractionMetricsEnabledCache: Boolean? = null
2525
private var cxxNativeAnimatedEnabledCache: Boolean? = null
2626
private var cxxNativeAnimatedRemoveJsSyncCache: Boolean? = null
27+
private var disableEarlyViewCommandExecutionCache: Boolean? = null
2728
private var disableFabricCommitInCXXAnimatedCache: Boolean? = null
2829
private var disableMountItemReorderingAndroidCache: Boolean? = null
2930
private var disableOldAndroidAttachmentMetricsWorkaroundsCache: Boolean? = null
@@ -135,6 +136,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
135136
return cached
136137
}
137138

139+
override fun disableEarlyViewCommandExecution(): Boolean {
140+
var cached = disableEarlyViewCommandExecutionCache
141+
if (cached == null) {
142+
cached = ReactNativeFeatureFlagsCxxInterop.disableEarlyViewCommandExecution()
143+
disableEarlyViewCommandExecutionCache = cached
144+
}
145+
return cached
146+
}
147+
138148
override fun disableFabricCommitInCXXAnimated(): Boolean {
139149
var cached = disableFabricCommitInCXXAnimatedCache
140150
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<<a8097e06e4ad0e329bd81fd6d23c031a>>
7+
* @generated SignedSource<<e72b92811e1f318291484d55f24f15b2>>
88
*/
99

1010
/**
@@ -36,6 +36,8 @@ public object ReactNativeFeatureFlagsCxxInterop {
3636

3737
@DoNotStrip @JvmStatic public external fun cxxNativeAnimatedRemoveJsSync(): Boolean
3838

39+
@DoNotStrip @JvmStatic public external fun disableEarlyViewCommandExecution(): Boolean
40+
3941
@DoNotStrip @JvmStatic public external fun disableFabricCommitInCXXAnimated(): Boolean
4042

4143
@DoNotStrip @JvmStatic public external fun disableMountItemReorderingAndroid(): 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<<c2e602217d9d88c23e958c8b73f22e5c>>
7+
* @generated SignedSource<<bd9b92174bcc46a6df01d0489b2aebc8>>
88
*/
99

1010
/**
@@ -31,6 +31,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi
3131

3232
override fun cxxNativeAnimatedRemoveJsSync(): Boolean = false
3333

34+
override fun disableEarlyViewCommandExecution(): Boolean = false
35+
3436
override fun disableFabricCommitInCXXAnimated(): Boolean = false
3537

3638
override fun disableMountItemReorderingAndroid(): Boolean = false

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<<f88ede5a21467165ef04b5d16d869d1f>>
7+
* @generated SignedSource<<226b5967122a840a4b6ac3fa41d3fb90>>
88
*/
99

1010
/**
@@ -28,6 +28,7 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
2828
private var cdpInteractionMetricsEnabledCache: Boolean? = null
2929
private var cxxNativeAnimatedEnabledCache: Boolean? = null
3030
private var cxxNativeAnimatedRemoveJsSyncCache: Boolean? = null
31+
private var disableEarlyViewCommandExecutionCache: Boolean? = null
3132
private var disableFabricCommitInCXXAnimatedCache: Boolean? = null
3233
private var disableMountItemReorderingAndroidCache: Boolean? = null
3334
private var disableOldAndroidAttachmentMetricsWorkaroundsCache: Boolean? = null
@@ -143,6 +144,16 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
143144
return cached
144145
}
145146

147+
override fun disableEarlyViewCommandExecution(): Boolean {
148+
var cached = disableEarlyViewCommandExecutionCache
149+
if (cached == null) {
150+
cached = currentProvider.disableEarlyViewCommandExecution()
151+
accessedFeatureFlags.add("disableEarlyViewCommandExecution")
152+
disableEarlyViewCommandExecutionCache = cached
153+
}
154+
return cached
155+
}
156+
146157
override fun disableFabricCommitInCXXAnimated(): Boolean {
147158
var cached = disableFabricCommitInCXXAnimatedCache
148159
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<<aa0e9e5830b13c4b290dcae42b757a3a>>
7+
* @generated SignedSource<<bdb7047972cb5fe84dcc11ea6409e603>>
88
*/
99

1010
/**
@@ -31,6 +31,8 @@ public interface ReactNativeFeatureFlagsProvider {
3131

3232
@DoNotStrip public fun cxxNativeAnimatedRemoveJsSync(): Boolean
3333

34+
@DoNotStrip public fun disableEarlyViewCommandExecution(): Boolean
35+
3436
@DoNotStrip public fun disableFabricCommitInCXXAnimated(): Boolean
3537

3638
@DoNotStrip public fun disableMountItemReorderingAndroid(): 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<<5cf0077264f7ceca6617dbe20bcc122f>>
7+
* @generated SignedSource<<bd78270ae3d4461c8f54a9bfb9fc8b20>>
88
*/
99

1010
/**
@@ -63,6 +63,12 @@ class ReactNativeFeatureFlagsJavaProvider
6363
return method(javaProvider_);
6464
}
6565

66+
bool disableEarlyViewCommandExecution() override {
67+
static const auto method =
68+
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("disableEarlyViewCommandExecution");
69+
return method(javaProvider_);
70+
}
71+
6672
bool disableFabricCommitInCXXAnimated() override {
6773
static const auto method =
6874
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("disableFabricCommitInCXXAnimated");
@@ -531,6 +537,11 @@ bool JReactNativeFeatureFlagsCxxInterop::cxxNativeAnimatedRemoveJsSync(
531537
return ReactNativeFeatureFlags::cxxNativeAnimatedRemoveJsSync();
532538
}
533539

540+
bool JReactNativeFeatureFlagsCxxInterop::disableEarlyViewCommandExecution(
541+
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
542+
return ReactNativeFeatureFlags::disableEarlyViewCommandExecution();
543+
}
544+
534545
bool JReactNativeFeatureFlagsCxxInterop::disableFabricCommitInCXXAnimated(
535546
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
536547
return ReactNativeFeatureFlags::disableFabricCommitInCXXAnimated();
@@ -944,6 +955,9 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() {
944955
makeNativeMethod(
945956
"cxxNativeAnimatedRemoveJsSync",
946957
JReactNativeFeatureFlagsCxxInterop::cxxNativeAnimatedRemoveJsSync),
958+
makeNativeMethod(
959+
"disableEarlyViewCommandExecution",
960+
JReactNativeFeatureFlagsCxxInterop::disableEarlyViewCommandExecution),
947961
makeNativeMethod(
948962
"disableFabricCommitInCXXAnimated",
949963
JReactNativeFeatureFlagsCxxInterop::disableFabricCommitInCXXAnimated),

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<<04929fb70beb00c1f690bdb11703b322>>
7+
* @generated SignedSource<<d6f0bbc55218e36f040c04baa9eb6d8d>>
88
*/
99

1010
/**
@@ -42,6 +42,9 @@ class JReactNativeFeatureFlagsCxxInterop
4242
static bool cxxNativeAnimatedRemoveJsSync(
4343
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
4444

45+
static bool disableEarlyViewCommandExecution(
46+
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
47+
4548
static bool disableFabricCommitInCXXAnimated(
4649
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
4750

packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp

Lines changed: 5 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<<bbe47fc20b10610f0ef14e2d35406145>>
7+
* @generated SignedSource<<68811ae2e7fcb8b8b406097839f669dd>>
88
*/
99

1010
/**
@@ -42,6 +42,10 @@ bool ReactNativeFeatureFlags::cxxNativeAnimatedRemoveJsSync() {
4242
return getAccessor().cxxNativeAnimatedRemoveJsSync();
4343
}
4444

45+
bool ReactNativeFeatureFlags::disableEarlyViewCommandExecution() {
46+
return getAccessor().disableEarlyViewCommandExecution();
47+
}
48+
4549
bool ReactNativeFeatureFlags::disableFabricCommitInCXXAnimated() {
4650
return getAccessor().disableFabricCommitInCXXAnimated();
4751
}

0 commit comments

Comments
 (0)