#![allow(unused)]
use crate::errors::Result;
use crate::errors::error::RateLimitError;
use crate::utils::lock::{AdvancedDistributedLock, DistributedLockManager};
use dashmap::DashMap;
use log::error;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
use tokio::sync::RwLock;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct RateLimitConfig {
pub max_requests_per_second: f32,
pub window_size_millis: u64,
#[serde(default)]
pub base_max_requests_per_second: Option<f32>,
}
impl Default for RateLimitConfig {
fn default() -> Self {
Self {
max_requests_per_second: 2.0,
window_size_millis: 1000,
base_max_requests_per_second: None,
}
}
}
impl RateLimitConfig {
pub fn new(max_requests_per_second: f32) -> Self {
Self {
max_requests_per_second,
window_size_millis: 1000,
base_max_requests_per_second: Some(max_requests_per_second),
}
}
pub fn with_window_size(mut self, window_size: Duration) -> Self {
self.window_size_millis = window_size.as_millis() as u64;
self
}
}
#[derive(Clone)]
pub struct DistributedSlidingWindowRateLimiter {
lock_manager: Arc<DistributedLockManager>,
key_prefix: String,
sub_prefix: String,
default_config_key: String,
config_key_prefix: String,
default_config: RateLimitConfig,
local_last_request: Arc<DashMap<String, u64>>,
local_configs: Arc<DashMap<String, RateLimitConfig>>,
local_default_config: Arc<RwLock<Option<RateLimitConfig>>>,
local_suspended: Arc<DashMap<String, u64>>,
local_wait_until: Arc<DashMap<String, u64>>,
suspend_cache: Arc<DashMap<String, (Instant, Option<u64>)>>,
coordination: Option<Arc<dyn crate::utils::coordination::CoordinationBackend>>,
}
impl std::fmt::Debug for DistributedSlidingWindowRateLimiter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DistributedSlidingWindowRateLimiter")
.field("has_coordination", &self.coordination.is_some())
.field("key_prefix", &self.key_prefix)
.field("default_config", &self.default_config)
.finish()
}
}
impl DistributedSlidingWindowRateLimiter {
pub fn new(
locker: Arc<DistributedLockManager>,
namespace: &str,
default_config: RateLimitConfig,
) -> Self {
Self::new_with_coordination(locker, None, namespace, default_config)
}
pub fn new_with_coordination(
locker: Arc<DistributedLockManager>,
coordination: Option<Arc<dyn crate::utils::coordination::CoordinationBackend>>,
namespace: &str,
default_config: RateLimitConfig,
) -> Self {
let sub_prefix = "rate_limiter".to_string();
let key_prefix = format!("{namespace}:{sub_prefix}");
let default_config_key = format!("{key_prefix}:default_config");
let config_key_prefix = format!("{key_prefix}:config");
Self {
lock_manager: locker,
key_prefix,
sub_prefix,
default_config_key,
config_key_prefix,
default_config,
local_last_request: Arc::new(DashMap::new()),
local_configs: Arc::new(DashMap::new()),
local_default_config: Arc::new(RwLock::new(None)),
local_suspended: Arc::new(DashMap::new()),
local_wait_until: Arc::new(DashMap::new()),
suspend_cache: Arc::new(DashMap::new()),
coordination,
}
}
fn share_rps(&self, rps: f32) -> f32 {
let n = self
.coordination
.as_ref()
.map(|c| c.cluster_size().max(1))
.unwrap_or(1) as f32;
(rps / n).max(f32::MIN_POSITIVE)
}
async fn effective_config(&self, identifier: &str) -> RateLimitConfig {
let mut cfg = match self.get_key_config(identifier).await {
Ok(c) => c,
Err(e) => {
error!("{e:?}");
self.default_config.clone()
}
};
cfg.max_requests_per_second = self.share_rps(cfg.max_requests_per_second);
cfg
}
fn get_last_request_key(&self, identifier: &str) -> String {
format!("{}:last_request:{}", self.key_prefix, identifier)
}
fn get_lock_key(&self, identifier: &str) -> String {
format!("{}:lock:{}", self.sub_prefix, identifier)
}
fn get_config_key(&self, identifier: &str) -> String {
format!("{}:{}", self.config_key_prefix, identifier)
}
fn get_suspended_key(&self, identifier: &str) -> String {
format!("{}:suspended:{}", self.key_prefix, identifier)
}
pub async fn suspend(&self, identifier: &str, duration: Duration) -> Result<()> {
let current_time = self.get_current_timestamp().await?;
let suspend_until = current_time + duration.as_millis() as u64;
self.local_suspended
.insert(identifier.to_string(), suspend_until);
self.suspend_cache.remove(identifier);
Ok(())
}
async fn check_suspended(&self, identifier: &str) -> Result<Option<u64>> {
let cached = self.suspend_cache.get(identifier).map(|entry| {
let (checked_at, suspend_until_opt) = entry.value();
(*checked_at, *suspend_until_opt)
});
if let Some((checked_at, suspend_until_opt)) = cached {
if checked_at.elapsed() < Duration::from_millis(500) {
let current_time = self.get_current_timestamp().await?;
if let Some(suspend_until) = suspend_until_opt {
if suspend_until > current_time {
return Ok(Some(suspend_until - current_time));
}
} else {
return Ok(None);
}
}
}
let current_time = self.get_current_timestamp().await?;
let mut found_suspend_until: Option<u64> = None;
if let Some(suspend_until) = self.local_suspended.get(identifier) {
if *suspend_until > current_time {
found_suspend_until = Some(*suspend_until);
} else {
drop(suspend_until); self.local_suspended.remove(identifier);
}
}
self.suspend_cache.insert(
identifier.to_string(),
(Instant::now(), found_suspend_until),
);
if let Some(suspend_until) = found_suspend_until {
Ok(Some(suspend_until - current_time))
} else {
Ok(None)
}
}
async fn get_current_timestamp(&self) -> Result<u64> {
Ok(SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis() as u64)
}
pub async fn set_key_config(&self, key: &str, config: RateLimitConfig) -> Result<()> {
if let Some(existing) = self.local_configs.get(key) {
if *existing == config {
return Ok(());
}
}
self.local_configs.insert(key.to_string(), config);
Ok(())
}
pub async fn set_limit(&self, id: &str, limit: f32) -> Result<()> {
let config = RateLimitConfig::new(limit);
self.set_key_config(id, config).await
}
pub async fn set_all_limit(&self, limit: f32) -> Result<()> {
let mut default_config = self.default_config.clone();
default_config.max_requests_per_second = limit;
*self.local_default_config.write().await = Some(default_config);
for mut entry in self.local_configs.iter_mut() {
entry.value_mut().max_requests_per_second = limit;
}
Ok(())
}
pub async fn set_key_configs(&self, configs: HashMap<String, RateLimitConfig>) -> Result<()> {
for (key, config) in configs {
self.local_configs.insert(key, config);
}
Ok(())
}
pub async fn remove_key_config(&self, key: &str) -> Result<()> {
self.local_configs.remove(key);
Ok(())
}
pub async fn get_key_config(&self, key: &str) -> Result<RateLimitConfig> {
if let Some(config) = self.local_configs.get(key) {
return Ok(config.clone());
}
if let Some(config) = self.local_default_config.read().await.as_ref() {
return Ok(config.clone());
}
Ok(self.default_config.clone())
}
pub async fn get_all_key_configs(&self) -> Result<HashMap<String, RateLimitConfig>> {
let mut configs = HashMap::new();
for entry in self.local_configs.iter() {
configs.insert(entry.key().clone(), entry.value().clone());
}
Ok(configs)
}
pub async fn record(&self, identifier: &str) -> Result<()> {
let lock_key = self.get_lock_key(identifier);
let last_request_key = self.get_last_request_key(identifier);
if let Ok(acquired) = self
.lock_manager
.acquire_lock(&lock_key, 30, Duration::from_secs(5))
.await
{
if !acquired {
return Err(RateLimitError::Backend("Failed to acquire lock".into()).into());
}
let current_time = self.get_current_timestamp().await?;
self.local_last_request
.insert(last_request_key, current_time);
let _ = self.lock_manager.release_lock(&lock_key).await;
} else {
return Err(RateLimitError::Backend("Failed to acquire lock".into()).into());
}
Ok(())
}
pub async fn acquire(&self, identifier: &str, _permits: f64) -> Result<()> {
let wait_ms = self.check_and_update(identifier).await?;
if wait_ms > 0 {
let wait_secs = wait_ms.div_ceil(1000);
return Err(RateLimitError::WaitTime(wait_secs).into());
}
Ok(())
}
pub async fn verify(&self, identifier: &str) -> Result<Option<u64>> {
if let Some(wait) = self.check_suspended(identifier).await? {
return Ok(Some(wait));
}
let config = self.effective_config(identifier).await;
let last_request_key = self.get_last_request_key(identifier);
let min_interval_millis =
(config.window_size_millis as f64 / config.max_requests_per_second as f64) as u64;
let current_time = self.get_current_timestamp().await?;
if let Some(last_time) = self.local_last_request.get(&last_request_key).map(|v| *v) {
let elapsed = current_time.saturating_sub(last_time);
if elapsed < min_interval_millis {
return Ok(Some(min_interval_millis - elapsed));
}
}
Ok(None)
}
pub async fn check_and_update(&self, identifier: &str) -> Result<u64> {
if let Some(wait) = self.check_suspended(identifier).await? {
return Ok(wait);
}
let current_time = self.get_current_timestamp().await?;
if let Some(entry) = self.local_wait_until.get(identifier) {
let wait_until = *entry;
if wait_until > current_time {
return Ok(wait_until - current_time);
}
drop(entry);
self.local_wait_until.remove(identifier);
}
let config = self.effective_config(identifier).await;
let last_request_key = self.get_last_request_key(identifier);
let min_interval_millis =
(config.window_size_millis as f64 / config.max_requests_per_second as f64) as u64;
let entry = self.local_last_request.entry(last_request_key);
match entry {
dashmap::mapref::entry::Entry::Occupied(mut occ) => {
let last_time = *occ.get();
let elapsed = current_time.saturating_sub(last_time);
if elapsed < min_interval_millis {
return Ok(min_interval_millis - elapsed);
}
occ.insert(current_time);
Ok(0)
}
dashmap::mapref::entry::Entry::Vacant(vac) => {
vac.insert(current_time);
Ok(0)
}
}
}
async fn reserve_permit(&self, identifier: &str) -> Result<u64> {
let current_time = self.get_current_timestamp().await?;
let config = self.effective_config(identifier).await;
let last_request_key = self.get_last_request_key(identifier);
let min_interval_millis =
(config.window_size_millis as f64 / config.max_requests_per_second as f64) as u64;
let entry = self.local_last_request.entry(last_request_key);
match entry {
dashmap::mapref::entry::Entry::Occupied(mut occ) => {
let last_time = *occ.get();
let next_available = (last_time + min_interval_millis).max(current_time);
let wait = next_available.saturating_sub(current_time);
occ.insert(next_available);
Ok(wait)
}
dashmap::mapref::entry::Entry::Vacant(vac) => {
vac.insert(current_time);
Ok(0)
}
}
}
pub async fn wait_for_permit(&self, identifier: &str) -> Result<()> {
while let Some(remaining_ms) = self.check_suspended(identifier).await? {
tokio::time::sleep(Duration::from_millis(remaining_ms)).await;
}
let wait_ms = self.reserve_permit(identifier).await?;
if wait_ms > 0 {
tokio::time::sleep(Duration::from_millis(wait_ms)).await;
}
Ok(())
}
pub async fn get_next_available_time(&self, identifier: &str) -> Result<u64> {
self.verify(identifier).await.map(|res| res.unwrap_or(0))
}
pub async fn decrease_limit(&self, identifier: &str, factor: f32) -> Result<f32> {
let mut config = self.get_key_config(identifier).await?;
if config.base_max_requests_per_second.is_none() {
config.base_max_requests_per_second = Some(config.max_requests_per_second);
}
let new_limit = config.max_requests_per_second * factor;
config.max_requests_per_second = new_limit.max(0.1);
self.set_key_config(identifier, config.clone()).await?;
Ok(config.max_requests_per_second)
}
pub async fn try_restore_limit(&self, identifier: &str, step_factor: f32) -> Result<f32> {
let mut config = self.get_key_config(identifier).await?;
if let Some(base) = config.base_max_requests_per_second
&& config.max_requests_per_second < base
{
let new_limit = config.max_requests_per_second * step_factor;
config.max_requests_per_second = new_limit.min(base);
self.set_key_config(identifier, config.clone()).await?;
}
Ok(config.max_requests_per_second)
}
pub async fn cleanup(&self) -> Result<u64> {
let mut total_cleaned = 0u64;
let current_time = self.get_current_timestamp().await?;
let mut keys_to_remove = Vec::new();
for entry in self.local_last_request.iter() {
let last_time = *entry.value();
let min_interval_millis = (self.default_config.window_size_millis as f64
/ self.default_config.max_requests_per_second as f64)
as u64;
let max_valid_age = min_interval_millis * 2;
if current_time.saturating_sub(last_time) > max_valid_age {
keys_to_remove.push(entry.key().clone());
}
}
for key in keys_to_remove {
if self.local_last_request.remove(&key).is_some() {
total_cleaned += 1;
}
}
Ok(total_cleaned)
}
pub async fn get_identifier_count(&self) -> Result<usize> {
Ok(self.local_last_request.len())
}
pub async fn reset(&self, identifier: &str) -> Result<()> {
let key = self.get_last_request_key(identifier);
self.local_last_request.remove(&key);
Ok(())
}
pub async fn reset_all(&self) -> Result<()> {
self.local_last_request.clear();
Ok(())
}
pub fn get_key_prefix(&self) -> &str {
&self.key_prefix
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
struct SizeBackend(usize);
#[async_trait::async_trait]
impl crate::utils::coordination::CoordinationBackend for SizeBackend {
async fn publish(&self, _: &str, _: &[u8]) -> std::result::Result<(), String> {
Ok(())
}
async fn subscribe(
&self,
_: &str,
) -> std::result::Result<tokio::sync::mpsc::Receiver<Vec<u8>>, String> {
let (_tx, rx) = tokio::sync::mpsc::channel(1);
Ok(rx)
}
async fn set(&self, _: &str, _: &[u8]) -> std::result::Result<(), String> {
Ok(())
}
async fn get(&self, _: &str) -> std::result::Result<Option<Vec<u8>>, String> {
Ok(None)
}
async fn cas(
&self,
_: &str,
_: Option<&[u8]>,
_: &[u8],
) -> std::result::Result<bool, String> {
Ok(true)
}
async fn acquire_lock(
&self,
_: &str,
_: &[u8],
_: u64,
) -> std::result::Result<bool, String> {
Ok(true)
}
async fn renew_lock(&self, _: &str, _: &[u8], _: u64) -> std::result::Result<bool, String> {
Ok(true)
}
fn cluster_size(&self) -> usize {
self.0
}
}
#[test]
fn shares_global_limit_by_cluster_size() {
let locker = Arc::new(DistributedLockManager::new("t"));
let backend: Arc<dyn crate::utils::coordination::CoordinationBackend> =
Arc::new(SizeBackend(4));
let limiter = DistributedSlidingWindowRateLimiter::new_with_coordination(
locker,
Some(backend),
"ns",
RateLimitConfig::new(8.0),
);
assert_eq!(limiter.share_rps(8.0), 2.0);
let plain = DistributedSlidingWindowRateLimiter::new(
Arc::new(DistributedLockManager::new("t")),
"ns",
RateLimitConfig::new(8.0),
);
assert_eq!(plain.share_rps(8.0), 8.0);
}
#[tokio::test]
async fn test_rate_limiter_local() {
let lock_manager = Arc::new(DistributedLockManager::new("test"));
let config = RateLimitConfig::new(1.0); let limiter = DistributedSlidingWindowRateLimiter::new(lock_manager, "test", config);
assert!(limiter.verify("user1").await.unwrap().is_none());
limiter.record("user1").await.unwrap();
let res = limiter.verify("user1").await.unwrap();
assert!(res.is_some());
}
#[tokio::test]
async fn test_cleanup() {
let lock_manager = Arc::new(DistributedLockManager::new("test_cleanup"));
let config = RateLimitConfig::new(10.0); let limiter = DistributedSlidingWindowRateLimiter::new(lock_manager, "test", config);
limiter.record("user_cleanup").await.unwrap();
assert!(limiter.get_identifier_count().await.unwrap() > 0);
tokio::time::sleep(Duration::from_millis(300)).await;
let cleaned = limiter.cleanup().await.unwrap();
assert!(cleaned > 0);
assert_eq!(limiter.get_identifier_count().await.unwrap(), 0);
}
#[tokio::test]
async fn test_adaptive_rate_limit() {
let lock_manager = Arc::new(DistributedLockManager::new("test_adaptive"));
let config = RateLimitConfig::new(10.0);
let limiter = DistributedSlidingWindowRateLimiter::new(lock_manager, "test", config);
limiter.decrease_limit("adaptive", 0.5).await.unwrap();
let limit = limiter
.get_key_config("adaptive")
.await
.unwrap()
.max_requests_per_second;
assert_eq!(limit, 5.0);
limiter.try_restore_limit("adaptive", 1.5).await.unwrap();
let limit = limiter
.get_key_config("adaptive")
.await
.unwrap()
.max_requests_per_second;
assert_eq!(limit, 7.5);
}
#[tokio::test]
async fn test_suspend() {
let lock_manager = Arc::new(DistributedLockManager::new("test_suspend"));
let config = RateLimitConfig::new(10.0);
let limiter = DistributedSlidingWindowRateLimiter::new(lock_manager, "test", config);
limiter
.suspend("user_suspend", Duration::from_millis(200))
.await
.unwrap();
let res = limiter.verify("user_suspend").await.unwrap();
assert!(res.is_some());
tokio::time::sleep(Duration::from_millis(300)).await;
let res = limiter.verify("user_suspend").await.unwrap();
assert!(res.is_none());
}
}