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.entry(endpoint.to_string()).or_insert_with(Vec::new);
44
45 endpoint_timestamps.retain(|t| now.duration_since(*t) < Duration::from_secs(60));
47
48 if endpoint_timestamps.len() >= self.requests_per_minute as usize {
50 let oldest = endpoint_timestamps[0];
52 Some(Duration::from_secs(60) - now.duration_since(oldest))
53 } else {
54 endpoint_timestamps.push(now);
56 None
57 }
58 };
59
60 if let Some(duration) = wait_time {
62 sleep(duration).await;
64
65 let mut timestamps = self.timestamps.lock().unwrap();
67 let endpoint_timestamps = timestamps.entry(endpoint.to_string()).or_insert_with(Vec::new);
68 endpoint_timestamps.push(Instant::now());
69 }
70 }
71
72 pub async fn handle_rate_limit_error(&self, attempt: u32) -> Duration {
74 self.backoff_strategy.get_backoff_duration(attempt)
75 }
76}
77
78#[derive(Debug, Clone, Copy)]
80pub enum BackoffStrategy {
81 Constant(Duration),
83
84 Linear {
86 initial: Duration,
88
89 increment: Duration,
91 },
92
93 Exponential {
95 initial: Duration,
97
98 multiplier: f64,
100
101 max: Duration,
103 },
104}
105
106impl BackoffStrategy {
107 pub fn get_backoff_duration(&self, attempt: u32) -> Duration {
109 match self {
110 Self::Constant(duration) => *duration,
111 Self::Linear { initial, increment } => {
112 *initial + *increment * attempt
113 }
114 Self::Exponential { initial, multiplier, max } => {
115 let duration = initial.as_secs_f64() * multiplier.powf(attempt as f64);
116 Duration::from_secs_f64(duration.min(max.as_secs_f64()))
117 }
118 }
119 }
120}
121
122impl Default for BackoffStrategy {
123 fn default() -> Self {
124 Self::Exponential {
125 initial: Duration::from_secs(1),
126 multiplier: 2.0,
127 max: Duration::from_secs(60),
128 }
129 }
130}