Replace merge commit scheduling with a run loop observer (#56726)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/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
This commit is contained in:
Jakub Piasecki
2026-05-11 04:04:40 -07:00
committed by meta-codesync[bot]
parent c0bf1549c2
commit d80377cd60
@@ -9,6 +9,7 @@
#import <mutex>
#import <shared_mutex>
#import <unordered_set>
#import <React/RCTAssert.h>
#import <React/RCTBridge+Private.h>
@@ -42,8 +43,28 @@ using namespace facebook;
using namespace facebook::react;
@interface RCTSurfacePresenter () <RCTSchedulerDelegate, RCTMountingManagerDelegate>
- (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 @@ using namespace facebook::react;
std::shared_mutex _observerListMutex;
std::vector<__weak id<RCTSurfacePresenterObserver>> _observers; // Protected by `_observerListMutex`.
std::mutex _pendingReactRevisionMergesMutex;
// Pending React revision merges, drained on the main run loop just before it sleeps.
std::unordered_set<SurfaceId> _pendingReactRevisionMerges; // Protected by `_pendingReactRevisionMergesMutex`.
std::shared_ptr<ReactRevisionMergeRunLoopObserverDelegate> _mergeRunLoopObserverDelegate;
std::unique_ptr<const MainRunLoopObserver> _mergeRunLoopObserver;
}
- (instancetype)initWithContextContainer:(std::shared_ptr<const ContextContainer>)contextContainer
@@ -74,6 +101,14 @@ using namespace facebook::react;
_mountingManager.contextContainer = contextContainer;
_mountingManager.delegate = self;
if (ReactNativeFeatureFlags::enableFabricCommitBranching()) {
_mergeRunLoopObserverDelegate = std::make_shared<ReactRevisionMergeRunLoopObserverDelegate>(self);
_mergeRunLoopObserver = std::make_unique<const MainRunLoopObserver>(
RunLoopObserver::Activity::BeforeWaiting, _mergeRunLoopObserverDelegate);
_mergeRunLoopObserver->setDelegate(_mergeRunLoopObserverDelegate.get());
_mergeRunLoopObserver->enable();
}
_scheduler = [self _createScheduler];
[[NSNotificationCenter defaultCenter] addObserver:self
@@ -309,11 +344,49 @@ using namespace facebook::react;
- (void)schedulerShouldMergeReactRevision:(SurfaceId)surfaceId
{
auto scheduler = [self scheduler];
RCTExecuteOnMainQueue(^{
scheduler.uiManager->getShadowTreeRegistry().visit(
surfaceId, [](const ShadowTree &shadowTree) { shadowTree.mergeReactRevision(); });
});
if (RCTIsMainQueue()) {
[self _mergeReactRevisionForSurfaceId:surfaceId];
return;
}
std::lock_guard<std::mutex> 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<SurfaceId> pending;
{
std::lock_guard<std::mutex> lock(_pendingReactRevisionMergesMutex);
if (_pendingReactRevisionMerges.empty()) {
return;
}
pending.swap(_pendingReactRevisionMerges);
}
for (auto surfaceId : pending) {
[self _mergeReactRevisionForSurfaceId:surfaceId];
}
}
- (void)schedulerDidDispatchCommand:(const ShadowView &)shadowView