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: 5000.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(6000.0),
344 "initial_rate (6000) must not exceed max_rate (5000)"
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_is_valid() {
402 AimdConfig::default().validate().unwrap();
403 }
404
405 #[test]
406 fn test_no_ceiling_config_is_valid() {
407 AimdConfig::default().with_max_rate(0.0).validate().unwrap();
408 }
409
410 #[test]
411 fn test_additive_increase_on_success_window() {
412 let config = AimdConfig::default()
413 .with_initial_rate(100.0)
414 .with_additive_increment(10.0)
415 .with_window_duration(Duration::from_millis(100));
416 let controller = AimdController::new(config).unwrap();
417
418 let start = std::time::Instant::now();
420 {
421 let mut state = controller.state.lock().unwrap();
422 controller.record_outcome_inner(&mut state, RequestOutcome::Success, start);
423 }
424
425 let after_window = start + Duration::from_millis(150);
427 {
428 let mut state = controller.state.lock().unwrap();
429 controller.record_outcome_inner(&mut state, RequestOutcome::Success, after_window);
430 }
431
432 assert_eq!(controller.current_rate(), 110.0);
434 }
435
436 #[test]
437 fn test_multiplicative_decrease_on_throttle_window() {
438 let config = AimdConfig::default()
439 .with_initial_rate(100.0)
440 .with_decrease_factor(0.5)
441 .with_window_duration(Duration::from_millis(100));
442 let controller = AimdController::new(config).unwrap();
443
444 let start = std::time::Instant::now();
445 {
446 let mut state = controller.state.lock().unwrap();
447 controller.record_outcome_inner(&mut state, RequestOutcome::Throttled, 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(), 50.0);
458 }
459
460 #[test]
461 fn test_floor_enforcement() {
462 let config = AimdConfig::default()
463 .with_initial_rate(2.0)
464 .with_min_rate(1.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 t1 = start + Duration::from_millis(150);
477 {
478 let mut state = controller.state.lock().unwrap();
479 controller.record_outcome_inner(&mut state, RequestOutcome::Throttled, t1);
480 }
481 assert_eq!(controller.current_rate(), 1.0);
482
483 let t2 = t1 + Duration::from_millis(150);
485 {
486 let mut state = controller.state.lock().unwrap();
487 controller.record_outcome_inner(&mut state, RequestOutcome::Success, t2);
488 }
489 assert_eq!(controller.current_rate(), 1.0);
490 }
491
492 #[test]
493 fn test_ceiling_enforcement() {
494 let config = AimdConfig::default()
495 .with_initial_rate(4990.0)
496 .with_max_rate(5000.0)
497 .with_additive_increment(20.0)
498 .with_window_duration(Duration::from_millis(100));
499 let controller = AimdController::new(config).unwrap();
500
501 let start = std::time::Instant::now();
502 {
503 let mut state = controller.state.lock().unwrap();
504 controller.record_outcome_inner(&mut state, RequestOutcome::Success, start);
505 }
506
507 let t1 = start + Duration::from_millis(150);
508 {
509 let mut state = controller.state.lock().unwrap();
510 controller.record_outcome_inner(&mut state, RequestOutcome::Success, t1);
511 }
512 assert_eq!(controller.current_rate(), 5000.0);
514 }
515
516 #[test]
517 fn test_no_ceiling_allows_unbounded_growth() {
518 let config = AimdConfig::default()
519 .with_initial_rate(100.0)
520 .with_max_rate(0.0)
521 .with_additive_increment(50.0)
522 .with_window_duration(Duration::from_millis(100));
523 let controller = AimdController::new(config).unwrap();
524
525 let start = std::time::Instant::now();
526 let mut t = start;
527
528 for _ in 0..5 {
529 {
530 let mut state = controller.state.lock().unwrap();
531 controller.record_outcome_inner(&mut state, RequestOutcome::Success, t);
532 }
533 t += Duration::from_millis(150);
534 }
535
536 {
538 let mut state = controller.state.lock().unwrap();
539 controller.record_outcome_inner(&mut state, RequestOutcome::Success, t);
540 }
541
542 assert_eq!(controller.current_rate(), 350.0);
544 }
545
546 #[test]
547 fn test_empty_window_no_adjustment() {
548 let config = AimdConfig::default()
549 .with_initial_rate(100.0)
550 .with_window_duration(Duration::from_millis(100));
551 let controller = AimdController::new(config).unwrap();
552
553 let start = std::time::Instant::now();
555 let after = start + Duration::from_millis(150);
556 {
557 let mut state = controller.state.lock().unwrap();
558 controller.record_outcome_inner(&mut state, RequestOutcome::Success, after);
560 }
561 assert_eq!(controller.current_rate(), 100.0);
563 }
564
565 #[test]
566 fn test_throttle_threshold_filtering() {
567 let config = AimdConfig::default()
569 .with_initial_rate(100.0)
570 .with_throttle_threshold(0.5)
571 .with_additive_increment(10.0)
572 .with_window_duration(Duration::from_millis(100));
573 let controller = AimdController::new(config).unwrap();
574
575 let start = std::time::Instant::now();
576 {
577 let mut state = controller.state.lock().unwrap();
578 controller.record_outcome_inner(&mut state, RequestOutcome::Success, start);
580 controller.record_outcome_inner(&mut state, RequestOutcome::Success, start);
581 controller.record_outcome_inner(&mut state, RequestOutcome::Throttled, start);
582 }
583
584 let t1 = start + Duration::from_millis(150);
586 {
587 let mut state = controller.state.lock().unwrap();
588 controller.record_outcome_inner(&mut state, RequestOutcome::Success, t1);
589 }
590
591 assert_eq!(controller.current_rate(), 110.0);
593 }
594
595 #[test]
596 fn test_throttle_threshold_triggers_decrease() {
597 let config = AimdConfig::default()
599 .with_initial_rate(100.0)
600 .with_throttle_threshold(0.5)
601 .with_decrease_factor(0.5)
602 .with_window_duration(Duration::from_millis(100));
603 let controller = AimdController::new(config).unwrap();
604
605 let start = std::time::Instant::now();
606 {
607 let mut state = controller.state.lock().unwrap();
608 controller.record_outcome_inner(&mut state, RequestOutcome::Success, start);
610 controller.record_outcome_inner(&mut state, RequestOutcome::Throttled, start);
611 controller.record_outcome_inner(&mut state, RequestOutcome::Throttled, start);
612 }
613
614 let t1 = start + Duration::from_millis(150);
615 {
616 let mut state = controller.state.lock().unwrap();
617 controller.record_outcome_inner(&mut state, RequestOutcome::Success, t1);
618 }
619
620 assert_eq!(controller.current_rate(), 50.0);
621 }
622
623 #[test]
624 fn test_recovery_after_decrease() {
625 let config = AimdConfig::default()
626 .with_initial_rate(100.0)
627 .with_decrease_factor(0.5)
628 .with_additive_increment(10.0)
629 .with_window_duration(Duration::from_millis(100));
630 let controller = AimdController::new(config).unwrap();
631
632 let start = std::time::Instant::now();
633
634 {
636 let mut state = controller.state.lock().unwrap();
637 controller.record_outcome_inner(&mut state, RequestOutcome::Throttled, start);
638 }
639 let t1 = start + Duration::from_millis(150);
640
641 {
643 let mut state = controller.state.lock().unwrap();
644 controller.record_outcome_inner(&mut state, RequestOutcome::Success, t1);
645 }
646 let t2 = t1 + Duration::from_millis(150);
647
648 {
650 let mut state = controller.state.lock().unwrap();
651 controller.record_outcome_inner(&mut state, RequestOutcome::Success, t2);
652 }
653 let t3 = t2 + Duration::from_millis(150);
654
655 {
657 let mut state = controller.state.lock().unwrap();
658 controller.record_outcome_inner(&mut state, RequestOutcome::Success, t3);
659 }
660
661 assert_eq!(controller.current_rate(), 70.0);
662 }
663
664 #[test]
665 fn test_within_window_no_adjustment() {
666 let config = AimdConfig::default()
667 .with_initial_rate(100.0)
668 .with_window_duration(Duration::from_secs(10));
669 let controller = AimdController::new(config).unwrap();
670
671 for _ in 0..100 {
673 controller.record_outcome(RequestOutcome::Throttled);
674 }
675
676 assert_eq!(controller.current_rate(), 100.0);
678 }
679}