From d7d565cfa00991abec95ef5a2a96ca5272f88d57 Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Sat, 27 Mar 2021 15:26:17 -0500 Subject: [PATCH] Clean up host pointers in level 2 of clean-up flag (#21112) The host tree is a cyclical structure. Leaking a single DOM node can retain a large amount of memory. React-managed DOM nodes also point back to a fiber tree. Perf testing suggests that disconnecting these fields has a big memory impact. That suggests leaks in non-React code but since it's hard to completely eliminate those, it may still be worth the extra work to clear these fields. I'm moving this to level 2 to confirm whether this alone is responsible for the memory savings, or if there are other fields that are retaining large amounts of memory. In our plan for removing the alternate model, DOM nodes would not be connected to fibers, except at the root of the whole tree, which is easy to disconnect on deletion. So in that world, we likely won't have to do any additional work. --- .../src/ReactFiberCommitWork.new.js | 21 +++++++++++-------- .../src/ReactFiberCommitWork.old.js | 21 +++++++++++-------- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.new.js b/packages/react-reconciler/src/ReactFiberCommitWork.new.js index 9bcaa5a4d7536..e9845e1e65dc4 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.new.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.new.js @@ -1251,6 +1251,18 @@ function detachFiberAfterEffects(fiber: Fiber) { fiber.deletions = null; fiber.sibling = null; + // The `stateNode` is cyclical because on host nodes it points to the host + // tree, which has its own pointers to children, parents, and siblings. + // The other host nodes also point back to fibers, so we should detach that + // one, too. + if (fiber.tag === HostComponent) { + const hostInstance: Instance = fiber.stateNode; + if (hostInstance !== null) { + detachDeletedInstance(hostInstance); + } + } + fiber.stateNode = null; + // I'm intentionally not clearing the `return` field in this level. We // already disconnect the `return` pointer at the root of the deleted // subtree (in `detachFiberMutation`). Besides, `return` by itself is not @@ -1269,15 +1281,6 @@ function detachFiberAfterEffects(fiber: Fiber) { // The purpose of this branch is to be super aggressive so we can measure // if there's any difference in memory impact. If there is, that could // indicate a React leak we don't know about. - - // For host components, disconnect host instance -> fiber pointer. - if (fiber.tag === HostComponent) { - const hostInstance: Instance = fiber.stateNode; - if (hostInstance !== null) { - detachDeletedInstance(hostInstance); - } - } - fiber.return = null; fiber.dependencies = null; fiber.memoizedProps = null; diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.old.js b/packages/react-reconciler/src/ReactFiberCommitWork.old.js index 6b184f30d3d9e..6ed8560bd7649 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.old.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.old.js @@ -1251,6 +1251,18 @@ function detachFiberAfterEffects(fiber: Fiber) { fiber.deletions = null; fiber.sibling = null; + // The `stateNode` is cyclical because on host nodes it points to the host + // tree, which has its own pointers to children, parents, and siblings. + // The other host nodes also point back to fibers, so we should detach that + // one, too. + if (fiber.tag === HostComponent) { + const hostInstance: Instance = fiber.stateNode; + if (hostInstance !== null) { + detachDeletedInstance(hostInstance); + } + } + fiber.stateNode = null; + // I'm intentionally not clearing the `return` field in this level. We // already disconnect the `return` pointer at the root of the deleted // subtree (in `detachFiberMutation`). Besides, `return` by itself is not @@ -1269,15 +1281,6 @@ function detachFiberAfterEffects(fiber: Fiber) { // The purpose of this branch is to be super aggressive so we can measure // if there's any difference in memory impact. If there is, that could // indicate a React leak we don't know about. - - // For host components, disconnect host instance -> fiber pointer. - if (fiber.tag === HostComponent) { - const hostInstance: Instance = fiber.stateNode; - if (hostInstance !== null) { - detachDeletedInstance(hostInstance); - } - } - fiber.return = null; fiber.dependencies = null; fiber.memoizedProps = null;