use std::cmp::max;
use std::fmt::Display;
use std::result;
use std::thread::sleep;
use std::time::{Duration, SystemTime};
use errors::*;
const MIN_SLEEP_SECS: u64 = 4;
pub struct WaitOptions {
timeout: Option<Duration>,
retry_interval: Duration,
allowed_errors: u16,
}
impl WaitOptions {
pub fn timeout<D: Into<Option<Duration>>>(mut self, timeout: D) -> Self {
self.timeout = timeout.into();
self
}
pub fn retry_interval(mut self, interval: Duration) -> Self {
self.retry_interval = interval;
self
}
pub fn allowed_errors(mut self, count: u16) -> Self {
self.allowed_errors = count;
self
}
}
impl Default for WaitOptions {
fn default() -> Self {
Self {
timeout: None,
retry_interval: Duration::from_secs(10),
allowed_errors: 2,
}
}
}
pub enum WaitStatus<T, E> {
Finished(T),
Waiting,
FailedTemporarily(E),
FailedPermanently(E),
}
#[macro_export]
macro_rules! try_with_temporary_failure {
($e:expr) => {
match $e {
Ok(v) => v,
Err(e) => return $crate::wait::WaitStatus::FailedTemporarily(e.into()),
}
};
}
#[macro_export]
macro_rules! try_with_permanent_failure {
($e:expr) => {
match $e {
Ok(v) => v,
Err(e) => return $crate::wait::WaitStatus::FailedPermanently(e.into()),
}
};
}
impl<T, E> From<E> for WaitStatus<T, E> {
fn from(err: E) -> Self {
WaitStatus::FailedTemporarily(err)
}
}
pub fn wait<T, E, F>(
options: &WaitOptions,
mut f: F,
) -> result::Result<T, E>
where
F: FnMut() -> WaitStatus<T, E>,
E: Display,
Error: Into<E>,
{
let deadline = options.timeout.map(|to| SystemTime::now() + to);
let mut errors_seen = 0;
loop {
if let Some(deadline) = deadline {
if SystemTime::now() > deadline {
return Err(Error::Timeout.into());
}
}
match f() {
WaitStatus::Finished(value) => { return Ok(value); }
WaitStatus::Waiting => {}
WaitStatus::FailedTemporarily(ref e) if errors_seen < options.allowed_errors => {
errors_seen += 1;
error!(
"Got error, will retry ({}/{}): {}",
errors_seen,
options.allowed_errors,
e,
);
}
WaitStatus::FailedTemporarily(err) => { return Err(err); }
WaitStatus::FailedPermanently(err) => { return Err(err); }
}
sleep(max(Duration::from_secs(MIN_SLEEP_SECS), options.retry_interval));
}
}