Repository navigation
fix(ws): guard _start() against re-entry; clear managers on _stop (#297) - #307
Conversation
…ers on _stop Re-entering an active session (nested connect() or restart-on-error with a cached instance) used to silently rebuild _connection, _sub_mgr, _seq_tracker, _orderbook_mgr, and _dispatcher, orphaning the outer session's subscriptions and recv task with no error. _start() now refuses re-entry while _connection/_running are live, with a message that points at the two legitimate paths (await the existing async-with, or build a fresh KalshiWebSocket()). _stop() now nulls _connection / _sub_mgr / _seq_tracker / _orderbook_mgr / _dispatcher / _recv_task and clears the pause/resume events AFTER awaiting connection close, so the same instance can be reused for a fresh connect() once the previous session has fully exited. Tests: test_double_start_raises pins the overlap rejection; test_reconnect_after_stop_works pins that clean restart still works (subscribe + receive a frame on the reused instance). Updated test_close_sets_state and the integration shutdown test to match the new clear-on-stop contract. Closes #297.
Code ReviewOverviewSolid fix for a real and well-described bug. The re-entry guard in One correctness issue needs addressing before merge. Bug: partial
|
…comment - Wrap _start() body in try/except BaseException that clears _connection, _sub_mgr, _seq_tracker, _orderbook_mgr, _dispatcher, and _running on any failure. Without this, a raise in ConnectionManager.connect() (auth/network) left _connection set while _running stayed False; __aexit__ doesn't run when __aenter__ raises, so the next connect() permanently tripped the 'already started' guard. - Add test_failed_connect_allows_retry pointing at an unreachable port to lock in the state-clear invariant. - Collapse the 4-line #297 cleanup comment in _stop() to a single line preserving the ordering WHY.
|
Round-2 fix-up (288d78b) addresses the bot review:
Verify: |
Code Review
OverviewClean fix for a real, well-described bug. Re-entry guard + post-close state reset is the right structure. Tests cover all three cases: overlap rejection, legitimate restart, and connect-time failure. What's Good
Minor Issues1. Leaked connection on post-connect constructor failure (theoretical) In the except BaseException:
self._connection = None # drops reference without closing
...
raiseIf except BaseException:
if self._connection is not None:
with contextlib.suppress(Exception):
await self._connection.close()
self._connection = None
...
raise2. If the callback registration loop fails midway (again, practically impossible since Neither of these is a blocker given how unlikely the failure modes are, but item 1 is worth a note in the except block at minimum. Changed Assertions in Existing TestsThe VerdictApprove with the above minor notes. The core logic is sound, the guard/cleanup interaction is correct, and test coverage is solid. The resource-leak edge case is too unlikely to block merge but worth acknowledging in a comment. |
…ack retry path - Close underlying ConnectionManager in the except block when connect() succeeded but a later step raised, instead of dropping the reference to None and relying on GC. - Document _pending_callbacks retry semantics: on mid-loop failure the unregistered tail replays on the next _start; already-registered entries are discarded with the failed dispatcher. Addresses round-2 review on #307.
|
Round-2 nits addressed in 9795502:
Verified: |
Code Review — PR #307OverviewThis PR fixes a real, subtle bug (#297): calling What's correctGuard logic (
Ordering in Resetting
Minor notes (non-blocking)
Test coverageAll three invariants from the bug are pinned:
The integration test and VerdictApprove. Correct implementation, correct ordering, excellent test coverage. The fix is surgical — only the lines that needed to change were touched. The non-obvious decisions (BaseException, post-close ordering, event resets) are all called out in comments. |
Closes #297.
What
Re-entering an active
KalshiWebSocketsession (nestedconnect()or restart-on-error with a cached instance) silently rebuilt every manager (_connection,_sub_mgr,_seq_tracker,_orderbook_mgr,_dispatcher), orphaning the outer session's subscriptions and recv task with no error._start()now refuses re-entry while_connectionor_runningis live, with aRuntimeErrorthat points at the two legitimate paths (await the existing context, or build a freshKalshiWebSocket)._stop()now nulls the manager refs and clears the pause/resume events AFTER awaiting connection close, so the same instance CAN be reused for a freshconnect()once the prior session has fully exited.Tests
test_double_start_raises— pins the overlap rejection.test_reconnect_after_stop_works— pins that legitimate restart still subscribes + receives a frame on the reused instance.test_close_sets_stateand the integration shutdown test to match the new clear-on-stop contract.Full
tests/ws/suite green (364 passed).Part of the post-v2.5 audit closure (Wave W1).