Fix AttributeError on SignalR disconnect during HA shutdown - #450
Merged
ic-dev21 merged 1 commit intoSep 5, 2026
Merged
Conversation
SignalRHub.disconnect() called self._client.stop(), but pysignalr.SignalRClient (>=1.3.0) has no stop() or close() method -- its run() coroutine blocks until cancelled and is designed to be stopped by cancelling the task awaiting it (see WebsocketTransport.run(), an unconditional reconnect loop with no external stop hook). This raised AttributeError on every graceful Home Assistant shutdown/restart, which then cascaded into "RuntimeError: aclose(): asynchronous generator is already running" from the interrupted cleanup. SignalRHub now records the task running run() via asyncio.current_task() and disconnect() cancels and awaits that task instead of calling the nonexistent client method. Impact was limited to shutdown: the AttributeError fired only from the EVENT_HOMEASSISTANT_STOP listener in custom_components/hilo, did not prevent Home Assistant from stopping, and did not affect the next startup (a fresh SignalRClient is always created). Cosmetic but real -- flooded logs on every restart. Added tests/test_signalr.py covering disconnect() cancelling a live run() task and disconnect() being a no-op when no task is running. Confirmed the new test fails with the original AttributeError against the pre-fix code and passes against the fix. Verified: full test suite (66 tests) passes, ruff check/format clean, mypy clean, bandit and codespell clean on the changed files.
Collaborator
|
I see you found the same thing I did while exploring pysignalR's library lol |
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.
Summary
SignalRHub.disconnect()calledself._client.stop(), butpysignalr.SignalRClient(>=1.3.0) has nostop()orclose()method. Itsrun()coroutine blocks until cancelled and is designed to be stopped by cancelling the task that's awaiting it (seepysignalr'sWebsocketTransport.run()— an unconditional reconnect loop with no external stop hook).This raised
AttributeError: 'SignalRClient' object has no attribute 'stop'on every graceful Home Assistant shutdown/restart (fired from theEVENT_HOMEASSISTANT_STOPlistener incustom_components/hilo), which then cascaded into a follow-onRuntimeError: aclose(): asynchronous generator is already runningfrom the interrupted cleanup.Impact
Limited to shutdown. It did not prevent Home Assistant from stopping and did not affect the next startup (a fresh
SignalRClientis always created on the nextrun()). Cosmetic but real — it flooded the logs with an ERROR + traceback on every single restart.Fix
SignalRHubnow records the task runningrun()viaasyncio.current_task().disconnect()cancels and awaits that task instead of calling the nonexistent client method.Testing
Added
tests/test_signalr.py:test_disconnect_cancels_running_task: startsrun()in a task, confirmsdisconnect()cancels it cleanly without raising.test_disconnect_without_a_running_task_is_a_no_op: confirms callingdisconnect()beforerun()has ever started doesn't raise.Confirmed the new test fails with the original
AttributeErroragainst the pre-fix code (git stash+ rerun) and passes against the fix.Full suite: 66 passed.
ruff check/ruff format,mypy,bandit, andcodespellall clean on the changed files.