fix: recover from stale libusb context in list_printers (v1.8.1)#46
Merged
Conversation
In a long-lived process pyusb caches its libusb context. When the Dymo re-enumerates to a new bus address (replug, or our own USB power-cycle), the cached context keeps returning an empty scan even though the printer is physically attached — so /api/printers goes empty, the client has no printer id to key against, and per-printer settings silently stop saving. list_printers() now recovers: on an empty scan, if uhubctl (which is libusb-independent) still sees the DYMO on a hub port, drop the cached context and rescan once. The uhubctl gate is deliberate — it targets the stale-context case and guarantees we never resume a deliberately powered-off port (no device on the bus -> gate is False). - promote usb_power._invalidate_libusb_cache -> public invalidate_libusb_cache - add usb_power.printer_attached() (uhubctl-based presence check) - 4 new regression tests; full suite 283 passing Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR fixes a long-running deployment issue where /api/printers could incorrectly return an empty list due to a stale, process-wide pyusb/libusb context after the DYMO re-enumerates. It adds a recovery path that conditionally invalidates libusb’s cached context and rescans, gated by an independent uhubctl-based presence check to avoid resuming deliberately powered-off ports.
Changes:
- Promote libusb cache invalidation to
usb_power.invalidate_libusb_cache()and addusb_power.printer_attached()(uhubctl-based presence gate). - Refactor printer enumeration into
_scan_real_printers()and recovery-aware_list_real_printers()used bylist_printers(). - Add regression tests covering stale-context recovery behavior and bump package version to
1.8.1.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| server/usb_power.py | Exposes libusb cache invalidation publicly and adds an uhubctl-based “printer attached” probe used as a recovery gate. |
| server/printer_service.py | Refactors USB scanning and adds gated stale-libusb-context recovery for /api/printers. |
| server/tests/test_printer_service.py | Adds regression tests ensuring recovery runs only when appropriate and remains quiet on the happy path. |
| package.json | PATCH version bump to 1.8.1. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
printer_attached() runs on the /api/printers read path. Calling through to _run() when uhubctl isn't installed emits the latched "uhubctl missing" WARNING that's meant for the deliberate power-control paths — surfacing a power-feature warning during plain printer listing on setups that never use power features (e.g. virtual-printer-only dev). Short-circuit with shutil.which() so the best-effort probe stays silent and returns False. Addresses Copilot review on PR #46. Co-Authored-By: Claude Opus 4.8 (1M context) <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.
Problem
On a long-running deployment (observed on
label.hakhorst.eu/ hector),/api/printersreturned[]even though the Dymo was plugged in and powered. With no printer detected, the client has no printer id to key against, soeffectivePrinterId()returnsundefined,usePrinterSettings.persist()no-ops, and per-printer settings silently stop being saved.Root cause
Not lost USB access — a stale process-wide libusb context. pyusb caches its libusb context at module level. When the Dymo re-enumerates to a new bus address (replug, or our own USB power-cycle), the cached context keeps returning an empty scan even though the device is physically attached. The codebase already knew about this (
usb_power.invalidate_libusb_cache, called afterpower_on()), butlist_printers()never invalidated — that asymmetry was the bug.Confirmed on the box: a fresh libusb context inside the stuck container enumerated the printer instantly, while the 8h-old app process saw nothing.
uhubctl(libusb-independent) showedPort 3 … connect [0922:1002 …]throughout.Fix
list_printers()now recovers from this state. On an empty scan, ifuhubctlstill sees the DYMO on a hub port, drop the cached libusb context and rescan once — so the printer reappears without a process/container restart.The
uhubctlgate is deliberate and addresses the existing comment's concern: re-creating the libusb context can trigger a kernel hub auto-resume that re-energizes a port. By only refreshing whenuhubctlconfirms a device is physically present, we never resume a deliberately powered-off port (no device on the bus → gate is False).usb_power._invalidate_libusb_cache→ publicinvalidate_libusb_cacheusb_power.printer_attached()(uhubctl-based, best-effort presence check)_scan_real_printers()+ recovery-aware_list_real_printers()Testing
TestStaleLibusbContextRecovery): recovers when attached; no refresh when uhubctl sees nothing (respects power-off); no uhubctl probe on the happy path; graceful empty when recovery still finds nothing.Version
PATCH bump
1.8.0→1.8.1(bug fix, no behavior change).🤖 Generated with Claude Code