Using CancellationToken for graceful process termination in libffmpeg
libffmpeg uses tokio_util::sync::CancellationToken to manage process lifecycle and enable cooperative cancellation across async tasks. All execution functions require a cancellation token.
let token = CancellationToken::new();// Cancel from another tasktokio::spawn(async move { tokio::time::sleep(Duration::from_secs(30)).await; token.cancel(); // Cancel after 30 seconds});let result = ffmpeg_slim(token, |cmd| { cmd.arg("-i").arg("input.mp4"); cmd.arg("output.mp4");}).await?;
From ~/workspace/source/libffmpeg/src/ffmpeg/graceful.rs:40-44:
// Different source token for the process, lets us gracefully exitlet process_token = CancellationToken::new();// Cancelled after the process exitslet exit_token = CancellationToken::new();
Graceful mode uses multiple tokens for sophisticated cancellation:
User cancels → Main cancellation token is cancelled
Send “q” command → client.send("q") to ffmpeg’s stdin
Wait up to 5 seconds → Process should exit cleanly
Fallback to SIGKILL → If timeout expires, force kill
From ~/workspace/source/libffmpeg/src/ffmpeg/graceful.rs:58-84:
let shutdown_handle = { let client = client.clone(); let process_token = process_token.clone(); let exit_token = exit_token.clone(); let kill_token = cancellation_token.child_token(); tokio::spawn( async move { // Wait for kill token to cancel (user requested cancellation) tokio::select! { () = exit_token.cancelled() => { // if process exits before kill is requested, we don't want to kill the process return }, () = kill_token.cancelled() => { // Continue killing the process } } // Send quit client.send("q").await; // Wait for exit to be cancelled (process exited), with max of 5 seconds match tokio::time::timeout(Duration::from_secs(5), exit_token.cancelled()).await { Ok(()) => {} Err(_timeout) => { // Process didn't respond to quit command, tell the manager to kill the process tracing::warn!( "ffmpeg process did not respond to quit command, sending SIGKILL" ); process_token.cancel(); } } } )};
Use graceful mode when you need output files to be properly finalized, even when cancelled. This ensures ffmpeg writes file headers, indexes, and metadata correctly.
From ~/workspace/source/libffmpeg/src/ffmpeg/graceful.rs:57,94:
let kill_token = cancellation_token.child_token();// ...process_token.child_token(),
By creating child tokens internally, libffmpeg ensures that cancelling your provided token will properly cancel all internal operations without affecting other operations that might share the same parent token.
#[tokio::test]async fn test_cancellation() { let token = CancellationToken::new(); let token_clone = token.clone(); // Cancel after 100ms tokio::spawn(async move { tokio::time::sleep(Duration::from_millis(100)).await; token_clone.cancel(); }); let result = ffmpeg_slim(token, |cmd| { cmd.arg("-f").arg("lavfi"); cmd.arg("-i").arg("testsrc=duration=60"); cmd.arg("-f").arg("null"); cmd.arg("-"); }).await; // Should fail due to cancellation assert!(result.is_err());}