aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authordiogo464 <[email protected]>2025-06-19 10:03:56 +0100
committerdiogo464 <[email protected]>2025-06-19 10:03:56 +0100
commit5101d9c410a7c901ea20636d2a4e56b3282a1c14 (patch)
tree4b229f6ced108f14957a7e80c2c30880f7174811 /src
parent7b7dbf8948fa063d040a744a4903be1df75ca943 (diff)
Add wait subcommand for blocking until process termination
Implements a new 'wait' subcommand that blocks until a specified daemon process terminates, with configurable timeout and polling interval. Features: - Default 30-second timeout, configurable with --timeout flag - Infinite wait with --timeout 0 - Configurable polling interval with --interval flag (default 1 second) - Quiet operation - only shows errors on failure - Preserves PID files (doesn't clean up) - Exit codes: 0 for success, 1 for failure Usage examples: - demon wait my-process # Wait 30s - demon wait my-process --timeout 0 # Wait indefinitely - demon wait my-process --timeout 60 --interval 2 # Custom timeout/interval Added comprehensive test suite covering: - Non-existent processes - Already terminated processes - Normal process termination - Timeout scenarios - Infinite timeout behavior - Custom polling intervals Updated documentation: - README.md with wait command reference and usage examples - LLM guide with detailed wait command documentation - Integration examples for development workflows 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
Diffstat (limited to 'src')
-rw-r--r--src/main.rs100
1 files changed, 100 insertions, 0 deletions
diff --git a/src/main.rs b/src/main.rs
index e90bfed..5547d9a 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -142,6 +142,9 @@ enum Commands {
142 142
143 /// Output comprehensive usage guide for LLMs 143 /// Output comprehensive usage guide for LLMs
144 Llm, 144 Llm,
145
146 /// Wait for a daemon process to terminate
147 Wait(WaitArgs),
145} 148}
146 149
147#[derive(Args)] 150#[derive(Args)]
@@ -204,6 +207,20 @@ struct StatusArgs {
204 id: String, 207 id: String,
205} 208}
206 209
210#[derive(Args)]
211struct WaitArgs {
212 /// Process identifier
213 id: String,
214
215 /// Timeout in seconds (0 = infinite)
216 #[arg(long, default_value = "30")]
217 timeout: u64,
218
219 /// Polling interval in seconds
220 #[arg(long, default_value = "1")]
221 interval: u64,
222}
223
207fn main() { 224fn main() {
208 tracing_subscriber::fmt() 225 tracing_subscriber::fmt()
209 .with_writer(std::io::stderr) 226 .with_writer(std::io::stderr)
@@ -243,6 +260,7 @@ fn run_command(command: Commands) -> Result<()> {
243 print_llm_guide(); 260 print_llm_guide();
244 Ok(()) 261 Ok(())
245 } 262 }
263 Commands::Wait(args) => wait_daemon(&args.id, args.timeout, args.interval),
246 } 264 }
247} 265}
248 266
@@ -954,6 +972,28 @@ demon tail web-server # Follow both logs
954demon tail web-server --stdout # Follow only stdout 972demon tail web-server --stdout # Follow only stdout
955``` 973```
956 974
975### demon wait <id> [--timeout <seconds>] [--interval <seconds>]
976Blocks until a daemon process terminates.
977
978**Syntax**: `demon wait <id> [--timeout <seconds>] [--interval <seconds>]`
979
980**Behavior**:
981- Checks if PID file exists and process is running
982- Polls the process every `interval` seconds (default: 1 second)
983- Waits for up to `timeout` seconds (default: 30 seconds)
984- Use `--timeout 0` for infinite wait
985- Exits successfully when process terminates
986- Fails with error if process doesn't exist or timeout is reached
987- Does not clean up PID files (use `demon clean` for that)
988
989**Examples**:
990```bash
991demon wait web-server # Wait 30s for termination
992demon wait backup-job --timeout 0 # Wait indefinitely
993demon wait data-processor --timeout 3600 # Wait up to 1 hour
994demon wait short-task --interval 2 # Poll every 2 seconds
995```
996
957### demon clean 997### demon clean
958Removes orphaned files from processes that are no longer running. 998Removes orphaned files from processes that are no longer running.
959 999
@@ -996,6 +1036,13 @@ demon status my-web-server # Check if it started
996demon tail my-web-server # Monitor logs 1036demon tail my-web-server # Monitor logs
997``` 1037```
998 1038
1039### Waiting for Process Completion
1040```bash
1041demon run batch-job python process_data.py
1042demon wait batch-job --timeout 600 # Wait up to 10 minutes
1043demon cat batch-job # Check output after completion
1044```
1045
999### Running a Backup Job 1046### Running a Backup Job
1000```bash 1047```bash
1001demon run nightly-backup -- rsync -av /data/ /backup/ 1048demon run nightly-backup -- rsync -av /data/ /backup/
@@ -1059,6 +1106,59 @@ This tool is designed for Linux environments and provides a simple interface for
1059 ); 1106 );
1060} 1107}
1061 1108
1109fn wait_daemon(id: &str, timeout: u64, interval: u64) -> Result<()> {
1110 let pid_file = format!("{}.pid", id);
1111
1112 // Check if PID file exists and read PID data
1113 let pid_file_data = match PidFile::read_from_file(&pid_file) {
1114 Ok(data) => data,
1115 Err(PidFileReadError::FileNotFound) => {
1116 return Err(anyhow::anyhow!("Process '{}' not found (no PID file)", id));
1117 }
1118 Err(PidFileReadError::FileInvalid(reason)) => {
1119 return Err(anyhow::anyhow!("Process '{}' has invalid PID file: {}", id, reason));
1120 }
1121 Err(PidFileReadError::IoError(err)) => {
1122 return Err(anyhow::anyhow!("Failed to read PID file for '{}': {}", id, err));
1123 }
1124 };
1125
1126 let pid = pid_file_data.pid;
1127
1128 // Check if process is currently running
1129 if !is_process_running_by_pid(pid) {
1130 return Err(anyhow::anyhow!("Process '{}' is not running", id));
1131 }
1132
1133 tracing::info!("Waiting for process '{}' (PID: {}) to terminate", id, pid);
1134
1135 // Handle infinite timeout case
1136 if timeout == 0 {
1137 loop {
1138 if !is_process_running_by_pid(pid) {
1139 tracing::info!("Process '{}' (PID: {}) has terminated", id, pid);
1140 return Ok(());
1141 }
1142 thread::sleep(Duration::from_secs(interval));
1143 }
1144 }
1145
1146 // Handle timeout case
1147 let mut elapsed = 0;
1148 while elapsed < timeout {
1149 if !is_process_running_by_pid(pid) {
1150 tracing::info!("Process '{}' (PID: {}) has terminated", id, pid);
1151 return Ok(());
1152 }
1153
1154 thread::sleep(Duration::from_secs(interval));
1155 elapsed += interval;
1156 }
1157
1158 // Timeout reached
1159 Err(anyhow::anyhow!("Timeout reached waiting for process '{}' to terminate", id))
1160}
1161
1062fn find_pid_files() -> Result<Vec<std::fs::DirEntry>> { 1162fn find_pid_files() -> Result<Vec<std::fs::DirEntry>> {
1063 let entries = std::fs::read_dir(".")? 1163 let entries = std::fs::read_dir(".")?
1064 .filter_map(|entry| { 1164 .filter_map(|entry| {