Reset keepalive state on reconnect#980
Conversation
The reconnect success path restored the connection and subscriptions but never reset _pings_outstanding, so a reconnect caused by reaching max_outstanding_pings inherited the maxed counter. The new write loop re-tripped it on its first idle timeout and force-disconnected again one ping_interval later, reconnecting forever. Reset the keepalive counters on reconnect as initial connect already does.
|
Claude finished @caspervonb's task in 1m 34s —— View job PR Review: Reset keepalive state on reconnect
Fix is correct and the root cause analysis is sound. The three-field reset (
Two minor notes left as inline comments:
Test is solid — directly setting |
| # ping_interval later (mirrors initial-connect setup). | ||
| self._pings_outstanding = 0 | ||
| self._last_pong_received = asyncio.get_running_loop().time() | ||
| self._last_ping_sent = self._last_pong_received |
There was a problem hiding this comment.
Minor inconsistency: __init__ timestamps these fields with asyncio.get_event_loop().time() (lines 457–458), while the reconnect path uses asyncio.get_running_loop().time(). Both return the same loop time in an async context, and get_running_loop() is actually the more correct call here — but the inconsistency could confuse a future reader auditing timer semantics. Worth aligning __init__ to get_running_loop() in a follow-up (or here).
| # Reset keepalive state so a reconnect caused by ping | ||
| # exhaustion doesn't inherit the stale counter and have | ||
| # the new write loop force-disconnect again one | ||
| # ping_interval later (mirrors initial-connect setup). |
There was a problem hiding this comment.
The comment is accurate and the bug it describes is non-obvious, so a comment is warranted. CLAUDE.md asks for one-line max though — something like # Reset keepalive state; a ping-exhaustion reconnect must not inherit a stale counter. would capture the why without the four lines.
A reconnect caused by reaching
max_outstanding_pingsinherited the maxed_pings_outstandingcounter: the reconnect success path restored the connection and subscriptions but never reset keepalive state. The new write loop saw the stale counter on its first idle timeout and force-disconnected again oneping_intervallater — reconnecting forever.Reset
_pings_outstanding/_last_ping_sent/_last_pong_receivedon reconnect, mirroring initial-connect setup. Covered by a regression test that puts the client in the ping-exhausted state, forces a reconnect, and asserts the counter is cleared and no spiral follows.