use crate::{Error, Process, Result, PID};
use serde::{Deserialize, Serialize};
use std::fmt::{self, Display, Formatter};
use std::path::PathBuf;
use std::time::Duration;
#[derive(PartialEq, Serialize, Deserialize, Debug)]
pub struct StartArgs {
pub prog: String,
pub args: Vec<String>,
pub restart_policy: RestartPolicy,
pub working_dir: PathBuf,
}
#[derive(PartialEq, Serialize, Deserialize, Debug)]
pub struct ChangeRestartPolicyArgs {
pub pid: PID,
pub restart_policy: RestartPolicy,
}
#[derive(PartialEq, Serialize, Deserialize, Debug)]
pub struct SendArgs {
pub pid: PID,
pub input: String,
}
struct ManagedProcess {
process: Process,
restarted: Option<PID>,
started: bool,
restart_policy: RestartPolicy,
}
pub struct State {
processes: Vec<ManagedProcess>,
stay_alive_duration: Duration,
}
#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)]
pub enum ExitStatus {
NotExited,
Success,
Failure,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct ProcessStatus {
pub pid: PID,
pub prog: String,
pub args: Vec<String>,
pub exit_status: ExitStatus,
pub started: bool,
pub restarted: Option<PID>,
pub restart_policy: RestartPolicy,
}
pub struct UpkeepInfo {
pub restarted: Vec<PID>,
}
#[derive(PartialEq, Serialize, Deserialize, Copy, Clone, Debug)]
pub enum RestartPolicy {
Never,
Always,
OnFailure(u32),
}
impl State {
pub fn new(stay_alive_secs: u64) -> Self {
let processes = Vec::new();
let stay_alive_duration = Duration::from_secs(stay_alive_secs);
Self {
processes,
stay_alive_duration,
}
}
pub fn start(&mut self, args: &StartArgs) -> Result<ProcessStatus> {
let mut managed = ManagedProcess {
process: Process::start(&args.prog, &args.args, &args.working_dir)?,
restarted: None,
started: false,
restart_policy: args.restart_policy,
};
std::thread::sleep(self.stay_alive_duration);
if let Some(true) = managed.process.is_alive() {
managed.started = true;
} else {
return Err(Error::StayAliveError);
}
self.processes.push(managed);
let pid = self.processes.len() - 1;
Ok(self.status_of(pid))
}
pub fn output(&mut self, args: PID) -> Result<&Vec<u8>> {
let pid = self.check_pid(args)?;
self.processes[pid].process.gather_output();
Ok(&self.processes[pid].process.output)
}
pub fn status(&mut self, count: &Option<usize>) -> Vec<ProcessStatus> {
let mut v = Vec::new();
let len = self.processes.len();
let mut count = count.unwrap_or(len);
if len < count {
count = len;
}
for pid in (len - count)..self.processes.len() {
let s = self.status_of(pid);
v.push(s);
}
v
}
pub fn stop(&mut self, args: PID) -> Result<ProcessStatus> {
let pid = self.check_pid(args)?;
match self.processes[pid].process.kill() {
Ok(_) => Ok(self.status_of(pid)),
Err(e) => Err(e),
}
}
pub fn restart(&mut self, args: PID, retry: bool) -> Result<ProcessStatus> {
let pid = self.check_pid(args)?;
let stopped = self.stop(args)?;
let restart_policy = if retry {
if let RestartPolicy::OnFailure(n) = stopped.restart_policy {
RestartPolicy::OnFailure(n - 1)
} else {
stopped.restart_policy
}
} else {
stopped.restart_policy
};
let status = self.start(&StartArgs {
prog: stopped.prog.to_owned(),
args: stopped.args.clone(),
restart_policy,
working_dir: self.processes[pid].process.working_dir.clone(),
})?;
self.processes[stopped.pid].restarted = Some(status.pid);
Ok(status)
}
pub fn send(&mut self, args: &SendArgs) -> Result<()> {
let pid = self.check_pid(args.pid)?;
self.processes[pid].process.send_input(&args.input)
}
pub fn status_of(&mut self, pid: PID) -> ProcessStatus {
let proc = self
.processes
.get_mut(pid)
.expect("attempted to get status of invalid PID");
let prog = proc.process.prog.clone();
let args = proc.process.args.clone();
let exit_status = match proc.process.exit_status() {
Some(e) => match e.success() {
true => ExitStatus::Success,
false => ExitStatus::Failure,
},
None => ExitStatus::NotExited,
};
let started = proc.started;
let restarted = proc.restarted;
let restart_policy = proc.restart_policy;
ProcessStatus {
pid,
prog,
args,
exit_status,
started,
restarted,
restart_policy,
}
}
pub fn change_restart_policy(
&mut self,
args: &ChangeRestartPolicyArgs,
) -> Result<ProcessStatus> {
let pid = self.check_pid(args.pid)?;
self.processes[pid].restart_policy = args.restart_policy;
Ok(self.status_of(pid))
}
pub fn check_pid(&self, pid: PID) -> Result<PID> {
if pid >= self.processes.len() {
Err(Error::InvalidPID)
} else {
Ok(pid)
}
}
pub fn upkeep(&mut self) -> Result<UpkeepInfo> {
let mut failed_processes = Vec::new();
for (i, proc) in self.processes.iter_mut().enumerate() {
if proc.process.exit_status().is_some() && proc.restarted.is_none()
{
failed_processes.push(i);
}
}
let mut restarts = Vec::new();
for pid in &failed_processes {
let status = self.status_of(*pid);
match status.restart_policy {
RestartPolicy::Never => continue,
RestartPolicy::Always => {
self.restart(*pid, false)?;
restarts.push(*pid);
}
RestartPolicy::OnFailure(n) => {
if n == 0 {
continue;
}
if status.exit_status == ExitStatus::Success {
continue;
}
if self.restart(*pid, true).is_ok() {
restarts.push(*pid);
break;
}
}
}
}
Ok(UpkeepInfo {
restarted: restarts,
})
}
}
impl Display for RestartPolicy {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
RestartPolicy::Never => write!(f, "never"),
RestartPolicy::Always => write!(f, "always"),
RestartPolicy::OnFailure(retries) => {
write!(f, "on failure ({})", retries)
}
}
}
}
impl Drop for State {
fn drop(&mut self) {
for proc in &mut self.processes {
proc.process
.kill()
.expect("failed to kill process during drop");
proc.process
.child
.wait()
.expect("failed to wait on process during drop");
}
}
}