use crate::diagnostic_log::warn;
use std::io::{Error, ErrorKind, Result};
use std::process::{Child, Command, ExitStatus, Output};
use std::time::Duration;
pub const START_RETRY_BUDGET: Duration = Duration::from_secs(5);
const FIRST_PAUSE: Duration = Duration::from_millis(10);
const LONGEST_PAUSE: Duration = Duration::from_millis(500);
const TAG: &str = "startRetry";
pub fn retry_refused_start_with<T>(
what: &str,
budget: Duration,
pause: &mut dyn FnMut(Duration),
start: &mut dyn FnMut() -> Result<T>,
) -> Result<T> {
let mut refusals: u32 = 0;
let mut paused: Duration = Duration::ZERO;
let mut delay: Duration = FIRST_PAUSE;
loop {
let error: Error = match start() {
Ok(started) => return Ok(started),
Err(error) if error.kind() == ErrorKind::WouldBlock => error,
Err(error) => return Err(error),
};
refusals += 1;
if paused + delay > budget {
return Err(Error::new(
ErrorKind::WouldBlock,
format!("{error}; the start was refused {refusals} times over {} ms", paused.as_millis()),
));
}
warn(
TAG,
format!(
"starting {what} was refused for lack of processes ({error}); trying again in {} ms",
delay.as_millis()
)
.as_str(),
);
pause(delay);
paused += delay;
delay = (delay * 2).min(LONGEST_PAUSE);
}
}
pub fn retry_refused_start<T>(what: &str, start: &mut dyn FnMut() -> Result<T>) -> Result<T> {
return retry_refused_start_with(what, START_RETRY_BUDGET, &mut |delay: Duration| { std::thread::sleep(delay); }, start);
}
pub trait RetryStart {
fn spawn_retrying(&mut self) -> Result<Child>;
fn output_retrying(&mut self) -> Result<Output>;
fn status_retrying(&mut self) -> Result<ExitStatus>;
}
impl RetryStart for Command {
fn spawn_retrying(&mut self) -> Result<Child> {
let what: String = self.get_program().to_string_lossy().into_owned();
return retry_refused_start(what.as_str(), &mut || { return self.spawn(); });
}
fn output_retrying(&mut self) -> Result<Output> {
let what: String = self.get_program().to_string_lossy().into_owned();
return retry_refused_start(what.as_str(), &mut || { return self.output(); });
}
fn status_retrying(&mut self) -> Result<ExitStatus> {
let what: String = self.get_program().to_string_lossy().into_owned();
return retry_refused_start(what.as_str(), &mut || { return self.status(); });
}
}
#[cfg(unix)]
pub fn exec_retrying(command: &mut Command) -> Error {
use std::os::unix::process::CommandExt;
let what: String = command.get_program().to_string_lossy().into_owned();
let refused: Result<()> = retry_refused_start(what.as_str(), &mut || { return Err(command.exec()); });
match refused {
Err(error) => return error,
Ok(()) => return Error::other("exec returned without an error"),
}
}
#[cfg(test)]
#[path = "start_retry_tests.rs"]
mod tests;