ZeeCoder / use-resize-observer

A React hook that allows you to use a ResizeObserver to measure an element's size.
MIT License
644 stars 42 forks source link

Refs not being released on component dismounts leading to memory leaks #79

Closed ivancuric closed 2 years ago

ivancuric commented 3 years ago

I noticed a memory leak in my application when I was using useResizeObserver to observe a <canvas> element: the app was keeping references to the webgl2 contexts and crashed after 15 component remounts since that is the maximum allowed number of detached GL contexts in Chromium

Removing the resize observer on the canvas element fixes the app crashes.

image

I'm using this hook like:

  const canvasRef = useRef<HTMLCanvasElement>(null);
  const observedCanvas = useResizeObserver({
    ref: canvasRef,
  });

// other code

  return (
    <div
      css={{
        flexGrow: 1,
        position: 'relative',
      }}
    >
      <canvas
        ref={canvasRef}
        width={constrained?.width ?? 1}
        height={constrained?.height ?? 1}
        css={{
          background: '#242424',
          objectFit: 'contain',
          position: 'absolute',
          top: 0,
          left: 0,
          width: '100%',
          height: '100%',
          zIndex: 1,
          pointerEvents: 'none',
        }}
      />
    </div>
  );
ZeeCoder commented 3 years ago

Interesting. This happens with the latest version I assume? Could you create a codesandbox replicating the issue?

I'm not too sure how to analyse memory leaks so I'll have to read up on this to understand what's happening on this screenshot.

ivancuric commented 3 years ago

Here's a repro: https://codesandbox.io/s/resize-observer-memory-leak-coyip?file=/src/App.js

I actually managed to pinpoint the issue by watching the new HTTP 203 episode: https://www.youtube.com/watch?v=YDU_3WdfkxA

ZeeCoder commented 3 years ago

haha mate, that's on my todo list. I need to do a deep dive when it comes to analyzing performance / memory.

ZeeCoder commented 3 years ago

@ivancuric I actually think this is not an issue with uRO. I did a bit of googling on how WebGL context is released, and supposedly garbage collection should clean up if there are no references left to the ctx, however there's no way of telling when that happens.

However, there's a way to manually clean up the context with a loseContext() call.

I forked your reproduction and when I added a cleanup function for the context, I stopped receiving the warning about too many active webgl contexts: https://codesandbox.io/s/resize-observer-memory-leak-forked-ef566?file=/src/App.js

ivancuric commented 3 years ago

The issue went away when I switched to another resize observer hook.

Of course you can lose the context manually, but the ref should be released to allow for GC.

ZeeCoder commented 3 years ago

That's weird. I've updated the same fork to do a minimal reproduction of the issue, and the same issue happens without a resize hook too.

Just click the toggle button enough times and it'll give you the same warning.

ZeeCoder commented 3 years ago

Here's an even smaller reproduction: https://codesandbox.io/s/resize-observer-memory-leak-forked-8i01l?file=/src/App.js