1use std::collections::VecDeque;
32use std::fmt;
33use std::str::FromStr;
34use std::sync::atomic::Ordering::Relaxed;
35use std::sync::atomic::{AtomicU32, AtomicU64};
36use std::sync::{Arc, LazyLock, Mutex};
37use std::time::{Duration, Instant};
38
39use async_trait::async_trait;
40use chrono::Utc;
41use object_store::ClientOptions;
42use object_store::client::{
43 HttpClient, HttpConnector, HttpError, HttpErrorKind, HttpRequest, HttpResponse, HttpService,
44};
45use polars_core::runtime::ASYNC;
46use tokio::sync::oneshot;
47
48use super::token_bucket::TokenBucket;
49use crate::cloud::{CloudDirectionalRateLimitConfig, CloudRateLimitConfig};
50
51static CONTROLLER_ID: AtomicU32 = AtomicU32::new(0);
53
54static LOG_RATE_LIMIT: LazyLock<bool> =
56 LazyLock::new(|| std::env::var("POLARS_LOG_RATE_LIMIT").is_ok());
57
58const DEFAULT_INIT_RATE: f64 = 1000.0;
60const DEFAULT_FLOOR_RATE: f64 = 10.0;
61const DEFAULT_CEILING_RATE: f64 = 50_000.0;
62
63const FAST_RAMP_FACTOR: f64 = 2.0;
66const PROBE_FRACTION: f64 = 0.1;
68const PROBE_MIN: f64 = 5.0;
69const BETA: f64 = 0.7;
71const BETA_NO_SIGNAL: f64 = 0.5;
73
74const DEFAULT_RATE_HORIZON_MS: u64 = 200;
77const DEFAULT_RATE_MAX_WAIT_MS: u64 = 10_000;
87const TICK_INTERVAL: Duration = Duration::from_secs(1);
89const REFRACTORY: Duration = Duration::from_secs(1);
91const PROBE_QUIET_PERIOD: Duration = Duration::from_secs(2);
93
94const SATURATION_THRESHOLD: f64 = 0.8;
97const SUCCESS_SMOOTH: f64 = 0.8;
99const IDLE_DECAY: f64 = 0.9;
101
102pub(crate) const HTTP_RATE_LIMIT_WAKE_TICK: Duration = Duration::from_millis(1);
105
106pub type RateCell = Arc<AtomicU64>;
109
110#[derive(Debug, Clone, Copy, PartialEq, Eq)]
113#[allow(unused)]
114pub(crate) enum InitPolicy {
115 SetToInit,
117 SetToFloor,
119 Inherit,
121}
122
123impl InitPolicy {
124 fn target_rate(self, config: &DirectionalRateLimitConfig) -> Option<f64> {
125 match self {
126 Self::SetToInit => Some(config.init_rate),
127 Self::SetToFloor => Some(config.floor_rate),
128 Self::Inherit => None,
129 }
130 }
131}
132
133#[derive(Debug, Clone)]
135pub(crate) struct DirectionalRateLimitConfig {
136 pub(crate) init_rate: f64,
138 pub(crate) floor_rate: f64,
139 pub(crate) ceiling_rate: f64,
140 pub(crate) horizon: Duration,
141 pub(crate) max_wait: Duration,
142 pub(crate) init_policy: InitPolicy,
143}
144
145#[derive(Debug, Clone)]
147pub(crate) struct RateLimitConfig {
148 pub(crate) read: DirectionalRateLimitConfig,
149 pub(crate) write: DirectionalRateLimitConfig,
150}
151
152impl From<CloudRateLimitConfig> for RateLimitConfig {
153 fn from(value: CloudRateLimitConfig) -> Self {
154 fn to_rate_limit_config(
155 config: &CloudDirectionalRateLimitConfig,
156 ) -> DirectionalRateLimitConfig {
157 DirectionalRateLimitConfig {
158 init_rate: config.init_rate.map_or(DEFAULTS.init_rate, |r| r as f64),
159 floor_rate: config.floor_rate.map_or(DEFAULTS.floor_rate, |r| r as f64),
160 ceiling_rate: config
161 .ceiling_rate
162 .map_or(DEFAULTS.ceiling_rate, |r| r as f64),
163 horizon: DEFAULTS.horizon,
164 max_wait: DEFAULTS.max_wait,
165 init_policy: DEFAULTS.init_policy,
166 }
167 }
168
169 let read_config = to_rate_limit_config(&value.read);
170 let write_config = to_rate_limit_config(&value.write);
171
172 return RateLimitConfig {
173 read: read_config,
174 write: write_config,
175 };
176
177 static DEFAULTS: LazyLock<DirectionalRateLimitConfig> =
178 LazyLock::new(|| DirectionalRateLimitConfig {
179 init_rate: parse_env_var(DEFAULT_INIT_RATE, "POLARS_CLOUD_INIT_RATE"),
180 floor_rate: {
181 let floor_rate = parse_env_var(DEFAULT_FLOOR_RATE, "POLARS_CLOUD_FLOOR_RATE");
182 assert!(floor_rate > 0.0);
183 floor_rate
184 },
185 ceiling_rate: parse_env_var(DEFAULT_CEILING_RATE, "POLARS_CLOUD_CEILING_RATE"),
186 horizon: Duration::from_millis(parse_env_var(
187 DEFAULT_RATE_HORIZON_MS,
188 "POLARS_CLOUD_RATE_HORIZON_MS",
189 )),
190 max_wait: Duration::from_millis(parse_env_var(
191 DEFAULT_RATE_MAX_WAIT_MS,
192 "POLARS_CLOUD_RATE_MAX_WAIT_MS",
193 )),
194 init_policy: InitPolicy::Inherit,
195 });
196
197 fn parse_env_var<T: FromStr>(default: T, name: &'static str) -> T {
198 std::env::var(name).map_or(default, |x| {
199 x.parse::<T>()
200 .ok()
201 .unwrap_or_else(|| panic!("invalid value for {name}: {x}"))
202 })
203 }
204 }
205}
206
207#[derive(Debug, Clone)]
214pub struct PacingBudget {
215 rate_bits: RateCell,
216 horizon: Duration,
217}
218
219impl PacingBudget {
220 pub fn rate(&self) -> f64 {
221 f64::from_bits(self.rate_bits.load(Relaxed))
222 }
223
224 pub fn horizon(&self) -> Duration {
225 self.horizon
226 }
227
228 pub fn request_budget(&self, bdp: f64) -> f64 {
230 bdp.min(self.rate() * self.horizon().as_secs_f64())
231 }
232}
233#[derive(Debug, Clone)]
235pub(crate) struct RateState {
236 pub rate_bits: RateCell,
237 pub max_bits: RateCell, }
239
240impl RateState {
241 pub fn new(init_rate: f64) -> Self {
242 Self {
243 rate_bits: Arc::new(AtomicU64::new(init_rate.to_bits())),
244 max_bits: Arc::new(AtomicU64::new(f64::NAN.to_bits())),
245 }
246 }
247
248 #[inline]
249 pub fn get_rate(&self) -> f64 {
250 f64::from_bits(self.rate_bits.load(Relaxed))
251 }
252
253 #[inline]
254 pub fn set_rate(&self, rate: f64) {
255 self.rate_bits.store(rate.to_bits(), Relaxed);
256 }
257
258 #[inline]
259 pub fn get_last_max(&self) -> Option<f64> {
260 let value = f64::from_bits(self.max_bits.load(Relaxed));
262 (!value.is_nan()).then_some(value)
263 }
264
265 #[inline]
266 pub fn set_last_max(&self, max: Option<f64>) {
267 let bits = max.unwrap_or(f64::NAN).to_bits();
268 self.max_bits.store(bits, Relaxed);
269 }
270
271 pub fn reset(&self, init_rate: f64) {
272 self.set_rate(init_rate);
273 self.set_last_max(None);
274 }
275}
276
277#[derive(Debug)]
279struct RateLimitState {
280 read_state: RateState,
281 write_state: RateState,
282}
283
284impl RateLimitState {
285 pub(crate) fn new(config: &RateLimitConfig) -> Self {
286 Self {
287 read_state: RateState::new(config.read.init_rate),
288 write_state: RateState::new(config.write.init_rate),
289 }
290 }
291
292 pub(crate) fn apply_init_policy(&self, config: &RateLimitConfig) {
294 fn apply_one(state: &RateState, config: &DirectionalRateLimitConfig) {
295 if let Some(rate) = config.init_policy.target_rate(config) {
296 state.reset(rate);
297 }
298 }
299
300 apply_one(&self.read_state, &config.read);
301 apply_one(&self.write_state, &config.write);
302 }
303}
304
305#[derive(Debug)]
306pub(crate) struct RateLimiter {
307 pub(crate) config: RateLimitConfig,
308 state: RateLimitState,
309}
310
311impl RateLimiter {
312 pub(crate) fn new(config: RateLimitConfig) -> Self {
313 let state = RateLimitState::new(&config);
314 Self { config, state }
315 }
316
317 pub(crate) fn apply_init_policy(&self) {
320 self.state.apply_init_policy(&self.config);
321 }
322
323 pub(crate) fn read_budget(&self) -> PacingBudget {
326 PacingBudget {
327 rate_bits: Arc::clone(&self.state.read_state.rate_bits),
328 horizon: self.config.read.horizon,
329 }
330 }
331
332 #[allow(unused)]
335 pub(crate) fn write_budget(&self) -> PacingBudget {
336 PacingBudget {
337 rate_bits: Arc::clone(&self.state.write_state.rate_bits),
338 horizon: self.config.write.horizon,
339 }
340 }
341}
342
343#[derive(Debug, Copy, Clone)]
344enum Regime {
345 Search,
347 Recover { until: Instant, anchor: f64 },
349 Track { anchor: f64 },
351}
352
353#[derive(Debug)]
357struct AimdState {
358 regime: Regime,
360
361 shared: RateState,
364 success_rate: Option<f64>,
366 window_start: Option<Instant>,
368 last_cut_time: Option<Instant>,
370
371 prev_admitted: u64,
373 prev_denied: u64,
374 prev_resp_succeeded: u64,
375 prev_resp_throttled: u64,
376}
377
378impl AimdState {
379 #[inline]
380 pub fn rate(&self) -> f64 {
381 self.shared.get_rate()
382 }
383
384 #[inline]
385 pub fn set_rate(&mut self, rate: f64) {
386 self.shared.set_rate(rate);
387 }
388
389 #[inline]
390 pub fn last_max(&self) -> Option<f64> {
391 self.shared.get_last_max()
392 }
393
394 #[inline]
395 pub fn set_last_max(&mut self, max: Option<f64>) {
396 self.shared.set_last_max(max);
397 }
398
399 fn update_success_rate(&mut self, successes: u64, elapsed_s: f64) {
401 if elapsed_s < 0.5 * TICK_INTERVAL.as_secs_f64() {
403 return;
404 }
405
406 if successes == 0 {
407 return;
408 }
409
410 let success_rate = successes as f64 / elapsed_s;
411 self.success_rate = Some(match self.success_rate {
412 None => success_rate,
413 Some(rate) => SUCCESS_SMOOTH * success_rate + (1.0 - SUCCESS_SMOOTH) * rate,
414 });
415 }
416}
417
418#[derive(Debug)]
420pub(crate) struct PacerSignal {
421 window_end_ns: AtomicU64,
423 last_cut_ns: AtomicU64,
425 resp_succeeded: AtomicU64,
427 resp_throttled: AtomicU64,
428}
429
430#[derive(Debug)]
431pub(crate) struct AdaptiveRateController {
432 epoch: Instant,
433 pacer: Arc<Pacer>,
436 signal: PacerSignal,
438 state: Mutex<AimdState>,
440 label: &'static str,
441 id: u32,
442 config: DirectionalRateLimitConfig,
443}
444
445impl AdaptiveRateController {
446 fn new(
447 label: &'static str,
448 id: u32,
449 shared: RateState,
450 config: DirectionalRateLimitConfig,
451 ) -> Self {
452 let epoch = Instant::now();
453
454 let regime = match shared.get_last_max() {
455 Some(anchor) => Regime::Track { anchor },
456 None => Regime::Search,
457 };
458
459 let token_bucket = Arc::new(TokenBucket::new(shared.rate_bits.clone()));
460 let pacer = Pacer::start(token_bucket, config.max_wait);
461
462 let signal = PacerSignal {
463 window_end_ns: AtomicU64::new(u64::MAX),
464 last_cut_ns: AtomicU64::new(0),
465 resp_succeeded: AtomicU64::new(0),
466 resp_throttled: AtomicU64::new(0),
467 };
468
469 let state = Mutex::new(AimdState {
470 regime,
471 shared,
472 success_rate: None,
473 window_start: None,
474 last_cut_time: None,
475 prev_admitted: 0,
476 prev_denied: 0,
477 prev_resp_succeeded: 0,
478 prev_resp_throttled: 0,
479 });
480
481 Self {
482 epoch,
483 pacer,
484 signal,
485 state,
486 label,
487 id,
488 config,
489 }
490 }
491
492 #[inline]
493 fn now_ns(&self) -> u64 {
494 self.epoch.elapsed().as_nanos() as u64
495 }
496
497 #[inline]
498 fn mark_first_traffic(&self) {
499 if self.signal.window_end_ns.load(Relaxed) == u64::MAX {
503 let now_ns = self.now_ns();
504 self.signal
505 .window_end_ns
506 .store(now_ns + TICK_INTERVAL.as_nanos() as u64, Relaxed);
507 self.state.lock().unwrap().window_start = Some(Instant::now());
508 }
509 }
510
511 fn on_congestion(&self) {
512 self.signal.resp_throttled.fetch_add(1, Relaxed);
513
514 self.mark_first_traffic();
515 self.maybe_settle();
516
517 let now_ns = self.now_ns();
519 let last = self.signal.last_cut_ns.load(Relaxed);
520 if last != 0 && now_ns.saturating_sub(last) < REFRACTORY.as_nanos() as u64 {
521 return;
522 }
523
524 let now = Instant::now();
526 let mut state = self.state.lock().unwrap();
527 if state
528 .last_cut_time
529 .is_some_and(|t| now.duration_since(t) < REFRACTORY)
530 {
531 return;
532 }
533
534 let (anchor, beta) = match state.success_rate {
537 None => (state.rate(), BETA_NO_SIGNAL),
538 Some(success_rate) => (
539 state.rate().min(success_rate.max(self.config.floor_rate)),
540 BETA,
541 ),
542 };
543
544 state.set_rate((anchor * beta).max(self.config.floor_rate));
546 state.last_cut_time = Some(now);
547 self.signal.last_cut_ns.store(now_ns, Relaxed);
548
549 state.regime = Regime::Recover {
551 until: now.checked_add(PROBE_QUIET_PERIOD).unwrap(),
552 anchor,
553 };
554
555 if *LOG_RATE_LIMIT {
557 eprintln!(
558 "[http rate_limit #{}_{} {}] ..cut (anchored): rate: {:.1}, success_rate: {:.1}, last_max: {:.1}",
559 self.id,
560 self.label,
561 Utc::now(),
562 state.rate(),
563 state.success_rate.unwrap_or_default(),
564 state.last_max().unwrap_or_default(),
565 );
566 }
567 }
568
569 fn on_success(&self) {
570 self.signal.resp_succeeded.fetch_add(1, Relaxed);
571
572 self.mark_first_traffic();
573 self.maybe_settle();
574 }
575
576 fn on_other(&self) {
577 self.mark_first_traffic();
578 self.maybe_settle();
579 }
580
581 #[inline]
582 fn maybe_settle(&self) {
583 if self.now_ns() >= self.signal.window_end_ns.load(Relaxed) {
586 self.settle_lazy_tick();
587 }
588 }
589
590 fn settle_lazy_tick(&self) {
592 let mut state = self.state.lock().unwrap();
593 let now = Instant::now();
594 let now_ns = self.now_ns();
595
596 let elapsed = now.duration_since(state.window_start.unwrap());
598 let ticks = (elapsed.as_secs_f64() / TICK_INTERVAL.as_secs_f64()).floor();
599 if ticks < 1.0 {
600 return;
601 }
602
603 let admitted = self.pacer.admitted();
605 let win_admitted = admitted.saturating_sub(state.prev_admitted);
606 state.prev_admitted = admitted;
607
608 let denied = self.pacer.denied.load(Relaxed);
609 let win_denied = denied.saturating_sub(state.prev_denied);
610 state.prev_denied = denied;
611
612 let succeeded = self.signal.resp_succeeded.load(Relaxed);
613 let win_succeeded = succeeded.saturating_sub(state.prev_resp_succeeded);
614 state.prev_resp_succeeded = succeeded;
615
616 let throttled = self.signal.resp_throttled.load(Relaxed);
617 let win_throttled = throttled.saturating_sub(state.prev_resp_throttled);
618 state.prev_resp_throttled = throttled;
619
620 let elapsed_s = elapsed.as_secs_f64();
622 let queued = self.pacer.queue_depth() > 0;
623 let util_bound = win_admitted as f64 >= SATURATION_THRESHOLD * state.rate() * elapsed_s;
624
625 state.update_success_rate(win_succeeded, elapsed_s);
627
628 if let Some(observed) = state.success_rate {
631 let pressure_tested =
632 win_admitted as f64 / elapsed_s >= state.last_max().unwrap_or(0.0);
633 let new_max = match state.last_max() {
634 Some(prev) if !pressure_tested => prev.max(observed),
635 _ => observed,
636 };
637 state.set_last_max(Some(new_max.max(self.config.floor_rate)));
638 }
639
640 let verdict = {
642 let rate = state.rate();
643 let init_rate = self.config.init_rate;
644 let floor_rate = self.config.floor_rate;
645 let ceiling_rate = self.config.ceiling_rate;
646
647 if let Regime::Recover { until, anchor } = state.regime
648 && now > until
649 {
650 state.regime = Regime::Track { anchor }
651 };
652
653 if win_admitted + win_denied == 0 {
654 if rate > init_rate {
656 let rate = init_rate + (state.rate() - init_rate) * IDLE_DECAY.powf(ticks);
657 state.set_rate(rate.clamp(floor_rate, ceiling_rate));
658 }
659 "decay (idle)"
661 } else {
662 match state.regime {
663 Regime::Search => {
664 if util_bound && win_throttled == 0 {
667 state.set_rate((rate * FAST_RAMP_FACTOR).min(ceiling_rate));
668 "increase (fast_ramp)"
669 } else {
670 "hold (app-limited)"
671 }
672 },
673 Regime::Recover { .. } => {
674 "hold (recover)"
676 },
677 Regime::Track { anchor } => {
678 if rate < anchor && util_bound {
680 state.set_rate((rate * FAST_RAMP_FACTOR).min(anchor).min(ceiling_rate));
681 "increase (reclaim)"
682 } else
683 if rate < anchor {
685 "hold (app-limited)"
686 } else
687 if util_bound {
689 let delta = (PROBE_FRACTION * anchor).max(PROBE_MIN);
690 state.set_rate((rate + delta).min(ceiling_rate));
691 "increase (probe)"
692 } else
693 if queued {
695 "hold (draining)"
696 } else {
697 "hold (app-limited)"
699 }
700 },
701 }
702 }
703 };
704
705 state.window_start = Some(now);
707 self.signal
708 .window_end_ns
709 .store(now_ns + TICK_INTERVAL.as_nanos() as u64, Relaxed);
710
711 if *LOG_RATE_LIMIT {
713 eprintln!(
714 "[http rate_limit #{}_{} {}] {}: rate: {:.1}, success_rate_ewma: {:.1}, last_max: {:.1}, \
715 elapsed: {:.1}s, win_admit: {}, win_deny: {}, win_success: {}, win_throttle: {}, q_depth: {}",
716 self.id,
717 self.label,
718 Utc::now(),
719 verdict,
720 state.rate(),
721 state.success_rate.unwrap_or_default(),
722 state.last_max().unwrap_or_default(),
723 elapsed_s,
724 win_admitted,
725 win_denied,
726 win_succeeded,
727 win_throttled,
728 self.pacer.queue_depth(),
729 );
730 }
731 }
732}
733
734#[derive(Debug)]
736pub(crate) struct RateController {
737 pub read: AdaptiveRateController,
739 pub write: AdaptiveRateController,
741}
742
743impl RateController {
744 pub(crate) fn new(rate_limiter: &RateLimiter) -> Arc<Self> {
745 let id = CONTROLLER_ID.fetch_add(1, Relaxed);
748 Arc::new(Self {
749 read: AdaptiveRateController::new(
750 "read",
751 id,
752 rate_limiter.state.read_state.clone(),
753 rate_limiter.config.read.clone(),
754 ),
755 write: AdaptiveRateController::new(
756 "write",
757 id,
758 rate_limiter.state.write_state.clone(),
759 rate_limiter.config.write.clone(),
760 ),
761 })
762 }
763
764 fn class(&self, req: &HttpRequest) -> &AdaptiveRateController {
765 match req.method().as_str() {
766 "GET" | "HEAD" => &self.read,
767 _ => &self.write,
768 }
769 }
770}
771
772#[derive(Debug)]
773struct PacerBusy {
774 est_wait_ms: u64,
775}
776impl fmt::Display for PacerBusy {
777 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
778 write!(
779 f,
780 "internal pacer denied: estimated wait {}ms exceeds bound",
781 self.est_wait_ms
782 )
783 }
784}
785impl std::error::Error for PacerBusy {}
786
787#[derive(Debug, Default)]
793struct WaiterQueue {
794 parked: Mutex<VecDeque<oneshot::Sender<()>>>,
795 depth: AtomicU64,
798}
799
800impl WaiterQueue {
801 fn park(&self) -> oneshot::Receiver<()> {
802 let (tx, rx) = oneshot::channel();
803 let mut q = self.parked.lock().unwrap();
804 q.push_back(tx);
805 self.depth.store(q.len() as u64, Relaxed);
806 rx
807 }
808
809 fn grant_one(&self) -> bool {
813 let mut q = self.parked.lock().unwrap();
814 while let Some(tx) = q.pop_front() {
815 self.depth.store(q.len() as u64, Relaxed);
816 if tx.send(()).is_ok() {
817 return true; }
819 }
821 false
822 }
823
824 fn depth(&self) -> u64 {
825 self.depth.load(Relaxed)
826 }
827}
828
829#[derive(Debug)]
831pub struct Pacer {
832 bucket: Arc<TokenBucket>,
833 queue: Arc<WaiterQueue>,
834 max_wait: Duration,
835 admitted: AtomicU64,
837 denied: AtomicU64,
839}
840
841impl Pacer {
842 pub fn start(bucket: Arc<TokenBucket>, max_wait: Duration) -> Arc<Self> {
844 let pacer = Arc::new(Self {
845 bucket,
846 queue: Arc::new(WaiterQueue::default()),
847 max_wait,
848 admitted: AtomicU64::new(0),
849 denied: AtomicU64::new(0),
850 });
851 Self::spawn_wake_tick(Arc::downgrade(&pacer));
852 pacer
853 }
854
855 pub async fn admit(&self) -> Result<(), HttpError> {
858 if self.queue.depth() == 0 && self.bucket.try_acquire().is_ok() {
860 self.admitted.fetch_add(1, Relaxed);
861 return Ok(());
862 }
863
864 let est_wait =
866 Duration::from_secs_f64((self.queue.depth() + 1) as f64 / self.bucket.rate());
867 if est_wait > self.max_wait {
868 self.denied.fetch_add(1, Relaxed);
869 return Err(HttpError::new(
870 HttpErrorKind::Timeout, PacerBusy {
872 est_wait_ms: est_wait.as_millis() as u64,
873 },
874 ));
875 }
876
877 let rx = self.queue.park();
879
880 let _ = rx.await;
882 self.admitted.fetch_add(1, Relaxed);
883 Ok(())
884 }
885
886 pub fn queue_depth(&self) -> u64 {
887 self.queue.depth()
888 }
889
890 pub fn admitted(&self) -> u64 {
891 self.admitted.load(Relaxed)
892 }
893
894 pub fn bucket(&self) -> &Arc<TokenBucket> {
895 &self.bucket
896 }
897
898 fn spawn_wake_tick(gate: std::sync::Weak<Self>) {
902 ASYNC.spawn(async move {
903 let mut tick = tokio::time::interval(HTTP_RATE_LIMIT_WAKE_TICK);
904 tick.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
905 loop {
906 tick.tick().await;
907 let Some(g) = gate.upgrade() else { return };
908
909 while g.queue.depth() > 0 {
910 if g.bucket.try_acquire().is_err() {
911 break;
912 }
913 if !g.queue.grant_one() {
914 break;
917 }
918 }
919 }
920 });
921 }
922}
923
924#[derive(Debug)]
927pub(crate) struct PacedHttpConnector {
928 inner: Box<dyn HttpConnector>,
929 controller: Arc<RateController>,
930}
931
932impl PacedHttpConnector {
933 pub(crate) fn new(inner: Box<dyn HttpConnector>, rate_limiter: &RateLimiter) -> Self {
934 rate_limiter.apply_init_policy();
936
937 Self {
938 inner,
939 controller: RateController::new(rate_limiter),
940 }
941 }
942}
943
944impl HttpConnector for PacedHttpConnector {
945 fn connect(&self, options: &ClientOptions) -> object_store::Result<HttpClient> {
946 let client = self.inner.connect(options)?;
947 Ok(HttpClient::new(PacedHttpService {
948 inner: client,
949 controller: Arc::clone(&self.controller),
950 }))
951 }
952}
953
954#[derive(Debug)]
955pub(crate) struct PacedHttpService {
956 inner: HttpClient,
957 controller: Arc<RateController>,
958}
959
960#[async_trait]
961impl HttpService for PacedHttpService {
962 async fn call(&self, req: HttpRequest) -> Result<HttpResponse, HttpError> {
963 let verb_pacer = self.controller.class(&req);
964
965 verb_pacer.pacer.admit().await?;
969
970 let response = self.inner.execute(req).await;
971 match &response {
972 Ok(r) if r.status().as_u16() == 429 || r.status().as_u16() == 503 => {
973 verb_pacer.on_congestion()
974 },
975 Ok(r) if r.status().is_success() => verb_pacer.on_success(),
976 _ => verb_pacer.on_other(),
977 }
978
979 response
980 }
981}