Skip to content

server.close() hangs forever when a dependency is discovered mid-optimize (optimizer never settles its processing promise) #22934

Description

@bent0b0x

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 depOptimizationProcessing promise plus a queue of enqueued ones. A dep discovered via registerMissingImport gets optimizedDepInfo.processing set to the current promise; startNextDiscoveredBatch() later moves it into the queue.
  • On shutdown these are settled inconsistently: runOptimizer()'s pre-optimize closed guard resolves the current and the enqueued promises; its post-optimize closed guard resolves only the enqueued ones; and close() resolves neither.
  • So a dep discovered into the current batch while a run is in flight keeps a forever-pending .processing. Its vite:optimized-deps load() awaits info.processing forever → the request never leaves environment._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 test

The 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.1

Used Package Manager

npm

Logs

No response

Validations

Activity

  1. added theissue type on Jul 14, 2026
  2. added a commit that references this issue on Jul 14, 2026
    0596c78
  3. nicomouss commented on Aug 3, 2026

    @nicomouss

    @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 (pascalcase in this scenario), is enough to trigger the hanging-out of the server.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.

  4. sapphi-red commented on Sep 24, 2026

    @sapphi-red
    Member

    fixed by #23567

  5. locked and limited conversation to collaborators on Oct 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions