fix(ws): guard register_client() against event_bus=None on reconnect race (0.21.111)#1020
Merged
Merged
Conversation
…race (0.21.111) Prod teardown race (nightly review, ~2/24h, caught/non-fatal): on a reconnect via reconnection_token immediately followed by a 1001 disconnect, the background register_client() task that RedisRPCConnection.on_message schedules could run just as disconnect() set self._event_bus = None, so `await self._event_bus.subscribe_to_client_events(...)` raised AttributeError: 'NoneType' object has no attribute subscribe_to_client_events. Fix: register_client() captures self._event_bus into a local and returns if it's already None; the local capture also closes the mid-await window where a concurrent disconnect() nils the attribute. Same teardown-race class as the 0.21.106 filtered_handler None guard. Test: tests/test_websocket_lifecycle_hardening.py::test_register_client_task_after_teardown_no_error — schedule register_client, null _event_bus before it runs, await it → must not raise (pre-fix: AttributeError). 4/4 hardening suite green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…11 bug 2) Second teardown race from the nightly review: if a client opens the ws and goes away (code 1005, no close frame) before sending its auth payload, receive_text() at websocket.py raised WebSocketDisconnect, which the existing except asyncio.TimeoutError didn't catch → uvicorn logged "Exception in ASGI application" (~2/24h, benign). Fix: catch WebSocketDisconnect on the auth read and return cleanly (log DEBUG). Test: test_ws_disconnect_during_auth_handshake_no_error drives the ASGI endpoint with a pre-auth disconnect and asserts it doesn't raise. Also: CHANGELOG now explicitly calls out that 0.21.111 rolls up the 0.21.108-110 local-auth + embedded-login features (per ops request — conscious feature call, not a silent ride-along). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Nightly-review hardening (mint-ram, 2026-07-14). The 0.21.107 send-after-close fix held (0/24h, no leak); this fixes one of two remaining low-volume WS teardown races.
Bug (caught, non-fatal, ~2/24h)
On a reconnect via reconnection-token immediately followed by a 1001 disconnect, the background
register_client()task (scheduled byRedisRPCConnection.on_messagetosubscribe_to_client_events) could run just asdisconnect()setself._event_bus = None:Fix (
hypha/core/__init__.py)register_client()captures the bus into a local and returns if it's alreadyNone. The local capture also closes the window where a concurrentdisconnect()nilsself._event_busmid-await. Same teardown-race class as the 0.21.106filtered_handlerNoneguard.Test
tests/test_websocket_lifecycle_hardening.py::test_register_client_task_after_teardown_no_error— schedulesregister_client, nulls_event_busbefore it runs, awaits it → must not raise (pre-fix:AttributeError). 4/4 hardening suite green.The second race (
Exception in ASGI application, 2/24h, ws upgrade via mcp→a2a→http) is held for the exact exception stack (requested from the reviewer) before touching that routing path — fast-follow if not folded in here.🤖 Generated with Claude Code