use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use std::time::{Duration, Instant};
use tokio::sync::Mutex;
#[derive(Debug, Clone, PartialEq)]
pub struct RateLimitConfig {
pub max_commands: u64,
pub window: Duration,
}
impl RateLimitConfig {
pub fn new(max_commands: u64, window: Duration) -> Self {
Self {
max_commands,
window,
}
}
pub fn per_second(max_commands: u64) -> Self {
Self {
max_commands,
window: Duration::from_secs(1),
}
}
pub fn per_minute(max_commands: u64) -> Self {
Self {
max_commands,
window: Duration::from_secs(60),
}
}
pub fn per_hour(max_commands: u64) -> Self {
Self {
max_commands,
window: Duration::from_secs(3600),
}
}
pub fn unlimited() -> Self {
Self {
max_commands: u64::MAX,
window: Duration::from_secs(1),
}
}
}
impl Default for RateLimitConfig {
fn default() -> Self {
Self::unlimited()
}
}
pub struct TokenBucketLimiter {
capacity: u64,
tokens: AtomicU64,
refill_rate: f64,
last_refill: Mutex<Instant>,
}
impl TokenBucketLimiter {
pub fn new(config: &RateLimitConfig) -> Self {
let refill_rate = config.max_commands as f64 / config.window.as_secs_f64();
Self {
capacity: config.max_commands,
tokens: AtomicU64::new(config.max_commands),
refill_rate,
last_refill: Mutex::new(Instant::now()),
}
}
pub async fn try_acquire(&self) -> bool {
self.refill().await;
loop {
let current = self.tokens.load(Ordering::Relaxed);
if current == 0 {
return false;
}
if self
.tokens
.compare_exchange(current, current - 1, Ordering::SeqCst, Ordering::Relaxed)
.is_ok()
{
return true;
}
}
}
pub async fn acquire(&self) -> Duration {
let start = Instant::now();
loop {
if self.try_acquire().await {
return start.elapsed();
}
let wait_time = Duration::from_secs_f64(1.0 / self.refill_rate);
tokio::time::sleep(wait_time.min(Duration::from_millis(10))).await;
}
}
async fn refill(&self) {
let mut last_refill = self.last_refill.lock().await;
let now = Instant::now();
let elapsed = now.duration_since(*last_refill);
if elapsed.as_secs_f64() > 0.0 {
let new_tokens = (elapsed.as_secs_f64() * self.refill_rate) as u64;
if new_tokens > 0 {
let current = self.tokens.load(Ordering::Relaxed);
let new_total = (current + new_tokens).min(self.capacity);
self.tokens.store(new_total, Ordering::Relaxed);
*last_refill = now;
}
}
}
pub fn available_tokens(&self) -> u64 {
self.tokens.load(Ordering::Relaxed)
}
pub fn capacity(&self) -> u64 {
self.capacity
}
pub fn refill_rate(&self) -> f64 {
self.refill_rate
}
}
pub struct SlidingWindowLimiter {
max_commands: u64,
window: Duration,
timestamps: Mutex<Vec<Instant>>,
}
impl SlidingWindowLimiter {
pub fn new(config: &RateLimitConfig) -> Self {
Self {
max_commands: config.max_commands,
window: config.window,
timestamps: Mutex::new(Vec::new()),
}
}
pub async fn try_acquire(&self) -> bool {
let mut timestamps = self.timestamps.lock().await;
let now = Instant::now();
let window_start = now - self.window;
timestamps.retain(|&ts| ts > window_start);
if (timestamps.len() as u64) < self.max_commands {
timestamps.push(now);
true
} else {
false
}
}
pub async fn acquire(&self) -> Duration {
let start = Instant::now();
loop {
if self.try_acquire().await {
return start.elapsed();
}
tokio::time::sleep(Duration::from_millis(10)).await;
}
}
pub async fn current_count(&self) -> u64 {
let mut timestamps = self.timestamps.lock().await;
let now = Instant::now();
let window_start = now - self.window;
timestamps.retain(|&ts| ts > window_start);
timestamps.len() as u64
}
pub async fn remaining(&self) -> u64 {
let count = self.current_count().await;
self.max_commands.saturating_sub(count)
}
}
#[derive(Clone, Default)]
pub enum RateLimiter {
TokenBucket(Arc<TokenBucketLimiter>),
SlidingWindow(Arc<SlidingWindowLimiter>),
#[default]
Unlimited,
}
impl RateLimiter {
pub fn token_bucket(config: &RateLimitConfig) -> Self {
Self::TokenBucket(Arc::new(TokenBucketLimiter::new(config)))
}
pub fn sliding_window(config: &RateLimitConfig) -> Self {
Self::SlidingWindow(Arc::new(SlidingWindowLimiter::new(config)))
}
pub fn unlimited() -> Self {
Self::Unlimited
}
pub async fn try_acquire(&self) -> bool {
match self {
Self::TokenBucket(limiter) => limiter.try_acquire().await,
Self::SlidingWindow(limiter) => limiter.try_acquire().await,
Self::Unlimited => true,
}
}
pub async fn acquire(&self) -> Duration {
match self {
Self::TokenBucket(limiter) => limiter.acquire().await,
Self::SlidingWindow(limiter) => limiter.acquire().await,
Self::Unlimited => Duration::ZERO,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rate_limit_config_per_second() {
let config = RateLimitConfig::per_second(100);
assert_eq!(config.max_commands, 100);
assert_eq!(config.window, Duration::from_secs(1));
}
#[test]
fn test_rate_limit_config_per_minute() {
let config = RateLimitConfig::per_minute(1000);
assert_eq!(config.max_commands, 1000);
assert_eq!(config.window, Duration::from_secs(60));
}
#[test]
fn test_rate_limit_config_unlimited() {
let config = RateLimitConfig::unlimited();
assert_eq!(config.max_commands, u64::MAX);
}
#[tokio::test]
async fn test_token_bucket_limiter_basic() {
let config = RateLimitConfig::per_second(10);
let limiter = TokenBucketLimiter::new(&config);
for _ in 0..10 {
assert!(limiter.try_acquire().await);
}
assert!(!limiter.try_acquire().await);
}
#[tokio::test]
async fn test_token_bucket_limiter_refill() {
let config = RateLimitConfig::new(10, Duration::from_millis(100));
let limiter = TokenBucketLimiter::new(&config);
for _ in 0..10 {
assert!(limiter.try_acquire().await);
}
assert!(!limiter.try_acquire().await);
tokio::time::sleep(Duration::from_millis(150)).await;
assert!(limiter.try_acquire().await);
}
#[tokio::test]
async fn test_sliding_window_limiter_basic() {
let config = RateLimitConfig::per_second(5);
let limiter = SlidingWindowLimiter::new(&config);
for _ in 0..5 {
assert!(limiter.try_acquire().await);
}
assert!(!limiter.try_acquire().await);
assert_eq!(limiter.current_count().await, 5);
assert_eq!(limiter.remaining().await, 0);
}
#[tokio::test]
async fn test_sliding_window_limiter_expiry() {
let config = RateLimitConfig::new(5, Duration::from_millis(100));
let limiter = SlidingWindowLimiter::new(&config);
for _ in 0..5 {
assert!(limiter.try_acquire().await);
}
assert!(!limiter.try_acquire().await);
tokio::time::sleep(Duration::from_millis(150)).await;
assert!(limiter.try_acquire().await);
}
#[tokio::test]
async fn test_rate_limiter_enum_token_bucket() {
let config = RateLimitConfig::per_second(5);
let limiter = RateLimiter::token_bucket(&config);
for _ in 0..5 {
assert!(limiter.try_acquire().await);
}
assert!(!limiter.try_acquire().await);
}
#[tokio::test]
async fn test_rate_limiter_enum_sliding_window() {
let config = RateLimitConfig::per_second(5);
let limiter = RateLimiter::sliding_window(&config);
for _ in 0..5 {
assert!(limiter.try_acquire().await);
}
assert!(!limiter.try_acquire().await);
}
#[tokio::test]
async fn test_rate_limiter_enum_unlimited() {
let limiter = RateLimiter::unlimited();
for _ in 0..1000 {
assert!(limiter.try_acquire().await);
}
}
#[tokio::test]
async fn test_rate_limiter_acquire_waits() {
let config = RateLimitConfig::new(1, Duration::from_millis(50));
let limiter = RateLimiter::token_bucket(&config);
let wait1 = limiter.acquire().await;
assert!(wait1 < Duration::from_millis(10));
let wait2 = limiter.acquire().await;
assert!(wait2 >= Duration::from_millis(10));
}
}