You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(qwp): pace zero-progress strike-exempt recycles
Three recycle paths are strike-exempt by design -- orderly closes
(NORMAL_CLOSURE/GOING_AWAY), non-orderly closes before any send on the
connection, and pre-send RETRIABLE_OTHER (NOT_WRITABLE) rejections: none
of them are a verdict on the bytes. But strike-exempt also meant
pace-exempt: failPaced keys its dose on poisonStrikes, and connectLoop's
backoff engages only on FAILED connect attempts, while a peer that
completes TCP+TLS+upgrade before closing makes every attempt "succeed".
With Invariant B having (correctly) removed the wall-clock reconnect
budget, nothing bounded these recycles at all: an LB drain window, a
GOING_AWAY rolling restart, or a health-checking middlebox in front of a
dead backend drove full recycles -- fresh WebSocketClient + SSLContext +
trust-store read -- at handshake RTT rate, forever. Measured red: ~100k
reconnects in 1.2s.
Fix: failExemptPaced, a zero-progress recycle pacer. It counts
consecutive exempt recycles with no acceptance progress in between
(max(ackedFsn, highestOkFsn) -- monotonic, so replay re-OKs cannot
launder it) and parks with the same doubling, capped dose failPaced
uses. The FIRST recycle after any progress stays immediate, preserving
failover latency for genuine role-change/drain handoffs. Pacing only:
the counter never escalates to a terminal (Invariant B).
Tests: orderly-close churn and pre-send close churn (both red at ~85k
recycles/sec unpaced, now bounded by the backoff), plus a recycle-level
contract test pinning first-immediate, consecutive-paced, and
reset-on-progress.
0 commit comments