use crate::traits::{RateLimiter, RateLimitKey, RateLimitDecision, RateLimiterStats};
use anyhow::Result;
use async_trait::async_trait;
use crossbeam_deque::{Injector, Stealer, Worker};
use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
use std::sync::Arc;
use std::time::{Duration, Instant};
use std::collections::HashMap;
use parking_lot::RwLock;
const CACHE_LINE_SIZE: usize = 64;
const MAX_CORES: usize = 128;
const STEAL_THRESHOLD: f64 = 0.2;
#[repr(align(64))]
struct CpuTokenBucket {
state: AtomicU64,
refill_rate: f64,
capacity: f64,
last_refill_ns: AtomicU64,
requests_served: AtomicU64,
tokens_stolen: AtomicU64,
_padding: [u8; CACHE_LINE_SIZE - 48],
}
struct TokenStealer {
worker: Worker<TokenTransfer>,
stealers: Vec<Stealer<TokenTransfer>>,
injector: Arc<Injector<TokenTransfer>>,
}
#[derive(Clone, Copy)]
struct TokenTransfer {
tokens: f64,
from_cpu: usize,
timestamp: u64,
}
pub struct HierarchicalRateLimiter {
cpu_buckets: Vec<CpuTokenBucket>,
stealers: Vec<TokenStealer>,
global_injector: Arc<Injector<TokenTransfer>>,
client_limits: Arc<RwLock<HashMap<String, ClientRateLimit>>>,
default_rpm: u32,
default_burst: u32,
num_cpus: usize,
total_requests: AtomicU64,
total_steals: AtomicU64,
cache_hits: AtomicU64,
}
#[derive(Clone)]
struct ClientRateLimit {
rpm: u32,
burst: u32,
priority: u8,
}
impl CpuTokenBucket {
fn new(rpm: u32, burst: u32) -> Self {
let capacity = burst as f64;
let refill_rate = rpm as f64 / 60.0;
let initial_state = ((capacity as u32) as u64) << 32;
Self {
state: AtomicU64::new(initial_state),
refill_rate,
capacity,
last_refill_ns: AtomicU64::new(0),
requests_served: AtomicU64::new(0),
tokens_stolen: AtomicU64::new(0),
_padding: [0; CACHE_LINE_SIZE - 48],
}
}
fn try_consume(&self, tokens: f64) -> bool {
self.refill();
let mut current = self.state.load(Ordering::Acquire);
loop {
let current_tokens = (current >> 32) as f32;
let version = current as u32;
if current_tokens < tokens as f32 {
return false; }
let new_tokens = current_tokens - tokens as f32;
let new_state = ((new_tokens as u32) as u64) << 32 | ((version + 1) as u64);
match self.state.compare_exchange_weak(
current,
new_state,
Ordering::Release,
Ordering::Acquire,
) {
Ok(_) => {
self.requests_served.fetch_add(1, Ordering::Relaxed);
return true;
}
Err(actual) => current = actual,
}
}
}
fn refill(&self) {
let now_ns = Instant::now().elapsed().as_nanos() as u64;
let last_ns = self.last_refill_ns.load(Ordering::Acquire);
if now_ns.saturating_sub(last_ns) < 1_000_000 { return;
}
match self.last_refill_ns.compare_exchange(
last_ns,
now_ns,
Ordering::Release,
Ordering::Acquire,
) {
Ok(_) => {
let elapsed_secs = (now_ns - last_ns) as f64 / 1_000_000_000.0;
let tokens_to_add = elapsed_secs * self.refill_rate;
let mut current = self.state.load(Ordering::Acquire);
loop {
let current_tokens = (current >> 32) as f32;
let version = current as u32;
let new_tokens = (current_tokens + tokens_to_add as f32).min(self.capacity as f32);
let new_state = ((new_tokens as u32) as u64) << 32 | ((version + 1) as u64);
match self.state.compare_exchange_weak(
current,
new_state,
Ordering::Release,
Ordering::Acquire,
) {
Ok(_) => break,
Err(actual) => current = actual,
}
}
}
Err(_) => {
}
}
}
fn tokens(&self) -> f64 {
let state = self.state.load(Ordering::Acquire);
(state >> 32) as f32 as f64
}
fn steal_tokens(&self, amount: f64) -> Option<f64> {
let mut current = self.state.load(Ordering::Acquire);
loop {
let current_tokens = (current >> 32) as f32;
let version = current as u32;
if current_tokens < self.capacity as f32 * 0.5 {
return None;
}
let steal_amount = amount.min((current_tokens as f64 - self.capacity * 0.3).max(0.0));
if steal_amount < 1.0 {
return None;
}
let new_tokens = current_tokens - steal_amount as f32;
let new_state = ((new_tokens as u32) as u64) << 32 | ((version + 1) as u64);
match self.state.compare_exchange_weak(
current,
new_state,
Ordering::Release,
Ordering::Acquire,
) {
Ok(_) => {
self.tokens_stolen.fetch_add(steal_amount as u64, Ordering::Relaxed);
return Some(steal_amount);
}
Err(actual) => current = actual,
}
}
}
fn inject_tokens(&self, amount: f64) {
let mut current = self.state.load(Ordering::Acquire);
loop {
let current_tokens = (current >> 32) as f32;
let version = current as u32;
let new_tokens = (current_tokens + amount as f32).min(self.capacity as f32);
let new_state = ((new_tokens as u32) as u64) << 32 | ((version + 1) as u64);
match self.state.compare_exchange_weak(
current,
new_state,
Ordering::Release,
Ordering::Acquire,
) {
Ok(_) => break,
Err(actual) => current = actual,
}
}
}
}
impl HierarchicalRateLimiter {
pub fn new(default_rpm: u32, default_burst: u32) -> Self {
let num_cpus = num_cpus::get().min(MAX_CORES);
let mut cpu_buckets = Vec::with_capacity(num_cpus);
for _ in 0..num_cpus {
cpu_buckets.push(CpuTokenBucket::new(default_rpm, default_burst));
}
let global_injector = Arc::new(Injector::new());
let mut workers = Vec::with_capacity(num_cpus);
let mut stealers_list = Vec::with_capacity(num_cpus);
for _ in 0..num_cpus {
let worker = Worker::new_fifo();
stealers_list.push(worker.stealer());
workers.push(worker);
}
let mut stealers = Vec::with_capacity(num_cpus);
for (i, worker) in workers.into_iter().enumerate() {
let mut other_stealers = Vec::with_capacity(num_cpus - 1);
for (j, stealer) in stealers_list.iter().enumerate() {
if i != j {
other_stealers.push(stealer.clone());
}
}
stealers.push(TokenStealer {
worker,
stealers: other_stealers,
injector: global_injector.clone(),
});
}
Self {
cpu_buckets,
stealers,
global_injector,
client_limits: Arc::new(RwLock::new(HashMap::new())),
default_rpm,
default_burst,
num_cpus,
total_requests: AtomicU64::new(0),
total_steals: AtomicU64::new(0),
cache_hits: AtomicU64::new(0),
}
}
fn get_cpu_index(&self) -> usize {
let thread_id = std::thread::current().id();
let hash = thread_id.as_u64().get() as usize;
hash % self.num_cpus
}
fn try_work_stealing(&self, cpu_idx: usize, needed_tokens: f64) -> Option<f64> {
let stealer = &self.stealers[cpu_idx];
if let Some(transfer) = stealer.worker.pop() {
return Some(transfer.tokens);
}
for (i, other_stealer) in stealer.stealers.iter().enumerate() {
if let Some(transfer) = other_stealer.steal().success() {
self.total_steals.fetch_add(1, Ordering::Relaxed);
return Some(transfer.tokens);
}
let other_idx = if i >= cpu_idx { i + 1 } else { i };
if let Some(stolen) = self.cpu_buckets[other_idx].steal_tokens(needed_tokens) {
self.total_steals.fetch_add(1, Ordering::Relaxed);
return Some(stolen);
}
}
if let Some(transfer) = stealer.injector.steal().success() {
self.total_steals.fetch_add(1, Ordering::Relaxed);
return Some(transfer.tokens);
}
None
}
fn get_limits(&self, client_id: &str) -> (u32, u32) {
let limits = self.client_limits.read();
if let Some(limit) = limits.get(client_id) {
(limit.rpm, limit.burst)
} else {
(self.default_rpm, self.default_burst)
}
}
}
#[async_trait]
impl RateLimiter for HierarchicalRateLimiter {
async fn check_rate_limit(&self, key: &RateLimitKey) -> Result<RateLimitDecision> {
self.total_requests.fetch_add(1, Ordering::Relaxed);
let cpu_idx = self.get_cpu_index();
let bucket = &self.cpu_buckets[cpu_idx];
if bucket.try_consume(1.0) {
self.cache_hits.fetch_add(1, Ordering::Relaxed);
return Ok(RateLimitDecision {
allowed: true,
tokens_remaining: bucket.tokens(),
reset_after: Duration::from_secs(1),
});
}
if let Some(stolen_tokens) = self.try_work_stealing(cpu_idx, 1.0) {
bucket.inject_tokens(stolen_tokens);
if bucket.try_consume(1.0) {
return Ok(RateLimitDecision {
allowed: true,
tokens_remaining: bucket.tokens(),
reset_after: Duration::from_secs(1),
});
}
}
Ok(RateLimitDecision {
allowed: false,
tokens_remaining: 0.0,
reset_after: Duration::from_secs(1),
})
}
async fn record_request(&self, _key: &RateLimitKey) -> Result<()> {
Ok(())
}
async fn apply_penalty(&self, client_id: &str, factor: f32) -> Result<()> {
let mut limits = self.client_limits.write();
let (rpm, burst) = self.get_limits(client_id);
limits.insert(client_id.to_string(), ClientRateLimit {
rpm: (rpm as f32 * factor) as u32,
burst: (burst as f32 * factor) as u32,
priority: 0,
});
Ok(())
}
fn get_stats(&self) -> RateLimiterStats {
let total_requests = self.total_requests.load(Ordering::Relaxed);
let cache_hits = self.cache_hits.load(Ordering::Relaxed);
let cache_hit_rate = if total_requests > 0 {
(cache_hits as f64 / total_requests as f64) * 100.0
} else {
0.0
};
let mut total_tokens = 0.0;
let mut total_served = 0u64;
for bucket in &self.cpu_buckets {
total_tokens += bucket.tokens();
total_served += bucket.requests_served.load(Ordering::Relaxed);
}
RateLimiterStats {
requests_allowed: total_served,
requests_denied: total_requests - total_served,
active_buckets: self.num_cpus as u64,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
use tokio::sync::Barrier;
#[test]
fn test_cpu_token_bucket() {
let bucket = CpuTokenBucket::new(60, 10);
assert_eq!(bucket.tokens() as u32, 10);
assert!(bucket.try_consume(5.0));
assert_eq!(bucket.tokens() as u32, 5);
assert!(!bucket.try_consume(10.0));
assert!(bucket.try_consume(5.0));
assert_eq!(bucket.tokens() as u32, 0);
}
#[tokio::test]
async fn test_hierarchical_rate_limiter() {
let limiter = Arc::new(HierarchicalRateLimiter::new(600, 100));
let key = RateLimitKey {
client_id: "test".to_string(),
method: None,
};
for _ in 0..50 {
let decision = limiter.check_rate_limit(&key).await.unwrap();
assert!(decision.allowed);
}
let stats = limiter.get_stats();
assert!(stats.requests_allowed >= 50);
}
#[tokio::test]
async fn test_concurrent_access() {
let limiter = Arc::new(HierarchicalRateLimiter::new(10000, 1000));
let barrier = Arc::new(Barrier::new(10));
let mut handles = vec![];
for i in 0..10 {
let limiter_clone = limiter.clone();
let barrier_clone = barrier.clone();
handles.push(tokio::spawn(async move {
barrier_clone.wait().await;
let key = RateLimitKey {
client_id: format!("client_{}", i),
method: None,
};
let mut allowed = 0;
for _ in 0..100 {
let decision = limiter_clone.check_rate_limit(&key).await.unwrap();
if decision.allowed {
allowed += 1;
}
}
allowed
}));
}
let mut total_allowed = 0;
for handle in handles {
total_allowed += handle.await.unwrap();
}
assert!(total_allowed > 500);
let stats = limiter.get_stats();
println!("Cache hit rate: {:.2}%",
(limiter.cache_hits.load(Ordering::Relaxed) as f64 /
limiter.total_requests.load(Ordering::Relaxed) as f64) * 100.0);
}
}