Fix streaming deadlock with Rack compression middleware#2529
Fix streaming deadlock with Rack compression middleware#2529
Conversation
Set Content-Encoding: identity on streaming responses to prevent Rack::Deflater and Rack::Brotli from iterating the Live::Buffer response body, which causes a deadlock with ActionController::Live. Fixes #2519 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
WalkthroughAdds explicit streaming compression handling to choose gzip or identity encoding and set Content-Encoding early. Introduces gzip-wrapping of the streaming writer to emit compressed chunks and append a gzip footer on close, avoiding deadlocks with Rack compression middleware. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant RackMiddleware as Rack::Deflater/Brotli
participant Controller as RscPayloadController
participant StreamWriter as Stream Writer / Live::Buffer
participant GzipWrapper as Gzip compressor
Client->>Controller: GET /rsc_payload/Component
Controller->>Controller: setup_streaming_compression (inspect Accept-Encoding)
alt client accepts gzip
Controller->>StreamWriter: wrap writer with GzipWrapper
Controller->>Controller: set response.headers["Content-Encoding"]="gzip"
else client does not accept gzip
Controller->>Controller: set response.headers["Content-Encoding"]="identity"
end
Controller->>StreamWriter: write chunks (possibly compressed by GzipWrapper)
StreamWriter-->>RackMiddleware: stream body chunks (middleware sees Content-Encoding and skips)
RackMiddleware-->>Client: forward streamed (pre-compressed or identity) chunks
Client->>RackMiddleware: receive streamed response
Controller->>GzipWrapper: close (Gzip footer appended) when done
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
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. Comment |
Review of PR #2529 — Fix streaming deadlock with Rack compression middlewareSummary: This is a targeted, minimal fix for a real deadlock issue. Setting CorrectnessThe fix is correct. Both # Rack::Deflater (simplified)
return [status, headers, body] if headers[CONTENT_ENCODING]Setting it to Issues1. No automated test coverage for the new behaviorThe existing Suggested approach in mocked_headers = {}
allow(mocked_response).to receive(:headers).and_return(mocked_headers)Then in a dedicated test: it 'sets Content-Encoding: identity to prevent compression middleware deadlocks' do
_queues, controller, _stream = setup_stream_test(component_count: 0)
run_stream(controller) { sleep 0.1 }
expect(controller.response.headers['Content-Encoding']).to eq('identity')
end2. Missing CHANGELOG entryThe 3.
|
| # the Live::Buffer response body, which causes a deadlock with ActionController::Live. | ||
| # Setting Content-Encoding before streaming tells middleware the response is already | ||
| # encoded, so they skip compression entirely. See https://github.com/shakacode/react_on_rails/issues/2519 | ||
| response.headers["Content-Encoding"] = "identity" |
There was a problem hiding this comment.
The fix is correct — setting any Content-Encoding value before the response body is written causes Rack::Deflater to skip compression. identity is the most semantically appropriate value ("no transformation applied").
One thing worth verifying: Rack::Brotli (a third-party gem) should behave the same way, but its source is less standardized than Rack::Deflater. Please confirm with the unchecked test plan item before merge.
Also, the existing stream_spec.rb mocks response but doesn't stub headers, so this line currently raises a NoMethodError in unit tests unless the mock is updated. Consider adding allow(mocked_response).to receive(:headers).and_return({}) to setup_stream_test and a dedicated test asserting this header is set.
- Add spec verifying Content-Encoding: identity is set on streaming responses to prevent compression middleware deadlock - Add changelog entry for the fix Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Greptile SummaryThis PR fixes a deadlock caused by The fix itself is minimal and the approach is correct for the described scenario. However, there are two issues worth addressing before merging:
Confidence Score: 2/5
Sequence DiagramsequenceDiagram
participant Client
participant RackBrotli as Rack::Brotli / Rack::Deflater
participant StreamConcern as Stream Concern
participant LiveBuffer as ActionController::Live::Buffer
Client->>RackBrotli: HTTP Request (Accept-Encoding: gzip/br)
RackBrotli->>StreamConcern: Forward request
StreamConcern->>StreamConcern: response.headers["Content-Encoding"] = "identity"
StreamConcern->>LiveBuffer: response.stream.write(template_string)
StreamConcern->>LiveBuffer: drain_streams_concurrently (chunks)
LiveBuffer-->>RackBrotli: Streaming response body
Note over RackBrotli: Detects Content-Encoding already set<br/>→ skips compression, passes body through
RackBrotli-->>Client: Streamed response (no deadlock)
Last reviewed commit: 4e77139 |
| # the Live::Buffer response body, which causes a deadlock with ActionController::Live. | ||
| # Setting Content-Encoding before streaming tells middleware the response is already | ||
| # encoded, so they skip compression entirely. See https://github.com/shakacode/react_on_rails/issues/2519 | ||
| response.headers["Content-Encoding"] = "identity" |
There was a problem hiding this comment.
The setup_stream_test helper in the spec file only stubs .stream on the mocked response, not .headers. This will cause all 7 existing tests that use setup_stream_test to fail with RSpec::Mocks::MockExpectationError: received unexpected message :headers when this line executes.
The test helper needs to add a stub for the headers:
| response.headers["Content-Encoding"] = "identity" | |
| def setup_stream_test(component_count: 2) | |
| component_queues = Array.new(component_count) { Async::Queue.new } | |
| controller = StreamController.new(component_queues: component_queues) | |
| mocked_response = instance_double(ActionController::Live::Response) | |
| mocked_stream = instance_double(ActionController::Live::Buffer) | |
| allow(mocked_response).to receive(:stream).and_return(mocked_stream) | |
| allow(mocked_response).to receive(:headers).and_return({}) | |
| allow(mocked_stream).to receive(:write) | |
| allow(mocked_stream).to receive(:close) | |
| allow(mocked_stream).to receive(:closed?).and_return(false) | |
| allow(controller).to receive(:response).and_return(mocked_response) | |
| [component_queues, controller, mocked_stream] | |
| end |
| # Prevent compression middleware (Rack::Deflater, Rack::Brotli) from iterating | ||
| # the Live::Buffer response body, which causes a deadlock with ActionController::Live. | ||
| # Setting Content-Encoding before streaming tells middleware the response is already | ||
| # encoded, so they skip compression entirely. See https://github.com/shakacode/react_on_rails/issues/2519 | ||
| response.headers["Content-Encoding"] = "identity" |
There was a problem hiding this comment.
The comment is slightly misleading. Content-Encoding: identity actually means no encoding has been applied (per RFC 7231), not that the response is already encoded. The reason this prevents compression is that Rack::Deflater and Rack::Brotli both skip compression when they detect any existing Content-Encoding header (presence check), regardless of value.
Consider updating the comment to reflect the actual mechanism:
| # Prevent compression middleware (Rack::Deflater, Rack::Brotli) from iterating | |
| # the Live::Buffer response body, which causes a deadlock with ActionController::Live. | |
| # Setting Content-Encoding before streaming tells middleware the response is already | |
| # encoded, so they skip compression entirely. See https://github.com/shakacode/react_on_rails/issues/2519 | |
| response.headers["Content-Encoding"] = "identity" | |
| # Prevent compression middleware (Rack::Deflater, Rack::Brotli) from iterating | |
| # the Live::Buffer response body, which causes a deadlock with ActionController::Live. | |
| # Both middlewares skip compression when Content-Encoding is already set (presence check). | |
| # "identity" signals no encoding has been applied, satisfying that check without implying | |
| # the body is pre-compressed. See https://github.com/shakacode/react_on_rails/issues/2519 | |
| response.headers["Content-Encoding"] = "identity" |
Review: Fix streaming deadlock with Rack compression middlewareThis is a clean, minimal, and correct fix for the deadlock described in #2519. Here's my assessment: Correctness ✅Setting The RSC payload endpoint ( Minor Observations1. Implicit override of any pre-existing response.headers["Content-Encoding"] = "identity"If a controller or upstream middleware has already set a # Overwrite any existing Content-Encoding — compression is incompatible with streaming.
response.headers["Content-Encoding"] = "identity"2. Test uses The new test follows the existing pattern in the file, so this is consistent. However, since the test only needs to verify a header set synchronously at the very start of 3. Changelog placement The new entry is placed correctly in the "Fixed" section under the in-progress release. SummaryThe fix is well-reasoned and properly targeted. The test coverage is appropriate, the comment in |
|
|
||
| run_stream(controller) do |_parent| | ||
| sleep 0.1 | ||
| end |
There was a problem hiding this comment.
The sleep 0.1 is not needed here — Content-Encoding: identity is set synchronously at the very start of stream_view_containing_react_components, before the Sync do block executes any async work. The test would pass with sleep 0 or even without any sleep. Not a bug, just a minor clarity point since the sleep implies the header might be set asynchronously.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@CHANGELOG.md`:
- Line 34: The changelog attribution on the RSC payload streaming fix entry has
a username/URL mismatch: the link URL points to "justin808" while the visible
label is "justin"; update the label to match the URL (use "justin808") so the
entry follows the required format "[PR
2529](https://github.com/shakacode/react_on_rails/pull/2529) by
[justin808](https://github.com/justin808)". Locate the changelog entry
containing "Fix RSC payload streaming deadlock with Rack compression middleware"
and change the displayed username to "justin808" to match the link.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: d01f4a79-561c-4fae-8bd1-deeff3062640
📒 Files selected for processing (2)
CHANGELOG.mdreact_on_rails_pro/spec/react_on_rails_pro/stream_spec.rb
CHANGELOG.md
Outdated
|
|
||
| ##### Fixed | ||
|
|
||
| - **Fix RSC payload streaming deadlock with Rack compression middleware**: The RSC payload endpoint (`/rsc_payload/:component_name`) would hang indefinitely when `Rack::Deflater` or `Rack::Brotli` compression middleware was present. Compression middleware attempted to iterate the `ActionController::Live` response body to check size, creating a deadlock. Now sets `Content-Encoding: identity` on all streaming responses to signal middleware to skip compression. [PR 2529](https://github.com/shakacode/react_on_rails/pull/2529) by [justin](https://github.com/justin808). |
There was a problem hiding this comment.
Fix username/link mismatch in changelog attribution.
On Line 34, the link points to justin808 but the label is justin. Use matching username text and URL.
Suggested fix
- ... [PR 2529](https://github.com/shakacode/react_on_rails/pull/2529) by [justin](https://github.com/justin808).
+ ... [PR 2529](https://github.com/shakacode/react_on_rails/pull/2529) by [justin808](https://github.com/justin808).As per coding guidelines: Format changelog entries as [PR <number>](https://github.com/shakacode/react_on_rails/pull/<number>) by [username](https://github.com/username) without a hash before the PR number.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| - **Fix RSC payload streaming deadlock with Rack compression middleware**: The RSC payload endpoint (`/rsc_payload/:component_name`) would hang indefinitely when `Rack::Deflater` or `Rack::Brotli` compression middleware was present. Compression middleware attempted to iterate the `ActionController::Live` response body to check size, creating a deadlock. Now sets `Content-Encoding: identity` on all streaming responses to signal middleware to skip compression. [PR 2529](https://github.com/shakacode/react_on_rails/pull/2529) by [justin](https://github.com/justin808). | |
| - **Fix RSC payload streaming deadlock with Rack compression middleware**: The RSC payload endpoint (`/rsc_payload/:component_name`) would hang indefinitely when `Rack::Deflater` or `Rack::Brotli` compression middleware was present. Compression middleware attempted to iterate the `ActionController::Live` response body to check size, creating a deadlock. Now sets `Content-Encoding: identity` on all streaming responses to signal middleware to skip compression. [PR 2529](https://github.com/shakacode/react_on_rails/pull/2529) by [justin808](https://github.com/justin808). |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@CHANGELOG.md` at line 34, The changelog attribution on the RSC payload
streaming fix entry has a username/URL mismatch: the link URL points to
"justin808" while the visible label is "justin"; update the label to match the
URL (use "justin808") so the entry follows the required format "[PR
2529](https://github.com/shakacode/react_on_rails/pull/2529) by
[justin808](https://github.com/justin808)". Locate the changelog entry
containing "Fix RSC payload streaming deadlock with Rack compression middleware"
and change the displayed username to "justin808" to match the link.
Instead of disabling compression entirely with Content-Encoding: identity, apply gzip compression ourselves when the client accepts it. This prevents the Rack middleware deadlock while preserving compressed responses. - When Accept-Encoding includes gzip: compress stream writes with Zlib::Deflate and set Content-Encoding: gzip - When gzip not accepted: set Content-Encoding: identity - Either way, Rack::Deflater/Brotli see Content-Encoding is set and skip, avoiding the Live::Buffer iteration deadlock - Add tests for gzip compression, identity fallback, and missing Accept-Encoding header Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Review: PR #2529 (final commit — inline gzip compression)This review covers the final state of the PR after commit CorrectnessThe fix is correct: by setting The inline gzip path ( Issues1. Bug:
|
| def client_accepts_gzip? | ||
| return false unless respond_to?(:request) | ||
|
|
||
| request.headers["Accept-Encoding"]&.match?(/\bgzip\b/) |
There was a problem hiding this comment.
The regex /\bgzip\b/ matches gzip;q=0, which per RFC 7231 means the client explicitly refuses gzip encoding. Sending a gzip body to such a client will cause decompression failures.
Consider parsing the quality value:
def client_accepts_gzip?
return false unless respond_to?(:request)
header = request.headers["Accept-Encoding"]
return false if header.nil?
header.split(",").any? do |part|
token, *params = part.split(";").map(&:strip)
token.casecmp?("gzip") && !params.any? { |p| p.match?(/\Aq=0(\.0*)?\z/i) }
end
end| request.headers["Accept-Encoding"]&.match?(/\bgzip\b/) | ||
| end | ||
|
|
||
| def wrap_stream_with_gzip_compression |
There was a problem hiding this comment.
Resource leak when close_stream_at_end: false: the redefined close is the only way the deflater gets finalized (deflater.finish + deflater.close). If a caller passes close_stream_at_end: false (a documented code path for error-recovery via rescue_from), the native Zlib object leaks and the gzip footer is never written, producing a truncated/corrupt gzip body.
At minimum, add a note that stream.close must eventually be called when compression is active. Ideally, gate the gzip path on close_stream_at_end or add an ensure in stream_view_containing_react_components that always closes the stream (or finalizes the deflater) when gzip is in use.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 02eae340cd
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| def client_accepts_gzip? | ||
| return false unless respond_to?(:request) | ||
|
|
||
| request.headers["Accept-Encoding"]&.match?(/\bgzip\b/) |
There was a problem hiding this comment.
Honor gzip q=0 when negotiating compression
client_accepts_gzip? currently checks Accept-Encoding with a simple \bgzip\b regex, so headers like gzip;q=0, br are treated as gzip-capable even though q=0 explicitly forbids gzip. In that case this change will force Content-Encoding: gzip, and clients/proxies that rely on quality values can receive an encoding they said they cannot accept. Please parse the quality values (or use Rack’s accept-encoding parsing) before deciding to gzip.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
react_on_rails_pro/spec/react_on_rails_pro/stream_spec.rb (1)
400-402: Make the “missing Accept-Encoding” case truly header-absent in test setup.The helper always creates the key with a nil value, so the “missing header” scenario is not fully exercised.
Suggested fix
- mocked_request_headers = { "Accept-Encoding" => accept_encoding } + mocked_request_headers = {} + mocked_request_headers["Accept-Encoding"] = accept_encoding unless accept_encoding.nil? mocked_request = double("request", headers: mocked_request_headers) # rubocop:disable RSpec/VerifiedDoubles🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@react_on_rails_pro/spec/react_on_rails_pro/stream_spec.rb` around lines 400 - 402, The test currently always sets "Accept-Encoding" in mocked_request_headers even when accept_encoding is nil, so the missing-header case isn't exercised; update the setup in the example using mocked_request_headers and mocked_request so that when accept_encoding is nil you create an empty hash (e.g. mocked_request_headers = accept_encoding.nil? ? {} : { "Accept-Encoding" => accept_encoding }) and then build the mocked_request double and allow(controller).to receive_messages(response: mocked_response, request: mocked_request) as before so the controller truly sees no Accept-Encoding header.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@react_on_rails_pro/lib/react_on_rails_pro/concerns/stream.rb`:
- Around line 136-143: In setup_streaming_compression, add the Vary header for
Accept-Encoding whenever you choose gzip vs identity: when setting
response.headers["Content-Encoding"] (inside the client_accepts_gzip? branch and
the else branch) also ensure response.headers["Vary"] includes "Accept-Encoding"
(append if a Vary header already exists) so caches know the response varies by
Accept-Encoding; update the code around setup_streaming_compression /
client_accepts_gzip? / wrap_stream_with_gzip_compression to set or merge the
Vary header accordingly.
- Around line 145-149: The method client_accepts_gzip? currently returns true if
"gzip" appears anywhere in Accept-Encoding even when q=0; update
client_accepts_gzip? to properly parse the request.headers["Accept-Encoding"]
string (in the ReactOnRailsPro::Concerns::Stream concern) and only return true
when an entry for "gzip" exists with an explicit weight > 0 (i.e., parse
comma-separated tokens, handle optional "q=" parameters, treat missing q as 1.0,
and ignore gzip if q=0). Ensure it still safely handles nil headers and uses
respond_to?(:request) as before.
---
Nitpick comments:
In `@react_on_rails_pro/spec/react_on_rails_pro/stream_spec.rb`:
- Around line 400-402: The test currently always sets "Accept-Encoding" in
mocked_request_headers even when accept_encoding is nil, so the missing-header
case isn't exercised; update the setup in the example using
mocked_request_headers and mocked_request so that when accept_encoding is nil
you create an empty hash (e.g. mocked_request_headers = accept_encoding.nil? ?
{} : { "Accept-Encoding" => accept_encoding }) and then build the mocked_request
double and allow(controller).to receive_messages(response: mocked_response,
request: mocked_request) as before so the controller truly sees no
Accept-Encoding header.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: fa96c13a-30b6-46fb-89b6-b39560e0c53c
📒 Files selected for processing (3)
CHANGELOG.mdreact_on_rails_pro/lib/react_on_rails_pro/concerns/stream.rbreact_on_rails_pro/spec/react_on_rails_pro/stream_spec.rb
🚧 Files skipped from review as they are similar to previous changes (1)
- CHANGELOG.md
| def setup_streaming_compression | ||
| if client_accepts_gzip? | ||
| response.headers["Content-Encoding"] = "gzip" | ||
| wrap_stream_with_gzip_compression | ||
| else | ||
| response.headers["Content-Encoding"] = "identity" | ||
| end | ||
| end |
There was a problem hiding this comment.
Set Vary: Accept-Encoding when selecting gzip vs identity.
Encoding now depends on request headers; without Vary, shared caches can serve the wrong variant to other clients.
Suggested fix
def setup_streaming_compression
+ vary_values = response.headers["Vary"].to_s.split(",").map(&:strip)
+ response.headers["Vary"] = (vary_values | ["Accept-Encoding"]).join(", ")
+
if client_accepts_gzip?
response.headers["Content-Encoding"] = "gzip"
wrap_stream_with_gzip_compression
else
response.headers["Content-Encoding"] = "identity"
end
end📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| def setup_streaming_compression | |
| if client_accepts_gzip? | |
| response.headers["Content-Encoding"] = "gzip" | |
| wrap_stream_with_gzip_compression | |
| else | |
| response.headers["Content-Encoding"] = "identity" | |
| end | |
| end | |
| def setup_streaming_compression | |
| vary_values = response.headers["Vary"].to_s.split(",").map(&:strip) | |
| response.headers["Vary"] = (vary_values | ["Accept-Encoding"]).join(", ") | |
| if client_accepts_gzip? | |
| response.headers["Content-Encoding"] = "gzip" | |
| wrap_stream_with_gzip_compression | |
| else | |
| response.headers["Content-Encoding"] = "identity" | |
| end | |
| end |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@react_on_rails_pro/lib/react_on_rails_pro/concerns/stream.rb` around lines
136 - 143, In setup_streaming_compression, add the Vary header for
Accept-Encoding whenever you choose gzip vs identity: when setting
response.headers["Content-Encoding"] (inside the client_accepts_gzip? branch and
the else branch) also ensure response.headers["Vary"] includes "Accept-Encoding"
(append if a Vary header already exists) so caches know the response varies by
Accept-Encoding; update the code around setup_streaming_compression /
client_accepts_gzip? / wrap_stream_with_gzip_compression to set or merge the
Vary header accordingly.
| def client_accepts_gzip? | ||
| return false unless respond_to?(:request) | ||
|
|
||
| request.headers["Accept-Encoding"]&.match?(/\bgzip\b/) | ||
| end |
There was a problem hiding this comment.
Honor Accept-Encoding quality values before enabling gzip.
client_accepts_gzip? currently enables gzip whenever "gzip" appears, including gzip;q=0, which explicitly means “do not send gzip”.
Suggested fix
def client_accepts_gzip?
- return false unless respond_to?(:request)
-
- request.headers["Accept-Encoding"]&.match?(/\bgzip\b/)
+ return false unless respond_to?(:request) && request
+
+ encodings = request.headers["Accept-Encoding"].to_s.split(",").map do |entry|
+ coding, *params = entry.strip.downcase.split(";")
+ q_param = params.find { |param| param.strip.start_with?("q=") }
+ q_value = q_param&.split("=", 2)&.last&.to_f
+ [coding, q_value.nil? ? 1.0 : q_value]
+ end.to_h
+
+ (encodings["gzip"] || encodings["*"] || 0.0).positive?
end🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@react_on_rails_pro/lib/react_on_rails_pro/concerns/stream.rb` around lines
145 - 149, The method client_accepts_gzip? currently returns true if "gzip"
appears anywhere in Accept-Encoding even when q=0; update client_accepts_gzip?
to properly parse the request.headers["Accept-Encoding"] string (in the
ReactOnRailsPro::Concerns::Stream concern) and only return true when an entry
for "gzip" exists with an explicit weight > 0 (i.e., parse comma-separated
tokens, handle optional "q=" parameters, treat missing q as 1.0, and ignore gzip
if q=0). Ensure it still safely handles nil headers and uses
respond_to?(:request) as before.
|
Closing in favor of #2530, which takes a better approach:
|
Summary
Content-Encoding: identityon all streaming responses in theStreamconcern to preventRack::DeflaterandRack::Brotlifrom iterating theLive::Bufferresponse body, which causes a deadlock withActionController::LiveFixes #2519
Test plan
Rack::Deflaterin middleware stack — RSC payload endpoint should respond without hangingRack::Brotliin middleware stack — same expected behavior🤖 Generated with Claude Code
Note
Cursor Bugbot is generating a summary for commit 3a633ea. Configure here.
Summary by CodeRabbit