Skip to content

Commit f63eaec

Browse files
Bartlomiej Bloniarzfacebook-github-bot
authored andcommitted
Fix AnimatedPropsRegistry surface-stop resurrection race
Summary: AnimatedPropsRegistry::update() runs on the UI thread every animation frame and created a surface's entry via operator[]. clearOnSurfaceStop() (run on the JS thread when a surface stops) erases that entry, but an in-flight animation frame landing after the stop re-created it via operator[] -- and since the surface is gone, nothing ever cleans it up again. The resurrected entry leaks its PropsSnapshot and ShadowNodeFamily for the lifetime of the registry. A surface's entry is legitimately created by getMap(), which AnimationBackendCommitHook calls on every React commit. stopSurface drains in-flight commits before unregistering the ShadowTree (ShadowTreeRegistry::remove takes the registry's unique lock, which excludes the shared-locked commit visits), so getMap() can never run for a stopped surface. That leaves update()'s operator[] as the only thing that can resurrect one. Fix: update() now only refines surfaces that already exist (find instead of operator[]) and never creates an entry; getMap() remains the sole creator. A stopped surface can no longer be resurrected, and there is no extra bookkeeping that could grow over time. Changelog: [General][Fixed] - Fix a surface-stop race in the C++ Animated shared backend that could permanently leak per-surface animated state Differential Revision: D109156094
1 parent b364490 commit f63eaec

13 files changed

Lines changed: 56 additions & 3 deletions

packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropsRegistry.cpp

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,12 @@ void AnimatedPropsRegistry::update(
1515
const std::unordered_map<SurfaceId, SurfaceUpdates>& surfaceUpdates) {
1616
auto lock = std::lock_guard(mutex_);
1717
for (const auto& [surfaceId, updates] : surfaceUpdates) {
18-
auto& surfaceContext = surfaceContexts_[surfaceId];
18+
// Do not recreate state for stopped surfaces from late animation frames.
19+
auto contextIt = surfaceContexts_.find(surfaceId);
20+
if (contextIt == surfaceContexts_.end()) {
21+
continue;
22+
}
23+
auto& surfaceContext = contextIt->second;
1924
auto& pendingMap = surfaceContext.pendingMap;
2025
auto& pendingFamilies = surfaceContext.pendingFamilies;
2126

@@ -55,6 +60,11 @@ void AnimatedPropsRegistry::update(
5560
}
5661
}
5762

63+
void AnimatedPropsRegistry::initializeSurface(SurfaceId surfaceId) {
64+
auto lock = std::lock_guard(mutex_);
65+
surfaceContexts_.try_emplace(surfaceId);
66+
}
67+
5868
std::pair<
5969
std::unordered_set<std::shared_ptr<const ShadowNodeFamily>>&,
6070
SnapshotMap&>

packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropsRegistry.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ using SnapshotMap = std::unordered_map<Tag, std::unique_ptr<PropsSnapshot>>;
3838
class AnimatedPropsRegistry {
3939
public:
4040
void update(const std::unordered_map<SurfaceId, SurfaceUpdates> &surfaceUpdates);
41+
void initializeSurface(SurfaceId surfaceId);
4142
void clear(SurfaceId surfaceId);
4243
void clearOnSurfaceStop(SurfaceId surfaceId);
4344
std::pair<std::unordered_set<std::shared_ptr<const ShadowNodeFamily>> &, SnapshotMap &> getMap(SurfaceId surfaceId);

packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,25 @@ AnimationBackend::AnimationBackend(
5656
commitHook_(*uiManager, animatedPropsRegistry_),
5757
uiManager_(std::move(uiManager)) {
5858
react_native_assert(uiManager_.expired() == false);
59+
60+
auto weakAnimatedPropsRegistry =
61+
std::weak_ptr<AnimatedPropsRegistry>(animatedPropsRegistry_);
62+
auto initializeSurfaceContext =
63+
[weakAnimatedPropsRegistry](const ShadowTree& shadowTree) {
64+
if (auto animatedPropsRegistry = weakAnimatedPropsRegistry.lock()) {
65+
animatedPropsRegistry->initializeSurface(shadowTree.getSurfaceId());
66+
}
67+
};
68+
69+
if (auto lockedUIManager = uiManager_.lock()) {
70+
lockedUIManager->getShadowTreeRegistry().enumerate(
71+
[&initializeSurfaceContext](
72+
const ShadowTree& shadowTree, bool& /*stop*/) {
73+
initializeSurfaceContext(shadowTree);
74+
});
75+
lockedUIManager->setOnSurfaceStartCallback(
76+
std::move(initializeSurfaceContext));
77+
}
5978
}
6079

6180
void AnimationBackend::unpackMutations(

packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include <react/renderer/runtimescheduler/RuntimeScheduler.h>
2323
#include <react/renderer/uimanager/UIManager.h>
2424
#include <react/renderer/uimanager/UIManagerBinding.h>
25+
#include <mutex>
2526

2627
namespace facebook::react {
2728

@@ -493,8 +494,21 @@ void Scheduler::removeEventListener(
493494

494495
void Scheduler::uiManagerShouldSetOnSurfaceStartCallback(
495496
OnSurfaceStartCallback&& callback) {
496-
std::shared_lock lock(onSurfaceStartCallbackMutex_);
497-
onSurfaceStartCallback_ = std::move(callback);
497+
std::unique_lock lock(onSurfaceStartCallbackMutex_);
498+
if (!onSurfaceStartCallback_) {
499+
onSurfaceStartCallback_ = std::move(callback);
500+
return;
501+
}
502+
503+
auto previousCallback = std::move(onSurfaceStartCallback_);
504+
onSurfaceStartCallback_ =
505+
[previousCallback = std::move(previousCallback),
506+
nextCallback = std::move(callback)](const ShadowTree& shadowTree) {
507+
previousCallback(shadowTree);
508+
if (nextCallback) {
509+
nextCallback(shadowTree);
510+
}
511+
};
498512
}
499513

500514
} // namespace facebook::react

scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1557,6 +1557,7 @@ class facebook::react::AnimatedPropsRegistry {
15571557
public std::pair<std::unordered_set<std::shared_ptr<const facebook::react::ShadowNodeFamily>>&, facebook::react::SnapshotMap&> getMap(facebook::react::SurfaceId surfaceId);
15581558
public void clear(facebook::react::SurfaceId surfaceId);
15591559
public void clearOnSurfaceStop(facebook::react::SurfaceId surfaceId);
1560+
public void initializeSurface(facebook::react::SurfaceId surfaceId);
15601561
public void update(const std::unordered_map<facebook::react::SurfaceId, facebook::react::SurfaceUpdates>& surfaceUpdates);
15611562
}
15621563

scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1552,6 +1552,7 @@ class facebook::react::AnimatedPropsRegistry {
15521552
public std::pair<std::unordered_set<std::shared_ptr<const facebook::react::ShadowNodeFamily>>&, facebook::react::SnapshotMap&> getMap(facebook::react::SurfaceId surfaceId);
15531553
public void clear(facebook::react::SurfaceId surfaceId);
15541554
public void clearOnSurfaceStop(facebook::react::SurfaceId surfaceId);
1555+
public void initializeSurface(facebook::react::SurfaceId surfaceId);
15551556
public void update(const std::unordered_map<facebook::react::SurfaceId, facebook::react::SurfaceUpdates>& surfaceUpdates);
15561557
}
15571558

scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1556,6 +1556,7 @@ class facebook::react::AnimatedPropsRegistry {
15561556
public std::pair<std::unordered_set<std::shared_ptr<const facebook::react::ShadowNodeFamily>>&, facebook::react::SnapshotMap&> getMap(facebook::react::SurfaceId surfaceId);
15571557
public void clear(facebook::react::SurfaceId surfaceId);
15581558
public void clearOnSurfaceStop(facebook::react::SurfaceId surfaceId);
1559+
public void initializeSurface(facebook::react::SurfaceId surfaceId);
15591560
public void update(const std::unordered_map<facebook::react::SurfaceId, facebook::react::SurfaceUpdates>& surfaceUpdates);
15601561
}
15611562

scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4141,6 +4141,7 @@ class facebook::react::AnimatedPropsRegistry {
41414141
public std::pair<std::unordered_set<std::shared_ptr<const facebook::react::ShadowNodeFamily>>&, facebook::react::SnapshotMap&> getMap(facebook::react::SurfaceId surfaceId);
41424142
public void clear(facebook::react::SurfaceId surfaceId);
41434143
public void clearOnSurfaceStop(facebook::react::SurfaceId surfaceId);
4144+
public void initializeSurface(facebook::react::SurfaceId surfaceId);
41444145
public void update(const std::unordered_map<facebook::react::SurfaceId, facebook::react::SurfaceUpdates>& surfaceUpdates);
41454146
}
41464147

scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4125,6 +4125,7 @@ class facebook::react::AnimatedPropsRegistry {
41254125
public std::pair<std::unordered_set<std::shared_ptr<const facebook::react::ShadowNodeFamily>>&, facebook::react::SnapshotMap&> getMap(facebook::react::SurfaceId surfaceId);
41264126
public void clear(facebook::react::SurfaceId surfaceId);
41274127
public void clearOnSurfaceStop(facebook::react::SurfaceId surfaceId);
4128+
public void initializeSurface(facebook::react::SurfaceId surfaceId);
41284129
public void update(const std::unordered_map<facebook::react::SurfaceId, facebook::react::SurfaceUpdates>& surfaceUpdates);
41294130
}
41304131

scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4140,6 +4140,7 @@ class facebook::react::AnimatedPropsRegistry {
41404140
public std::pair<std::unordered_set<std::shared_ptr<const facebook::react::ShadowNodeFamily>>&, facebook::react::SnapshotMap&> getMap(facebook::react::SurfaceId surfaceId);
41414141
public void clear(facebook::react::SurfaceId surfaceId);
41424142
public void clearOnSurfaceStop(facebook::react::SurfaceId surfaceId);
4143+
public void initializeSurface(facebook::react::SurfaceId surfaceId);
41434144
public void update(const std::unordered_map<facebook::react::SurfaceId, facebook::react::SurfaceUpdates>& surfaceUpdates);
41444145
}
41454146

0 commit comments

Comments
 (0)