Skip to content

Commit 855bd45

Browse files
authored
fix: Android ANR deadlock between CSS updates-registry lock and Fabric commit (#9715)
## Issue On Android, screens using a relative CSS length (e.g. a `transformOrigin` mixing `%` and `px`) could freeze (ANR). Resolving the relative length read the live ShadowTree, which takes the ShadowTree commit lock, while holding Reanimated's updates-registry lock; a concurrent Fabric commit takes the same two locks in the opposite order, so they deadlock (AB-BA). Regression from #9495. ## Fix Resolve relative lengths against the last mounted tree instead of the live ShadowTree, so the read path never takes the ShadowTree lock. `ReanimatedMountHook` records the mounted root per surface (under the registry lock it already holds), and `ViewStylesRepository` reads layout from that snapshot via a plain parent/child walk. With lock A gone from Reanimated's read path, the AB-BA cycle can't form. Animation-backend mode keeps reading the live tree (its commit hook is disabled there, so there is no deadlock to avoid). Verified on device: Android (deadlock gone) and iOS (relative resolution correct).
1 parent a0549d7 commit 855bd45

5 files changed

Lines changed: 88 additions & 53 deletions

File tree

packages/react-native-reanimated/Common/cpp/reanimated/CSS/misc/ViewStylesRepository.cpp

Lines changed: 63 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include <reanimated/CSS/misc/ViewStylesRepository.h>
2+
#include <reanimated/Tools/FeatureFlags.h>
23

34
#include <memory>
45
#include <string>
@@ -13,13 +14,11 @@ ViewStylesRepository::ViewStylesRepository(
1314
jsi::Value ViewStylesRepository::getNodeProp(
1415
const std::shared_ptr<const ShadowNode> &shadowNode,
1516
const std::string &propName) {
16-
int tag = shadowNode->getTag();
17-
18-
auto &cachedNode = shadowNodeCache_[tag];
19-
updateCacheIfNeeded(cachedNode, shadowNode);
17+
const auto resolvedNode = getNewestNode(shadowNode);
18+
const auto *layoutableShadowNode = dynamic_cast<const LayoutableShadowNode *>(resolvedNode.get());
2019

2120
if (propName == "width" || propName == "height" || propName == "top" || propName == "left") {
22-
const auto &layoutMetrics = cachedNode.layoutMetrics;
21+
const auto layoutMetrics = layoutableShadowNode ? layoutableShadowNode->layoutMetrics_ : LayoutMetrics{};
2322

2423
if (propName == "width") {
2524
return {layoutMetrics.frame.size.width};
@@ -31,7 +30,13 @@ jsi::Value ViewStylesRepository::getNodeProp(
3130
return {layoutMetrics.frame.origin.x};
3231
}
3332
} else {
34-
const auto &viewProps = cachedNode.viewProps;
33+
if (!layoutableShadowNode) {
34+
return jsi::Value::undefined();
35+
}
36+
const auto viewProps = std::static_pointer_cast<const ViewProps>(resolvedNode->getProps());
37+
if (!viewProps) {
38+
return jsi::Value::undefined();
39+
}
3540

3641
if (propName == "opacity") {
3742
return {viewProps->opacity};
@@ -48,15 +53,7 @@ jsi::Value ViewStylesRepository::getNodeProp(
4853
jsi::Value ViewStylesRepository::getParentNodeProp(
4954
const std::shared_ptr<const ShadowNode> &shadowNode,
5055
const std::string &propName) {
51-
const auto surfaceId = shadowNode->getSurfaceId();
52-
const auto &shadowTreeRegistry = uiManager_->getShadowTreeRegistry();
53-
54-
std::shared_ptr<const ShadowNode> parentNode = nullptr;
55-
56-
shadowTreeRegistry.visit(surfaceId, [&](ShadowTree const &shadowTree) {
57-
auto currentRevision = shadowTree.getCurrentRevision();
58-
parentNode = dom::getParentNode(currentRevision.rootShadowNode, *shadowNode);
59-
});
56+
const auto parentNode = getParentNode(shadowNode);
6057

6158
if (!parentNode) {
6259
return jsi::Value::undefined();
@@ -65,37 +62,67 @@ jsi::Value ViewStylesRepository::getParentNodeProp(
6562
return getNodeProp(parentNode, propName);
6663
}
6764

68-
folly::dynamic ViewStylesRepository::getStyleProp(const Tag tag, const PropertyPath &propertyPath) {
69-
auto animatedValue = getPropertyValue(animatedPropsRegistry_->get(tag), propertyPath);
70-
if (!animatedValue.isNull()) {
71-
return animatedValue;
65+
std::shared_ptr<const ShadowNode> ViewStylesRepository::getNewestNode(
66+
const std::shared_ptr<const ShadowNode> &shadowNode) const {
67+
if constexpr (StaticFeatureFlags::getFlag("USE_ANIMATION_BACKEND")) {
68+
// Backend mode has no commit hook (it returns before taking the updates-registry
69+
// lock), so reading the live tree here cannot deadlock and needs no snapshot.
70+
const auto newestNode = uiManager_->getNewestCloneOfShadowNode(*shadowNode);
71+
return newestNode ? newestNode : shadowNode;
7272
}
7373

74-
return getPropertyValue(staticPropsRegistry_->get(tag), propertyPath);
75-
}
74+
// Resolve shadowNode against the last mounted root without the ShadowTree lock (the
75+
// deadlock) that getNewestCloneOfShadowNode takes, falling back to the passed node.
76+
// Mirrors RN's getShadowNodeInSubtree:
77+
// https://github.com/facebook/react-native/blob/v0.86.0-rc.3/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp#L339
78+
const auto it = lastMountedRootBySurface_.find(shadowNode->getSurfaceId());
79+
if (it == lastMountedRootBySurface_.end()) {
80+
return shadowNode;
81+
}
82+
const auto &root = it->second;
7683

77-
void ViewStylesRepository::clearNodesCache() {
78-
shadowNodeCache_.clear();
84+
if (ShadowNode::sameFamily(*root, *shadowNode)) {
85+
return root;
86+
}
87+
88+
const auto ancestors = shadowNode->getFamily().getAncestors(*root);
89+
if (ancestors.empty()) {
90+
return shadowNode;
91+
}
92+
93+
const auto &deepest = ancestors.back();
94+
return deepest.first.get().getChildren().at(deepest.second);
7995
}
8096

81-
void ViewStylesRepository::updateCacheIfNeeded(
82-
CachedShadowNode &cachedNode,
83-
const std::shared_ptr<const ShadowNode> &shadowNode) {
84-
auto newestCloneOfShadowNode = uiManager_->getNewestCloneOfShadowNode(*shadowNode);
97+
std::shared_ptr<const ShadowNode> ViewStylesRepository::getParentNode(
98+
const std::shared_ptr<const ShadowNode> &shadowNode) const {
99+
if constexpr (StaticFeatureFlags::getFlag("USE_ANIMATION_BACKEND")) {
100+
// Backend mode resolves against the live tree (see getNewestNode).
101+
std::shared_ptr<const ShadowNode> parentNode = nullptr;
102+
uiManager_->getShadowTreeRegistry().visit(shadowNode->getSurfaceId(), [&](const ShadowTree &shadowTree) {
103+
parentNode = dom::getParentNode(shadowTree.getCurrentRevision().rootShadowNode, *shadowNode);
104+
});
105+
return parentNode;
106+
}
85107

86-
// Check if newestCloneOfShadowNode is valid (is already mounted / not
87-
// yet unmounted)
88-
if (!newestCloneOfShadowNode) {
89-
return;
108+
const auto it = lastMountedRootBySurface_.find(shadowNode->getSurfaceId());
109+
if (it == lastMountedRootBySurface_.end()) {
110+
return nullptr;
90111
}
112+
return dom::getParentNode(it->second, *shadowNode);
113+
}
114+
115+
void ViewStylesRepository::setLastMountedRoot(const RootShadowNode::Shared &rootShadowNode) {
116+
lastMountedRootBySurface_[rootShadowNode->getSurfaceId()] = rootShadowNode;
117+
}
91118

92-
auto layoutableShadowNode = dynamic_cast<const LayoutableShadowNode *>(newestCloneOfShadowNode.get());
93-
if (!layoutableShadowNode) {
94-
return;
119+
folly::dynamic ViewStylesRepository::getStyleProp(const Tag tag, const PropertyPath &propertyPath) {
120+
auto animatedValue = getPropertyValue(animatedPropsRegistry_->get(tag), propertyPath);
121+
if (!animatedValue.isNull()) {
122+
return animatedValue;
95123
}
96124

97-
cachedNode.layoutMetrics = layoutableShadowNode->layoutMetrics_;
98-
cachedNode.viewProps = std::static_pointer_cast<const ViewProps>(newestCloneOfShadowNode->getProps());
125+
return getPropertyValue(staticPropsRegistry_->get(tag), propertyPath);
99126
}
100127

101128
folly::dynamic ViewStylesRepository::getPropertyValue(const folly::dynamic &value, const PropertyPath &propertyPath) {

packages/react-native-reanimated/Common/cpp/reanimated/CSS/misc/ViewStylesRepository.h

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44
#include <reanimated/CSS/registries/StaticPropsRegistry.h>
55
#include <reanimated/Fabric/updates/AnimatedPropsRegistry.h>
66

7+
#include <react/renderer/components/root/RootShadowNode.h>
78
#include <react/renderer/components/view/ViewProps.h>
89
#include <react/renderer/core/LayoutableShadowNode.h>
910
#include <react/renderer/dom/DOM.h>
11+
#include <react/renderer/uimanager/UIManager.h>
1012

1113
#include <memory>
1214
#include <string>
@@ -17,11 +19,6 @@ namespace reanimated::css {
1719
using namespace facebook;
1820
using namespace react;
1921

20-
struct CachedShadowNode {
21-
LayoutMetrics layoutMetrics;
22-
std::shared_ptr<const ViewProps> viewProps;
23-
};
24-
2522
class ViewStylesRepository {
2623
public:
2724
ViewStylesRepository(
@@ -36,16 +33,17 @@ class ViewStylesRepository {
3633
jsi::Value getParentNodeProp(const std::shared_ptr<const ShadowNode> &shadowNode, const std::string &propName);
3734
folly::dynamic getStyleProp(Tag tag, const PropertyPath &propertyPath);
3835

39-
void clearNodesCache();
36+
void setLastMountedRoot(const RootShadowNode::Shared &rootShadowNode);
4037

4138
private:
4239
std::shared_ptr<UIManager> uiManager_;
4340
std::shared_ptr<StaticPropsRegistry> staticPropsRegistry_;
4441
std::shared_ptr<AnimatedPropsRegistry> animatedPropsRegistry_;
4542

46-
std::unordered_map<int, CachedShadowNode> shadowNodeCache_;
43+
std::unordered_map<SurfaceId, RootShadowNode::Shared> lastMountedRootBySurface_;
4744

48-
void updateCacheIfNeeded(CachedShadowNode &cachedNode, const std::shared_ptr<const ShadowNode> &shadowNode);
45+
std::shared_ptr<const ShadowNode> getNewestNode(const std::shared_ptr<const ShadowNode> &shadowNode) const;
46+
std::shared_ptr<const ShadowNode> getParentNode(const std::shared_ptr<const ShadowNode> &shadowNode) const;
4947

5048
static folly::dynamic getPropertyValue(const folly::dynamic &value, const PropertyPath &propertyPath);
5149
};

packages/react-native-reanimated/Common/cpp/reanimated/Fabric/ReanimatedMountHook.cpp

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,12 @@ namespace reanimated {
99
ReanimatedMountHook::ReanimatedMountHook(
1010
const std::shared_ptr<UIManager> &uiManager,
1111
const std::shared_ptr<UpdatesRegistryManager> &updatesRegistryManager,
12+
const std::shared_ptr<css::ViewStylesRepository> &viewStylesRepository,
1213
const std::function<void()> &requestFlush)
13-
: uiManager_(uiManager), updatesRegistryManager_(updatesRegistryManager), requestFlush_(requestFlush) {
14+
: uiManager_(uiManager),
15+
updatesRegistryManager_(updatesRegistryManager),
16+
viewStylesRepository_(viewStylesRepository),
17+
requestFlush_(requestFlush) {
1418
uiManager_->registerMountHook(*this);
1519
}
1620

@@ -26,17 +30,24 @@ void ReanimatedMountHook::shadowTreeDidMount(
2630
auto reaShadowNode = std::reinterpret_pointer_cast<ReanimatedCommitShadowNode>(
2731
std::const_pointer_cast<RootShadowNode>(rootShadowNode));
2832

29-
if (reaShadowNode->hasReanimatedMountTrait()) {
33+
const bool isReanimatedMount = reaShadowNode->hasReanimatedMountTrait();
34+
if (isReanimatedMount) {
3035
// We mark reanimated commits with ReanimatedMountTrait. We don't want other
3136
// shadow nodes to use this trait, but since this rootShadowNode is Shared,
3237
// we don't have that guarantee. That's why we also unset this trait in the
3338
// commit hook. We remove it here mainly for the sake of cleanliness.
3439
reaShadowNode->unsetReanimatedMountTrait();
35-
return;
3640
}
3741

3842
{
3943
auto lock = updatesRegistryManager_->lock();
44+
// Record the mounted tree for relative-length resolution.
45+
viewStylesRepository_->setLastMountedRoot(rootShadowNode);
46+
47+
if (isReanimatedMount) {
48+
return;
49+
}
50+
4051
updatesRegistryManager_->handleNodeRemovals(*rootShadowNode);
4152

4253
// When commit from React Native has finished, we reset the skip commit flag

packages/react-native-reanimated/Common/cpp/reanimated/Fabric/ReanimatedMountHook.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#pragma once
22

3+
#include <reanimated/CSS/misc/ViewStylesRepository.h>
34
#include <reanimated/Fabric/ShadowTreeCloner.h>
45
#include <reanimated/Fabric/updates/UpdatesRegistryManager.h>
56

@@ -16,6 +17,7 @@ class ReanimatedMountHook : public UIManagerMountHook {
1617
ReanimatedMountHook(
1718
const std::shared_ptr<UIManager> &uiManager,
1819
const std::shared_ptr<UpdatesRegistryManager> &updatesRegistryManager,
20+
const std::shared_ptr<css::ViewStylesRepository> &viewStylesRepository,
1921
const std::function<void()> &requestFlush);
2022
~ReanimatedMountHook() noexcept override;
2123

@@ -24,6 +26,7 @@ class ReanimatedMountHook : public UIManagerMountHook {
2426
private:
2527
const std::shared_ptr<UIManager> uiManager_;
2628
const std::shared_ptr<UpdatesRegistryManager> updatesRegistryManager_;
29+
const std::shared_ptr<css::ViewStylesRepository> viewStylesRepository_;
2730
const std::function<void()> requestFlush_;
2831
};
2932

packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.cpp

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -810,11 +810,6 @@ void ReanimatedModuleProxy::performOperations() {
810810
}
811811

812812
commitUpdates(uiRuntime, updatesBatch);
813-
814-
// Clear the entire cache after the commit
815-
// (we don't know if the view is updated from outside of Reanimated
816-
// so we have to clear the entire cache)
817-
viewStylesRepository_->clearNodesCache();
818813
}
819814

820815
void ReanimatedModuleProxy::performNonLayoutOperations() {
@@ -1208,7 +1203,8 @@ void ReanimatedModuleProxy::initializeFabric(const std::shared_ptr<UIManager> &u
12081203
// TODO: we don't use the mount hook here, but we still need a way to handleNodeRemovals
12091204
// for now we leave this to leak the memory, a fix will come in a follow-up
12101205
} else {
1211-
mountHook_ = std::make_shared<ReanimatedMountHook>(uiManager_, updatesRegistryManager_, request);
1206+
mountHook_ =
1207+
std::make_shared<ReanimatedMountHook>(uiManager_, updatesRegistryManager_, viewStylesRepository_, request);
12121208
}
12131209

12141210
commitHook_ = std::make_shared<ReanimatedCommitHook>(uiManager_, updatesRegistryManager_, layoutAnimationsProxy_);

0 commit comments

Comments
 (0)