jimmycuadra / retry

A Rust library to retry some code until its return value satisfies a condition.
https://docs.rs/retry
MIT License
116 stars 26 forks source link

Support Result-based retries -- return the last result's error instead of a custom error type #17

Open mmstick opened 5 years ago

mmstick commented 5 years ago

Looking for a solution like this:

#[derive(SmartDefault)]
struct Retry {
    #[default = "3"]
    attempts: u64,
    #[default = "1000"]
    interval: u64,
}

impl Retry {
    pub fn attempts(mut self, attempts: u64) -> Self {
        self.attempts = attempts;
        self
    }

    pub fn interval(mut self, interval: u64) -> Self {
        self.interval = interval;
        self
    }

    pub fn retry_until_ok<F, T, E>(&self, mut func: F) -> Result<T, E>
        where F: FnMut() -> Result<T, E>
    {
        let duration = ::std::time::Duration::from_millis(self.interval);
        let mut attempt = 0;
        loop {
            match func() {
                Ok(value) => return Ok(value),
                Err(why)  => {
                    if attempt == self.attempts {
                        return Err(why);
                    } else {
                        attempt += 1;
                        ::std::thread::sleep(duration);
                    }
                }
            }
        }
    }
}
pub fn fsck<P: AsRef<Path>>(part: P, cmd: Option<(&str, &str)>) -> io::Result<()> {
    let (cmd, arg) = cmd.unwrap_or(("fsck", "-fy"));

    // Attempt this once every second, with up to 3 attempts before giving up.
    Retry::default()
        .attempts(3)
        .interval(1000)
        .retry_until_ok(move || exec(cmd, None, None, &[arg.into(), part.as_ref().into()]))
}
jimmycuadra commented 5 years ago

Sounds like a good idea to me!

wellcaffeinated commented 3 years ago

This is a great idea.

I just implemented this for myself:

use ::std::time::Duration;

pub enum RetryResult<T, E> {
  Ok(T),
  Retry(E),
  Err(E),
}

impl<T, E> From<Result<T, E>> for RetryResult<T, E> {
  fn from(r : Result<T, E>) -> Self {
    match r {
      Ok(v) => RetryResult::Ok(v),
      Err(e) => RetryResult::Retry(e),
    }
  }
}

pub struct Retry {
  attempts: Option<u64>,
  interval: Duration,
}

impl Default for Retry {
  fn default() -> Self {
    Retry {
      attempts: None,
      interval: Duration::from_millis(1000),
    }
  }
}

impl Retry {
  pub fn attempts(mut self, attempts: u64) -> Self {
    self.attempts = Some(attempts);
    self
  }

  pub fn interval(mut self, interval: u64) -> Self {
    self.interval = Duration::from_millis(interval);
    self
  }

  pub fn retry_until_ok<F, T, E, R>(&self, mut func: F) -> Result<T, E>
    where F: FnMut() -> R,
      R : Into<RetryResult<T, E>>
  {
    let mut attempt = 0;
    loop {
      match func().into() {
        RetryResult::Ok(value) => return Ok(value),
        RetryResult::Err(why) => return Err(why),
        RetryResult::Retry(why) => {
          if Some(attempt) == self.attempts {
            return Err(why);
          } else {
            attempt += 1;
            ::std::thread::sleep(self.interval);
          }
        }
      }
    }
  }
}