1use std::sync::Mutex;
16use std::time::Duration;
17
18use crate::Result;
19
20#[derive(Debug, Clone)]
34pub struct AimdConfig {
35 pub initial_rate: f64,
36 pub min_rate: f64,
37 pub max_rate: f64,
38 pub decrease_factor: f64,
39 pub additive_increment: f64,
40 pub window_duration: Duration,
41 pub throttle_threshold: f64,
42}
43
44impl Default for AimdConfig {
45 fn default() -> Self {
46 Self {
47 initial_rate: 2000.0,
48 min_rate: 1.0,
49 max_rate: 10000.0,
50 decrease_factor: 0.5,
51 additive_increment: 300.0,
52 window_duration: Duration::from_secs(1),
53 throttle_threshold: 0.0,
54 }
55 }
56}
57
58impl AimdConfig {
59 pub fn with_initial_rate(self, initial_rate: f64) -> Self {
60 Self {
61 initial_rate,
62 ..self
63 }
64 }
65
66 pub fn with_min_rate(self, min_rate: f64) -> Self {
67 Self { min_rate, ..self }
68 }
69
70 pub fn with_max_rate(self, max_rate: f64) -> Self {
71 Self { max_rate, ..self }
72 }
73
74 pub fn with_decrease_factor(self, decrease_factor: f64) -> Self {
75 Self {
76 decrease_factor,
77 ..self
78 }
79 }
80
81 pub fn with_additive_increment(self, additive_increment: f64) -> Self {
82 Self {
83 additive_increment,
84 ..self
85 }
86 }
87
88 pub fn with_window_duration(self, window_duration: Duration) -> Self {
89 Self {
90 window_duration,
91 ..self
92 }
93 }
94
95 pub fn with_throttle_threshold(self, throttle_threshold: f64) -> Self {
96 Self {
97 throttle_threshold,
98 ..self
99 }
100 }
101
102 pub fn validate(&self) -> Result<()> {
104 for (name, value) in [
111 ("initial_rate", self.initial_rate),
112 ("min_rate", self.min_rate),
113 ("max_rate", self.max_rate),
114 ("decrease_factor", self.decrease_factor),
115 ("additive_increment", self.additive_increment),
116 ("throttle_threshold", self.throttle_threshold),
117 ] {
118 if !value.is_finite() {
119 return Err(crate::Error::invalid_input(format!(
120 "{name} must be finite, got {value}"
121 )));
122 }
123 }
124 if self.initial_rate <= 0.0 {
125 return Err(crate::Error::invalid_input(format!(
126 "initial_rate must be positive, got {}",
127 self.initial_rate
128 )));
129 }
130 if self.min_rate <= 0.0 {
131 return Err(crate::Error::invalid_input(format!(
132 "min_rate must be positive, got {}",
133 self.min_rate
134 )));
135 }
136 if self.max_rate < 0.0 {
137 return Err(crate::Error::invalid_input(format!(
138 "max_rate must be non-negative (0.0 = no ceiling), got {}",
139 self.max_rate
140 )));
141 }
142 if self.max_rate > 0.0 && self.min_rate > self.max_rate {
143 return Err(crate::Error::invalid_input(format!(
144 "min_rate ({}) must not exceed max_rate ({})",
145 self.min_rate, self.max_rate
146 )));
147 }
148 if self.decrease_factor <= 0.0 || self.decrease_factor >= 1.0 {
149 return Err(crate::Error::invalid_input(format!(
150 "decrease_factor must be in (0, 1), got {}",
151 self.decrease_factor
152 )));
153 }
154 if self.additive_increment <= 0.0 {
155 return Err(crate::Error::invalid_input(format!(
156 "additive_increment must be positive, got {}",
157 self.additive_increment
158 )));
159 }
160 if self.window_duration.is_zero() {
161 return Err(crate::Error::invalid_input(
162 "window_duration must be non-zero",
163 ));
164 }
165 if !(0.0..=1.0).contains(&self.throttle_threshold) {
166 return Err(crate::Error::invalid_input(format!(
167 "throttle_threshold must be in [0.0, 1.0], got {}",
168 self.throttle_threshold
169 )));
170 }
171 if self.max_rate > 0.0 && self.initial_rate > self.max_rate {
172 return Err(crate::Error::invalid_input(format!(
173 "initial_rate ({}) must not exceed max_rate ({})",
174 self.initial_rate, self.max_rate
175 )));
176 }
177 if self.initial_rate < self.min_rate {
178 return Err(crate::Error::invalid_input(format!(
179 "initial_rate ({}) must not be below min_rate ({})",
180 self.initial_rate, self.min_rate
181 )));
182 }
183 Ok(())
184 }
185}
186
187#[derive(Debug, Clone, Copy, PartialEq, Eq)]
192pub enum RequestOutcome {
193 Success,
194 Throttled,
195}
196
197struct AimdState {
198 rate: f64,
199 window_start: std::time::Instant,
200 success_count: u64,
201 throttle_count: u64,
202}
203
204pub struct AimdController {
209 config: AimdConfig,
210 state: Mutex<AimdState>,
211}
212
213impl std::fmt::Debug for AimdController {
214 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
215 f.debug_struct("AimdController")
216 .field("config", &self.config)
217 .field("rate", &self.current_rate())
218 .finish()
219 }
220}
221
222impl AimdController {
223 pub fn new(config: AimdConfig) -> Result<Self> {
225 config.validate()?;
226 let rate = config.initial_rate;
227 Ok(Self {
228 config,
229 state: Mutex::new(AimdState {
230 rate,
231 window_start: std::time::Instant::now(),
232 success_count: 0,
233 throttle_count: 0,
234 }),
235 })
236 }
237
238 pub fn record_outcome(&self, outcome: RequestOutcome) -> f64 {
243 let mut state = self.state.lock().unwrap();
244 self.record_outcome_inner(&mut state, outcome, std::time::Instant::now())
245 }
246
247 fn record_outcome_inner(
248 &self,
249 state: &mut AimdState,
250 outcome: RequestOutcome,
251 now: std::time::Instant,
252 ) -> f64 {
253 let elapsed = now.duration_since(state.window_start);
255 if elapsed >= self.config.window_duration {
256 let total = state.success_count + state.throttle_count;
257 if total > 0 {
258 let throttle_ratio = state.throttle_count as f64 / total as f64;
259 if throttle_ratio > self.config.throttle_threshold {
260 state.rate =
262 (state.rate * self.config.decrease_factor).max(self.config.min_rate);
263 } else {
264 state.rate += self.config.additive_increment;
266 if self.config.max_rate > 0.0 {
267 state.rate = state.rate.min(self.config.max_rate);
268 }
269 }
270 }
271 state.window_start = now;
273 state.success_count = 0;
274 state.throttle_count = 0;
275 }
276
277 match outcome {
279 RequestOutcome::Success => state.success_count += 1,
280 RequestOutcome::Throttled => state.throttle_count += 1,
281 }
282
283 state.rate
284 }
285
286 pub fn current_rate(&self) -> f64 {
288 self.state.lock().unwrap().rate
289 }
290}
291
292#[cfg(test)]
293mod tests {
294 use super::*;
295 use rstest::rstest;
296
297 #[rstest]
298 #[case::zero_initial_rate(
299 AimdConfig::default().with_initial_rate(0.0),
300 "initial_rate must be positive"
301 )]
302 #[case::negative_min_rate(
303 AimdConfig::default().with_min_rate(-1.0),
304 "min_rate must be positive"
305 )]
306 #[case::negative_max_rate(
307 AimdConfig::default().with_max_rate(-1.0),
308 "max_rate must be non-negative"
309 )]
310 #[case::min_exceeds_max(
311 AimdConfig::default().with_min_rate(100.0).with_max_rate(10.0),
312 "min_rate (100) must not exceed max_rate (10)"
313 )]
314 #[case::decrease_factor_zero(
315 AimdConfig::default().with_decrease_factor(0.0),
316 "decrease_factor must be in (0, 1)"
317 )]
318 #[case::decrease_factor_one(
319 AimdConfig::default().with_decrease_factor(1.0),
320 "decrease_factor must be in (0, 1)"
321 )]
322 #[case::decrease_factor_over_one(
323 AimdConfig::default().with_decrease_factor(1.5),
324 "decrease_factor must be in (0, 1)"
325 )]
326 #[case::zero_additive_increment(
327 AimdConfig::default().with_additive_increment(0.0),
328 "additive_increment must be positive"
329 )]
330 #[case::zero_window_duration(
331 AimdConfig::default().with_window_duration(Duration::ZERO),
332 "window_duration must be non-zero"
333 )]
334 #[case::threshold_over_one(
335 AimdConfig::default().with_throttle_threshold(1.1),
336 "throttle_threshold must be in [0.0, 1.0]"
337 )]
338 #[case::threshold_negative(
339 AimdConfig::default().with_throttle_threshold(-0.1),
340 "throttle_threshold must be in [0.0, 1.0]"
341 )]
342 #[case::initial_exceeds_max(
343 AimdConfig::default().with_initial_rate(11000.0),
344 "initial_rate (11000) must not exceed max_rate (10000)"
345 )]
346 #[case::initial_below_min(
347 AimdConfig::default().with_initial_rate(0.5).with_min_rate(1.0),
348 "initial_rate (0.5) must not be below min_rate (1)"
349 )]
350 #[case::nan_initial_rate(
351 AimdConfig::default().with_initial_rate(f64::NAN),
352 "initial_rate must be finite"
353 )]
354 #[case::inf_initial_rate(
355 AimdConfig::default().with_initial_rate(f64::INFINITY),
356 "initial_rate must be finite"
357 )]
358 #[case::nan_min_rate(
359 AimdConfig::default().with_min_rate(f64::NAN),
360 "min_rate must be finite"
361 )]
362 #[case::nan_max_rate(
363 AimdConfig::default().with_max_rate(f64::NAN),
364 "max_rate must be finite"
365 )]
366 #[case::inf_max_rate(
367 AimdConfig::default().with_max_rate(f64::INFINITY),
368 "max_rate must be finite"
369 )]
370 #[case::nan_decrease_factor(
371 AimdConfig::default().with_decrease_factor(f64::NAN),
372 "decrease_factor must be finite"
373 )]
374 #[case::nan_additive_increment(
375 AimdConfig::default().with_additive_increment(f64::NAN),
376 "additive_increment must be finite"
377 )]
378 #[case::nan_throttle_threshold(
379 AimdConfig::default().with_throttle_threshold(f64::NAN),
380 "throttle_threshold must be finite"
381 )]
382 fn test_config_validation_rejects_invalid(
383 #[case] config: AimdConfig,
384 #[case] expected_msg: &str,
385 ) {
386 let err = config.validate().unwrap_err();
387 assert!(
388 matches!(&err, crate::Error::InvalidInput { .. }),
389 "expected InvalidInput, got: {err:?}"
390 );
391 let msg = err.to_string();
392 assert!(
393 msg.contains(expected_msg),
394 "Expected error containing '{}', got: {}",
395 expected_msg,
396 msg
397 );
398 }
399
400 #[test]
401 fn test_default_config_ramps_to_ceiling_and_backs_off() {
402 let controller = AimdController::new(AimdConfig::default()).unwrap();
403 assert_eq!(controller.current_rate(), 2000.0);
404
405 let mut state = controller.state.lock().unwrap();
406 let start = state.window_start;
407 controller.record_outcome_inner(&mut state, RequestOutcome::Success, start);
408 for seconds in 1..=28 {
409 controller.record_outcome_inner(
410 &mut state,
411 RequestOutcome::Success,
412 start + Duration::from_secs(seconds),
413 );
414 }
415 assert_eq!(state.rate, 10000.0);
416
417 controller.record_outcome_inner(
418 &mut state,
419 RequestOutcome::Throttled,
420 start + Duration::from_secs(28),
421 );
422 controller.record_outcome_inner(
423 &mut state,
424 RequestOutcome::Success,
425 start + Duration::from_secs(29),
426 );
427 assert_eq!(state.rate, 5000.0);
428 }
429
430 #[test]
431 fn test_no_ceiling_config_is_valid() {
432 AimdConfig::default().with_max_rate(0.0).validate().unwrap();
433 }
434
435 #[test]
436 fn test_additive_increase_on_success_window() {
437 let config = AimdConfig::default()
438 .with_initial_rate(100.0)
439 .with_additive_increment(10.0)
440 .with_window_duration(Duration::from_millis(100));
441 let controller = AimdController::new(config).unwrap();
442
443 let start = std::time::Instant::now();
445 {
446 let mut state = controller.state.lock().unwrap();
447 controller.record_outcome_inner(&mut state, RequestOutcome::Success, start);
448 }
449
450 let after_window = start + Duration::from_millis(150);
452 {
453 let mut state = controller.state.lock().unwrap();
454 controller.record_outcome_inner(&mut state, RequestOutcome::Success, after_window);
455 }
456
457 assert_eq!(controller.current_rate(), 110.0);
459 }
460
461 #[test]
462 fn test_multiplicative_decrease_on_throttle_window() {
463 let config = AimdConfig::default()
464 .with_initial_rate(100.0)
465 .with_decrease_factor(0.5)
466 .with_window_duration(Duration::from_millis(100));
467 let controller = AimdController::new(config).unwrap();
468
469 let start = std::time::Instant::now();
470 {
471 let mut state = controller.state.lock().unwrap();
472 controller.record_outcome_inner(&mut state, RequestOutcome::Throttled, start);
473 }
474
475 let after_window = start + Duration::from_millis(150);
477 {
478 let mut state = controller.state.lock().unwrap();
479 controller.record_outcome_inner(&mut state, RequestOutcome::Success, after_window);
480 }
481
482 assert_eq!(controller.current_rate(), 50.0);
483 }
484
485 #[test]
486 fn test_floor_enforcement() {
487 let config = AimdConfig::default()
488 .with_initial_rate(2.0)
489 .with_min_rate(1.0)
490 .with_decrease_factor(0.5)
491 .with_window_duration(Duration::from_millis(100));
492 let controller = AimdController::new(config).unwrap();
493
494 let start = std::time::Instant::now();
495 {
496 let mut state = controller.state.lock().unwrap();
497 controller.record_outcome_inner(&mut state, RequestOutcome::Throttled, start);
498 }
499
500 let t1 = start + Duration::from_millis(150);
502 {
503 let mut state = controller.state.lock().unwrap();
504 controller.record_outcome_inner(&mut state, RequestOutcome::Throttled, t1);
505 }
506 assert_eq!(controller.current_rate(), 1.0);
507
508 let t2 = t1 + Duration::from_millis(150);
510 {
511 let mut state = controller.state.lock().unwrap();
512 controller.record_outcome_inner(&mut state, RequestOutcome::Success, t2);
513 }
514 assert_eq!(controller.current_rate(), 1.0);
515 }
516
517 #[test]
518 fn test_ceiling_enforcement() {
519 let config = AimdConfig::default()
520 .with_initial_rate(4990.0)
521 .with_max_rate(5000.0)
522 .with_additive_increment(20.0)
523 .with_window_duration(Duration::from_millis(100));
524 let controller = AimdController::new(config).unwrap();
525
526 let start = std::time::Instant::now();
527 {
528 let mut state = controller.state.lock().unwrap();
529 controller.record_outcome_inner(&mut state, RequestOutcome::Success, start);
530 }
531
532 let t1 = start + Duration::from_millis(150);
533 {
534 let mut state = controller.state.lock().unwrap();
535 controller.record_outcome_inner(&mut state, RequestOutcome::Success, t1);
536 }
537 assert_eq!(controller.current_rate(), 5000.0);
539 }
540
541 #[test]
542 fn test_no_ceiling_allows_unbounded_growth() {
543 let config = AimdConfig::default()
544 .with_initial_rate(100.0)
545 .with_max_rate(0.0)
546 .with_additive_increment(50.0)
547 .with_window_duration(Duration::from_millis(100));
548 let controller = AimdController::new(config).unwrap();
549
550 let start = std::time::Instant::now();
551 let mut t = start;
552
553 for _ in 0..5 {
554 {
555 let mut state = controller.state.lock().unwrap();
556 controller.record_outcome_inner(&mut state, RequestOutcome::Success, t);
557 }
558 t += Duration::from_millis(150);
559 }
560
561 {
563 let mut state = controller.state.lock().unwrap();
564 controller.record_outcome_inner(&mut state, RequestOutcome::Success, t);
565 }
566
567 assert_eq!(controller.current_rate(), 350.0);
569 }
570
571 #[test]
572 fn test_empty_window_no_adjustment() {
573 let config = AimdConfig::default()
574 .with_initial_rate(100.0)
575 .with_window_duration(Duration::from_millis(100));
576 let controller = AimdController::new(config).unwrap();
577
578 let start = std::time::Instant::now();
580 let after = start + Duration::from_millis(150);
581 {
582 let mut state = controller.state.lock().unwrap();
583 controller.record_outcome_inner(&mut state, RequestOutcome::Success, after);
585 }
586 assert_eq!(controller.current_rate(), 100.0);
588 }
589
590 #[test]
591 fn test_throttle_threshold_filtering() {
592 let config = AimdConfig::default()
594 .with_initial_rate(100.0)
595 .with_throttle_threshold(0.5)
596 .with_additive_increment(10.0)
597 .with_window_duration(Duration::from_millis(100));
598 let controller = AimdController::new(config).unwrap();
599
600 let start = std::time::Instant::now();
601 {
602 let mut state = controller.state.lock().unwrap();
603 controller.record_outcome_inner(&mut state, RequestOutcome::Success, start);
605 controller.record_outcome_inner(&mut state, RequestOutcome::Success, start);
606 controller.record_outcome_inner(&mut state, RequestOutcome::Throttled, start);
607 }
608
609 let t1 = start + Duration::from_millis(150);
611 {
612 let mut state = controller.state.lock().unwrap();
613 controller.record_outcome_inner(&mut state, RequestOutcome::Success, t1);
614 }
615
616 assert_eq!(controller.current_rate(), 110.0);
618 }
619
620 #[test]
621 fn test_throttle_threshold_triggers_decrease() {
622 let config = AimdConfig::default()
624 .with_initial_rate(100.0)
625 .with_throttle_threshold(0.5)
626 .with_decrease_factor(0.5)
627 .with_window_duration(Duration::from_millis(100));
628 let controller = AimdController::new(config).unwrap();
629
630 let start = std::time::Instant::now();
631 {
632 let mut state = controller.state.lock().unwrap();
633 controller.record_outcome_inner(&mut state, RequestOutcome::Success, start);
635 controller.record_outcome_inner(&mut state, RequestOutcome::Throttled, start);
636 controller.record_outcome_inner(&mut state, RequestOutcome::Throttled, start);
637 }
638
639 let t1 = start + Duration::from_millis(150);
640 {
641 let mut state = controller.state.lock().unwrap();
642 controller.record_outcome_inner(&mut state, RequestOutcome::Success, t1);
643 }
644
645 assert_eq!(controller.current_rate(), 50.0);
646 }
647
648 #[test]
649 fn test_recovery_after_decrease() {
650 let config = AimdConfig::default()
651 .with_initial_rate(100.0)
652 .with_decrease_factor(0.5)
653 .with_additive_increment(10.0)
654 .with_window_duration(Duration::from_millis(100));
655 let controller = AimdController::new(config).unwrap();
656
657 let start = std::time::Instant::now();
658
659 {
661 let mut state = controller.state.lock().unwrap();
662 controller.record_outcome_inner(&mut state, RequestOutcome::Throttled, start);
663 }
664 let t1 = start + Duration::from_millis(150);
665
666 {
668 let mut state = controller.state.lock().unwrap();
669 controller.record_outcome_inner(&mut state, RequestOutcome::Success, t1);
670 }
671 let t2 = t1 + Duration::from_millis(150);
672
673 {
675 let mut state = controller.state.lock().unwrap();
676 controller.record_outcome_inner(&mut state, RequestOutcome::Success, t2);
677 }
678 let t3 = t2 + Duration::from_millis(150);
679
680 {
682 let mut state = controller.state.lock().unwrap();
683 controller.record_outcome_inner(&mut state, RequestOutcome::Success, t3);
684 }
685
686 assert_eq!(controller.current_rate(), 70.0);
687 }
688
689 #[test]
690 fn test_within_window_no_adjustment() {
691 let config = AimdConfig::default()
692 .with_initial_rate(100.0)
693 .with_window_duration(Duration::from_secs(10));
694 let controller = AimdController::new(config).unwrap();
695
696 for _ in 0..100 {
698 controller.record_outcome(RequestOutcome::Throttled);
699 }
700
701 assert_eq!(controller.current_rate(), 100.0);
703 }
704}