ej_io/process.rs
1//! Low-level process management utilities.
2
3use std::{
4 ffi::OsStr,
5 io,
6 process::{Child, Command, ExitStatus, Stdio},
7 sync::{
8 Arc,
9 atomic::{AtomicBool, Ordering},
10 },
11 thread::sleep,
12 time::Duration,
13};
14
15/// Errors that can occur during process operations.
16#[derive(Debug)]
17pub enum ProcessError {
18 /// Failed to wait for child process.
19 WaitChildFail,
20 /// Failed to spawn the process.
21 SpawnProcessFail(io::Error),
22 /// Process was terminated.
23 Quit,
24}
25
26/// Current status of a running process.
27pub enum ProcessStatus {
28 /// Process has completed with exit status.
29 Done(ExitStatus),
30 /// Process is still running.
31 Running,
32}
33/// Spawn a new process with piped stdout and stderr.
34///
35/// Launches a subprocess with the given command and arguments.
36/// Both stdout and stderr are piped and can be accessed via the returned Child.
37///
38/// # Arguments
39///
40/// * `cmd` - Command to execute
41/// * `args` - Command line arguments
42///
43/// # Examples
44///
45/// ```rust
46/// use ej_io::process::spawn_process;
47///
48/// let mut child = spawn_process("echo", vec!["Hello".to_string()]).unwrap();
49/// let output = child.stdout.take().unwrap();
50/// ```
51pub fn spawn_process(cmd: &str, args: Vec<String>) -> Result<Child, io::Error> {
52 Command::new(OsStr::new(&cmd))
53 .args(args)
54 .stdout(Stdio::piped())
55 .stderr(Stdio::piped())
56 .spawn()
57}
58/// Check process status without blocking.
59///
60/// Polls the process status in a non-blocking manner. Includes a small sleep
61/// to prevent excessive CPU usage when called in a loop.
62///
63/// Note: This function may never return `ProcessStatus::Done` if the process
64/// is blocked waiting for stdin. Use `stop_child` and `capture_exit_status`
65/// to handle such cases.
66///
67/// # Arguments
68///
69/// * `child` - Mutable reference to the child process
70///
71/// # Examples
72///
73/// ```rust
74/// use ej_io::process::{spawn_process, get_process_status, ProcessStatus};
75///
76/// let mut child = spawn_process("sleep", vec!["1".to_string()]).unwrap();
77///
78/// loop {
79/// match get_process_status(&mut child).unwrap() {
80/// ProcessStatus::Done(exit_status) => {
81/// println!("Process finished with: {:?}", exit_status);
82/// break;
83/// }
84/// ProcessStatus::Running => {
85/// println!("Still running...");
86/// }
87/// }
88/// }
89/// ```
90pub fn get_process_status(child: &mut Child) -> Result<ProcessStatus, ProcessError> {
91 match child.try_wait() {
92 Ok(status) => match status {
93 Some(exit_status) => Ok(ProcessStatus::Done(exit_status)),
94 None => {
95 sleep(Duration::from_millis(10));
96 Ok(ProcessStatus::Running)
97 }
98 },
99 Err(_) => return Err(ProcessError::WaitChildFail),
100 }
101}
102
103/// Terminate a child process.
104///
105/// Sends a kill signal to the child process.
106///
107/// # Examples
108///
109/// ```rust
110/// use ej_io::process::{spawn_process, stop_child};
111///
112/// let mut child = spawn_process("sleep", vec!["60".to_string()]).unwrap();
113/// stop_child(&mut child).unwrap();
114/// ```
115pub fn stop_child(child: &mut Child) -> Result<(), io::Error> {
116 child.kill()
117}
118/// Capture the exit status of a child process.
119///
120/// Waits for the child process to complete and returns its exit status.
121/// This will close the stdin pipe, which can unblock processes waiting for input.
122///
123/// # Examples
124///
125/// ```rust
126/// use ej_io::process::{spawn_process, capture_exit_status};
127///
128/// let mut child = spawn_process("echo", vec!["done".to_string()]).unwrap();
129/// let exit_status = capture_exit_status(&mut child).unwrap();
130/// assert!(exit_status.success());
131/// ```
132pub fn capture_exit_status(child: &mut Child) -> Result<ExitStatus, io::Error> {
133 child.wait()
134}
135
136/// Wait for a child process with cancellation support.
137///
138/// Waits for the child process to complete while periodically checking
139/// if it should be cancelled via the atomic boolean flag.
140///
141/// # Arguments
142///
143/// * `child` - Mutable reference to the child process
144/// * `should_stop` - Atomic flag to signal process termination
145///
146/// # Examples
147///
148/// ```rust
149/// use ej_io::process::{spawn_process, wait_child};
150/// use std::sync::{Arc, atomic::AtomicBool};
151///
152/// let mut child = spawn_process("sleep", vec!["1".to_string()]).unwrap();
153/// let should_stop = Arc::new(AtomicBool::new(false));
154///
155/// let exit_status = wait_child(&mut child, should_stop).unwrap();
156/// assert!(exit_status.success());
157/// ```
158pub fn wait_child(
159 child: &mut Child,
160 should_stop: Arc<AtomicBool>,
161) -> Result<ExitStatus, ProcessError> {
162 loop {
163 if should_stop.load(Ordering::Relaxed) {
164 let _ = stop_child(child);
165 return Err(ProcessError::Quit);
166 }
167 match get_process_status(child) {
168 Ok(status) => match status {
169 ProcessStatus::Done(exit_status) => return Ok(exit_status),
170 ProcessStatus::Running => {
171 sleep(Duration::from_millis(10));
172 }
173 },
174 Err(_) => {
175 return Err(ProcessError::WaitChildFail);
176 }
177 }
178 }
179}