From b4527989e8570d9413461007fd44ae346334106a Mon Sep 17 00:00:00 2001 From: Zeya Peng Date: Mon, 20 Apr 2026 03:52:23 -0700 Subject: [PATCH] Clear AnimatedPropsRegistry on surface stop (#56485) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/56485 ## Changelog: [Internal] [Fixed] - Clear AnimatedPropsRegistry on surface stop When `useSharedAnimatedBackend` is enabled, the `AnimatedPropsRegistry` accumulates `SurfaceContext` entries (containing `shared_ptr` and `PropsSnapshot` data) for each surface that has animated views. These entries are never cleaned up when a surface is destroyed via `UIManager::stopSurface()`, because that method only calls the legacy `stopSurfaceForAnimationDelegate()` — the shared backend's registry is untouched. We also see increased `RetryableMountingLayerException` errors in production which stack trace shows there are mount items committing to surface that no longer exists. This change: 1. Adds `animationBackend_->clearRegistry(surfaceId)` to `UIManager::stopSurface()` 2. Changes `AnimatedPropsRegistry::clear()` to fully erase the `surfaceContexts_` map entry instead of just clearing its contents Reviewed By: christophpurrer Differential Revision: D101354994 --- .../animationbackend/AnimatedPropsRegistry.cpp | 13 ++++++++++--- .../animationbackend/AnimatedPropsRegistry.h | 1 + .../renderer/animationbackend/AnimationBackend.cpp | 4 ++++ .../renderer/animationbackend/AnimationBackend.h | 1 + .../react/renderer/uimanager/UIManager.cpp | 6 +++++- .../renderer/uimanager/UIManagerAnimationBackend.h | 1 + .../cxx-api/api-snapshots/ReactAndroidDebugCxx.api | 5 ++++- .../api-snapshots/ReactAndroidReleaseCxx.api | 5 ++++- .../cxx-api/api-snapshots/ReactAppleDebugCxx.api | 5 ++++- .../cxx-api/api-snapshots/ReactAppleReleaseCxx.api | 5 ++++- .../cxx-api/api-snapshots/ReactCommonDebugCxx.api | 5 ++++- .../cxx-api/api-snapshots/ReactCommonReleaseCxx.api | 5 ++++- 12 files changed, 46 insertions(+), 10 deletions(-) diff --git a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropsRegistry.cpp b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropsRegistry.cpp index 041eac315d5e..1598269ea785 100644 --- a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropsRegistry.cpp +++ b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropsRegistry.cpp @@ -93,10 +93,17 @@ AnimatedPropsRegistry::getMap(SurfaceId surfaceId) { void AnimatedPropsRegistry::clear(SurfaceId surfaceId) { auto lock = std::lock_guard(mutex_); + if (auto it = surfaceContexts_.find(surfaceId); + it != surfaceContexts_.end()) { + auto& surfaceContext = it->second; + surfaceContext.families.clear(); + surfaceContext.map.clear(); + } +} - auto& surfaceContext = surfaceContexts_[surfaceId]; - surfaceContext.families.clear(); - surfaceContext.map.clear(); +void AnimatedPropsRegistry::clearOnSurfaceStop(SurfaceId surfaceId) { + auto lock = std::lock_guard(mutex_); + surfaceContexts_.erase(surfaceId); } } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropsRegistry.h b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropsRegistry.h index f577519762b8..fe4b9b62a0d6 100644 --- a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropsRegistry.h +++ b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropsRegistry.h @@ -39,6 +39,7 @@ class AnimatedPropsRegistry { public: void update(const std::unordered_map &surfaceUpdates); void clear(SurfaceId surfaceId); + void clearOnSurfaceStop(SurfaceId surfaceId); std::pair> &, SnapshotMap &> getMap(SurfaceId surfaceId); private: diff --git a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.cpp b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.cpp index 789bb1fc4f7f..62a8676088ca 100644 --- a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.cpp +++ b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.cpp @@ -257,6 +257,10 @@ void AnimationBackend::clearRegistry(SurfaceId surfaceId) { animatedPropsRegistry_->clear(surfaceId); } +void AnimationBackend::clearRegistryOnSurfaceStop(SurfaceId surfaceId) { + animatedPropsRegistry_->clearOnSurfaceStop(surfaceId); +} + void AnimationBackend::registerJSInvoker( std::shared_ptr jsInvoker) { if (!jsInvoker_) { diff --git a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.h b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.h index c96bbf932058..c01ff23a8e71 100644 --- a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.h +++ b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.h @@ -55,6 +55,7 @@ class AnimationBackend : public UIManagerAnimationBackend { void synchronouslyUpdateProps(const std::unordered_map &updates); void requestAsyncFlushForSurfaces(const std::set &surfaces); void clearRegistry(SurfaceId surfaceId) override; + void clearRegistryOnSurfaceStop(SurfaceId surfaceId) override; void registerJSInvoker(std::shared_ptr jsInvoker) override; void onAnimationFrame(AnimationTimestamp timestamp) override; diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp index 72a7e3f8d1ab..3e48dabc6fff 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp @@ -272,9 +272,13 @@ void UIManager::setSurfaceProps( ShadowTree::Unique UIManager::stopSurface(SurfaceId surfaceId) const { TraceSection s("UIManager::stopSurface"); - // Stop any ongoing animations. + // Stop any ongoing layout animations. stopSurfaceForAnimationDelegate(surfaceId); + if (ReactNativeFeatureFlags::useSharedAnimatedBackend()) { + animationBackend_->clearRegistryOnSurfaceStop(surfaceId); + } + // Waiting for all concurrent commits to be finished and unregistering the // `ShadowTree`. auto shadowTree = getShadowTreeRegistry().remove(surfaceId); diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerAnimationBackend.h b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerAnimationBackend.h index 46ecea5e4f07..27c39b18e5e0 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerAnimationBackend.h +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerAnimationBackend.h @@ -30,6 +30,7 @@ class UIManagerAnimationBackend { virtual CallbackId start(const Callback &callback) = 0; virtual void stop(CallbackId callbackId) = 0; virtual void clearRegistry(SurfaceId surfaceId) = 0; + virtual void clearRegistryOnSurfaceStop(SurfaceId surfaceId) = 0; virtual void trigger() = 0; virtual void pushAnimationMutations(const Callback &callback) = 0; virtual void registerJSInvoker(std::shared_ptr jsInvoker) = 0; diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api index 725830820727..c9b2e3e21b3f 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api @@ -1536,6 +1536,7 @@ class facebook::react::AnimatedNode { class facebook::react::AnimatedPropsRegistry { public std::pair>&, facebook::react::SnapshotMap&> getMap(facebook::react::SurfaceId surfaceId); public void clear(facebook::react::SurfaceId surfaceId); + public void clearOnSurfaceStop(facebook::react::SurfaceId surfaceId); public void update(const std::unordered_map& surfaceUpdates); } @@ -1545,6 +1546,7 @@ class facebook::react::AnimationBackend : public facebook::react::UIManagerAnima public using ResumeCallback = std::function; public virtual facebook::react::CallbackId start(const facebook::react::UIManagerAnimationBackend::Callback& callback) override; public virtual void clearRegistry(facebook::react::SurfaceId surfaceId) override; + public virtual void clearRegistryOnSurfaceStop(facebook::react::SurfaceId surfaceId) override; public virtual void onAnimationFrame(facebook::react::AnimationTimestamp timestamp) override; public virtual void pushAnimationMutations(const facebook::react::UIManagerAnimationBackend::Callback& callback) override; public virtual void registerJSInvoker(std::shared_ptr jsInvoker) override; @@ -5219,6 +5221,7 @@ class facebook::react::UIManagerAnimationBackend { public using Callback = std::function; public virtual facebook::react::CallbackId start(const facebook::react::UIManagerAnimationBackend::Callback& callback) = 0; public virtual void clearRegistry(facebook::react::SurfaceId surfaceId) = 0; + public virtual void clearRegistryOnSurfaceStop(facebook::react::SurfaceId surfaceId) = 0; public virtual void onAnimationFrame(facebook::react::AnimationTimestamp timestamp) = 0; public virtual void pushAnimationMutations(const facebook::react::UIManagerAnimationBackend::Callback& callback) = 0; public virtual void registerJSInvoker(std::shared_ptr jsInvoker) = 0; @@ -13503,4 +13506,4 @@ struct std::hash> { } -void YGJNIVanilla::registerNatives(JNIEnv* env); \ No newline at end of file +void YGJNIVanilla::registerNatives(JNIEnv* env); diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api index 3d481c044fc5..5ee714e71636 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api @@ -1535,6 +1535,7 @@ class facebook::react::AnimatedNode { class facebook::react::AnimatedPropsRegistry { public std::pair>&, facebook::react::SnapshotMap&> getMap(facebook::react::SurfaceId surfaceId); public void clear(facebook::react::SurfaceId surfaceId); + public void clearOnSurfaceStop(facebook::react::SurfaceId surfaceId); public void update(const std::unordered_map& surfaceUpdates); } @@ -1544,6 +1545,7 @@ class facebook::react::AnimationBackend : public facebook::react::UIManagerAnima public using ResumeCallback = std::function; public virtual facebook::react::CallbackId start(const facebook::react::UIManagerAnimationBackend::Callback& callback) override; public virtual void clearRegistry(facebook::react::SurfaceId surfaceId) override; + public virtual void clearRegistryOnSurfaceStop(facebook::react::SurfaceId surfaceId) override; public virtual void onAnimationFrame(facebook::react::AnimationTimestamp timestamp) override; public virtual void pushAnimationMutations(const facebook::react::UIManagerAnimationBackend::Callback& callback) override; public virtual void registerJSInvoker(std::shared_ptr jsInvoker) override; @@ -5210,6 +5212,7 @@ class facebook::react::UIManagerAnimationBackend { public using Callback = std::function; public virtual facebook::react::CallbackId start(const facebook::react::UIManagerAnimationBackend::Callback& callback) = 0; public virtual void clearRegistry(facebook::react::SurfaceId surfaceId) = 0; + public virtual void clearRegistryOnSurfaceStop(facebook::react::SurfaceId surfaceId) = 0; public virtual void onAnimationFrame(facebook::react::AnimationTimestamp timestamp) = 0; public virtual void pushAnimationMutations(const facebook::react::UIManagerAnimationBackend::Callback& callback) = 0; public virtual void registerJSInvoker(std::shared_ptr jsInvoker) = 0; @@ -13359,4 +13362,4 @@ struct std::hash> { } -void YGJNIVanilla::registerNatives(JNIEnv* env); \ No newline at end of file +void YGJNIVanilla::registerNatives(JNIEnv* env); diff --git a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api index 314ad5098fe1..eaa34289ad64 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api @@ -4481,6 +4481,7 @@ class facebook::react::AnimatedNode { class facebook::react::AnimatedPropsRegistry { public std::pair>&, facebook::react::SnapshotMap&> getMap(facebook::react::SurfaceId surfaceId); public void clear(facebook::react::SurfaceId surfaceId); + public void clearOnSurfaceStop(facebook::react::SurfaceId surfaceId); public void update(const std::unordered_map& surfaceUpdates); } @@ -4490,6 +4491,7 @@ class facebook::react::AnimationBackend : public facebook::react::UIManagerAnima public using ResumeCallback = std::function; public virtual facebook::react::CallbackId start(const facebook::react::UIManagerAnimationBackend::Callback& callback) override; public virtual void clearRegistry(facebook::react::SurfaceId surfaceId) override; + public virtual void clearRegistryOnSurfaceStop(facebook::react::SurfaceId surfaceId) override; public virtual void onAnimationFrame(facebook::react::AnimationTimestamp timestamp) override; public virtual void pushAnimationMutations(const facebook::react::UIManagerAnimationBackend::Callback& callback) override; public virtual void registerJSInvoker(std::shared_ptr jsInvoker) override; @@ -7788,6 +7790,7 @@ class facebook::react::UIManagerAnimationBackend { public using Callback = std::function; public virtual facebook::react::CallbackId start(const facebook::react::UIManagerAnimationBackend::Callback& callback) = 0; public virtual void clearRegistry(facebook::react::SurfaceId surfaceId) = 0; + public virtual void clearRegistryOnSurfaceStop(facebook::react::SurfaceId surfaceId) = 0; public virtual void onAnimationFrame(facebook::react::AnimationTimestamp timestamp) = 0; public virtual void pushAnimationMutations(const facebook::react::UIManagerAnimationBackend::Callback& callback) = 0; public virtual void registerJSInvoker(std::shared_ptr jsInvoker) = 0; @@ -16099,4 +16102,4 @@ class FB::LazyVector { public using reference = T; public using size_type = std::int32_t; public using value_type = T; -} \ No newline at end of file +} diff --git a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api index 69fd72ccabf0..1e39b07f873b 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api @@ -4480,6 +4480,7 @@ class facebook::react::AnimatedNode { class facebook::react::AnimatedPropsRegistry { public std::pair>&, facebook::react::SnapshotMap&> getMap(facebook::react::SurfaceId surfaceId); public void clear(facebook::react::SurfaceId surfaceId); + public void clearOnSurfaceStop(facebook::react::SurfaceId surfaceId); public void update(const std::unordered_map& surfaceUpdates); } @@ -4489,6 +4490,7 @@ class facebook::react::AnimationBackend : public facebook::react::UIManagerAnima public using ResumeCallback = std::function; public virtual facebook::react::CallbackId start(const facebook::react::UIManagerAnimationBackend::Callback& callback) override; public virtual void clearRegistry(facebook::react::SurfaceId surfaceId) override; + public virtual void clearRegistryOnSurfaceStop(facebook::react::SurfaceId surfaceId) override; public virtual void onAnimationFrame(facebook::react::AnimationTimestamp timestamp) override; public virtual void pushAnimationMutations(const facebook::react::UIManagerAnimationBackend::Callback& callback) override; public virtual void registerJSInvoker(std::shared_ptr jsInvoker) override; @@ -7779,6 +7781,7 @@ class facebook::react::UIManagerAnimationBackend { public using Callback = std::function; public virtual facebook::react::CallbackId start(const facebook::react::UIManagerAnimationBackend::Callback& callback) = 0; public virtual void clearRegistry(facebook::react::SurfaceId surfaceId) = 0; + public virtual void clearRegistryOnSurfaceStop(facebook::react::SurfaceId surfaceId) = 0; public virtual void onAnimationFrame(facebook::react::AnimationTimestamp timestamp) = 0; public virtual void pushAnimationMutations(const facebook::react::UIManagerAnimationBackend::Callback& callback) = 0; public virtual void registerJSInvoker(std::shared_ptr jsInvoker) = 0; @@ -15965,4 +15968,4 @@ class FB::LazyVector { public using reference = T; public using size_type = std::int32_t; public using value_type = T; -} \ No newline at end of file +} diff --git a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api index db5b97440f2c..317b797d7b2c 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api @@ -864,6 +864,7 @@ class facebook::react::AnimatedNode { class facebook::react::AnimatedPropsRegistry { public std::pair>&, facebook::react::SnapshotMap&> getMap(facebook::react::SurfaceId surfaceId); public void clear(facebook::react::SurfaceId surfaceId); + public void clearOnSurfaceStop(facebook::react::SurfaceId surfaceId); public void update(const std::unordered_map& surfaceUpdates); } @@ -873,6 +874,7 @@ class facebook::react::AnimationBackend : public facebook::react::UIManagerAnima public using ResumeCallback = std::function; public virtual facebook::react::CallbackId start(const facebook::react::UIManagerAnimationBackend::Callback& callback) override; public virtual void clearRegistry(facebook::react::SurfaceId surfaceId) override; + public virtual void clearRegistryOnSurfaceStop(facebook::react::SurfaceId surfaceId) override; public virtual void onAnimationFrame(facebook::react::AnimationTimestamp timestamp) override; public virtual void pushAnimationMutations(const facebook::react::UIManagerAnimationBackend::Callback& callback) override; public virtual void registerJSInvoker(std::shared_ptr jsInvoker) override; @@ -3685,6 +3687,7 @@ class facebook::react::UIManagerAnimationBackend { public using Callback = std::function; public virtual facebook::react::CallbackId start(const facebook::react::UIManagerAnimationBackend::Callback& callback) = 0; public virtual void clearRegistry(facebook::react::SurfaceId surfaceId) = 0; + public virtual void clearRegistryOnSurfaceStop(facebook::react::SurfaceId surfaceId) = 0; public virtual void onAnimationFrame(facebook::react::AnimationTimestamp timestamp) = 0; public virtual void pushAnimationMutations(const facebook::react::UIManagerAnimationBackend::Callback& callback) = 0; public virtual void registerJSInvoker(std::shared_ptr jsInvoker) = 0; @@ -10453,4 +10456,4 @@ struct std::hash template struct std::hash> { public size_t operator()(const facebook::react::jsinspector_modern::UniqueMonostate&) const noexcept; -} \ No newline at end of file +} diff --git a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api index f35367f460b8..4765ca080da7 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api @@ -863,6 +863,7 @@ class facebook::react::AnimatedNode { class facebook::react::AnimatedPropsRegistry { public std::pair>&, facebook::react::SnapshotMap&> getMap(facebook::react::SurfaceId surfaceId); public void clear(facebook::react::SurfaceId surfaceId); + public void clearOnSurfaceStop(facebook::react::SurfaceId surfaceId); public void update(const std::unordered_map& surfaceUpdates); } @@ -872,6 +873,7 @@ class facebook::react::AnimationBackend : public facebook::react::UIManagerAnima public using ResumeCallback = std::function; public virtual facebook::react::CallbackId start(const facebook::react::UIManagerAnimationBackend::Callback& callback) override; public virtual void clearRegistry(facebook::react::SurfaceId surfaceId) override; + public virtual void clearRegistryOnSurfaceStop(facebook::react::SurfaceId surfaceId) override; public virtual void onAnimationFrame(facebook::react::AnimationTimestamp timestamp) override; public virtual void pushAnimationMutations(const facebook::react::UIManagerAnimationBackend::Callback& callback) override; public virtual void registerJSInvoker(std::shared_ptr jsInvoker) override; @@ -3676,6 +3678,7 @@ class facebook::react::UIManagerAnimationBackend { public using Callback = std::function; public virtual facebook::react::CallbackId start(const facebook::react::UIManagerAnimationBackend::Callback& callback) = 0; public virtual void clearRegistry(facebook::react::SurfaceId surfaceId) = 0; + public virtual void clearRegistryOnSurfaceStop(facebook::react::SurfaceId surfaceId) = 0; public virtual void onAnimationFrame(facebook::react::AnimationTimestamp timestamp) = 0; public virtual void pushAnimationMutations(const facebook::react::UIManagerAnimationBackend::Callback& callback) = 0; public virtual void registerJSInvoker(std::shared_ptr jsInvoker) = 0; @@ -10444,4 +10447,4 @@ struct std::hash template struct std::hash> { public size_t operator()(const facebook::react::jsinspector_modern::UniqueMonostate&) const noexcept; -} \ No newline at end of file +}