Subscribe on changes!

Component anchor memory leak

avatar
Oct 22nd 2020

Version

3.0.2

Reproduction link

https://codepen.io/basvanmeurs/full/dyXpPJY

Steps to reproduce

  1. Open link
  2. Wait for 2s until only one 'my-test' heading remains
  3. Make heap snapshot

What is expected?

There should be 1 instances of IAmStillThere

What is actually happening?

There are 2 instances of IAmStillThere rather than 1


The removed item's instance and full scope (IAmStillThere) is still in memory, because it is retained by the instance.

Open the memory profiler and create a snapshot. Search for IAmStillThere. Observe that there are 2 rather than 1. Observe that the top one (which belongs to key "2") should not be there as it was removed.

The top IAmStillThere is retained because the handler was attached to the vnode as a prop. This vnode is retained because it was the anchor which was passed over to setupRenderEffect for the initial patch only of the component with key "1".

Later on, as "2" is removed, the anchor will remain in the scope of instance.update of "1". So the HTMLElement will leak, and become detached (search for 'Detached HTMLHeadingElement' in the snapshot).

The background of the problem is that setupRenderEffect servers two separate purposes: Mounting and updating. A big if-else statement distinguishes between the two. During mounting, anchor is used to invoke the patch. Anchor is never used while updating (and better so, as it is only valid within the current patch tree state). Even though it will never be used again, it will still stay in the scope of instance.update, which causes it to cling on to things that it shouldn't cling on to.

In my own use case, an advanced scroller, when scrolling up this bug caused all previously visible rows to stay in memory, building up to a major leak.

The solution for this specific case is actually quite simple: dereference immediately after mounting by setting to null.

Let's also do this for initialVNode and container. I don't have an example of how this can go wrong, but I suspect that it can go wrong in edge cases (teleports?). In any way, it's never a bad idea to clean up after yourself ASAP ;-)