Repository navigation
server.close() hangs forever when a dependency is discovered mid-optimize (optimizer never settles its processing promise) #22934
Description
Describe the bug
What I'm doing: running a Vitest browser-mode suite (vitest run, which drives a Vite dev server) on a cold optimizer cache, where dependencies are discovered progressively while the dep optimizer is still running.
What I expect: once the tests finish, vitest run exits.
What actually happens: the tests pass, but vitest run never exits — the process hangs indefinitely and has to be killed manually. The hang is the Vite dev server's close() never resolving during teardown.
Root cause — createDepsOptimizer() (packages/vite/src/node/optimizer/optimizer.ts) leaves a depOptimizationProcessing promise permanently unresolved when the server is closed while an optimize run is in flight:
- The optimizer tracks pending work with a current
depOptimizationProcessingpromise plus a queue of enqueued ones. A dep discovered viaregisterMissingImportgetsoptimizedDepInfo.processingset to the current promise;startNextDiscoveredBatch()later moves it into the queue. - On shutdown these are settled inconsistently:
runOptimizer()'s pre-optimizeclosedguard resolves the current and the enqueued promises; its post-optimizeclosedguard resolves only the enqueued ones; andclose()resolves neither. - So a dep discovered into the current batch while a run is in flight keeps a forever-pending
.processing. Itsvite:optimized-depsload()awaitsinfo.processingforever → the request never leavesenvironment._pendingRequests→DevEnvironment.close()'s drain loop (while (this._pendingRequests.size > 0) await ...) never finishes →close()never resolves.
Reproduced on vite 8.0.8 and 8.1.4.
I intend to submit a PR. The proposed fix is to have close() settle the pending promises, mirroring runOptimizer's pre-optimize closed guard:
async function close() {
closed = true
+ depOptimizationProcessing.resolve()
+ resolveEnqueuedProcessingPromises()
await Promise.allSettled([
discover?.cancel(),
depsOptimizer.scanProcessing,
optimizationResult?.cancel(),
])
}Reproduction
https://github.com/bent0b0x/vite-optimizer-orphan-hang
Steps to reproduce
npm install
npm testThe test passes, then the process hangs and never exits (kill with Ctrl-C). Applying the included fix.patch to vite makes npm test exit cleanly.
The two small plugins in vitest.config.ts only make the race deterministic — slow-optimize keeps an optimize run in flight at teardown, and slow-chain opens a >100ms gap between dependency discoveries so one lands on the in-flight run's current batch. They are timing controls, not the cause: with fix.patch applied, the identical config exits cleanly. The same race arises naturally on a cold optimizer cache when dependencies are discovered progressively during a slow pre-bundle (e.g. a Vitest browser run with coverage.include + default server.preTransformRequests).
System Info
System: macOS 26.5.1, arm64 (Apple M1 Max)
Binaries: Node 22.14.0, npm 10.9.2, pnpm 10.33.0
Browsers: Chrome 150.0.7871.102
npmPackages:
vite: ^8 => 8.1.4
vitest: ^4 => 4.1.10
@vitest/browser-playwright: ^4 => 4.1.10
playwright: ^1.59 => 1.61.1Used Package Manager
npm
Logs
No response
Validations
- Follow our Code of Conduct
- Read the Contributing Guidelines.
- Read the docs.
- Check that there isn't already an issue that reports the same bug to avoid creating a duplicate.
- Make sure this is a Vite issue and not a framework-specific issue. For example, if it's a Vue SFC related bug, it should likely be reported to vuejs/core instead.
- Check that this is a concrete bug. For Q&A open a GitHub Discussion or join our Discord Chat Server.
- The provided reproduction is a minimal reproducible example of the bug.
Activity
fix(optimizer): settle pending processing promises on close()
nicomouss commentedon Aug 3, 2026
@bent0b0x I have created #23143 that seems to be quite similar to the issue you have raised here.
The difference being that the test I have provided in the reproduction repo does not need any "deterministic race" to be implemented in order the error to be triggered. Just by requesting two files (using
server.transformRequest(..)) that contain an import to a random library (pascalcasein this scenario), is enough to trigger the hanging-out of theserver.close()instruction.In my case I have noticed the issue happens from Vite 8.1.4.
Everything seems to be fine for versions 8.1.3 and older.
Feel free to have a look when you have a moment.
fix(plugin-js): surface js_test output live and end the dependency-sc…
fix(plugin-js): surface js_test output live and end the dependency-sc…
fix(plugin-js): surface js_test output live and end the dependency-sc…
sapphi-red commentedon Sep 24, 2026
fixed by #23567