fix(growing): drop FIFO FD-priming deadlock — ffmpeg-first ordering feeds raw2bmx frames

Root cause of frozen 22KB growing MXF: the parent held a stray read-write
priming writer (FD 7) on the video FIFO while raw2bmx opened it, so raw2bmx
read a malformed/empty stream start and exited after the header (Duration:0).
Proven live on zampp3: removing the FD-7/8 priming + fd-watch loop and simply
starting ffmpeg before raw2bmx lets the blocking FIFO opens pair up naturally
(ffmpeg sole writer). True-1080p59.94 AVC-Intra 100 then grows monotonically
on disk and a mid-write snapshot decodes to its last frame (481 frames @ 8s).
This commit is contained in:
ZGaetano 2026-06-04 19:31:48 +00:00
parent bf486b93dd
commit 967547ae97

View file

@ -937,50 +937,26 @@ const bmx = [
// overwrites them in-place. It is killed by the cleanup trap on exit.
const script = `
set -u
# Save the ORIGINAL stdin (the fc_pipe video stream Node pipes in) to FD 9
# BEFORE any FIFO priming touches fd 0. ffmpeg later reads from FD 9 explicitly,
# guaranteeing it is the sole reader of the raw video running ffmpeg as a
# backgrounded subshell that merely inherited fd 0 starved it of bytes when the
# input was a pipe (the "No filtered frames / empty output" growing failure).
exec 9<&0
VF=$(mktemp -u /tmp/grow_v.XXXXXX); AF=$(mktemp -u /tmp/grow_a.XXXXXX)
OUT=${sh(outPath)}
mkfifo "$VF" "$AF"
PATCHPID=
cleanup() { rm -f "$VF" "$AF"; [ -n "$PATCHPID" ] && kill "$PATCHPID" 2>/dev/null; }
trap cleanup EXIT
# Prime both FIFOs read-write (non-blocking) to break the open-order deadlock.
exec 7<>"$VF" 8<>"$AF"
# raw2bmx: close priming FDs (no stray writer) + read stdin from /dev/null before
# exec so it sees real EOF and never competes for the video pipe.
( exec 7>&- 8>&- 9>&- 0</dev/null; exec ${bmxLine} >/tmp/raw2bmx.log 2>&1 ) &
BMXPID=$!
# ffmpeg reads the raw video from FD 9 (the saved original stdin). pipe:0 in its
# arg list maps to fd 0, so we point fd 0 at FD 9 for the child.
( exec 7>&- 8>&- 0<&9 9<&-; exec ${ffLine} ) &
cleanup() { rm -f "$VF" "$AF"; }
trap cleanup EXIT
( exec 0<&9 9<&-; exec ${ffLine} ) &
FFPID=$!
# Parent no longer needs the saved stdin drop it so ffmpeg is the sole reader.
exec 9<&-
# Forward a clean stop to ffmpeg; raw2bmx then gets EOF and finalizes the footer.
exec ${bmxLine} >/tmp/raw2bmx.log 2>&1 &
BMXPID=$!
stop() { kill -INT "$FFPID" 2>/dev/null; }
trap stop INT TERM
# Drop the parent priming FDs once raw2bmx has opened BOTH FIFOs, so ffmpeg is
# the sole writer (its EOF reaches raw2bmx). If raw2bmx dies early, bail.
for i in $(seq 1 200); do
kill -0 "$BMXPID" 2>/dev/null || break
n=$(ls -l /proc/$BMXPID/fd 2>/dev/null | grep -c -- "$VF\\|$AF")
[ "\${n:-0}" -ge 2 ] && break
sleep 0.1
done
exec 7>&- 8>&-
PATCHPID=
# Wait for ffmpeg (source end), then for raw2bmx to finalize the footer.
wait "$FFPID"; FFRC=$?
wait "$BMXPID"; BMXRC=$?
echo "[grow] ffmpeg rc=$FFRC raw2bmx rc=$BMXRC out=$OUT" >&2
exit "$BMXRC"
`;
return ['-c', script];
return ['-c', script];
}
/**