React: Memory leak after removing video elements

Created on 8 May 2019  路  15Comments  路  Source: facebook/react

This is a bug report. Seems to be related to https://github.com/facebook/react/issues/14272 but this clearly affects other browsers than just IE 11. I'm using the latest Google Chrome.

What is the current behavior?
The current behavior is that when you create and remove many video elements, the browser isn't able to garbage collect detached nodes after the video elements have been removed. This will cause a memory leak and eventually crash the browser (if you hit the memory limit)

Sandbox demo: https://codesandbox.io/s/9jjlr52vr - I have one button that renders 20 empty video tags and another button to render 20 empty img tags, to show the difference of detached nodes, that there's clearly something wrong with how React handles the video tag.

Production demo: https://csb-9jjlr52vr.netlify.com/

When you run the website, take a heap snapshot. Click on "_Render 20 empty video components_" and "_Remove empty video components_" a few times. Take another heap snapshot. On the second heap snapshot compare it with the first one. In the class filter type: _Detached_ and note how there are bunch of detached nodes that weren't garbage collected.

What is the expected behavior?
That React removes all references to the video element, so that the browser can garbage collect the detached nodes.

Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?
React: 16.8.6
CRA: 3.0.0
Chrome: Version 74.0.3729.131 (Official Build) (64-bit)

Needs Investigation

Most helpful comment

I'm seeing the same thing, video elements in Chrome/Chromium 74 don't seem to fully go away when detached. Tried your codepen example and some of my own, videos always wind up in heap snapshots showing as "detached HTMLVideoElement". I tried lots of combinations of messing with playback state, removing/clearing/altering video.src before detachment, etc. but same result every time.

Tried Chromium 73, not seeing the issue there. Videos look to be cleaned up properly when removed from DOM, using exact same code that leads to leaks in 74.

This problem _looks_ like it's compounded (in terms of leak size) by react 16 but not _caused_ by it. Haven't had any success finding an open bug for this on Chromium/Chrome trackers, which is surprising as it's so easy to reproduce using vanilla JS. Our app has a use-case very similar to yours - effectively also a long-running slideshow rotating through content that can contain video elements. We're also seeing memory exhaustion with this combination of Chrome 74 and React 16.

All 15 comments

Here are two screenshots that show the difference between video and img. The memory is clearly released for the img elements, but not the video elements. The blue should become gray. This was tested on the production version on Netlify.

Creating & removing empty video elements:
image

Creating & removing empty img elements:
image

Here you can see the listeners, nodes and JS heap grow as I create and remove video elements over time. I clicked on "Collect garbage" a few times during this process. Again, this is being tested on the production version.

image

Here's the same graph for images instead of videos. You can clearly see that the garbage collector is able to free the memory just fine for images, so there's clearly some issue with videos.

image

I can confirm that this memory leak also occurs for the audio element. Demo is available here: https://codesandbox.io/s/ol5x012n95

I'm not the best when it comes to debugging memory leaks with the heap snapshots, but something is holding a reference to the element in/around stateNode -> nextEffect

Dev:
image

Prod:
image

Is there anything I can do to help get this fixed? E.g. if someone could point me to some places that I could start investigating. Thanks.

I cleaned up the demo a bit, to get a better heap snapshot of the problem, e.g. by no longer creating divs with the videos.

Here's a screenshot of what's keeping a ref to the video element:
image

It shows that stateNode holds a ref to the video element, thus making the browser keep the HTMLVideoElement detached in memory. Even though it should be long gone.

I tried using class components instead of functional components, tried downgrading React to 16.2.0, and I always get the same memory leak, so this isn't new.

This memory leak isn't a problem unless you have a website running for a long time, like in my case (slideshow displaying videos on digital signage screens), or if you are watching many videos within a short time frame. E.g. if you watch many videos on Facebook.com.

It would be nice if someone from the React team could confirm this memory leak. I'm not sure what options I have for my website to temporarily fix this. I could append the video element manually to the DOM, but that could easily be overwritten by React, and is a huge anti-pattern AFAIK.

I've tried something similar on our app on Chrome v68, latest React. We always render a single video element but I tried to start playback 20 times (or something close to that because I might have lost count at some point). The final snapshot had only one detached video element not garbage-collected, with a reference in stateNode just like in your screenshot.

The difference here is that your demo renders 20 video nodes at once where we render only one and I manually did it 20 times. Can you try mounting and unmounting a component that renders a single video node 20 or more times and see if you get the same problem?

Either way, this definitely is an issue. One funky thing we are doing in order to work around some bugs in older versions of Chrome is:

    videoRef.src = '';
    videoRef.load();

in CWU. Maybe this would help in your case? The bug that requires this has long been fixed in Chrome but I would bet they are still optimizing the video tag in some ways, and that this together with React optimizations may be causing the video nodes to not be collected.

I've been digging a bit more. The memory leak seems to be present in vanilla JS:
Prod: https://csb-rrpokq4q3n.netlify.com/
Codesandbox: https://codesandbox.io/s/rrpokq4q3n

I can create and remove img elements without any detached nodes. But the video element creates the same problem. I'm starting to suspect this is a Chrome bug.

I went through all of your codesandbox examples and confirm that I am seeing the same information. I will continue to investigate and would love to be a part of finding a solution for this issue.

image

I'm seeing the same thing, video elements in Chrome/Chromium 74 don't seem to fully go away when detached. Tried your codepen example and some of my own, videos always wind up in heap snapshots showing as "detached HTMLVideoElement". I tried lots of combinations of messing with playback state, removing/clearing/altering video.src before detachment, etc. but same result every time.

Tried Chromium 73, not seeing the issue there. Videos look to be cleaned up properly when removed from DOM, using exact same code that leads to leaks in 74.

This problem _looks_ like it's compounded (in terms of leak size) by react 16 but not _caused_ by it. Haven't had any success finding an open bug for this on Chromium/Chrome trackers, which is surprising as it's so easy to reproduce using vanilla JS. Our app has a use-case very similar to yours - effectively also a long-running slideshow rotating through content that can contain video elements. We're also seeing memory exhaustion with this combination of Chrome 74 and React 16.

Throwing my hat into the ring here on this issue. I created a test case that compares React and native DOM methods. I suggest refreshing between tries on each version. My test case is similar to the ones here, keep an eye on the Memory tab in the dev tools and also keep an eye on the Allocation instrumentation timeline.

https://video-leaker.amadeus.now.sh/

It looks like chrome may have an issue where it holds onto these elements - but normally this is incredibly cheap. In my case here, you can get up to 100k+ video nodes and memory barely goes up. However because React is keeping references attached to the nodes, it can quickly balloon in a React environment.

I've come across this whilst building a kiosk as well so thought I'd chip in my observations.

It's very likely that I've misconfigured something in my app (still fairly new to React) to cause the following, but what I found was that once a video was rendered in one of my route components, even non-video components would have dom-node detachment and not get GC'd (I'd ruled out other dom leaks after removing all videos), which of course is a huge leak. Again it's probably just how I've setup my app though.

Following RaRa's suggestion - even though an anti-pattern - I then tried adding a native dom video, it was still creating permanent detached nodes itself, but now, if I remove the video element manually from the document during component unmount, it no longer affects the other components' GC, which makes sense as it's not in the React environment.

I'm supplying my kiosk as an electron build so hopefully I can roll back to a non-affected chromium version - though I'm pretty sure electron 5.0,1 should already be the non-affected chromium 73...).

For everyone facing this issue and could not find any workaround for this, I can give you quick solution for this. If you put your video element inside iframe tag you can get rid of memory leak. This is not very professional solution but works for me and as I understand this is not something we can fix as this happens in all frameworks and also in vanilla JS. Until this issue get solved by Chrome or Mozilla community you can use my solution.

By the way the idea came from @raRaRa when we discuss about how we can bypass this memory leak.

This is a Chrome bug. We can鈥檛 fix it so I鈥檒l close but feel free to continue the discussion. (And star the Chrome issue.)

https://bugs.chromium.org/p/chromium/issues/detail?id=969049

@gaearon Indeed, but please note that the memory leak is much worse in React, likely related to https://github.com/facebook/react/pull/15157#issuecomment-503413083

Yeah but that part isn鈥檛 really actionable in the context of this issue. There are separate threads discussing related problems too.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

jvorcak picture jvorcak  路  3Comments

framerate picture framerate  路  3Comments

trusktr picture trusktr  路  3Comments

kocokolo picture kocokolo  路  3Comments

jimfb picture jimfb  路  3Comments