fix(deltacast-bridge): video EPIPE reopens FIFO instead of stopping port permanently

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
This commit is contained in:
Claude 2026-06-02 11:33:17 +00:00
parent 858c9f7b97
commit 08be8fea77

View file

@ -297,39 +297,51 @@ static void *audio_thread(void *arg) {
static void *video_thread(void *arg) { static void *video_thread(void *arg) {
PortState *ps = (PortState *)arg; PortState *ps = (PortState *)arg;
int fd = open(ps->video_fifo, O_WRONLY); /* Outer loop: reopen the FIFO writer each time a reader connects.
if (fd < 0) { * Mirror the audio thread pattern EPIPE means the ffmpeg sidecar for
fprintf(stderr, "[video:%u] open FIFO failed: %s\n", ps->port, strerror(errno)); * this port died (session stop/restart), NOT a hardware fault. We reopen
atomic_store(&g_port_stop[ps->port], 1); * and block until the next recorder start; other ports are unaffected. */
return NULL;
}
HANDLE slot = NULL;
while (!atomic_load(&g_stop) && !atomic_load(&g_port_stop[ps->port])) { while (!atomic_load(&g_stop) && !atomic_load(&g_port_stop[ps->port])) {
ULONG r = VHD_LockSlotHandle(ps->video_stream, &slot);
if (r == VHDERR_NOERROR) { int fd = open(ps->video_fifo, O_WRONLY);
BYTE *buf = NULL; if (fd < 0) {
ULONG sz = 0; fprintf(stderr, "[video:%u] open FIFO failed: %s\n", ps->port, strerror(errno));
if (VHD_GetSlotBuffer(slot, VHD_SDI_BT_VIDEO, &buf, &sz) == VHDERR_NOERROR) { struct timespec ts = {0, 200000000L};
if (write_all(fd, buf, sz) < 0) { nanosleep(&ts, NULL);
/* EPIPE on video: the capture sidecar for this port died. continue;
* 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;
} }
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; return NULL;
} }