fix(adapters): tolerate EAGAIN/EINTR in UVC capture loop - #8
Merged
Conversation
AVFoundation (macOS) and V4L2 (Linux) raise BlockingIOError or OSError(EAGAIN) from the decode iterator during camera warmup and occasionally between frames. The previous for-in-decode loop treated every exception as fatal, killing the capture thread and surfacing as 'capture loop ended: BlockingIOError(35, ...)' in the viewer health panel within a second of connect() — despite the camera being fine. Drive the iterator manually via next() so EAGAIN can be caught per call. Catch all OSError variants and dispatch on errno: - 4 (EINTR), 11 (Linux EAGAIN), 35 (macOS EAGAIN), None: sleep 1ms and retry. These are 'not ready yet' signals, never fatal. - Other errnos (EIO, ENODEV, etc.): fall through to the fatal health-event path. Non-OSError exceptions keep the original fatal behavior. Adds two tests: - test_blocking_io_error_does_not_kill_loop — inject EAGAIN 3x, then real frames; verify frames arrive and no health event emitted. - test_fatal_os_error_still_ends_loop — inject ENODEV; verify the loop exits and emits a health event. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
2 tasks
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.
Summary
Fixes the
capture loop ended: BlockingIOError(35, 'Resource temporarily unavailable', '0')health event that kills the UVC capture thread immediately afterconnect()on macOS, leaving the viewer's stream card frozen on a single frame.Root cause
AVFoundation (macOS) and V4L2 (Linux) raise
BlockingIOError/OSError(EAGAIN)from the decode iterator during camera warmup and between frames. This is the demuxer saying "no frame ready yet — try again," NOT a fatal error. The previousfor frame in self._input.decode(video=0):structure caught it under a broadexcept Exceptionand terminated the thread.Fix
Drive the decode iterator manually via
next()so EAGAIN can be caught and retried per-call:Test plan
test_blocking_io_error_does_not_kill_loop— inject EAGAIN 3x, verify real frames arrive and no health event emittedtest_fatal_os_error_still_ends_loop— inject ENODEV, verify fatal path still worksexamples/iphone_mac_webcam/record.py— streams should render live in the viewer, no BlockingIOError health events🤖 Generated with Claude Code