From 08be8fea77b517f04e5fedffabdc05cf4e7437ea Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 2 Jun 2026 11:33:17 +0000 Subject: [PATCH] fix(deltacast-bridge): video EPIPE reopens FIFO instead of stopping port permanently MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a capture sidecar stopped/restarted, the bridge video thread got EPIPE on the FIFO write, set g_port_stop[port]=1, and the port went dead — requiring a full bridge restart to recover. Subsequent record attempts on that port would hang in 'connecting' forever. Fix: mirror the audio thread pattern — on EPIPE, close the FIFO and loop back to open() blocking for the next reader. Hardware lock errors (SDK failures) still stop the port via g_port_stop as before. Only reader-disconnect (EPIPE) now recovers gracefully. This was the cause of port 6 (Ghost) failure in the burn test. 🤖 Generated with Claude Code --- services/capture/deltacast-bridge/main.c | 70 ++++++++++++++---------- 1 file changed, 41 insertions(+), 29 deletions(-) diff --git a/services/capture/deltacast-bridge/main.c b/services/capture/deltacast-bridge/main.c index 1e1f76f..829f4bf 100644 --- a/services/capture/deltacast-bridge/main.c +++ b/services/capture/deltacast-bridge/main.c @@ -297,39 +297,51 @@ static void *audio_thread(void *arg) { static void *video_thread(void *arg) { PortState *ps = (PortState *)arg; - int fd = open(ps->video_fifo, O_WRONLY); - if (fd < 0) { - fprintf(stderr, "[video:%u] open FIFO failed: %s\n", ps->port, strerror(errno)); - atomic_store(&g_port_stop[ps->port], 1); - return NULL; - } - - HANDLE slot = NULL; + /* Outer loop: reopen the FIFO writer each time a reader connects. + * Mirror the audio thread pattern — EPIPE means the ffmpeg sidecar for + * this port died (session stop/restart), NOT a hardware fault. We reopen + * and block until the next recorder start; other ports are unaffected. */ while (!atomic_load(&g_stop) && !atomic_load(&g_port_stop[ps->port])) { - ULONG r = VHD_LockSlotHandle(ps->video_stream, &slot); - if (r == VHDERR_NOERROR) { - BYTE *buf = NULL; - ULONG sz = 0; - if (VHD_GetSlotBuffer(slot, VHD_SDI_BT_VIDEO, &buf, &sz) == VHDERR_NOERROR) { - if (write_all(fd, buf, sz) < 0) { - /* EPIPE on video: the capture sidecar for this port died. - * Stop only this port's threads — other ports unaffected. */ - fprintf(stderr, "[video:%u] EPIPE — stopping port\n", ps->port); - atomic_store(&g_port_stop[ps->port], 1); - VHD_UnlockSlotHandle(slot); - break; - } - } - VHD_UnlockSlotHandle(slot); - } else if (r != VHDERR_TIMEOUT) { - fprintf(stderr, "[video:%u] VHD_LockSlotHandle error %lu — stopping port\n", - ps->port, r); - atomic_store(&g_port_stop[ps->port], 1); - break; + + int fd = open(ps->video_fifo, O_WRONLY); + if (fd < 0) { + fprintf(stderr, "[video:%u] open FIFO failed: %s\n", ps->port, strerror(errno)); + struct timespec ts = {0, 200000000L}; + nanosleep(&ts, NULL); + continue; } + fprintf(stderr, "[video:%u] FIFO writer connected\n", ps->port); + + HANDLE slot = NULL; + int fatal = 0; + while (!atomic_load(&g_stop) && !atomic_load(&g_port_stop[ps->port])) { + ULONG r = VHD_LockSlotHandle(ps->video_stream, &slot); + if (r == VHDERR_NOERROR) { + BYTE *buf = NULL; + ULONG sz = 0; + if (VHD_GetSlotBuffer(slot, VHD_SDI_BT_VIDEO, &buf, &sz) == VHDERR_NOERROR) { + if (write_all(fd, buf, sz) < 0) { + /* EPIPE: sidecar died (session stop/restart). + * Break to outer loop — reopen for next session. */ + fprintf(stderr, "[video:%u] EPIPE — waiting for next reader\n", ps->port); + VHD_UnlockSlotHandle(slot); + break; + } + } + VHD_UnlockSlotHandle(slot); + } else if (r != VHDERR_TIMEOUT) { + fprintf(stderr, "[video:%u] VHD_LockSlotHandle error %lu — stopping port\n", + ps->port, r); + atomic_store(&g_port_stop[ps->port], 1); + fatal = 1; + break; + } + } + + close(fd); + if (fatal) break; } - close(fd); return NULL; }