mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Add eager alternate.stateNode cleanup (#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 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..  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.
This commit is contained in:
@@ -57,6 +57,7 @@ import {
|
||||
enableComponentPerformanceTrack,
|
||||
enableViewTransition,
|
||||
enableFragmentRefs,
|
||||
enableEagerAlternateStateNodeCleanup,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import {
|
||||
FunctionComponent,
|
||||
@@ -1947,6 +1948,20 @@ function commitMutationEffectsOnFiber(
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (enableEagerAlternateStateNodeCleanup) {
|
||||
if (supportsPersistence) {
|
||||
if (finishedWork.alternate !== null) {
|
||||
// `finishedWork.alternate.stateNode` is pointing to a stale shadow
|
||||
// node at this point, retaining it and its subtree. To reclaim
|
||||
// memory, point `alternate.stateNode` to new shadow node. This
|
||||
// prevents shadow node from staying in memory longer than it
|
||||
// needs to. The correct behaviour of this is checked by test in
|
||||
// React Native: ShadowNodeReferenceCounter-itest.js#L150
|
||||
finishedWork.alternate.stateNode = finishedWork.stateNode;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -135,6 +135,8 @@ export const enableShallowPropDiffing = false;
|
||||
|
||||
export const enableSiblingPrerendering = true;
|
||||
|
||||
export const enableEagerAlternateStateNodeCleanup = true;
|
||||
|
||||
/**
|
||||
* Enables an expiration time for retry lanes to avoid starvation.
|
||||
*/
|
||||
|
||||
@@ -22,6 +22,7 @@ export const enableObjectFiber = __VARIANT__;
|
||||
export const enableHiddenSubtreeInsertionEffectCleanup = __VARIANT__;
|
||||
export const enablePersistedModeClonedFlag = __VARIANT__;
|
||||
export const enableShallowPropDiffing = __VARIANT__;
|
||||
export const enableEagerAlternateStateNodeCleanup = __VARIANT__;
|
||||
export const passChildrenWhenCloningPersistedNodes = __VARIANT__;
|
||||
export const enableSiblingPrerendering = __VARIANT__;
|
||||
export const enableUseEffectCRUDOverload = __VARIANT__;
|
||||
|
||||
@@ -25,6 +25,7 @@ export const {
|
||||
enablePersistedModeClonedFlag,
|
||||
enableShallowPropDiffing,
|
||||
enableUseEffectCRUDOverload,
|
||||
enableEagerAlternateStateNodeCleanup,
|
||||
passChildrenWhenCloningPersistedNodes,
|
||||
enableSiblingPrerendering,
|
||||
enableFastAddPropertiesInDiffing,
|
||||
|
||||
@@ -49,6 +49,7 @@ export const enableSchedulingProfiler = __PROFILE__;
|
||||
export const enableComponentPerformanceTrack = false;
|
||||
export const enableScopeAPI = false;
|
||||
export const enableShallowPropDiffing = false;
|
||||
export const enableEagerAlternateStateNodeCleanup = false;
|
||||
export const enableSuspenseAvoidThisFallback = false;
|
||||
export const enableSuspenseCallback = false;
|
||||
export const enableTaint = true;
|
||||
|
||||
@@ -65,6 +65,7 @@ export const enableShallowPropDiffing = false;
|
||||
export const enableSiblingPrerendering = true;
|
||||
|
||||
export const enableUseEffectCRUDOverload = false;
|
||||
export const enableEagerAlternateStateNodeCleanup = false;
|
||||
|
||||
export const enableYieldingBeforePassive = true;
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ export const enableSchedulingProfiler = __PROFILE__;
|
||||
export const enableComponentPerformanceTrack = false;
|
||||
export const enableScopeAPI = false;
|
||||
export const enableShallowPropDiffing = false;
|
||||
export const enableEagerAlternateStateNodeCleanup = false;
|
||||
export const enableSuspenseAvoidThisFallback = false;
|
||||
export const enableSuspenseCallback = false;
|
||||
export const enableTaint = true;
|
||||
|
||||
@@ -74,6 +74,7 @@ export const enableShallowPropDiffing = false;
|
||||
export const enableSiblingPrerendering = true;
|
||||
|
||||
export const enableUseEffectCRUDOverload = false;
|
||||
export const enableEagerAlternateStateNodeCleanup = false;
|
||||
|
||||
export const enableHydrationLaneScheduling = true;
|
||||
|
||||
|
||||
@@ -110,6 +110,8 @@ export const disableLegacyMode = true;
|
||||
|
||||
export const enableShallowPropDiffing = false;
|
||||
|
||||
export const enableEagerAlternateStateNodeCleanup = false;
|
||||
|
||||
export const enableLazyPublicInstanceInFabric = false;
|
||||
|
||||
export const enableSwipeTransition = false;
|
||||
|
||||
Reference in New Issue
Block a user