#7725 miniflare.dispatchFetch() buffers streaming responses
Bug confirmed on miniflare 4.20260120.0. Related to #8004 (same root cause). PR #9625 in progress but doesn't reference this issue. Compression inference causes buffering.
Add #7725 to PR #9625 fixes list; optionally comment with workaround
Analysis Report
Issue Review: cloudflare/workers-sdk#7725
Summary
miniflare.dispatchFetch() buffers streaming responses, delivering all chunks at once when the stream closes instead of progressively as they are enqueued.
Findings
- Created: 2025-01-10
- Updated: 2025-10-30
- Version: 3.20241230.0 → 4.20260120.0 (current)
- Component: Miniflare
- Labels: bug, miniflare
- Comments: 0
Key Evidence
Bug reproduced with latest Miniflare (4.20260120.0): Worker enqueues 3 chunks 500ms apart, but client receives all chunks as a single read after stream closes (~1700ms total).
Related issues exist:
- #8004 "miniflare aggressively buffers responses" - Same root cause, has detailed analysis
- #6577 "
wrangler devdoesn't stream compressed response while deployed workers do"
Root cause identified by @petebacondarwin in #8004:
- Miniflare's entry worker attempts to compress responses based on
Accept-Encodingheader - workerd's compression algorithms (gzip, br) don't support streaming
- Response is buffered for compression, breaking streaming behavior
- Miniflare's entry worker attempts to compress responses based on
PR #9625 in progress: "fix: streamed response bodies in Miniflare should not infer non-streaming compression by default" - adds
inferContentEncodingoption (default: false)Issue #7725 not referenced in PR #9625 - The fix PR mentions #8004 and #6577 but not this issue.
Reproduction Results
Testing dispatchFetch() streaming behavior...
[worker] enqueued chunk 1 at 1769200167552
[204ms] Response received, status: 200
[...] Reading chunks...
[worker] enqueued chunk 2 at 1769200168044
[worker] enqueued chunk 3 at 1769200168544
[worker] stream closed at 1769200169045
[1691ms] Client received: "chunk 1\nchunk 2\nchunk 3"
--- Summary ---
Total chunks received: 1
Chunk arrival times: 1691ms
❌ BUG CONFIRMED: All chunks delivered as single read
Expected: 3 separate chunks ~500ms apart
Recommendation
Status: KEEP OPEN
Reasoning: This is a valid, reproducible bug that affects users testing streaming responses with dispatchFetch(). A fix is in progress (PR #9625) but this specific issue isn't referenced. The issue should remain open until the fix is merged and released.
Action:
- Add #7725 to PR #9625's "Fixes" list so it auto-closes when merged
- Optionally add a comment linking to PR #9625 so the reporter knows a fix is coming
Suggested Comment
This issue is related to the same root cause as #8004. A fix is in progress at PR #9625 which adds an
inferContentEncodingoption (default:false) to prevent Miniflare from inferring compression that doesn't support streaming.Workaround: Set
Content-Encoding: identityon your response headers to disable compression inference.
Root Cause Analysis
The buffering occurs due to Miniflare's response compression logic in the entry worker:
File: packages/miniflare/src/workers/core/entry.worker.ts:230-294
The ensureAcceptableEncoding() function checks the Accept-Encoding request header and attempts to compress the response if:
- The response has no
Content-Encodingheader set - The content type is in the list of types Cloudflare FL would compress (includes
text/plain,text/html, etc.) - The client accepts
gziporbrencoding
When compression is applied, workerd uses algorithms that don't support streaming, causing the entire response to be buffered before being sent to the client.
Key code path:
// entry.worker.ts:269-282
for (const encoding of encodings) {
if (encoding.weight === 0) {
if (encoding.coding === "identity" || encoding.coding === "*") {
identityDisallowed = true;
}
} else if (encoding.coding === "gzip" || encoding.coding === "br") {
// If the client accepts one of our supported encodings, use that
desiredEncoding = encoding.coding;
break;
} else if (encoding.coding === "identity") {
break;
}
}
When desiredEncoding is gzip or br, the response headers are modified:
// entry.worker.ts:291
response.headers.set("Content-Encoding", desiredEncoding);
This triggers workerd's compression which buffers the entire body.
Proposed Solution
PR #9625 implements the correct fix by making compression inference opt-in:
- Add
inferContentEncodingoption to Miniflare (default:false) - Only run
ensureAcceptableEncoding()when the option istrue
Changes needed in packages/miniflare/src/index.ts:
// Add to MiniflareOptions interface
inferContentEncoding?: boolean;
// Pass to entry worker config
JSON_INFER_CONTENT_ENCODING: options.inferContentEncoding ?? false,
Changes needed in packages/miniflare/src/workers/core/entry.worker.ts:
// Only infer encoding if explicitly enabled
if (env[CoreBindings.JSON_INFER_CONTENT_ENCODING]) {
response = ensureAcceptableEncoding(clientAcceptEncoding, response);
}
Implementation Difficulty: Easy
Justification:
- Fix is already implemented in PR #9625
- Change is minimal (~10 lines of code)
- Well-defined scope with no breaking changes (opt-in behavior)
- Tests already written in the PR
Files to Modify
packages/miniflare/src/index.ts- Add option and pass to worker configpackages/miniflare/src/workers/core/entry.worker.ts- Conditionally apply encodingpackages/miniflare/src/workers/core/constants.ts- Add binding constant
Testing Recommendations
- Unit test: Verify streaming responses are not buffered with
inferContentEncoding: false(default) - Unit test: Verify compression still works with
inferContentEncoding: true - E2E test: Test
dispatchFetch()with streaming worker returns chunks progressively - E2E test: Test
wrangler devwith streaming responses via HTTP
Notes & Feedback (0)
No notes yet.