I’m encountering an issue where executing an ffmpeg concatenation command through Rust’s Tokio process in a Docker container causes subsequent HTTP requests to fail. The error occurs exclusively after running the ffmpeg command and making immediate requests, resulting in a “curl 52 empty response from server” error with the connection being closed. Notably, this issue does not occur when running the same setup outside of Docker. Additionally, if no HTTP requests are made after the ffmpeg command, the curl 52 error does not occur.
Here is the verbose curl output of my minimum reproducible example (see below).
curl -v "http://localhost:3030"
* Trying 127.0.0.1:3030...
* Connected to localhost (127.0.0.1) port 3030 (#0)
> GET / HTTP/1.1
> Host: localhost:3030
> User-Agent: curl/8.1.2
> Accept: */*
>
* Empty reply from server
* Closing connection 0
curl: (52) Empty reply from server
Here are Docker logs from my minimum reproducible example (see below). The wav files are concatenated successfully, then the container appears to rebuild.
[2024-06-03T05:26:58Z INFO minimal_docker_webserver_post_error] Starting server on 0.0.0.0:3030
[2024-06-03T05:26:58Z INFO warp::server] Server::run; addr=0.0.0.0:3030
[2024-06-03T05:26:58Z INFO warp::server] listening on http://0.0.0.0:3030
[2024-06-03T05:27:07Z INFO minimal_docker_webserver_post_error] WAV files concatenated successfully
[Running 'cargo run']
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.06s
Running `target/debug/minimal_docker_webserver_post_error`
[2024-06-03T05:27:08Z INFO minimal_docker_webserver_post_error] Starting server on 0.0.0.0:3030
[2024-06-03T05:27:08Z INFO warp::server] Server::run; addr=0.0.0.0:3030
[2024-06-03T05:27:08Z INFO warp::server] listening on http://0.0.0.0:3030
What I have tried: I tried using different web frameworks (Warp, Actix-web) and request crates (reqwest, ureq). I also tried running the setup outside of Docker, which worked as expected without any issues. Additionally, I tried running the setup in Docker without making any HTTP requests after the ffmpeg command, and the connection closed successfully without errors. I also tried posting to httpbin with a minimal request, but the issue persisted.
Minimum reproducible example:
main.rs
use warp::Filter;
use reqwest::Client;
use std::convert::Infallible;
use log::{info, error};
use env_logger;
use tokio::process::Command;
#[tokio::main]
async fn main() {
std::env::set_var("RUST_LOG", "debug");
env_logger::init();
let route = warp::path::end()
.and_then(handle_request);
info!("Starting server on 0.0.0.0:3030");
warp::serve(route)
.run(([0, 0, 0, 0], 3030))
.await;
}
async fn handle_request() -> Result<impl warp::Reply, Infallible> {
let client = Client::new();
let output = Command::new("ffmpeg")
.args(&[
"y",
"-i", "concat:/usr/src/minimal_docker_webserver_post_error/file1.wav|/usr/src/minimal_docker_webserver_post_error/file2.wav",
"-c", "copy",
"/usr/src/minimal_docker_webserver_post_error/combined.wav"
])
.output()
.await;
match output {
Ok(output) => {
if output.status.success() {
info!("WAV files concatenated successfully");
} else {
error!("Failed to concatenate WAV files: {:?}", output);
return Ok(warp::reply::with_status("Failed to concatenate WAV files", warp::http::StatusCode::INTERNAL_SERVER_ERROR));
}
},
Err(e) => {
error!("Failed to execute ffmpeg: {:?}", e);
return Ok(warp::reply::with_status("Failed to execute ffmpeg", warp::http::StatusCode::INTERNAL_SERVER_ERROR));
}
}
// ISSUE: Connection closes with curl: (52) Empty reply from server
match client.get("https://httpbin.org/get").send().await {
Ok(response) => info!("GET request successful: {:?}", response),
Err(e) => error!("GET request failed: {:?}", e),
}
match client.post("https://httpbin.org/post")
.body("field1=value1&field2=value2")
.send().await {
Ok(response) => info!("POST request successful: {:?}", response),
Err(e) => error!("POST request failed: {:?}", e),
}
Ok(warp::reply::with_status("Request handled", warp::http::StatusCode::OK))
}
FFMPEG command to generate the two wav files for concatenation
ffmpeg -f lavfi -i "sine=frequency=1000:duration=5" file1.wav && ffmpeg -f lavfi -i "sine=frequency=500:duration=5" file2.wav
Dockerfile
# Use the official Rust image as the base image
FROM rust:latest
# Install cargo-watch
RUN cargo install cargo-watch
# Install ffmpeg
RUN apt-get update && apt-get install -y ffmpeg
# Set the working directory inside the container
WORKDIR /usr/src/minimal_docker_webserver_post_error
# Copy the Cargo.toml and Cargo.lock files
COPY Cargo.toml Cargo.lock ./
# Copy the source code
COPY src ./src
# Copy wav files
COPY file1.wav /usr/src/minimal_docker_webserver_post_error/file1.wav
COPY file2.wav /usr/src/minimal_docker_webserver_post_error/file2.wav
# Install dependencies
RUN cargo build --release
# Expose the port that the application will run on
EXPOSE 3030
# Set the entry point to use cargo-watch
CMD ["cargo", "watch", "-x", "run"]
Cargo.toml
[package]
name = "minimal_docker_webserver_post_error"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
warp = "0.3"
reqwest = { version = "0.12.4", features = ["json"] }
tokio = { version = "1", features = ["full"] }
log = "0.4"
env_logger = "0.11.3"
Making the request to the warp server
curl -v "http://localhost:3030"
This behavior is caused by using cargo watch
and your FFmpeg command re-triggering it.
Your Dockerfile launches the server with cargo watch -x run
, which is poised to rerun cargo run
whenever the local directory is modified - which is /usr/src/minimal_docker_webserver_post_error
. The command being ran on your Warp endpoint will create/modify combined.wav
in that directory. This will cause the current server to be killed by cargo watch
and restarted. This is what you see in your server logs.
The reason this seems to work in isolation but causes a curl (52)
error when an HTTP call is introduced is just due to timing. When there is no HTTP call, your Warp endpoint is able to write the response immediately and thus the response is seen fully by curl before cargo watch
kills the server. But when there is an HTTP call, there is a significant delay (at least many milliseconds) such that the cargo watch
kills the server before it resolves. This means the Warp endpoint did not yet construct the response to curl and the connection was severed, which is reported by curl as "Empty reply from server".
All-in-all cargo watch
is a fine tool, but I would not recommend using it in this scenario. A cargo run
should suffice; you can and should configure Docker itself to auto-restart the container if that is desired.