use std::time::Instant;
pub struct RateLimiter {
tokens_x1m: i64,
max_tokens_x1m: i64,
refill_per_us_x1m: i64,
last_refill: Instant,
rate_pps: u64,
}
const TOKEN_SCALE: i64 = 1_000_000;
impl RateLimiter {
#[must_use]
pub fn new(rate_pps: u64, burst: u64) -> Self {
let burst = burst.max(1).min(i64::MAX as u64 / TOKEN_SCALE as u64);
let refill_per_us_x1m = if rate_pps == 0 {
i64::MAX / 2 } else {
rate_pps.min(i64::MAX as u64).max(1) as i64
};
Self {
tokens_x1m: (burst as i64) * TOKEN_SCALE,
max_tokens_x1m: (burst as i64) * TOKEN_SCALE,
refill_per_us_x1m,
last_refill: Instant::now(),
rate_pps,
}
}
#[must_use]
pub fn unlimited() -> Self {
Self::new(0, u64::MAX / 2)
}
pub fn try_consume(&mut self) -> bool {
self.refill();
if self.tokens_x1m >= TOKEN_SCALE {
self.tokens_x1m -= TOKEN_SCALE;
true
} else {
false
}
}
pub fn try_consume_batch(&mut self, n: u64) -> u64 {
self.refill();
let available = (self.tokens_x1m / TOKEN_SCALE).max(0) as u64;
let consumed = available.min(n);
self.tokens_x1m -= (consumed as i64) * TOKEN_SCALE;
consumed
}
pub fn consume_blocking(&mut self) {
loop {
self.refill();
if self.tokens_x1m >= TOKEN_SCALE {
self.tokens_x1m -= TOKEN_SCALE;
return;
}
let deficit = TOKEN_SCALE - self.tokens_x1m;
if self.refill_per_us_x1m > 0 {
let wait_us = deficit / self.refill_per_us_x1m.max(1);
if wait_us > 100 {
std::thread::yield_now();
} else {
std::hint::spin_loop();
}
} else {
std::hint::spin_loop();
}
}
}
#[must_use]
pub fn rate_pps(&self) -> u64 {
self.rate_pps
}
#[must_use]
pub fn is_unlimited(&self) -> bool {
self.rate_pps == 0
}
pub fn set_rate_pps(&mut self, rate_pps: u64) {
self.rate_pps = rate_pps;
self.refill_per_us_x1m = if rate_pps == 0 {
i64::MAX / 2
} else {
rate_pps.min(i64::MAX as u64).max(1) as i64
};
}
fn refill(&mut self) {
let now = Instant::now();
let elapsed_us = now.duration_since(self.last_refill).as_micros() as i64;
if elapsed_us > 0 {
let new_tokens = elapsed_us.saturating_mul(self.refill_per_us_x1m);
self.tokens_x1m = self
.tokens_x1m
.saturating_add(new_tokens)
.min(self.max_tokens_x1m);
self.last_refill = now;
}
}
}
pub const ADAPTIVE_MIN_PPS: u64 = 1_000;
pub const ADAPTIVE_SUCCESS_STREAK_THRESHOLD: u32 = 10;
pub const ADAPTIVE_AI_DIVISOR: u64 = 20;
pub const ADAPTIVE_MD_NUMER: u64 = 3;
pub const ADAPTIVE_MD_DENOM: u64 = 4;
pub struct AdaptiveRate {
current_pps: u64,
max_pps: u64,
min_pps: u64,
success_streak: u32,
drop_streak: u32,
}
impl AdaptiveRate {
#[must_use]
pub fn new(max_pps: u64) -> Self {
let min_pps = if max_pps == 0 {
0
} else {
ADAPTIVE_MIN_PPS.min(max_pps).max(1)
};
let current_pps = if max_pps == 0 {
0
} else {
(max_pps / 2).max(min_pps).min(max_pps)
};
Self {
current_pps,
max_pps,
min_pps,
success_streak: 0,
drop_streak: 0,
}
}
pub fn report_success(&mut self) {
self.drop_streak = 0;
self.success_streak += 1;
if self.success_streak >= ADAPTIVE_SUCCESS_STREAK_THRESHOLD {
self.current_pps = (self.current_pps + self.max_pps / ADAPTIVE_AI_DIVISOR)
.min(self.max_pps);
self.success_streak = 0;
}
}
pub fn report_drops(&mut self, _drop_count: u64) {
self.success_streak = 0;
self.drop_streak += 1;
self.current_pps =
(self.current_pps * ADAPTIVE_MD_NUMER / ADAPTIVE_MD_DENOM).max(self.min_pps);
}
#[must_use]
pub fn current_pps(&self) -> u64 {
self.current_pps
}
}
pub struct AdaptiveLoop {
rate: AdaptiveRate,
last_tx_packets: u64,
last_tx_drops: u64,
initialized: bool,
}
impl AdaptiveLoop {
#[must_use]
pub fn new(max_pps: u64) -> Self {
Self {
rate: AdaptiveRate::new(max_pps),
last_tx_packets: 0,
last_tx_drops: 0,
initialized: false,
}
}
pub fn tick(&mut self, tx_packets: u64, tx_drops: u64) -> u64 {
if !self.initialized {
self.last_tx_packets = tx_packets;
self.last_tx_drops = tx_drops;
self.initialized = true;
return self.rate.current_pps();
}
let drop_delta = tx_drops.saturating_sub(self.last_tx_drops);
let packet_delta = tx_packets.saturating_sub(self.last_tx_packets);
if drop_delta > 0 {
self.rate.report_drops(drop_delta);
} else if packet_delta > 0 {
self.rate.report_success();
}
self.last_tx_packets = tx_packets;
self.last_tx_drops = tx_drops;
self.rate.current_pps()
}
pub fn apply(&self, limiter: &mut RateLimiter) {
limiter.set_rate_pps(self.rate.current_pps());
}
#[must_use]
pub fn current_pps(&self) -> u64 {
self.rate.current_pps()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rate_limiter_unlimited_always_succeeds() {
let mut rl = RateLimiter::unlimited();
for _ in 0..10_000 {
assert!(rl.try_consume());
}
}
#[test]
fn rate_limiter_respects_burst() {
let mut rl = RateLimiter::new(1_000_000, 10);
for i in 0..10 {
assert!(rl.try_consume(), "failed at burst token {i}");
}
}
#[test]
fn rate_limiter_batch_consume() {
let mut rl = RateLimiter::new(10, 100);
let consumed = rl.try_consume_batch(50);
assert_eq!(consumed, 50);
let consumed2 = rl.try_consume_batch(100);
assert_eq!(consumed2, 50); }
#[test]
fn adaptive_rate_decreases_on_drops() {
let mut ar = AdaptiveRate::new(1_000_000);
let initial = ar.current_pps();
ar.report_drops(100);
assert!(ar.current_pps() < initial);
}
#[test]
fn adaptive_rate_increases_on_success() {
let mut ar = AdaptiveRate::new(1_000_000);
let initial = ar.current_pps();
for _ in 0..20 {
ar.report_success();
}
assert!(ar.current_pps() > initial);
}
#[test]
fn adaptive_rate_never_below_floor() {
let mut ar = AdaptiveRate::new(1_000_000);
for _ in 0..100 {
ar.report_drops(1000);
}
assert!(ar.current_pps() >= 1000);
}
#[test]
fn set_rate_pps_changes_refill() {
let mut r = RateLimiter::new(1_000, 100);
assert_eq!(r.rate_pps(), 1_000);
r.set_rate_pps(500);
assert_eq!(r.rate_pps(), 500);
r.set_rate_pps(0);
assert!(r.is_unlimited());
}
#[test]
fn adaptive_loop_initial_tick_is_baseline_only() {
let mut lo = AdaptiveLoop::new(1_000_000);
let initial = lo.current_pps();
let returned = lo.tick(0, 0);
assert_eq!(returned, initial);
}
#[test]
fn adaptive_loop_decreases_on_tx_drop_burst() {
let mut lo = AdaptiveLoop::new(1_000_000);
let before = lo.tick(0, 0); let after = lo.tick(1000, 50);
assert!(
after < before,
"expected rate to decrease: {after} < {before}"
);
}
#[test]
fn adaptive_loop_increases_after_clean_streak() {
let mut lo = AdaptiveLoop::new(1_000_000);
lo.tick(0, 0); let before = lo.current_pps();
for i in 1..=15 {
lo.tick(i * 100, 0);
}
assert!(
lo.current_pps() > before,
"expected rate to increase after streak: {} > {before}",
lo.current_pps()
);
}
#[test]
fn adaptive_loop_apply_propagates_to_limiter() {
let mut lo = AdaptiveLoop::new(1_000_000);
let mut limiter = RateLimiter::new(1_000_000, 1000);
lo.tick(0, 0);
lo.tick(1000, 100);
lo.apply(&mut limiter);
assert_eq!(limiter.rate_pps(), lo.current_pps());
}
#[test]
fn rate_limiter_actually_throttles_to_configured_rate() {
use std::time::{Duration, Instant};
const CONFIGURED_PPS: u64 = 10_000;
let mut rl = RateLimiter::new(CONFIGURED_PPS, 32);
while rl.try_consume() {}
let start = Instant::now();
let mut consumed: u64 = 0;
while start.elapsed() < Duration::from_millis(100) {
if rl.try_consume() {
consumed += 1;
} else {
std::hint::spin_loop();
}
}
let elapsed_ms = start.elapsed().as_millis().max(1) as u64;
let observed_pps = consumed.saturating_mul(1000) / elapsed_ms;
let upper_bound = CONFIGURED_PPS * 4;
assert!(
observed_pps <= upper_bound,
"RateLimiter configured at {CONFIGURED_PPS} pps achieved \
{observed_pps} pps (consumed {consumed} in {elapsed_ms}ms). \
refill scaling regressed"
);
}
#[test]
fn adaptive_loop_converges_under_synthetic_loss_pattern() {
let mut lo = AdaptiveLoop::new(10_000_000);
let start = lo.current_pps();
for i in 1..=20 {
lo.tick(i * 1000, i * 500);
}
let final_pps = lo.current_pps();
assert!(
final_pps < start / 4,
"expected aggressive decrease: {final_pps} >= {} (start/4)",
start / 4
);
}
#[test]
fn rate_limiter_zero_burst_does_not_panic() {
let mut rl = RateLimiter::new(1000, 0);
assert!(rl.try_consume());
}
#[test]
fn adaptive_rate_zero_max_does_not_panic() {
let mut ar = AdaptiveRate::new(0);
ar.report_drops(1);
ar.report_success();
assert_eq!(ar.current_pps(), 0);
}
#[test]
fn adaptive_rate_floor_never_exceeds_low_ceiling() {
let mut ar = AdaptiveRate::new(50);
assert!(ar.current_pps() <= 50);
ar.report_drops(100);
assert!(
ar.current_pps() <= 50,
"floor must not raise rate above ceiling: {}",
ar.current_pps()
);
assert!(ar.current_pps() >= 1);
}
#[test]
fn rate_limiter_extreme_rate_does_not_panic() {
let mut rl = RateLimiter::new(u64::MAX, 1);
let _ = rl.try_consume();
rl.set_rate_pps(u64::MAX);
let _ = rl.try_consume();
}
}
#[cfg(test)]
mod proptests {
use super::*;
use proptest::prelude::*;
proptest! {
#[test]
fn rate_limiter_batch_consume_never_exceeds_available(
rate in 0u64..1_000_000u64,
burst in 1u64..1000u64,
request in 0u64..10_000u64,
) {
let mut rl = RateLimiter::new(rate, burst);
let first = rl.try_consume_batch(request);
prop_assert!(first <= request);
let second = rl.try_consume_batch(request);
prop_assert!(second <= request);
prop_assert!(first + second <= burst);
}
#[test]
fn adaptive_rate_pps_stays_in_bounds(
max_pps in 0u64..10_000_000u64,
drops in 0u64..1000u64,
successes in 0u32..50u32,
) {
let mut ar = AdaptiveRate::new(max_pps);
for _ in 0..successes {
ar.report_success();
}
ar.report_drops(drops);
let current = ar.current_pps();
let floor = if max_pps == 0 {
0
} else {
ADAPTIVE_MIN_PPS.min(max_pps).max(1)
};
prop_assert!(current <= max_pps, "rate exceeded max: {current} > {max_pps}");
prop_assert!(current >= floor, "rate below floor: {current} < {floor}");
}
#[test]
fn adaptive_loop_ticks_never_panic(
max_pps in 0u64..10_000_000u64,
tx_packets in 0u64..u64::MAX,
tx_drops in 0u64..u64::MAX,
) {
let mut lo = AdaptiveLoop::new(max_pps);
lo.tick(tx_packets, tx_drops);
lo.tick(tx_packets.saturating_add(100), tx_drops);
let _ = lo.current_pps();
}
}
}