use governor::clock::{Clock, DefaultClock};
use governor::middleware::NoOpMiddleware;
use governor::state::{InMemoryState, NotKeyed};
use governor::{Quota, RateLimiter};
use std::num::NonZeroU32;
use std::sync::atomic::{AtomicBool, Ordering};
use std::thread;
use std::time::Duration;
const MAX_NAP: Duration = Duration::from_millis(100);
type DirectLimiter<C> = RateLimiter<
NotKeyed,
InMemoryState,
C,
NoOpMiddleware<<C as Clock>::Instant>,
>;
pub struct Limiter<C: Clock = DefaultClock> {
inner: DirectLimiter<C>,
clock: C,
}
impl Limiter<DefaultClock> {
#[must_use]
pub fn new(rate: NonZeroU32) -> Self {
Self::with_clock(rate, DefaultClock::default())
}
}
impl<C: Clock + Clone> Limiter<C> {
pub fn with_clock(rate: NonZeroU32, clock: C) -> Self {
let inner = RateLimiter::direct_with_clock(
Quota::per_second(rate),
clock.clone(),
);
Self { inner, clock }
}
pub fn try_acquire(&self) -> Result<(), Duration> {
self.inner.check().map_err(|nu| nu.wait_time_from(self.clock.now()))
}
#[must_use]
pub fn acquire(&self, quit: &AtomicBool) -> bool {
loop {
if quit.load(Ordering::Relaxed) {
return false;
}
match self.try_acquire() {
Ok(()) => return true,
Err(wait) => thread::sleep(wait.min(MAX_NAP)),
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use governor::clock::FakeRelativeClock;
fn lim(rate: u32, clock: FakeRelativeClock) -> Limiter<FakeRelativeClock> {
Limiter::with_clock(NonZeroU32::new(rate).unwrap(), clock)
}
#[test]
fn try_acquire_allows_burst_then_denies() {
let clock = FakeRelativeClock::default();
let l = lim(2, clock);
assert!(l.try_acquire().is_ok());
assert!(l.try_acquire().is_ok());
assert!(l.try_acquire().is_err());
}
#[test]
fn try_acquire_replenishes_after_time() {
let clock = FakeRelativeClock::default();
let l = lim(2, clock.clone());
assert!(l.try_acquire().is_ok());
assert!(l.try_acquire().is_ok());
assert!(l.try_acquire().is_err());
clock.advance(Duration::from_millis(500));
assert!(l.try_acquire().is_ok());
assert!(l.try_acquire().is_err());
}
#[test]
fn acquire_returns_false_when_quit_already_set() {
let clock = FakeRelativeClock::default();
let l = lim(1, clock);
let quit = AtomicBool::new(true);
assert!(!l.acquire(&quit));
}
#[test]
fn acquire_succeeds_immediately_when_token_available() {
let clock = FakeRelativeClock::default();
let l = lim(5, clock);
let quit = AtomicBool::new(false);
assert!(l.acquire(&quit));
}
}