| Crates.io | command_timeout |
| lib.rs | command_timeout |
| version | 0.1.3 |
| created_at | 2025-03-29 18:49:33.872454+00 |
| updated_at | 2025-04-11 05:22:29.247774+00 |
| description | A tokio friendly command exec with smart timeout |
| homepage | |
| repository | https://github.com/cfsmp3/exec_timeout_rs |
| max_upload_size | |
| id | 1611546 |
| size | 92,952 |
A Rust library providing an async (Tokio) function to run external commands with advanced timeout capabilities. "Advanced" means that the timeout can be extended if the external command is making progress. Progress is defined as "is writing to stdout or stderr".
Sometimes, external commands like git clone or other network-dependent operations can hang indefinitely due to network issues or other problems. This library provides a way to run such commands while enforcing multiple timeout constraints:
The primary goal is to allow commands to run as long as they are actively making progress (producing output) but terminate them if they become stuck or exceed a hard limit.
SIGKILL via nix::sys::signal::killpg) to ensure child processes (like sleep started by sh) are also terminated.Vec<u8>, suitable for binary data.CommandOutput) containing captured output, exit status (Option<ExitStatus>), total duration, and whether a timeout occurred.tracing crate (debug! level) for detailed logging of internal events (spawning, deadlines, activity, kills).setpgid, killpg with SIGKILL) for process group management and termination. It will not compile or work correctly on other platforms like Windows or macOS. This is intentional.Add this to your Cargo.toml:
[dependencies]
command-timeout = "0.1.0" # Replace with the desired version from crates.io
# Required dependencies if you don't already have them
tokio = { version = "1", features = ["full"] }
tracing = "0.1"
thiserror = "1.0"
nix = { version = "0.29", features = ["signal", "process"] } # Check latest version
libc = "0.2"
The main entry point is the run_command_with_timeout async function.
use command_timeout::{run_command_with_timeout, CommandOutput, CommandError};
use std::process::Command;
use std::time::Duration;
use tokio; // Ensure tokio runtime is available
#[tokio::main]
async fn main() -> Result<(), CommandError> {
// 1. Configure the command
let mut cmd = Command::new("sh");
cmd.arg("-c")
.arg("echo 'Starting...'; sleep 1; echo 'Progress...' >&2; sleep 3; echo 'Done.'");
// 2. Define timeouts
let min_timeout = Duration::from_millis(500); // Must run for at least 0.5s
let max_timeout = Duration::from_secs(10); // Absolute limit of 10s
let activity_timeout = Duration::from_secs(2); // Kill if idle for 2s
// 3. Run the command
println!("Running command...");
let result = run_command_with_timeout(
cmd,
min_timeout,
max_timeout,
activity_timeout,
).await?; // Handle potential setup/IO errors
// 4. Process the results
println!("\n--- Results ---");
println!("Timed Out: {}", result.timed_out);
println!("Duration: {:?}", result.duration);
if let Some(status) = result.exit_status {
println!("Exit Status: {}", status);
println!("Exit Code: {:?}", status.code());
println!("Terminated by Signal: {:?}", status.signal()); // Useful if killed
} else {
println!("Exit Status: None (Process killed and status maybe unavailable)");
}
// Output is Vec<u8>, use from_utf8_lossy for display if expecting text
println!("Stdout:\n{}", String::from_utf8_lossy(&result.stdout));
println!("Stderr:\n{}", String::from_utf8_lossy(&result.stderr));
Ok(())
}