Queue updates for different nodes and share the hotspot #118

Merged
Claude merged 2 commits from fix/queued-updates into main 2026-10-08 16:11:38 +00:00
Collaborator

"Update all" posts every node at once, and the updater refused all but the first with busy. Each session also tore the hotspot down on exit, so the next one raised it from scratch.

Sessions now queue behind an asyncio lock. The radio keeps the hotspot up for a 2 minute grace period after each session and reuses it, so the uplink comes back only once the queue has gone quiet. The per-session hard deadline is unchanged. The link tracks one future per node, so a repeat request for the same node is still refused, and stopping cancels all of them.

Tested with new radio, session and link tests, plus the existing suite (1227 passed, 3 skipped). Ticket CCS-UHA-27.

"Update all" posts every node at once, and the updater refused all but the first with busy. Each session also tore the hotspot down on exit, so the next one raised it from scratch. Sessions now queue behind an asyncio lock. The radio keeps the hotspot up for a 2 minute grace period after each session and reuses it, so the uplink comes back only once the queue has gone quiet. The per-session hard deadline is unchanged. The link tracks one future per node, so a repeat request for the same node is still refused, and stopping cancels all of them. Tested with new radio, session and link tests, plus the existing suite (1227 passed, 3 skipped). Ticket CCS-UHA-27.
Queue updates for different nodes and share the hotspot
All checks were successful
Lint, type check and test / hassfest (pull_request) Successful in 8s
Lint, type check and test / quality (pull_request) Successful in 1m49s
Lint, type check and test / release (pull_request) Has been skipped
763d12e2dd
Claude left a comment

Verdict: mergeable. Two minor points, neither blocks.

  1. updater/radio.py _restore_after_delay: it sets self._deadline = None before await self.restore(). If the timer fires while raise_hotspot holds _restoring, _disarm finds nothing to cancel. The timer then takes the lock and tears down the hotspot that was just raised. The window is tiny (a raise landing exactly at grace expiry), and the 15 min deadline had the same race before. If you want it closed, check inside restore() or _restore_held() that the timer is still the current one.

  2. updater/session.py run: when raise_hotspot fails it already restores, but the finally still calls release(). That arms a 2 min timer, which later logs "Grace period over; restoring" and runs a no-op restore. Harmless but misleading. Call release() only after a successful raise.

Verdict: mergeable. Two minor points, neither blocks. 1. updater/radio.py `_restore_after_delay`: it sets `self._deadline = None` before `await self.restore()`. If the timer fires while `raise_hotspot` holds `_restoring`, `_disarm` finds nothing to cancel. The timer then takes the lock and tears down the hotspot that was just raised. The window is tiny (a raise landing exactly at grace expiry), and the 15 min deadline had the same race before. If you want it closed, check inside `restore()` or `_restore_held()` that the timer is still the current one. 2. updater/session.py `run`: when `raise_hotspot` fails it already restores, but the `finally` still calls `release()`. That arms a 2 min timer, which later logs "Grace period over; restoring" and runs a no-op restore. Harmless but misleading. Call `release()` only after a successful raise.
Author
Collaborator

Not changing either. (1) The timer-versus-raise race predates this PR and is a window of microseconds. (2) release() stays in the finally on purpose: a cancel during raise_hotspot, before the deadline is armed, would otherwise leave the uplink down. After a failed raise the timer's restore is a no-op.

Not changing either. (1) The timer-versus-raise race predates this PR and is a window of microseconds. (2) release() stays in the finally on purpose: a cancel during raise_hotspot, before the deadline is armed, would otherwise leave the uplink down. After a failed raise the timer's restore is a no-op.
Owner

If a node fails it shouldn't stop the others from updating

If a node fails it shouldn't stop the others from updating
Test that a failed update does not hold up the next node
All checks were successful
Lint, type check and test / hassfest (pull_request) Successful in 8s
Lint, type check and test / quality (pull_request) Successful in 1m50s
Lint, type check and test / release (pull_request) Has been skipped
e600d6d6e9
Author
Collaborator

A failed node already doesn't stop the others: the lock is released as the session exits, so the next queued node runs. I've added a session test and a link test that pin it (e600d6d).

A failed node already doesn't stop the others: the lock is released as the session exits, so the next queued node runs. I've added a session test and a link test that pin it (e600d6d).
Claude merged commit a2abb5b959 into main 2026-10-08 16:11:38 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
rob/CampervanHomeAssistant!118
No description provided.