This repository was archived by the owner on May 31, 2026. It is now read-only.
Repository navigation
Conversation
Replaces the one-shot find_adapter() call at startup with a bounded poll
(15s timeout, 200ms interval). The unit's existing busctl wait loop only
confirms BlueZ is running — BlueZ exposes /org/bluez/hci0 asynchronously
after claiming its bus name. A single GetManagedObjects() can race ahead
of adapter enumeration on slow boots, busy SD cards, or service restarts,
causing the script to exit 1.
Reproduced live on Rock Pi 4C+ running SentryUSB v3.6.2 (2026-04-26):
14:30 Pi boot
14:30-14:44 (14 min) service inactive — StartLimitBurst exhausted at boot
14:44:53 manual `systemctl start sentryusb-ble` resets counter -> fails
again with same race
14:45:00 systemd auto-retry (RestartSec=5) -> succeeds
The "Restart=always handles it" assumption only holds when the first
5-attempt burst (default StartLimitBurst=5 in 10s) succeeds. If the race
wins all 5 attempts, systemd gives up and the service stays inactive
until next reboot. Was 14 minutes of dead BLE in this reproduction.
Same risk applies to every restart, not just boot:
- After archiveloop's awake_stop (PR Scottmg1#43 fixed the loop, not the race)
- After OOM kill or process crash
- After daily RuntimeMaxSec=86400 forced restart
- During Pi OTA updates that bounce the service
First-principle fix: a service that depends on a resource should wait for
the resource, not exit when it's missing. wait_for_adapter() polls every
200ms for ~5 iterations until BlueZ exposes the adapter. Negligible CPU
during the typical sub-second wait window.
Defense in depth in the unit file: bump StartLimitBurst to 20 within 120s
so unrelated future failures (BlueZ crash, OOM, etc.) don't get locked out
either. Not load-bearing for the race — Python handles that.
Why polling instead of D-Bus InterfacesAdded signal:
Signal subscription has its own race (if adapter exists at subscribe time
you miss the event — still need a fallback poll). Polling once every
200ms for ~5 iterations is effectively free, no concurrency complexity.
Files touched:
- server/ble/sentryusb-ble.py — new wait_for_adapter() helper, replaces
the existing one-shot check at startup
- server/ble/sentryusb-ble.service — adds StartLimitBurst=20 and
StartLimitIntervalSec=120
chufleco
marked this pull request as ready for review
April 28, 2026 04:23
4 tasks done
Scottmg1
pushed a commit
to Sentry-Six/Sentry-USB-Rusty
that referenced
this pull request
May 18, 2026
…ailing Mirrors merged upstream Sentry-USB PR #47. The unit's `busctl status org.bluez` ExecStartPre only confirms BlueZ has claimed its bus name — BlueZ exposes `/org/bluez/hci0` asynchronously *after* that claim. A single `GetManagedObjects()` can race ahead of adapter enumeration on slow boots, busy SD/eMMC cards, or service restarts (after archiveloop's awake_stop, OOM kill, RuntimeMaxSec, etc.), causing the script to exit 1. First-principles fix: wait for the dependency we actually need (the LE adapter object), not just the bus name. Bounded poll: 15s timeout, 200ms interval. Simpler than `InterfacesAdded` signal subscription (which has its own race when the adapter already exists at subscribe time). **Validated live on Rock Pi 4C+ / DietPi + Rusty v2.6.6 (2026-05-18):** After applying this patch + a clean reboot, sentryusb-ble.py finds the adapter on the first poll (sub-200ms), GATT app + advertisement register within 1s of service start, and an incoming central connects + authenticates 6 seconds after service start. Previously the service required 1-2 StartLimitBurst retries to land successfully after each boot, leaving the BLE peripheral unavailable for the first 30+ seconds. Defense in depth: bump `StartLimitBurst` to 20 within `StartLimitIntervalSec=120`. The Python `wait_for_adapter()` handles 99% of cases on the first attempt; the unit-level bump is insurance for unrelated failure modes (BlueZ crash, OOM, etc.) where systemd's default 5-attempts-in-10s budget would be exhausted. Refs Scottmg1/Sentry-USB#47 (the merged Go-version PR with the same fix).
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 subscribe to this conversation on GitHub.
Already have an account?
Sign in.
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.
Replaces the one-shot
find_adapter()call at startup with a bounded poll (15s timeout, 200ms interval). The unit's existingbusctlwait loop only confirms BlueZ is running — BlueZ exposes/org/bluez/hci0asynchronously after claiming its bus name. A singleGetManagedObjects()can race ahead of adapter enumeration on slow boots, busy SD cards, or service restarts, causing the script to exit 1.Reproduced live on Rock Pi 4C+ running SentryUSB v3.6.2 (2026-04-26):
The "Restart=always handles it" assumption only holds when the first 5-attempt burst (default
StartLimitBurst=5in 10s) succeeds. If the race wins all 5 attempts, systemd gives up and the service stays inactive until next reboot. Was 14 minutes of dead BLE in this reproduction.Same risk applies to every restart, not just boot:
awake_stop(PR fix: awake_stop preserves keep-awake state across BLE service restarts #43 fixed the loop, not the race)RuntimeMaxSec=86400forced restartFirst-principle fix: a service that depends on a resource should wait for the resource, not exit when it's missing.
wait_for_adapter()polls every 200ms for ~5 iterations until BlueZ exposes the adapter. Negligible CPU during the typical sub-second wait window.Defense in depth in the unit file: bump
StartLimitBurstto 20 within 120s so unrelated future failures (BlueZ crash, OOM, etc.) don't get locked out either. Not load-bearing for the race — Python handles that.Why polling instead of D-Bus
InterfacesAddedsignal: Signal subscription has its own race (if adapter exists at subscribe time you miss the event — still need a fallback poll). Polling once every 200ms for ~5 iterations is effectively free, no concurrency complexity.Files touched:
server/ble/sentryusb-ble.py— newwait_for_adapter()helper, replaces the existing one-shot check at startupserver/ble/sentryusb-ble.service— addsStartLimitBurst=20andStartLimitIntervalSec=120Verified
v3.8.9-beta.4does not contain this fix; PR is not duplicating existing work.