From 3412a90212f04aec89220b0ba7abe19832f8e169 Mon Sep 17 00:00:00 2001 From: silverwind Date: Mon, 11 May 2026 00:30:40 +0200 Subject: [PATCH 1/3] Drain undici dispatcher before exit to fix Node 24 Windows race MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Aborting the prewarms isn't enough — the keep-alive socket pool still has idle handles that race during natural shutdown on Windows + Node 24 (nodejs/node#56645). Closing the global dispatcher drains the pool cleanly, eliminating the race. Node exposes the global dispatcher via Symbol.for("undici.globalDispatcher.1"); this is the same mechanism undici itself uses to share it with the runtime. Co-Authored-By: Claude (Opus 4.7) --- index.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/index.ts b/index.ts index 63b0430..3d33feb 100755 --- a/index.ts +++ b/index.ts @@ -80,6 +80,10 @@ async function end(err?: Error | void, exitCode?: number): Promise { prewarmAbort.abort(); await Promise.all(prewarms); + // Drain undici's keep-alive pool to avoid a libuv async/close race during + // shutdown on Windows + Node 24 (nodejs/node#56645). + const dispatcher = (globalThis as any)[Symbol.for("undici.globalDispatcher.1")]; + if (dispatcher?.close) await dispatcher.close(); process.exitCode = exitCode ?? (err ? 1 : 0); } From df09be76969acc079d3cd86059466394aaacabe1 Mon Sep 17 00:00:00 2001 From: silverwind Date: Mon, 11 May 2026 00:34:36 +0200 Subject: [PATCH 2/3] Restore Windows exit delay and setBlocking workarounds The Symbol-based dispatcher.close workaround relied on a private Node API and didn't reliably fix the Node 24 Windows flake. Restoring the straightforward 100ms-on-Windows delay and the stdio setBlocking workaround is honest about the upstream Node bug and avoids reaching into internals. Refs https://github.com/nodejs/node/issues/56645 Co-Authored-By: Claude (Opus 4.7) --- index.ts | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/index.ts b/index.ts index 3d33feb..1d6c6b7 100755 --- a/index.ts +++ b/index.ts @@ -1,5 +1,5 @@ #!/usr/bin/env node -import {argv, cwd, stdout} from "node:process"; +import {argv, cwd, stdout, stderr, exit, platform, versions} from "node:process"; import {stripVTControlCharacters, styleText, parseArgs} from "node:util"; import {dirname, isAbsolute, resolve} from "node:path"; import {statSync} from "node:fs"; @@ -12,9 +12,7 @@ import {prewarmOrigins} from "./utils/prewarm.ts"; import type {Arg} from "./config.ts"; import type {Output, UpdatesOptions} from "./api.ts"; -const prewarmAbort = new AbortController(); -const prewarms = prewarmOrigins(cwd(), argv.slice(2)) - .map(url => fetch(url, {method: "HEAD", signal: prewarmAbort.signal}).catch(() => {})); +for (const url of prewarmOrigins(cwd(), argv.slice(2))) fetch(url, {method: "HEAD"}).catch(() => {}); const result = parseArgs({ strict: false, @@ -78,16 +76,19 @@ async function end(err?: Error | void, exitCode?: number): Promise { } } - prewarmAbort.abort(); - await Promise.all(prewarms); - // Drain undici's keep-alive pool to avoid a libuv async/close race during - // shutdown on Windows + Node 24 (nodejs/node#56645). - const dispatcher = (globalThis as any)[Symbol.for("undici.globalDispatcher.1")]; - if (dispatcher?.close) await dispatcher.close(); - process.exitCode = exitCode ?? (err ? 1 : 0); + // https://github.com/nodejs/node/issues/56645 + if (platform === "win32" && Number(versions?.node?.split(".")[0]) >= 23) { + await new Promise(resolve => setTimeout(resolve, 100)); + } + + exit(exitCode ?? (err ? 1 : 0)); } async function main(): Promise { + for (const stream of [stdout, stderr]) { + (stream as any)?._handle?.setBlocking?.(true); + } + const maxSockets = 25; if (args.help) { @@ -137,13 +138,11 @@ async function main(): Promise { $ updates -f docker-compose.yml `); await end(); - return; } if (args.version) { console.info(packageVersion); await end(); - return; } const fileSet = parseMixedArg(args.file); From b8b28d83498743a34ee24d5e6c0f553cd1cd228c Mon Sep 17 00:00:00 2001 From: silverwind Date: Mon, 11 May 2026 00:35:08 +0200 Subject: [PATCH 3/3] Bump Windows exit delay from 100ms to 200ms Refs https://github.com/nodejs/node/issues/56645 Co-Authored-By: Claude (Opus 4.7) --- index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.ts b/index.ts index 1d6c6b7..ff87223 100755 --- a/index.ts +++ b/index.ts @@ -78,7 +78,7 @@ async function end(err?: Error | void, exitCode?: number): Promise { // https://github.com/nodejs/node/issues/56645 if (platform === "win32" && Number(versions?.node?.split(".")[0]) >= 23) { - await new Promise(resolve => setTimeout(resolve, 100)); + await new Promise(resolve => setTimeout(resolve, 200)); } exit(exitCode ?? (err ? 1 : 0));