Skip to content

Document compression middleware compatibility with streaming SSR#2544

Merged
AbanoubGhadban merged 1 commit intomasterfrom
worktree-docs-compression-middleware
Mar 6, 2026
Merged

Document compression middleware compatibility with streaming SSR#2544
AbanoubGhadban merged 1 commit intomasterfrom
worktree-docs-compression-middleware

Conversation

@AbanoubGhadban
Copy link
Copy Markdown
Collaborator

@AbanoubGhadban AbanoubGhadban commented Mar 6, 2026

Summary

  • Add "Compression Middleware Compatibility" section to both streaming SSR guides (open-source and Pro) explaining the Rack::Deflater/Rack::Brotli deadlock when :if conditions call body.each on streaming responses
  • Add troubleshooting entry to Pro troubleshooting docs for the "streaming hangs indefinitely" symptom
  • Add a note to the server rendering tips troubleshooting section with a cross-reference

The root cause is that streaming bodies use ActionController::Live::Buffer backed by a SizedQueue, where each is destructive (pops items). When a middleware :if condition calls body.each to check response size, it drains the queue, leaving nothing for the compressor. The fix is to check body.respond_to?(:to_ary) first — streaming bodies don't support to_ary, so the condition should return true (always compress) for them and only check size for buffered responses.

Closes #2519

Test plan

  • Documentation-only change, no code changes
  • Verified markdown renders correctly
  • Cross-references between docs use correct relative paths

🤖 Generated with Claude Code

Summary by CodeRabbit

Documentation

  • Added comprehensive guidance on compression middleware compatibility with streaming server-side rendering, including common deadlock scenarios and correct implementation patterns.
  • Enhanced troubleshooting documentation with new section addressing streaming server-side rendering request hangs and resolution steps.

Document the Rack::Deflater/Brotli deadlock when `:if` conditions call
`body.each` on streaming responses, and provide the correct `to_ary`
check pattern. Closes #2519.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 6, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ececa784-4052-495a-bee8-4db218633e06

📥 Commits

Reviewing files that changed from the base of the PR and between 687c4a1 and d78ab4b.

📒 Files selected for processing (4)
  • docs/building-features/streaming-server-rendering.md
  • docs/deployment/server-rendering-tips.md
  • react_on_rails_pro/docs/streaming-server-rendering.md
  • react_on_rails_pro/docs/troubleshooting.md

Walkthrough

Documentation updates addressing RSC payload streaming deadlock issues with Rack compression middleware. Adds guidance on compression middleware compatibility, explains the deadlock scenario caused by body.each consumption on streaming responses, and provides correct patterns with troubleshooting references across four documentation files.

Changes

Cohort / File(s) Summary
Compression Middleware Compatibility Guides
docs/building-features/streaming-server-rendering.md, react_on_rails_pro/docs/streaming-server-rendering.md
Added new "Compression Middleware Compatibility" sections explaining how streaming responses interact with Rack::Deflater and Rack::Brotli. Highlights deadlock risk when middleware :if conditions call body.each on streaming bodies. Provides correct pattern using body.respond_to?(:to_ary) check to handle both streaming and non-streaming responses appropriately.
Troubleshooting References
docs/deployment/server-rendering-tips.md, react_on_rails_pro/docs/troubleshooting.md
Added troubleshooting tips under server rendering sections identifying hanging streaming SSR requests. References compression middleware as potential cause and directs users to the Compression Middleware Compatibility guidance for diagnosis and resolution.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~3 minutes

Poem

🐰 When streams and compression collide,
The middleware's hunger can't be denied,
But check to_ary first, we say with care,
And deadlocks vanish—oh what a pair!
Documentation shines, the path is clear. ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely describes the primary change: documenting compression middleware compatibility with streaming SSR.
Linked Issues check ✅ Passed The PR fully addresses issue #2519 by documenting the Rack::Deflater/Brotli deadlock incompatibility and the correct to_ary check pattern across all relevant guides.
Out of Scope Changes check ✅ Passed All changes are directly scoped to documenting compression middleware compatibility with streaming responses; no unrelated changes are present.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch worktree-docs-compression-middleware

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Mar 6, 2026

Greptile Summary

This PR is a documentation-only change that adds a "Compression Middleware Compatibility" section to both the open-source and Pro streaming SSR guides, a troubleshooting entry in the Pro troubleshooting doc, and a cross-reference note in the general server-rendering tips page. It addresses a real, non-obvious deadlock (#2519) where compression middleware :if callbacks that call body.each destructively drain the ActionController::Live::Buffer SizedQueue, causing streaming responses to hang indefinitely.

Key additions:

  • Clear explanation of the SizedQueue semantics and why body.each is destructive for streaming bodies
  • A labeled BAD code snippet demonstrating the problematic pattern
  • A reference to the Rack SPEC to ground the explanation in a formal requirement
  • A correct pattern using body.respond_to?(:to_ary) as a guard, with an accurate explanation of why streaming bodies don't implement to_ary
  • Consistent cross-references between the four affected docs
  • One minor inconsistency: the Fix link in react_on_rails_pro/docs/troubleshooting.md omits the #compression-middleware-compatibility anchor that the parallel entry in docs/deployment/server-rendering-tips.md correctly includes

Confidence Score: 5/5

  • Documentation-only change that is safe to merge; technical guidance is accurate and the code snippets are correct.
  • All four files are Markdown documentation with no code changes. The Ruby code examples are technically sound: the |*, body| lambda signature is valid, body.respond_to?(:to_ary) is the correct Rack-idiomatic guard, return in a lambda is semantically correct, and body.to_ary.sum(&:bytesize) safely materialises buffered bodies without destructive side effects. The only finding is a missing section anchor in one cross-reference link, which is cosmetic.
  • No files require special attention; the single minor issue (missing anchor in react_on_rails_pro/docs/troubleshooting.md) is a one-line fix.

Important Files Changed

Filename Overview
docs/building-features/streaming-server-rendering.md Adds a new "Compression Middleware Compatibility" section explaining the SizedQueue deadlock, the BAD pattern using body.each, and the correct to_ary guard pattern. Content is technically accurate and well-placed before the "When to Use Streaming" section.
react_on_rails_pro/docs/streaming-server-rendering.md Identical "Compression Middleware Compatibility" section added to the Pro streaming guide, mirroring the open-source version. Content is correct and the insertion point (before "When to Use Streaming") is consistent with the OSS guide.
docs/deployment/server-rendering-tips.md Adds a step 3 to the troubleshooting list pointing to the new compression middleware section. Relative path ../building-features/streaming-server-rendering.md#compression-middleware-compatibility is correct and includes the section anchor.
react_on_rails_pro/docs/troubleshooting.md Adds a well-structured Symptom/Cause/Fix troubleshooting entry for the streaming deadlock. The Fix link is missing the #compression-middleware-compatibility anchor that the parallel entry in server-rendering-tips.md correctly includes.

Sequence Diagram

sequenceDiagram
    participant Browser
    participant RackMiddleware as Rack Compression Middleware
    participant SizedQueue as ActionController::Live::Buffer (SizedQueue)
    participant Compressor

    Note over RackMiddleware: BAD: :if condition calls body.each
    Browser->>RackMiddleware: HTTP Request (streaming page)
    RackMiddleware->>SizedQueue: body.each { |i| sum += i.length } — drains queue
    SizedQueue-->>RackMiddleware: All chunks consumed (queue empty)
    RackMiddleware->>Compressor: Attempt to compress remaining body
    Compressor->>SizedQueue: each (blocks waiting for chunks)
    Note over Compressor,SizedQueue: DEADLOCK — queue empty, no more chunks arrive

    Note over RackMiddleware: GOOD: :if condition checks to_ary first
    Browser->>RackMiddleware: HTTP Request (streaming page)
    RackMiddleware->>SizedQueue: body.respond_to?(:to_ary) → false
    RackMiddleware-->>Compressor: return true (always compress streaming body)
    loop Each streamed chunk
        SizedQueue-->>Compressor: chunk
        Compressor-->>Browser: compressed chunk (sync flush)
    end
Loading

Last reviewed commit: d78ab4b


**Cause**: A compression middleware (`Rack::Deflater`, `Rack::Brotli`) is configured with an `:if` condition that calls `body.each` to check the response size. This destructively consumes streaming chunks from the `SizedQueue`, causing a deadlock.

**Fix**: See the "Compression Middleware Compatibility" section in the [Streaming Server Rendering guide](./streaming-server-rendering.md).
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Missing section anchor in cross-reference link

The link here drops the user at the top of the streaming guide rather than directly at the relevant section. The corresponding link in docs/deployment/server-rendering-tips.md correctly uses the #compression-middleware-compatibility anchor. Adding it here keeps both cross-references consistent and saves the reader from having to scroll.

Suggested change
**Fix**: See the "Compression Middleware Compatibility" section in the [Streaming Server Rendering guide](./streaming-server-rendering.md).
**Fix**: See the "Compression Middleware Compatibility" section in the [Streaming Server Rendering guide](./streaming-server-rendering.md#compression-middleware-compatibility).

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Mar 6, 2026

Review: Documentation — Compression Middleware Compatibility with Streaming SSR

This is a well-written, technically accurate documentation addition that addresses a real and subtle deadlock issue. The explanation of the root cause (destructive SizedQueue + middleware :if consuming the body) is clear, and the fix using respond_to?(:to_ary) correctly targets the ActionController::Live::Buffer behaviour.

Positive aspects

  • The three-file structure (open-source streaming guide, Pro streaming guide, Pro troubleshooting) covers all the right entry points readers might hit.
  • The cross-references between docs use correct relative paths.
  • The "BAD vs GOOD" code pattern structure is easy to follow.
  • The Rack SPEC citation gives readers a standards-backed rationale, not just "trust us".

Issues

Minor (documentation clarity)

  1. i.length vs i.bytesize in the BAD example — The good example correctly uses bytesize, but the bad example uses .length (character count). For pure ASCII bodies these are equivalent, but for multibyte content they differ. Since the only thing that should be "bad" about the bad example is the destructive body.each call, using i.bytesize there too would isolate the single anti-pattern rather than silently introducing a second, unrelated mistake. A reader doing a line-by-line diff might miss the lengthbytesize change entirely and assume both are equivalent.

  2. |*, body| lambda signature — This is valid Ruby, but it's non-obvious that the middleware passes (env, status, headers, body) as four positional arguments. A one-line comment like # Rack calls the :if proc with (env, status, headers, body) (or expanding the signature to |_env, _status, _headers, body|) would help readers adapt the snippet to their own codebase without needing to dig into Rack source.

  3. "Works correctly with streaming by default" caveat — This claim is accurate for modern Rack (Rack::Deflater uses Z_SYNC_FLUSH per chunk), but older Rack versions buffered the full response before deflating. A version footnote (e.g., "Rack ≥ 2.x") or a brief qualifier ("in modern Rack") would prevent confusion for users on older stacks.

Non-issues / confirmed correct

  • respond_to?(:to_ary) is the right guard: ActionController::Live::Buffer deliberately omits to_ary.
  • body.to_ary.sum(&:bytesize) for buffered bodies is fine — it materialises the array once for the size check, which is acceptable because buffered responses are already fully in memory.
  • The troubleshooting entry in server-rendering-tips.md correctly carries the item numbering forward (1 → 2 → 3).
  • The relative path ../building-features/streaming-server-rendering.md#compression-middleware-compatibility from docs/deployment/ is correct.

Overall this is a valuable addition. Addressing the i.length / i.bytesize inconsistency would be the most impactful change before merge.

# BAD — causes deadlocks with streaming responses
config.middleware.use Rack::Deflater, if: lambda { |*, body|
sum = 0
body.each { |i| sum += i.length } # destructive — drains the queue
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.

The bad example uses i.length (character count) while the good example uses i.bytesize (byte count). Since the only anti-pattern here is the destructive body.each call, keeping the measurement consistent (bytesize in both) would isolate the single mistake and make the before/after diff clearer for readers.

Suggested change
body.each { |i| sum += i.length } # destructive — drains the queue
body.each { |i| sum += i.bytesize } # destructive — drains the queue

sum = 0
body.each { |i| sum += i.length } # destructive — drains the queue
sum > 512
}
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.

Same i.length vs i.bytesize inconsistency as in the open-source streaming guide — using bytesize here too would keep the BAD/GOOD examples focused on the single anti-pattern (destructive iteration).

Suggested change
}
body.each { |i| sum += i.bytesize } # destructive — drains the queue

@AbanoubGhadban AbanoubGhadban merged commit f5a3945 into master Mar 6, 2026
15 checks passed
@AbanoubGhadban AbanoubGhadban deleted the worktree-docs-compression-middleware branch March 6, 2026 11:38
AbanoubGhadban added a commit that referenced this pull request Mar 7, 2026
## Summary
- Add "Compression Middleware Compatibility" section to both streaming
SSR guides (open-source and Pro) explaining the
`Rack::Deflater`/`Rack::Brotli` deadlock when `:if` conditions call
`body.each` on streaming responses
- Add troubleshooting entry to Pro troubleshooting docs for the
"streaming hangs indefinitely" symptom
- Add a note to the server rendering tips troubleshooting section with a
cross-reference

The root cause is that streaming bodies use
`ActionController::Live::Buffer` backed by a `SizedQueue`, where `each`
is destructive (pops items). When a middleware `:if` condition calls
`body.each` to check response size, it drains the queue, leaving nothing
for the compressor. The fix is to check `body.respond_to?(:to_ary)`
first — streaming bodies don't support `to_ary`, so the condition should
return `true` (always compress) for them and only check size for
buffered responses.

Closes #2519

## Test plan
- [x] Documentation-only change, no code changes
- [x] Verified markdown renders correctly
- [x] Cross-references between docs use correct relative paths

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

## Documentation

* Added comprehensive guidance on compression middleware compatibility
with streaming server-side rendering, including common deadlock
scenarios and correct implementation patterns.
* Enhanced troubleshooting documentation with new section addressing
streaming server-side rendering request hangs and resolution steps.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
justin808 added a commit that referenced this pull request Mar 8, 2026
…upport

* origin/master: (38 commits)
  fix: use env-var-driven ports in Procfile templates to support multiple worktrees (#2539)
  Fix prettier formatting in auto-bundling doc
  Docs: Clarify .client/.server suffixes vs use client RSC directive (#2406)
  Warn against using .server/.client variants with RSC features
  Docs: move internal-only docs out of published docs trees (#2414)
  Fix crash when HTTPX::ErrorResponse is returned in get_form_body_for_file (#2532)
  Skip flaky external URLs in lychee checks (#2547)
  Update mise docs: prefer shell-level shims over conductor-exec (#2537)
  Document compression middleware compatibility with streaming SSR (#2544)
  Fix duplicate node-renderer message reporting in render failures (#2531)
  Fix private_output_path not configured on fresh Shakapacker installs (#2289)
  Bump the npm-security group across 1 directory with 3 updates (#2387)
  docs: use https links (#2518)
  Consolidate changelog to keep only rc6 prerelease (#2533)
  Fix CSS module class name divergence with rspack SSR (#2489)
  Bump version to 16.4.0.rc.6
  Add BugBot license scanning config (#2515)
  Fix buildVM promise cleanup ordering (#2483) (#2484)
  Fix streaming SSR hangs and silent error absorption in RSC payload injection (#2407)
  Ensure lefthook uses mise tools in non-interactive shells (#2512)
  ...

# Conflicts:
#	CHANGELOG.md
justin808 added a commit that referenced this pull request Mar 9, 2026
## Summary

- Add changelog entries for 6 user-visible PRs merged since v16.4.0.rc.6
that were missing from `[Unreleased]`
- Update existing #2561 entry to include #2568 contributor credit

### New entries added

| Section | PR | Description |
|---|---|---|
| Added | #2539 | Environment-variable-driven ports in Procfile
templates |
| Fixed | #2417 | Rspack generator config path fix |
| Fixed | #2419 | Precompile hook load-based execution fix |
| Fixed | #2577 | `create-react-on-rails-app` validation improvements |
| Pro Fixed | #2416 | StreamResponse status fallback fix |
| Pro Fixed | #2566 | Empty-string license plan mismatch fix |

### Skipped PRs (not user-visible)

Docs (#2406, #2414, #2479, #2494, #2518, #2537, #2544), CI/internal
(#2533, #2547, #2555, #2557, #2558, #2564), dependabot (#2387, #2541),
dev dependencies (#2559, #2569, #2573).

## Test plan

- [ ] Verify changelog formatting matches existing entries
- [ ] Verify all user-visible PRs since v16.4.0.rc.6 are covered

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Documentation-only changelog updates with no runtime or build behavior
changes.
> 
> **Overview**
> Updates `CHANGELOG.md`’s **[Unreleased]** section to include
previously missing user-facing entries: Procfile templates now support
env-driven ports, several generator/`bin/dev` precompile-hook and
rspack-path fixes are documented, and `create-react-on-rails-app`
validation improvements are noted.
> 
> Also adds two Pro fix entries (StreamResponse status fallback and
license plan empty-string validation) and updates the existing `bin/dev`
precompile-hook entry to credit an additional PR/contributor.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
e75d2b5. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

RSC payload streaming deadlocks with Rack::Deflater and Rack::Brotli compression middleware

1 participant