Vue version
3.5.32
Link to minimal reproduction
https://stackblitz.com/edit/vitejs-vite-zqf6g74x?file=src%2Fcomponents%2FComponentA.vue&terminal=dev
Steps to reproduce
- download the reproduction from stackblitz (won't work there)
- run
pnpm i && pnpm ssr
- see the heap size & alive app instances grow with each request
The script creates 1000 fresh ssr apps, renders them to string and tracks whether each of them gets garbage-collected.
2 sibling async SFC components with top-level await are needed for this to reproduce. One of them needs to capture a stop handle for an immediate watcher, which then ends up keeping the whole scope in memory and through it, the whole app. (probably)
ComponentA
<script setup>
const enabled = await Promise.resolve(false)
const el = ref(null)
const stopWatch = watch(() => el.value, () => {}, { immediate: true }) // actually runs on the server
onScopeDispose(() => { stopWatch() })
</script>
<template>
<div ref="el">Component A {{ enabled }}</div>
</template>
Component B
<script setup>
const enabled = await Promise.resolve(false)
</script>
<template>
<div>Component B {{ enabled }}</div>
</template>
app:
<template>
<div>
<ComponentA />
<ComponentB />
</div>
</template>
What is expected?
All apps should get garbage collected.
What is actually happening?
Every app is kept in memory forever:
FINAL: created=10000 finalized=0 alive=10000
System Info
Any additional comments?
Maybe related to #14674?
In some cases, this can cause the whole NuxtApp to be kept in memory in Nuxt, along with all the rendered server payload, which adds up very quickly.
Vue version
3.5.32
Link to minimal reproduction
https://stackblitz.com/edit/vitejs-vite-zqf6g74x?file=src%2Fcomponents%2FComponentA.vue&terminal=dev
Steps to reproduce
pnpm i && pnpm ssrThe script creates 1000 fresh ssr apps, renders them to string and tracks whether each of them gets garbage-collected.
2 sibling async SFC components with top-level await are needed for this to reproduce. One of them needs to capture a stop handle for an immediate watcher, which then ends up keeping the whole scope in memory and through it, the whole app. (probably)
ComponentA
Component B
app:
What is expected?
All apps should get garbage collected.
What is actually happening?
Every app is kept in memory forever:
System Info
Any additional comments?
Maybe related to #14674?
In some cases, this can cause the whole
NuxtAppto be kept in memory in Nuxt, along with all the rendered server payload, which adds up very quickly.