Skip to content

Instantly share code, notes, and snippets.

@danieltroger
Created April 20, 2026 15:14
Show Gist options
  • Select an option

  • Save danieltroger/d8bbdafd3d92d93b4d82cb29f0774147 to your computer and use it in GitHub Desktop.

Select an option

Save danieltroger/d8bbdafd3d92d93b4d82cb29f0774147 to your computer and use it in GitHub Desktop.
Repro: wrangler dev does not enforce the 6 simultaneous open connections limit

Repro: wrangler dev does not enforce the 6 simultaneous open connections limit

Per the Workers Platform Limits docs:

Each Worker invocation can have up to six connections simultaneously waiting for response headers. ... If a seventh connection is attempted while six are already waiting for headers, it is queued until one of the existing connections receives its response headers.

In production this cap is enforced (six in‑flight subrequests). In wrangler dev / workerd locally, it is not: the Worker can have arbitrarily many in‑flight fetches.

Why it matters

Code that relies on the production clamp (or simply uses Promise.all(array.map(fetch)) for convenience) works fine in prod and misbehaves locally. Depending on the fetch target this can manifest as:

  • The dev server freezing to unrelated requests (event‑loop / IO starvation).
  • Host‑level ephemeral port exhaustion.
  • Out‑of‑memory errors as response bodies buffer.

The inverse mismatch is also possible: code tuned to run fast locally exceeds the 6‑cap in prod and hits the "cancel LRU subrequest to make room" behaviour documented in workerd#4471.

Repro

npm install

# Terminal A: local echo server that reports max concurrent in-flight requests
node echo-server.mjs 9192 1000

# Terminal B:
npx wrangler dev   # listens on localhost:9191

# Terminal C:
curl "http://localhost:9191/storm?n=100"
curl "http://localhost:9191/storm?n=500"
curl "http://localhost:9191/storm?n=2000"

Output observed on wrangler 4.84.0 / macOS:

{ "n": 100,  "ok": 100,  "maxInFlightObservedByTarget": 100,  "elapsedMs": 1022 }
{ "n": 500,  "ok": 500,  "maxInFlightObservedByTarget": 500,  "elapsedMs": 1108 }
{ "n": 2000, "ok": 2000, "maxInFlightObservedByTarget": 2000, "elapsedMs": 1454 }

The maxInFlightObservedByTarget field is the max concurrent in‑flight HTTP requests the target server saw. Deployed to Workers production, this value is 6.

Bonus: dev server freeze

Start the echo server with a 30s delay and fire a storm against an external HTTPS target:

node echo-server.mjs 9192 30000 &
# and/or change the target url in /storm to https://example.com
curl "http://localhost:9191/storm?n=5000&target=https://example.com" &
# while storm is in-flight, from another terminal:
curl --max-time 5 "http://localhost:9191/probe"

Probes against /probe (a trivial endpoint) time out for tens of seconds because workerd is saturated handling thousands of TCP/TLS connections. On macOS the host itself can also run out of ephemeral ports, breaking all outbound connections system‑wide until workerd is killed.

Requested behaviour

wrangler dev / workerd should clamp concurrent outgoing subrequests to the same six that prod enforces, so that:

  1. Code behaviour locally matches production.
  2. Accidental fan‑outs don't DoS the dev machine.

Ideally exposed as a limits.simultaneousConnections field analogous to the limits.subrequests that #11803 added, defaulting to 6 to match prod.

Related

Environment

  • wrangler 4.84.0
  • macOS 25.2.0 (Darwin, arm64)
  • Node 24.1.0
// HTTP server used as the fetch target in the repro.
// - Each request sleeps `delayMs` before responding.
// - Tracks simultaneous in-flight requests and exposes /stats so the worker
// can report exactly how many of its fetches were in flight at once.
//
// Usage: node echo-server.mjs [port=9192] [delayMs=1000]
import http from "node:http";
const port = Number(process.argv[2] ?? 9192);
const delayMs = Number(process.argv[3] ?? 1000);
let inFlight = 0;
let maxInFlight = 0;
const server = http.createServer((req, res) => {
if (req.url === "/stats") {
res.writeHead(200, { "content-type": "application/json" });
res.end(JSON.stringify({ inFlight, maxInFlight }));
return;
}
if (req.url === "/reset") {
maxInFlight = inFlight;
res.writeHead(200, { "content-type": "application/json" });
res.end(JSON.stringify({ reset: true, inFlight, maxInFlight }));
return;
}
inFlight++;
if (inFlight > maxInFlight) maxInFlight = inFlight;
setTimeout(() => {
inFlight--;
res.writeHead(200, { "content-type": "application/json" });
res.end(JSON.stringify({ ok: true }));
}, delayMs);
});
server.maxConnections = 100000;
server.listen(port, () => {
console.log(`echo-server on ${port}, delay=${delayMs}ms`);
});
// Minimal repro: wrangler dev does not enforce the "6 simultaneous open
// connections" limit that applies in production
// https://developers.cloudflare.com/workers/platform/limits/#simultaneous-open-connections
//
// Quote from the docs:
// "Each Worker invocation can have up to six connections simultaneously
// waiting for response headers. ... If a seventh connection is attempted
// while six are already waiting for headers, it is queued until one of the
// existing connections receives its response headers."
//
// This handler fires N concurrent fetch()es to a local echo server that
// tracks the maximum number of simultaneous in-flight requests it observed.
// Running this deployed to production reports maxInFlight=6. Running it
// against `wrangler dev` reports maxInFlight=N.
//
// Endpoints:
// GET /storm?n=100&target=http://localhost:9192/
// Fires N fetches in parallel, then queries target/stats.
// Returns { n, maxInFlightObservedByTarget, elapsedMs }.
//
// GET /probe
// Trivial endpoint. Run it in parallel with /storm to observe dev-server
// freezes: probes time out while /storm is in flight.
export default {
async fetch(req: Request): Promise<Response> {
const url = new URL(req.url);
if (url.pathname === "/probe") {
return Response.json({ ok: true, t: Date.now() });
}
if (url.pathname === "/storm") {
const n = Number(url.searchParams.get("n") ?? "100");
const target = url.searchParams.get("target") ?? "http://localhost:9192/";
const statsUrl = new URL("/stats", target).toString();
const resetUrl = new URL("/reset", target).toString();
await fetch(resetUrl).then(r => r.body?.cancel());
const started = performance.now();
const results = await Promise.allSettled(
Array.from({ length: n }, () =>
fetch(target).then(r => {
r.body?.cancel();
return r.status;
})
)
);
const elapsedMs = Math.round(performance.now() - started);
const stats = (await (await fetch(statsUrl)).json()) as {
maxInFlight: number;
};
const ok = results.filter(r => r.status === "fulfilled").length;
return Response.json({
n,
ok,
failed: n - ok,
maxInFlightObservedByTarget: stats.maxInFlight,
elapsedMs,
});
}
return Response.json(
{ error: "try /storm?n=100 or /probe" },
{ status: 404 }
);
},
};
{
"name": "cf-simul-connections-repro",
"version": "1.0.0",
"private": true,
"scripts": {
"start": "wrangler dev"
},
"devDependencies": {
"wrangler": "^4.83.0"
}
}
{
"name": "cf-simul-connections-repro",
"main": "src/index.ts",
"compatibility_date": "2026-04-15",
"dev": { "port": 9191 }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment