From da68cd965df9b729a245ff4f4fdc5fc815c427a6 Mon Sep 17 00:00:00 2001 From: Jan Date: Wed, 8 Apr 2026 11:28:44 +0200 Subject: [PATCH] fix ffmpeg fallback decoder pipe deadlock Prevent the fallback FFmpeg decoder from deadlocking on longer-running streams. The decoder previously drained stderr with io.ReadAll() before reading PCM from stdout. Once FFmpeg filled the stdout pipe buffer, the process blocked on further stdout writes, never closed stderr, and io.ReadAll(stderr) never returned. That stalled the decoder before readPCM() could even start. Drain stderr concurrently in its own goroutine so stdin, stdout, and stderr can all make progress in parallel. This matches the expected pipe handling model for long-running FFmpeg processes and keeps the fallback decoder usable for real streams. --- internal/ingest/decoder/fallback/ffmpeg.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/internal/ingest/decoder/fallback/ffmpeg.go b/internal/ingest/decoder/fallback/ffmpeg.go index 6dc4198..27f10ee 100644 --- a/internal/ingest/decoder/fallback/ffmpeg.go +++ b/internal/ingest/decoder/fallback/ffmpeg.go @@ -81,7 +81,16 @@ func (d *FFmpegDecoder) DecodeStream(ctx context.Context, r io.Reader, meta deco } }() - stderrData, _ := io.ReadAll(stderr) + // DEADLOCK FIX: stderr and stdout must be drained concurrently. + // Reading stderr synchronously before readPCM means ffmpeg blocks when + // stdout's pipe buffer fills (typically 64KB), which prevents it from + // closing stderr, which prevents ReadAll from returning — deadlock. + var stderrData []byte + wg.Add(1) + go func() { + defer wg.Done() + stderrData, _ = io.ReadAll(stderr) + }() readErr := d.readPCM(ctx, stdout, sampleRate, channels, meta.SourceID, emit) waitErr := cmd.Wait() wg.Wait()