-
-
Notifications
You must be signed in to change notification settings - Fork 638
Add client disconnect handling for concurrent component streaming #2137
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
When a client disconnects during streaming (browser closed, network drop), the response stream write raises IOError or Errno::EPIPE. Without handling, this crashes the request and wastes CPU on continued processing. Changes: 1. Writer error handling (stream.rb): - Catch IOError/EPIPE in writing task - Stop barrier to cancel producer tasks on disconnect - Log disconnect for debugging (when logging_on_server enabled) 2. Producer early termination (react_on_rails_pro_helper.rb): - Add stream.closed? check before processing each chunk - Prevents deadlock when producer blocks on full queue - Prevents wasted CPU when producer runs ahead of failed writer 3. Configuration validation (configuration.rb): - Add setter validation for concurrent_component_streaming_buffer_size - Must be a positive integer 4. Tests: - Add "stops writing when client disconnects" test - Add buffer size validation tests - Add closed? stub to stream test setup Based on error handling from PRs #2017 and #2026, adapted for the new Async::Barrier architecture introduced in PR #2111. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
- Reorganize disconnect tests under "client disconnect handling" describe block - Add test for Errno::EPIPE (broken pipe) in addition to IOError - Simplify test names: "stops writing on IOError/Errno::EPIPE" - Clean up comments and remove redundant assertions 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
WalkthroughRefactors streaming chunk processing into a reusable helper, adds client-disconnect detection and conditional logging to concurrent streaming, and enforces validation on the concurrent streaming buffer size configuration. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant Consumer as consumer_stream_async
participant Helper as process_stream_chunks
participant Queue as MainOutputQueue
participant Drain as drain_streams_concurrently
rect rgb(235,248,255)
Note right of Helper: Centralized chunk iteration\n(first chunk capture, enqueue, closed? checks)
end
Client->>Consumer: request streaming
Consumer->>Helper: iterate over stream chunks
alt chunk produced
Helper->>Queue: enqueue chunk
Helper-->>Consumer: return first chunk if first
else stream closed or no chunks
Helper-->>Consumer: stop iteration / return nil
end
Consumer->>Drain: run concurrent draining (writer task)
alt writer raises IOError or Errno::EPIPE
Drain->>Drain: set client_disconnected = true
Drain->>Drain: log_client_disconnect()
Drain->>Drain: stop async barrier early
else normal completion
Drain->>Drain: close queue and complete barrier
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
🧰 Additional context used📓 Path-based instructions (2)**/*.{rb,rake}📄 CodeRabbit inference engine (CLAUDE.md)
Files:
**/*📄 CodeRabbit inference engine (CLAUDE.md)
Files:
🧠 Learnings (1)📓 Common learnings🧬 Code graph analysis (1)react_on_rails_pro/lib/react_on_rails_pro/concerns/stream.rb (3)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
🔇 Additional comments (4)
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 |
Code Review✅ Overall AssessmentThis is a high-quality PR that adds critical error handling for client disconnects during concurrent streaming. The implementation is well-thought-out, properly tested, and follows the project's conventions. 🌟 Strengths
🔍 Detailed Analysis1. Writer Error Handling (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (2)
react_on_rails_pro/spec/react_on_rails_pro/stream_spec.rb (1)
494-549: Consider adding test for logging behavior.The disconnect handling tests verify the write stopping behavior, but there's no coverage for
log_client_disconnect. Consider adding a test that verifies the debug log is written whenReactOnRails.configuration.logging_on_serveris enabled.react_on_rails_pro/lib/react_on_rails_pro/configuration.rb (1)
232-238: Redundant validation method.The
validate_concurrent_component_streaming_buffer_sizemethod is now redundant since the custom setter (lines 86-92) already performs the same validation at assignment time. The setter is triggered both during initialization (line 131) and when users configure viaReactOnRailsPro.configure.Consider removing this method and the call on line 140:
def setup_config_values configure_default_url_if_not_provided validate_url validate_remote_bundle_cache_adapter setup_renderer_password setup_assets_to_copy - validate_concurrent_component_streaming_buffer_size setup_execjs_profiler_if_needed check_react_on_rails_support_for_rsc end # ... later in the file ... -def validate_concurrent_component_streaming_buffer_size - return if concurrent_component_streaming_buffer_size.is_a?(Integer) && - concurrent_component_streaming_buffer_size.positive? - - raise ReactOnRailsPro::Error, - "config.concurrent_component_streaming_buffer_size must be a positive integer" -end
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
react_on_rails_pro/app/helpers/react_on_rails_pro_helper.rb(2 hunks)react_on_rails_pro/lib/react_on_rails_pro/concerns/stream.rb(2 hunks)react_on_rails_pro/lib/react_on_rails_pro/configuration.rb(1 hunks)react_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rb(1 hunks)react_on_rails_pro/spec/react_on_rails_pro/stream_spec.rb(2 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{rb,rake}
📄 CodeRabbit inference engine (CLAUDE.md)
ALWAYS run
bundle exec rubocopand fix ALL violations before every commit/push
Files:
react_on_rails_pro/spec/react_on_rails_pro/stream_spec.rbreact_on_rails_pro/lib/react_on_rails_pro/concerns/stream.rbreact_on_rails_pro/app/helpers/react_on_rails_pro_helper.rbreact_on_rails_pro/lib/react_on_rails_pro/configuration.rbreact_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rb
**/*
📄 CodeRabbit inference engine (CLAUDE.md)
ALWAYS ensure files end with a newline character before committing/pushing
Files:
react_on_rails_pro/spec/react_on_rails_pro/stream_spec.rbreact_on_rails_pro/lib/react_on_rails_pro/concerns/stream.rbreact_on_rails_pro/app/helpers/react_on_rails_pro_helper.rbreact_on_rails_pro/lib/react_on_rails_pro/configuration.rbreact_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rb
🧠 Learnings (12)
📚 Learning: 2025-11-25T08:05:17.804Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T08:05:17.804Z
Learning: Applies to /CHANGELOG.md : Update `/CHANGELOG.md` for user-visible changes (features, bug fixes, breaking changes, deprecations, performance improvements) to the open-source React on Rails gem and npm package
Applied to files:
react_on_rails_pro/app/helpers/react_on_rails_pro_helper.rbreact_on_rails_pro/lib/react_on_rails_pro/configuration.rbreact_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rb
📚 Learning: 2025-04-09T13:02:16.009Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1696
File: node_package/src/transformRSCNodeStream.ts:3-31
Timestamp: 2025-04-09T13:02:16.009Z
Learning: In the React Server Components (RSC) stream processing implementation, all chunks end with a newline character (`\n`), so the `lastIncompleteChunk` variable is expected to be empty by the time the stream ends, making a flush handler unnecessary in the Transform stream.
Applied to files:
react_on_rails_pro/app/helpers/react_on_rails_pro_helper.rb
📚 Learning: 2025-11-25T08:05:17.804Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T08:05:17.804Z
Learning: Applies to lib/react_on_rails/*.rb : Create RBS signature files for new Ruby files in `lib/react_on_rails/` by adding corresponding `.rbs` file in `sig/react_on_rails/`, adding to Steepfile, and validating before committing
Applied to files:
react_on_rails_pro/app/helpers/react_on_rails_pro_helper.rbreact_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rb
📚 Learning: 2025-07-08T05:57:29.630Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1745
File: node_package/src/RSCRequestTracker.ts:8-14
Timestamp: 2025-07-08T05:57:29.630Z
Learning: The global `generateRSCPayload` function in React on Rails Pro (RORP) is provided by the framework during rendering requests, not implemented in application code. The `declare global` statements are used to document the expected interface that RORP will inject at runtime.
Applied to files:
react_on_rails_pro/app/helpers/react_on_rails_pro_helper.rb
📚 Learning: 2025-02-18T13:08:01.477Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1644
File: lib/react_on_rails/helper.rb:190-197
Timestamp: 2025-02-18T13:08:01.477Z
Learning: RSC support validation in React on Rails Pro is handled through a chain of validations:
1. Pro version check in `run_stream_inside_fiber`
2. RSC support check during pack generation via `ReactOnRailsPro.configuration.enable_rsc_support`
3. RSC support validation during component registration
This makes additional validation in the helper methods unnecessary.
Applied to files:
react_on_rails_pro/app/helpers/react_on_rails_pro_helper.rbreact_on_rails_pro/lib/react_on_rails_pro/configuration.rbreact_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rb
📚 Learning: 2025-09-16T08:01:11.146Z
Learnt from: justin808
Repo: shakacode/react_on_rails PR: 1770
File: lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.client.jsx:2-2
Timestamp: 2025-09-16T08:01:11.146Z
Learning: React on Rails uses webpack CSS Modules configuration with namedExports: true, which requires the import syntax `import * as style from './file.module.css'` rather than the default export pattern. This configuration enables better tree shaking and bundle size optimization for CSS modules.
Applied to files:
react_on_rails_pro/app/helpers/react_on_rails_pro_helper.rbreact_on_rails_pro/lib/react_on_rails_pro/configuration.rb
📚 Learning: 2025-04-26T21:55:55.874Z
Learnt from: alexeyr-ci2
Repo: shakacode/react_on_rails PR: 1732
File: spec/dummy/client/app-react16/startup/ReduxSharedStoreApp.client.jsx:40-44
Timestamp: 2025-04-26T21:55:55.874Z
Learning: In the react_on_rails project, files under `app-react16` directories are copied/moved to corresponding `/app` directories during the conversion process (removing the `-react16` suffix), which affects their relative import paths at runtime.
Applied to files:
react_on_rails_pro/app/helpers/react_on_rails_pro_helper.rb
📚 Learning: 2025-10-23T17:22:01.074Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1875
File: lib/react_on_rails/utils.rb:112-124
Timestamp: 2025-10-23T17:22:01.074Z
Learning: In React on Rails, when Pro is installed but not licensed, the intended behavior is to raise an error on boot. The `react_on_rails_pro?` method validates licenses and should raise errors early (including during path resolution in methods like `server_bundle?`) to enforce licensing requirements rather than failing later with obscure errors.
Applied to files:
react_on_rails_pro/lib/react_on_rails_pro/configuration.rbreact_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rb
📚 Learning: 2025-02-18T13:08:01.477Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1644
File: lib/react_on_rails/helper.rb:190-197
Timestamp: 2025-02-18T13:08:01.477Z
Learning: RSC support validation is handled in deeper level calls of the React on Rails Pro codebase, so it doesn't need to be validated again in the `rsc_payload_react_component` helper method.
Applied to files:
react_on_rails_pro/lib/react_on_rails_pro/configuration.rb
📚 Learning: 2025-02-13T14:29:49.267Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1644
File: spec/react_on_rails/utils_spec.rb:218-218
Timestamp: 2025-02-13T14:29:49.267Z
Learning: In RSpec tests, prefer using local variables over constants within test blocks to avoid constant redefinition warnings and maintain better test isolation.
Applied to files:
react_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rb
📚 Learning: 2025-11-25T08:05:17.804Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T08:05:17.804Z
Learning: The `react_on_rails_pro/` directory has its own Prettier/ESLint configuration and must be linted separately
Applied to files:
react_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rb
📚 Learning: 2025-11-25T08:05:17.804Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T08:05:17.804Z
Learning: Applies to sig/react_on_rails/**/*.rbs : In RBS type signature files, define type signatures that correspond to Ruby files in `lib/react_on_rails/`
Applied to files:
react_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rb
🧬 Code graph analysis (3)
react_on_rails_pro/spec/react_on_rails_pro/stream_spec.rb (3)
react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/RouterApp.server.jsx (1)
controller(10-10)react_on_rails_pro/spec/dummy/spec/helpers/react_on_rails_pro_helper_spec.rb (1)
run_stream(615-618)packages/react-on-rails-pro/tests/AsyncQueue.ts (1)
enqueue(10-20)
react_on_rails_pro/lib/react_on_rails_pro/concerns/stream.rb (1)
packages/react-on-rails-pro/tests/AsyncQueue.ts (1)
dequeue(27-54)
react_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rb (1)
react_on_rails_pro/lib/react_on_rails_pro/configuration.rb (2)
concurrent_component_streaming_buffer_size(86-92)configuration(9-38)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
- GitHub Check: examples (3.4, latest)
- GitHub Check: build-dummy-app-webpack-test-bundles
- GitHub Check: pro-lint-js-and-ruby
- GitHub Check: build-dummy-app-webpack-test-bundles
- GitHub Check: claude-review
🔇 Additional comments (9)
react_on_rails_pro/lib/react_on_rails_pro/concerns/stream.rb (3)
63-86: LGTM! Client disconnect handling is well-implemented.The rescue block correctly catches
IOErrorandErrno::EPIPE, which are the standard exceptions raised when writing to a disconnected client. Settingclient_disconnectedallows the ensure block to stop producers and avoid wasted work.One consideration: the
client_disconnectedvariable is set inside the async block. Ruby closures capture the variable binding, so this works correctly, but be aware that the variable must be read only afterwriting_task.waitcompletes to ensure visibility of the updated value.
95-102: Correct shutdown sequence on disconnect.The order of operations is appropriate: stopping the barrier first cancels producer tasks to prevent wasted work, then closing the queue signals end of streaming, and finally waiting for the writing task ensures cleanup completes.
104-110: Good logging implementation.Using a block for
Rails.logger.debugis the right approach for lazy evaluation. The debug level is appropriate since client disconnects are expected occurrences in production, not errors.react_on_rails_pro/app/helpers/react_on_rails_pro_helper.rb (2)
325-346: Good extraction with proper disconnect handling.The
process_stream_chunksmethod cleanly encapsulates the streaming logic. Theresponse.stream.closed?check on line 330 correctly prevents wasted work by breaking early when the client disconnects.The first chunk / subsequent chunks separation is clear, and the empty stream case (line 345) is handled correctly by setting
first_chunk_var.value = nilonly when no chunks were received.
314-318: Clean refactor.Extracting the chunk processing logic into
process_stream_chunksimproves the readability ofconsumer_stream_asyncwhile maintaining the same behavior.react_on_rails_pro/spec/react_on_rails_pro/stream_spec.rb (2)
395-395: Necessary stub for disconnect detection.Adding the
closed?stub is required becauseprocess_stream_chunksin the helper now checksresponse.stream.closed?to detect client disconnects and break early.
494-548: Good test coverage for client disconnect scenarios.The tests correctly verify behavior for both
IOErrorandErrno::EPIPEexceptions:
- Write 1: TEMPLATE (from
render_to_string)- Write 2: Chunk1 (first enqueued chunk)
- Write 3: Raises exception on Chunk2
The assertions
expect(written_chunks).to eq(%w[TEMPLATE Chunk1])correctly verify that streaming stops gracefully after the disconnect, with no exception propagating to the caller.react_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rb (1)
264-299: Comprehensive validation tests.Good coverage of the validation edge cases:
- Positive integer (valid) ✓
- Zero (invalid boundary) ✓
- Negative integer (invalid) ✓
- Non-integer string (type check) ✓
The tests correctly verify both successful configuration and error messages for invalid values.
react_on_rails_pro/lib/react_on_rails_pro/configuration.rb (1)
73-92: Well-documented setter with proper validation.The YARD documentation clearly explains the purpose, behavior, and constraints of the buffer size configuration. The validation correctly rejects non-integers and non-positive values.
Test that response.stream.closed? check in process_stream_chunks actually stops processing when client disconnects early. Also adds required response mocks to existing streaming tests. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
The concurrent_component_streaming_buffer_size setter already validates the value when assigned (lines 86-92), making the separate validation method and its call in setup_config_values unnecessary. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
Code Review: Client Disconnect Handling for Concurrent Component StreamingSummaryThis PR addresses a critical gap in error handling for client disconnects during streaming, which was missing after the architectural changes in #2111. The implementation is well-designed and thoroughly tested. ✅ Strengths1. Excellent Architecture & Design 🎯
2. Comprehensive Test Coverage ✅The tests cover all critical scenarios:
3. Good Refactoring 🔧
Issues & RecommendationsCritical Issues1. Potential Race Condition in
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (4)
react_on_rails_pro/spec/dummy/spec/helpers/react_on_rails_pro_helper_spec.rb (3)
383-389: Consistentresponse.streammocking for newclosed?checksStubbing
response.stream.closed?tofalseby default ensures the new helper logic that consultsstream.closed?can run in these specs without spurious failures. If this pattern grows further, consider extracting a small helper (e.g.,stub_open_stream) to DRY up similar setups across streaming specs, but it’s optional at this scale.
466-496: Client-disconnect test is sound; consider avoiding a magic numberThe “stops processing chunks when client disconnects” example correctly simulates a disconnect by toggling
closed?after the first call and asserting that fewer than all remaining chunks are processed, which guards the newstream.closed?checks inprocess_stream_chunks.To make the test a bit more future-proof and self-documenting, you could express the expectation in terms of
many_chunks.lengthinstead of the literal9:- # Should have stopped early - not all chunks processed - # The exact count depends on timing, but should be less than 9 (all remaining) - expect(collected_chunks.length).to be < 9 + # Should have stopped early - not all non-initial chunks processed + expect(collected_chunks.length).to be < (many_chunks.length - 1)
769-775: Ensuremocked_streamstubs stay minimal but sufficientStubbing only
closed?and wiringresponse.streamtomocked_streamis appropriate as long as the RandomValue integration path doesn’t invokewrite/closeon the stream in these tests (which seems true given current behavior). If future changes add writes here, you may need to extend the stub:allow(mocked_stream).to receive(:write) allow(mocked_stream).to receive(:close)For now, the setup is lean and adequate.
react_on_rails_pro/lib/react_on_rails_pro/configuration.rb (1)
73-92: Validation onconcurrent_component_streaming_buffer_sizeis appropriate and fail-fastSwitching from an
attr_accessorto anattr_readerplus a custom setter that enforces a positiveIntegerand raisesReactOnRailsPro::Erroron invalid values is a solid improvement: it prevents silent misconfiguration and constrains this tuning knob to sane values.If you expect users to often source this from environment variables or YAML (so you get strings), you might optionally consider a small coercion step (e.g.,
Integer(value)with a rescue that raises the same error) instead of requiringis_a?(Integer), but strict typing is also a reasonable choice if that’s the intended API.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
react_on_rails_pro/lib/react_on_rails_pro/configuration.rb(1 hunks)react_on_rails_pro/spec/dummy/spec/helpers/react_on_rails_pro_helper_spec.rb(9 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{rb,rake}
📄 CodeRabbit inference engine (CLAUDE.md)
ALWAYS run
bundle exec rubocopand fix ALL violations before every commit/push
Files:
react_on_rails_pro/lib/react_on_rails_pro/configuration.rbreact_on_rails_pro/spec/dummy/spec/helpers/react_on_rails_pro_helper_spec.rb
**/*
📄 CodeRabbit inference engine (CLAUDE.md)
ALWAYS ensure files end with a newline character before committing/pushing
Files:
react_on_rails_pro/lib/react_on_rails_pro/configuration.rbreact_on_rails_pro/spec/dummy/spec/helpers/react_on_rails_pro_helper_spec.rb
🧠 Learnings (13)
📓 Common learnings
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1696
File: node_package/src/transformRSCNodeStream.ts:3-31
Timestamp: 2025-04-09T13:02:16.009Z
Learning: In the React Server Components (RSC) stream processing implementation, all chunks end with a newline character (`\n`), so the `lastIncompleteChunk` variable is expected to be empty by the time the stream ends, making a flush handler unnecessary in the Transform stream.
📚 Learning: 2025-11-25T08:05:17.804Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T08:05:17.804Z
Learning: Applies to /CHANGELOG.md : Update `/CHANGELOG.md` for user-visible changes (features, bug fixes, breaking changes, deprecations, performance improvements) to the open-source React on Rails gem and npm package
Applied to files:
react_on_rails_pro/lib/react_on_rails_pro/configuration.rbreact_on_rails_pro/spec/dummy/spec/helpers/react_on_rails_pro_helper_spec.rb
📚 Learning: 2025-02-18T13:08:01.477Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1644
File: lib/react_on_rails/helper.rb:190-197
Timestamp: 2025-02-18T13:08:01.477Z
Learning: RSC support validation in React on Rails Pro is handled through a chain of validations:
1. Pro version check in `run_stream_inside_fiber`
2. RSC support check during pack generation via `ReactOnRailsPro.configuration.enable_rsc_support`
3. RSC support validation during component registration
This makes additional validation in the helper methods unnecessary.
Applied to files:
react_on_rails_pro/lib/react_on_rails_pro/configuration.rb
📚 Learning: 2025-10-23T17:22:01.074Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1875
File: lib/react_on_rails/utils.rb:112-124
Timestamp: 2025-10-23T17:22:01.074Z
Learning: In React on Rails, when Pro is installed but not licensed, the intended behavior is to raise an error on boot. The `react_on_rails_pro?` method validates licenses and should raise errors early (including during path resolution in methods like `server_bundle?`) to enforce licensing requirements rather than failing later with obscure errors.
Applied to files:
react_on_rails_pro/lib/react_on_rails_pro/configuration.rb
📚 Learning: 2025-09-16T08:01:11.146Z
Learnt from: justin808
Repo: shakacode/react_on_rails PR: 1770
File: lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.client.jsx:2-2
Timestamp: 2025-09-16T08:01:11.146Z
Learning: React on Rails uses webpack CSS Modules configuration with namedExports: true, which requires the import syntax `import * as style from './file.module.css'` rather than the default export pattern. This configuration enables better tree shaking and bundle size optimization for CSS modules.
Applied to files:
react_on_rails_pro/lib/react_on_rails_pro/configuration.rb
📚 Learning: 2025-02-18T13:08:01.477Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1644
File: lib/react_on_rails/helper.rb:190-197
Timestamp: 2025-02-18T13:08:01.477Z
Learning: RSC support validation is handled in deeper level calls of the React on Rails Pro codebase, so it doesn't need to be validated again in the `rsc_payload_react_component` helper method.
Applied to files:
react_on_rails_pro/lib/react_on_rails_pro/configuration.rb
📚 Learning: 2025-11-25T08:05:17.804Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T08:05:17.804Z
Learning: Applies to lib/react_on_rails/*.rb : Create RBS signature files for new Ruby files in `lib/react_on_rails/` by adding corresponding `.rbs` file in `sig/react_on_rails/`, adding to Steepfile, and validating before committing
Applied to files:
react_on_rails_pro/lib/react_on_rails_pro/configuration.rb
📚 Learning: 2025-11-25T08:05:17.804Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T08:05:17.804Z
Learning: The `react_on_rails_pro/` directory has its own Prettier/ESLint configuration and must be linted separately
Applied to files:
react_on_rails_pro/lib/react_on_rails_pro/configuration.rb
📚 Learning: 2024-10-08T20:53:47.076Z
Learnt from: theforestvn88
Repo: shakacode/react_on_rails PR: 1620
File: spec/dummy/client/app/startup/HelloTurboStream.jsx:3-3
Timestamp: 2024-10-08T20:53:47.076Z
Learning: The `RailsContext` import in `spec/dummy/client/app/startup/HelloTurboStream.jsx` is used later in the project, as clarified by the user theforestvn88.
Applied to files:
react_on_rails_pro/spec/dummy/spec/helpers/react_on_rails_pro_helper_spec.rb
📚 Learning: 2025-11-25T08:05:17.804Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T08:05:17.804Z
Learning: Applies to spec/dummy/e2e/playwright/**/*.spec.js : Use Rails helpers (`appFactories`, `appEval`) in Playwright tests instead of UI-based setup to leverage factory_bot and arbitrary Ruby code execution
Applied to files:
react_on_rails_pro/spec/dummy/spec/helpers/react_on_rails_pro_helper_spec.rb
📚 Learning: 2025-04-09T13:02:16.009Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1696
File: node_package/src/transformRSCNodeStream.ts:3-31
Timestamp: 2025-04-09T13:02:16.009Z
Learning: In the React Server Components (RSC) stream processing implementation, all chunks end with a newline character (`\n`), so the `lastIncompleteChunk` variable is expected to be empty by the time the stream ends, making a flush handler unnecessary in the Transform stream.
Applied to files:
react_on_rails_pro/spec/dummy/spec/helpers/react_on_rails_pro_helper_spec.rb
📚 Learning: 2025-02-13T14:29:49.267Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1644
File: spec/react_on_rails/utils_spec.rb:218-218
Timestamp: 2025-02-13T14:29:49.267Z
Learning: In RSpec tests, prefer using local variables over constants within test blocks to avoid constant redefinition warnings and maintain better test isolation.
Applied to files:
react_on_rails_pro/spec/dummy/spec/helpers/react_on_rails_pro_helper_spec.rb
📚 Learning: 2024-10-08T20:53:47.076Z
Learnt from: justin808
Repo: shakacode/react_on_rails PR: 1622
File: spec/dummy/spec/rake/assets_precompile_rake_spec.rb:12-12
Timestamp: 2024-10-08T20:53:47.076Z
Learning: When stubbing environment variables in RSpec tests, use `before` and `after` hooks to ensure that the original values are restored after the tests, preventing any side effects on other tests. Example provided by justin808:
```ruby
describe "My test" do
before do
original_value = ENV["VARIABLE_NAME"]
allow(ENV).to receive(:[]).with("VARIABLE_NAME").and_return("stubbed_value")
end
after do
allow(ENV).to receive(:[]).with("VARIABLE_NAME").and_call_original
ENV["VARIABLE_NAME"] = original_value
end
it "tests something" do
# Your test code here
end
end
```
This practice ensures test isolation and reliability.
Applied to files:
react_on_rails_pro/spec/dummy/spec/helpers/react_on_rails_pro_helper_spec.rb
🧬 Code graph analysis (1)
react_on_rails_pro/spec/dummy/spec/helpers/react_on_rails_pro_helper_spec.rb (2)
react_on_rails_pro/app/helpers/react_on_rails_pro_helper.rb (1)
stream_react_component(126-138)react_on_rails_pro/spec/react_on_rails_pro/stream_spec.rb (1)
include(8-35)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
- GitHub Check: examples (3.2, minimum)
- GitHub Check: examples (3.4, latest)
- GitHub Check: build-dummy-app-webpack-test-bundles
- GitHub Check: build-dummy-app-webpack-test-bundles
- GitHub Check: pro-lint-js-and-ruby
- GitHub Check: claude-review
🔇 Additional comments (6)
react_on_rails_pro/spec/dummy/spec/helpers/react_on_rails_pro_helper_spec.rb (5)
365-367: Good isolation forstream_react_componentstreaming testsThe dedicated
describe "#stream_react_component"with its ownmocked_rails_streamdouble cleanly scopes the streaming tests and keeps the setup localized. This aligns well with the newstream.closed?behavior.
521-521:closed?stub keepsstream_view_containing_react_componentsspecs aligned with new behaviorAdding
allow(mocked_stream).to receive(:closed?).and_return(false)here is necessary now that the streaming helpers may consultstream.closed?. This keeps the existing view-level streaming specs focused on behavior rather than low-level stream state and looks correct.
611-611: Cached streaming specs correctly mockclosed?on the streamThe additional
closed?stub for the cached streaming tests ensures that the new client-disconnect-aware logic doesn’t inadvertently terminate cached streams during these examples. The change is minimal and appropriate.
756-758: Integration describe for cached streaming plus RandomValue is well-scopedThe new
describe "cached_stream_react_component integration with RandomValue"and itsmocked_streamdouble carve out a focused space to validate cache behavior under the concurrent streaming architecture. This structure is clear and consistent with the earlier streaming specs.
836-852: Attribution tests correctly adapt toclosed?-aware streamingThe attribution specs now mock
response.stream.closed?and routeresponse.streamthrough a double, which keeps them compatible with the new disconnect-aware streaming logic while still focusing purely on the presence and multiplicity of the attribution comment. This is a good, minimal adjustment.react_on_rails_pro/lib/react_on_rails_pro/configuration.rb (1)
105-132: Initializer correctly routes through the validated setterPassing
concurrent_component_streaming_buffer_size:into the initializer with a default ofDEFAULT_CONCURRENT_COMPONENT_STREAMING_BUFFER_SIZEand then assigning viaself.concurrent_component_streaming_buffer_size = ...ensures the validation is applied at construction time as well, not just on later mutations. That’s a good fail-fast behavior for bad configuration.Given this is a user-visible configuration validation (raising a specific
ReactOnRailsPro::Error), it’s worth confirming the CHANGELOG entry reflects this stricter behavior so upgrades don’t surprise existing users. Based on learnings, this should be documented in/CHANGELOG.md.
The ensure block was checking client_disconnected before waiting for writing_task to complete. This caused a race where the flag might still be false when checked, even though writing_task would set it to true moments later. Fixed by reordering the ensure block: 1. Close queue (unblocks writing_task if waiting on dequeue) 2. Wait for writing_task (ensures flag is set if disconnect occurred) 3. Check flag and stop barrier if needed This ensures producers are properly stopped when clients disconnect, preventing wasted CPU cycles. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
Code Review - PR #2137: Client Disconnect Handling for Concurrent Component StreamingI've reviewed this PR and it's a solid implementation. Here's my feedback: ✅ Strengths1. Well-Structured Error HandlingThe error handling in
2. Race Condition FixThe # Close the queue first to unblock writing_task (it may be waiting on dequeue)
@main_output_queue.close
# Wait for writing_task to ensure client_disconnected flag is set
# before we check it (fixes race condition where ensure runs before
# writing_task's rescue block sets the flag)
writing_task.wait
# If client disconnected, stop all producer tasks to avoid wasted work
@async_barrier.stop if client_disconnectedThis is excellent - the comment clearly explains the race condition and the fix is correct. 3. Producer-Side Early TerminationThe stream.each_chunk do |chunk|
# Check if client disconnected before processing chunk
break if response.stream.closed?
# ...
endThis is important when the queue is full and the producer would otherwise block. 4. Configuration ValidationMoving validation to a setter is the right approach: def concurrent_component_streaming_buffer_size=(value)
unless value.is_a?(Integer) && value.positive?
raise ReactOnRailsPro::Error,
"config.concurrent_component_streaming_buffer_size must be a positive integer"
end
@concurrent_component_streaming_buffer_size = value
endThis provides immediate feedback on invalid configuration. 5. Excellent Test Coverage
6. Code OrganizationExtracting 💡 Suggestions for Improvement1. Consider Logging LevelThe disconnect logging uses Rails.logger.debug do
"[React on Rails Pro] Client disconnected during streaming (#{context}): #{exception.class}"
endConsideration: Client disconnects are expected behavior (user closes browser, network issues).
The current choice seems reasonable, but worth documenting the rationale. 2. Potential Race in Producer CheckIn break if response.stream.closed?Minor concern: There's a small window between checking
So this is likely fine, but worth noting for future debugging if weird edge cases appear. 3. Test DeterminismIn the test: # Should have stopped early - not all chunks processed
# The exact count depends on timing, but should be less than 9 (all remaining)
expect(collected_chunks.length).to be < 9The comment acknowledges timing dependency. This is acceptable for this type of integration test, but if it becomes flaky, consider:
🔒 Security Considerations✅ No security concerns. The error handling doesn't expose sensitive information, and the logging respects the ⚡ Performance Considerations✅ Excellent performance characteristics:
The overhead of checking 📋 Additional NotesMissing from #2017/#2026As noted in the PR description, this implementation differs from the closed PRs because it's adapted for the #2111 architecture ( Architecture FitThis implementation fits well with the producer-consumer pattern:
✅ Final VerdictLGTM - Ready to merge pending minor considerations above. The code is well-written, thoroughly tested, and solves a real problem (wasted CPU on disconnect). The race condition fix is particularly well-handled. Great work on this implementation! 🎉 📝 Checklist ReviewBased on CLAUDE.md requirements:
|
Code Review: Client Disconnect Handling for Concurrent Component StreamingSummaryExcellent work! This PR adds critical error handling for client disconnects that was missing after the architectural changes in #2111. The implementation is well-designed, thoroughly tested, and follows best practices. ✅ Strengths1. Correct Error Handling
2. Resource Management
3. Code Quality
4. Test Coverage
5. Race Condition HandlingThe ensure block in ensure
@main_output_queue.close # Unblock writer first
writing_task.wait # Wait for flag to be set
@async_barrier.stop if client_disconnected # Then cancel producers
endThis prevents the race where 🔍 Observations & Suggestions1. Optional Logging ConsiderationThe logging is behind # Current approach is fine, but you could consider:
Rails.logger.info "[React on Rails Pro] Client disconnected during streaming"
# instead of debug level, since this is normal operational infoHowever, 2. Test DeterminismThe early termination test has this comment: # Should have stopped early - not all chunks processed
# The exact count depends on timing, but should be less than 9
expect(collected_chunks.length).to be < 9This is reasonable given async timing, but if flakiness appears, consider:
Current approach is fine for now - just flagging for future reference. 3. Configuration Validation TimingMoving validation from
This is a best practice improvement! 🎯 Architecture AlignmentThis PR correctly adapts the client disconnect handling to the new Barrier + Queue architecture:
🔒 Security & Performance
📝 DocumentationThe inline comments in #### Fixed
- Fixed missing client disconnect handling in concurrent component streaming that could waste CPU resources when clients disconnect mid-stream. [PR 2137](https://github.com/shakacode/react_on_rails/pull/2137) by [ihabadham](https://github.com/ihabadham)✅ Final VerdictLGTM! Ready to merge. This is high-quality work:
Great job on the thorough testing and attention to edge cases like the race condition in the ensure block! Review by Claude Code - all 152 Pro tests passing, RuboCop clean |
Summary
Adds error handling for client disconnects during streaming, which was missing after the architecture change in #2111.
Background
Async::SemaphoreAsync::Barrier+Async::Variable+Async::LimitedQueueProblem
When a client disconnects mid-stream (browser closed, network drop),
response.stream.writeraisesIOErrororErrno::EPIPE. Without handling:Changes
1. Writer error handling (
stream.rb)IOError/Errno::EPIPEin writing taskclient_disconnectedflag and stop barrier to cancel producer taskslogging_on_serverenabled)2. Producer early termination (
react_on_rails_pro_helper.rb)stream.closed?check before processing each chunkprocess_stream_chunksmethod for clarity3. Configuration validation (
configuration.rb)concurrent_component_streaming_buffer_size4. Tests
client disconnect handlingdescribe block with IOError and EPIPE testsclosed?stub to stream test setupTest Plan
bundle exec rspec spec/react_on_rails_pro/- all 152 tests passbundle exec rubocop- no offenses🤖 Generated with Claude Code
Summary by CodeRabbit
Bug Fixes
Configuration
Enhancements
Tests
✏️ Tip: You can customize this high-level summary in your review settings.