use std::sync::atomic::Ordering;
pub(crate) const MAX_REFILL_PERIODS: u64 = 100;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MemoryOrdering {
Relaxed,
AcquireRelease,
Sequential,
}
impl MemoryOrdering {
#[inline(always)]
pub(crate) fn load(&self) -> Ordering {
match self {
Self::Relaxed => Ordering::Relaxed,
Self::AcquireRelease => Ordering::Acquire,
Self::Sequential => Ordering::SeqCst,
}
}
#[inline(always)]
pub(crate) fn store(&self) -> Ordering {
match self {
Self::Relaxed => Ordering::Relaxed,
Self::AcquireRelease => Ordering::Release,
Self::Sequential => Ordering::SeqCst,
}
}
#[inline(always)]
pub(crate) fn rmw(&self) -> Ordering {
match self {
Self::Relaxed => Ordering::Relaxed,
Self::AcquireRelease => Ordering::AcqRel,
Self::Sequential => Ordering::SeqCst,
}
}
#[inline(always)]
#[allow(dead_code)]
pub(crate) fn cas_failure(&self) -> Ordering {
match self {
Self::Relaxed => Ordering::Relaxed,
Self::AcquireRelease => Ordering::Acquire,
Self::Sequential => Ordering::SeqCst,
}
}
}
impl Default for MemoryOrdering {
fn default() -> Self {
Self::AcquireRelease
}
}
#[derive(Debug, Clone)]
pub struct RateLimiterConfig {
pub max_tokens: u64,
pub refill_rate: u32,
pub refill_interval_ms: u64,
pub ordering: MemoryOrdering,
}
impl Default for RateLimiterConfig {
fn default() -> Self {
Self {
max_tokens: 50,
refill_rate: 10,
refill_interval_ms: 1000,
ordering: MemoryOrdering::AcquireRelease,
}
}
}
impl RateLimiterConfig {
pub fn new(max_tokens: u64, refill_rate: u32, refill_interval_ms: u64) -> Self {
Self {
max_tokens,
refill_rate,
refill_interval_ms,
ordering: MemoryOrdering::default(),
}
}
pub fn per_second(requests_per_second: u32) -> Self {
Self {
max_tokens: (requests_per_second * 2) as u64, refill_rate: requests_per_second,
refill_interval_ms: 1000,
ordering: MemoryOrdering::default(),
}
}
pub fn per_minute(requests_per_minute: u32) -> Self {
Self {
max_tokens: requests_per_minute as u64,
refill_rate: requests_per_minute,
refill_interval_ms: 60_000,
ordering: MemoryOrdering::default(),
}
}
pub fn with_ordering(mut self, ordering: MemoryOrdering) -> Self {
self.ordering = ordering;
self
}
pub fn with_burst_multiplier(mut self, multiplier: u32) -> Self {
self.max_tokens = (self.refill_rate * multiplier) as u64;
self
}
pub fn validate(&self) -> Result<(), &'static str> {
if self.max_tokens == 0 {
return Err("max_tokens must be greater than 0");
}
if self.refill_rate == 0 {
return Err("refill_rate must be greater than 0");
}
#[cfg(not(target_pointer_width = "64"))]
{
if self.max_tokens > u32::MAX as u64 {
return Err("max_tokens exceeds platform limit (u32::MAX on 32-bit systems)");
}
let max_refill = (self.refill_rate as u64).saturating_mul(MAX_REFILL_PERIODS);
if max_refill > u32::MAX as u64 {
return Err("refill_rate * MAX_REFILL_PERIODS would overflow on 32-bit systems");
}
}
if self.refill_interval_ms == 0 {
return Err("refill_interval_ms must be greater than 0");
}
if self.refill_rate as u64 > self.max_tokens {
return Err("refill_rate should not exceed max_tokens");
}
Ok(())
}
pub fn effective_rate_per_second(&self) -> f64 {
if self.refill_interval_ms == 0 {
0.0
} else {
(self.refill_rate as f64 * 1000.0) / self.refill_interval_ms as f64
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_memory_ordering() {
let ordering = MemoryOrdering::AcquireRelease;
assert_eq!(ordering.load(), Ordering::Acquire);
assert_eq!(ordering.store(), Ordering::Release);
assert_eq!(ordering.rmw(), Ordering::AcqRel);
assert_eq!(ordering.cas_failure(), Ordering::Acquire);
}
#[test]
fn test_config_validation() {
let valid = RateLimiterConfig::default();
assert!(valid.validate().is_ok());
let invalid = RateLimiterConfig {
max_tokens: 0,
..Default::default()
};
assert!(invalid.validate().is_err());
let invalid_refill = RateLimiterConfig {
max_tokens: 10,
refill_rate: 20,
..Default::default()
};
assert!(invalid_refill.validate().is_err());
}
#[test]
fn test_config_builders() {
let config = RateLimiterConfig::per_second(100);
assert_eq!(config.max_tokens, 200);
assert_eq!(config.refill_rate, 100);
assert_eq!(config.effective_rate_per_second(), 100.0);
}
#[cfg(not(target_pointer_width = "64"))]
#[test]
fn test_32bit_validation() {
let config = RateLimiterConfig {
max_tokens: (u32::MAX as u64) + 1,
..Default::default()
};
assert!(config.validate().is_err());
}
#[test]
fn test_memory_ordering_all_variants() {
let relaxed = MemoryOrdering::Relaxed;
assert_eq!(relaxed.load(), Ordering::Relaxed);
assert_eq!(relaxed.store(), Ordering::Relaxed);
assert_eq!(relaxed.rmw(), Ordering::Relaxed);
assert_eq!(relaxed.cas_failure(), Ordering::Relaxed);
let sequential = MemoryOrdering::Sequential;
assert_eq!(sequential.load(), Ordering::SeqCst);
assert_eq!(sequential.store(), Ordering::SeqCst);
assert_eq!(sequential.rmw(), Ordering::SeqCst);
assert_eq!(sequential.cas_failure(), Ordering::SeqCst);
}
#[test]
fn test_config_with_burst_multiplier() {
let config = RateLimiterConfig::per_second(10).with_burst_multiplier(5);
assert_eq!(config.max_tokens, 50);
assert_eq!(config.refill_rate, 10);
}
#[test]
fn test_config_per_minute() {
let config = RateLimiterConfig::per_minute(120);
assert_eq!(config.max_tokens, 120);
assert_eq!(config.refill_rate, 120);
assert_eq!(config.refill_interval_ms, 60_000);
assert_eq!(config.effective_rate_per_second(), 2.0);
}
#[test]
fn test_config_with_ordering() {
let config = RateLimiterConfig::default().with_ordering(MemoryOrdering::Sequential);
assert_eq!(config.ordering, MemoryOrdering::Sequential);
}
#[test]
fn test_config_validation_edge_cases() {
let config = RateLimiterConfig {
max_tokens: 10,
refill_rate: 5,
refill_interval_ms: 0,
ordering: MemoryOrdering::default(),
};
assert!(config.validate().is_err());
assert_eq!(config.effective_rate_per_second(), 0.0);
}
#[test]
fn test_default_memory_ordering() {
assert_eq!(MemoryOrdering::default(), MemoryOrdering::AcquireRelease);
}
#[cfg(not(target_pointer_width = "64"))]
#[test]
fn test_32bit_overflow_validation() {
let config = RateLimiterConfig {
max_tokens: 1000,
refill_rate: u32::MAX / 10,
refill_interval_ms: 1000,
ordering: MemoryOrdering::default(),
};
assert!(config.validate().is_err());
}
#[test]
fn test_refill_rate_zero_validation() {
let config = RateLimiterConfig {
max_tokens: 10,
refill_rate: 0,
refill_interval_ms: 1000,
ordering: MemoryOrdering::default(),
};
assert!(config.validate().is_err());
assert_eq!(
config.validate().unwrap_err(),
"refill_rate must be greater than 0"
);
}
#[test]
fn test_config_clone() {
let original = RateLimiterConfig::per_second(100);
let cloned = original.clone();
assert_eq!(cloned.max_tokens, original.max_tokens);
assert_eq!(cloned.refill_rate, original.refill_rate);
assert_eq!(cloned.refill_interval_ms, original.refill_interval_ms);
assert_eq!(cloned.ordering, original.ordering);
}
#[test]
fn test_config_debug() {
let config = RateLimiterConfig::default();
let debug = format!("{:?}", config);
assert!(debug.contains("RateLimiterConfig"));
assert!(debug.contains("max_tokens"));
}
#[test]
fn test_effective_rate_various_intervals() {
let config = RateLimiterConfig::new(200, 100, 500);
assert_eq!(config.effective_rate_per_second(), 200.0);
let config = RateLimiterConfig::new(10, 1, 100);
assert_eq!(config.effective_rate_per_second(), 10.0);
}
#[test]
fn test_burst_multiplier_with_per_minute() {
let config = RateLimiterConfig::per_minute(60).with_burst_multiplier(3);
assert_eq!(config.max_tokens, 180); assert_eq!(config.refill_rate, 60);
assert_eq!(config.refill_interval_ms, 60_000);
}
#[test]
fn test_valid_config_refill_equals_max() {
let config = RateLimiterConfig::new(10, 10, 1000);
assert!(config.validate().is_ok());
}
#[test]
fn test_new_constructor() {
let config = RateLimiterConfig::new(50, 5, 500);
assert_eq!(config.max_tokens, 50);
assert_eq!(config.refill_rate, 5);
assert_eq!(config.refill_interval_ms, 500);
assert_eq!(config.ordering, MemoryOrdering::AcquireRelease);
}
}