Skip to content

[Bug]: Memory leak and high CPU usage for link with ref preload #1707

Open
@maksimr

Description

@maksimr

Preflight Checklist

  • I have searched the issue tracker for a bug report that matches the one I want to file, without success.

What package is this bug report for?

rrweb

Version

2.0.0-alpha.18

Expected Behavior

Prevent memory leak.

  1. First of all we should cleanup unload listener when try to make snapshot again
  2. It's unclear why we should at all try to listen preload stylesheet doesn't contains sheet at all and only preload it so maybe we should just drop this check or upgrade it to rel="preload stylesheet" which preload an apply styles.

Actual Behavior

Memory Leak:
<link rel="preload" href="./style.css" as="style"> always returns link.sheet equals to null. So we repeatedly call setTimeout and addListener for such link and don't cleanup added event listener that lead to memory leak.

Hight CPU usage:
The previous case with memory leak lead to degradation of performance if repeatedly stop and start recording. For example we stop recording when page invisible and start again when visible. In this case leaked timer hold previous record and now we have N(number of restarts) timers which constantly call setTimeout and add listeners so every stylesheetLoadTimeout we call N timers which add N listeners. In worst case the page is freez and consume 100% of CPU because we have a lot of timers and a lot of listeners.

Steps to Reproduce

index.html

<html>

  <link rel="preload" href="./style.css" as="style">

  <body>
    <script type="module" src="./index.js"></script>
  </body>

</html>

index.js

// Just Memory Leak
import { record } from "rrweb";

record({
  emit: () => void 0,
  checkoutEveryNms: 5000,
});

// Run getEventListeners every 5 seconds in DevTools
// getEventListeners(document.querySelector('link'));
Image

index.js

// Memory leak + high CPU usage
import { record } from "rrweb";

async function main() {
  const stopRef = { current: null };

  const pageVisibilityChangeNTimes = 5000;
  for (let i = 0; i < pageVisibilityChangeNTimes; i++) {
    await new Promise((resolve) => {
      startRecording(resolve);
    });
  }

  function startRecording(resolve) {
    stopRef.current?.();
    stopRef.current = record({
      emit: () => resolve(),
      checkoutEveryNms: 1000,
    });
  }
}

main();
Image

Testcase Gist URL

No response

Additional Information

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions