Skip to content

V3 engine: the ab-av1 adapter probes media synchronously, blocking the runtime thread and unreachable by cancel_job #87

Description

@Loufe

Line references at rewrite b58160b, and at ab-av1 upstream/main 0e682f2 for the dependency. Same class as #82, different code path: #82 covers the media.rs probes and assumes "force-stop reaches the ab-av1/remux child". It does not reach this one.

What happens

run_search and run_encode each probe the input synchronously before entering their cancellation loop:

  • ab_av1/operation.rs:80let probe = Arc::new(ffprobe::probe(&request.input));, with the tokio::select! cancellation loop only starting at :89.
  • ab_av1/operation.rs:159 — same, loop starts at :163.

ffprobe::probe is ab_av1::ffprobe::probe (ffprobe.rs:42 upstream), which calls the ffprobe 0.4 crate. That crate uses std::process::Command::output(): a real subprocess, blocking the calling thread until it exits, with no timeout. Upstream has nine such synchronous call sites in async contexts, including command/sample_encode.rs:292 inside the sample loop.

1. It blocks the whole runtime

The adapter runs on Builder::new_current_thread() on a dedicated OS thread (ab_av1/runtime.rs:412). A blocking call there stops every task, including the one that would observe a cancellation. The pre-loop cancellation checks at :77 and :156 have already passed by then, so a cancel arriving during the probe is not seen until the probe returns.

Upstream has the same exposure in its own CLI (main.rs:32 is #[tokio::main(flavor = "current_thread")]), and worse: at the first probe of each command the block precedes any poll of signal::ctrl_c(), so the handler may never install.

2. cancel_job cannot reach the child

The ffprobe crate spawns through std::process::Command, not through process::child::spawn, so the child gets no dedicated process group and no CREATE_NO_WINDOW, and is never registered in ab-av1's child registry. cancel_job() (operation.rs:205) iterates that registry, so it cannot terminate this probe.

To be precise about a thing that is easy to overstate: std::process::Command does not setsid, so the child is not group-less, it inherits ours. It is unreachable by our per-job kill, not an orphan in the general sense.

When this actually bites

Verified against upstream with a hanging ffprobe shim. At an interactive TTY a hung ffprobe is killed by Ctrl-C, because the signal reaches the whole foreground process group, and the probe unblocks. Reproducing the wedge required trap '' INT TERM in the shim plus a kill -INT targeted at the ab-av1 pid alone. Reproduction showed the process still alive after two SIGINTs; only SIGTERM ended it, bypassing cleanup entirely. A control run blocked on an async ffmpeg child instead exited in about a second with Error: ctrl_c.

That leaves two live cases, and both are ours:

  1. ffprobe stuck in uninterruptible D-state I/O (network mount, failing disk), where signals do not help.
  2. A signal delivered programmatically to the supervised process alone, which is exactly what a GUI supervisor does.

In either case the job worker wedges with no path to recovery: cancellation is not observed, force-stop does not reach the process, and wait_for_report polls indefinitely (ab_av1/job.rs:518-553).

Direction

The bounded mechanism already exists in media.rs:215-270 (probe_supervised, 30s timeout, cooperative cancel), as #82 observes. The adapter can probe through it and hand the result over, because crf_search::run and encode::run both take a caller-supplied Arc<Ffprobe> rather than probing themselves. That fixes operation.rs:80 and :159 entirely.

It does not fix sample_encode.rs:292, which is inside the dependency. That one needs an upstream change (make probe async with the body in spawn_blocking, roughly 30 lines across the nine call sites) or a patch in our pinned fork.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions