fix(deck): swallow HID errors in refresh() after disconnect#383
Merged
Conversation
Background tasks (clock ticks, spinners, timers) call
KeySlot.request_refresh() / Deck.refresh() without knowing the deck's
lifecycle. When a device disconnects between the refresh being scheduled
and the executor running the blocking HID write, the call surfaces as a
bare HidApiError("Device is not open") from the ctypes hidapi backend
and crashes the background task with an unhandled exception.
Treat refresh() as best-effort: catch HidApiError/HidWriteTimeout from
the per-control render+push stage and log at DEBUG. The manager's
disconnect handler already tears down the deck and emits a user-facing
warning, so silent recovery here is appropriate.
Repro: examples/streamdeck.py — unplugging the deck while the
ClockController tick loop is running triggered:
ERROR: Task exception was never retrieved
future: <Task ... coro=<ClockController._tick_loop() ...>
exception=HidApiError('Device is not open')>
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.
Problem
Running
examples/streamdeck.pyand unplugging the Stream Deck while theClockControllertick loop is active produces an unhandled task exception:Root cause
Background tasks (clock ticks, spinners, timers) call
KeySlot.request_refresh()->Deck.refresh()without knowing the deck's lifecycle. WhenDeckManagertears down the device after a disconnect, an in-flight refresh that has already been scheduled lands in_exec_device_io, where the executor callsdevice.set_key_imageon a closed handle and raises a bareHidApiErrorfrom ctypes hidapi. That exception bubbles up to the user's tick task, which has no reasonable way to guard every call site.Fix
Treat
Deck.refresh()as best-effort: catchHidApiErrorandHidWriteTimeoutfrom the per-control render+push stage and log at DEBUG. The manager's disconnect handler already emits a user-facing warning and re-attaches on reconnect, so silent recovery here is the right boundary.Tests
test_swallows_hidwritetimeout-- refresh tolerates aHidWriteTimeoutfrom any push.test_swallows_hidapierror-- same for a bareHidApiErrordeeper in the stack.