use super::traits::{Limiter, validate_cost};
use crate::error::LimiteronError;
use async_trait::async_trait;
use parking_lot::Mutex;
use std::collections::VecDeque;
use std::sync::Arc;
use std::time::{Duration, Instant};
#[deprecated(
since = "0.1.1",
note = "使用 `ShardedSlidingWindowLimiter` 替代。此实现使用 O(n) 复杂度,高并发场景下性能较差。"
)]
pub struct SlidingWindowLimiter {
window_size: Duration,
max_requests: u64,
requests: Arc<Mutex<VecDeque<Instant>>>,
}
impl SlidingWindowLimiter {
#[deprecated(since = "0.1.1", note = "使用 `ShardedSlidingWindowLimiter` 替代。")]
pub fn new(window_size: Duration, max_requests: u64) -> Self {
Self {
window_size,
max_requests,
requests: Arc::new(Mutex::new(VecDeque::with_capacity(
max_requests as usize + 10,
))),
}
}
#[deprecated(since = "0.1.1", note = "使用 `ShardedSlidingWindowLimiter` 替代。")]
pub fn window_size(&self) -> Duration {
self.window_size
}
#[deprecated(since = "0.1.1", note = "使用 `ShardedSlidingWindowLimiter` 替代。")]
pub fn max_requests(&self) -> u64 {
self.max_requests
}
}
#[async_trait]
impl Limiter for SlidingWindowLimiter {
async fn allow(&self, cost: u64) -> Result<bool, LimiteronError> {
validate_cost(cost)?;
let now = Instant::now();
let mut requests = self.requests.lock();
let cutoff = now - self.window_size;
while let Some(&front) = requests.front() {
if front <= cutoff {
requests.pop_front();
} else {
break;
}
}
let current_count = requests.len() as u64;
if current_count + cost > self.max_requests {
return Ok(false);
}
for _ in 0..cost {
requests.push_back(now);
}
Ok(true)
}
}
#[cfg(test)]
#[allow(deprecated)]
mod tests {
use super::*;
#[tokio::test]
async fn test_sliding_window_basic() {
let limiter = SlidingWindowLimiter::new(Duration::from_secs(60), 10);
for _ in 0..5 {
assert!(limiter.allow(1).await.unwrap());
}
}
#[tokio::test]
async fn test_sliding_window_exceed() {
let limiter = SlidingWindowLimiter::new(Duration::from_secs(60), 3);
assert!(limiter.allow(1).await.unwrap());
assert!(limiter.allow(1).await.unwrap());
assert!(limiter.allow(1).await.unwrap());
assert!(!limiter.allow(1).await.unwrap());
}
#[tokio::test]
async fn test_sliding_window_accessors() {
let limiter = SlidingWindowLimiter::new(Duration::from_secs(120), 50);
assert_eq!(limiter.window_size(), Duration::from_secs(120));
assert_eq!(limiter.max_requests(), 50);
}
#[tokio::test]
async fn test_sliding_window_zero_cost_errors() {
let limiter = SlidingWindowLimiter::new(Duration::from_secs(60), 10);
let result = limiter.allow(0).await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_sliding_window_cost_exceeds_max() {
let limiter = SlidingWindowLimiter::new(Duration::from_secs(60), 10);
let result = limiter.allow(crate::constants::MAX_COST + 1).await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_sliding_window_cost_blocks_when_exceeds() {
let limiter = SlidingWindowLimiter::new(Duration::from_secs(60), 5);
assert!(limiter.allow(3).await.unwrap());
assert!(!limiter.allow(3).await.unwrap());
}
#[tokio::test]
async fn test_sliding_window_check_default_impl() {
use crate::limiters::Limiter;
let limiter = SlidingWindowLimiter::new(Duration::from_secs(60), 10);
assert!(limiter.check("any_key").await.is_ok());
}
}