Workers SDK Issue Reports

← Back to Dashboard

#7725 miniflare.dispatchFetch() buffers streaming responses

Download Reproduction
Recommendation:KEEP OPEN
Difficulty:easy
Reasoning:

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.

Suggested Action:

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

  1. 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).

  2. Related issues exist:

    • #8004 "miniflare aggressively buffers responses" - Same root cause, has detailed analysis
    • #6577 "wrangler dev doesn't stream compressed response while deployed workers do"
  3. Root cause identified by @petebacondarwin in #8004:

    • Miniflare's entry worker attempts to compress responses based on Accept-Encoding header
    • workerd's compression algorithms (gzip, br) don't support streaming
    • Response is buffered for compression, breaking streaming behavior
  4. PR #9625 in progress: "fix: streamed response bodies in Miniflare should not infer non-streaming compression by default" - adds inferContentEncoding option (default: false)

  5. 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:

  1. Add #7725 to PR #9625's "Fixes" list so it auto-closes when merged
  2. 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 inferContentEncoding option (default: false) to prevent Miniflare from inferring compression that doesn't support streaming.

Workaround: Set Content-Encoding: identity on 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:

  1. The response has no Content-Encoding header set
  2. The content type is in the list of types Cloudflare FL would compress (includes text/plain, text/html, etc.)
  3. The client accepts gzip or br encoding

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:

  1. Add inferContentEncoding option to Miniflare (default: false)
  2. Only run ensureAcceptableEncoding() when the option is true

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

  1. packages/miniflare/src/index.ts - Add option and pass to worker config
  2. packages/miniflare/src/workers/core/entry.worker.ts - Conditionally apply encoding
  3. packages/miniflare/src/workers/core/constants.ts - Add binding constant

Testing Recommendations

  1. Unit test: Verify streaming responses are not buffered with inferContentEncoding: false (default)
  2. Unit test: Verify compression still works with inferContentEncoding: true
  3. E2E test: Test dispatchFetch() with streaming worker returns chunks progressively
  4. E2E test: Test wrangler dev with streaming responses via HTTP

Notes & Feedback (0)

No notes yet.

Add Note