Skip to content

Fix AttributeError on SignalR disconnect during HA shutdown - #450

Merged
ic-dev21 merged 1 commit into
dvd-dev:mainfrom
njloof:fix/signalr-disconnect-attributeerror
Sep 5, 2026
Merged

Fix AttributeError on SignalR disconnect during HA shutdown#450
ic-dev21 merged 1 commit into
dvd-dev:mainfrom
njloof:fix/signalr-disconnect-attributeerror

Conversation

@njloof

@njloof njloof commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Summary

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 that's awaiting it (see pysignalr's WebsocketTransport.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 the EVENT_HOMEASSISTANT_STOP listener in custom_components/hilo), which then cascaded into a follow-on RuntimeError: aclose(): asynchronous generator is already running from the interrupted cleanup.

Impact

Limited to shutdown. It did not prevent Home Assistant from stopping and did not affect the next startup (a fresh SignalRClient is always created on the next run()). Cosmetic but real — it flooded the logs with an ERROR + traceback on every single restart.

Fix

SignalRHub now records the task running run() via asyncio.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: starts run() in a task, confirms disconnect() cancels it cleanly without raising.
  • test_disconnect_without_a_running_task_is_a_no_op: confirms calling disconnect() before run() has ever started doesn't raise.

Confirmed the new test fails with the original AttributeError against the pre-fix code (git stash + rerun) and passes against the fix.

Full suite: 66 passed. ruff check/ruff format, mypy, bandit, and codespell all clean on the changed files.

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.
@ic-dev21

ic-dev21 commented Sep 5, 2026

Copy link
Copy Markdown
Collaborator

I see you found the same thing I did while exploring pysignalR's library lol

@ic-dev21
ic-dev21 merged commit ba61be2 into dvd-dev:main Sep 5, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants