#![allow(non_snake_case)]
use std::collections::HashMap as FxHashMap;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use tokio::sync::RwLock;
use std::time::{Duration, Instant};
#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
#[derive(Debug, Clone)]
pub struct RiRateLimitConfig {
pub requests_per_second: u32,
pub burst_size: u32,
pub window_seconds: u64,
pub max_keys: usize,
}
const MAX_KEY_LENGTH: usize = 256;
#[cfg(feature = "pyo3")]
#[pyo3::prelude::pymethods]
impl RiRateLimitConfig {
#[new]
fn py_new() -> Self {
Self::default()
}
#[staticmethod]
fn py_new_with_values(requests_per_second: u32, burst_size: u32, window_seconds: u64) -> Self {
Self {
requests_per_second,
burst_size,
window_seconds,
max_keys: 10000,
}
}
fn get_requests_per_second(&self) -> u32 {
self.requests_per_second
}
fn set_requests_per_second(&mut self, value: u32) {
self.requests_per_second = value;
}
fn get_burst_size(&self) -> u32 {
self.burst_size
}
fn set_burst_size(&mut self, value: u32) {
self.burst_size = value;
}
fn get_window_seconds(&self) -> u64 {
self.window_seconds
}
fn set_window_seconds(&mut self, value: u64) {
self.window_seconds = value;
}
}
impl Default for RiRateLimitConfig {
fn default() -> Self {
Self {
requests_per_second: 10,
burst_size: 20,
window_seconds: 60,
max_keys: 10000,
}
}
}
#[derive(Debug)]
struct RateLimitBucket {
tokens: AtomicUsize,
last_update: RwLock<Instant>,
request_count: AtomicUsize,
}
impl RateLimitBucket {
fn new(tokens: usize) -> Self {
Self {
tokens: AtomicUsize::new(tokens),
last_update: RwLock::new(Instant::now()),
request_count: AtomicUsize::new(0),
}
}
async fn try_consume(&self, tokens: usize, config: &RiRateLimitConfig) -> bool {
let now = Instant::now();
let mut last_update = self.last_update.write().await;
let elapsed = now.duration_since(*last_update).as_secs_f64();
let tokens_to_add = (elapsed * config.requests_per_second as f64) as usize;
if tokens_to_add > 0 {
let current_tokens = self.tokens.load(Ordering::Relaxed);
let new_tokens = std::cmp::min(current_tokens + tokens_to_add, config.burst_size as usize);
self.tokens.store(new_tokens, Ordering::Relaxed);
*last_update = now;
}
let current_tokens = self.tokens.load(Ordering::Relaxed);
if current_tokens >= tokens {
self.tokens.fetch_sub(tokens, Ordering::Relaxed);
self.request_count.fetch_add(1, Ordering::Relaxed);
true
} else {
false
}
}
fn get_stats(&self) -> RiRateLimitStats {
RiRateLimitStats {
current_tokens: self.tokens.load(Ordering::Relaxed),
total_requests: self.request_count.load(Ordering::Relaxed),
}
}
}
#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass(get_all, set_all))]
#[derive(Debug, Clone)]
pub struct RiRateLimitStats {
pub current_tokens: usize,
pub total_requests: usize,
}
#[cfg(feature = "pyo3")]
#[pyo3::prelude::pymethods]
impl RiRateLimitStats {
#[new]
fn py_new(current_tokens: usize, total_requests: usize) -> Self {
Self {
current_tokens,
total_requests,
}
}
fn get_current_tokens(&self) -> usize {
self.current_tokens
}
fn get_total_requests(&self) -> usize {
self.total_requests
}
}
#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
pub struct RiRateLimiter {
config: RiRateLimitConfig,
buckets: RwLock<FxHashMap<String, Arc<RateLimitBucket>>>,
}
impl RiRateLimiter {
pub fn new(config: RiRateLimitConfig) -> Self {
Self {
config,
buckets: RwLock::new(FxHashMap::default()),
}
}
pub async fn check_request(&self, request: &crate::gateway::RiGatewayRequest) -> bool {
let key = request.remote_addr.clone();
self.check_rate_limit(&key, 1)
}
pub fn check_rate_limit(&self, key: &str, tokens: usize) -> bool {
if key.is_empty() || key.len() > MAX_KEY_LENGTH {
log::warn!(
"[Ri.RateLimiter] Invalid key length: {} chars (max {})",
key.len(), MAX_KEY_LENGTH
);
return false;
}
futures::executor::block_on(async {
let buckets = self.buckets.read().await;
if let Some(bucket) = buckets.get(key) {
bucket.try_consume(tokens, &self.config).await
} else {
drop(buckets);
let mut buckets = self.buckets.write().await;
if buckets.len() >= self.config.max_keys {
log::warn!(
"[Ri.RateLimiter] Maximum keys limit reached: {} (max {})",
buckets.len(), self.config.max_keys
);
return false;
}
if let Some(bucket) = buckets.get(key) {
bucket.try_consume(tokens, &self.config).await
} else {
let bucket = Arc::new(RateLimitBucket::new(self.config.burst_size as usize));
let result = bucket.try_consume(tokens, &self.config).await;
buckets.insert(key.to_string(), bucket);
result
}
}
})
}
pub fn get_stats(&self, key: &str) -> Option<RiRateLimitStats> {
futures::executor::block_on(async {
let buckets = self.buckets.read().await;
buckets.get(key).map(|bucket| bucket.get_stats())
})
}
pub fn get_remaining(&self, key: &str) -> Option<f64> {
futures::executor::block_on(async {
let buckets = self.buckets.read().await;
buckets.get(key).map(|bucket| {
let stats = bucket.get_stats();
stats.current_tokens as f64
})
})
}
pub fn get_all_stats(&self) -> FxHashMap<String, RiRateLimitStats> {
futures::executor::block_on(async {
let buckets = self.buckets.read().await;
let mut stats = FxHashMap::default();
for (key, bucket) in buckets.iter() {
stats.insert(key.clone(), bucket.get_stats());
}
stats
})
}
pub fn reset_bucket(&self, key: &str) {
futures::executor::block_on(async {
let mut buckets = self.buckets.write().await;
buckets.remove(key);
})
}
pub fn clear_all_buckets(&self) {
futures::executor::block_on(async {
let mut buckets = self.buckets.write().await;
buckets.clear();
})
}
pub fn get_config(&self) -> RiRateLimitConfig {
self.config.clone()
}
pub async fn update_config(&mut self, config: RiRateLimitConfig) {
self.config = config;
let mut buckets = self.buckets.write().await;
buckets.clear();
}
pub async fn check_multi(&self, keys: &[String], tokens: usize) -> Vec<bool> {
let mut results = Vec::with_capacity(keys.len());
for key in keys {
results.push(self.check_rate_limit(key, tokens));
}
results
}
pub async fn get_keys(&self) -> Vec<String> {
let buckets = self.buckets.read().await;
buckets.keys().cloned().collect()
}
pub fn bucket_count(&self) -> usize {
futures::executor::block_on(async {
let buckets = self.buckets.read().await;
buckets.len()
})
}
}
#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
pub struct RiSlidingWindowRateLimiter {
max_requests: u32,
window_duration: Duration,
requests: RwLock<Vec<Instant>>,
}
impl RiSlidingWindowRateLimiter {
pub fn new(max_requests: u32, window_seconds: u64) -> Self {
Self {
max_requests,
window_duration: Duration::from_secs(window_seconds),
requests: RwLock::new(Vec::new()),
}
}
pub fn allow_request(&self) -> bool {
futures::executor::block_on(async {
let mut requests = self.requests.write().await;
let now = Instant::now();
requests.retain(|×tamp| now.duration_since(timestamp) < self.window_duration);
if requests.len() < self.max_requests as usize {
requests.push(now);
true
} else {
false
}
})
}
pub fn get_current_count(&self) -> usize {
futures::executor::block_on(async {
let mut requests = self.requests.write().await;
let now = Instant::now();
requests.retain(|×tamp| now.duration_since(timestamp) < self.window_duration);
requests.len()
})
}
pub fn reset(&self) {
futures::executor::block_on(async {
let mut requests = self.requests.write().await;
requests.clear();
})
}
pub fn get_max_requests(&self) -> u32 {
self.max_requests
}
pub fn get_window_seconds(&self) -> u64 {
self.window_duration.as_secs()
}
}