feat: use signal-exit to handle shutdown - #23159
Conversation
| // Gracefully shut down when stdin ends (e.g. a parent process closed the pipe). | ||
| // `signal-exit` does not cover this. This was originally borrowed from Rollup, | ||
| // which has since dropped it; Vite keeps it to avoid leaving zombie processes. | ||
| // See https://github.com/vitejs/vite/pull/1857. | ||
| const onStdinEnd = () => { | ||
| runExitCallbacks(null, 0) | ||
| } |
There was a problem hiding this comment.
Checking rollup/rollup#3493 which is linked in that PR, it seems the code was reverted in rollup/rollup#5803 so probably we should do that later.
| }) | ||
| }) | ||
|
|
||
| describe('exit listener', () => { |
There was a problem hiding this comment.
I think it'd be better to use an e2e test instead. Either by adding a new playground similarly to playground/cli or spawning node here.
|
Have you tested the repro in #15418? |
|
/ecosystem-ci run |
@vitejs/plugin-legacy
vite
commit: |
|
📝 Ran ecosystem CI on
✅ nuxt, module-federation, quasar, marko, laravel, vike, storybook, analogjs, vite-plugin-react, unocss, waku, vite-plugin-vue, vuepress, vite-plugin-cloudflare, vite-setup-catalogue, vite-plugin-pwa, vite-plugin-rsc, vite-plugin-svelte, vitepress, vite-environment-examples |
|
I've tried the prerelease and unfortunately it doesn't work correctly so I'll convert this to draft. Here's the AI analysis:
|
Summary
Replaces Vite's ad-hoc signal handling (
process.once('SIGTERM')) with signal-exit (https://github.com/tapjs/signal-exit) for shutdown of the dev and preview servers.signal-exitcovers the full set of exit signals (SIGINT/Ctrl+C, SIGTERM, etc.) and interceptedprocess.exit()calls through a single, well-tested code path, and coordinates across separately-bundled copies (e.g. Vite's and Rolldown's) via a sharedglobalThisregistry so callbacks fire exactly once. The existing stdin-end handling is retained (see below), since signal-exit does not cover it.Stacked on #23110.
Notes
stdin end
Keeps the
process.stdin.on('end')listener (with the existingCI !== 'true'guard) thatsignal-exitdoes not cover. This handles the case where a parent process shuts Vite down by closing the pipe rather than sending a signal — common when Vite runs as a child of another dev tool (e.g. Phoenix/Elixir watchers), and the source of zombie-process reports (#5743, #19091).Added fallback for WebContainers
signal-exitworks by patchingprocess.reallyExit, which is a no-op in WebContainers (e.g. StackBlitz). Since Vite currently uses plainprocess.on('SIGTERM')/process.stdin.on('end')listeners (which do fire there), migrating to signal-exit would silently drop exit cleanup in WebContainers. This adds a fallback that registers a plainprocess.on('exit')listener whenprocess.versions.webcontaineris set, matching Rolldown's handling (https://github.com/rolldown/rolldown/blob/main/packages/rolldown/src/utils/signal-exit.ts).Note that exit listeners run synchronously and the process terminates as soon as they return, so async cleanup (
await server.close()) may not finish on this path. This is inherent toprocess.on('exit')and is the same trade-off Rolldown makes. It is still strictly better than the alternative, wheresignal-exitis a plain no-op in WebContainers and no cleanup runs at all.