Skip to content

fix(miniflare): disable keep-alive timeout on the loopback server - #14850

Open
exKAZUu wants to merge 4 commits into
cloudflare:mainfrom
exKAZUu:fix-loopback-keepalive-timeout
Open

fix(miniflare): disable keep-alive timeout on the loopback server#14850
exKAZUu wants to merge 4 commits into
cloudflare:mainfrom
exKAZUu:fix-loopback-keepalive-timeout

Conversation

@exKAZUu

@exKAZUu exKAZUu commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Fixes #14848.

The loopback server was a plain node:http server with default timeouts, so Node closed idle keep-alive sockets after the default server.keepAliveTimeout of 5 seconds. workerd pools and reuses connections to the loopback server, so this raced with connection reuse: Node closes an idle pooled socket (clean FIN) just as workerd sends the next request on it, and that request fails inside the Worker with Error: Network connection lost..

This change disables keepAliveTimeout on the loopback server, mirroring the undici pools used for dispatch in the opposite direction, which already disable their timeouts with a "Disable timeouts for local dev" comment.

Full analysis and evidence in #14848. Summary: with @cloudflare/vite-plugin and a large SSR module graph on a cold optimizer cache (~3,000 fetchModule loopback calls per page load with multi-second idle gaps), 2/3 cold starts failed with Network connection lost on stock Miniflare, while 4/4 succeeded with this change applied; the Node side logs no socket error on the failing runs, consistent with a clean keep-alive close losing the reuse race.


  • Tests
    • Tests included/updated: a deterministic regression test opens a raw keep-alive connection to the loopback server, idles past Node's default keepAliveTimeout (5s), and asserts a second request on the same socket still succeeds. Without the fix, Node deterministically closes the idle socket at ~5s and the test fails. The original end-to-end race was also manually verified via the A/B experiment in [miniflare] Loopback server uses Node's default 5s keepAliveTimeout, causing intermittent "Network connection lost" in workerd #14848 (patched Miniflare 4/4 cold-start successes vs 2/3 failures on stock; reproduced under both Node 24 and Bun).
    • Automated tests not possible - manual testing has been completed as follows:
    • Additional testing not necessary because:
  • Public documentation
    • Cloudflare docs PR(s):
    • Documentation not necessary because: dev-only internal transport fix with no user-facing API or behavior change other than removing spurious failures.

Open in Devin Review

workerd pools and reuses connections to the loopback server, and Node's
default keepAliveTimeout (5s) races with that reuse: Node closes an idle
pooled socket just as workerd sends the next request on it, which fails
inside the Worker with "Network connection lost". Disable the idle
timeouts, mirroring the undici dispatch pools in the opposite direction.

Fixes cloudflare#14848
@exKAZUu
exKAZUu requested a review from workers-devprod as a code owner July 26, 2026 01:50
@changeset-bot

changeset-bot Bot commented Jul 26, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 5a5103e

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 8 packages
Name Type
miniflare Patch
@cloudflare/deploy-helpers Patch
@cloudflare/pages-shared Patch
@cloudflare/remote-bindings Patch
@cloudflare/runtime-types Patch
@cloudflare/vite-plugin Patch
@cloudflare/vitest-pool-workers Patch
wrangler Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@workers-devprod
workers-devprod requested review from a team and NuroDev and removed request for a team July 26, 2026 01:50
@workers-devprod

workers-devprod commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Codeowners approval required for this PR:

  • ✅ @cloudflare/wrangler
Show detailed file reviewers

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Open in Devin Review

@NuroDev NuroDev left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we add a deterministic regression test for this?

Also, is headersTimeout = 0 necessary? That controls how long clients may take to send request headers & is unrelated to idle keep-alive connections.

…ession test

headersTimeout only limits how long a client may take to send request
headers; it does not apply to idle keep-alive sockets, so it is not
needed for this fix. Verified empirically: with keepAliveTimeout = 0 and
a short headersTimeout, an idle keep-alive socket survives past the
headersTimeout and still serves a second request.

The new test talks to the loopback server over a raw keep-alive socket,
idles past Node's default keepAliveTimeout of 5 seconds, and asserts a
second request still succeeds. Without the fix the socket is closed
deterministically at ~5s and the test fails.
@exKAZUu

exKAZUu commented Jul 27, 2026

Copy link
Copy Markdown
Contributor Author

@NuroDev Thanks for the review — both points addressed.

Regression test: Added a deterministic one in packages/miniflare/test/index.spec.ts. It opens a raw keep-alive connection to the loopback server, sends a request, idles past Node's default keepAliveTimeout (5s), and asserts a second request on the same socket still succeeds. This doesn't depend on the workerd reuse race: without the fix, Node closes the idle socket at ~5s on a timer, so the test fails deterministically (verified locally in both directions: fails in ~7s without the fix, passes with it).

headersTimeout: You're right — it limits how long a client may take to send request headers and doesn't govern idle keep-alive sockets, so it isn't needed for this fix. I confirmed empirically that with keepAliveTimeout = 0 and a deliberately short headersTimeout, an idle keep-alive socket survives past the headersTimeout and still serves a second request. Dropped it (I'd originally included it only to mirror the undici pools on the dispatch side,which disable their headersTimeout/bodyTimeout).

@exKAZUu
exKAZUu requested a review from NuroDev July 28, 2026 02:51
@edmundhung
edmundhung requested review from edmundhung and removed request for NuroDev July 28, 2026 15:22
@github-project-automation github-project-automation Bot moved this to In Review in workers-sdk Jul 28, 2026
@pkg-pr-new

pkg-pr-new Bot commented Jul 28, 2026

Copy link
Copy Markdown
@cloudflare/autoconfig

npm i https://pkg.pr.new/@cloudflare/autoconfig@14850

@cloudflare/config

npm i https://pkg.pr.new/@cloudflare/config@14850

create-cloudflare

npm i https://pkg.pr.new/create-cloudflare@14850

@cloudflare/deploy-helpers

npm i https://pkg.pr.new/@cloudflare/deploy-helpers@14850

@cloudflare/kv-asset-handler

npm i https://pkg.pr.new/@cloudflare/kv-asset-handler@14850

miniflare

npm i https://pkg.pr.new/miniflare@14850

@cloudflare/pages-shared

npm i https://pkg.pr.new/@cloudflare/pages-shared@14850

@cloudflare/unenv-preset

npm i https://pkg.pr.new/@cloudflare/unenv-preset@14850

@cloudflare/vite-plugin

npm i https://pkg.pr.new/@cloudflare/vite-plugin@14850

@cloudflare/vitest-pool-workers

npm i https://pkg.pr.new/@cloudflare/vitest-pool-workers@14850

@cloudflare/workers-auth

npm i https://pkg.pr.new/@cloudflare/workers-auth@14850

@cloudflare/workers-editor-shared

npm i https://pkg.pr.new/@cloudflare/workers-editor-shared@14850

@cloudflare/workers-utils

npm i https://pkg.pr.new/@cloudflare/workers-utils@14850

wrangler

npm i https://pkg.pr.new/wrangler@14850

commit: 5a5103e

Comment on lines +274 to +278
script: `addEventListener("fetch", (event) => {
event.respondWith(new Response("<p>👋</p>", {
headers: { "Content-Type": "text/html;charset=utf-8" }
}));
})`,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit pick: We could probably make this a module script instead of a service worker. EG: export default { fetch: () => {...} };

@workers-devprod workers-devprod left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Codeowners reviews satisfied

@github-project-automation github-project-automation Bot moved this from In Review to Approved in workers-sdk Jul 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Approved

Development

Successfully merging this pull request may close these issues.

[miniflare] Loopback server uses Node's default 5s keepAliveTimeout, causing intermittent "Network connection lost" in workerd

3 participants