use parking_lot::Mutex;
use std::time::Instant;
pub struct RateLimiter {
inner: Mutex<RateLimiterInner>,
}
struct RateLimiterInner {
tokens: f64,
max_tokens: f64,
refill_rate: f64,
last_refill: Instant,
dropped_count: u64,
}
impl RateLimiter {
pub fn new(rate: u64) -> Self {
let rate_f = rate as f64;
Self {
inner: Mutex::new(RateLimiterInner {
tokens: rate_f,
max_tokens: rate_f,
refill_rate: rate_f,
last_refill: Instant::now(),
dropped_count: 0,
}),
}
}
pub fn try_acquire(&self) -> bool {
let mut inner = self.inner.lock();
inner.refill();
if inner.tokens >= 1.0 {
inner.tokens -= 1.0;
true
} else {
inner.dropped_count += 1;
false
}
}
pub fn dropped_count(&self) -> u64 {
self.inner.lock().dropped_count
}
}
impl RateLimiterInner {
fn refill(&mut self) {
let now = Instant::now();
let elapsed = now.duration_since(self.last_refill).as_secs_f64();
if elapsed > 0.0 {
self.tokens = (self.tokens + elapsed * self.refill_rate).min(self.max_tokens);
self.last_refill = now;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rate_limiter_allows_within_limit() {
let limiter = RateLimiter::new(1000);
for _ in 0..100 {
assert!(limiter.try_acquire());
}
}
#[test]
fn test_rate_limiter_rejects_when_exhausted() {
let limiter = RateLimiter::new(2);
assert!(limiter.try_acquire());
assert!(limiter.try_acquire());
assert!(!limiter.try_acquire());
}
#[test]
fn test_rate_limiter_dropped_count_increments() {
let limiter = RateLimiter::new(1);
assert!(limiter.try_acquire());
assert!(!limiter.try_acquire());
assert!(!limiter.try_acquire());
assert_eq!(limiter.dropped_count(), 2);
}
#[test]
fn test_rate_limiter_refills_over_time() {
let limiter = RateLimiter::new(1000);
for _ in 0..1000 {
limiter.try_acquire();
}
std::thread::sleep(std::time::Duration::from_millis(100));
assert!(limiter.try_acquire());
}
#[test]
fn test_rate_limiter_zero_rate() {
let limiter = RateLimiter::new(0);
assert!(!limiter.try_acquire());
assert_eq!(limiter.dropped_count(), 1);
}
}