Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .changeset/miniflare-dispose-cleanup-followup.md
Original file line number Diff line number Diff line change
@@ -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.
20 changes: 13 additions & 7 deletions packages/miniflare/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<void> {
if (this.#disposeController.signal.aborted) return;
Expand Down Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:")) {
Expand Down
Loading