Skip to content

Commit 94d33e3

Browse files
j-piaseckifacebook-github-bot
authored andcommitted
Replace merge commit schediling with a run loop observer
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<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. Differential Revision: D104227839
1 parent 18ef5d1 commit 94d33e3

1 file changed

Lines changed: 84 additions & 5 deletions

File tree

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

Lines changed: 84 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
@@ -76,6 +103,13 @@ - (instancetype)initWithContextContainer:(std::shared_ptr<const ContextContainer
76103

77104
_scheduler = [self _createScheduler];
78105

106+
if (ReactNativeFeatureFlags::enableFabricCommitBranching()) {
107+
_mergeRunLoopObserverDelegate = std::make_shared<ReactRevisionMergeRunLoopObserverDelegate>(self);
108+
_mergeRunLoopObserver = std::make_unique<const MainRunLoopObserver>(
109+
RunLoopObserver::Activity::BeforeWaiting, _mergeRunLoopObserverDelegate);
110+
_mergeRunLoopObserver->setDelegate(_mergeRunLoopObserverDelegate.get());
111+
}
112+
79113
[[NSNotificationCenter defaultCenter] addObserver:self
80114
selector:@selector(_applicationWillTerminate)
81115
name:UIApplicationWillTerminateNotification
@@ -309,11 +343,56 @@ - (void)schedulerShouldRenderTransactions:(std::shared_ptr<const MountingCoordin
309343

310344
- (void)schedulerShouldMergeReactRevision:(SurfaceId)surfaceId
311345
{
312-
auto scheduler = [self scheduler];
313-
RCTExecuteOnMainQueue(^{
314-
scheduler.uiManager->getShadowTreeRegistry().visit(
315-
surfaceId, [](const ShadowTree &shadowTree) { shadowTree.mergeReactRevision(); });
316-
});
346+
if (RCTIsMainQueue()) {
347+
[self _mergeReactRevisionForSurfaceId:surfaceId];
348+
return;
349+
}
350+
351+
{
352+
// Hold the mutex across `enable()` to serialize with `disable()` in
353+
// `_drainPendingReactRevisionMerges`.
354+
std::lock_guard<std::mutex> lock(_pendingReactRevisionMergesMutex);
355+
_pendingReactRevisionMerges.insert(surfaceId);
356+
_mergeRunLoopObserver->enable();
357+
}
358+
359+
CFRunLoopWakeUp(CFRunLoopGetMain());
360+
}
361+
362+
- (void)_mergeReactRevisionForSurfaceId:(SurfaceId)surfaceId
363+
{
364+
RCTAssertMainQueue();
365+
RCTScheduler *scheduler = [self scheduler];
366+
if (!scheduler) {
367+
return;
368+
}
369+
370+
auto uiManager = scheduler.uiManager;
371+
if (!uiManager) {
372+
return;
373+
}
374+
375+
uiManager->getShadowTreeRegistry().visit(
376+
surfaceId, [](const ShadowTree &shadowTree) { shadowTree.mergeReactRevision(); });
377+
}
378+
379+
- (void)_drainPendingReactRevisionMerges
380+
{
381+
RCTAssertMainQueue();
382+
383+
std::unordered_set<SurfaceId> pending;
384+
{
385+
std::lock_guard<std::mutex> lock(_pendingReactRevisionMergesMutex);
386+
pending.swap(_pendingReactRevisionMerges);
387+
388+
// The queue is empty, so disable the observer to avoid waking on every
389+
// run loop tick. It's re-enabled on the next enqueue.
390+
_mergeRunLoopObserver->disable();
391+
}
392+
393+
for (auto surfaceId : pending) {
394+
[self _mergeReactRevisionForSurfaceId:surfaceId];
395+
}
317396
}
318397

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

0 commit comments

Comments
 (0)