Skip to content

Commit 7a5bae6

Browse files
javachefacebook-github-bot
authored andcommitted
Clean up YogaLayoutableShadowNode (re-enable assert, name sentinel, document ABA)
Summary: 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 a0628b5 commit 7a5bae6

2 files changed

Lines changed: 29 additions & 22 deletions

File tree

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

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,16 @@ static int FabricDefaultYogaLog(
6464

6565
thread_local LayoutContext threadLocalLayoutContext;
6666

67+
// Owner sentinel used to detach our previously-adopted Yoga children from
68+
// `yogaNode_` without leaving the pointer at nullptr (which would lose the
69+
// "this node was owned" signal) and without leaving it at our address
70+
// (which would invite ABA when our `yoga::Node` slot is reused). The bit
71+
// pattern is intentionally recognisable in debuggers.
72+
// `reinterpret_cast` is not constexpr, so this is a runtime-initialised
73+
// `const` rather than `constexpr`.
74+
yoga::Node* const kDetachedYogaNodeOwnerSentinel =
75+
reinterpret_cast<yoga::Node*>(0xBADC0FFEE0DDF00DULL);
76+
6777
YogaLayoutableShadowNode::YogaLayoutableShadowNode(
6878
const ShadowNodeFragment& fragment,
6979
const ShadowNodeFamily::Shared& family,
@@ -100,19 +110,14 @@ YogaLayoutableShadowNode::YogaLayoutableShadowNode(
100110
yogaNode_(
101111
static_cast<const YogaLayoutableShadowNode&>(sourceShadowNode)
102112
.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_) &&
113+
// Note, cloned `yoga::Node` instance (copied using copy-constructor)
114+
// inherits dirty flag, measure function, and other properties being set
115+
// originally in the `YogaLayoutableShadowNode` constructor above.
116+
react_native_assert(
117+
YGNodeIsDirty(
118+
&static_cast<const YogaLayoutableShadowNode&>(sourceShadowNode)
119+
.yogaNode_) == YGNodeIsDirty(&yogaNode_) &&
114120
"Yoga node must inherit dirty flag.");
115-
#endif
116121
if (!getTraits().check(ShadowNodeTraits::Trait::LeafYogaNode) &&
117122
!fragment.children) {
118123
// Children unchanged: copy the filtered list directly from the source,
@@ -326,11 +331,21 @@ bool YogaLayoutableShadowNode::shouldNewRevisionDirtyMeasurement(
326331
return true;
327332
}
328333

334+
// Detects the ABA scenario where a freshly-allocated `yogaNode_` happens
335+
// to land at the same address a previous parent occupied. After such a
336+
// realloc, any Yoga child whose owner pointer still equals `&yogaNode_`
337+
// is a stale match — yoga would mistake it for "owned by us" and skip the
338+
// clone-on-write check. We rewrite those spurious owner pointers to
339+
// `kDetachedYogaNodeOwnerSentinel` so the next
340+
// `YGNodeGetOwner(child) == this` check correctly returns false and yoga
341+
// clones the child as it would for any foreign tree.
342+
//
343+
// No-op in the common case: right after a clone, children's owner
344+
// pointers still reference the source node, not us.
329345
void YogaLayoutableShadowNode::updateYogaChildrenOwnersIfNeeded() {
330346
for (auto& childYogaNode : yogaNode_.getChildren()) {
331347
if (YGNodeGetOwner(childYogaNode) == &yogaNode_) {
332-
childYogaNode->setOwner(
333-
reinterpret_cast<yoga::Node*>(0xBADC0FFEE0DDF00D));
348+
childYogaNode->setOwner(kDetachedYogaNodeOwnerSentinel);
334349
}
335350
}
336351
}

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)