From 5097abd9ffb84985343e53c0ff95c80e7ce65f70 Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Fri, 8 May 2026 04:41:52 -0700 Subject: [PATCH] Replace merge commit scheduling with a run loop observer (#56726) Summary: 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` 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 --- .../React/Fabric/RCTSurfacePresenter.mm | 83 +++++++++++++++++-- 1 file changed, 78 insertions(+), 5 deletions(-) diff --git a/packages/react-native/React/Fabric/RCTSurfacePresenter.mm b/packages/react-native/React/Fabric/RCTSurfacePresenter.mm index 5da1d1b75ca8..ab8f3c490780 100644 --- a/packages/react-native/React/Fabric/RCTSurfacePresenter.mm +++ b/packages/react-native/React/Fabric/RCTSurfacePresenter.mm @@ -9,6 +9,7 @@ #import #import +#import #import #import @@ -42,8 +43,28 @@ using namespace facebook::react; @interface RCTSurfacePresenter () +- (void)_drainPendingReactRevisionMerges; @end +namespace { +class ReactRevisionMergeRunLoopObserverDelegate final : public RunLoopObserver::Delegate { + public: + explicit ReactRevisionMergeRunLoopObserverDelegate(RCTSurfacePresenter *surfacePresenter) + : surfacePresenter_(surfacePresenter) + { + } + + void activityDidChange(const RunLoopObserver::Delegate * /*delegate*/, RunLoopObserver::Activity /*activity*/) + const noexcept override + { + [surfacePresenter_ _drainPendingReactRevisionMerges]; + } + + private: + __weak RCTSurfacePresenter *surfacePresenter_; +}; +} // namespace + @implementation RCTSurfacePresenter { RCTMountingManager *_mountingManager; // Thread-safe. RCTSurfaceRegistry *_surfaceRegistry; // Thread-safe. @@ -57,6 +78,12 @@ @implementation RCTSurfacePresenter { std::shared_mutex _observerListMutex; std::vector<__weak id> _observers; // Protected by `_observerListMutex`. + + std::mutex _pendingReactRevisionMergesMutex; + // Pending React revision merges, drained on the main run loop just before it sleeps. + std::unordered_set _pendingReactRevisionMerges; // Protected by `_pendingReactRevisionMergesMutex`. + std::shared_ptr _mergeRunLoopObserverDelegate; + std::unique_ptr _mergeRunLoopObserver; } - (instancetype)initWithContextContainer:(std::shared_ptr)contextContainer @@ -74,6 +101,14 @@ - (instancetype)initWithContextContainer:(std::shared_ptr(self); + _mergeRunLoopObserver = std::make_unique( + RunLoopObserver::Activity::BeforeWaiting, _mergeRunLoopObserverDelegate); + _mergeRunLoopObserver->setDelegate(_mergeRunLoopObserverDelegate.get()); + _mergeRunLoopObserver->enable(); + } + _scheduler = [self _createScheduler]; [[NSNotificationCenter defaultCenter] addObserver:self @@ -309,11 +344,49 @@ - (void)schedulerShouldRenderTransactions:(std::shared_ptrgetShadowTreeRegistry().visit( - surfaceId, [](const ShadowTree &shadowTree) { shadowTree.mergeReactRevision(); }); - }); + if (RCTIsMainQueue()) { + [self _mergeReactRevisionForSurfaceId:surfaceId]; + return; + } + + std::lock_guard lock(_pendingReactRevisionMergesMutex); + _pendingReactRevisionMerges.insert(surfaceId); +} + +- (void)_mergeReactRevisionForSurfaceId:(SurfaceId)surfaceId +{ + RCTAssertMainQueue(); + RCTScheduler *scheduler = [self scheduler]; + if (!scheduler) { + return; + } + + auto uiManager = scheduler.uiManager; + if (!uiManager) { + return; + } + + uiManager->getShadowTreeRegistry().visit( + surfaceId, [](const ShadowTree &shadowTree) { shadowTree.mergeReactRevision(); }); +} + +- (void)_drainPendingReactRevisionMerges +{ + RCTAssertMainQueue(); + + std::unordered_set pending; + { + std::lock_guard lock(_pendingReactRevisionMergesMutex); + if (_pendingReactRevisionMerges.empty()) { + return; + } + + pending.swap(_pendingReactRevisionMerges); + } + + for (auto surfaceId : pending) { + [self _mergeReactRevisionForSurfaceId:surfaceId]; + } } - (void)schedulerDidDispatchCommand:(const ShadowView &)shadowView