[rb] fix silent hang on large WebSocket frame#17286
[rb] fix silent hang on large WebSocket frame#17286Chandan25sharma wants to merge 1 commit intoSeleniumHQ:trunkfrom
Conversation
Review Summary by QodoFix WebSocket hangs and add network interception options
WalkthroughsDescription• Fixes silent hangs on large WebSocket frames by enabling error raising • Increases max frame size to 100MB to handle large CDP payloads • Detects dead listener threads to fail immediately instead of timeout • Elevates WebSocket errors from debug to warn logging level • Adds optional track_cancelled parameter to reduce Network domain events File Changes1. rb/lib/selenium/webdriver/common/websocket_connection.rb
|
Code Review by Qodo
1. Global WebSocket config override
|
- Sets `WebSocket.should_raise = true` to prevent `websocket-ruby` from returning `nil` frames on failure, which previously caused infinite read loops and silent hangs. - Increases the default `WebSocket.max_frame_size` to 100MB (from 20MB) to handle large CDP payloads such as Data URLs and screenshots. - Updates `WebSocketConnection#send_cmd` to fail immediately if the listener thread dies, avoiding a 30-second timeout wait when the connection is already broken. - Changes WebSocket error logging from `debug` to `warn` to make failures visible to users by default. - Adds `track_cancelled` option to `Driver#intercept` (default: `true`), enabling users to opt-out of the `Network` domain to reduce bandwidth from chatty events.
| WebSocket.should_raise = true | ||
| WebSocket.max_frame_size = 100 * 1024 * 1024 | ||
|
|
There was a problem hiding this comment.
1. Global websocket config override 🐞 Bug ☼ Reliability
websocket_connection.rb sets WebSocket.should_raise and WebSocket.max_frame_size at file load time, overriding process-wide websocket-ruby configuration for every Selenium user (even those not using DevTools/BiDi). This can unexpectedly change behavior and memory limits for other WebSocket clients in the same Ruby process and makes the new 100MB frame size an unconditional global default.
Agent Prompt
### Issue description
`WebSocket.should_raise` and `WebSocket.max_frame_size` are set as global websocket-ruby configuration at file load time. Because this file is required by default, it changes behavior and memory limits process-wide for all Selenium users and potentially any other WebSocket usage in the same app.
### Issue Context
This is a library-level side effect: merely requiring Selenium modifies global WebSocket behavior.
### Fix Focus Areas
- rb/lib/selenium/webdriver/common/websocket_connection.rb[20-24]
- rb/lib/selenium/webdriver/common.rb[70-103]
### What to change
- Avoid unconditional top-level global assignments. Prefer an explicit Selenium-owned configuration surface (e.g., `Selenium::WebDriver::WebSocketConnection.configure(max_frame_size:, should_raise:)`) or, at minimum, only apply defaults if the user hasn’t configured them and document the side effect.
- If websocket-ruby supports per-instance/per-frame configuration, set it on the connection/frame objects instead of mutating global module state.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| def intercept(track_cancelled: true, &block) | ||
| devtools.network.on(:loading_failed) { |params| track_cancelled_request(params) } if track_cancelled | ||
| devtools.fetch.on(:request_paused) { |params| request_paused(params, &block) } | ||
|
|
||
| devtools.network.set_cache_disabled(cache_disabled: true) | ||
| devtools.network.enable | ||
| devtools.network.enable if track_cancelled | ||
| devtools.fetch.enable(patterns: [{requestStage: 'Request'}, {requestStage: 'Response'}]) |
There was a problem hiding this comment.
2. Track_cancelled opt-out breaks 🐞 Bug ≡ Correctness
When NetworkInterceptor#intercept(track_cancelled: false) is used, Selenium stops recording cancelled request IDs, but the cancellation-suppression logic still depends on that list to decide whether to swallow INVALID_INTERCEPTION_ID errors. As a result, normal browser cancellations can start raising WebDriverError exceptions and break interception for users who disable tracking.
Agent Prompt
### Issue description
With `track_cancelled: false`, the interceptor can no longer mark cancellations, but `with_cancellable_request` still requires a cancellation marker to suppress `INVALID_INTERCEPTION_ID` errors. This makes expected browser cancellations surface as exceptions and can break request interception.
### Issue Context
- `cancelled_requests` is only populated by `track_cancelled_request`, which only runs if the `loading_failed` handler is installed.
- `with_cancellable_request` treats `INVALID_INTERCEPTION_ID` as cancellable only when `cancelled?(network_id)` returns true.
### Fix Focus Areas
- rb/lib/selenium/webdriver/devtools/network_interceptor.rb[46-53]
- rb/lib/selenium/webdriver/devtools/network_interceptor.rb[71-75]
- rb/lib/selenium/webdriver/devtools/network_interceptor.rb[77-89]
- rb/lib/selenium/webdriver/devtools/network_interceptor.rb[161-169]
### What to change
Implement a behavior for `track_cancelled: false` that does not rely on `cancelled_requests`:
- Option A: Store `@track_cancelled` in `intercept(...)` and update `with_cancellable_request` to swallow `INVALID_INTERCEPTION_ID` unconditionally when tracking is disabled.
- Option B: Derive cancellation from other available signals in `Fetch.requestPaused` (if feasible) so `cancelled?` can still be true without enabling Network events.
- Ensure the opt-out reduces bandwidth *without* turning expected cancellations into hard failures.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
aguspe
left a comment
There was a problem hiding this comment.
hey thanks for looking into this, the websocket hang is a real pain
the global WebSocket config setting should_raise and max_frame_size at require time will affect any other websocket-ruby usage in the same process which is not great, maybe we can scope this to the connection instance instead?
also when track_cancelled: false the cancelled_requests never gets populated but with_cancellable_request still checks cancelled?, so browser cancellations will raise instead of being swallowed, that needs to be handled
this PR is doing a lot of things at once though, websocket config, dead thread detection, logging changes and the track_cancelled feature, wouldnt it be easier to review if we split into smaller PRs? maybe start with the websocket hang fix first and then the track_cancelled feature separately, we'd also need tests for the new behavior
WebSocket.should_raise = trueto preventwebsocket-rubyfromreturning
nilframes on failure, which previously caused infinite readloops and silent hangs.
WebSocket.max_frame_sizeto 100MB (from 20MB)to handle large CDP payloads such as Data URLs and screenshots.
WebSocketConnection#send_cmdto fail immediately if thelistener thread dies, avoiding a 30-second timeout wait when the
connection is already broken.
debugtowarnto make failuresvisible to users by default.
track_cancelledoption toDriver#intercept(default:true),enabling users to opt-out of the
Networkdomain to reduce bandwidthfrom chatty events.