webull_rs/utils/
rate_limit.rs1use std::collections::HashMap;
2use std::sync::{Arc, Mutex};
3use std::time::{Duration, Instant};
4use tokio::time::sleep;
5
6pub struct RateLimiter {
8 requests_per_minute: u32,
10
11 timestamps: Arc<Mutex<HashMap<String, Vec<Instant>>>>,
13
14 backoff_strategy: BackoffStrategy,
16}
17
18impl RateLimiter {
19 pub fn new(requests_per_minute: u32) -> Self {
21 Self {
22 requests_per_minute,
23 timestamps: Arc::new(Mutex::new(HashMap::new())),
24 backoff_strategy: BackoffStrategy::default(),
25 }
26 }
27
28 pub fn with_backoff_strategy(mut self, strategy: BackoffStrategy) -> Self {
30 self.backoff_strategy = strategy;
31 self
32 }
33
34 pub async fn wait(&self, endpoint: &str) {
36 let now = Instant::now();
38
39 let wait_time = {
41 let mut timestamps = self.timestamps.lock().unwrap();
43 let endpoint_timestamps = timestamps
44 .entry(endpoint.to_string())
45 .or_insert_with(Vec::new);
46
47 endpoint_timestamps.retain(|t| now.duration_since(*t) < Duration::from_secs(60));
49
50 if endpoint_timestamps.len() >= self.requests_per_minute as usize {
52 let oldest = endpoint_timestamps[0];
54 Some(Duration::from_secs(60) - now.duration_since(oldest))
55 } else {
56 endpoint_timestamps.push(now);
58 None
59 }
60 };
61
62 if let Some(duration) = wait_time {
64 sleep(duration).await;
66
67 let mut timestamps = self.timestamps.lock().unwrap();
69 let endpoint_timestamps = timestamps
70 .entry(endpoint.to_string())
71 .or_insert_with(Vec::new);
72 endpoint_timestamps.push(Instant::now());
73 }
74 }
75
76 pub async fn handle_rate_limit_error(&self, attempt: u32) -> Duration {
78 self.backoff_strategy.get_backoff_duration(attempt)
79 }
80}
81
82#[derive(Debug, Clone, Copy)]
84pub enum BackoffStrategy {
85 Constant(Duration),
87
88 Linear {
90 initial: Duration,
92
93 increment: Duration,
95 },
96
97 Exponential {
99 initial: Duration,
101
102 multiplier: f64,
104
105 max: Duration,
107 },
108}
109
110impl BackoffStrategy {
111 pub fn get_backoff_duration(&self, attempt: u32) -> Duration {
113 match self {
114 Self::Constant(duration) => *duration,
115 Self::Linear { initial, increment } => *initial + *increment * attempt,
116 Self::Exponential {
117 initial,
118 multiplier,
119 max,
120 } => {
121 let duration = initial.as_secs_f64() * multiplier.powf(attempt as f64);
122 Duration::from_secs_f64(duration.min(max.as_secs_f64()))
123 }
124 }
125 }
126}
127
128impl Default for BackoffStrategy {
129 fn default() -> Self {
130 Self::Exponential {
131 initial: Duration::from_secs(1),
132 multiplier: 2.0,
133 max: Duration::from_secs(60),
134 }
135 }
136}