Skip to content

Commit ee85098

Browse files
authored
[cleanup] remove deletedTreeCleanUpLevel feature flag (#25529)
I noticed this was an experiment concluded 16 months ago (#21679) that this extra work is beneficial to break up cycles leaking memory in product code.
1 parent 4f8ffec commit ee85098

11 files changed

+65
-133
lines changed

packages/react-reconciler/src/ReactFiberCommitWork.js

Lines changed: 65 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ import {
4646
enableSchedulingProfiler,
4747
enableSuspenseCallback,
4848
enableScopeAPI,
49-
deletedTreeCleanUpLevel,
5049
enableUpdaterTracking,
5150
enableCache,
5251
enableTransitionTracing,
@@ -1658,74 +1657,43 @@ function detachFiberAfterEffects(fiber: Fiber) {
16581657
detachFiberAfterEffects(alternate);
16591658
}
16601659

1661-
// Note: Defensively using negation instead of < in case
1662-
// `deletedTreeCleanUpLevel` is undefined.
1663-
if (!(deletedTreeCleanUpLevel >= 2)) {
1664-
// This is the default branch (level 0).
1665-
fiber.child = null;
1666-
fiber.deletions = null;
1667-
fiber.dependencies = null;
1668-
fiber.memoizedProps = null;
1669-
fiber.memoizedState = null;
1670-
fiber.pendingProps = null;
1671-
fiber.sibling = null;
1672-
fiber.stateNode = null;
1673-
fiber.updateQueue = null;
1674-
1675-
if (__DEV__) {
1676-
fiber._debugOwner = null;
1660+
// Clear cyclical Fiber fields. This level alone is designed to roughly
1661+
// approximate the planned Fiber refactor. In that world, `setState` will be
1662+
// bound to a special "instance" object instead of a Fiber. The Instance
1663+
// object will not have any of these fields. It will only be connected to
1664+
// the fiber tree via a single link at the root. So if this level alone is
1665+
// sufficient to fix memory issues, that bodes well for our plans.
1666+
fiber.child = null;
1667+
fiber.deletions = null;
1668+
fiber.sibling = null;
1669+
1670+
// The `stateNode` is cyclical because on host nodes it points to the host
1671+
// tree, which has its own pointers to children, parents, and siblings.
1672+
// The other host nodes also point back to fibers, so we should detach that
1673+
// one, too.
1674+
if (fiber.tag === HostComponent) {
1675+
const hostInstance: Instance = fiber.stateNode;
1676+
if (hostInstance !== null) {
1677+
detachDeletedInstance(hostInstance);
16771678
}
1678-
} else {
1679-
// Clear cyclical Fiber fields. This level alone is designed to roughly
1680-
// approximate the planned Fiber refactor. In that world, `setState` will be
1681-
// bound to a special "instance" object instead of a Fiber. The Instance
1682-
// object will not have any of these fields. It will only be connected to
1683-
// the fiber tree via a single link at the root. So if this level alone is
1684-
// sufficient to fix memory issues, that bodes well for our plans.
1685-
fiber.child = null;
1686-
fiber.deletions = null;
1687-
fiber.sibling = null;
1688-
1689-
// The `stateNode` is cyclical because on host nodes it points to the host
1690-
// tree, which has its own pointers to children, parents, and siblings.
1691-
// The other host nodes also point back to fibers, so we should detach that
1692-
// one, too.
1693-
if (fiber.tag === HostComponent) {
1694-
const hostInstance: Instance = fiber.stateNode;
1695-
if (hostInstance !== null) {
1696-
detachDeletedInstance(hostInstance);
1697-
}
1698-
}
1699-
fiber.stateNode = null;
1700-
1701-
// I'm intentionally not clearing the `return` field in this level. We
1702-
// already disconnect the `return` pointer at the root of the deleted
1703-
// subtree (in `detachFiberMutation`). Besides, `return` by itself is not
1704-
// cyclical — it's only cyclical when combined with `child`, `sibling`, and
1705-
// `alternate`. But we'll clear it in the next level anyway, just in case.
1679+
}
1680+
fiber.stateNode = null;
17061681

1707-
if (__DEV__) {
1708-
fiber._debugOwner = null;
1709-
}
1710-
1711-
if (deletedTreeCleanUpLevel >= 3) {
1712-
// Theoretically, nothing in here should be necessary, because we already
1713-
// disconnected the fiber from the tree. So even if something leaks this
1714-
// particular fiber, it won't leak anything else
1715-
//
1716-
// The purpose of this branch is to be super aggressive so we can measure
1717-
// if there's any difference in memory impact. If there is, that could
1718-
// indicate a React leak we don't know about.
1719-
fiber.return = null;
1720-
fiber.dependencies = null;
1721-
fiber.memoizedProps = null;
1722-
fiber.memoizedState = null;
1723-
fiber.pendingProps = null;
1724-
fiber.stateNode = null;
1725-
// TODO: Move to `commitPassiveUnmountInsideDeletedTreeOnFiber` instead.
1726-
fiber.updateQueue = null;
1727-
}
1682+
if (__DEV__) {
1683+
fiber._debugOwner = null;
17281684
}
1685+
1686+
// Theoretically, nothing in here should be necessary, because we already
1687+
// disconnected the fiber from the tree. So even if something leaks this
1688+
// particular fiber, it won't leak anything else.
1689+
fiber.return = null;
1690+
fiber.dependencies = null;
1691+
fiber.memoizedProps = null;
1692+
fiber.memoizedState = null;
1693+
fiber.pendingProps = null;
1694+
fiber.stateNode = null;
1695+
// TODO: Move to `commitPassiveUnmountInsideDeletedTreeOnFiber` instead.
1696+
fiber.updateQueue = null;
17291697
}
17301698

17311699
function emptyPortalContainer(current: Fiber) {
@@ -3986,31 +3954,29 @@ export function commitPassiveUnmountEffects(finishedWork: Fiber): void {
39863954
}
39873955

39883956
function detachAlternateSiblings(parentFiber: Fiber) {
3989-
if (deletedTreeCleanUpLevel >= 1) {
3990-
// A fiber was deleted from this parent fiber, but it's still part of the
3991-
// previous (alternate) parent fiber's list of children. Because children
3992-
// are a linked list, an earlier sibling that's still alive will be
3993-
// connected to the deleted fiber via its `alternate`:
3994-
//
3995-
// live fiber --alternate--> previous live fiber --sibling--> deleted
3996-
// fiber
3997-
//
3998-
// We can't disconnect `alternate` on nodes that haven't been deleted yet,
3999-
// but we can disconnect the `sibling` and `child` pointers.
4000-
4001-
const previousFiber = parentFiber.alternate;
4002-
if (previousFiber !== null) {
4003-
let detachedChild = previousFiber.child;
4004-
if (detachedChild !== null) {
4005-
previousFiber.child = null;
4006-
do {
4007-
// $FlowFixMe[incompatible-use] found when upgrading Flow
4008-
const detachedSibling = detachedChild.sibling;
4009-
// $FlowFixMe[incompatible-use] found when upgrading Flow
4010-
detachedChild.sibling = null;
4011-
detachedChild = detachedSibling;
4012-
} while (detachedChild !== null);
4013-
}
3957+
// A fiber was deleted from this parent fiber, but it's still part of the
3958+
// previous (alternate) parent fiber's list of children. Because children
3959+
// are a linked list, an earlier sibling that's still alive will be
3960+
// connected to the deleted fiber via its `alternate`:
3961+
//
3962+
// live fiber --alternate--> previous live fiber --sibling--> deleted
3963+
// fiber
3964+
//
3965+
// We can't disconnect `alternate` on nodes that haven't been deleted yet,
3966+
// but we can disconnect the `sibling` and `child` pointers.
3967+
3968+
const previousFiber = parentFiber.alternate;
3969+
if (previousFiber !== null) {
3970+
let detachedChild = previousFiber.child;
3971+
if (detachedChild !== null) {
3972+
previousFiber.child = null;
3973+
do {
3974+
// $FlowFixMe[incompatible-use] found when upgrading Flow
3975+
const detachedSibling = detachedChild.sibling;
3976+
// $FlowFixMe[incompatible-use] found when upgrading Flow
3977+
detachedChild.sibling = null;
3978+
detachedChild = detachedSibling;
3979+
} while (detachedChild !== null);
40143980
}
40153981
}
40163982
}
@@ -4196,8 +4162,7 @@ function commitPassiveUnmountEffectsInsideOfDeletedTree_begin(
41964162
resetCurrentDebugFiberInDEV();
41974163

41984164
const child = fiber.child;
4199-
// TODO: Only traverse subtree if it has a PassiveStatic flag. (But, if we
4200-
// do this, still need to handle `deletedTreeCleanUpLevel` correctly.)
4165+
// TODO: Only traverse subtree if it has a PassiveStatic flag.
42014166
if (child !== null) {
42024167
child.return = fiber;
42034168
nextEffect = child;
@@ -4217,23 +4182,13 @@ function commitPassiveUnmountEffectsInsideOfDeletedTree_complete(
42174182
const sibling = fiber.sibling;
42184183
const returnFiber = fiber.return;
42194184

4220-
if (deletedTreeCleanUpLevel >= 2) {
4221-
// Recursively traverse the entire deleted tree and clean up fiber fields.
4222-
// This is more aggressive than ideal, and the long term goal is to only
4223-
// have to detach the deleted tree at the root.
4224-
detachFiberAfterEffects(fiber);
4225-
if (fiber === deletedSubtreeRoot) {
4226-
nextEffect = null;
4227-
return;
4228-
}
4229-
} else {
4230-
// This is the default branch (level 0). We do not recursively clear all
4231-
// the fiber fields. Only the root of the deleted subtree.
4232-
if (fiber === deletedSubtreeRoot) {
4233-
detachFiberAfterEffects(fiber);
4234-
nextEffect = null;
4235-
return;
4236-
}
4185+
// Recursively traverse the entire deleted tree and clean up fiber fields.
4186+
// This is more aggressive than ideal, and the long term goal is to only
4187+
// have to detach the deleted tree at the root.
4188+
detachFiberAfterEffects(fiber);
4189+
if (fiber === deletedSubtreeRoot) {
4190+
nextEffect = null;
4191+
return;
42374192
}
42384193

42394194
if (sibling !== null) {

packages/shared/ReactFeatureFlags.js

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -101,19 +101,6 @@ export const enableHostSingletons = true;
101101

102102
export const enableFloat = true;
103103

104-
// When a node is unmounted, recurse into the Fiber subtree and clean out
105-
// references. Each level cleans up more fiber fields than the previous level.
106-
// As far as we know, React itself doesn't leak, but because the Fiber contains
107-
// cycles, even a single leak in product code can cause us to retain large
108-
// amounts of memory.
109-
//
110-
// The long term plan is to remove the cycles, but in the meantime, we clear
111-
// additional fields to mitigate.
112-
//
113-
// It's an enum so that we can experiment with different levels of
114-
// aggressiveness.
115-
export const deletedTreeCleanUpLevel = 3;
116-
117104
export const enableUseHook = true;
118105

119106
// Enables unstable_useMemoCache hook, intended as a compilation target for

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ export const enableLegacyFBSupport = false;
5959
export const enableFilterEmptyStringAttributesDOM = false;
6060
export const disableNativeComponentFrames = false;
6161
export const skipUnmountedBoundaries = false;
62-
export const deletedTreeCleanUpLevel = 3;
6362
export const enableGetInspectorDataForInstanceInProduction = true;
6463
export const deferRenderPhaseUpdateToNextBatch = false;
6564

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ export const enableLegacyFBSupport = false;
4949
export const enableFilterEmptyStringAttributesDOM = false;
5050
export const disableNativeComponentFrames = false;
5151
export const skipUnmountedBoundaries = false;
52-
export const deletedTreeCleanUpLevel = 3;
5352
export const enableGetInspectorDataForInstanceInProduction = false;
5453
export const deferRenderPhaseUpdateToNextBatch = false;
5554

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ export const enableLegacyFBSupport = false;
4949
export const enableFilterEmptyStringAttributesDOM = false;
5050
export const disableNativeComponentFrames = false;
5151
export const skipUnmountedBoundaries = false;
52-
export const deletedTreeCleanUpLevel = 3;
5352
export const enableGetInspectorDataForInstanceInProduction = false;
5453
export const deferRenderPhaseUpdateToNextBatch = false;
5554

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ export const enableLegacyFBSupport = false;
4141
export const enableFilterEmptyStringAttributesDOM = false;
4242
export const disableNativeComponentFrames = false;
4343
export const skipUnmountedBoundaries = false;
44-
export const deletedTreeCleanUpLevel = 3;
4544
export const enableGetInspectorDataForInstanceInProduction = false;
4645
export const deferRenderPhaseUpdateToNextBatch = false;
4746
export const enableSuspenseAvoidThisFallback = false;

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ export const enableLegacyFBSupport = false;
4949
export const enableFilterEmptyStringAttributesDOM = false;
5050
export const disableNativeComponentFrames = false;
5151
export const skipUnmountedBoundaries = false;
52-
export const deletedTreeCleanUpLevel = 3;
5352
export const enableGetInspectorDataForInstanceInProduction = false;
5453
export const deferRenderPhaseUpdateToNextBatch = false;
5554

packages/shared/forks/ReactFeatureFlags.testing.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ export const enableLegacyFBSupport = false;
4949
export const enableFilterEmptyStringAttributesDOM = false;
5050
export const disableNativeComponentFrames = false;
5151
export const skipUnmountedBoundaries = false;
52-
export const deletedTreeCleanUpLevel = 3;
5352
export const enableGetInspectorDataForInstanceInProduction = false;
5453
export const deferRenderPhaseUpdateToNextBatch = false;
5554

packages/shared/forks/ReactFeatureFlags.testing.www.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ export const enableLegacyFBSupport = !__EXPERIMENTAL__;
4949
export const enableFilterEmptyStringAttributesDOM = false;
5050
export const disableNativeComponentFrames = false;
5151
export const skipUnmountedBoundaries = true;
52-
export const deletedTreeCleanUpLevel = 3;
5352
export const enableGetInspectorDataForInstanceInProduction = false;
5453
export const deferRenderPhaseUpdateToNextBatch = false;
5554

packages/shared/forks/ReactFeatureFlags.www-dynamic.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ export const enableFilterEmptyStringAttributesDOM = __VARIANT__;
1919
export const enableLegacyFBSupport = __VARIANT__;
2020
export const skipUnmountedBoundaries = __VARIANT__;
2121
export const enableUseRefAccessWarning = __VARIANT__;
22-
export const deletedTreeCleanUpLevel: number = __VARIANT__ ? 3 : 1;
2322
export const enableProfilerNestedUpdateScheduledHook = __VARIANT__;
2423
export const disableSchedulerTimeoutInWorkLoop = __VARIANT__;
2524
export const enableLazyContextPropagation = __VARIANT__;

packages/shared/forks/ReactFeatureFlags.www.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,6 @@ export const disableTextareaChildren = __EXPERIMENTAL__;
9595

9696
export const allowConcurrentByDefault = true;
9797

98-
export const deletedTreeCleanUpLevel = 3;
99-
10098
export const consoleManagedByDevToolsDuringStrictMode = true;
10199
export const enableServerContext = true;
102100

0 commit comments

Comments
 (0)