Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Memory leak #11901

Closed
ppyl-datBoi opened this issue Sep 12, 2024 · 22 comments
Closed

Memory leak #11901

ppyl-datBoi opened this issue Sep 12, 2024 · 22 comments
Labels
❗ p4-important Priority 4: this fixes bugs that violate documented behavior, or significantly improves perf. 🐞 bug Something isn't working

Comments

@ppyl-datBoi
Copy link

Vue version

3.5.4

Link to minimal reproduction

http://cant.insert.code/commercial_project

Steps to reproduce

The problem started to appear in Vue 3.5.* (including version 3.5.4). There are no problems in version 3.4.38.
In my case, the approximate source of the problem is an active websocket connection.

What is expected?

No memory leak

What is actually happening?

After establishing a socket connection from which data is coming, the memory grows infinitely. Moreover, the more socket connections, the more the memory grows without stopping. With 10 connections in 5-10 minutes, the heap size consumption is more than 3 GB.
An example of how a leak starts after the application starts:
image

At the same time, taking memory snapshots is very difficult because the snapshot formation often gets stuck at the stage (Building dominator tree).
image

If you still manage to take a memory snapshot, it turns out that the preliminary cause is deps (I may be wrong).
image

I want to clarify that in case of a leak:
DOM Nodes - do not change.
JS Event Listeners - do not change.

System Info

System:
    OS: Windows 10 10.0.19044
    CPU: (12) x64 12th Gen Intel(R) Core(TM) i5-12400
    Memory: 13.66 GB / 31.77 GB
  Binaries:
    Node: 18.12.0 - C:\Program Files\nodejs\node.EXE
    Yarn: 1.22.4 - C:\Program Files (x86)\Yarn\bin\yarn.CMD
    npm: 8.19.2 - C:\Program Files\nodejs\npm.CMD
  Electron": 32.0.1
  Browsers:
    Chrome: 128.0.6613.120
  npmPackages:
    vue: ^3.5.4 => 3.5.4

Any additional comments?

No response

@ppyl-datBoi
Copy link
Author

Solution: After I removed {deep: true} in all "watch" functions the problem disappeared.
But because of the many such "watch " now I will have to reconsider the logic in the application.

@jh-leong
Copy link
Member

Could you please check the reproduction link? It seems to be inaccessible.

@ppyl-datBoi
Copy link
Author

I can't provide full repro code because I have a complex commercial project from which it is difficult to extract repro code. The fact is that "watch" with {deep: true} starts to leak.

@ppyl-datBoi
Copy link
Author

I also noticed that it's not just watch that's leaking. I have a couple of computed functions that are also leaking (the events array is updated several times per second). I compared several snapshots and noticed that the deps class is not being cleared, and that's a problem on your end.
image

Example of comparing snapshots 3 minutes apart:
image

@edison1105
Copy link
Member

edison1105 commented Sep 12, 2024

We can't analyze the bug without a runnable minimal reproduction.
Please follow the issue requirement.

@ppyl-datBoi
Copy link
Author

ppyl-datBoi commented Sep 12, 2024

I don't know if this mini demo is what you wanted to see, but even from it you can see that when comparing memory snapshots, the Dep class is not cleared by GC.
But the computed leak could not be confirmed.
Here is the link

@edison1105
Copy link
Member

edison1105 commented Sep 12, 2024

Still unable to reproduce

  • in Chrome incognito mode, ensure there are no browser extensions
  • manually collect garbage
  • JS heap size, DOM Nodes, JS event listeners. Their numbers will not continue to grow.

image

@gkubisa
Copy link

gkubisa commented Sep 12, 2024

I'm not sure if it is related but when debugging a reactivity issue in one of my vitest tests I noticed an issue with the double-linked list for tracking dependencies:

  • when traversing the list starting from depsTail using prevDep, there are 6 dependencies (not expected)
  • when traversing the list starting from deps using nextDep, there are 2 dependencies (expected)

image

I wonder if a memory leak could be caused by the redundant Dep objects referenced by depsTail.prevDep.prevDep.prevDep.*? Maybe in some situations they could be accumulating much more than in my test code?

Unfortunately I'm still struggling to isolate the issue. :-/

PS. The issue I'm debugging is about a computed not registering as a dependency of a shallowRef, even though I can see that the track function is called by Vue as expected.

@ppyl-datBoi
Copy link
Author

Still unable to reproduce

  • in Chrome incognito mode, ensure there are no browser extensions
  • manually collect garbage
  • JS heap size, DOM Nodes, JS event listeners. Their numbers will not continue to grow.

image

I also deliberately launched the demo in incognito mode. As you can see from the screenshot, the memory is gradually growing from snapshot to snapshot. Deps is also growing and is not cleared. Even deliberately calling the garbage collector from devtools does not improve the situation.

image

@edison1105 edison1105 added 🐞 bug Something isn't working ❗ p4-important Priority 4: this fixes bugs that violate documented behavior, or significantly improves perf. and removed ❗ p4-important Priority 4: this fixes bugs that violate documented behavior, or significantly improves perf. labels Sep 13, 2024
@rxdiscovery
Copy link

Thank you very much for this patch, it explains why all my applications made with the Quasar framework lagged, and the browser froze.

@rxdiscovery
Copy link

rxdiscovery commented Sep 17, 2024

Hello,
@yyx990803 @ppyl-datBoi : Version 3.5.5 had solved the problem, the new version 3.5.6 brought back the problem, but now my application made with Vue lag after 15 min of use, and crashes totally after 1h to 2h, is this a new memory leak?

Important note: this happens in dev mode, in release mode everything's fine (for now), I'm using the Quasar framework and Vite/yarn for the build.

@edison1105
Copy link
Member

@rxdiscovery
Does the reproduction of this issue reproduce your problem? if not, please create a new issue with a minimal reproduction.

@yyx990803
Copy link
Member

Might be related to c74bb8c

@rxdiscovery can you try the commit release here (set vue dependency in package.json to https://pkg.pr.new/vue@cbc39d5) to see if it works?

@rxdiscovery
Copy link

rxdiscovery commented Sep 19, 2024

Hello,

@yyx990803 I'll test the c74bb8c version and give you feedback as soon as possible.

I'm currently investigating the cause, there are 3 potential causes :

  • Firefox + Live debug mode with Vite / Webpack
  • Vue
  • Quasar framework

@edison1105 @yyx990803
I've also reported the problem here (the link below) with a video demonstration and the steps to reproduce it.
quasarframework/quasar#17520

(in the video, the first minute the application is reactive, but in minute 4 and +, it lags until it freezes.)

@edison1105
Copy link
Member

edison1105 commented Sep 19, 2024

@rxdiscovery
please also try the preview packages from #11971 to confirm if it can resolve your problem. It fix a memory leak which happens in dev.

@rxdiscovery
Copy link

rxdiscovery commented Sep 19, 2024

Hello,

to avoid any pollution, I created a new virtual machine with debian and the latest version of nodeJS and the latest version of Firefox.
The same problem occurred

then I tested your suggestions:

@yyx990803

  1. modification of the dependense
"dependecies" : {
...
"vue" : "https://pkg.pr.new/vue@cbc39d5"
...
}
  1. yarn cache clean
  2. rm yarn.lock
  3. yarn install
  4. quasar clean
  5. quasar dev
  6. clean browser cache
  7. check ===> same problem

@edison1105

  1. modification of the dependense
"dependecies" : {
...
"vue" : "https://pkg.pr.new/vue@11971"
...
}
  1. yarn cache clean
  2. rm yarn.lock
  3. yarn install
  4. quasar clean
  5. quasar dev
  6. clean browser cache
  7. check ===> same problem

even if I force my Vue version, does quasar still use 3.5.5 internally?

Is the problem related to this issue? Or are we on the wrong track and I have to open another issue?

@edison1105
Copy link
Member

@rxdiscovery
Please try to provide a minimal reproduction without quasar. I will take a deep look then.

@rxdiscovery
Copy link

rxdiscovery commented Sep 20, 2024

@edison1105 There is a leak in Vue, it is located at the level of :

node_modules/@vue/shared/dist/shared.esm-bundler.js

Chrome memory profiling :

Screenshot from 2024-09-20 23-07-42

Firefox memory profiling :

Screenshot from 2024-09-20 23-08-29

it's like there's a loop !

Screenshot from 2024-09-20 23-11-02

perhaps the problem lies here:

const patchKeyedChildren = (c1, c2, container, parentAnchor, parentComponent, parentSuspense, namespace, slotScopeIds, optimized) => {
    let i = 0;
    const l2 = c2.length;
    let e1 = c1.length - 1;
    let e2 = l2 - 1;
    while (i <= e1 && i <= e2) {
      const n1 = c1[i];
      const n2 = c2[i] = optimized ? cloneIfMounted(c2[i]) : normalizeVNode(c2[i]);
      if (isSameVNodeType(n1, n2)) {
        patch(
          n1,
          n2,
          container,
          null,
          parentComponent,
          parentSuspense,
          namespace,
          slotScopeIds,
          optimized
        );
      } else {
        break;
      }
      i++;
    }
    while (i <= e1 && i <= e2) {
      const n1 = c1[e1];
      const n2 = c2[e2] = optimized ? cloneIfMounted(c2[e2]) : normalizeVNode(c2[e2]);
      if (isSameVNodeType(n1, n2)) {
        patch(
          n1,
          n2,
          container,
          null,
          parentComponent,
          parentSuspense,
          namespace,
          slotScopeIds,
          optimized
        );
      } else {
        break;
      }
      e1--;
      e2--;
    }
    if (i > e1) {
      if (i <= e2) {
        const nextPos = e2 + 1;
        const anchor = nextPos < l2 ? c2[nextPos].el : parentAnchor;
        while (i <= e2) {
          patch(
            null,
            c2[i] = optimized ? cloneIfMounted(c2[i]) : normalizeVNode(c2[i]),
            container,
            anchor,
            parentComponent,
            parentSuspense,
            namespace,
            slotScopeIds,
            optimized
          );
          i++;
        }
      }
    } else if (i > e2) {
      while (i <= e1) {
        unmount(c1[i], parentComponent, parentSuspense, true);
        i++;
      }
    } else {
      const s1 = i;
      const s2 = i;
      const keyToNewIndexMap = /* @__PURE__ */ new Map();
      for (i = s2; i <= e2; i++) {
        const nextChild = c2[i] = optimized ? cloneIfMounted(c2[i]) : normalizeVNode(c2[i]);
        if (nextChild.key != null) {
          if (keyToNewIndexMap.has(nextChild.key)) {
            warn$1(
              `Duplicate keys found during update:`,
              JSON.stringify(nextChild.key),
              `Make sure keys are unique.`
            );
          }
          keyToNewIndexMap.set(nextChild.key, i);
        }
      }
      let j;
      let patched = 0;
      const toBePatched = e2 - s2 + 1;
      let moved = false;
      let maxNewIndexSoFar = 0;
      const newIndexToOldIndexMap = new Array(toBePatched);
      for (i = 0; i < toBePatched; i++)
        newIndexToOldIndexMap[i] = 0;
      for (i = s1; i <= e1; i++) {
        const prevChild = c1[i];
        if (patched >= toBePatched) {
          unmount(prevChild, parentComponent, parentSuspense, true);
          continue;
        }
        let newIndex;
        if (prevChild.key != null) {
          newIndex = keyToNewIndexMap.get(prevChild.key);
        } else {
          for (j = s2; j <= e2; j++) {
            if (newIndexToOldIndexMap[j - s2] === 0 && isSameVNodeType(prevChild, c2[j])) {
              newIndex = j;
              break;
            }
          }
        }
        if (newIndex === void 0) {
          unmount(prevChild, parentComponent, parentSuspense, true);
        } else {
          newIndexToOldIndexMap[newIndex - s2] = i + 1;
          if (newIndex >= maxNewIndexSoFar) {
            maxNewIndexSoFar = newIndex;
          } else {
            moved = true;
          }
          patch(
            prevChild,
            c2[newIndex],
            container,
            null,
            parentComponent,
            parentSuspense,
            namespace,
            slotScopeIds,
            optimized
          );
          patched++;
        }
      }
      const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : EMPTY_ARR;
      j = increasingNewIndexSequence.length - 1;
      for (i = toBePatched - 1; i >= 0; i--) {
        const nextIndex = s2 + i;
        const nextChild = c2[nextIndex];
        const anchor = nextIndex + 1 < l2 ? c2[nextIndex + 1].el : parentAnchor;
        if (newIndexToOldIndexMap[i] === 0) {
          patch(
            null,
            nextChild,
            container,
            anchor,
            parentComponent,
            parentSuspense,
            namespace,
            slotScopeIds,
            optimized
          );
        } else if (moved) {
          if (j < 0 || i !== increasingNewIndexSequence[j]) {
            move(nextChild, container, anchor, 2);
          } else {
            j--;
          }
        }
      }
    }
  };

@edison1105
Copy link
Member

the only thing we need is a minimal reproduction not screenshots

@rxdiscovery
Copy link

@edison1105 I'm currently analyzing the source code of Quasar's “QSelect”, to reproduce something similar in Vue, then I'll be able to give you a code without any dependencies.

@edison1105
Copy link
Member

@rxdiscovery try v3.5.7 by the way. We fixed some memory leaks yesterday.

@rxdiscovery
Copy link

@rxdiscovery try v3.5.7 by the way. We fixed some memory leaks yesterday.

I've already done the test, but unfortunately the problem persists.

@github-actions github-actions bot locked and limited conversation to collaborators Oct 6, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
❗ p4-important Priority 4: this fixes bugs that violate documented behavior, or significantly improves perf. 🐞 bug Something isn't working
Projects
None yet
Development

No branches or pull requests

6 participants