Summary
Follow-up to #15466 / #15485. #startLoopbackServer now rejects bind failures, but two related cleanup gaps remain in Miniflare listeners:
- After a successful
listen(), the one-shot error reject handler is left attached.
- After a failed
listen(), the http.Server is never close()d.
Hyperdrive already handles (1) correctly. The inspector proxy has the same leftover-handler pattern as loopback.
Source evidence
Hyperdrive removes the startup reject handler once the port is known:
server.once("error", reject);
server.listen(0, "127.0.0.1", () => {
server.off("error", reject);
// ...
});
packages/miniflare/src/plugins/hyperdrive/hyperdrive-proxy.ts (HyperdriveProxyController.createProxyServer)
Loopback (after #15485) and inspector do not:
packages/miniflare/src/index.ts — #startLoopbackServer: server.once("error", reject) then server.listen(...) with no off and no close() on reject.
packages/miniflare/src/plugins/core/inspector-proxy/inspector-proxy-controller.ts — #startListening: server.once("error", reject) then server.listen(...) with no off.
Why this matters
- A later
error on the loopback/inspector server is consumed by the leftover once("error", reject) callback. reject is a no-op on an already-settled promise, so the error is swallowed instead of being logged or triggering a restart.
- A failed bind still constructs an
http.Server. Without close(), that object can linger until GC even though it never became #loopbackServer.
Expected behavior
- On successful listen:
server.off("error", reject) (match Hyperdrive).
- On listen error:
close() the server, then reject with the original error.
- Apply the same pattern in
#startLoopbackServer and inspector #startListening.
Suggested test
Extend the existing 192.0.2.1 bind-failure test to assert the failed server does not remain listening, and add a unit-level check that a post-ready server error is not swallowed by the startup reject handler.
Related: #15466, #15485
Summary
Follow-up to #15466 / #15485.
#startLoopbackServernow rejects bind failures, but two related cleanup gaps remain in Miniflare listeners:listen(), the one-shoterrorreject handler is left attached.listen(), thehttp.Serveris neverclose()d.Hyperdrive already handles (1) correctly. The inspector proxy has the same leftover-handler pattern as loopback.
Source evidence
Hyperdrive removes the startup reject handler once the port is known:
packages/miniflare/src/plugins/hyperdrive/hyperdrive-proxy.ts(HyperdriveProxyController.createProxyServer)Loopback (after #15485) and inspector do not:
packages/miniflare/src/index.ts—#startLoopbackServer:server.once("error", reject)thenserver.listen(...)with nooffand noclose()on reject.packages/miniflare/src/plugins/core/inspector-proxy/inspector-proxy-controller.ts—#startListening:server.once("error", reject)thenserver.listen(...)with nooff.Why this matters
erroron the loopback/inspector server is consumed by the leftoveronce("error", reject)callback.rejectis a no-op on an already-settled promise, so the error is swallowed instead of being logged or triggering a restart.http.Server. Withoutclose(), that object can linger until GC even though it never became#loopbackServer.Expected behavior
server.off("error", reject)(match Hyperdrive).close()the server, then reject with the original error.#startLoopbackServerand inspector#startListening.Suggested test
Extend the existing
192.0.2.1bind-failure test to assert the failed server does not remain listening, and add a unit-level check that a post-ready servererroris not swallowed by the startup reject handler.Related: #15466, #15485