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
Sending resubscribe commands and reading their ok responses.
Rebuilding _sid_to_client with new SIDs.
Releasing the lock.
Between steps 2 and 4, data frames sent by the server immediately after assigning the new SID (before the resubscribe response is fully processed) reach the dispatcher with a SID that hasn't been wired yet. The dispatcher has no route and the frame is logged and dropped.
Rare under normal reconnects, but observable under high-volume channels (trade, fill, ticker) when reconnect happens during a market burst.
Fix
Stash data frames arriving during the resubscribe window into a per-SID staging buffer. When the new _sid_to_client mapping lands, drain the staging buffer through the normal dispatch path. Bound the buffer to avoid memory growth if resubscribe stalls.
ConnectionManager flips _stashing on at reconnect-start, off after resubscribe_all returns. On flip-off, drain _stash[sid] for each newly-mapped SID through dispatch(), then clear _stash.
Acceptance
Integration test in tests/ws/test_recv_loop_hardening.py (or new file) that simulates a reconnect while a high-rate stream is active and asserts the consumer iterator sees no message gap.
The stash is bounded; if resubscribe takes longer than the stash maxlen permits, excess frames are dropped with a logged WARNING (graceful degradation, not unbounded growth).
From #106 F-R-13. Severity: medium correctness (silent message loss during reconnect bursts).
Behavior
kalshi/ws/client.py:301-319(reconnect path) andkalshi/ws/channels.py:207-220(resubscribe_all) handle reconnect by:_subscribe_lock._sid_to_client(channels.py:220, F-P-03 — prevents stale-sid mis-routing).okresponses._sid_to_clientwith new SIDs.Between steps 2 and 4, data frames sent by the server immediately after assigning the new SID (before the resubscribe response is fully processed) reach the dispatcher with a SID that hasn't been wired yet. The dispatcher has no route and the frame is logged and dropped.
Rare under normal reconnects, but observable under high-volume channels (
trade,fill,ticker) when reconnect happens during a market burst.Fix
Stash data frames arriving during the resubscribe window into a per-SID staging buffer. When the new
_sid_to_clientmapping lands, drain the staging buffer through the normal dispatch path. Bound the buffer to avoid memory growth if resubscribe stalls.Sketch
ConnectionManagerflips_stashingon at reconnect-start, off afterresubscribe_allreturns. On flip-off, drain_stash[sid]for each newly-mapped SID throughdispatch(), then clear_stash.Acceptance
tests/ws/test_recv_loop_hardening.py(or new file) that simulates a reconnect while a high-rate stream is active and asserts the consumer iterator sees no message gap.