Skip to content

Conversation

@AbanoubGhadban
Copy link
Collaborator

@AbanoubGhadban AbanoubGhadban commented Nov 10, 2025

Summary

Remove this paragraph and provide a general description of the code changes in your pull
request... were there any bugs you had fixed? If so, mention them. If
these bugs have open GitHub issues, be sure to tag them here as well,
to keep the conversation linked together.

Pull Request checklist

Remove this line after checking all the items here. If the item is not applicable to the PR, both check it out and wrap it by ~.

  • Add/update test to cover these changes
  • Update documentation
  • Update CHANGELOG file

Add the CHANGELOG entry at the top of the file.

Other Information

Remove this paragraph and mention any other important and relevant information such as benchmarks.


This change is Reviewable

Summary by CodeRabbit

  • New Features

    • New configurable graceful worker restart timeout for Node Renderer to control forced restarts during graceful shutdowns.
  • Bug Fixes

    • Fixed worker shutdown edge case to avoid closed-descriptor errors when requests are in flight.
  • Documentation

    • Added docs and troubleshooting notes for the graceful worker restart timeout, with guidance for streaming scenarios.
  • Tests

    • Updated system test metadata and re-enabled previously skipped tests for more reliable end-to-end coverage.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 10, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Adds graceful worker restart support to the Node Renderer (config, master scheduling, sequential restarts, worker-side graceful shutdown), updates config/ docs, and enables/adjusts several integration system tests and assertions.

Changes

Cohort / File(s) Summary
Integration tests
react_on_rails_pro/spec/dummy/spec/system/integration_spec.rb
Updated assertions (use .not_to have_content / .not_to have_text patterns), removed Capybara rubocop disable comments, added type: :system metadata, and re-enabled previously-skipped test blocks.
Docs — installation & Node renderer
react_on_rails_pro/docs/installation.md, react_on_rails_pro/docs/node-renderer/js-configuration.md, react_on_rails_pro/docs/node-renderer/troubleshooting.md
Documented new gracefulWorkerRestartTimeout option and added troubleshooting guidance for streaming scenarios.
Config builder
react_on_rails_pro/packages/node-renderer/src/shared/configBuilder.ts
Added gracefulWorkerRestartTimeout to Config and defaultConfig; read from GRACEFUL_WORKER_RESTART_TIMEOUT; exposed in envValuesUsed() and sanitizedSettings().
Master scheduler
react_on_rails_pro/packages/node-renderer/src/master.ts
Replaced setInterval scheduling with recursive setTimeout to call restartWorkers(...) and reschedule after each full restart cycle; passes gracefulWorkerRestartTimeout.
Worker restart logic
react_on_rails_pro/packages/node-renderer/src/master/restartWorkers.ts
Converted to async, sequential per-worker restarts; sends shutdown message, waits for exit (with optional graceful timeout and forced destroy), delays between workers; signature now accepts gracefulWorkerRestartTimeout.
Shutdown constant
react_on_rails_pro/packages/node-renderer/src/shared/utils.ts
Added exported SHUTDOWN_WORKER_MESSAGE = 'NODE_RENDERER_SHUTDOWN_WORKER'.
Worker init
react_on_rails_pro/packages/node-renderer/src/worker.ts
Imports and invokes handleGracefulShutdown(app) during worker startup.
Graceful shutdown handler
react_on_rails_pro/packages/node-renderer/src/worker/handleGracefulShutdown.ts
New module implementing worker-side graceful shutdown: tracks active requests, listens for SHUTDOWN_WORKER_MESSAGE, coordinates disconnect/destroy based on in-flight requests.
Changelog
react_on_rails_pro/CHANGELOG.md
Added entry describing fix for descriptor closed error during shutdown and graceful shutdown behavior.

Sequence Diagram(s)

sequenceDiagram
    participant Master as Master
    participant Worker as Worker
    participant App as Fastify App

    Note over Master: Recursive scheduler (setTimeout)
    Master->>Master: schedule restartWorkers(...)
    Master->>Worker: Send SHUTDOWN_WORKER_MESSAGE
    Worker->>Worker: isShuttingDown = true
    Worker->>App: track activeRequestsCount (on each request/response)
    alt activeRequestsCount > 0
        App->>Worker: requests finish (decrement)
        Worker->>Worker: when 0 -> destroy() (forced if timed out)
    else no in-flight requests
        Worker->>Worker: disconnect() (immediate)
    end
    Master->>Master: after all workers processed -> reschedule next restart
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Pay special attention to:
    • handleGracefulShutdown.ts — request counting, edge cases, worker lifecycle calls.
    • restartWorkers.ts — async sequencing, timeout enforcement, and error paths.
    • master.ts — rescheduling correctness and interaction with process lifecycle.
    • Integration test changes — ensure assertions/metadata align with test runner expectations.

Possibly related PRs

Suggested reviewers

  • Judahmeek
  • alexeyr-ci

Poem

🐰 In hops and logs the master waits,
It tells each worker to close the gates,
Requests finish first, no sudden end,
Then one by one the restarts send —
A gentle timeout guards the state.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title directly matches the main objective: fixing a 'descriptor closed' error caused by worker restarts during active requests, which is reflected throughout the code changes (graceful shutdown handling, timeout configuration, sequential worker restarts).

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4b0f581 and 0e434f5.

📒 Files selected for processing (1)
  • react_on_rails_pro/CHANGELOG.md (1 hunks)

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.

@coderabbitai coderabbitai bot added the bug label Nov 10, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
react_on_rails_pro/lib/react_on_rails_pro/request.rb (2)

10-15: Remove unused connection management.

The @connection_without_retries is closed and recreated here, but since line 102 now always uses connection, this secondary connection pool is never used. This dead code should be removed to eliminate unnecessary resource allocation.

Apply this diff to remove the unused connection:

 def reset_connection
   @connection&.close
-  @connection_without_retries&.close
   @connection = create_connection
-  @connection_without_retries = create_connection(enable_retries: false)
 end

87-97: Update or remove outdated architecture comment and unused method.

The comment describes maintaining two separate HTTP connection pools to handle streaming vs non-streaming requests, but line 102 now uses only connection for all requests. The connection_without_retries method is now dead code and should be removed along with the misleading comment.

Apply this diff:

-# NOTE: We maintain two separate HTTP connection pools to handle streaming vs non-streaming requests.
-# This doubles the memory footprint (e.g., if renderer_http_pool_size is 10, we use 20 total connections).
-# This tradeoff is acceptable to prevent body duplication in streaming responses.
-
 def connection
   @connection ||= create_connection
 end
-
-def connection_without_retries
-  @connection_without_retries ||= create_connection(enable_retries: false)
-end
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8b26575 and c9aad87.

📒 Files selected for processing (1)
  • react_on_rails_pro/lib/react_on_rails_pro/request.rb (1 hunks)
⏰ 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). (4)
  • GitHub Check: build-dummy-app-webpack-test-bundles
  • GitHub Check: build-dummy-app-webpack-test-bundles
  • GitHub Check: lint-js-and-ruby
  • GitHub Check: claude-review
🔇 Additional comments (1)
react_on_rails_pro/lib/react_on_rails_pro/request.rb (1)

244-245: Streaming requests now enable connection retries, but comment references outdated concern.

The comment warns about body duplication from retries in streaming requests (citing issue #1895), but the current implementation at line 102 enables retries for all requests, including streaming. The comment should be updated or reconciled with this behavior change.

@claude
Copy link

claude bot commented Nov 10, 2025

Code Review

Summary

This PR re-enables previously skipped RSpec system tests for streaming components and React Router while making minor RSpec style improvements. The changes are investigative in nature to understand the "descriptor closed" error.


Positive Changes ✅

1. RSpec Best Practices

  • Lines 14, 410-412, 419-420: Correctly replaced have_no_content and have_no_text with not_to have_content and not_to have_text
    • This follows RSpec best practices and improves readability
    • The negative expectation syntax (not_to) is more idiomatic than the have_no_* matchers

2. Code Cleanup

  • Lines 113, 177, 184, 189, 247: Removed unnecessary RuboCop disable comments for Capybara/ClickLinkOrButtonStyle
    • Indicates the code now complies with the style guide without exceptions

3. Type Specification

  • Line 242: Added explicit type: :system to "Manual client hydration" test block
    • Improves test metadata consistency

Concerns & Issues ⚠️

1. CRITICAL: Re-enabling Skipped Tests Without Resolution

The most significant concern is re-enabling tests that were previously marked as flaky/problematic:

  • Line 425-428: Removed skip for "Pages/stream_async_components_for_testing"

    • Previous skip reason: "Flaky test replaced by Playwright E2E tests in e2e-tests/streaming.spec.ts"
    • Verified: Playwright E2E coverage exists in react_on_rails_pro/spec/dummy/e2e-tests/streaming.spec.ts
  • Line 430-433: Removed skip for "React Router Sixth Page"

    • Previous skip reason: "Work in progress in another branch: justin808/fix-body-dup-retry"

Questions:

  1. What investigation has been done to confirm these tests are no longer flaky?
  2. Has the underlying "descriptor closed" error been identified and fixed?
  3. Were the tests passing reliably in your local environment before re-enabling?
  4. Is the work from branch justin808/fix-body-dup-retry now merged?

2. Missing Context in PR Description

The PR template sections are not filled out:

  • No description of what "descriptor closed" error is or where it occurs
  • No test coverage update mentioned
  • No CHANGELOG entry (though this may not warrant one if it's investigative)
  • Checklist items are unchecked

3. Potential CI Flakiness Risk

Re-enabling flaky tests without documented fixes could:

  • Introduce intermittent CI failures that block other PRs
  • Create confusion for other contributors
  • Waste CI resources on unreliable test runs

Recommendations 📋

Before Merging:

  1. Document the Investigation

    • Update PR description with findings about the "descriptor closed" error
    • Explain what changed that makes these tests reliable now
    • Reference any related issues or commits that fixed underlying problems
  2. Test Reliability Validation

    • Run the re-enabled tests multiple times (10+ runs) to verify stability
    • Check if CI passes consistently across multiple builds
    • Consider using RSpec's --seed option to test with different execution orders
  3. Consider Gradual Re-enabling

    • If tests are still showing issues, consider re-enabling one test suite at a time
    • Use :retry metadata for temporary flakiness mitigation (though not ideal long-term)
  4. Update Documentation

    • If Playwright E2E tests provide better coverage, document why both test suites are needed
    • Or consider removing redundant RSpec tests if Playwright coverage is sufficient

Example RSpec retry (temporary measure):

describe "Pages/stream_async_components_for_testing", :js, retry: 3 do
  # tests...
end

Code Quality Assessment

Style & Conventions: ✅ Excellent

  • Follows RSpec best practices
  • Cleaner code with removed RuboCop exceptions

Testing: ⚠️ Needs Investigation

  • Re-enabling flaky tests requires justification
  • Risk of introducing CI instability

Documentation: ❌ Incomplete

  • PR description needs substantial detail about the investigation

Verdict

Status: ⚠️ CHANGES REQUESTED

This PR makes good style improvements, but the core purpose (investigating "descriptor closed" error) needs better documentation. Before merging:

  1. Fill out the PR description with investigation findings
  2. Provide evidence that tests are now stable
  3. Explain the relationship between RSpec and Playwright E2E test coverage
  4. Ensure CI passes consistently across multiple runs

The RSpec style improvements are good and can be merged once the test stability concerns are addressed.


Questions for Author

  1. What is the "descriptor closed" error specifically? Where does it occur?
  2. Have you identified the root cause?
  3. How many times did you run these tests locally, and what was the success rate?
  4. Is there a related issue or discussion about this error that should be referenced?
  5. Should the Playwright E2E tests in streaming.spec.ts be considered the authoritative test suite, making these RSpec tests redundant?

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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 (3)
react_on_rails_pro/spec/dummy/spec/system/integration_spec.rb (3)

14-14: LGTM: Improved assertion style.

The change from have_no_content to not_to have_content is more idiomatic RSpec and improves consistency with standard matcher patterns.


242-242: Type annotation potentially redundant but acceptable.

RSpec typically infers type: :system from the spec/system/ directory path, making this annotation redundant. However, explicit type declarations can improve clarity and are acceptable practice.


410-413: LGTM: Consistent assertion style improvements.

These changes align with the refactor on line 14, making the assertions more idiomatic by using not_to have_text instead of have_no_text.

Also applies to: 420-421

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0b2b5fb and 33105d0.

📒 Files selected for processing (1)
  • react_on_rails_pro/spec/dummy/spec/system/integration_spec.rb (5 hunks)
🧰 Additional context used
🧠 Learnings (6)
📓 Common learnings
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.
📚 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/spec/dummy/spec/system/integration_spec.rb
📚 Learning: 2024-07-27T10:08:35.868Z
Learnt from: theforestvn88
Repo: shakacode/react_on_rails PR: 1620
File: spec/dummy/client/app/startup/HelloTurboStream.jsx:3-3
Timestamp: 2024-07-27T10:08:35.868Z
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/system/integration_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/spec/dummy/spec/system/integration_spec.rb
📚 Learning: 2025-02-12T16:38:06.537Z
Learnt from: Romex91
Repo: shakacode/react_on_rails PR: 1697
File: package-scripts.yml:28-28
Timestamp: 2025-02-12T16:38:06.537Z
Learning: The file `node_package/lib/ReactOnRails.full.js` is autogenerated during the build process and should not be present in the repository.

Applied to files:

  • react_on_rails_pro/spec/dummy/spec/system/integration_spec.rb
📚 Learning: 2025-02-13T16:50:47.848Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1644
File: node_package/src/clientStartup.ts:18-21
Timestamp: 2025-02-13T16:50:47.848Z
Learning: In the react_on_rails module, the `reactOnRailsPageUnloaded` function in clientStartup.ts is intentionally kept private as it's only used internally as a callback for `onPageUnloaded`.

Applied to files:

  • react_on_rails_pro/spec/dummy/spec/system/integration_spec.rb
⏰ 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). (4)
  • GitHub Check: build-dummy-app-webpack-test-bundles
  • GitHub Check: build-dummy-app-webpack-test-bundles
  • GitHub Check: lint-js-and-ruby
  • GitHub Check: claude-review
🔇 Additional comments (2)
react_on_rails_pro/spec/dummy/spec/system/integration_spec.rb (2)

113-113: LGTM: Rubocop comment cleanup.

Removing the inline rubocop disable comments suggests these are no longer needed. Ensure that your linter passes without warnings on these lines.

Also applies to: 177-177, 184-184, 189-189, 247-247


425-428: The premise of this review comment is incorrect.

The tests at lines 425-428 and 430-432 are not being re-enabled. They are already active in the codebase with no skip keyword. The PR's changes to this file are purely stylistic refactoring (converting have_no_* to not_to have_* and removing rubocop disable comments), not behavioral changes that re-enable previously skipped tests. Additionally, no "descriptor closed" error appears in the codebase as a concrete error string to verify against.

Likely an incorrect or invalid review comment.

@claude
Copy link

claude bot commented Nov 10, 2025

Updated Code Review

This PR refactors system test assertions and re-enables two previously skipped test suites. The changes improve RSpec style but raise test stability questions.

Strengths

  1. RSpec Best Practices (lines 14, 410-412, 419-420): Correctly replaced have_no_content/have_no_text with not_to have_content/not_to have_text
  2. Code Cleanup: Removed 5 unnecessary RuboCop disable comments
  3. Consistency: Added type: system annotation (line 242)
  4. Regex Improvement (line 399): Better handles webpack content hashing

Critical Concerns

  1. Re-enabling Flaky Tests Without Documentation

    • Lines 425-428: Previously skipped as flaky, replaced by Playwright E2E tests
    • Lines 430-433: Previously WIP in another branch
    • Questions: What was descriptor closed error? What investigation confirmed resolution? Why re-enable when Playwright provides coverage?
  2. Missing PR Context: Template incomplete, no error description, checklist unchecked

Recommendations

Before Merging:

  1. Update PR description with investigation details and root cause
  2. Clarify test strategy (why both RSpec and Playwright?)
  3. Validate stability (5+ CI runs)
  4. Consider gradual re-enabling if issues persist

Code Quality

  • Style: Excellent (follows RSpec standards)
  • Changes: Good (all appropriate)
  • Documentation: Poor (missing context)
  • Risk: Medium (potential CI flakiness)

Verdict: CHANGES REQUESTED

Code changes are good, but PR needs better documentation about the investigation findings and justification for re-enabling previously flaky tests.

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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 (1)
react_on_rails_pro/spec/dummy/client/node-renderer.js (1)

45-45: Clarify the rationale for the fixed 100-minute interval and address inconsistency.

The hardcoded value of 100 minutes creates an inconsistency with line 48, which still uses environment-based logic (env.CI ? 0.01 : 1). Additionally, 100 minutes seems quite long for a test/dummy application.

Since this PR is investigating a "descriptor closed" error, is this fixed value intended to create more deterministic test conditions? Please clarify:

  1. Why was 100 minutes specifically chosen?
  2. Is the inconsistency with line 48's dynamic approach intentional?
  3. How does this change help investigate the descriptor closed error?

Consider adding a comment explaining the rationale for this specific value.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cd7e7b0 and 7212568.

📒 Files selected for processing (1)
  • react_on_rails_pro/spec/dummy/client/node-renderer.js (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
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.
⏰ 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). (4)
  • GitHub Check: claude-review
  • GitHub Check: build-dummy-app-webpack-test-bundles
  • GitHub Check: build-dummy-app-webpack-test-bundles
  • GitHub Check: lint-js-and-ruby

@claude
Copy link

claude bot commented Nov 10, 2025

PR Review: Investigate descriptor closed error

Summary

This PR appears to be investigating flaky test failures related to a descriptor closed error by re-enabling previously skipped system tests, improving test assertions, and updating regex patterns for bundle matching.

Code Quality - Positive Changes

  1. Better RSpec Syntax - Correctly changed have_no_content to not_to have_content and have_no_text to not_to have_text. This is the preferred RSpec style.

  2. Removed Linting Suppressions - Removed rubocop:disable comments throughout the file. Good practice to follow linting rules.

  3. More Robust Bundle Matching - Changed from exact string match to regex pattern that handles hashed bundle names like client-bundle.abc123.js

  4. Added Test Metadata - Added type: :system to Manual client hydration test for explicit test categorization.

CRITICAL ISSUE - Hardcoded Worker Restart Interval

Changed in react_on_rails_pro/spec/dummy/client/node-renderer.js:45 from dynamic config to hardcoded 100 minutes.

Problems:

  • Removes environment-specific configuration (CI vs development)
  • Sets restart to 100 minutes regardless of environment
  • The commit message says revert this: increase the workers restart interval but the final diff shows it is still set to 100
  • This appears to be debugging code that should NOT be merged

Recommendation: Revert this change before merging. The original logic was better with CI at 2 minutes and development configurable.

Other Concerns

  1. Incomplete PR Description - Template placeholders not filled in, no explanation of the descriptor closed error or findings

  2. Re-enabling Skipped Tests Without Context - Two previously skipped tests are now enabled without explanation of why they were skipped or what changed

  3. Missing Test Evidence - No information about whether tests now pass consistently or what the root cause was

Recommendations

MUST FIX BEFORE MERGE:

  • Revert the allWorkersRestartInterval: 100 change
  • Complete the PR description with findings
  • Fill out the PR checklist

SHOULD ADDRESS:

  • Add comments explaining why previously skipped tests are now safe
  • Provide context about the descriptor closed error resolution

Verdict

Not Ready to Merge - The hardcoded worker restart interval appears to be debugging code. The RSpec improvements are good but the node-renderer.js change should be reverted and more context is needed.

Review generated with guidance from CLAUDE.md conventions

@claude
Copy link

claude bot commented Nov 10, 2025

Code Review

Thank you for investigating the descriptor closed error. I have reviewed the changes and have several concerns.

Critical Concerns

1. Streaming Request Retry Logic Change (High Risk)

The change in request.rb:102 removes the conditional that uses connection_without_retries for streaming requests. This appears to contradict the original fix for issue #1895, which documented that using retries for streaming requests causes body duplication when connection errors occur mid-stream.

Questions:

  • Has the root cause of descriptor closed been identified and resolved?
  • Have you verified streaming requests won't duplicate chunks when retries occur?
  • What testing was done to ensure this doesn't reintroduce the body duplication issue?

2. Re-enabling Previously Skipped Tests

PR #1941 (merged 2 days ago) skipped these tests because they are flaky and were replaced by Playwright E2E tests. Why re-enable them now?

Code Quality Issues

  • Missing PR description and root cause analysis
  • Gemfile.lock shows Shakapacker upgraded from 8.0.0 to 9.3.0 (major version jump) - was this intentional?
  • PR template checklist items not completed

Positive Changes

  • Modernizing Capybara assertions
  • Removing inline RuboCop disable comments
  • Adding proper system test metadata
  • Better bundle filename matching with regex

Recommendations

  1. Document investigation findings in PR description
  2. Justify why removing connection_without_retries is safe for streaming
  3. Verify Shakapacker upgrade is intentional
  4. Consider splitting into separate PRs
  5. Update CHANGELOG per CLAUDE.md guidelines
  6. Run bundle exec rubocop before merging

The investigation is valuable work, but needs clearer documentation and justification given the risks with streaming retry logic.

@claude
Copy link

claude bot commented Nov 10, 2025

Code Review: Investigate descriptor closed error

Thank you for investigating this issue! Here's my feedback on the changes:

🔍 Summary

This PR makes three key changes:

  1. Always use retry-enabled connection for streaming requests (request.rb:102)
  2. Un-skip previously flaky streaming tests
  3. Improve test assertions (RSpec best practices)

Positive Changes

1. Test Assertion Improvements

The changes from have_no_text to not_to have_text and have_no_content to not_to have_content follow RSpec best practices and improve readability:

# Before
expect(page).to have_no_text "Loading branch1"

# After  
expect(page).not_to have_text "Loading branch1"

This is the recommended RSpec style and makes negations more explicit.

2. Regex Pattern for Bundle Matching

The change from exact string match to regex for client bundle detection (line 399) is more robust:

# Handles webpack hash suffixes like client-bundle.abc123.js
expect(page.html).to match(/client-bundle[^"]*.js/)

This accommodates webpack's content hashing in production builds.


⚠️ Critical Concerns

1. Streaming Connection Change Lacks Justification 🚨

Line 102 in request.rb:

# Before
conn = post_options[:stream] ? connection_without_retries : connection

# After
conn = connection  # Always use retry-enabled connection

Issues:

  • The PR description is empty - no explanation of why this change fixes the descriptor closed error
  • Contradicts existing comments (lines 87-89, 100-101) that explicitly state retry-enabled connections cause body duplication in streaming
  • Previous issue [Important] Fix the body duplication problem when an retry happen after a connection error #1895 was specifically about preventing body duplication by using connection_without_retries for streaming
  • StreamRequest class was designed to handle retries for streaming - bypassing it may reintroduce bugs

Questions:

  • How does this fix the descriptor closed error?
  • What testing confirmed body duplication doesn't occur?
  • Were there specific error logs that led to this conclusion?

2. Missing CHANGELOG Entry 📝

According to CLAUDE.md, user-visible changes (bug fixes affecting behavior) should be documented in CHANGELOG.md with format:

[PR 1970](https://github.com/shakacode/react_on_rails/pull/1970) by [AbanoubGhadban](https://github.com/AbanoubGhadban)

3. Un-skipping Flaky Tests Without Evidence 🧪

The PR removes skip markers from two test suites (lines 425, 431) but doesn't provide:

  • Evidence that tests are now stable
  • Root cause analysis of why they were flaky
  • Multiple successful local runs or CI runs

Risk: If these tests are still flaky, it will cause intermittent CI failures for all contributors.

4. Gemfile.lock Changes Unexplained

The Shakapacker upgrade from 8.0.0 → 9.3.0 is a major version bump that:

  • May introduce breaking changes
  • Should be in a separate PR with full testing
  • Requires coordination with the CLAUDE.md instructions about CI configuration switching

🔧 Recommendations

  1. Fill out the PR description with:

    • What the descriptor closed error was
    • When it occurred (which tests, conditions)
    • How the connection change fixes it
    • Why body duplication is no longer a concern
  2. Add test evidence:

    • Screenshot of multiple successful CI runs
    • Local test runs with bundle exec rake run_rspec:all_dummy
    • Specific reproduction steps for the original error
  3. Consider splitting this PR:

    • Core fix (connection change) + tests → one PR
    • Shakapacker upgrade → separate PR
    • Test cleanup (assertions) → can stay or separate
  4. Run mandatory pre-commit checks:

    bundle exec rubocop
    rake autofix
  5. Update CHANGELOG.md if this fixes user-visible streaming issues.

  6. Verify no regression with the body duplication issue from [Important] Fix the body duplication problem when an retry happen after a connection error #1895.


🎯 Testing Checklist

Before merging, please verify:

  • bundle exec rake run_rspec:all_dummy passes locally
  • All CI jobs pass (check both latest and minimum configurations if applicable)
  • Streaming tests run successfully 3+ times consecutively
  • No console errors about body duplication in streaming scenarios
  • RuboCop passes: bundle exec rubocop

📚 References


Overall: The test improvements are great, but the core streaming connection change needs thorough documentation and testing evidence before merging. This is a critical code path for server-side rendering functionality.

@claude
Copy link

claude bot commented Nov 11, 2025

Code Review

Summary

This PR investigates and attempts to fix the descriptor closed error by modifying the HTTP connection retry logic for streaming requests. The changes include:

  1. Removing the conditional logic that used connection_without_retries for streaming requests
  2. Enabling RSpec test skips (re-enabling previously skipped tests)
  3. Updating test assertions to use modern RSpec syntax

🔴 Critical Issues

1. Potential Body Duplication in Streaming Requests

Location: react_on_rails_pro/lib/react_on_rails_pro/request.rb:102

-conn = post_options[:stream] ? connection_without_retries : connection
+conn = connection

Issue: This change removes the safeguard against body duplication in streaming requests. The original logic was carefully designed to prevent this exact problem.

Evidence from code comments:

  • Line 87-89: "NOTE: We maintain two separate HTTP connection pools to handle streaming vs non-streaming requests... This tradeoff is acceptable to prevent body duplication in streaming responses."
  • Line 100-101: "For streaming requests, use connection without retries to prevent body duplication. The StreamRequest class handles retries properly by starting fresh requests"
  • stream_request.rb:78-80: "Retry logic for streaming requests is handled here by starting fresh requests. The HTTPx connection used for streaming has retries disabled to prevent body duplication when partial chunks are already sent to the client."

Why this is problematic:

  1. The connection method uses HTTPx with max_retries: 1 (line 246)
  2. If a retry happens mid-stream, partial chunks already sent to the client will be duplicated
  3. The StreamRequest class has its own retry logic at a higher level (lines 81-92 in stream_request.rb)
  4. You're now applying two layers of retries - one at the HTTPx level and one at the StreamRequest level

Reference: This relates to issue #1895 mentioned in line 245 comments.

Recommendation:

  • If this change is necessary to fix the descriptor closed error, you need to:
    1. Document why the original design is insufficient
    2. Add safeguards to prevent body duplication
    3. Add tests that verify streaming behavior with retries
    4. Consider if the issue is actually in the StreamRequest retry logic instead

⚠️ Moderate Issues

2. Test Re-enablement Without Justification

Location: react_on_rails_pro/spec/dummy/spec/system/integration_spec.rb:425-434

-describe "Pages/stream_async_components_for_testing", :js,
-         skip: "Flaky test replaced by Playwright E2E tests in e2e-tests/streaming.spec.ts" do
+describe "Pages/stream_async_components_for_testing", :js do

-describe "React Router Sixth Page", :js, skip: "Work in progress in another branch: justin808/fix-body-dup-retry" do
+describe "React Router Sixth Page", :js do

Issue:

  • The first test was disabled because it was "flaky" and replaced by Playwright tests
  • The second test was disabled because it's "work in progress" and specifically mentions fix-body-dup-retry in the skip message
  • Both are now re-enabled without explanation

Questions:

  1. Does re-enabling the flaky test mean it's no longer flaky? What changed?
  2. The fix-body-dup-retry skip message suggests this PR might be related to body duplication retry issues - is that the case?
  3. Are the Playwright E2E tests still running? If so, are these RSpec tests redundant?

Recommendation:

  • Document why these tests are being re-enabled
  • If they were flaky, explain what makes them stable now
  • Consider if both RSpec and Playwright tests are needed

3. Incomplete PR Description

The PR body still contains placeholder text and doesn't explain:

  • What the "descriptor closed" error is
  • When/how it reproduces
  • Why this specific change fixes it
  • What testing was done to verify the fix

Recommendation: Update the PR description with:

  • Steps to reproduce the error
  • Root cause analysis
  • Why changing the connection retry logic fixes it
  • Test results showing the fix works

✅ Positive Changes

4. Modern RSpec Syntax

The test changes improve code quality:

-expect(subject).to have_no_content new_text
+expect(subject).not_to have_content new_text

-expect(page).to have_no_text "Loading branch1"
+expect(page).not_to have_text "Loading branch1"

These changes align with modern RSpec best practices (not_to is preferred over have_no_*).

5. Removed Unnecessary RuboCop Disables

-click_link "Hello World" # rubocop:disable Capybara/ClickLinkOrButtonStyle
+click_link "Hello World"

Good cleanup - suggests RuboCop rules may have changed or these are no longer violations.

6. Improved Regex Matcher

-expect(page.html).to include("client-bundle.js")
+expect(page.html).to match(/client-bundle[^"]*\.js/)

This is more robust - handles webpack chunkhashes like client-bundle.abc123.js.


🔍 Security & Performance Considerations

Performance:

  • Using connection (with retries) for all requests may increase latency for streaming requests that fail, as they'll retry at the HTTPx level before StreamRequest's retry logic kicks in
  • Maintaining two connection pools doubles memory footprint - this change doesn't eliminate the second pool, so we pay the memory cost without the benefit

Security:

  • No security concerns identified

📝 Testing Recommendations

Required tests before merging:

  1. Streaming retry test: Verify that when a streaming request fails mid-stream, the retry doesn't duplicate chunks
  2. Descriptor closed test: Add a test that reproduces the descriptor closed error and verifies this fix resolves it
  3. Integration test: Verify both streaming tests actually pass consistently (not just locally)

Test coverage gaps:

  • No new tests added to verify the fix
  • Re-enabled tests may not actually test the specific bug being fixed

🎯 Recommended Actions

Before merging:

  1. Verify this approach: Confirm with the team that using connection for streaming is the right fix
  2. Add documentation: Update the PR description with root cause analysis
  3. Add tests: Create tests that verify streaming retries don't duplicate chunks
  4. Remove unused pool: If connection_without_retries is no longer used, remove it and its @connection_without_retries instance variable
  5. Update comments: Lines 87-89 and 100-101 comments are now outdated
  6. ⚠️ Consider alternatives: Could the issue be fixed in StreamRequest's retry logic instead?

Questions for the author:

  1. What exactly is the "descriptor closed" error? Is it a Errno::EBADF or HTTPx-specific?
  2. Have you verified streaming responses don't have duplicated chunks with this change?
  3. Did you test with actual network failures/timeouts to ensure retry behavior works correctly?
  4. Why does using connection fix the descriptor closed error? What's the root cause?

📚 References


Overall Assessment: ⚠️ This PR needs more investigation before merging. The core change contradicts carefully documented design decisions about preventing body duplication in streaming requests. While it may fix the descriptor closed error, it could introduce the exact problem it was designed to prevent.

@claude
Copy link

claude bot commented Nov 11, 2025

Code Review

Thank you for working on this PR! I've reviewed the changes and have important feedback regarding the implementation.

Critical Concerns

1. Streaming Request Retry Logic Change (HIGH PRIORITY)
File: react_on_rails_pro/lib/react_on_rails_pro/request.rb:102

The change from conditional connection selection to always using connection (with retries) contradicts the existing comment and design. The comment at line 100-101 explicitly states streaming requests should NOT use retries to prevent body duplication. StreamRequest#each_chunk (line 78-80 in stream_request.rb) documents that retry logic is handled at the stream level precisely because the HTTP connection has retries disabled.

This creates a conflict: HTTP-level retries + stream-level retries could cause duplicate content delivery to clients. The skip message in the old test (line 431) mentions fix-body-dup-retry suggesting this was a known issue.

Recommendation: If this change is intentional to fix the descriptor closed error, you need to:

  1. Update/remove the misleading comments in both files
  2. Review the retry logic in StreamRequest#each_chunk to prevent double-retry scenarios
  3. Add tests that verify no body duplication occurs during retry scenarios
  4. Document WHY this approach is now correct

Test Changes

2. Removed Test Skip Markers
You've removed skip markers from two test suites that were previously skipped. Have you verified these tests now pass consistently? The second test's skip message directly references the body duplication retry issue. Consider running these tests multiple times locally.

3. Test Assertion Updates - Good! The changes from have_no_content to not_to have_content follow modern RSpec/Capybara best practices.

4. Regex Pattern for Bundle Detection - Changed to use regex matching. If Shakapacker 9.3.0 adds hash suffixes to bundle names, this makes sense. Consider adding a comment explaining why.

Missing Requirements (per CLAUDE.md)

5. PR Description - Still template boilerplate. Please explain what descriptor closed error is, the root cause, and why this fixes it.

6. Changelog Entry - Per CLAUDE.md, user-visible bug fixes require a CHANGELOG entry.

7. Tests - Add regression test for the descriptor closed error if possible, and tests verifying no body duplication.

Action Items Before Merging

  1. Explain the descriptor closed error and root cause in PR description
  2. Update/remove conflicting comments about retry logic in both request.rb and stream_request.rb
  3. Verify no body duplication occurs (add test if possible)
  4. Confirm removed skip tests pass consistently (run 10+ times)
  5. Add CHANGELOG entry if user-facing
  6. Run bundle exec rubocop and ensure it passes
  7. Ensure all files end with newlines

Overall Assessment: The code changes are minimal and targeted, but the streaming retry logic change is significant and needs careful validation. The test improvements are welcome, but the core fix requires better documentation and verification.

Let me know if you need help with any of these items!

@claude
Copy link

claude bot commented Nov 11, 2025

Code Review - PR #1970: Investigate descriptor closed error

Summary

This PR makes test assertion improvements and changes streaming request handling. The changes include refactoring RSpec assertions to use modern syntax, removing RuboCop disable comments, un-skipping previously disabled tests, and critically changing how streaming requests handle retries.


🔴 Critical Issues

1. Breaking Change in Streaming Request Retry Logic (react_on_rails_pro/lib/react_on_rails_pro/request.rb:102)

Current (before PR):

conn = post_options[:stream] ? connection_without_retries : connection

Changed to:

conn = connection

Impact:

  • Streaming requests now use the connection WITH retries (previously used connection_without_retries)
  • The code comment on line 100-101 explicitly warns: "For streaming requests, use connection without retries to prevent body duplication"
  • This change contradicts the documented architecture (lines 87-89) which explains why two separate connection pools exist
  • This is a significant behavioral change that could cause the exact "descriptor closed" errors you're investigating

Questions:

  1. Is this change intentional or accidental?
  2. Have you tested that streaming requests don't experience body duplication issues with retry logic enabled?
  3. The comment warns about this exact scenario - why is this change safe?

Dead Code Created:

  • connection_without_retries method (lines 95-97) is now unused
  • @connection_without_retries in reset_connection (lines 12, 14) is now unused
  • Comment on lines 87-89 is now misleading

2. Re-enabling Previously Skipped Flaky Tests

Before PR (lines 425-426):

describe "Pages/stream_async_components_for_testing", :js,
         skip: "Flaky test replaced by Playwright E2E tests in e2e-tests/streaming.spec.ts"

After PR:

describe "Pages/stream_async_components_for_testing", :js do

Concerns:

  • Tests were previously skipped because they were flaky and already covered by Playwright E2E tests
  • No evidence provided that the flakiness has been resolved
  • Could introduce CI instability and false failures for other contributors
  • The PR title "Investigate descriptor closed error" suggests this is exploratory, not a fix

Also applies to: Lines 431-433 ("React Router Sixth Page" test previously marked as WIP)


⚠️ Moderate Issues

3. Inconsistent Test Assertions (integration_spec.rb:399)

Changed from:

expect(page.html).to include("client-bundle.js")

Changed to:

expect(page.html).to match(/client-bundle[^\"]*\.js/)

Analysis:

  • Good change - handles webpack content hashing (e.g., client-bundle.abc123.js)
  • However, this is a functional change, not just a style refactor
  • Should be tested to ensure it doesn't break in edge cases

✅ Positive Changes

4. RSpec Best Practices (multiple locations)

Improvements:

  • Lines 14, 410-412, 419-420: Changed have_no_content/have_no_text to not_to have_content/not_to have_text
  • This is the idiomatic RSpec style and improves readability
  • Removed 5 unnecessary RuboCop disable comments for Capybara/ClickLinkOrButtonStyle

5. Test Metadata (line 242)

  • Added explicit type: :system annotation for consistency (though typically inferred from spec/system/ path)

📋 Code Quality Assessment

Category Rating Notes
Code Style ✅ Excellent RSpec improvements follow best practices
Test Changes 🔴 Concerning Re-enabling flaky tests without evidence of fixes
Logic Changes 🔴 Critical Streaming retry behavior change contradicts documented design
Documentation ❌ Poor PR description incomplete, checklist unchecked
Risk Level 🔴 HIGH Could introduce streaming bugs and CI flakiness

🚨 Recommendations

Before Merging:

  1. Clarify Intent of Streaming Change

    • Is line 102 change intentional or accidental?
    • If intentional, update/remove comments on lines 87-89, 100-101
    • Remove dead code: connection_without_retries method and related cleanup
    • Document why body duplication is no longer a concern
  2. Justify Test Re-enabling

    • Provide evidence that flaky tests are now stable (e.g., "ran 20 times locally, all passed")
    • Explain what fixed the underlying issues
    • If not fixed, revert the un-skip changes to avoid CI noise
  3. Update PR Description

    • Fill out the template with actual information
    • Describe what "descriptor closed" error is and where it occurs
    • Document investigation findings
    • Complete the checklist
  4. Validate Changes

    • Run bundle exec rubocop (required per CLAUDE.md)
    • Run yarn run eslint --report-unused-disable-directives
    • Run bundle exec rake run_rspec:all_dummy multiple times to check stability
    • Ensure trailing newlines in all files
  5. Consider Splitting the PR

    • Style improvements (RSpec refactoring) could be merged separately
    • Streaming logic change needs its own PR with thorough testing
    • Test re-enabling needs separate justification

❓ Questions for Author

  1. What is the "descriptor closed" error? Where does it occur? In which logs/tests?
  2. Is the streaming retry change intentional? If so, what testing confirms it's safe?
  3. What investigation was performed? What were the findings?
  4. Why re-enable flaky tests when Playwright E2E coverage already exists?
  5. Have you run the tests locally? How many times? What was the success rate?
  6. Related work? Is branch justin808/fix-body-dup-retry merged? Related to this PR?

🎯 Verdict

Status: 🔴 CHANGES REQUESTED

Blockers:

  1. Clarify and justify the streaming request retry logic change (line 102)
  2. Provide evidence that re-enabled tests are stable
  3. Complete PR description with investigation details

The RSpec style improvements are excellent and ready to merge once separated from the higher-risk changes.


📚 References

  • Per CLAUDE.md: Always run bundle exec rubocop before commits
  • Per CLAUDE.md: Run yarn run eslint --report-unused-disable-directives before commits
  • Test locally with: bundle exec rake run_rspec:all_dummy
  • Related comment in code: react_on_rails_pro/lib/react_on_rails_pro/request.rb:100-101

@AbanoubGhadban AbanoubGhadban force-pushed the investigate-description-closed-error branch from d8b6c86 to d3c02bf Compare November 11, 2025 15:20
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
react_on_rails_pro/packages/node-renderer/src/shared/configBuilder.ts (1)

205-222: Preserve explicit 0 timeout when logging config

Using truthy checks means a legit 0 (which your docs say disables the kill) is treated as “not provided”, so the logs wrongly claim the env var was used and the sanitized config prints <NOT PROVIDED>. Please preserve 0 values explicitly.

-    GRACEFUL_WORKER_RESTART_TIMEOUT:
-      !userConfig.gracefulWorkerRestartTimeout && env.GRACEFUL_WORKER_RESTART_TIMEOUT,
+    GRACEFUL_WORKER_RESTART_TIMEOUT:
+      !('gracefulWorkerRestartTimeout' in userConfig) && env.GRACEFUL_WORKER_RESTART_TIMEOUT,
@@
-        gracefulWorkerRestartTimeout: aConfig.gracefulWorkerRestartTimeout || defaultValue,
+        gracefulWorkerRestartTimeout: aConfig?.gracefulWorkerRestartTimeout ?? defaultValue,
🧹 Nitpick comments (1)
react_on_rails_pro/docs/node-renderer/troubleshooting.md (1)

5-5: Polish the wording for clarity

“Ensure to not set” reads awkwardly. A small tweak improves readability.

-- If your app contains streamed pages that take too much time to be streamed to the client, ensure to not set the `gracefulWorkerRestartTimeout` parameter or set to a high number, so the worker is not killed while it's still serving an active request.
+- If your app streams pages for a long time, make sure you either leave `gracefulWorkerRestartTimeout` unset or set it to a high number so the worker isn’t killed while it’s still serving an active request.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d3c02bf and 31fa75c.

📒 Files selected for processing (9)
  • react_on_rails_pro/docs/installation.md (1 hunks)
  • react_on_rails_pro/docs/node-renderer/js-configuration.md (1 hunks)
  • react_on_rails_pro/docs/node-renderer/troubleshooting.md (1 hunks)
  • react_on_rails_pro/packages/node-renderer/src/master.ts (2 hunks)
  • react_on_rails_pro/packages/node-renderer/src/master/restartWorkers.ts (2 hunks)
  • react_on_rails_pro/packages/node-renderer/src/shared/configBuilder.ts (4 hunks)
  • react_on_rails_pro/packages/node-renderer/src/shared/utils.ts (1 hunks)
  • react_on_rails_pro/packages/node-renderer/src/worker.ts (2 hunks)
  • react_on_rails_pro/packages/node-renderer/src/worker/handleGracefulShutdown.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • react_on_rails_pro/docs/installation.md
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2024-12-12T13:07:09.929Z
Learnt from: alexeyr-ci
Repo: shakacode/react_on_rails PR: 1644
File: node_package/src/ReactOnRailsRSC.ts:87-87
Timestamp: 2024-12-12T13:07:09.929Z
Learning: When handling errors in 'node_package/src/ReactOnRailsRSC.ts', include the error stack in error messages in development and test environments to aid debugging.

Applied to files:

  • react_on_rails_pro/packages/node-renderer/src/worker.ts
📚 Learning: 2025-02-13T16:50:47.848Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1644
File: node_package/src/clientStartup.ts:18-21
Timestamp: 2025-02-13T16:50:47.848Z
Learning: In the react_on_rails module, the `reactOnRailsPageUnloaded` function in clientStartup.ts is intentionally kept private as it's only used internally as a callback for `onPageUnloaded`.

Applied to files:

  • react_on_rails_pro/packages/node-renderer/src/worker.ts
🧬 Code graph analysis (4)
react_on_rails_pro/packages/node-renderer/src/worker/handleGracefulShutdown.ts (2)
react_on_rails_pro/packages/node-renderer/src/worker/types.ts (1)
  • FastifyInstance (9-9)
react_on_rails_pro/packages/node-renderer/src/shared/utils.ts (1)
  • SHUTDOWN_WORKER_MESSAGE (14-14)
react_on_rails_pro/packages/node-renderer/src/master/restartWorkers.ts (1)
react_on_rails_pro/packages/node-renderer/src/shared/utils.ts (1)
  • SHUTDOWN_WORKER_MESSAGE (14-14)
react_on_rails_pro/packages/node-renderer/src/worker.ts (1)
react_on_rails_pro/packages/node-renderer/tests/htmlStreaming.test.js (2)
  • app (7-7)
  • app (23-23)
react_on_rails_pro/packages/node-renderer/src/master.ts (1)
react_on_rails_pro/spec/dummy/client/node-renderer.js (1)
  • config (30-66)
🪛 LanguageTool
react_on_rails_pro/docs/node-renderer/js-configuration.md

[grammar] ~25-~25: Ensure spelling is correct
Context: ...id situations where worker stucks at an inifite loop and doesn't restart. This config i...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

react_on_rails_pro/docs/node-renderer/troubleshooting.md

[style] ~5-~5: Consider changing the order of words to improve your wording.
Context: ...me to be streamed to the client, ensure to not set the gracefulWorkerRestartTimeout ...

(TO_NOT_VB)

⏰ 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: build-dummy-app-webpack-test-bundles
  • GitHub Check: lint-js-and-ruby
  • GitHub Check: build-dummy-app-webpack-test-bundles
  • GitHub Check: markdown-link-check
  • GitHub Check: claude-review
🔇 Additional comments (4)
react_on_rails_pro/packages/node-renderer/src/master.ts (1)

22-65: Sequential rescheduling logic looks solid

Passing the timeout through and re-scheduling via setTimeout avoids overlapping restarts and keeps the cadence predictable. Nice improvement.

react_on_rails_pro/packages/node-renderer/src/shared/utils.ts (1)

14-14: New shutdown message constant fits well

Centralizing the shutdown message string here keeps the master/worker handshake tidy. Looks good.

react_on_rails_pro/packages/node-renderer/src/worker.ts (1)

20-132: Glad to see graceful shutdown wired in

Hooking the Fastify instance into handleGracefulShutdown right after creation makes the worker opt-in automatic. Thanks for threading it through.

react_on_rails_pro/packages/node-renderer/src/worker/handleGracefulShutdown.ts (1)

1-49: Graceful shutdown hook looks correct

The message handler, active-request accounting, and destroy/disconnect flow align with how cluster workers should wind down. Implementation looks good to me.

within("h3") do
if expect_no_change
expect(subject).to have_no_content new_text
expect(subject).not_to have_content new_text
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

All of these changes at this file just reverts the changes made at the PR that skips flaky tests (I think many of these changes unnecessary at that PR)

@AbanoubGhadban AbanoubGhadban changed the title Investigate descriptor closed error Fix descriptor closed error caused by workers restart while request is in progress Nov 12, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a 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/packages/node-renderer/src/master/restartWorkers.ts (2)

27-27: Consider using a TypeScript type guard for better type safety.

While the current filter (w) => !!w works correctly, using an explicit type guard would provide better TypeScript type narrowing and make the intent clearer.

Apply this diff to use a type guard:

-  for (const worker of Object.values(cluster.workers).filter((w) => !!w)) {
+  for (const worker of Object.values(cluster.workers).filter((w): w is cluster.Worker => w !== undefined)) {

This matches the suggestion from the past review and eliminates any remaining ambiguity about the worker type within the loop.


33-33: Fix typo in comment.

The comment contains a typo: "inteded" should be "intended".

Apply this diff:

-    // It's inteded to restart worker in sequence, it shouldn't happens in parallel
+    // It's intended to restart workers in sequence, it shouldn't happen in parallel
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 31fa75c and 4b0f581.

📒 Files selected for processing (3)
  • react_on_rails_pro/docs/node-renderer/js-configuration.md (1 hunks)
  • react_on_rails_pro/packages/node-renderer/src/master/restartWorkers.ts (2 hunks)
  • react_on_rails_pro/spec/dummy/spec/system/integration_spec.rb (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • react_on_rails_pro/docs/node-renderer/js-configuration.md
🧰 Additional context used
🧠 Learnings (5)
📚 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/spec/dummy/spec/system/integration_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/spec/dummy/spec/system/integration_spec.rb
📚 Learning: 2025-02-12T16:38:06.537Z
Learnt from: Romex91
Repo: shakacode/react_on_rails PR: 1697
File: package-scripts.yml:28-28
Timestamp: 2025-02-12T16:38:06.537Z
Learning: The file `node_package/lib/ReactOnRails.full.js` is autogenerated during the build process and should not be present in the repository.

Applied to files:

  • react_on_rails_pro/spec/dummy/spec/system/integration_spec.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/system/integration_spec.rb
📚 Learning: 2025-02-13T16:50:47.848Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1644
File: node_package/src/clientStartup.ts:18-21
Timestamp: 2025-02-13T16:50:47.848Z
Learning: In the react_on_rails module, the `reactOnRailsPageUnloaded` function in clientStartup.ts is intentionally kept private as it's only used internally as a callback for `onPageUnloaded`.

Applied to files:

  • react_on_rails_pro/spec/dummy/spec/system/integration_spec.rb
🧬 Code graph analysis (1)
react_on_rails_pro/packages/node-renderer/src/master/restartWorkers.ts (1)
react_on_rails_pro/packages/node-renderer/src/shared/utils.ts (1)
  • SHUTDOWN_WORKER_MESSAGE (14-14)
⏰ 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). (9)
  • GitHub Check: build-dummy-app-webpack-test-bundles
  • GitHub Check: build
  • GitHub Check: build-dummy-app-webpack-test-bundles (3.4, 22, latest)
  • GitHub Check: build-dummy-app-webpack-test-bundles
  • GitHub Check: lint-js-and-ruby
  • GitHub Check: rspec-package-tests (3.4, latest)
  • GitHub Check: rspec-package-tests (3.2, minimum)
  • GitHub Check: markdown-link-check
  • GitHub Check: claude-review
🔇 Additional comments (8)
react_on_rails_pro/packages/node-renderer/src/master/restartWorkers.ts (2)

8-8: Good addition of graceful shutdown support.

The import of SHUTDOWN_WORKER_MESSAGE and the updated async function signature with gracefulWorkerRestartTimeout parameter align well with the new sequential restart flow. The changes enable coordinated graceful shutdown across workers.

Also applies to: 18-21


35-58: Well-structured sequential restart logic with graceful shutdown.

The implementation correctly handles the sequential restart flow:

  • Sends SHUTDOWN_WORKER_MESSAGE to signal graceful shutdown
  • Waits for worker exit with an optional timeout
  • Forces termination via worker.destroy() if the timeout expires
  • Enforces delay between individual worker restarts

The timeout handling (lines 36-52) works as intended. As you clarified in the past review, clearTimeout(undefined) is safe in Node.js and the code functions correctly at runtime.

The cleanup on line 49 (worker.off('exit', onExit)) before calling resolve() in the timeout handler is good practice to prevent any potential race conditions.

react_on_rails_pro/spec/dummy/spec/system/integration_spec.rb (6)

14-14: LGTM! Prefer explicit negation.

The change from have_no_content to not_to have_content is a style improvement that makes the assertion more explicit and aligns with RSpec best practices.


242-242: LGTM! Proper system test metadata.

Adding type: :system to the describe block is the correct way to mark system tests in RSpec, ensuring proper configuration and test helpers are applied.


399-399: LGTM! Better pattern matching for hashed asset filenames.

The regex pattern /client-bundle[^\"]*.js/ correctly handles hashed or versioned bundle filenames (e.g., "client-bundle.abc123.js") that result from asset compilation and cache-busting strategies. This makes the test more resilient to changes in the build process.

Also applies to: 406-406


410-412: LGTM! Consistent negation style.

Changing from have_no_text to not_to have_text maintains consistency with the explicit negation style used throughout the file and aligns with RSpec best practices.

Also applies to: 420-421


425-425: Confirm test stability in CI.

The git history confirms these tests were previously skipped (commit a66fb93: "don't skip flaky tests" and 8b32c4c: "don't skip tests"), and they are now properly enabled in the current state. However, the primary concerns require manual verification:

  • Run these tests multiple times in CI to confirm they pass consistently without flakiness
  • Verify that the "descriptor closed" error investigated in this PR is resolved
  • Monitor that the graceful worker restart mechanism prevents re-occurrence of these issues

113-113: ****

The review comment references the removal of RuboCop disable comments from lines 113, 177, 184, and 189, but verification shows:

  • No RuboCop disable/enable comments exist at these lines in the current file
  • The actual changes in commit 4b0f5816 ("tiny fixes and linting") are at line 406 (updating an expect statement), not the referenced lines
  • All lines at 113, 177, 184, and 189 are already RuboCop-compliant (well under the 120-character line length limit)

The code requires no action; the original review comment does not match the actual changes made.

Likely an incorrect or invalid review comment.

@AbanoubGhadban AbanoubGhadban merged commit 6c2b0a9 into master Nov 12, 2025
21 of 22 checks passed
@AbanoubGhadban AbanoubGhadban deleted the investigate-description-closed-error branch November 12, 2025 10:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants