-
-
Notifications
You must be signed in to change notification settings - Fork 638
Fix HTTPX thread-safety issue with thread-local connections for incremental rendering #2116
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: abanoubghadban/pro509/make-renderer-use-ndjson-for-communication
Are you sure you want to change the base?
Conversation
…mental rendering Problem: The HTTPX stream_bidi plugin has a critical limitation - only the thread that creates the bidirectional streaming connection can write chunks to the request stream. This causes race conditions in multi-threaded production environments (e.g., Puma) where different HTTP requests run on different threads. When the first request creates a connection on Thread A and stores it in an instance variable, subsequent requests on Thread B cannot write to that connection, causing connection errors and unpredictable behavior. Solution: Implement thread-local storage for incremental rendering connections. Each thread now gets its own persistent bidirectional streaming connection stored in Thread.current instead of a shared instance variable. Changes: - Modified `incremental_connection` to use `Thread.current[:react_on_rails_incremental_connection]` - Added `reset_thread_local_incremental_connections` to properly clean up all thread-local connections - Updated `reset_connection` to call the new cleanup method - Removed `@incremental_connection` instance variable Trade-offs: - ✅ Eliminates race conditions and thread-safety issues - ✅ Simple implementation using Ruby's built-in thread-local storage - ✅ Each thread has isolated, persistent connection -⚠️ Higher memory usage (one connection per Puma worker thread) -⚠️ Not optimal for high-scale production with many threads This is a temporary solution. Long-term options: 1. Fix HTTPX to support multi-threaded bidirectional streaming 2. Migrate to async-http gem which is thread-safe by design Fixes #2115 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the ✨ Finishing touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Code Review: Thread-Safety Fix for Incremental RenderingI've reviewed the changes in this PR, which addresses a critical thread-safety issue with HTTPX's ✅ What Works Well1. Core Solution is SoundThe switch from instance variables to thread-local storage correctly solves the immediate problem:
2. TypeScript Changes are Correct
3. Good Documentation
|
|
I have reviewed PR #2116. Here is my comprehensive code review feedback. |
✅ Strengths
|
|
|
|
|
🔍 Additional Concerns5. Missing Defensive Check: The addition of 6. No Performance Impact Assessment: The PR mentions memory usage concerns but does not quantify the performance impact. Consider:
|
📋 Checklist Before Merge
🎯 SummaryThis PR correctly identifies and addresses a critical thread-safety bug, but it needs stronger testing before merge. The lack of thread-safety tests is the most significant gap - this fix is worthless if we cannot verify it works and prevent future regressions. The solution is reasonable as a temporary fix but should be clearly marked as such in the code. Make sure to track the long-term solutions mentioned in #2115. Recommendation: Request changes to add thread-safety tests before merging. |
Code ReviewSummaryThis PR addresses a critical thread-safety issue with HTTPX bidirectional streaming by implementing thread-local storage for incremental rendering connections. Overall, the approach is sound for a temporary workaround. 🔴 Critical Issues1. Resource Leak in
|
Code Review: Thread-Local HTTPX Connections for Incremental RenderingOverviewThis PR addresses a critical thread-safety issue in HTTPX's 🟢 Strengths1. Problem Correctly IdentifiedThe root cause analysis is solid - HTTPX's 2. Clean Ruby Implementationdef incremental_connection
Thread.current[:react_on_rails_incremental_connection] ||= create_incremental_connection
end
3. Proper CleanupThe Thread.list.each do |thread|
conn = thread[:react_on_rails_incremental_connection]
conn&.close
thread[:react_on_rails_incremental_connection] = nil
end4. TypeScript Async/Await ConsistencyConverting 🟡 Concerns & Recommendations1. Resource Leaks (CRITICAL)Problem: Thread-local storage persists for the lifetime of the thread. In Puma, worker threads are long-lived. If a thread handles requests for different apps/instances, the connection stored in Scenarios:
Current code: def reset_connection
@standard_connection&.close
@standard_connection = nil
reset_thread_local_incremental_connections # Only called here\!
endWhen is Recommendations:
2. Thread.list Safety (MEDIUM)Problem:
Current code: Thread.list.each do |thread|
conn = thread[:react_on_rails_incremental_connection]
conn&.close
endRecommendations:
3. Memory Usage Documentation (LOW)The PR description mentions memory concerns ("5 threads × 5 workers = 25 connections"), but:
Recommendation: Add a comment in the code: # Thread-local connection for incremental rendering
# Trade-off: One connection per Puma worker thread (e.g., 5 workers × 5 threads = 25 connections)
# Memory overhead: ~X KB per connection (TODO: measure)
# This is a temporary solution until HTTPX supports multi-threaded bidirectional streaming4. TypeScript: Error Handling (LOW)In await executionContext.runInVM(chunk.updateChunk, bundlePath).catch((err: unknown) => {
log.error({ msg: 'Error running incremental render chunk', err, chunk });
});The error is logged but not propagated. Is this intentional? Should the sink be notified of failures? 5. TypeScript: Defensive Null Check (GOOD)The change in - if (log.level === 'debug') {
+ if (log.level === 'debug' && result) {This prevents potential issues with 🔴 Critical IssuesMissing Test CoverageThe PR checklist shows:
This is a critical fix for a threading bug. Without tests:
Recommendations:
📋 Additional Suggestions1. Add ObservabilityTrack connection creation for monitoring: def incremental_connection
Thread.current[:react_on_rails_incremental_connection] ||= begin
Rails.logger.debug { "[ReactOnRailsPro] Creating thread-local incremental connection for thread #{Thread.current.object_id}" }
create_incremental_connection
end
end2. Consider Connection Pool LimitsWith thread-local connections, you bypass HTTPX's connection pool limits. If Puma has 5 threads × 5 workers = 25 threads, you'll have 25 bidirectional connections to the Node renderer. Does the renderer handle this? Is there a max connection limit? 3. Documentation UpdateUpdate the Pro README or configuration docs to mention:
🎯 VerdictOverall: Good temporary fix, but needs safety improvements and tests before merging. Before Merging:
After Merging:
📚 References for Long-Term FixAs mentioned in #2115, consider:
Great work identifying and addressing this critical race condition! With the suggested improvements, this will be a solid temporary solution while pursuing a permanent fix. Generated with Claude Code - Feel free to push back on any suggestions! |
Code Review - PR #2116: HTTPX Thread-Safety FixThank you for tackling this critical thread-safety issue! The overall approach is sound, but I've identified several important concerns that need to be addressed before merging. 🔴 Critical Issues1. Memory Leak Risk in
|
Code Review - Thread Safety Fix for HTTPX Incremental RenderingI've reviewed the changes in this PR that address the critical thread-safety issue with HTTPX bidirectional streaming. Here's my analysis: ✅ Strengths
|
Code ReviewThank you for addressing this critical thread-safety issue! The PR correctly identifies and fixes the HTTPX bidirectional streaming limitation with multi-threaded environments. Here's my detailed review: ✅ Strengths1. Correct Problem Identification
2. Appropriate Solution for the Constraint
3. Good Documentation
|
Code ReviewThank you for addressing the thread-safety issue with HTTPX bidirectional streaming! This is a critical fix for production environments. Here's my detailed review: ✅ Strengths1. Correct Problem IdentificationThe PR correctly identifies that HTTPX's 2. Simple, Effective SolutionUsing 3. Good DocumentationThe PR description clearly explains the problem, solution, trade-offs, and long-term alternatives. 4. Async Handling ImprovementsThe TypeScript changes properly convert 🔍 Issues & Concerns1. Critical: Resource Leak in
|
Problem
The HTTPX
stream_bidiplugin has a critical limitation: only the thread that creates the bidirectional streaming connection can write chunks to the request stream. This causes race conditions in multi-threaded production environments.Race Condition Scenario
First request (Thread A):
@incremental_connectioninstance variableSecond request (Thread B - different Puma worker thread):
@incremental_connectionRoot Cause
stream_bidiplugin requires thread affinity for writesSolution
Implemented thread-local storage for incremental rendering connections:
How It Works
Thread.currentinstead of instance variablereset_thread_local_incremental_connectionsChanges
incremental_connectionto use thread-local storagereset_thread_local_incremental_connectionsmethodreset_connectionto clean up all thread-local connections@incremental_connectioninstance variableTrade-offs
✅ Benefits
Long-Term Solutions
This is a temporary workaround. Permanent solutions being considered:
See #2115 for detailed discussion.
Testing
Related Issues
Fixes #2115
🤖 Generated with Claude Code