spry 0.0.4

Resilient, self-healing async process hierarchies in the style of Erlang/OTP
Documentation
//! Library of common restart breaker patterns.

use std::time::Duration;
use tokio::time::Instant;

/// Never restart the subsystem, trivial.
pub struct NeverRestart;

impl<K> Breaker<K> for NeverRestart {
  // todo: make this async to allow for, e.g., exponential backoff strategies
  fn may_restart(&mut self, _key: &K, _at: Instant) -> bool {
    false
  }
}

/// Always restart the subsystem, trivial.
pub struct AlwaysRestart;

impl<K> Breaker<K> for AlwaysRestart {
  fn may_restart(&mut self, _key: &K, _at: Instant) -> bool {
    true
  }
}

/// Restart the subsystem if it has not been restarted too much recently.
pub struct RateLimited {
  max: usize,
  period: Duration,
  seen: Vec<Instant>,
}

/// The default rate limit is 3 restarts within 10 seconds.
impl Default for RateLimited {
  fn default() -> Self {
    Self::new(3, Duration::from_secs(10))
  }
}

impl RateLimited {
  /// A breaker which allows no more than `max` restarts within the last `period` of time.
  pub fn new(max: usize, period: Duration) -> Self {
    Self { max, period, seen: Vec::new() }
  }
}

impl<K> Breaker<K> for RateLimited {
  fn may_restart(&mut self, _key: &K, at: Instant) -> bool {
    self.seen.retain(|&t| {
      let age = at.duration_since(t);
      age < self.period
    });

    // inclusive of the new event, do we now exceed the rate limit?
    self.seen.push(at);
    self.seen.len() <= self.max
  }
}

/// An algorithm for deciding how often a system may be restarted before escalating the failure.
pub trait Breaker<K>: Send + 'static {
  /// Notes a new restart attempt and returns whether the system may be restarted.
  fn may_restart(&mut self, key: &K, at: Instant) -> bool;
}