Repository navigation
Conversation
capture_process_stderr redirects process-wide fd 2 into a pipe for the duration of each cv2.VideoCapture open. Sources open on their own threads, so two opens starting together interleave their redirects: the first to finish blocks forever reading a pipe whose write end the second still holds, and fd 2 is finally restored to that pipe instead of real stderr. Take a process-wide lock around the capture and drain the pipe on a thread so output larger than the pipe buffer cannot block the writer either.
|
👋 Thanks for the pull request! Here is how automated Claude review works here, so you spend credits (and reviewer time) wisely. 🚧 Right now this is a draft, so automated Claude review is paused — nothing is being spent yet. Mark it Ready for review to trigger it. Warning 💸 The Claude reviewer bills in credits, not vibesAutomated review spins up a real agent that reads real code and spends real credits on every pass. It is glad to help — but it is not a rubber duck, a linter you poke in a loop, or a substitute for reading the contributing guide. Treat it like an expensive senior reviewer whose time you booked, and show up prepared. Draft when unsure, Ready when you mean it:
However you get there, arrive prepared:
Reviews are not free. A draft costs nothing to review; a Ready PR is a promise that it is worth reviewing.
|
|
Duplicate re #3014 |
What changed
capture_process_stderrininference/core/interfaces/camera/stream_error_classifier.pynow takes a process-wide lock for the duration of the capture and drains the pipe on a thread instead of oneos.readafter the fact.Why
The helper redirects the process-wide fd 2 into a pipe around every
cv2.VideoCaptureopen so FFmpeg's error text can be classified. EachVideoSourceopens its capture on its own thread, so two sources starting together (for example two RTSP streams in one process after a restart) interleave their redirects:os.readon its pipe. B still holds a dup of A's write end, so if A's open wrote nothing to stderr the read never returns.The source that opened first sits in
INITIALISINGforever with no error. Only a process restart recovers it, and that repeats the race.The lock is the same approach
opencv_rtsps_tls_envalready uses to serialise the capture-options env var around the same open, and it is held across the open for the same reason. The drain thread fixes a second problem in the old single read: anything over the pipe buffer (64 KiB on Linux) blocked the writer inside the open, and only the first 64 KiB was ever kept. The thread is joined before the context exits, so nothing outlives the capture.An open timeout for the capture itself is a separate change and not part of this PR.
Tests
Two regression tests in
tests/inference/unit_tests/core/interfaces/camera/test_stream_error_classifier.py, collected byunit_tests_inference_x86.yml:test_capture_process_stderr_serialises_interleaved_opensforces the interleave above with events. Before the fix it fails with fd 2 left pointing at a pipe after both captures exit. After the fix both captures complete with their own output and fd 2 is restored.test_capture_process_stderr_drains_output_larger_than_pipe_bufferwrites 256 KiB to fd 2 inside a capture. Before the fix the writer blocks forever. After the fix everything is captured.Commands run: