1use std::time::Instant;
8
9pub struct RateLimiter {
23 tokens_x1m: i64,
25 max_tokens_x1m: i64,
27 refill_per_us_x1m: i64,
29 last_refill: Instant,
31 rate_pps: u64,
33}
34
35const TOKEN_SCALE: i64 = 1_000_000;
36
37impl RateLimiter {
38 #[must_use]
43 pub fn new(rate_pps: u64, burst: u64) -> Self {
44 let burst = burst.max(1).min(i64::MAX as u64 / TOKEN_SCALE as u64);
45 let refill_per_us_x1m = if rate_pps == 0 {
46 i64::MAX / 2 } else {
48 (rate_pps as i64).max(1)
51 };
52
53 Self {
54 tokens_x1m: (burst as i64) * TOKEN_SCALE,
55 max_tokens_x1m: (burst as i64) * TOKEN_SCALE,
56 refill_per_us_x1m,
57 last_refill: Instant::now(),
58 rate_pps,
59 }
60 }
61
62 #[must_use]
64 pub fn unlimited() -> Self {
65 Self::new(0, u64::MAX / 2)
66 }
67
68 pub fn try_consume(&mut self) -> bool {
71 self.refill();
72 if self.tokens_x1m >= TOKEN_SCALE {
73 self.tokens_x1m -= TOKEN_SCALE;
74 true
75 } else {
76 false
77 }
78 }
79
80 pub fn try_consume_batch(&mut self, n: u64) -> u64 {
82 self.refill();
83 let available = (self.tokens_x1m / TOKEN_SCALE).max(0) as u64;
84 let consumed = available.min(n);
85 self.tokens_x1m -= (consumed as i64) * TOKEN_SCALE;
86 consumed
87 }
88
89 pub fn consume_blocking(&mut self) {
94 loop {
95 self.refill();
96 if self.tokens_x1m >= TOKEN_SCALE {
97 self.tokens_x1m -= TOKEN_SCALE;
98 return;
99 }
100 let deficit = TOKEN_SCALE - self.tokens_x1m;
101 if self.refill_per_us_x1m > 0 {
102 let wait_us = deficit / self.refill_per_us_x1m.max(1);
103 if wait_us > 100 {
104 std::thread::yield_now();
105 } else {
106 std::hint::spin_loop();
107 }
108 } else {
109 std::hint::spin_loop();
110 }
111 }
112 }
113
114 #[must_use]
116 pub fn rate_pps(&self) -> u64 {
117 self.rate_pps
118 }
119
120 #[must_use]
122 pub fn is_unlimited(&self) -> bool {
123 self.rate_pps == 0
124 }
125
126 pub fn set_rate_pps(&mut self, rate_pps: u64) {
130 self.rate_pps = rate_pps;
131 self.refill_per_us_x1m = if rate_pps == 0 {
132 i64::MAX / 2
133 } else {
134 (rate_pps as i64).max(1)
135 };
136 }
137
138 fn refill(&mut self) {
139 let now = Instant::now();
140 let elapsed_us = now.duration_since(self.last_refill).as_micros() as i64;
141 if elapsed_us > 0 {
142 let new_tokens = elapsed_us.saturating_mul(self.refill_per_us_x1m);
143 self.tokens_x1m = self
144 .tokens_x1m
145 .saturating_add(new_tokens)
146 .min(self.max_tokens_x1m);
147 self.last_refill = now;
148 }
149 }
150}
151
152pub struct AdaptiveRate {
154 current_pps: u64,
156 max_pps: u64,
158 min_pps: u64,
160 success_streak: u32,
162 drop_streak: u32,
164}
165
166impl AdaptiveRate {
167 #[must_use]
169 pub fn new(max_pps: u64) -> Self {
170 Self {
171 current_pps: max_pps / 2, max_pps,
173 min_pps: 1000, success_streak: 0,
175 drop_streak: 0,
176 }
177 }
178
179 pub fn report_success(&mut self) {
181 self.drop_streak = 0;
182 self.success_streak += 1;
183
184 if self.success_streak >= 10 {
186 self.current_pps = (self.current_pps + self.max_pps / 20).min(self.max_pps);
187 self.success_streak = 0;
188 }
189 }
190
191 pub fn report_drops(&mut self, _drop_count: u64) {
193 self.success_streak = 0;
194 self.drop_streak += 1;
195
196 self.current_pps = (self.current_pps * 3 / 4).max(self.min_pps);
198 }
199
200 #[must_use]
202 pub fn current_pps(&self) -> u64 {
203 self.current_pps
204 }
205}
206
207pub struct AdaptiveLoop {
225 rate: AdaptiveRate,
226 last_tx_packets: u64,
227 last_tx_drops: u64,
228 initialized: bool,
229}
230
231impl AdaptiveLoop {
232 #[must_use]
235 pub fn new(max_pps: u64) -> Self {
236 Self {
237 rate: AdaptiveRate::new(max_pps),
238 last_tx_packets: 0,
239 last_tx_drops: 0,
240 initialized: false,
241 }
242 }
243
244 pub fn tick(&mut self, tx_packets: u64, tx_drops: u64) -> u64 {
249 if !self.initialized {
250 self.last_tx_packets = tx_packets;
251 self.last_tx_drops = tx_drops;
252 self.initialized = true;
253 return self.rate.current_pps();
254 }
255 let drop_delta = tx_drops.saturating_sub(self.last_tx_drops);
256 let packet_delta = tx_packets.saturating_sub(self.last_tx_packets);
257 if drop_delta > 0 {
258 self.rate.report_drops(drop_delta);
259 } else if packet_delta > 0 {
260 self.rate.report_success();
261 }
262 self.last_tx_packets = tx_packets;
263 self.last_tx_drops = tx_drops;
264 self.rate.current_pps()
265 }
266
267 pub fn apply(&self, limiter: &mut RateLimiter) {
270 limiter.set_rate_pps(self.rate.current_pps());
271 }
272
273 #[must_use]
275 pub fn current_pps(&self) -> u64 {
276 self.rate.current_pps()
277 }
278}
279
280#[cfg(test)]
281mod tests {
282 use super::*;
283
284 #[test]
285 fn rate_limiter_unlimited_always_succeeds() {
286 let mut rl = RateLimiter::unlimited();
287 for _ in 0..10_000 {
288 assert!(rl.try_consume());
289 }
290 }
291
292 #[test]
293 fn rate_limiter_respects_burst() {
294 let mut rl = RateLimiter::new(1_000_000, 10);
295 for i in 0..10 {
297 assert!(rl.try_consume(), "failed at burst token {i}");
298 }
299 }
302
303 #[test]
304 fn rate_limiter_batch_consume() {
305 let mut rl = RateLimiter::new(10, 100);
311 let consumed = rl.try_consume_batch(50);
312 assert_eq!(consumed, 50);
313 let consumed2 = rl.try_consume_batch(100);
314 assert_eq!(consumed2, 50); }
316
317 #[test]
318 fn adaptive_rate_decreases_on_drops() {
319 let mut ar = AdaptiveRate::new(1_000_000);
320 let initial = ar.current_pps();
321 ar.report_drops(100);
322 assert!(ar.current_pps() < initial);
323 }
324
325 #[test]
326 fn adaptive_rate_increases_on_success() {
327 let mut ar = AdaptiveRate::new(1_000_000);
328 let initial = ar.current_pps();
329 for _ in 0..20 {
330 ar.report_success();
331 }
332 assert!(ar.current_pps() > initial);
333 }
334
335 #[test]
336 fn adaptive_rate_never_below_floor() {
337 let mut ar = AdaptiveRate::new(1_000_000);
338 for _ in 0..100 {
339 ar.report_drops(1000);
340 }
341 assert!(ar.current_pps() >= 1000);
342 }
343
344 #[test]
345 fn set_rate_pps_changes_refill() {
346 let mut r = RateLimiter::new(1_000, 100);
347 assert_eq!(r.rate_pps(), 1_000);
348 r.set_rate_pps(500);
349 assert_eq!(r.rate_pps(), 500);
350 r.set_rate_pps(0);
352 assert!(r.is_unlimited());
353 }
354
355 #[test]
356 fn adaptive_loop_initial_tick_is_baseline_only() {
357 let mut lo = AdaptiveLoop::new(1_000_000);
358 let initial = lo.current_pps();
359 let returned = lo.tick(0, 0);
361 assert_eq!(returned, initial);
362 }
363
364 #[test]
365 fn adaptive_loop_decreases_on_tx_drop_burst() {
366 let mut lo = AdaptiveLoop::new(1_000_000);
367 let before = lo.tick(0, 0); let after = lo.tick(1000, 50);
370 assert!(
371 after < before,
372 "expected rate to decrease: {after} < {before}"
373 );
374 }
375
376 #[test]
377 fn adaptive_loop_increases_after_clean_streak() {
378 let mut lo = AdaptiveLoop::new(1_000_000);
379 lo.tick(0, 0); let before = lo.current_pps();
381 for i in 1..=15 {
382 lo.tick(i * 100, 0);
384 }
385 assert!(
386 lo.current_pps() > before,
387 "expected rate to increase after streak: {} > {before}",
388 lo.current_pps()
389 );
390 }
391
392 #[test]
393 fn adaptive_loop_apply_propagates_to_limiter() {
394 let mut lo = AdaptiveLoop::new(1_000_000);
395 let mut limiter = RateLimiter::new(1_000_000, 1000);
396 lo.tick(0, 0);
397 lo.tick(1000, 100);
399 lo.apply(&mut limiter);
400 assert_eq!(limiter.rate_pps(), lo.current_pps());
401 }
402
403 #[test]
409 fn rate_limiter_actually_throttles_to_configured_rate() {
410 use std::time::{Duration, Instant};
411 const CONFIGURED_PPS: u64 = 10_000;
412 let mut rl = RateLimiter::new(CONFIGURED_PPS, 32);
415
416 while rl.try_consume() {}
419
420 let start = Instant::now();
421 let mut consumed: u64 = 0;
422 while start.elapsed() < Duration::from_millis(100) {
423 if rl.try_consume() {
424 consumed += 1;
425 } else {
426 std::hint::spin_loop();
427 }
428 }
429 let elapsed_ms = start.elapsed().as_millis().max(1) as u64;
430 let observed_pps = consumed.saturating_mul(1000) / elapsed_ms;
431
432 let upper_bound = CONFIGURED_PPS * 4;
435 assert!(
436 observed_pps <= upper_bound,
437 "RateLimiter configured at {CONFIGURED_PPS} pps achieved \
438 {observed_pps} pps (consumed {consumed} in {elapsed_ms}ms) — \
439 refill scaling regressed"
440 );
441 }
442
443 #[test]
444 fn adaptive_loop_converges_under_synthetic_loss_pattern() {
445 let mut lo = AdaptiveLoop::new(10_000_000);
447 let start = lo.current_pps();
448 for i in 1..=20 {
449 lo.tick(i * 1000, i * 500);
450 }
451 let final_pps = lo.current_pps();
452 assert!(
453 final_pps < start / 4,
454 "expected aggressive decrease: {final_pps} >= {} (start/4)",
455 start / 4
456 );
457 }
458}