Skip to content

Commit 2e4edc2

Browse files
javachemeta-codesync[bot]
authored andcommitted
Clean up YogaLayoutableShadowNode (re-enable assert, name sentinel, document ABA) (#57064)
Summary: Pull Request resolved: #57064 Three small cleanups uncovered during review of the clone path: - Re-enable the dirty-flag inheritance assert in the clone constructor. It was disabled while the background executor was potentially racing the JS thread; background executor is no longer in use, so the assert is meaningful again. - Lift the `0xBADC0FFEE0DDF00D` magic owner sentinel out into a named `yoga::Node* const` (`reinterpret_cast` is not constexpr, so it can't be `constexpr`). The bit pattern stays the same so it remains recognisable in debuggers. - Move the explanatory comment for `updateYogaChildrenOwnersIfNeeded` next to its implementation and rewrite it to clearly describe the ABA scenario it guards against (and the common no-op case). Changelog: [Internal] Reviewed By: christophpurrer Differential Revision: D107079944
1 parent 9d910a5 commit 2e4edc2

2 files changed

Lines changed: 26 additions & 22 deletions

File tree

packages/react-native/ReactCommon/react/renderer/components/view/YogaLayoutableShadowNode.cpp

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -100,19 +100,14 @@ YogaLayoutableShadowNode::YogaLayoutableShadowNode(
100100
yogaNode_(
101101
static_cast<const YogaLayoutableShadowNode&>(sourceShadowNode)
102102
.yogaNode_) {
103-
// Note, cloned `yoga::Node` instance (copied using copy-constructor) inherits
104-
// dirty flag, measure function, and other properties being set originally in
105-
// the `YogaLayoutableShadowNode` constructor above.
106-
107-
// There is a known race condition when background executor is enabled, where
108-
// a tree may be laid out on the Fabric background thread concurrently with
109-
// the ShadowTree being created on the JS thread. This assert can be
110-
// re-enabled after disabling background executor everywhere.
111-
#if 0
112-
react_native_assert(YGNodeIsDirty(&static_cast<const YogaLayoutableShadowNode&>(sourceShadowNode)
113-
.yogaNode_) == YGNodeIsDirty(&yogaNode_) &&
103+
// Note, cloned `yoga::Node` instance (copied using copy-constructor)
104+
// inherits dirty flag, measure function, and other properties being set
105+
// originally in the `YogaLayoutableShadowNode` constructor above.
106+
react_native_assert(
107+
YGNodeIsDirty(
108+
&static_cast<const YogaLayoutableShadowNode&>(sourceShadowNode)
109+
.yogaNode_) == YGNodeIsDirty(&yogaNode_) &&
114110
"Yoga node must inherit dirty flag.");
115-
#endif
116111
if (!getTraits().check(ShadowNodeTraits::Trait::LeafYogaNode) &&
117112
!fragment.children) {
118113
// Children unchanged: copy the filtered list directly from the source,
@@ -326,11 +321,28 @@ bool YogaLayoutableShadowNode::shouldNewRevisionDirtyMeasurement(
326321
return true;
327322
}
328323

324+
// Detects the ABA scenario where a freshly-allocated `yogaNode_` happens
325+
// to land at the same address a previous parent occupied. After such a
326+
// realloc, any Yoga child whose owner pointer still equals `&yogaNode_`
327+
// is a stale match — yoga would mistake it for "owned by us" and skip the
328+
// clone-on-write check. We rewrite those spurious owner pointers to a
329+
// recognisable sentinel so the next `YGNodeGetOwner(child) == this` check
330+
// correctly returns false and yoga clones the child as it would for any
331+
// foreign tree.
332+
//
333+
// No-op in the common case: right after a clone, children's owner
334+
// pointers still reference the source node, not us.
329335
void YogaLayoutableShadowNode::updateYogaChildrenOwnersIfNeeded() {
336+
// Magic constant intentionally recognisable in debuggers when the address
337+
// pops up. `reinterpret_cast` is not constexpr, so this is a runtime-
338+
// initialised function-local static.
339+
// NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast)
340+
static auto* const kDetachedYogaNodeOwnerSentinel =
341+
reinterpret_cast<yoga::Node*>(0xBADC0FFEE0DDF00DULL);
342+
// NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast)
330343
for (auto& childYogaNode : yogaNode_.getChildren()) {
331344
if (YGNodeGetOwner(childYogaNode) == &yogaNode_) {
332-
childYogaNode->setOwner(
333-
reinterpret_cast<yoga::Node*>(0xBADC0FFEE0DDF00D));
345+
childYogaNode->setOwner(kDetachedYogaNodeOwnerSentinel);
334346
}
335347
}
336348
}

packages/react-native/ReactCommon/react/renderer/components/view/YogaLayoutableShadowNode.h

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -108,14 +108,6 @@ class YogaLayoutableShadowNode : public LayoutableShadowNode {
108108
mutable yoga::Node yogaNode_;
109109

110110
private:
111-
/*
112-
* Goes over `yogaNode_.getChildren()` and in case child's owner is
113-
* equal to address of `yogaNode_`, it sets child's owner address
114-
* to `0xBADC0FFEE0DDF00D`. This is magic constant, the intention
115-
* is to make debugging easier when the address pops up in debugger.
116-
* This prevents ABA problem where child yoga node goes from owned -> unowned
117-
* -> back to owned because its parent is allocated at the same address.
118-
*/
119111
void updateYogaChildrenOwnersIfNeeded();
120112

121113
/*

0 commit comments

Comments
 (0)