From dc5a13723fcc7a0b4f733fa8093dae2dbeb934c3 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Fri, 17 Apr 2026 16:19:02 +0100 Subject: [PATCH] [miniflare] Fix resource leaks during config updates - Only close/recreate devRegistryDispatcher when the port changes, matching the existing runtimeDispatcher pattern (fixes #13584) - Dispose old InspectorProxy instances before replacing them in updateConnection, preventing leaked WebSockets and keepalive timers (fixes #13585) --- .../miniflare-dispose-cleanup-followup.md | 10 ++++++++++ packages/miniflare/src/index.ts | 20 ++++++++++++------- .../inspector-proxy-controller.ts | 4 ++++ 3 files changed, 27 insertions(+), 7 deletions(-) create mode 100644 .changeset/miniflare-dispose-cleanup-followup.md diff --git a/.changeset/miniflare-dispose-cleanup-followup.md b/.changeset/miniflare-dispose-cleanup-followup.md new file mode 100644 index 0000000000..8aa0fb974f --- /dev/null +++ b/.changeset/miniflare-dispose-cleanup-followup.md @@ -0,0 +1,10 @@ +--- +"miniflare": patch +--- + +Fix resource leaks during config updates + +Two follow-up fixes to the dispose cleanup in #13515: + +- Only close and recreate the dev-registry dispatcher when its port actually changes, matching the existing `runtimeDispatcher` behavior. Previously, every config update unconditionally tore down and rebuilt the connection pool, which could cause brief request failures if a registry push was in-flight. +- Dispose old `InspectorProxy` instances before replacing them during `updateConnection()`. Previously, stale proxies were silently discarded, leaking their runtime WebSocket connections and 10-second keepalive interval timers. diff --git a/packages/miniflare/src/index.ts b/packages/miniflare/src/index.ts index f6d5a1a4bd..8a617b8559 100644 --- a/packages/miniflare/src/index.ts +++ b/packages/miniflare/src/index.ts @@ -1107,6 +1107,7 @@ export class Miniflare { * Called when the dev registry detects changes to external services. */ #devRegistryDispatcher?: Dispatcher; + #devRegistryPort?: number; async #pushRegistryUpdate(retries = 3): Promise { if (this.#disposeController.signal.aborted) return; @@ -2422,14 +2423,19 @@ export class Miniflare { // Set up a direct dispatcher to the dev-registry-proxy socket so we can // push registry updates without routing through the entry worker. + // Only close/recreate when the port actually changes, to avoid tearing + // down the connection pool while a #pushRegistryUpdate is in-flight. const devRegistryPort = maybeSocketPorts.get(SOCKET_DEV_REGISTRY); - void this.#devRegistryDispatcher?.close().catch(() => {}); - if (devRegistryPort !== undefined) { - this.#devRegistryDispatcher = new Pool( - new URL(`http://127.0.0.1:${devRegistryPort}`) - ); - } else { - this.#devRegistryDispatcher = undefined; + if (devRegistryPort !== this.#devRegistryPort) { + void this.#devRegistryDispatcher?.close().catch(() => {}); + this.#devRegistryPort = devRegistryPort; + if (devRegistryPort !== undefined) { + this.#devRegistryDispatcher = new Pool( + new URL(`http://127.0.0.1:${devRegistryPort}`) + ); + } else { + this.#devRegistryDispatcher = undefined; + } } if (this.#proxyClient === undefined) { this.#proxyClient = new ProxyClient( diff --git a/packages/miniflare/src/plugins/core/inspector-proxy/inspector-proxy-controller.ts b/packages/miniflare/src/plugins/core/inspector-proxy/inspector-proxy-controller.ts index 79d91b0d26..507f8d0a21 100644 --- a/packages/miniflare/src/plugins/core/inspector-proxy/inspector-proxy-controller.ts +++ b/packages/miniflare/src/plugins/core/inspector-proxy/inspector-proxy-controller.ts @@ -289,6 +289,10 @@ export class InspectorProxyController { id: string; }[]; + // Dispose old proxies before replacing them, so their runtime WebSocket + // connections and keepalive intervals are properly cleaned up. + await Promise.all(this.#proxies.map((proxy) => proxy.dispose())); + this.#proxies = workerdInspectorJson .map(({ id }) => { if (!id.startsWith("core:user:")) {