Skip to content

Commit 975cad1

Browse files
committed
fix race condition hardware bug
1 parent dfbb374 commit 975cad1

1 file changed

Lines changed: 31 additions & 0 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,37 @@ release.
240240
survives at ~72 dB SNR.
241241

242242
### Fixed
243+
- Abandoning a live stream could leak a child that held the USB claim for
244+
the rest of the process: with the consumer gone, the 64 KB stdout pipe
245+
fills in milliseconds at capture rates and the child blocks inside
246+
write(); hackrf tools' SIGINT/SIGTERM handlers only set an exit flag that
247+
a blocked write never returns to check, and the breakout teardown ended
248+
at an unreaped terminate() -- so hackrf_transfer stayed frozen in
249+
write(), and every later open in the same process failed
250+
`hackrf_open() failed: Resource busy (-1000)`. Found on the Linux
251+
hardware verification run: a wall of 7 failures starting immediately
252+
after the two stream-breakout tests, unmoved by open-retries (the claim
253+
was held, not slow to release). Teardown now gives the clean interrupt a
254+
short window with the pipe open (well-behaved children still flush and
255+
exit cleanly -- tested), then closes the read end so a frozen write
256+
becomes EPIPE and the child can die (the kernel releases the USB claim
257+
on any death), then completes the terminate -> kill ladder. Three
258+
regression tests reproduce the frozen-writer state with a deaf, flooding
259+
stub and pin bounded teardown, immediate reopen, and the preserved
260+
clean-exit window.
261+
- Rapid back-to-back device operations could fail with
262+
`hackrf_open() failed: Resource busy (-1000)`: after a `hackrf_*` child
263+
exits, the kernel takes a moment to release its USB claim, and the next
264+
open can lose that race -- found on the first real-hardware Linux run,
265+
where 7 hardware tests failed with exactly this error (Windows' USB stack
266+
never exposed it). The library now absorbs it with bounded, backed-off
267+
retries in every acquisition mode: blocking/timed runs re-invoke, and
268+
streaming paths (sweep, monitor, the persistent receiver) respawn only if
269+
the child died busy BEFORE yielding anything -- once data has flowed, a
270+
busy error cannot be a stale-claim race and is raised as-is. Tunable via
271+
`busy_retries` (default 3) and `busy_backoff` (0.25 s, doubling);
272+
`busy_retries=0` restores fail-fast. The test stub can now simulate the
273+
race (`busy_fails=N`), and five tests pin the behavior across modes.
243274
- The `atexit` backstop never reaped an orphaned `PersistentReceiver`.
244275
`PersistentReceiver` registers itself in the live-handle registry, whose
245276
shutdown hook calls `if h.is_alive(): h.stop()` -- but `is_alive()` did not

0 commit comments

Comments
 (0)