1#![allow(dead_code)]
10
11use crate::client::retries::RetryPartition;
12use std::sync::{Arc, Mutex};
13use std::time::Duration;
14use tracing::debug;
15
16#[non_exhaustive]
18#[derive(Clone, Debug, Hash, PartialEq, Eq)]
19pub struct ClientRateLimiterPartition {
20 retry_partition: RetryPartition,
21}
22
23impl ClientRateLimiterPartition {
24 pub fn new(retry_partition: RetryPartition) -> Self {
26 Self { retry_partition }
27 }
28}
29
30const RETRY_COST: f64 = 5.0;
31const RETRY_TIMEOUT_COST: f64 = RETRY_COST * 2.0;
32const INITIAL_REQUEST_COST: f64 = 1.0;
33
34const MIN_FILL_RATE: f64 = 0.5;
35const MIN_CAPACITY: f64 = 1.0;
36const SMOOTH: f64 = 0.8;
37const BETA: f64 = 0.7;
39const SCALE_CONSTANT: f64 = 0.4;
41
42#[derive(Clone, Debug)]
44pub struct ClientRateLimiter {
45 pub(crate) inner: Arc<Mutex<Inner>>,
46}
47
48#[derive(Debug)]
49pub(crate) struct Inner {
50 fill_rate: f64,
52 max_capacity: f64,
54 current_capacity: f64,
56 last_timestamp: Option<f64>,
58 enabled: bool,
62 measured_tx_rate: f64,
64 last_tx_rate_bucket: f64,
66 request_count: u64,
68 last_max_rate: f64,
70 time_of_last_throttle: f64,
72}
73
74pub(crate) enum RequestReason {
75 Retry,
76 RetryTimeout,
77 InitialRequest,
78}
79
80impl Default for ClientRateLimiter {
81 fn default() -> Self {
82 Self::builder().build()
83 }
84}
85
86impl ClientRateLimiter {
87 pub fn new(seconds_since_unix_epoch: f64) -> Self {
89 Self::builder()
90 .tokens_retrieved_per_second(MIN_FILL_RATE)
91 .time_of_last_throttle(seconds_since_unix_epoch)
92 .previous_time_bucket(seconds_since_unix_epoch.floor())
93 .build()
94 }
95
96 pub fn builder() -> ClientRateLimiterBuilder {
98 ClientRateLimiterBuilder::new()
99 }
100
101 pub(crate) fn acquire_permission_to_send_a_request(
102 &self,
103 seconds_since_unix_epoch: f64,
104 kind: RequestReason,
105 ) -> Result<(), Duration> {
106 let mut it = self.inner.lock().unwrap();
107
108 if !it.enabled {
109 return Ok(());
111 }
112 let amount = match kind {
113 RequestReason::Retry => RETRY_COST,
114 RequestReason::RetryTimeout => RETRY_TIMEOUT_COST,
115 RequestReason::InitialRequest => INITIAL_REQUEST_COST,
116 };
117
118 it.refill(seconds_since_unix_epoch);
119
120 if amount > it.current_capacity {
121 let sleep_time = (amount - it.current_capacity) / it.fill_rate;
122 let dur = Duration::from_secs_f64(sleep_time);
123 if dur.is_zero() {
124 it.current_capacity = (it.current_capacity - amount).max(0.0);
130 Ok(())
131 } else {
132 debug!(
133 amount,
134 it.current_capacity,
135 it.fill_rate,
136 sleep_time,
137 "client rate limiter delayed a request"
138 );
139 Err(dur)
141 }
142 } else {
143 it.current_capacity -= amount;
144 Ok(())
145 }
146 }
147
148 pub(crate) fn update_rate_limiter(
149 &self,
150 seconds_since_unix_epoch: f64,
151 is_throttling_error: bool,
152 ) {
153 let mut it = self.inner.lock().unwrap();
154 it.update_tokens_retrieved_per_second(seconds_since_unix_epoch);
155
156 let calculated_rate;
157 if is_throttling_error {
158 let rate_to_use = if it.enabled {
159 f64::min(it.measured_tx_rate, it.fill_rate)
160 } else {
161 it.measured_tx_rate
162 };
163
164 it.last_max_rate = rate_to_use;
166 it.calculate_time_window();
167 it.time_of_last_throttle = seconds_since_unix_epoch;
168 calculated_rate = cubic_throttle(rate_to_use);
169 it.enable_token_bucket();
170 } else {
171 it.calculate_time_window();
172 calculated_rate = it.cubic_success(seconds_since_unix_epoch);
173 }
174
175 let new_rate = f64::min(calculated_rate, 2.0 * it.measured_tx_rate);
176 it.update_bucket_refill_rate(seconds_since_unix_epoch, new_rate);
177 }
178}
179
180impl Inner {
181 fn refill(&mut self, seconds_since_unix_epoch: f64) {
182 if let Some(last_timestamp) = self.last_timestamp {
183 let fill_amount = (seconds_since_unix_epoch - last_timestamp) * self.fill_rate;
184 self.current_capacity =
185 f64::min(self.max_capacity, self.current_capacity + fill_amount);
186 debug!(
187 fill_amount,
188 self.current_capacity, self.max_capacity, "refilling client rate limiter tokens"
189 );
190 }
191 self.last_timestamp = Some(seconds_since_unix_epoch);
192 }
193
194 fn update_bucket_refill_rate(&mut self, seconds_since_unix_epoch: f64, new_fill_rate: f64) {
195 self.refill(seconds_since_unix_epoch);
197
198 self.fill_rate = f64::max(new_fill_rate, MIN_FILL_RATE);
199 self.max_capacity = f64::max(new_fill_rate, MIN_CAPACITY);
200
201 debug!(
202 fill_rate = self.fill_rate,
203 max_capacity = self.max_capacity,
204 current_capacity = self.current_capacity,
205 measured_tx_rate = self.measured_tx_rate,
206 "client rate limiter state has been updated"
207 );
208
209 self.current_capacity = f64::min(self.current_capacity, self.max_capacity);
211 }
212
213 fn enable_token_bucket(&mut self) {
214 if !self.enabled {
216 debug!("client rate limiting has been enabled");
217 }
218 self.enabled = true;
219 }
220
221 fn update_tokens_retrieved_per_second(&mut self, seconds_since_unix_epoch: f64) {
222 let next_time_bucket = (seconds_since_unix_epoch * 2.0).floor() / 2.0;
223 self.request_count += 1;
224
225 if next_time_bucket > self.last_tx_rate_bucket {
226 let current_rate =
227 self.request_count as f64 / (next_time_bucket - self.last_tx_rate_bucket);
228 self.measured_tx_rate = current_rate * SMOOTH + self.measured_tx_rate * (1.0 - SMOOTH);
229 self.request_count = 0;
230 self.last_tx_rate_bucket = next_time_bucket;
231 }
232 }
233
234 fn calculate_time_window(&self) -> f64 {
235 let base = (self.last_max_rate * (1.0 - BETA)) / SCALE_CONSTANT;
236 base.powf(1.0 / 3.0)
237 }
238
239 fn cubic_success(&self, seconds_since_unix_epoch: f64) -> f64 {
240 let dt =
241 seconds_since_unix_epoch - self.time_of_last_throttle - self.calculate_time_window();
242 (SCALE_CONSTANT * dt.powi(3)) + self.last_max_rate
243 }
244}
245
246fn cubic_throttle(rate_to_use: f64) -> f64 {
247 rate_to_use * BETA
248}
249
250#[derive(Clone, Debug, Default)]
252pub struct ClientRateLimiterBuilder {
253 token_refill_rate: Option<f64>,
255 maximum_bucket_capacity: Option<f64>,
257 current_bucket_capacity: Option<f64>,
259 time_of_last_refill: Option<f64>,
261 tokens_retrieved_per_second: Option<f64>,
263 previous_time_bucket: Option<f64>,
265 request_count: Option<u64>,
267 enable_throttling: Option<bool>,
269 tokens_retrieved_per_second_at_time_of_last_throttle: Option<f64>,
271 time_of_last_throttle: Option<f64>,
273}
274
275impl ClientRateLimiterBuilder {
276 pub fn new() -> Self {
278 ClientRateLimiterBuilder::default()
279 }
280 pub fn token_refill_rate(mut self, token_refill_rate: f64) -> Self {
282 self.set_token_refill_rate(Some(token_refill_rate));
283 self
284 }
285 pub fn set_token_refill_rate(&mut self, token_refill_rate: Option<f64>) -> &mut Self {
287 self.token_refill_rate = token_refill_rate;
288 self
289 }
290 pub fn maximum_bucket_capacity(mut self, maximum_bucket_capacity: f64) -> Self {
294 self.set_maximum_bucket_capacity(Some(maximum_bucket_capacity));
295 self
296 }
297 pub fn set_maximum_bucket_capacity(
301 &mut self,
302 maximum_bucket_capacity: Option<f64>,
303 ) -> &mut Self {
304 self.maximum_bucket_capacity = maximum_bucket_capacity;
305 self
306 }
307 pub fn current_bucket_capacity(mut self, current_bucket_capacity: f64) -> Self {
311 self.set_current_bucket_capacity(Some(current_bucket_capacity));
312 self
313 }
314 pub fn set_current_bucket_capacity(
318 &mut self,
319 current_bucket_capacity: Option<f64>,
320 ) -> &mut Self {
321 self.current_bucket_capacity = current_bucket_capacity;
322 self
323 }
324 fn time_of_last_refill(mut self, time_of_last_refill: f64) -> Self {
326 self.set_time_of_last_refill(Some(time_of_last_refill));
327 self
328 }
329 fn set_time_of_last_refill(&mut self, time_of_last_refill: Option<f64>) -> &mut Self {
331 self.time_of_last_refill = time_of_last_refill;
332 self
333 }
334 pub fn tokens_retrieved_per_second(mut self, tokens_retrieved_per_second: f64) -> Self {
336 self.set_tokens_retrieved_per_second(Some(tokens_retrieved_per_second));
337 self
338 }
339 pub fn set_tokens_retrieved_per_second(
341 &mut self,
342 tokens_retrieved_per_second: Option<f64>,
343 ) -> &mut Self {
344 self.tokens_retrieved_per_second = tokens_retrieved_per_second;
345 self
346 }
347 fn previous_time_bucket(mut self, previous_time_bucket: f64) -> Self {
349 self.set_previous_time_bucket(Some(previous_time_bucket));
350 self
351 }
352 fn set_previous_time_bucket(&mut self, previous_time_bucket: Option<f64>) -> &mut Self {
354 self.previous_time_bucket = previous_time_bucket;
355 self
356 }
357 fn request_count(mut self, request_count: u64) -> Self {
359 self.set_request_count(Some(request_count));
360 self
361 }
362 fn set_request_count(&mut self, request_count: Option<u64>) -> &mut Self {
364 self.request_count = request_count;
365 self
366 }
367 fn enable_throttling(mut self, enable_throttling: bool) -> Self {
369 self.set_enable_throttling(Some(enable_throttling));
370 self
371 }
372 fn set_enable_throttling(&mut self, enable_throttling: Option<bool>) -> &mut Self {
374 self.enable_throttling = enable_throttling;
375 self
376 }
377 fn tokens_retrieved_per_second_at_time_of_last_throttle(
379 mut self,
380 tokens_retrieved_per_second_at_time_of_last_throttle: f64,
381 ) -> Self {
382 self.set_tokens_retrieved_per_second_at_time_of_last_throttle(Some(
383 tokens_retrieved_per_second_at_time_of_last_throttle,
384 ));
385 self
386 }
387 fn set_tokens_retrieved_per_second_at_time_of_last_throttle(
389 &mut self,
390 tokens_retrieved_per_second_at_time_of_last_throttle: Option<f64>,
391 ) -> &mut Self {
392 self.tokens_retrieved_per_second_at_time_of_last_throttle =
393 tokens_retrieved_per_second_at_time_of_last_throttle;
394 self
395 }
396 fn time_of_last_throttle(mut self, time_of_last_throttle: f64) -> Self {
398 self.set_time_of_last_throttle(Some(time_of_last_throttle));
399 self
400 }
401 fn set_time_of_last_throttle(&mut self, time_of_last_throttle: Option<f64>) -> &mut Self {
403 self.time_of_last_throttle = time_of_last_throttle;
404 self
405 }
406 pub fn build(self) -> ClientRateLimiter {
408 ClientRateLimiter {
409 inner: Arc::new(Mutex::new(Inner {
410 fill_rate: self.token_refill_rate.unwrap_or_default(),
411 max_capacity: self.maximum_bucket_capacity.unwrap_or(f64::MAX),
412 current_capacity: self.current_bucket_capacity.unwrap_or_default(),
413 last_timestamp: self.time_of_last_refill,
414 enabled: self.enable_throttling.unwrap_or_default(),
415 measured_tx_rate: self.tokens_retrieved_per_second.unwrap_or_default(),
416 last_tx_rate_bucket: self.previous_time_bucket.unwrap_or_default(),
417 request_count: self.request_count.unwrap_or_default(),
418 last_max_rate: self
419 .tokens_retrieved_per_second_at_time_of_last_throttle
420 .unwrap_or_default(),
421 time_of_last_throttle: self.time_of_last_throttle.unwrap_or_default(),
422 })),
423 }
424 }
425}
426
427#[cfg(test)]
428mod tests {
429 use super::{cubic_throttle, ClientRateLimiter, INITIAL_REQUEST_COST};
430 use crate::client::retries::client_rate_limiter::RequestReason;
431 use approx::assert_relative_eq;
432 use aws_smithy_async::rt::sleep::AsyncSleep;
433 use aws_smithy_async::test_util::instant_time_and_sleep;
434 use std::time::{Duration, SystemTime};
435
436 const ONE_SECOND: Duration = Duration::from_secs(1);
437 const TWO_HUNDRED_MILLISECONDS: Duration = Duration::from_millis(200);
438
439 #[test]
440 fn should_match_beta_decrease() {
441 let new_rate = cubic_throttle(10.0);
442 assert_relative_eq!(new_rate, 7.0);
443
444 let rate_limiter = ClientRateLimiter::builder()
445 .tokens_retrieved_per_second_at_time_of_last_throttle(10.0)
446 .time_of_last_throttle(1.0)
447 .build();
448
449 rate_limiter.inner.lock().unwrap().calculate_time_window();
450 let new_rate = rate_limiter.inner.lock().unwrap().cubic_success(1.0);
451 assert_relative_eq!(new_rate, 7.0);
452 }
453
454 #[tokio::test]
455 async fn throttling_is_enabled_once_throttling_error_is_received() {
456 let rate_limiter = ClientRateLimiter::builder()
457 .previous_time_bucket(0.0)
458 .time_of_last_throttle(0.0)
459 .build();
460
461 assert!(
462 !rate_limiter.inner.lock().unwrap().enabled,
463 "rate_limiter should be disabled by default"
464 );
465 rate_limiter.update_rate_limiter(0.0, true);
466 assert!(
467 rate_limiter.inner.lock().unwrap().enabled,
468 "rate_limiter should be enabled after throttling error"
469 );
470 }
471
472 #[tokio::test]
473 async fn test_calculated_rate_with_successes() {
474 let rate_limiter = ClientRateLimiter::builder()
475 .time_of_last_throttle(5.0)
476 .tokens_retrieved_per_second_at_time_of_last_throttle(10.0)
477 .build();
478
479 struct Attempt {
480 seconds_since_unix_epoch: f64,
481 expected_calculated_rate: f64,
482 }
483
484 let attempts = [
485 Attempt {
486 seconds_since_unix_epoch: 5.0,
487 expected_calculated_rate: 7.0,
488 },
489 Attempt {
490 seconds_since_unix_epoch: 6.0,
491 expected_calculated_rate: 9.64893600966,
492 },
493 Attempt {
494 seconds_since_unix_epoch: 7.0,
495 expected_calculated_rate: 10.000030849917364,
496 },
497 Attempt {
498 seconds_since_unix_epoch: 8.0,
499 expected_calculated_rate: 10.453284520772092,
500 },
501 Attempt {
502 seconds_since_unix_epoch: 9.0,
503 expected_calculated_rate: 13.408697022224185,
504 },
505 Attempt {
506 seconds_since_unix_epoch: 10.0,
507 expected_calculated_rate: 21.26626835427364,
508 },
509 Attempt {
510 seconds_since_unix_epoch: 11.0,
511 expected_calculated_rate: 36.425998516920465,
512 },
513 ];
514
515 for attempt in attempts {
519 rate_limiter.inner.lock().unwrap().calculate_time_window();
520 let calculated_rate = rate_limiter
521 .inner
522 .lock()
523 .unwrap()
524 .cubic_success(attempt.seconds_since_unix_epoch);
525
526 assert_relative_eq!(attempt.expected_calculated_rate, calculated_rate);
527 }
528 }
529
530 #[tokio::test]
531 async fn test_calculated_rate_with_throttles() {
532 let rate_limiter = ClientRateLimiter::builder()
533 .tokens_retrieved_per_second_at_time_of_last_throttle(10.0)
534 .time_of_last_throttle(5.0)
535 .build();
536
537 struct Attempt {
538 throttled: bool,
539 seconds_since_unix_epoch: f64,
540 expected_calculated_rate: f64,
541 }
542
543 let attempts = [
544 Attempt {
545 throttled: false,
546 seconds_since_unix_epoch: 5.0,
547 expected_calculated_rate: 7.0,
548 },
549 Attempt {
550 throttled: false,
551 seconds_since_unix_epoch: 6.0,
552 expected_calculated_rate: 9.64893600966,
553 },
554 Attempt {
555 throttled: true,
556 seconds_since_unix_epoch: 7.0,
557 expected_calculated_rate: 6.754255206761999,
558 },
559 Attempt {
560 throttled: true,
561 seconds_since_unix_epoch: 8.0,
562 expected_calculated_rate: 4.727978644733399,
563 },
564 Attempt {
565 throttled: false,
566 seconds_since_unix_epoch: 9.0,
567 expected_calculated_rate: 4.670125557970046,
568 },
569 Attempt {
570 throttled: false,
571 seconds_since_unix_epoch: 10.0,
572 expected_calculated_rate: 4.770870456867401,
573 },
574 Attempt {
575 throttled: false,
576 seconds_since_unix_epoch: 11.0,
577 expected_calculated_rate: 6.011819748005445,
578 },
579 Attempt {
580 throttled: false,
581 seconds_since_unix_epoch: 12.0,
582 expected_calculated_rate: 10.792973431384178,
583 },
584 ];
585
586 let mut calculated_rate = 0.0;
590 for attempt in attempts {
591 let mut inner = rate_limiter.inner.lock().unwrap();
592 inner.calculate_time_window();
593 if attempt.throttled {
594 calculated_rate = cubic_throttle(calculated_rate);
595 inner.time_of_last_throttle = attempt.seconds_since_unix_epoch;
596 inner.last_max_rate = calculated_rate;
597 } else {
598 calculated_rate = inner.cubic_success(attempt.seconds_since_unix_epoch);
599 };
600
601 assert_relative_eq!(attempt.expected_calculated_rate, calculated_rate);
602 }
603 }
604
605 #[tokio::test]
606 async fn test_client_sending_rates() {
607 let (_, sleep_impl) = instant_time_and_sleep(SystemTime::UNIX_EPOCH);
608 let rate_limiter = ClientRateLimiter::builder().build();
609
610 struct Attempt {
611 throttled: bool,
612 seconds_since_unix_epoch: f64,
613 expected_tokens_retrieved_per_second: f64,
614 expected_token_refill_rate: f64,
615 }
616
617 let attempts = [
618 Attempt {
619 throttled: false,
620 seconds_since_unix_epoch: 0.2,
621 expected_tokens_retrieved_per_second: 0.000000,
622 expected_token_refill_rate: 0.500000,
623 },
624 Attempt {
625 throttled: false,
626 seconds_since_unix_epoch: 0.4,
627 expected_tokens_retrieved_per_second: 0.000000,
628 expected_token_refill_rate: 0.500000,
629 },
630 Attempt {
631 throttled: false,
632 seconds_since_unix_epoch: 0.6,
633 expected_tokens_retrieved_per_second: 4.800000000000001,
634 expected_token_refill_rate: 0.500000,
635 },
636 Attempt {
637 throttled: false,
638 seconds_since_unix_epoch: 0.8,
639 expected_tokens_retrieved_per_second: 4.800000000000001,
640 expected_token_refill_rate: 0.500000,
641 },
642 Attempt {
643 throttled: false,
644 seconds_since_unix_epoch: 1.0,
645 expected_tokens_retrieved_per_second: 4.160000,
646 expected_token_refill_rate: 0.500000,
647 },
648 Attempt {
649 throttled: false,
650 seconds_since_unix_epoch: 1.2,
651 expected_tokens_retrieved_per_second: 4.160000,
652 expected_token_refill_rate: 0.691200,
653 },
654 Attempt {
655 throttled: false,
656 seconds_since_unix_epoch: 1.4,
657 expected_tokens_retrieved_per_second: 4.160000,
658 expected_token_refill_rate: 1.0975999999999997,
659 },
660 Attempt {
661 throttled: false,
662 seconds_since_unix_epoch: 1.6,
663 expected_tokens_retrieved_per_second: 5.632000000000001,
664 expected_token_refill_rate: 1.6384000000000005,
665 },
666 Attempt {
667 throttled: false,
668 seconds_since_unix_epoch: 1.8,
669 expected_tokens_retrieved_per_second: 5.632000000000001,
670 expected_token_refill_rate: 2.332800,
671 },
672 Attempt {
673 throttled: true,
674 seconds_since_unix_epoch: 2.0,
675 expected_tokens_retrieved_per_second: 4.326400,
676 expected_token_refill_rate: 3.0284799999999996,
677 },
678 Attempt {
679 throttled: false,
680 seconds_since_unix_epoch: 2.2,
681 expected_tokens_retrieved_per_second: 4.326400,
682 expected_token_refill_rate: 3.48663917347026,
683 },
684 Attempt {
685 throttled: false,
686 seconds_since_unix_epoch: 2.4,
687 expected_tokens_retrieved_per_second: 4.326400,
688 expected_token_refill_rate: 3.821874416040255,
689 },
690 Attempt {
691 throttled: false,
692 seconds_since_unix_epoch: 2.6,
693 expected_tokens_retrieved_per_second: 5.665280,
694 expected_token_refill_rate: 4.053385727709987,
695 },
696 Attempt {
697 throttled: false,
698 seconds_since_unix_epoch: 2.8,
699 expected_tokens_retrieved_per_second: 5.665280,
700 expected_token_refill_rate: 4.200373108479454,
701 },
702 Attempt {
703 throttled: false,
704 seconds_since_unix_epoch: 3.0,
705 expected_tokens_retrieved_per_second: 4.333056,
706 expected_token_refill_rate: 4.282036558348658,
707 },
708 Attempt {
709 throttled: true,
710 seconds_since_unix_epoch: 3.2,
711 expected_tokens_retrieved_per_second: 4.333056,
712 expected_token_refill_rate: 2.99742559084406,
713 },
714 Attempt {
715 throttled: false,
716 seconds_since_unix_epoch: 3.4,
717 expected_tokens_retrieved_per_second: 4.333056,
718 expected_token_refill_rate: 3.4522263943863463,
719 },
720 ];
721
722 for attempt in attempts {
723 sleep_impl.sleep(TWO_HUNDRED_MILLISECONDS).await;
724 assert_eq!(
725 attempt.seconds_since_unix_epoch,
726 sleep_impl.total_duration().as_secs_f64()
727 );
728
729 rate_limiter.update_rate_limiter(attempt.seconds_since_unix_epoch, attempt.throttled);
730 assert_relative_eq!(
731 attempt.expected_tokens_retrieved_per_second,
732 rate_limiter.inner.lock().unwrap().measured_tx_rate
733 );
734 assert_relative_eq!(
735 attempt.expected_token_refill_rate,
736 rate_limiter.inner.lock().unwrap().fill_rate
737 );
738 }
739 }
740
741 #[tokio::test]
749 async fn test_capacity_never_goes_negative() {
750 let rate_limiter = ClientRateLimiter::builder()
751 .time_of_last_throttle(0.0)
752 .previous_time_bucket(0.0)
753 .build();
754
755 rate_limiter.update_rate_limiter(0.0, true);
756
757 let result =
760 rate_limiter.acquire_permission_to_send_a_request(0.1, RequestReason::InitialRequest);
761
762 assert!(result.is_err(), "should require waiting for capacity");
763
764 let inner = rate_limiter.inner.lock().unwrap();
765 assert_relative_eq!(inner.current_capacity, 0.05, epsilon = 0.01);
766 assert_relative_eq!(result.unwrap_err().as_secs_f64(), 1.9, epsilon = 0.01);
767 }
768
769 #[tokio::test]
770 async fn test_concurrent_acquires_no_cascading_delays() {
771 let rate_limiter = ClientRateLimiter::builder()
772 .time_of_last_throttle(0.0)
773 .previous_time_bucket(0.0)
774 .build();
775
776 rate_limiter.update_rate_limiter(0.0, true);
777
778 let mut delays = Vec::with_capacity(10);
779 for _ in 0..10 {
780 let result = rate_limiter
781 .acquire_permission_to_send_a_request(0.1, RequestReason::InitialRequest);
782 assert!(result.is_err());
783 delays.push(result.unwrap_err());
784 }
785
786 let first = delays[0];
788 for delay in &delays {
789 assert_eq!(*delay, first, "all tasks should get the same delay");
790 }
791
792 let inner = rate_limiter.inner.lock().unwrap();
793 assert!(
794 inner.current_capacity >= 0.0,
795 "capacity must never be negative"
796 );
797 }
798
799 #[tokio::test]
800 async fn test_acquire_succeeds_after_sufficient_refill() {
801 let rate_limiter = ClientRateLimiter::builder()
802 .time_of_last_throttle(0.0)
803 .previous_time_bucket(0.0)
804 .build();
805
806 rate_limiter.update_rate_limiter(0.0, true);
807
808 let result =
809 rate_limiter.acquire_permission_to_send_a_request(0.1, RequestReason::InitialRequest);
810 assert!(result.is_err());
811
812 let result =
814 rate_limiter.acquire_permission_to_send_a_request(2.1, RequestReason::InitialRequest);
815 assert!(result.is_ok(), "should succeed after sufficient refill");
816
817 let inner = rate_limiter.inner.lock().unwrap();
818 assert_relative_eq!(inner.current_capacity, 0.0, epsilon = 0.01);
819 }
820
821 #[tokio::test]
822 async fn test_sub_nanosecond_shortfall_grants_instead_of_returning_zero_delay() {
823 let rate_limiter = ClientRateLimiter::builder()
824 .time_of_last_throttle(0.0)
825 .previous_time_bucket(0.0)
826 .build();
827 rate_limiter.update_rate_limiter(0.0, true); {
833 let mut inner = rate_limiter.inner.lock().unwrap();
834 inner.current_capacity = INITIAL_REQUEST_COST - 1e-12;
835 inner.last_timestamp = Some(0.5);
836 }
837
838 let result =
841 rate_limiter.acquire_permission_to_send_a_request(0.5, RequestReason::InitialRequest);
842 assert!(
843 result.is_ok(),
844 "a sub-nanosecond shortfall must grant, not return Err(0ns)"
845 );
846
847 let inner = rate_limiter.inner.lock().unwrap();
849 assert!(
850 inner.current_capacity >= 0.0,
851 "capacity must not go negative, was {}",
852 inner.current_capacity
853 );
854 }
855
856 #[tokio::test]
862 async fn test_when_throttling_is_enabled_requests_can_still_be_sent() {
863 let (time_source, sleep_impl) = instant_time_and_sleep(SystemTime::UNIX_EPOCH);
864 let crl = ClientRateLimiter::builder()
865 .time_of_last_throttle(0.0)
866 .previous_time_bucket(0.0)
867 .build();
868
869 crl.update_rate_limiter(0.0, true);
871
872 for _i in 0..100 {
873 let duration = Duration::from_secs_f64(fastrand::f64());
875 sleep_impl.sleep(duration).await;
876 if let Err(delay) = crl.acquire_permission_to_send_a_request(
877 time_source.seconds_since_unix_epoch(),
878 RequestReason::InitialRequest,
879 ) {
880 sleep_impl.sleep(delay).await;
881 }
882
883 crl.update_rate_limiter(time_source.seconds_since_unix_epoch(), false);
885 }
886
887 let inner = crl.inner.lock().unwrap();
888 assert!(inner.enabled, "the rate limiter should still be enabled");
889 assert_relative_eq!(
891 inner.last_timestamp.unwrap(),
892 sleep_impl.total_duration().as_secs_f64(),
893 max_relative = 0.0001
894 );
895 }
896
897 #[tokio::test]
898 async fn test_multi_task_recovery_after_throttle_blip() {
899 let crl = ClientRateLimiter::builder()
903 .time_of_last_throttle(0.0)
904 .previous_time_bucket(0.0)
905 .build();
906
907 let num_tasks = 50;
908 let mut time = 0.0;
909
910 for _ in 0..num_tasks {
912 time += 0.001;
913 crl.update_rate_limiter(time, true);
914 }
915
916 assert_relative_eq!(crl.inner.lock().unwrap().fill_rate, 0.5, epsilon = 0.01);
917
918 let mut total_acquired = 0;
922 let rounds = 200;
923 for _ in 0..rounds {
924 time += 0.1;
925 let mut acquired_this_round = 0;
926
927 for task in 0..num_tasks {
928 let task_time = time + (task as f64) * 0.0001;
929 if crl
930 .acquire_permission_to_send_a_request(task_time, RequestReason::InitialRequest)
931 .is_ok()
932 {
933 acquired_this_round += 1;
934 }
935 }
936
937 for _ in 0..acquired_this_round {
938 time += 0.001;
939 crl.update_rate_limiter(time, false);
940 }
941
942 total_acquired += acquired_this_round;
943 }
944
945 assert!(
946 total_acquired > 100,
947 "expected recovery after throttle blip, but only acquired {total_acquired} tokens in {rounds} rounds"
948 );
949
950 let inner = crl.inner.lock().unwrap();
951 assert!(
952 inner.current_capacity >= 0.0,
953 "capacity must never be negative, got: {}",
954 inner.current_capacity
955 );
956 }
957}