From a514ab2f85e9888ea75126a214a91e4e2dc4439c Mon Sep 17 00:00:00 2001 From: safaiyeh Date: Sun, 27 Sep 2026 22:55:21 -0700 Subject: [PATCH] fix(iOS): retain active Scheduler delegates and retire queued work --- .../react-native/React/Fabric/RCTScheduler.mm | 26 +- .../react/renderer/scheduler/Scheduler.cpp | 110 +++++--- .../react/renderer/scheduler/Scheduler.h | 27 +- .../scheduler/SchedulerDelegateRegistration.h | 85 ++++++ .../SchedulerDelegateInvalidationTest.cpp | 251 ++++++++++++++---- .../SchedulerDelegateRegistrationTest.cpp | 195 ++++++++++++++ .../api-snapshots/ReactAndroidDebugCxx.api | 15 ++ .../api-snapshots/ReactAndroidNewarchCxx.api | 15 ++ .../api-snapshots/ReactAndroidReleaseCxx.api | 15 ++ .../api-snapshots/ReactAppleDebugCxx.api | 15 ++ .../api-snapshots/ReactAppleNewarchCxx.api | 15 ++ .../api-snapshots/ReactAppleReleaseCxx.api | 15 ++ .../api-snapshots/ReactCommonDebugCxx.api | 15 ++ .../api-snapshots/ReactCommonNewarchCxx.api | 15 ++ .../api-snapshots/ReactCommonReleaseCxx.api | 15 ++ 15 files changed, 730 insertions(+), 99 deletions(-) create mode 100644 packages/react-native/ReactCommon/react/renderer/scheduler/SchedulerDelegateRegistration.h create mode 100644 packages/react-native/ReactCommon/react/renderer/scheduler/tests/SchedulerDelegateRegistrationTest.cpp diff --git a/packages/react-native/React/Fabric/RCTScheduler.mm b/packages/react-native/React/Fabric/RCTScheduler.mm index 9519f18428e4..54e8da5e9246 100644 --- a/packages/react-native/React/Fabric/RCTScheduler.mm +++ b/packages/react-native/React/Fabric/RCTScheduler.mm @@ -23,23 +23,23 @@ class SchedulerDelegateProxy : public SchedulerDelegate { public: - SchedulerDelegateProxy(void *scheduler) : scheduler_(scheduler) {} + SchedulerDelegateProxy(RCTScheduler *scheduler) : scheduler_(scheduler) {} void schedulerDidFinishTransaction(const std::shared_ptr &mountingCoordinator) override { - RCTScheduler *scheduler = (__bridge RCTScheduler *)scheduler_; + RCTScheduler *scheduler = scheduler_; [scheduler.delegate schedulerDidFinishTransaction:mountingCoordinator]; } void schedulerShouldRenderTransactions(const std::shared_ptr &mountingCoordinator) override { - RCTScheduler *scheduler = (__bridge RCTScheduler *)scheduler_; + RCTScheduler *scheduler = scheduler_; [scheduler.delegate schedulerShouldRenderTransactions:mountingCoordinator]; } void schedulerShouldMergeReactRevision(SurfaceId surfaceId) override { - RCTScheduler *scheduler = (__bridge RCTScheduler *)scheduler_; + RCTScheduler *scheduler = scheduler_; [scheduler.delegate schedulerShouldMergeReactRevision:surfaceId]; } @@ -54,14 +54,14 @@ void schedulerDidDispatchCommand( const std::string &commandName, const folly::dynamic &args) override { - RCTScheduler *scheduler = (__bridge RCTScheduler *)scheduler_; + RCTScheduler *scheduler = scheduler_; [scheduler.delegate schedulerDidDispatchCommand:shadowView commandName:commandName args:args]; } void schedulerDidSetIsJSResponder(const ShadowView &shadowView, bool isJSResponder, bool blockNativeResponder) override { - RCTScheduler *scheduler = (__bridge RCTScheduler *)scheduler_; + RCTScheduler *scheduler = scheduler_; [scheduler.delegate schedulerDidSetIsJSResponder:isJSResponder blockNativeResponder:blockNativeResponder forShadowView:shadowView]; @@ -69,13 +69,13 @@ void schedulerDidSetIsJSResponder(const ShadowView &shadowView, bool isJSRespond void schedulerDidSendAccessibilityEvent(const ShadowView &shadowView, const std::string &eventType) override { - RCTScheduler *scheduler = (__bridge RCTScheduler *)scheduler_; + RCTScheduler *scheduler = scheduler_; [scheduler.delegate schedulerDidSendAccessibilityEvent:shadowView eventType:eventType]; } void schedulerShouldSynchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic &props) override { - RCTScheduler *scheduler = (__bridge RCTScheduler *)scheduler_; + RCTScheduler *scheduler = scheduler_; [scheduler.delegate schedulerDidSynchronouslyUpdateViewOnUIThread:tag props:props]; } @@ -104,7 +104,7 @@ void schedulerDidClearPendingSnapshots() override } private: - void *scheduler_; + __weak RCTScheduler *scheduler_; }; class LayoutAnimationDelegateProxy : public LayoutAnimationStatusDelegate, public RunLoopObserver::Delegate { @@ -187,7 +187,7 @@ void pause() override @implementation RCTScheduler { std::unique_ptr _scheduler; std::shared_ptr _animationDriver; - std::unique_ptr _delegateProxy; + std::shared_ptr _delegateProxy; std::shared_ptr _layoutAnimationDelegateProxy; std::unique_ptr _uiRunLoopObserver; } @@ -195,7 +195,7 @@ @implementation RCTScheduler { - (instancetype)initWithToolbox:(SchedulerToolbox)toolbox { if (self = [super init]) { - _delegateProxy = std::make_unique((__bridge void *)self); + _delegateProxy = std::make_shared(self); if (ReactNativeFeatureFlags::enableLayoutAnimationsOnIOS()) { _layoutAnimationDelegateProxy = std::make_shared((__bridge void *)self); @@ -212,8 +212,8 @@ - (instancetype)initWithToolbox:(SchedulerToolbox)toolbox toolbox.animationChoreographer = std::make_shared(); } - _scheduler = std::make_unique( - toolbox, (_animationDriver ? _animationDriver.get() : nullptr), _delegateProxy.get()); + _scheduler = + std::make_unique(toolbox, (_animationDriver ? _animationDriver.get() : nullptr), _delegateProxy); } return self; diff --git a/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp b/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp index 81d9de17660c..8c082119f88f 100644 --- a/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp +++ b/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp @@ -7,6 +7,8 @@ #include "Scheduler.h" +#include "SchedulerDelegateRegistration.h" + #include #include @@ -36,7 +38,27 @@ Scheduler::Scheduler( const SchedulerToolbox& schedulerToolbox, UIManagerAnimationDelegate* animationDelegate, SchedulerDelegate* delegate) - : runtimeExecutor_(schedulerToolbox.runtimeExecutor), + : Scheduler( + schedulerToolbox, + animationDelegate, + std::make_shared(delegate)) {} + +Scheduler::Scheduler( + const SchedulerToolbox& schedulerToolbox, + UIManagerAnimationDelegate* animationDelegate, + std::shared_ptr delegate) + : Scheduler( + schedulerToolbox, + animationDelegate, + std::make_shared( + std::move(delegate))) {} + +Scheduler::Scheduler( + const SchedulerToolbox& schedulerToolbox, + UIManagerAnimationDelegate* animationDelegate, + std::shared_ptr delegateRegistration) + : delegateRegistration_(std::move(delegateRegistration)), + runtimeExecutor_(schedulerToolbox.runtimeExecutor), contextContainer_(schedulerToolbox.contextContainer) { // Creating a container for future `EventDispatcher` instance. eventDispatcher_ = std::make_shared>(); @@ -156,7 +178,6 @@ Scheduler::Scheduler( std::weak_ptr( componentDescriptorRegistry_)); - delegate_ = delegate; commitHooks_ = schedulerToolbox.commitHooks; // Layout events (`onLayout`) are emitted as a standalone consumer of the @@ -185,6 +206,8 @@ Scheduler::Scheduler( } Scheduler::~Scheduler() { + std::atomic_load(&delegateRegistration_)->retire(); + LOG(WARNING) << "Scheduler::~Scheduler() was called (address: " << this << ")."; @@ -278,11 +301,23 @@ Scheduler::findComponentDescriptorByHandle_DO_NOT_USE_THIS_IS_BROKEN( #pragma mark - Delegate void Scheduler::setDelegate(SchedulerDelegate* delegate) { - delegate_ = delegate; + replaceDelegate(std::make_shared(delegate)); +} + +void Scheduler::setOwnedDelegate(std::shared_ptr delegate) { + replaceDelegate( + std::make_shared(std::move(delegate))); +} + +void Scheduler::replaceDelegate( + std::shared_ptr delegateRegistration) { + auto previous = std::atomic_exchange( + &delegateRegistration_, std::move(delegateRegistration)); + previous->retire(); } SchedulerDelegate* Scheduler::getDelegate() const { - return delegate_; + return std::atomic_load(&delegateRegistration_)->acquire().get(); } #pragma mark - UIManagerAnimationDelegate @@ -301,29 +336,32 @@ void Scheduler::uiManagerDidFinishTransaction( bool mountSynchronously) { TraceSection s("Scheduler::uiManagerDidFinishTransaction"); - if (delegate_ != nullptr) { + auto registration = std::atomic_load(&delegateRegistration_); + if (auto delegate = registration->acquire()) { // This is no-op on all platforms except for Android where we need to // observe each transaction to be able to mount correctly. - delegate_->schedulerDidFinishTransaction(mountingCoordinator); + delegate->schedulerDidFinishTransaction(mountingCoordinator); if (!mountSynchronously) { auto surfaceId = mountingCoordinator->getSurfaceId(); runtimeScheduler_->scheduleRenderingUpdate( surfaceId, - [delegate = delegate_, + [registration = std::move(registration), mountingCoordinator = std::move(mountingCoordinator)]() { - delegate->schedulerShouldRenderTransactions(mountingCoordinator); + if (auto delegate = registration->acquire()) { + delegate->schedulerShouldRenderTransactions(mountingCoordinator); + } }); } else { - delegate_->schedulerShouldRenderTransactions(mountingCoordinator); + delegate->schedulerShouldRenderTransactions(mountingCoordinator); } } } void Scheduler::uiManagerDidCreateShadowNode(const ShadowNode& shadowNode) { - if (delegate_ != nullptr) { - delegate_->schedulerDidRequestPreliminaryViewAllocation(shadowNode); + if (auto delegate = std::atomic_load(&delegateRegistration_)->acquire()) { + delegate->schedulerDidRequestPreliminaryViewAllocation(shadowNode); } } @@ -333,15 +371,19 @@ void Scheduler::uiManagerDidDispatchCommand( const folly::dynamic& args) { TraceSection s( "Scheduler::uiManagerDispatchCommand", "commandName", commandName); - if (delegate_ != nullptr) { + auto registration = std::atomic_load(&delegateRegistration_); + if (registration->acquire()) { auto shadowView = ShadowView(*shadowNode); runtimeScheduler_->scheduleRenderingUpdate( shadowNode->getSurfaceId(), - [delegate = delegate_, + [registration = std::move(registration), shadowView = std::move(shadowView), commandName, args]() { - delegate->schedulerDidDispatchCommand(shadowView, commandName, args); + if (auto delegate = registration->acquire()) { + delegate->schedulerDidDispatchCommand( + shadowView, commandName, args); + } }); } } @@ -351,9 +393,9 @@ void Scheduler::uiManagerDidSendAccessibilityEvent( const std::string& eventType) { TraceSection s("Scheduler::uiManagerDidSendAccessibilityEvent"); - if (delegate_ != nullptr) { + if (auto delegate = std::atomic_load(&delegateRegistration_)->acquire()) { auto shadowView = ShadowView(*shadowNode); - delegate_->schedulerDidSendAccessibilityEvent(shadowView, eventType); + delegate->schedulerDidSendAccessibilityEvent(shadowView, eventType); } } @@ -364,8 +406,8 @@ void Scheduler::uiManagerDidSetIsJSResponder( const std::shared_ptr& shadowNode, bool isJSResponder, bool blockNativeResponder) { - if (delegate_ != nullptr) { - delegate_->schedulerDidSetIsJSResponder( + if (auto delegate = std::atomic_load(&delegateRegistration_)->acquire()) { + delegate->schedulerDidSetIsJSResponder( ShadowView(*shadowNode), isJSResponder, blockNativeResponder); } } @@ -373,21 +415,21 @@ void Scheduler::uiManagerDidSetIsJSResponder( void Scheduler::uiManagerShouldSynchronouslyUpdateViewOnUIThread( Tag tag, const folly::dynamic& props) { - if (delegate_ != nullptr) { - delegate_->schedulerShouldSynchronouslyUpdateViewOnUIThread(tag, props); + if (auto delegate = std::atomic_load(&delegateRegistration_)->acquire()) { + delegate->schedulerShouldSynchronouslyUpdateViewOnUIThread(tag, props); } } void Scheduler::uiManagerDidUpdateShadowTree( const std::unordered_map& tagToProps) { - if (delegate_ != nullptr) { - delegate_->schedulerDidUpdateShadowTree(tagToProps); + if (auto delegate = std::atomic_load(&delegateRegistration_)->acquire()) { + delegate->schedulerDidUpdateShadowTree(tagToProps); } } void Scheduler::uiManagerDidCaptureViewSnapshot(Tag tag, SurfaceId surfaceId) { - if (delegate_ != nullptr) { - delegate_->schedulerDidCaptureViewSnapshot(tag, surfaceId); + if (auto delegate = std::atomic_load(&delegateRegistration_)->acquire()) { + delegate->schedulerDidCaptureViewSnapshot(tag, surfaceId); } } @@ -395,14 +437,14 @@ void Scheduler::uiManagerDidSetViewSnapshot( Tag sourceTag, Tag targetTag, SurfaceId surfaceId) { - if (delegate_ != nullptr) { - delegate_->schedulerDidSetViewSnapshot(sourceTag, targetTag, surfaceId); + if (auto delegate = std::atomic_load(&delegateRegistration_)->acquire()) { + delegate->schedulerDidSetViewSnapshot(sourceTag, targetTag, surfaceId); } } void Scheduler::uiManagerDidClearPendingSnapshots() { - if (delegate_ != nullptr) { - delegate_->schedulerDidClearPendingSnapshots(); + if (auto delegate = std::atomic_load(&delegateRegistration_)->acquire()) { + delegate->schedulerDidClearPendingSnapshots(); } } @@ -417,13 +459,21 @@ void Scheduler::uiManagerShouldRemoveEventListener( } void Scheduler::uiManagerDidFinishReactCommit(const ShadowTree& shadowTree) { - if (delegate_ == nullptr) { + auto registration = std::atomic_load(&delegateRegistration_); + if (!registration->acquire()) { return; } auto surfaceId = shadowTree.getSurfaceId(); runtimeScheduler_->scheduleRenderingUpdate( - surfaceId, [surfaceId, uiManager = uiManager_, delegate = delegate_]() { + surfaceId, + [surfaceId, + uiManager = uiManager_, + registration = std::move(registration)]() { + auto delegate = registration->acquire(); + if (!delegate) { + return; + } bool promoted = false; uiManager->getShadowTreeRegistry().visit( diff --git a/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.h b/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.h index 179d6701727e..363536835e93 100644 --- a/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.h +++ b/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.h @@ -33,6 +33,7 @@ namespace facebook::react { +class SchedulerDelegateRegistration; class CdpMetricsReporter; class CdpPerfIssuesReporter; class EventPerformanceLogger; @@ -48,6 +49,10 @@ class Scheduler final : public UIManagerDelegate { const SchedulerToolbox &schedulerToolbox, UIManagerAnimationDelegate *animationDelegate, SchedulerDelegate *delegate); + Scheduler( + const SchedulerToolbox &schedulerToolbox, + UIManagerAnimationDelegate *animationDelegate, + std::shared_ptr delegate); ~Scheduler() override; #pragma mark - Surface Management @@ -68,11 +73,19 @@ class Scheduler final : public UIManagerDelegate { #pragma mark - Delegate /* - * Sets and gets the Scheduler's delegate. - * If you requesting a ComponentDescriptor and unsure that it's there, you are - * doing something wrong. + * Each assignment starts a new delegate generation and cancels queued work + * for the previous generation. Already active callbacks may finish. + * Lifecycle mutations (replacement and destruction) must be serialized by + * the caller. Already acquired delegate invocations may overlap retirement. + * Calls into Scheduler itself still require the caller to keep it alive; + * only deferred rendering callbacks are independent of Scheduler's lifetime. + * A borrowed delegate must outlive its active callbacks. The owned form + * keeps it alive until those callbacks return. + * getDelegate() is a non-owning snapshot; callers must synchronize its use + * with replacement and destruction. */ void setDelegate(SchedulerDelegate *delegate); + void setOwnedDelegate(std::shared_ptr delegate); SchedulerDelegate *getDelegate() const; #pragma mark - UIManagerAnimationDelegate @@ -127,7 +140,13 @@ class Scheduler final : public UIManagerDelegate { private: friend class SurfaceHandler; - SchedulerDelegate *delegate_; + Scheduler( + const SchedulerToolbox &schedulerToolbox, + UIManagerAnimationDelegate *animationDelegate, + std::shared_ptr delegateRegistration); + void replaceDelegate(std::shared_ptr delegateRegistration); + + std::shared_ptr delegateRegistration_; SharedComponentDescriptorRegistry componentDescriptorRegistry_; RuntimeExecutor runtimeExecutor_; std::shared_ptr uiManager_; diff --git a/packages/react-native/ReactCommon/react/renderer/scheduler/SchedulerDelegateRegistration.h b/packages/react-native/ReactCommon/react/renderer/scheduler/SchedulerDelegateRegistration.h new file mode 100644 index 000000000000..262aead55e5a --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/scheduler/SchedulerDelegateRegistration.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include + +#include +#include +#include +#include + +namespace facebook::react { + +class SchedulerDelegate; + +/* + * One delegate generation. Queued work retains the registration, not its target. + * Retirement prevents new leases while existing owned leases may finish. + * Borrowed delegates still require the caller to outlive every active lease. + */ +class SchedulerDelegateRegistration final { + using Target = std::variant>; + + public: + class Lease final { + public: + explicit operator bool() const + { + return target_ != nullptr; + } + + SchedulerDelegate *operator->() const + { + return get(); + } + + SchedulerDelegate *get() const + { + if (!target_) { + return nullptr; + } + if (auto borrowed = std::get_if(target_.get())) { + return *borrowed; + } + return std::get>(*target_).get(); + } + + private: + friend class SchedulerDelegateRegistration; + explicit Lease(std::shared_ptr target) : target_(std::move(target)) {} + std::shared_ptr target_; + }; + + explicit SchedulerDelegateRegistration(SchedulerDelegate *delegate) + : target_(delegate ? std::make_shared(delegate) : nullptr) + { + } + + explicit SchedulerDelegateRegistration(std::shared_ptr delegate) + : target_(delegate ? std::make_shared(std::move(delegate)) : nullptr) + { + } + + Lease acquire() const + { + return Lease(std::atomic_load(&target_)); + } + + void retire() + { + // The last owner is released after the atomic operation's internal lock. + // Delegate destruction may reenter Scheduler, just like delegate methods. + auto previous = std::atomic_exchange(&target_, std::shared_ptr{}); + } + + private: + std::shared_ptr target_; +}; + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/scheduler/tests/SchedulerDelegateInvalidationTest.cpp b/packages/react-native/ReactCommon/react/renderer/scheduler/tests/SchedulerDelegateInvalidationTest.cpp index 76c1646058f9..5d1dbd7b0232 100644 --- a/packages/react-native/ReactCommon/react/renderer/scheduler/tests/SchedulerDelegateInvalidationTest.cpp +++ b/packages/react-native/ReactCommon/react/renderer/scheduler/tests/SchedulerDelegateInvalidationTest.cpp @@ -5,23 +5,9 @@ * LICENSE file in the root directory of this source tree. */ -// Standalone reproduction of the use-after-free race between Scheduler -// teardown and pending rendering-update lambdas previously enqueued via -// runtimeScheduler_->scheduleRenderingUpdate inside -// Scheduler::uiManagerDidFinishTransaction (and its sibling -// uiManagerDidDispatchCommand). The lambda captures the delegate by raw -// pointer; if the delegate is destroyed (as part of an instance teardown -// triggered by an uncaught fatal error) before the lambda runs, the -// dereference is a use-after-free. What closes the race is -// RuntimeScheduler_Modern::handleTaskError, which clears the pending -// rendering updates before the host error handler drops the delegate. -// -// The test drives the *real* Scheduler::uiManagerDidFinishTransaction so the -// rendering-update lambda is enqueued via the regular code path into a real -// RuntimeScheduler's pending-rendering-updates queue. The teardown is -// initiated by an uncaught JSI host-function throw routed through -// RuntimeScheduler's onTaskError callback (the test's analog of the host -// fatal handler), then we trigger an event loop tick to drain the queue. +// Exercise real Scheduler callbacks and the RuntimeScheduler rendering queue. +// Delegate retirement must cover both fatal-error teardown and ordinary host +// replacement/destruction, while preserving work for other generations. // // Fantom is intentionally not used here: it shares the global runtime VM // across tests, which would interfere with this test's contract that no @@ -42,8 +28,8 @@ #include #include #include -#include #include +#include #include #include #include @@ -51,6 +37,7 @@ #include #include #include +#include #include #include @@ -84,8 +71,15 @@ class RecordingDelegate : public SchedulerDelegate { public: RecordingDelegate() = default; + std::function onRenderTransactions; + std::function onDispatchCommand; + std::function onDestroy; + ~RecordingDelegate() noexcept override { aliveMagic_ = kDestroyed; + if (onDestroy) { + onDestroy(); + } } void schedulerDidFinishTransaction( @@ -105,9 +99,14 @@ class RecordingDelegate : public SchedulerDelegate { << "schedulerShouldRenderTransactions invoked after delegate " << "destruction (use-after-free)"; ++shouldRenderTransactionsCount_; + if (onRenderTransactions) { + onRenderTransactions(); + } } - void schedulerShouldMergeReactRevision(SurfaceId /*unused*/) override {} + void schedulerShouldMergeReactRevision(SurfaceId /*unused*/) override { + ++shouldMergeReactRevisionCount_; + } void schedulerDidRequestPreliminaryViewAllocation( const ShadowNode& /*unused*/) override {} void schedulerDidDispatchCommand( @@ -121,6 +120,9 @@ class RecordingDelegate : public SchedulerDelegate { << "schedulerDidDispatchCommand invoked after delegate destruction " << "(commandName=" << commandName << ")"; ++didDispatchCommandCount_; + if (onDispatchCommand) { + onDispatchCommand(); + } } void schedulerDidSendAccessibilityEvent( const ShadowView& /*unused*/, @@ -151,12 +153,16 @@ class RecordingDelegate : public SchedulerDelegate { int didDispatchCommandCount() const { return didDispatchCommandCount_; } + int shouldMergeReactRevisionCount() const { + return shouldMergeReactRevisionCount_; + } private: uint64_t aliveMagic_{kAlive}; int didFinishTransactionCount_{0}; int shouldRenderTransactionsCount_{0}; int didDispatchCommandCount_{0}; + int shouldMergeReactRevisionCount_{0}; }; // EventBeat stub. Scheduler instantiates one through the toolbox factory but @@ -207,6 +213,9 @@ class TestFeatureFlags : public ReactNativeFeatureFlagsDefaults { bool enableBridgelessArchitecture() override { return true; } + bool enableFabricCommitBranching() override { + return true; + } }; // Builds a ComponentRegistryFactory with just the descriptors needed for the @@ -233,7 +242,7 @@ ComponentRegistryFactory makeComponentRegistryFactory() { // a real Scheduler and drive uiManagerDidFinishTransaction end-to-end. class SchedulerDelegateInvalidationTest : public ::testing::Test { protected: - void setUp() { + void setUp(bool ownedDelegate = true) { ReactNativeFeatureFlags::override(std::make_unique()); runtime_ = facebook::hermes::makeHermesRuntime( @@ -264,8 +273,8 @@ class SchedulerDelegateInvalidationTest : public ::testing::Test { // Note: step 1 does NOT drain RuntimeScheduler_Modern's // pendingRenderingUpdates_. That's exactly the point of the test — // surface-level shutdown can't reach the lambda race; the race lives in - // the runtime scheduler's queue and is only closed by the invalidation - // token guard added in Scheduler::setDelegate or by runtime-scheduler-level + // the runtime scheduler's queue and is closed by delegate registration + // retirement in Scheduler::setDelegate or by runtime-scheduler-level // queue clearing on error. auto onTaskError = [this]( jsi::Runtime& /*runtime*/, jsi::JSError& /*error*/) { @@ -301,9 +310,16 @@ class SchedulerDelegateInvalidationTest : public ::testing::Test { std::move(ownerBox), *runtimeScheduler_); }; - delegate_ = std::make_unique(); - scheduler_ = std::make_unique( - toolbox, /*animationDelegate=*/nullptr, delegate_.get()); + delegate_ = std::make_shared(); + if (ownedDelegate) { + scheduler_ = std::make_unique( + toolbox, + /*animationDelegate=*/nullptr, + std::static_pointer_cast(delegate_)); + } else { + scheduler_ = std::make_unique( + toolbox, /*animationDelegate=*/nullptr, delegate_.get()); + } // Register a surface with the scheduler so the teardown path mirrors // production cascade ordering (per-surface unregister BEFORE delegate @@ -320,6 +336,9 @@ class SchedulerDelegateInvalidationTest : public ::testing::Test { } void TearDown() override { + if (scheduler_) { + scheduler_->getUIManager()->getShadowTreeRegistry().remove(11); + } if (surfaceHandler_ && scheduler_ && surfaceHandler_->getStatus() != SurfaceHandler::Status::Unregistered) { scheduler_->unregisterSurface(*surfaceHandler_); @@ -329,6 +348,7 @@ class SchedulerDelegateInvalidationTest : public ::testing::Test { delegate_.reset(); coordinator_.reset(); rootShadowNode_.reset(); + componentDescriptorRegistry_.reset(); runtimeScheduler_.reset(); contextContainer_.reset(); executorQueue_.reset(); @@ -342,7 +362,9 @@ class SchedulerDelegateInvalidationTest : public ::testing::Test { // queue time. Saves the rootShadowNode in the fixture so dispatch-command // tests can pass it to Scheduler::uiManagerDidDispatchCommand. std::shared_ptr makeCoordinator(SurfaceId surfaceId) { - auto builder = simpleComponentBuilder(contextContainer_); + componentDescriptorRegistry_ = makeComponentRegistryFactory()( + EventDispatcher::Weak{}, contextContainer_); + auto builder = ComponentBuilder{componentDescriptorRegistry_}; std::shared_ptr rootShadowNode; auto element = Element() .reference(rootShadowNode) @@ -370,6 +392,27 @@ class SchedulerDelegateInvalidationTest : public ::testing::Test { executorQueue_->flush(); } + void queueReactCommit() { + auto uiManager = scheduler_->getUIManager(); + auto tree = std::make_unique( + 11, + LayoutConstraints{}, + LayoutContext{}, + *uiManager, + *contextContainer_); + auto& shadowTree = *tree; + uiManager->getShadowTreeRegistry().add(std::move(tree)); + shadowTree.commit( + [this](const RootShadowNode&) { + return std::static_pointer_cast( + rootShadowNode_->ShadowNode::clone({})); + }, + {.enableStateReconciliation = false, + .mountSynchronously = false, + .source = ShadowTreeCommitSource::React}); + ASSERT_TRUE(shadowTree.getCurrentReactRevision().has_value()); + } + // Schedules a JS task whose host function throws an uncaught Error. When // the task runs and the throw escapes the host-function boundary, // RuntimeScheduler routes it through the onTaskError callback installed @@ -403,11 +446,12 @@ class SchedulerDelegateInvalidationTest : public ::testing::Test { std::unique_ptr executorQueue_; std::shared_ptr runtimeScheduler_; std::shared_ptr contextContainer_; - std::unique_ptr delegate_; + std::shared_ptr delegate_; std::unique_ptr scheduler_; std::unique_ptr surfaceHandler_; std::shared_ptr coordinator_; std::shared_ptr rootShadowNode_; + SharedComponentDescriptorRegistry componentDescriptorRegistry_; bool jsThrowObserved_{false}; bool postErrorTaskRan_{false}; }; @@ -481,30 +525,16 @@ TEST_F(SchedulerDelegateInvalidationTest, JSThrowInitiatedTeardownIsSafe) { } // --------------------------------------------------------------------------- -// Test 3 — The window that remains open: a delegate detached with no error -// involved. -// -// handleTaskError clears pendingRenderingUpdates_ before the host error -// handler runs, so an error-driven teardown is safe (Test 2). A plain -// setDelegate swap never reaches handleTaskError, so the lambda enqueued in -// (a) still calls through the raw pointer it captured when the queue drains -// in (c). Had the host also destroyed the delegate — as an instance teardown -// does — that call would be a use-after-free. -// -// The delegate is deliberately kept alive here rather than destroyed under an -// EXPECT_DEATH. Asserting on the crash asks undefined behaviour to reliably -// terminate the process, which it does not: the death-test form of this test -// (and its two predecessors) passed on the fbcode host but flaked above 88% -// on the Android instrumentation runner, reporting "failed to die" until -// trunk auto-disabled them. Observing the stale call directly pins the same -// open window deterministically. +// Plain delegate detachment has no JS error to clear the rendering queue. +// Keep the borrowed delegate alive and observe that its stale callback is +// cancelled deterministically, without relying on undefined behaviour to crash. // --------------------------------------------------------------------------- TEST_F( SchedulerDelegateInvalidationTest, - DelegateDetachedWithoutError_PendingRenderingUpdateCallsStaleDelegate) { - setUp(); + DelegateDetachedWithoutError_CancelsPendingRenderingUpdate) { + setUp(/*ownedDelegate=*/false); - // (a) Enqueue a rendering-update lambda capturing delegate_ raw. + // (a) Enqueue work for the current delegate registration. scheduler_->uiManagerDidFinishTransaction( coordinator_, /*mountSynchronously=*/false); EXPECT_EQ(delegate_->shouldRenderTransactionsCount(), 0); @@ -514,10 +544,9 @@ TEST_F( scheduler_->setDelegate(nullptr); EXPECT_EQ(scheduler_->getDelegate(), nullptr); - // (c) Drain — the lambda calls through its captured pointer even though the - // scheduler itself no longer has a delegate. + // (c) Drain — retirement prevents a call through the old registration. runOneEventLoopTick(); - EXPECT_EQ(delegate_->shouldRenderTransactionsCount(), 1); + EXPECT_EQ(delegate_->shouldRenderTransactionsCount(), 0); } // --------------------------------------------------------------------------- @@ -560,8 +589,8 @@ TEST_F( // race wouldn't be reachable." It would not — surface-shutdown clears the // per-surface UIManager pointer but doesn't touch the runtime scheduler's // pending-rendering-updates queue. The lambda still runs and still calls -// the delegate. Only an error-driven queue clear, or a longer-term -// runtime-scheduler-level shutdown signal, closes this race. +// the delegate. Delegate registration retirement cancels that queued work +// when the host detaches the delegate. // --------------------------------------------------------------------------- TEST_F( SchedulerDelegateInvalidationTest, @@ -588,4 +617,122 @@ TEST_F( EXPECT_EQ(delegate_->shouldRenderTransactionsCount(), 1); } +TEST_F( + SchedulerDelegateInvalidationTest, + DetachmentDropsQueuedCommandAndReleasesOwnedDelegate) { + setUp(); + std::weak_ptr weakDelegate = delegate_; + scheduler_->uiManagerDidDispatchCommand( + rootShadowNode_, "scrollTo", folly::dynamic::array()); + scheduler_->setOwnedDelegate(nullptr); + delegate_.reset(); + + EXPECT_TRUE(weakDelegate.expired()); + runOneEventLoopTick(); +} + +TEST_F( + SchedulerDelegateInvalidationTest, + ReplacementPreservesOnlyNewGenerationWork) { + setUp(); + scheduler_->uiManagerDidFinishTransaction(coordinator_, false); + scheduler_->uiManagerDidDispatchCommand( + rootShadowNode_, "old", folly::dynamic::array()); + auto replacement = std::make_shared(); + scheduler_->setOwnedDelegate(replacement); + scheduler_->uiManagerDidFinishTransaction(coordinator_, false); + scheduler_->uiManagerDidDispatchCommand( + rootShadowNode_, "new", folly::dynamic::array()); + int unrelatedCalls = 0; + runtimeScheduler_->scheduleRenderingUpdate(22, [&] { ++unrelatedCalls; }); + + runOneEventLoopTick(); + + EXPECT_EQ(delegate_->shouldRenderTransactionsCount(), 0); + EXPECT_EQ(delegate_->didDispatchCommandCount(), 0); + EXPECT_EQ(replacement->shouldRenderTransactionsCount(), 1); + EXPECT_EQ(replacement->didDispatchCommandCount(), 1); + EXPECT_EQ(unrelatedCalls, 1); +} + +TEST_F( + SchedulerDelegateInvalidationTest, + ReassigningSameDelegateDoesNotReviveOldWork) { + setUp(); + scheduler_->uiManagerDidDispatchCommand( + rootShadowNode_, "old", folly::dynamic::array()); + scheduler_->setOwnedDelegate(delegate_); + scheduler_->uiManagerDidDispatchCommand( + rootShadowNode_, "new", folly::dynamic::array()); + + runOneEventLoopTick(); + + EXPECT_EQ(delegate_->didDispatchCommandCount(), 1); +} + +TEST_F( + SchedulerDelegateInvalidationTest, + CallbackRetirementCancelsLaterWorkInSameFlush) { + setUp(); + delegate_->onRenderTransactions = [this] { + scheduler_->setOwnedDelegate(nullptr); + }; + scheduler_->uiManagerDidFinishTransaction(coordinator_, false); + scheduler_->uiManagerDidDispatchCommand( + rootShadowNode_, "scrollTo", folly::dynamic::array()); + int unrelatedCalls = 0; + runtimeScheduler_->scheduleRenderingUpdate(22, [&] { ++unrelatedCalls; }); + + runOneEventLoopTick(); + + EXPECT_EQ(delegate_->shouldRenderTransactionsCount(), 1); + EXPECT_EQ(delegate_->didDispatchCommandCount(), 0); + EXPECT_EQ(unrelatedCalls, 1); +} + +TEST_F( + SchedulerDelegateInvalidationTest, + SchedulerDestructionRetiresQueuedWork) { + setUp(); + // RuntimeScheduler's revision-manager pointer has an independent lifetime. + // Keep that owner alive while testing cancellation of the delegate work. + auto uiManager = scheduler_->getUIManager(); + std::weak_ptr weakDelegate = delegate_; + scheduler_->uiManagerDidFinishTransaction(coordinator_, false); + scheduler_->uiManagerDidDispatchCommand( + rootShadowNode_, "scrollTo", folly::dynamic::array()); + scheduler_->unregisterSurface(*surfaceHandler_); + scheduler_.reset(); + delegate_.reset(); + + EXPECT_TRUE(weakDelegate.expired()); + runOneEventLoopTick(); +} + +TEST_F( + SchedulerDelegateInvalidationTest, + LiveDelegateReceivesReactRevisionPromotion) { + setUp(); + queueReactCommit(); + EXPECT_EQ(delegate_->shouldMergeReactRevisionCount(), 0); + + runOneEventLoopTick(); + + EXPECT_EQ(delegate_->shouldMergeReactRevisionCount(), 1); +} + +TEST_F( + SchedulerDelegateInvalidationTest, + RetiredReactRevisionWorkDoesNotReachReplacement) { + setUp(); + queueReactCommit(); + auto replacement = std::make_shared(); + scheduler_->setOwnedDelegate(replacement); + + runOneEventLoopTick(); + + EXPECT_EQ(delegate_->shouldMergeReactRevisionCount(), 0); + EXPECT_EQ(replacement->shouldMergeReactRevisionCount(), 0); +} + } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/scheduler/tests/SchedulerDelegateRegistrationTest.cpp b/packages/react-native/ReactCommon/react/renderer/scheduler/tests/SchedulerDelegateRegistrationTest.cpp new file mode 100644 index 000000000000..cb93c174d27a --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/scheduler/tests/SchedulerDelegateRegistrationTest.cpp @@ -0,0 +1,195 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace facebook::react { +namespace { + +class TestDelegate final : public SchedulerDelegate { + public: + std::function onCall; + std::function onDestroy; + + ~TestDelegate() noexcept override { + if (onDestroy) { + onDestroy(); + } + } + + void schedulerDidFinishTransaction( + const std::shared_ptr&) override {} + void schedulerShouldRenderTransactions( + const std::shared_ptr&) override {} + void schedulerShouldMergeReactRevision(SurfaceId) override {} + void schedulerDidRequestPreliminaryViewAllocation( + const ShadowNode&) override {} + void schedulerDidDispatchCommand( + const ShadowView&, + const std::string&, + const folly::dynamic&) override {} + void schedulerDidSendAccessibilityEvent(const ShadowView&, const std::string&) + override {} + void schedulerDidSetIsJSResponder(const ShadowView&, bool, bool) override {} + void schedulerShouldSynchronouslyUpdateViewOnUIThread( + Tag, + const folly::dynamic&) override {} + void schedulerDidUpdateShadowTree( + const std::unordered_map&) override {} + void schedulerDidCaptureViewSnapshot(Tag, SurfaceId) override {} + void schedulerDidSetViewSnapshot(Tag, Tag, SurfaceId) override {} + void schedulerDidClearPendingSnapshots() override { + if (onCall) { + onCall(); + } + } +}; + +TEST(SchedulerDelegateRegistrationTest, EmptyRegistrationsHaveNoLease) { + SchedulerDelegateRegistration borrowed(nullptr); + SchedulerDelegateRegistration owned(std::shared_ptr{}); + EXPECT_FALSE(borrowed.acquire()); + EXPECT_FALSE(owned.acquire()); + borrowed.retire(); + owned.retire(); + EXPECT_FALSE(borrowed.acquire()); + EXPECT_FALSE(owned.acquire()); +} + +TEST( + SchedulerDelegateRegistrationTest, + RetiredGenerationCannotAcquireReplacement) { + auto first = std::make_shared(); + auto second = std::make_shared(); + SchedulerDelegateRegistration previous(first); + SchedulerDelegateRegistration replacement(second); + SchedulerDelegateRegistration independent(first); + + previous.retire(); + + EXPECT_FALSE(previous.acquire()); + EXPECT_EQ(replacement.acquire().get(), second.get()); + EXPECT_EQ(independent.acquire().get(), first.get()); +} + +TEST(SchedulerDelegateRegistrationTest, RetiringBorrowedDelegateDoesNotOwnIt) { + bool destroyed = false; + auto delegate = std::make_unique(); + delegate->onDestroy = [&] { destroyed = true; }; + SchedulerDelegateRegistration registration(delegate.get()); + { + auto lease = registration.acquire(); + EXPECT_EQ(lease.get(), delegate.get()); + registration.retire(); + EXPECT_FALSE(registration.acquire()); + EXPECT_FALSE(destroyed); + } + EXPECT_FALSE(destroyed); + delegate.reset(); + EXPECT_TRUE(destroyed); +} + +TEST( + SchedulerDelegateRegistrationTest, + AcquiredOwnedLeaseSurvivesConcurrentRetirement) { + std::atomic destroyed{false}; + std::atomic calls{0}; + std::binary_semaphore acquired{0}; + std::binary_semaphore invoke{0}; + auto delegate = std::make_shared(); + delegate->onDestroy = [&] { destroyed = true; }; + delegate->onCall = [&] { ++calls; }; + SchedulerDelegateRegistration registration(delegate); + + std::thread worker([&] { + auto lease = registration.acquire(); + acquired.release(); + invoke.acquire(); + EXPECT_TRUE(lease); + if (lease) { + lease->schedulerDidClearPendingSnapshots(); + } + }); + + acquired.acquire(); + registration.retire(); + delegate.reset(); + EXPECT_FALSE(registration.acquire()); + EXPECT_FALSE(destroyed.load()); + invoke.release(); + worker.join(); + + EXPECT_EQ(calls.load(), 1); + EXPECT_TRUE(destroyed.load()); +} + +TEST( + SchedulerDelegateRegistrationTest, + DelegateCallbackCanRetireItsRegistration) { + bool destroyed = false; + auto delegate = std::make_shared(); + delegate->onDestroy = [&] { destroyed = true; }; + SchedulerDelegateRegistration registration(delegate); + delegate->onCall = [&] { + registration.retire(); + EXPECT_FALSE(registration.acquire()); + EXPECT_FALSE(destroyed); + }; + delegate.reset(); + + { + auto lease = registration.acquire(); + ASSERT_TRUE(lease); + lease->schedulerDidClearPendingSnapshots(); + EXPECT_FALSE(destroyed); + } + EXPECT_TRUE(destroyed); +} + +TEST( + SchedulerDelegateRegistrationTest, + DelegateDestructorCanReenterRetirement) { + bool destroyed = false; + auto delegate = std::make_shared(); + SchedulerDelegateRegistration registration(delegate); + delegate->onDestroy = [&] { + EXPECT_FALSE(registration.acquire()); + registration.retire(); + destroyed = true; + }; + delegate.reset(); + + registration.retire(); + + EXPECT_TRUE(destroyed); +} + +TEST( + SchedulerDelegateRegistrationTest, + QueuedRegistrationDoesNotRetainRetiredDelegate) { + auto delegate = std::make_shared(); + std::weak_ptr weakDelegate = delegate; + auto registration = std::make_shared(delegate); + auto queued = [registration] { EXPECT_FALSE(registration->acquire()); }; + delegate.reset(); + registration->retire(); + registration.reset(); + + EXPECT_TRUE(weakDelegate.expired()); + queued(); +} + +} // namespace +} // namespace facebook::react diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api index 52f451b09994..f74fa1362b22 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api @@ -4601,6 +4601,7 @@ class facebook::react::SafeAreaViewState { class facebook::react::Scheduler : public facebook::react::UIManagerDelegate { public Scheduler(const facebook::react::SchedulerToolbox& schedulerToolbox, facebook::react::UIManagerAnimationDelegate* animationDelegate, facebook::react::SchedulerDelegate* delegate); + public Scheduler(const facebook::react::SchedulerToolbox& schedulerToolbox, facebook::react::UIManagerAnimationDelegate* animationDelegate, std::shared_ptr delegate); public facebook::react::SchedulerDelegate* getDelegate() const; public std::shared_ptr getContextContainer() const; public std::shared_ptr getUIManager() const; @@ -4625,6 +4626,7 @@ class facebook::react::Scheduler : public facebook::react::UIManagerDelegate { public void removeEventListener(const std::shared_ptr& listener); public void reportMount(facebook::react::SurfaceId surfaceId) const; public void setDelegate(facebook::react::SchedulerDelegate* delegate); + public void setOwnedDelegate(std::shared_ptr delegate); public void unregisterSurface(const facebook::react::SurfaceHandler& surfaceHandler) const noexcept; public ~Scheduler() override; } @@ -4645,6 +4647,19 @@ class facebook::react::SchedulerDelegate { public virtual ~SchedulerDelegate() noexcept = default; } +class facebook::react::SchedulerDelegateRegistration { + public SchedulerDelegateRegistration(facebook::react::SchedulerDelegate* delegate); + public SchedulerDelegateRegistration(std::shared_ptr delegate); + public facebook::react::SchedulerDelegateRegistration::Lease acquire() const; + public void retire(); +} + +class facebook::react::SchedulerDelegateRegistration::Lease { + public facebook::react::SchedulerDelegate* get() const; + public facebook::react::SchedulerDelegate* operator->() const; + public operator bool() const; +} + class facebook::react::ScopedShadowTreeRevisionLock { public ScopedShadowTreeRevisionLock(const facebook::react::ScopedShadowTreeRevisionLock&) = delete; public ScopedShadowTreeRevisionLock(facebook::react::ScopedShadowTreeRevisionLock&&) = delete; diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api index 61a157f45c78..15c5fa67c477 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api @@ -4417,6 +4417,7 @@ class facebook::react::SafeAreaViewState { class facebook::react::Scheduler : public facebook::react::UIManagerDelegate { public Scheduler(const facebook::react::SchedulerToolbox& schedulerToolbox, facebook::react::UIManagerAnimationDelegate* animationDelegate, facebook::react::SchedulerDelegate* delegate); + public Scheduler(const facebook::react::SchedulerToolbox& schedulerToolbox, facebook::react::UIManagerAnimationDelegate* animationDelegate, std::shared_ptr delegate); public facebook::react::SchedulerDelegate* getDelegate() const; public std::shared_ptr getContextContainer() const; public std::shared_ptr getUIManager() const; @@ -4441,6 +4442,7 @@ class facebook::react::Scheduler : public facebook::react::UIManagerDelegate { public void removeEventListener(const std::shared_ptr& listener); public void reportMount(facebook::react::SurfaceId surfaceId) const; public void setDelegate(facebook::react::SchedulerDelegate* delegate); + public void setOwnedDelegate(std::shared_ptr delegate); public void unregisterSurface(const facebook::react::SurfaceHandler& surfaceHandler) const noexcept; public ~Scheduler() override; } @@ -4461,6 +4463,19 @@ class facebook::react::SchedulerDelegate { public virtual ~SchedulerDelegate() noexcept = default; } +class facebook::react::SchedulerDelegateRegistration { + public SchedulerDelegateRegistration(facebook::react::SchedulerDelegate* delegate); + public SchedulerDelegateRegistration(std::shared_ptr delegate); + public facebook::react::SchedulerDelegateRegistration::Lease acquire() const; + public void retire(); +} + +class facebook::react::SchedulerDelegateRegistration::Lease { + public facebook::react::SchedulerDelegate* get() const; + public facebook::react::SchedulerDelegate* operator->() const; + public operator bool() const; +} + class facebook::react::ScopedShadowTreeRevisionLock { public ScopedShadowTreeRevisionLock(const facebook::react::ScopedShadowTreeRevisionLock&) = delete; public ScopedShadowTreeRevisionLock(facebook::react::ScopedShadowTreeRevisionLock&&) = delete; diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api index 12e08f58270a..273c2ad9e966 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api @@ -4598,6 +4598,7 @@ class facebook::react::SafeAreaViewState { class facebook::react::Scheduler : public facebook::react::UIManagerDelegate { public Scheduler(const facebook::react::SchedulerToolbox& schedulerToolbox, facebook::react::UIManagerAnimationDelegate* animationDelegate, facebook::react::SchedulerDelegate* delegate); + public Scheduler(const facebook::react::SchedulerToolbox& schedulerToolbox, facebook::react::UIManagerAnimationDelegate* animationDelegate, std::shared_ptr delegate); public facebook::react::SchedulerDelegate* getDelegate() const; public std::shared_ptr getContextContainer() const; public std::shared_ptr getUIManager() const; @@ -4622,6 +4623,7 @@ class facebook::react::Scheduler : public facebook::react::UIManagerDelegate { public void removeEventListener(const std::shared_ptr& listener); public void reportMount(facebook::react::SurfaceId surfaceId) const; public void setDelegate(facebook::react::SchedulerDelegate* delegate); + public void setOwnedDelegate(std::shared_ptr delegate); public void unregisterSurface(const facebook::react::SurfaceHandler& surfaceHandler) const noexcept; public ~Scheduler() override; } @@ -4642,6 +4644,19 @@ class facebook::react::SchedulerDelegate { public virtual ~SchedulerDelegate() noexcept = default; } +class facebook::react::SchedulerDelegateRegistration { + public SchedulerDelegateRegistration(facebook::react::SchedulerDelegate* delegate); + public SchedulerDelegateRegistration(std::shared_ptr delegate); + public facebook::react::SchedulerDelegateRegistration::Lease acquire() const; + public void retire(); +} + +class facebook::react::SchedulerDelegateRegistration::Lease { + public facebook::react::SchedulerDelegate* get() const; + public facebook::react::SchedulerDelegate* operator->() const; + public operator bool() const; +} + class facebook::react::ScopedShadowTreeRevisionLock { public ScopedShadowTreeRevisionLock(const facebook::react::ScopedShadowTreeRevisionLock&) = delete; public ScopedShadowTreeRevisionLock(facebook::react::ScopedShadowTreeRevisionLock&&) = delete; diff --git a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api index a8f040724fa6..c4254fd3b5fa 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api @@ -6749,6 +6749,7 @@ class facebook::react::SafeAreaViewState { class facebook::react::Scheduler : public facebook::react::UIManagerDelegate { public Scheduler(const facebook::react::SchedulerToolbox& schedulerToolbox, facebook::react::UIManagerAnimationDelegate* animationDelegate, facebook::react::SchedulerDelegate* delegate); + public Scheduler(const facebook::react::SchedulerToolbox& schedulerToolbox, facebook::react::UIManagerAnimationDelegate* animationDelegate, std::shared_ptr delegate); public facebook::react::SchedulerDelegate* getDelegate() const; public std::shared_ptr getContextContainer() const; public std::shared_ptr getUIManager() const; @@ -6773,6 +6774,7 @@ class facebook::react::Scheduler : public facebook::react::UIManagerDelegate { public void removeEventListener(const std::shared_ptr& listener); public void reportMount(facebook::react::SurfaceId surfaceId) const; public void setDelegate(facebook::react::SchedulerDelegate* delegate); + public void setOwnedDelegate(std::shared_ptr delegate); public void unregisterSurface(const facebook::react::SurfaceHandler& surfaceHandler) const noexcept; public ~Scheduler() override; } @@ -6793,6 +6795,19 @@ class facebook::react::SchedulerDelegate { public virtual ~SchedulerDelegate() noexcept = default; } +class facebook::react::SchedulerDelegateRegistration { + public SchedulerDelegateRegistration(facebook::react::SchedulerDelegate* delegate); + public SchedulerDelegateRegistration(std::shared_ptr delegate); + public facebook::react::SchedulerDelegateRegistration::Lease acquire() const; + public void retire(); +} + +class facebook::react::SchedulerDelegateRegistration::Lease { + public facebook::react::SchedulerDelegate* get() const; + public facebook::react::SchedulerDelegate* operator->() const; + public operator bool() const; +} + class facebook::react::ScopedShadowTreeRevisionLock { public ScopedShadowTreeRevisionLock(const facebook::react::ScopedShadowTreeRevisionLock&) = delete; public ScopedShadowTreeRevisionLock(facebook::react::ScopedShadowTreeRevisionLock&&) = delete; diff --git a/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api index 3c0a535fb10e..0c1670c2df69 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api @@ -6597,6 +6597,7 @@ class facebook::react::SafeAreaViewState { class facebook::react::Scheduler : public facebook::react::UIManagerDelegate { public Scheduler(const facebook::react::SchedulerToolbox& schedulerToolbox, facebook::react::UIManagerAnimationDelegate* animationDelegate, facebook::react::SchedulerDelegate* delegate); + public Scheduler(const facebook::react::SchedulerToolbox& schedulerToolbox, facebook::react::UIManagerAnimationDelegate* animationDelegate, std::shared_ptr delegate); public facebook::react::SchedulerDelegate* getDelegate() const; public std::shared_ptr getContextContainer() const; public std::shared_ptr getUIManager() const; @@ -6621,6 +6622,7 @@ class facebook::react::Scheduler : public facebook::react::UIManagerDelegate { public void removeEventListener(const std::shared_ptr& listener); public void reportMount(facebook::react::SurfaceId surfaceId) const; public void setDelegate(facebook::react::SchedulerDelegate* delegate); + public void setOwnedDelegate(std::shared_ptr delegate); public void unregisterSurface(const facebook::react::SurfaceHandler& surfaceHandler) const noexcept; public ~Scheduler() override; } @@ -6641,6 +6643,19 @@ class facebook::react::SchedulerDelegate { public virtual ~SchedulerDelegate() noexcept = default; } +class facebook::react::SchedulerDelegateRegistration { + public SchedulerDelegateRegistration(facebook::react::SchedulerDelegate* delegate); + public SchedulerDelegateRegistration(std::shared_ptr delegate); + public facebook::react::SchedulerDelegateRegistration::Lease acquire() const; + public void retire(); +} + +class facebook::react::SchedulerDelegateRegistration::Lease { + public facebook::react::SchedulerDelegate* get() const; + public facebook::react::SchedulerDelegate* operator->() const; + public operator bool() const; +} + class facebook::react::ScopedShadowTreeRevisionLock { public ScopedShadowTreeRevisionLock(const facebook::react::ScopedShadowTreeRevisionLock&) = delete; public ScopedShadowTreeRevisionLock(facebook::react::ScopedShadowTreeRevisionLock&&) = delete; diff --git a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api index 958586fc805e..1fecea89fdec 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api @@ -6746,6 +6746,7 @@ class facebook::react::SafeAreaViewState { class facebook::react::Scheduler : public facebook::react::UIManagerDelegate { public Scheduler(const facebook::react::SchedulerToolbox& schedulerToolbox, facebook::react::UIManagerAnimationDelegate* animationDelegate, facebook::react::SchedulerDelegate* delegate); + public Scheduler(const facebook::react::SchedulerToolbox& schedulerToolbox, facebook::react::UIManagerAnimationDelegate* animationDelegate, std::shared_ptr delegate); public facebook::react::SchedulerDelegate* getDelegate() const; public std::shared_ptr getContextContainer() const; public std::shared_ptr getUIManager() const; @@ -6770,6 +6771,7 @@ class facebook::react::Scheduler : public facebook::react::UIManagerDelegate { public void removeEventListener(const std::shared_ptr& listener); public void reportMount(facebook::react::SurfaceId surfaceId) const; public void setDelegate(facebook::react::SchedulerDelegate* delegate); + public void setOwnedDelegate(std::shared_ptr delegate); public void unregisterSurface(const facebook::react::SurfaceHandler& surfaceHandler) const noexcept; public ~Scheduler() override; } @@ -6790,6 +6792,19 @@ class facebook::react::SchedulerDelegate { public virtual ~SchedulerDelegate() noexcept = default; } +class facebook::react::SchedulerDelegateRegistration { + public SchedulerDelegateRegistration(facebook::react::SchedulerDelegate* delegate); + public SchedulerDelegateRegistration(std::shared_ptr delegate); + public facebook::react::SchedulerDelegateRegistration::Lease acquire() const; + public void retire(); +} + +class facebook::react::SchedulerDelegateRegistration::Lease { + public facebook::react::SchedulerDelegate* get() const; + public facebook::react::SchedulerDelegate* operator->() const; + public operator bool() const; +} + class facebook::react::ScopedShadowTreeRevisionLock { public ScopedShadowTreeRevisionLock(const facebook::react::ScopedShadowTreeRevisionLock&) = delete; public ScopedShadowTreeRevisionLock(facebook::react::ScopedShadowTreeRevisionLock&&) = delete; diff --git a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api index 8b46097a89f9..a14f0ece3b1a 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api @@ -3144,6 +3144,7 @@ class facebook::react::SafeAreaViewState { class facebook::react::Scheduler : public facebook::react::UIManagerDelegate { public Scheduler(const facebook::react::SchedulerToolbox& schedulerToolbox, facebook::react::UIManagerAnimationDelegate* animationDelegate, facebook::react::SchedulerDelegate* delegate); + public Scheduler(const facebook::react::SchedulerToolbox& schedulerToolbox, facebook::react::UIManagerAnimationDelegate* animationDelegate, std::shared_ptr delegate); public facebook::react::SchedulerDelegate* getDelegate() const; public std::shared_ptr getContextContainer() const; public std::shared_ptr getUIManager() const; @@ -3168,6 +3169,7 @@ class facebook::react::Scheduler : public facebook::react::UIManagerDelegate { public void removeEventListener(const std::shared_ptr& listener); public void reportMount(facebook::react::SurfaceId surfaceId) const; public void setDelegate(facebook::react::SchedulerDelegate* delegate); + public void setOwnedDelegate(std::shared_ptr delegate); public void unregisterSurface(const facebook::react::SurfaceHandler& surfaceHandler) const noexcept; public ~Scheduler() override; } @@ -3188,6 +3190,19 @@ class facebook::react::SchedulerDelegate { public virtual ~SchedulerDelegate() noexcept = default; } +class facebook::react::SchedulerDelegateRegistration { + public SchedulerDelegateRegistration(facebook::react::SchedulerDelegate* delegate); + public SchedulerDelegateRegistration(std::shared_ptr delegate); + public facebook::react::SchedulerDelegateRegistration::Lease acquire() const; + public void retire(); +} + +class facebook::react::SchedulerDelegateRegistration::Lease { + public facebook::react::SchedulerDelegate* get() const; + public facebook::react::SchedulerDelegate* operator->() const; + public operator bool() const; +} + class facebook::react::ScopedShadowTreeRevisionLock { public ScopedShadowTreeRevisionLock(const facebook::react::ScopedShadowTreeRevisionLock&) = delete; public ScopedShadowTreeRevisionLock(facebook::react::ScopedShadowTreeRevisionLock&&) = delete; diff --git a/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api index 692b5064e583..bf8703b1016c 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api @@ -3000,6 +3000,7 @@ class facebook::react::SafeAreaViewState { class facebook::react::Scheduler : public facebook::react::UIManagerDelegate { public Scheduler(const facebook::react::SchedulerToolbox& schedulerToolbox, facebook::react::UIManagerAnimationDelegate* animationDelegate, facebook::react::SchedulerDelegate* delegate); + public Scheduler(const facebook::react::SchedulerToolbox& schedulerToolbox, facebook::react::UIManagerAnimationDelegate* animationDelegate, std::shared_ptr delegate); public facebook::react::SchedulerDelegate* getDelegate() const; public std::shared_ptr getContextContainer() const; public std::shared_ptr getUIManager() const; @@ -3024,6 +3025,7 @@ class facebook::react::Scheduler : public facebook::react::UIManagerDelegate { public void removeEventListener(const std::shared_ptr& listener); public void reportMount(facebook::react::SurfaceId surfaceId) const; public void setDelegate(facebook::react::SchedulerDelegate* delegate); + public void setOwnedDelegate(std::shared_ptr delegate); public void unregisterSurface(const facebook::react::SurfaceHandler& surfaceHandler) const noexcept; public ~Scheduler() override; } @@ -3044,6 +3046,19 @@ class facebook::react::SchedulerDelegate { public virtual ~SchedulerDelegate() noexcept = default; } +class facebook::react::SchedulerDelegateRegistration { + public SchedulerDelegateRegistration(facebook::react::SchedulerDelegate* delegate); + public SchedulerDelegateRegistration(std::shared_ptr delegate); + public facebook::react::SchedulerDelegateRegistration::Lease acquire() const; + public void retire(); +} + +class facebook::react::SchedulerDelegateRegistration::Lease { + public facebook::react::SchedulerDelegate* get() const; + public facebook::react::SchedulerDelegate* operator->() const; + public operator bool() const; +} + class facebook::react::ScopedShadowTreeRevisionLock { public ScopedShadowTreeRevisionLock(const facebook::react::ScopedShadowTreeRevisionLock&) = delete; public ScopedShadowTreeRevisionLock(facebook::react::ScopedShadowTreeRevisionLock&&) = delete; diff --git a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api index ff0ef95bbfdb..dc31c3d6ff4d 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api @@ -3141,6 +3141,7 @@ class facebook::react::SafeAreaViewState { class facebook::react::Scheduler : public facebook::react::UIManagerDelegate { public Scheduler(const facebook::react::SchedulerToolbox& schedulerToolbox, facebook::react::UIManagerAnimationDelegate* animationDelegate, facebook::react::SchedulerDelegate* delegate); + public Scheduler(const facebook::react::SchedulerToolbox& schedulerToolbox, facebook::react::UIManagerAnimationDelegate* animationDelegate, std::shared_ptr delegate); public facebook::react::SchedulerDelegate* getDelegate() const; public std::shared_ptr getContextContainer() const; public std::shared_ptr getUIManager() const; @@ -3165,6 +3166,7 @@ class facebook::react::Scheduler : public facebook::react::UIManagerDelegate { public void removeEventListener(const std::shared_ptr& listener); public void reportMount(facebook::react::SurfaceId surfaceId) const; public void setDelegate(facebook::react::SchedulerDelegate* delegate); + public void setOwnedDelegate(std::shared_ptr delegate); public void unregisterSurface(const facebook::react::SurfaceHandler& surfaceHandler) const noexcept; public ~Scheduler() override; } @@ -3185,6 +3187,19 @@ class facebook::react::SchedulerDelegate { public virtual ~SchedulerDelegate() noexcept = default; } +class facebook::react::SchedulerDelegateRegistration { + public SchedulerDelegateRegistration(facebook::react::SchedulerDelegate* delegate); + public SchedulerDelegateRegistration(std::shared_ptr delegate); + public facebook::react::SchedulerDelegateRegistration::Lease acquire() const; + public void retire(); +} + +class facebook::react::SchedulerDelegateRegistration::Lease { + public facebook::react::SchedulerDelegate* get() const; + public facebook::react::SchedulerDelegate* operator->() const; + public operator bool() const; +} + class facebook::react::ScopedShadowTreeRevisionLock { public ScopedShadowTreeRevisionLock(const facebook::react::ScopedShadowTreeRevisionLock&) = delete; public ScopedShadowTreeRevisionLock(facebook::react::ScopedShadowTreeRevisionLock&&) = delete;