Skip to content

fix(dev): quit command hangs/timeouts on Windows - #1532

Draft
userquin wants to merge 1 commit into
nuxt:mainfrom
userquin:fix-quit-timeout-on-windows
Draft

fix(dev): quit command hangs/timeouts on Windows#1532
userquin wants to merge 1 commit into
nuxt:mainfrom
userquin:fix-quit-timeout-on-windows

Conversation

@userquin

@userquin userquin commented Sep 10, 2026

Copy link
Copy Markdown
Member

🔗 Linked issue

resolves #1531

📚 Description

This PR includes:

Maybe we can fix the deadlock here, not yet sure...

@pkg-pr-new

pkg-pr-new Bot commented Sep 10, 2026

Copy link
Copy Markdown
  • nuxt-cli-playground

    npm i https://pkg.pr.new/create-nuxt@1532
    
    npm i https://pkg.pr.new/nuxi@1532
    
    npm i https://pkg.pr.new/@nuxt/cli@1532
    

commit: dc2c208

@github-actions

Copy link
Copy Markdown
Contributor

CLI benchmark

@nuxt/cli v4.0.0-alpha.1 (baseline) vs v4.0.0-alpha.1 (this PR)

Metric baseline v4.0.0-alpha.1 head v4.0.0-alpha.1 Delta
nuxt --version wall time (median) 64 ms 63 ms -1.5%
nuxt --help wall time (median) 143 ms 142 ms -0.8%
nuxt dev --help wall time (median) 106 ms 105 ms -0.5%
nuxt --version modules loaded 37 37 0.0%
nuxt --help modules loaded 146 146 0.0%
nuxt dev --help modules loaded 81 81 0.0%
Installed node_modules 2.49 MB 2.49 MB -0.0%
Published tarball (packed) 286.8 kB 286.9 kB +0.0%
Full report

@nuxt/cli v4.0.0-alpha.1 (baseline) vs v4.0.0-alpha.1 (head)

Setting Value
Baseline ref:fbc6ac038bc37503e729906f58c1f0bbf05f6d9c (v4.0.0-alpha.1)
Head local packages/nuxt-cli at 736e174 (v4.0.0-alpha.1)
Node v24.21.0
OS Linux 6.17.0 (kernel 6.17.0-1022-azure)
CPU AMD EPYC 7763 64-Core Processor x 4
Memory 15.6 GB
Load average at start 0.60, 0.19, 0.07
Run started 2026-09-10T18:29:20.898Z

Cold CLI startup

Median of 15 interleaved runs per command, one warmup discarded.

Command baseline v4.0.0-alpha.1 median head v4.0.0-alpha.1 median Delta baseline v4.0.0-alpha.1 min / p95 head v4.0.0-alpha.1 min / p95
nuxt --version 64 ms 63 ms -1.5% 63 ms / 66 ms 62 ms / 70 ms
nuxt --version (first output byte) 60 ms 59 ms -1.8% 59 ms / 62 ms 58 ms / 66 ms
nuxt --help 143 ms 142 ms -0.8% 137 ms / 144 ms 137 ms / 145 ms
nuxt --help (first output byte) 138 ms 137 ms -0.7% 132 ms / 139 ms 132 ms / 140 ms
nuxt dev --help 106 ms 105 ms -0.5% 103 ms / 109 ms 103 ms / 110 ms
nuxt dev --help (first output byte) 102 ms 101 ms -0.6% 99 ms / 104 ms 99 ms / 105 ms
nuxt <unknown-command> (no-op) 153 ms 151 ms -0.9% 149 ms / 159 ms 149 ms / 154 ms
nuxt <unknown-command> (no-op) (first output byte) 148 ms 146 ms -1.0% 143 ms / 154 ms 144 ms / 149 ms

Module load cost

Counted with a module.registerHooks load hook, compile cache disabled. Counts every JS module actually evaluated on that code path (built-ins excluded, native addons excluded).

Command baseline v4.0.0-alpha.1 modules head v4.0.0-alpha.1 modules Delta baseline v4.0.0-alpha.1 source bytes head v4.0.0-alpha.1 source bytes Delta
nuxt --version 37 37 0.0% 302.2 kB 302.2 kB 0.0%
nuxt --help 146 146 0.0% 1.03 MB 1.03 MB 0.0%
nuxt dev --help 81 81 0.0% 625.1 kB 625.1 kB 0.0%

Install footprint and published tarball

Each version installed on its own into an empty project with nothing but @nuxt/cli as a dependency, so the tree is exactly the CLI and its transitive dependencies. npm cache is warm and the registry is only consulted for metadata, so install wall time is indicative, not a network benchmark.

Metric baseline v4.0.0-alpha.1 head v4.0.0-alpha.1 Delta
Direct dependencies of @nuxt/cli 21 21 0.0%
Packages in the installed tree (unique name@version) 35 35 0.0%
Unique package names 34 34 0.0%
Package directories on disk (cross-check) 29 29 0.0%
Installed node_modules on disk 2.49 MB 2.49 MB -0.0%
Installed files 448 448 0.0%
Install wall time (warm npm cache, median of 3) 672 ms 668 ms -0.6%
Published tarball (packed) 286.8 kB 286.9 kB +0.0%
Published tarball (unpacked) 989.7 kB 989.7 kB 0.0%
Files in tarball 132 132 0.0%

Interleaved runs on a shared runner: trust the deltas, not the absolute timings. The dev, restart and build suites run locally via pnpm bench:cli.

@codspeed-hq

codspeed-hq Bot commented Sep 10, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 2 untouched benchmarks


Comparing userquin:fix-quit-timeout-on-windows (dc2c208) with main (31dda97)1

Open in CodSpeed

Footnotes

  1. No successful run was found on main (fbc6ac0) during the generation of this report, so 31dda97 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@codecov-commenter

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 0% with 3 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (main@fbc6ac0). Learn more about missing BASE report.

Files with missing lines Patch % Lines
packages/nuxt-cli/src/dev/utils.ts 0.00% 2 Missing and 1 partial ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main    #1532   +/-   ##
=======================================
  Coverage        ?   82.86%           
=======================================
  Files           ?      161           
  Lines           ?    10296           
  Branches        ?     2997           
=======================================
  Hits            ?     8532           
  Misses          ?     1496           
  Partials        ?      268           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@userquin

userquin commented Sep 10, 2026

Copy link
Copy Markdown
Member Author

Nuxt cli timeout: there is a bug at nitropack (nitro v2.13.4), when closing the worker, if there is a timeout, the caller will hang forever, in this case, nuxt cli will timeout with its default (15 seconds), missing resolve call on setTimeout at #closeWorker.

The #closeWorker is fine at nitro v3.

Patching nitropack on my local node_modules calling the resolve method inside the setTimeout:
teemirnal showing nuxt cli closes gracefully with the nitropack patch but with 5 seconds of time

@userquin

userquin commented Sep 10, 2026

Copy link
Copy Markdown
Member Author

Found the issue, it is a dead lock with nitropack 🥳 🥳 🥳

@userquin

userquin commented Sep 10, 2026

Copy link
Copy Markdown
Member Author

The dead lock

When shutting down the dev server (nitro dev-server at nitropack), the process often delays for 5 seconds until it throws WARN force closing dev worker..., ultimately failing to gracefully close the IPC sockets.

Root Cause

There is a deadlock between the dev worker and the parent proxy during shutdown.
Inside the dev worker's shutdown sequence, it executes await new Promise((resolve) => listener?.close(resolve)). The listener.close() method waits for all active connections to drain before resolving. However, the parent process holds keep-alive/upgrade connections open and waits for the worker to send the { event: "exit" } message before destroying its proxy sockets.

  • The worker waits for the parent to disconnect.
  • The parent waits for the worker to send "exit".

This results in an infinite loop that only breaks when the parent's 5-second graceful shutdown timeout forcefully terminates the worker thread.

The Fix at nitropack

By calling listener?.close() asynchronously without wrapping it in an await Promise, the shutdown sequence proceeds and immediately sends the "exit" event to the parent. The parent receives the signal, successfully closes the proxy connections, and the listener naturally drains and closes in milliseconds. This completely eliminates the 5-second delay and prevents orphaned resources.

Changes at nitro-dev.ts shutdown

async function shutdown() {
  server.closeAllConnections?.();
  listener?.close();
  await nitroApp.hooks.callHook("close").catch(console.error);
  parentPort?.postMessage({ event: "exit" });
}

Applied these changes on my local:

async function shutdown() {
  console.log('🛑 [Worker] Shutdown started...');
  server.closeAllConnections?.();
  listener?.close();
  await nitroApp.hooks.callHook("close").then(() => console.log('🛑 [Worker] Nitro hooks called')).catch(console.error);
  console.log('🛑 [Worker] Sending "exit" event to the parent...');
  parentPort?.postMessage({ event: "exit" });
}
Nuxt cli quit correctly

@userquin
userquin marked this pull request as ready for review September 10, 2026 22:20
@userquin
userquin requested a review from danielroe as a code owner September 10, 2026 22:20
@coderabbitai

coderabbitai Bot commented Sep 10, 2026

Copy link
Copy Markdown

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: b948195f-8eeb-4430-8731-39f65c10e3dc

📥 Commits

Reviewing files that changed from the base of the PR and between fbc6ac0 and dc2c208.

📒 Files selected for processing (1)
  • packages/nuxt-cli/src/dev/utils.ts

Included review availability: Your plan provides up to 2 included reviews per hour; 1 remains after this review.


📝 Walkthrough

Walkthrough

The development server now tracks each WebSocket immediately after acceptance. The tracking occurs before Vite HMR filtering and Nitro upgrade handling. Each socket is removed from the tracking set when it closes, so shutdown cleanup can destroy connections that bypass request handling.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Severity of issue fixed: Medium

Merge Risk: ⚪ Minimal · up to dc2c2

The shutdown connection-tracking change has no remaining actionable risk identified.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The change satisfies issue #1531 by tracking accepted Vite HMR WebSocket connections before filtering, then removing them on close so shutdown can close them.
Out of Scope Changes check ✅ Passed The changed code is limited to WebSocket connection tracking in the development shutdown flow and is related to issue #1531.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 1…
Title check ✅ Passed The title clearly and concisely describes the main change: fixing the Windows shutdown hang in the dev command. It also follows the conventional commit format.
Description check ✅ Passed The description is related to the changeset. It explains the Vite HMR connection tracking and links the work to issue #1531 and shutdown deadlocks.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@userquin

userquin commented Sep 10, 2026

Copy link
Copy Markdown
Member Author

The changes in this PR won't be necessary if nitropack change shutdown logic:

nuxt cli quit command without changes in this pr but nitrpack patched

@userquin
userquin marked this pull request as draft September 10, 2026 22:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

alpha version: quit command hangs/timeouts on Windows 10 PRO

2 participants