Skip to content

Commit 5d04d73

Browse files
authored
Add eager alternate.stateNode cleanup (facebook#33161)
This is a fix for a problem where React retains shadow nodes longer than it needs to. The behaviour is shown in React Native test: https://github.com/facebook/react-native/blob/main/packages/react-native/src/private/__tests__/utilities/__tests__/ShadowNodeReferenceCounter-itest.js#L169 # Problem When React commits a new shadow tree, old shadow nodes are stored inside `fiber.alternate.stateNode`. This is not cleared up until React clones the node again. This may be problematic if mutation deletes a subtree, in that case `fiber.alternate.stateNode` will retain entire subtree until next update. In case of image nodes, this means retaining entire images. So when React goes from revision A: `<View><View /></View>` to revision B: `<View />`, `fiber.alternate.stateNode` will be pointing to Shadow Node that represents revision A.. ![image](https://github.com/user-attachments/assets/076b677e-d152-4763-8c9d-4f923212b424) # Fix To fix this, this PR adds a new feature flag `enableEagerAlternateStateNodeCleanup`. When enabled, `alternate.stateNode` is proactively pointed towards finishedWork's stateNode, releasing resources sooner. I have verified this fixes the issue [demonstrated by React Native tests](https://github.com/facebook/react-native/blob/main/packages/react-native/src/private/__tests__/utilities/__tests__/ShadowNodeReferenceCounter-itest.js#L169). All existing React tests pass when the flag is enabled.
1 parent 3820740 commit 5d04d73

9 files changed

+25
-0
lines changed

packages/react-reconciler/src/ReactFiberCommitWork.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ import {
5959
enableComponentPerformanceTrack,
6060
enableViewTransition,
6161
enableFragmentRefs,
62+
enableEagerAlternateStateNodeCleanup,
6263
} from 'shared/ReactFeatureFlags';
6364
import {
6465
FunctionComponent,
@@ -2170,6 +2171,20 @@ function commitMutationEffectsOnFiber(
21702171
}
21712172
}
21722173
}
2174+
} else {
2175+
if (enableEagerAlternateStateNodeCleanup) {
2176+
if (supportsPersistence) {
2177+
if (finishedWork.alternate !== null) {
2178+
// `finishedWork.alternate.stateNode` is pointing to a stale shadow
2179+
// node at this point, retaining it and its subtree. To reclaim
2180+
// memory, point `alternate.stateNode` to new shadow node. This
2181+
// prevents shadow node from staying in memory longer than it
2182+
// needs to. The correct behaviour of this is checked by test in
2183+
// React Native: ShadowNodeReferenceCounter-itest.js#L150
2184+
finishedWork.alternate.stateNode = finishedWork.stateNode;
2185+
}
2186+
}
2187+
}
21732188
}
21742189
break;
21752190
}

packages/shared/ReactFeatureFlags.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,8 @@ export const enablePersistedModeClonedFlag = false;
141141

142142
export const enableShallowPropDiffing = false;
143143

144+
export const enableEagerAlternateStateNodeCleanup = true;
145+
144146
/**
145147
* Enables an expiration time for retry lanes to avoid starvation.
146148
*/

packages/shared/forks/ReactFeatureFlags.native-fb-dynamic.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export const enableObjectFiber = __VARIANT__;
2222
export const enableHiddenSubtreeInsertionEffectCleanup = __VARIANT__;
2323
export const enablePersistedModeClonedFlag = __VARIANT__;
2424
export const enableShallowPropDiffing = __VARIANT__;
25+
export const enableEagerAlternateStateNodeCleanup = __VARIANT__;
2526
export const passChildrenWhenCloningPersistedNodes = __VARIANT__;
2627
export const enableFastAddPropertiesInDiffing = __VARIANT__;
2728
export const enableLazyPublicInstanceInFabric = __VARIANT__;

packages/shared/forks/ReactFeatureFlags.native-fb.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export const {
2424
enableObjectFiber,
2525
enablePersistedModeClonedFlag,
2626
enableShallowPropDiffing,
27+
enableEagerAlternateStateNodeCleanup,
2728
passChildrenWhenCloningPersistedNodes,
2829
enableFastAddPropertiesInDiffing,
2930
enableLazyPublicInstanceInFabric,

packages/shared/forks/ReactFeatureFlags.native-oss.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ export const enableSchedulingProfiler = __PROFILE__;
4949
export const enableComponentPerformanceTrack = false;
5050
export const enableScopeAPI = false;
5151
export const enableShallowPropDiffing = false;
52+
export const enableEagerAlternateStateNodeCleanup = false;
5253
export const enableSuspenseAvoidThisFallback = false;
5354
export const enableSuspenseCallback = false;
5455
export const enableTaint = true;

packages/shared/forks/ReactFeatureFlags.test-renderer.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ export const enableInfiniteRenderLoopDetection = false;
6262

6363
export const renameElementSymbol = true;
6464
export const enableShallowPropDiffing = false;
65+
export const enableEagerAlternateStateNodeCleanup = false;
6566

6667
export const enableYieldingBeforePassive = true;
6768

packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export const enableSchedulingProfiler = __PROFILE__;
4747
export const enableComponentPerformanceTrack = false;
4848
export const enableScopeAPI = false;
4949
export const enableShallowPropDiffing = false;
50+
export const enableEagerAlternateStateNodeCleanup = false;
5051
export const enableSuspenseAvoidThisFallback = false;
5152
export const enableSuspenseCallback = false;
5253
export const enableTaint = true;

packages/shared/forks/ReactFeatureFlags.test-renderer.www.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ export const renameElementSymbol = false;
7171

7272
export const enableObjectFiber = false;
7373
export const enableShallowPropDiffing = false;
74+
export const enableEagerAlternateStateNodeCleanup = false;
7475

7576
export const enableHydrationLaneScheduling = true;
7677

packages/shared/forks/ReactFeatureFlags.www.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ export const disableLegacyMode = true;
107107

108108
export const enableShallowPropDiffing = false;
109109

110+
export const enableEagerAlternateStateNodeCleanup = false;
111+
110112
export const enableLazyPublicInstanceInFabric = false;
111113

112114
export const enableGestureTransition = false;

0 commit comments

Comments
 (0)