use std::{
io, thread,
time::{Duration, Instant},
};
use log::debug;
use crate::stream::Stream;
pub const DEFAULT_TIMEOUT: Duration = Duration::from_secs(60);
const RETRY_BACKOFF_MIN: Duration = Duration::from_millis(1);
const RETRY_BACKOFF_MAX: Duration = Duration::from_millis(250);
#[derive(Clone, Copy, Debug)]
pub enum Retry {
Never,
Until(Duration),
}
impl Default for Retry {
fn default() -> Self {
Self::Until(DEFAULT_TIMEOUT)
}
}
impl Stream {
pub(crate) fn retry<T>(
&mut self,
mut op: impl FnMut(&mut Self) -> io::Result<T>,
) -> io::Result<T> {
let Retry::Until(timeout) = self.retry else {
return op(self);
};
let start = Instant::now();
let mut backoff = RETRY_BACKOFF_MIN;
let mut retries = 0usize;
loop {
let err = match op(self) {
Err(err) if err.kind() == io::ErrorKind::Interrupted => continue,
Err(err) if matches!(err.kind(), io::ErrorKind::TimedOut) => err,
Err(err) if matches!(err.kind(), io::ErrorKind::WouldBlock) => err,
Err(err) => return Err(err),
Ok(out) => {
if retries > 0 {
debug!("stream ready again after {retries} retries");
}
return Ok(out);
}
};
if start.elapsed() >= timeout {
debug!("give up on stream after {retries} retries: {err}");
let kind = io::ErrorKind::TimedOut;
let msg = format!("stream stopped responding after {timeout:?}");
return Err(io::Error::new(kind, msg));
}
let kind = err.kind();
let errno = err.raw_os_error();
debug!("retry stream after transient {kind:?} failure (errno {errno:?}): {err}");
thread::sleep(backoff);
backoff = (backoff * 2).min(RETRY_BACKOFF_MAX);
retries += 1;
}
}
}