use super::traits::{Limiter, validate_cost};
use crate::clock::{Clock, SystemClock};
use crate::error::LimiteronError;
use async_trait::async_trait;
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
pub struct TokenBucketLimiter {
capacity: u64,
tokens: AtomicU64,
refill_rate: u64,
last_refill: AtomicU64,
clock: Arc<dyn Clock>,
}
impl TokenBucketLimiter {
pub fn new(capacity: u64, refill_rate: u64) -> Self {
Self::with_clock(capacity, refill_rate, Arc::new(SystemClock))
}
pub fn with_clock(capacity: u64, refill_rate: u64, clock: Arc<dyn Clock>) -> Self {
let now_nanos = clock.unix_timestamp_nanos();
Self {
capacity,
tokens: AtomicU64::new(capacity),
refill_rate,
last_refill: AtomicU64::new(now_nanos),
clock,
}
}
pub fn capacity(&self) -> u64 {
self.capacity
}
pub fn refill_rate(&self) -> u64 {
self.refill_rate
}
pub fn tokens(&self) -> u64 {
self.tokens.load(Ordering::SeqCst)
}
fn refill_tokens(&self) {
let now = self.clock.unix_timestamp_nanos();
loop {
let last = self.last_refill.load(Ordering::SeqCst);
let elapsed_nanos = now.saturating_sub(last);
if elapsed_nanos < 1_000_000 {
break;
}
let elapsed_seconds = elapsed_nanos as f64 / 1_000_000_000.0;
let tokens_to_add = (elapsed_seconds * self.refill_rate as f64) as u64;
if tokens_to_add == 0 {
break;
}
if self
.last_refill
.compare_exchange(last, now, Ordering::SeqCst, Ordering::SeqCst)
.is_ok()
{
loop {
let current = self.tokens.load(Ordering::SeqCst);
let new_tokens = current.saturating_add(tokens_to_add).min(self.capacity);
if self
.tokens
.compare_exchange(current, new_tokens, Ordering::SeqCst, Ordering::SeqCst)
.is_ok()
{
break;
}
}
break;
}
}
}
}
#[async_trait]
impl Limiter for TokenBucketLimiter {
async fn allow(&self, cost: u64) -> Result<bool, LimiteronError> {
validate_cost(cost)?;
self.refill_tokens();
loop {
let current = self.tokens.load(Ordering::SeqCst);
if current < cost {
return Ok(false);
}
if self
.tokens
.compare_exchange(current, current - cost, Ordering::SeqCst, Ordering::SeqCst)
.is_ok()
{
return Ok(true);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::clock::MockClock;
use std::time::Duration;
#[tokio::test]
async fn test_token_bucket_basic() {
let limiter = TokenBucketLimiter::new(100, 10);
assert_eq!(limiter.tokens(), 100);
assert!(limiter.allow(10).await.unwrap());
assert_eq!(limiter.tokens(), 90);
}
#[tokio::test]
async fn test_token_bucket_exceed_capacity() {
let limiter = TokenBucketLimiter::new(10, 10);
assert!(limiter.allow(10).await.unwrap());
assert!(!limiter.allow(1).await.unwrap());
}
#[tokio::test]
async fn test_token_bucket_refill() {
let limiter = TokenBucketLimiter::new(10, 1000);
assert!(limiter.allow(10).await.unwrap());
assert_eq!(limiter.tokens(), 0);
tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
let _ = limiter.allow(1).await;
let tokens = limiter.tokens();
assert!(tokens > 0, "Expected tokens > 0, got {}", tokens);
}
#[tokio::test]
async fn test_token_bucket_zero_cost() {
let limiter = TokenBucketLimiter::new(100, 10);
let result = limiter.allow(0).await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_token_bucket_exceed_max_cost() {
let limiter = TokenBucketLimiter::new(100, 10);
let result = limiter.allow(1_000_001).await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_token_bucket_with_mock_clock() {
let mock_clock = Arc::new(MockClock::new());
let clock: Arc<dyn Clock> = mock_clock.clone();
let limiter = TokenBucketLimiter::with_clock(10, 100, clock);
assert!(limiter.allow(10).await.unwrap());
assert_eq!(limiter.tokens(), 0);
mock_clock.advance(Duration::from_secs(1));
let _ = limiter.allow(1).await;
let tokens = limiter.tokens();
assert!(
tokens > 0,
"Expected tokens > 0 after time advance, got {}",
tokens
);
}
#[test]
fn test_token_bucket_accessors() {
let limiter = TokenBucketLimiter::new(500, 25);
assert_eq!(limiter.capacity(), 500);
assert_eq!(limiter.refill_rate(), 25);
assert_eq!(limiter.tokens(), 500);
}
#[tokio::test]
async fn test_token_bucket_check_default_impl() {
use crate::limiters::Limiter;
let limiter = TokenBucketLimiter::new(100, 10);
assert!(limiter.check("any_key").await.is_ok());
assert_eq!(limiter.tokens(), 99);
}
#[tokio::test]
async fn test_token_bucket_refill_capped_at_capacity() {
let mock_clock = Arc::new(MockClock::new());
let clock: Arc<dyn Clock> = mock_clock.clone();
let limiter = TokenBucketLimiter::with_clock(10, 1000, clock);
assert!(limiter.allow(10).await.unwrap());
assert_eq!(limiter.tokens(), 0);
mock_clock.advance(Duration::from_secs(100));
let _ = limiter.allow(1).await;
let tokens = limiter.tokens();
assert!(
tokens <= 10,
"Expected tokens <= capacity (10), got {}",
tokens
);
}
}