From 46bacd070ca7fd0ddfd5ffe081fdd068dcafbf81 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Wed, 15 Apr 2026 16:30:14 +0100 Subject: [PATCH] fix(miniflare): close WebSocket servers during dispose --- .changeset/fix-close-websocket-servers-dispose.md | 7 +++++++ packages/miniflare/src/index.ts | 11 +++++++++++ 2 files changed, 18 insertions(+) create mode 100644 .changeset/fix-close-websocket-servers-dispose.md diff --git a/.changeset/fix-close-websocket-servers-dispose.md b/.changeset/fix-close-websocket-servers-dispose.md new file mode 100644 index 0000000000..e0722305c4 --- /dev/null +++ b/.changeset/fix-close-websocket-servers-dispose.md @@ -0,0 +1,7 @@ +--- +"miniflare": patch +--- + +Close WebSocket servers during dispose to prevent lingering connections + +The `#liveReloadServer` and `#webSocketServer` (both created with `noServer: true`) were never explicitly closed during `Miniflare.dispose()`. Connected WebSocket clients (e.g., browser live reload, devtools) would keep sockets alive, preventing the Node.js event loop from draining cleanly. diff --git a/packages/miniflare/src/index.ts b/packages/miniflare/src/index.ts index 20b433349b..a218148bfc 100644 --- a/packages/miniflare/src/index.ts +++ b/packages/miniflare/src/index.ts @@ -3030,6 +3030,17 @@ export class Miniflare { await this.#runtime?.dispose(); await this.#stopLoopbackServer(); + // Terminate connected WebSocket clients and close the WebSocket servers. + // These are created with `noServer: true` so they aren't closed by + // stopping the loopback HTTP server. + for (const ws of this.#liveReloadServer.clients) { + ws.terminate(); + } + this.#liveReloadServer.close(); + for (const ws of this.#webSocketServer.clients) { + ws.terminate(); + } + this.#webSocketServer.close(); // Best-effort cleanup: on Windows, workerd may not release file handles // immediately after disposal, causing EBUSY errors. The temp directory // lives in os.tmpdir() so the OS will clean it up eventually.