Make websocket ping_interval/ping_timeout configurable (default off) to avoid false 1011 disconnects - #25
Conversation
CDP is a request/response protocol over this socket -- its own traffic is already sufficient liveness detection. The websockets library's independent keepalive ping can time out under real load (a browser busy servicing a heavy command, or extra latency on a remote/cloud session) well before the connection has actually died, closing it with code 1011 and surfacing as an unexplained mid-session drop. This file already has dedicated PING/PONG timeout-tracking logging above, which reads like this exact failure mode has been hit and worked around by logging it rather than by addressing the cause. Defaults ping_interval/ping_timeout to None (disabled) rather than the library's own 20s/20s, and exposes both as constructor params so a caller who wants transport-level liveness checks back can restore them explicitly.
There was a problem hiding this comment.
1 issue found across 1 file
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="cdp_use/client.py">
<violation number="1" location="cdp_use/client.py:290">
P2: With keepalive pings disabled by default (ping_interval=None), a connection that dies silently—network partition or drop with no TCP FIN/RST—is no longer detected. Both `await self.ws.recv()` in `_handle_messages` and `await future` in `send_raw` block indefinitely, so CDP commands hang forever instead of failing fast as they did when a missed pong closed the socket after ~20s. The comment's claim that CDP's own traffic is 'sufficient liveness detection' only holds while traffic is flowing; during idle moments a dead connection is indistinguishable from a live one. Consider keeping detection available (e.g., a conservative non-None default such as a long ping_interval, or an application-level request timeout) rather than removing it entirely, and document that None means commands can hang when the socket dies without an RST.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Fix all with cubic | Re-trigger cubic
| ) | ||
| connect_kwargs = { | ||
| "max_size": self.max_ws_frame_size, | ||
| "ping_interval": self.ping_interval, |
There was a problem hiding this comment.
P2: With keepalive pings disabled by default (ping_interval=None), a connection that dies silently—network partition or drop with no TCP FIN/RST—is no longer detected. Both await self.ws.recv() in _handle_messages and await future in send_raw block indefinitely, so CDP commands hang forever instead of failing fast as they did when a missed pong closed the socket after ~20s. The comment's claim that CDP's own traffic is 'sufficient liveness detection' only holds while traffic is flowing; during idle moments a dead connection is indistinguishable from a live one. Consider keeping detection available (e.g., a conservative non-None default such as a long ping_interval, or an application-level request timeout) rather than removing it entirely, and document that None means commands can hang when the socket dies without an RST.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At cdp_use/client.py, line 290:
<comment>With keepalive pings disabled by default (ping_interval=None), a connection that dies silently—network partition or drop with no TCP FIN/RST—is no longer detected. Both `await self.ws.recv()` in `_handle_messages` and `await future` in `send_raw` block indefinitely, so CDP commands hang forever instead of failing fast as they did when a missed pong closed the socket after ~20s. The comment's claim that CDP's own traffic is 'sufficient liveness detection' only holds while traffic is flowing; during idle moments a dead connection is indistinguishable from a live one. Consider keeping detection available (e.g., a conservative non-None default such as a long ping_interval, or an application-level request timeout) rather than removing it entirely, and document that None means commands can hang when the socket dies without an RST.</comment>
<file context>
@@ -271,6 +287,8 @@ async def start(self):
)
connect_kwargs = {
"max_size": self.max_ws_frame_size,
+ "ping_interval": self.ping_interval,
+ "ping_timeout": self.ping_timeout,
}
</file context>
…he timeout-close Only ping_timeout was the cause of the premature 1011 close under load; ping_interval still sending pings gives a liveness signal (useful for NAT/proxy keepalive and logging) without forcing a close on a slow pong. Documents the ping_interval/ping_timeout coupling and the hang-forever tradeoff a reviewer bot correctly flagged.
|
Both points are fair, thanks. Pushed a fix:
Commit: bbce4bb |
There was a problem hiding this comment.
All reported issues were addressed across 1 file (changes from recent commits).
Reply with feedback, questions, or to request a fix.
Fix all with cubic | Re-trigger cubic
… tied to ping_timeout
|
Good catch — fixed in 401545e. The comment implied the PING/PONG log warning "tracks exactly that timeout window," i.e. that it stays in sync with whatever `ping_timeout` is set to. It doesn't: `check_timeout()` above hard-codes `asyncio.sleep(3)`, so it always warns at 3s regardless of the configured `ping_timeout` value. Reworded to describe it as a separate, hard-coded 3s check rather than implying synchronization. |
|
All CI checks are green and both review comments from last week are addressed (401545e, and the ping_interval default fix before that). Let me know if anything else would help get this over the line, or if you'd rather I change the API shape (e.g. make the disabled ping strictly opt-in instead of the new default). |
What
CDPClientcurrently connects with thewebsocketslibrary's own defaults forping_interval/ping_timeout(20s/20s). This PR makes both configurable and defaults them toNone(disabled).Why
CDP is a request/response protocol carried over this same socket -- real CDP traffic is already sufficient evidence the connection is alive.
websockets' own keepalive ping is a separate, independent liveness check: it can time out under perfectly normal conditions (the browser busy servicing a heavy command, extra latency on a remote/cloud session) well before the connection has actually died, and the library then force-closes with code 1011. From the client's point of view this is indistinguishable from a real disconnect -- it just looks like an unexplained mid-session drop.I hit and root-caused the identical failure in a separate from-scratch CDP-over-websockets client I built and have run against a range of real, hostile-to-automation production sites (
ping_interval=Noneis the one-line fix). Worth notingWebSocketLogFilterin this file already has dedicated logic to track ping-send-time and warn on "PING not answered by browser... (>3s and no PONG received)" -- that's exactly the timeout window this PR removes, which reads like this exact class of failure has already been noticed and worked around by logging it rather than by addressing why it happens.Possibly related: browser-use/browser-use#4688 describes CDP websocket connections intermittently dropping mid-interaction on cloud/remote browser sessions, which is the shape (added latency, no clear cause) this failure mode produces -- flagging it as a plausible match, not a confirmed one, since the report doesn't include a close code.
What this changes
ping_interval/ping_timeoutbecome constructor params onCDPClient, both defaulting toNone.Happy to adjust the defaults/API shape if you'd rather this be strictly opt-in instead of on-by-default.