use std::{
process::ExitStatus,
thread,
time::{
Duration,
Instant,
},
};
use super::{
command_io::CommandIo,
finished_command::FinishedCommand,
kill_failed,
managed_child_process::ManagedChildProcess,
next_sleep,
wait_failed,
};
use crate::CommandError;
pub(crate) struct RunningCommand {
command_text: String,
child_process: ManagedChildProcess,
io: CommandIo,
started_at: Instant,
lossy_output: bool,
}
impl RunningCommand {
pub(crate) fn new(
command_text: String,
child_process: ManagedChildProcess,
io: CommandIo,
lossy_output: bool,
) -> Self {
Self {
command_text,
child_process,
io,
started_at: Instant::now(),
lossy_output,
}
}
pub(crate) fn wait_for_completion(
mut self,
timeout: Option<Duration>,
) -> Result<FinishedCommand, CommandError> {
loop {
let maybe_status = match self.child_process.try_wait() {
Ok(status) => status,
Err(source) => {
let error = wait_failed(&self.command_text, source);
return Err(self.clean_up_after_wait_error(error));
}
};
if let Some(status) = maybe_status {
return self.complete(status);
}
if let Some(timeout) = timeout
&& self.started_at.elapsed() >= timeout
{
return self.handle_timeout(timeout);
}
thread::sleep(next_sleep(timeout, self.started_at.elapsed()));
}
}
fn handle_timeout(mut self, timeout: Duration) -> Result<FinishedCommand, CommandError> {
if let Err(source) = self.child_process.start_kill() {
let error = kill_failed(self.command_text.clone(), timeout, source);
return Err(self.collect_if_child_exited(error));
}
let exit_status = match self.child_process.wait() {
Ok(status) => status,
Err(source) => {
let error = wait_failed(&self.command_text, source);
return Err(self.collect_if_child_exited(error));
}
};
let finished = self.complete(exit_status)?;
Err(CommandError::TimedOut {
command: finished.command_text,
timeout,
output: Box::new(finished.output),
})
}
fn complete(self, status: ExitStatus) -> Result<FinishedCommand, CommandError> {
let output = self.io.collect(
&self.command_text,
status,
self.started_at.elapsed(),
self.lossy_output,
)?;
Ok(FinishedCommand {
command_text: self.command_text,
output,
})
}
fn clean_up_after_wait_error(mut self, error: CommandError) -> CommandError {
let _ = self.child_process.start_kill();
self.collect_if_child_exited(error)
}
fn collect_if_child_exited(mut self, error: CommandError) -> CommandError {
if let Ok(Some(status)) = self.child_process.try_wait() {
let _ = self.complete(status);
}
error
}
}