1use std::time::Instant;
8
9pub struct RateLimiter {
14 tokens_x1000: i64,
16 max_tokens_x1000: i64,
18 refill_per_us_x1000: i64,
20 last_refill: Instant,
22 rate_pps: u64,
24}
25
26impl RateLimiter {
27 #[must_use]
32 pub fn new(rate_pps: u64, burst: u64) -> Self {
33 let burst = burst.max(1).min(i64::MAX as u64 / 1000);
34 let refill_per_us_x1000 = if rate_pps == 0 {
35 i64::MAX / 2 } else {
37 (rate_pps as i64).max(1) };
41
42 Self {
43 tokens_x1000: (burst as i64) * 1000,
44 max_tokens_x1000: (burst as i64) * 1000,
45 refill_per_us_x1000,
46 last_refill: Instant::now(),
47 rate_pps,
48 }
49 }
50
51 #[must_use]
53 pub fn unlimited() -> Self {
54 Self::new(0, u64::MAX / 2)
55 }
56
57 pub fn try_consume(&mut self) -> bool {
60 self.refill();
61 if self.tokens_x1000 >= 1000 {
62 self.tokens_x1000 -= 1000;
63 true
64 } else {
65 false
66 }
67 }
68
69 pub fn try_consume_batch(&mut self, n: u64) -> u64 {
71 self.refill();
72 let available = (self.tokens_x1000 / 1000).max(0) as u64;
73 let consumed = available.min(n);
74 self.tokens_x1000 -= (consumed as i64) * 1000;
75 consumed
76 }
77
78 pub fn consume_blocking(&mut self) {
83 loop {
84 self.refill();
85 if self.tokens_x1000 >= 1000 {
86 self.tokens_x1000 -= 1000;
87 return;
88 }
89 let deficit = 1000 - self.tokens_x1000;
91 if self.refill_per_us_x1000 > 0 {
92 let wait_us = deficit / self.refill_per_us_x1000.max(1);
93 if wait_us > 100 {
94 std::thread::yield_now();
95 } else {
96 std::hint::spin_loop();
97 }
98 } else {
99 std::hint::spin_loop();
100 }
101 }
102 }
103
104 #[must_use]
106 pub fn rate_pps(&self) -> u64 {
107 self.rate_pps
108 }
109
110 #[must_use]
112 pub fn is_unlimited(&self) -> bool {
113 self.rate_pps == 0
114 }
115
116 pub fn set_rate_pps(&mut self, rate_pps: u64) {
120 self.rate_pps = rate_pps;
121 self.refill_per_us_x1000 = if rate_pps == 0 {
122 i64::MAX / 2
123 } else {
124 (rate_pps as i64).max(1)
125 };
126 }
127
128 fn refill(&mut self) {
129 let now = Instant::now();
130 let elapsed_us = now.duration_since(self.last_refill).as_micros() as i64;
131 if elapsed_us > 0 {
132 let new_tokens = elapsed_us.saturating_mul(self.refill_per_us_x1000);
133 self.tokens_x1000 = self
134 .tokens_x1000
135 .saturating_add(new_tokens)
136 .min(self.max_tokens_x1000);
137 self.last_refill = now;
138 }
139 }
140}
141
142pub struct AdaptiveRate {
144 current_pps: u64,
146 max_pps: u64,
148 min_pps: u64,
150 success_streak: u32,
152 drop_streak: u32,
154}
155
156impl AdaptiveRate {
157 #[must_use]
159 pub fn new(max_pps: u64) -> Self {
160 Self {
161 current_pps: max_pps / 2, max_pps,
163 min_pps: 1000, success_streak: 0,
165 drop_streak: 0,
166 }
167 }
168
169 pub fn report_success(&mut self) {
171 self.drop_streak = 0;
172 self.success_streak += 1;
173
174 if self.success_streak >= 10 {
176 self.current_pps = (self.current_pps + self.max_pps / 20).min(self.max_pps);
177 self.success_streak = 0;
178 }
179 }
180
181 pub fn report_drops(&mut self, _drop_count: u64) {
183 self.success_streak = 0;
184 self.drop_streak += 1;
185
186 self.current_pps = (self.current_pps * 3 / 4).max(self.min_pps);
188 }
189
190 #[must_use]
192 pub fn current_pps(&self) -> u64 {
193 self.current_pps
194 }
195}
196
197pub struct AdaptiveLoop {
215 rate: AdaptiveRate,
216 last_tx_packets: u64,
217 last_tx_drops: u64,
218 initialized: bool,
219}
220
221impl AdaptiveLoop {
222 #[must_use]
225 pub fn new(max_pps: u64) -> Self {
226 Self {
227 rate: AdaptiveRate::new(max_pps),
228 last_tx_packets: 0,
229 last_tx_drops: 0,
230 initialized: false,
231 }
232 }
233
234 pub fn tick(&mut self, tx_packets: u64, tx_drops: u64) -> u64 {
239 if !self.initialized {
240 self.last_tx_packets = tx_packets;
241 self.last_tx_drops = tx_drops;
242 self.initialized = true;
243 return self.rate.current_pps();
244 }
245 let drop_delta = tx_drops.saturating_sub(self.last_tx_drops);
246 let packet_delta = tx_packets.saturating_sub(self.last_tx_packets);
247 if drop_delta > 0 {
248 self.rate.report_drops(drop_delta);
249 } else if packet_delta > 0 {
250 self.rate.report_success();
251 }
252 self.last_tx_packets = tx_packets;
253 self.last_tx_drops = tx_drops;
254 self.rate.current_pps()
255 }
256
257 pub fn apply(&self, limiter: &mut RateLimiter) {
260 limiter.set_rate_pps(self.rate.current_pps());
261 }
262
263 #[must_use]
265 pub fn current_pps(&self) -> u64 {
266 self.rate.current_pps()
267 }
268}
269
270#[cfg(test)]
271mod tests {
272 use super::*;
273
274 #[test]
275 fn rate_limiter_unlimited_always_succeeds() {
276 let mut rl = RateLimiter::unlimited();
277 for _ in 0..10_000 {
278 assert!(rl.try_consume());
279 }
280 }
281
282 #[test]
283 fn rate_limiter_respects_burst() {
284 let mut rl = RateLimiter::new(1_000_000, 10);
285 for i in 0..10 {
287 assert!(rl.try_consume(), "failed at burst token {i}");
288 }
289 }
292
293 #[test]
294 fn rate_limiter_batch_consume() {
295 let mut rl = RateLimiter::new(10, 100);
301 let consumed = rl.try_consume_batch(50);
302 assert_eq!(consumed, 50);
303 let consumed2 = rl.try_consume_batch(100);
304 assert_eq!(consumed2, 50); }
306
307 #[test]
308 fn adaptive_rate_decreases_on_drops() {
309 let mut ar = AdaptiveRate::new(1_000_000);
310 let initial = ar.current_pps();
311 ar.report_drops(100);
312 assert!(ar.current_pps() < initial);
313 }
314
315 #[test]
316 fn adaptive_rate_increases_on_success() {
317 let mut ar = AdaptiveRate::new(1_000_000);
318 let initial = ar.current_pps();
319 for _ in 0..20 {
320 ar.report_success();
321 }
322 assert!(ar.current_pps() > initial);
323 }
324
325 #[test]
326 fn adaptive_rate_never_below_floor() {
327 let mut ar = AdaptiveRate::new(1_000_000);
328 for _ in 0..100 {
329 ar.report_drops(1000);
330 }
331 assert!(ar.current_pps() >= 1000);
332 }
333
334 #[test]
335 fn set_rate_pps_changes_refill() {
336 let mut r = RateLimiter::new(1_000, 100);
337 assert_eq!(r.rate_pps(), 1_000);
338 r.set_rate_pps(500);
339 assert_eq!(r.rate_pps(), 500);
340 r.set_rate_pps(0);
342 assert!(r.is_unlimited());
343 }
344
345 #[test]
346 fn adaptive_loop_initial_tick_is_baseline_only() {
347 let mut lo = AdaptiveLoop::new(1_000_000);
348 let initial = lo.current_pps();
349 let returned = lo.tick(0, 0);
351 assert_eq!(returned, initial);
352 }
353
354 #[test]
355 fn adaptive_loop_decreases_on_tx_drop_burst() {
356 let mut lo = AdaptiveLoop::new(1_000_000);
357 let before = lo.tick(0, 0); let after = lo.tick(1000, 50);
360 assert!(
361 after < before,
362 "expected rate to decrease: {after} < {before}"
363 );
364 }
365
366 #[test]
367 fn adaptive_loop_increases_after_clean_streak() {
368 let mut lo = AdaptiveLoop::new(1_000_000);
369 lo.tick(0, 0); let before = lo.current_pps();
371 for i in 1..=15 {
372 lo.tick(i * 100, 0);
374 }
375 assert!(
376 lo.current_pps() > before,
377 "expected rate to increase after streak: {} > {before}",
378 lo.current_pps()
379 );
380 }
381
382 #[test]
383 fn adaptive_loop_apply_propagates_to_limiter() {
384 let mut lo = AdaptiveLoop::new(1_000_000);
385 let mut limiter = RateLimiter::new(1_000_000, 1000);
386 lo.tick(0, 0);
387 lo.tick(1000, 100);
389 lo.apply(&mut limiter);
390 assert_eq!(limiter.rate_pps(), lo.current_pps());
391 }
392
393 #[test]
394 fn adaptive_loop_converges_under_synthetic_loss_pattern() {
395 let mut lo = AdaptiveLoop::new(10_000_000);
397 let start = lo.current_pps();
398 for i in 1..=20 {
399 lo.tick(i * 1000, i * 500);
400 }
401 let final_pps = lo.current_pps();
402 assert!(
403 final_pps < start / 4,
404 "expected aggressive decrease: {final_pps} >= {} (start/4)",
405 start / 4
406 );
407 }
408}