Skip to content

Commit d80377c

Browse files
j-piaseckimeta-codesync[bot]
authored andcommitted
Replace merge commit scheduling with a run loop observer (#56726)
Summary: Pull Request resolved: #56726 Changelog: [IOS][CHANGED] - Drain React-revision merges from a `BeforeWaiting` main run loop observer instead of dispatching each merge as a separate main-queue block With Fabric commit branching (`enableFabricCommitBranching`), React commits land on a forked `currentReactRevision_` and are merged into the main branch later via `ShadowTree::mergeReactRevision()`. On iOS, the `schedulerShouldMergeReactRevision:` callback used to do a plain `RCTExecuteOnMainQueue` per promotion, so every promotion enqueued a fresh main queue block that competed for ordering with mount blocks dispatched from concurrent commits with `mountSynchronously=true`. Instead of dispatching each merge as its own main-queue block, enqueue the surface id into an `unordered_set<SurfaceId>` and drain it from a `MainRunLoopObserver` registered at `kCFRunLoopBeforeWaiting`. The merge calls `ShadowTree::commit(..., mountSynchronously = true)`, so the mount completes inline before the observer returns. A similar mechanism has already been implemented on Android in D100966623 Reviewed By: javache Differential Revision: D104227839 fbshipit-source-id: 94953290fad3e65f038846ba81e05591756864a8
1 parent c0bf154 commit d80377c

1 file changed

Lines changed: 78 additions & 5 deletions

File tree

packages/react-native/React/Fabric/RCTSurfacePresenter.mm

Lines changed: 78 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
#import <mutex>
1111
#import <shared_mutex>
12+
#import <unordered_set>
1213

1314
#import <React/RCTAssert.h>
1415
#import <React/RCTBridge+Private.h>
@@ -42,8 +43,28 @@
4243
using namespace facebook::react;
4344

4445
@interface RCTSurfacePresenter () <RCTSchedulerDelegate, RCTMountingManagerDelegate>
46+
- (void)_drainPendingReactRevisionMerges;
4547
@end
4648

49+
namespace {
50+
class ReactRevisionMergeRunLoopObserverDelegate final : public RunLoopObserver::Delegate {
51+
public:
52+
explicit ReactRevisionMergeRunLoopObserverDelegate(RCTSurfacePresenter *surfacePresenter)
53+
: surfacePresenter_(surfacePresenter)
54+
{
55+
}
56+
57+
void activityDidChange(const RunLoopObserver::Delegate * /*delegate*/, RunLoopObserver::Activity /*activity*/)
58+
const noexcept override
59+
{
60+
[surfacePresenter_ _drainPendingReactRevisionMerges];
61+
}
62+
63+
private:
64+
__weak RCTSurfacePresenter *surfacePresenter_;
65+
};
66+
} // namespace
67+
4768
@implementation RCTSurfacePresenter {
4869
RCTMountingManager *_mountingManager; // Thread-safe.
4970
RCTSurfaceRegistry *_surfaceRegistry; // Thread-safe.
@@ -57,6 +78,12 @@ @implementation RCTSurfacePresenter {
5778

5879
std::shared_mutex _observerListMutex;
5980
std::vector<__weak id<RCTSurfacePresenterObserver>> _observers; // Protected by `_observerListMutex`.
81+
82+
std::mutex _pendingReactRevisionMergesMutex;
83+
// Pending React revision merges, drained on the main run loop just before it sleeps.
84+
std::unordered_set<SurfaceId> _pendingReactRevisionMerges; // Protected by `_pendingReactRevisionMergesMutex`.
85+
std::shared_ptr<ReactRevisionMergeRunLoopObserverDelegate> _mergeRunLoopObserverDelegate;
86+
std::unique_ptr<const MainRunLoopObserver> _mergeRunLoopObserver;
6087
}
6188

6289
- (instancetype)initWithContextContainer:(std::shared_ptr<const ContextContainer>)contextContainer
@@ -74,6 +101,14 @@ - (instancetype)initWithContextContainer:(std::shared_ptr<const ContextContainer
74101
_mountingManager.contextContainer = contextContainer;
75102
_mountingManager.delegate = self;
76103

104+
if (ReactNativeFeatureFlags::enableFabricCommitBranching()) {
105+
_mergeRunLoopObserverDelegate = std::make_shared<ReactRevisionMergeRunLoopObserverDelegate>(self);
106+
_mergeRunLoopObserver = std::make_unique<const MainRunLoopObserver>(
107+
RunLoopObserver::Activity::BeforeWaiting, _mergeRunLoopObserverDelegate);
108+
_mergeRunLoopObserver->setDelegate(_mergeRunLoopObserverDelegate.get());
109+
_mergeRunLoopObserver->enable();
110+
}
111+
77112
_scheduler = [self _createScheduler];
78113

79114
[[NSNotificationCenter defaultCenter] addObserver:self
@@ -309,11 +344,49 @@ - (void)schedulerShouldRenderTransactions:(std::shared_ptr<const MountingCoordin
309344

310345
- (void)schedulerShouldMergeReactRevision:(SurfaceId)surfaceId
311346
{
312-
auto scheduler = [self scheduler];
313-
RCTExecuteOnMainQueue(^{
314-
scheduler.uiManager->getShadowTreeRegistry().visit(
315-
surfaceId, [](const ShadowTree &shadowTree) { shadowTree.mergeReactRevision(); });
316-
});
347+
if (RCTIsMainQueue()) {
348+
[self _mergeReactRevisionForSurfaceId:surfaceId];
349+
return;
350+
}
351+
352+
std::lock_guard<std::mutex> lock(_pendingReactRevisionMergesMutex);
353+
_pendingReactRevisionMerges.insert(surfaceId);
354+
}
355+
356+
- (void)_mergeReactRevisionForSurfaceId:(SurfaceId)surfaceId
357+
{
358+
RCTAssertMainQueue();
359+
RCTScheduler *scheduler = [self scheduler];
360+
if (!scheduler) {
361+
return;
362+
}
363+
364+
auto uiManager = scheduler.uiManager;
365+
if (!uiManager) {
366+
return;
367+
}
368+
369+
uiManager->getShadowTreeRegistry().visit(
370+
surfaceId, [](const ShadowTree &shadowTree) { shadowTree.mergeReactRevision(); });
371+
}
372+
373+
- (void)_drainPendingReactRevisionMerges
374+
{
375+
RCTAssertMainQueue();
376+
377+
std::unordered_set<SurfaceId> pending;
378+
{
379+
std::lock_guard<std::mutex> lock(_pendingReactRevisionMergesMutex);
380+
if (_pendingReactRevisionMerges.empty()) {
381+
return;
382+
}
383+
384+
pending.swap(_pendingReactRevisionMerges);
385+
}
386+
387+
for (auto surfaceId : pending) {
388+
[self _mergeReactRevisionForSurfaceId:surfaceId];
389+
}
317390
}
318391

319392
- (void)schedulerDidDispatchCommand:(const ShadowView &)shadowView

0 commit comments

Comments
 (0)