Skip to content

Commit e44fdfe

Browse files
rubennortesammy-SCtomekzawpiaskowyk
authored andcommitted
Set up test to reduce the amount of failed commits in ShadowTree (#38706)
Summary: Pull Request resolved: #38706 Fabric has a mechanism to resolve conflicts when there are race conditions committing new versions in different threads. This mechanism forces commits to be done in order, retrying commits when they didn't have time to finish before another concurrent commit. {F1061612667} This mechanism has an important drawback. If we have a continuous stream of short commits (e.g.: animations via Reanimated) and we want to do a large commit (e.g.: coming from React), the large commit could exhaust all attempts to finish and ultimately fail. Every time we tried to commit the large one, another small one would have had a chance to finish, forcing another retry. {F1061614717} In most of the cases where this happens, we didn't even need to fail the commits in the first place! **The only reason why we retry commits is so we make sure we are propagating the last state** (when state propagation is enabled in that commit). We don't reconcile the contents of the commits themselves (the tree we retry is exactly the same, if we exclude state). We can reduce the number of reconciliation failures (and completely remove them in the case of animations) if instead of checking if the base revision for a commit changed, we checked if the version of the state we propagated changed. We would have 3 scenarios when doing that: 1. Commit creating state vs. commit reusing state. If the commit creating the state went first, we would have to retry the commit reusing the state (to make sure we're reusing the new state). If the commit reusing the state went first, we wouldn't have conflicts, and we would just apply the new state on top of it. {F1061617730} 2. Commit reusing state vs. another commit reusing state. In this case the order doesn't matter and we don't need to trigger a conflict. Commits would be updated in the order in which they are applied. {F1061618406} 3. Commit creating state vs. commit creating state. In this case, we are not propagating state, so retrying the commits wouldn't do anything. We can ignore the conflicts in this case too. {F1061618689} Changelog: [internal] Reviewed By: sammy-SC Differential Revision: D47915384 fbshipit-source-id: bb4510c59bf32ca342c02f3bdb7029b545f56d99 Co-authored-by: Samuel Susla <samuel.susla@gmail.com> Co-authored-by: David Vacca Co-authored-by: Tomek Zawadzki <tomasz.zawadzki@swmansion.com> Co-authored-by: Krzysztof Piaskowy <krzysztof.piaskowy@swmansion.com>
1 parent e29b5bb commit e44fdfe

5 files changed

Lines changed: 35 additions & 3 deletions

File tree

packages/react-native/ReactCommon/react/renderer/mounting/ShadowTree.cpp

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include <react/renderer/mounting/ShadowTreeRevision.h>
1717
#include <react/renderer/mounting/ShadowViewMutation.h>
1818
#include <react/renderer/telemetry/TransactionTelemetry.h>
19+
#include <react/utils/CoreFeatures.h>
1920

2021
#include "ShadowTreeDelegate.h"
2122

@@ -249,6 +250,8 @@ ShadowTree::ShadowTree(
249250
currentRevision_ = ShadowTreeRevision{
250251
rootShadowNode, INITIAL_REVISION, TransactionTelemetry{}};
251252

253+
lastRevisionNumberWithNewState_ = currentRevision_.number;
254+
252255
mountingCoordinator_ =
253256
std::make_shared<MountingCoordinator const>(currentRevision_);
254257
}
@@ -322,12 +325,14 @@ CommitStatus ShadowTree::tryCommit(
322325
CommitMode commitMode;
323326
auto oldRevision = ShadowTreeRevision{};
324327
auto newRevision = ShadowTreeRevision{};
328+
ShadowTreeRevision::Number lastRevisionNumberWithNewState;
325329

326330
{
327331
// Reading `currentRevision_` in shared manner.
328332
std::shared_lock lock(commitMutex_);
329333
commitMode = commitMode_;
330334
oldRevision = currentRevision_;
335+
lastRevisionNumberWithNewState = lastRevisionNumberWithNewState_;
331336
}
332337

333338
auto const &oldRootShadowNode = oldRevision.rootShadowNode;
@@ -377,11 +382,21 @@ CommitStatus ShadowTree::tryCommit(
377382
return CommitStatus::Cancelled;
378383
}
379384

380-
if (currentRevision_.number != oldRevision.number) {
381-
return CommitStatus::Failed;
385+
if (CoreFeatures::enableGranularShadowTreeStateReconciliation) {
386+
auto lastRevisionNumberWithNewStateChanged =
387+
lastRevisionNumberWithNewState != lastRevisionNumberWithNewState_;
388+
// Commit should only fail if we propagated the wrong state.
389+
if (commitOptions.enableStateReconciliation &&
390+
lastRevisionNumberWithNewStateChanged) {
391+
return CommitStatus::Failed;
392+
}
393+
} else {
394+
if (currentRevision_.number != oldRevision.number) {
395+
return CommitStatus::Failed;
396+
}
382397
}
383398

384-
auto newRevisionNumber = oldRevision.number + 1;
399+
auto newRevisionNumber = currentRevision_.number + 1;
385400

386401
{
387402
std::scoped_lock dispatchLock(EventEmitter::DispatchMutex());
@@ -398,6 +413,9 @@ CommitStatus ShadowTree::tryCommit(
398413
std::move(newRootShadowNode), newRevisionNumber, telemetry};
399414

400415
currentRevision_ = newRevision;
416+
if (!commitOptions.enableStateReconciliation) {
417+
lastRevisionNumberWithNewState_ = newRevisionNumber;
418+
}
401419
}
402420

403421
emitLayoutEvents(affectedLayoutableNodes);

packages/react-native/ReactCommon/react/renderer/mounting/ShadowTree.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ class ShadowTree final {
5555
};
5656

5757
struct CommitOptions {
58+
// When set to true, Shadow Node state from current revision will be applied
59+
// to the new revision. For more details see
60+
// https://reactnative.dev/architecture/render-pipeline#react-native-renderer-state-updates
5861
bool enableStateReconciliation{false};
5962

6063
// Indicates if mounting will be triggered synchronously and React will
@@ -144,6 +147,8 @@ class ShadowTree final {
144147
mutable CommitMode commitMode_{
145148
CommitMode::Normal}; // Protected by `commitMutex_`.
146149
mutable ShadowTreeRevision currentRevision_; // Protected by `commitMutex_`.
150+
mutable ShadowTreeRevision::Number
151+
lastRevisionNumberWithNewState_; // Protected by `commitMutex_`.
147152
MountingCoordinator::Shared mountingCoordinator_;
148153
};
149154

packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,10 @@ Scheduler::Scheduler(
134134
CoreFeatures::cacheLastTextMeasurement =
135135
reactNativeConfig_->getBool("react_fabric:enable_text_measure_cache");
136136

137+
CoreFeatures::enableGranularShadowTreeStateReconciliation =
138+
reactNativeConfig_->getBool(
139+
"react_fabric:enable_granular_shadow_tree_state_reconciliation");
140+
137141
if (animationDelegate != nullptr) {
138142
animationDelegate->setComponentDescriptorRegistry(
139143
componentDescriptorRegistry_);

packages/react-native/ReactCommon/react/utils/CoreFeatures.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,6 @@ bool CoreFeatures::enableMountHooks = false;
2020
bool CoreFeatures::doNotSwapLeftAndRightOnAndroidInLTR = false;
2121
bool CoreFeatures::enableCleanParagraphYogaNode = false;
2222
bool CoreFeatures::disableScrollEventThrottleRequirement = false;
23+
bool CoreFeatures::enableGranularShadowTreeStateReconciliation = false;
2324

2425
} // namespace facebook::react

packages/react-native/ReactCommon/react/utils/CoreFeatures.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ class CoreFeatures {
6060
// Fire `onScroll` events continuously on iOS without a `scrollEventThrottle`
6161
// props, and provide continuous `onScroll` upates like other platforms.
6262
static bool disableScrollEventThrottleRequirement;
63+
64+
// When enabled, the renderer would only fail commits when they propagate
65+
// state and the last commit that updated state changed before committing.
66+
static bool enableGranularShadowTreeStateReconciliation;
6367
};
6468

6569
} // namespace facebook::react

0 commit comments

Comments
 (0)