1use std::sync::Mutex;
10use std::time::{Duration, Instant};
11
12pub struct RateLimiter {
13 bytes_per_sec: f64,
14 capacity: f64,
20 state: Mutex<Bucket>,
21}
22
23struct Bucket {
24 tokens: f64,
25 last: Instant,
26}
27
28const MIN_CAPACITY: f64 = 1.0 * 1024.0 * 1024.0;
31
32impl RateLimiter {
33 pub fn new(bytes_per_sec: u64) -> Self {
34 let rate = bytes_per_sec as f64;
35 let capacity = (rate / 4.0).max(MIN_CAPACITY).min(rate.max(1.0));
39 Self {
40 bytes_per_sec: rate,
41 capacity,
42 state: Mutex::new(Bucket {
43 tokens: capacity,
44 last: Instant::now(),
45 }),
46 }
47 }
48
49 pub async fn acquire(&self, n: u64) {
53 let mut remaining = n as f64;
54 while remaining > 0.0 {
55 let wait = {
56 let mut b = self.state.lock().expect("rate limiter poisoned");
57 let now = Instant::now();
58 let elapsed = now.duration_since(b.last).as_secs_f64();
59 b.last = now;
60 b.tokens = (b.tokens + elapsed * self.bytes_per_sec).min(self.capacity);
61
62 if b.tokens >= remaining {
63 b.tokens -= remaining;
64 remaining = 0.0;
65 Duration::ZERO
66 } else {
67 remaining -= b.tokens.max(0.0);
71 b.tokens = 0.0;
72 Duration::from_secs_f64((remaining / self.bytes_per_sec).min(1.0))
73 }
74 };
75 if wait > Duration::ZERO {
76 tokio::time::sleep(wait).await;
77 }
78 }
79 }
80}
81
82pub fn parse_rate(input: &str) -> Result<u64, String> {
84 let s = input.trim().trim_end_matches("/s").trim_end_matches("/S");
85 let s = s.trim();
86 let split = s
87 .find(|c: char| !c.is_ascii_digit() && c != '.' && c != ',')
88 .unwrap_or(s.len());
89 let (num, unit) = s.split_at(split);
90 let num: f64 = num
91 .replace(',', "")
92 .parse()
93 .map_err(|_| format!("invalid rate `{input}`"))?;
94 if num <= 0.0 {
95 return Err(format!("rate must be positive, got `{input}`"));
96 }
97 let mult: f64 = match unit.trim().to_ascii_lowercase().as_str() {
98 "" | "b" => 1.0,
99 "k" | "kb" | "kib" => 1024.0,
100 "m" | "mb" | "mib" => 1024.0 * 1024.0,
101 "g" | "gb" | "gib" => 1024.0 * 1024.0 * 1024.0,
102 other => return Err(format!("unknown rate unit `{other}` in `{input}`")),
103 };
104 Ok((num * mult) as u64)
105}
106
107pub fn parse_duration(input: &str) -> Result<Duration, String> {
109 let s = input.trim();
110 let split = s
111 .find(|c: char| !c.is_ascii_digit() && c != '.')
112 .unwrap_or(s.len());
113 let (num, unit) = s.split_at(split);
114 let num: f64 = num
115 .parse()
116 .map_err(|_| format!("invalid duration `{input}`"))?;
117 let secs = match unit.trim().to_ascii_lowercase().as_str() {
118 "ms" => num / 1000.0,
119 "" | "s" | "sec" | "secs" => num,
120 "m" | "min" | "mins" => num * 60.0,
121 "h" | "hr" | "hrs" => num * 3600.0,
122 other => return Err(format!("unknown duration unit `{other}` in `{input}`")),
123 };
124 if secs <= 0.0 {
125 return Err(format!("duration must be positive, got `{input}`"));
126 }
127 Ok(Duration::from_secs_f64(secs))
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133
134 #[test]
135 fn parses_rates() {
136 assert_eq!(parse_rate("1024"), Ok(1024));
137 assert_eq!(parse_rate("1k"), Ok(1024));
138 assert_eq!(parse_rate("20MiB/s"), Ok(20 * 1024 * 1024));
139 assert_eq!(
140 parse_rate("20 MB / s".replace(' ', "").as_str()),
141 Ok(20 * 1024 * 1024)
142 );
143 assert_eq!(parse_rate("1.5m"), Ok(1_572_864));
144 assert_eq!(parse_rate("2G"), Ok(2 * 1024 * 1024 * 1024));
145 assert!(parse_rate("0").is_err());
146 assert!(parse_rate("-5m").is_err());
147 assert!(parse_rate("fast").is_err());
148 assert!(parse_rate("10furlongs").is_err());
149 }
150
151 #[test]
152 fn parses_durations() {
153 assert_eq!(parse_duration("30"), Ok(Duration::from_secs(30)));
154 assert_eq!(parse_duration("30s"), Ok(Duration::from_secs(30)));
155 assert_eq!(parse_duration("500ms"), Ok(Duration::from_millis(500)));
156 assert_eq!(parse_duration("2m"), Ok(Duration::from_secs(120)));
157 assert_eq!(parse_duration("1h"), Ok(Duration::from_secs(3600)));
158 assert!(parse_duration("0").is_err());
159 assert!(parse_duration("soon").is_err());
160 }
161
162 #[tokio::test(start_paused = true)]
163 async fn limits_throughput_globally() {
164 let rate = 8 * 1024 * 1024;
166 let limiter = RateLimiter::new(rate);
167 let start = tokio::time::Instant::now();
168
169 limiter.acquire(rate / 4).await;
171 assert_eq!(start.elapsed(), Duration::ZERO);
172
173 limiter.acquire(rate).await;
176 limiter.acquire(rate).await;
177 assert!(
178 start.elapsed() >= Duration::from_millis(1900),
179 "elapsed {:?}",
180 start.elapsed()
181 );
182 }
183
184 #[test]
185 fn burst_allowance_is_a_small_fraction_of_the_rate() {
186 let fast = RateLimiter::new(40 * 1024 * 1024);
188 assert_eq!(fast.capacity, 10.0 * 1024.0 * 1024.0);
189
190 let slow = RateLimiter::new(64 * 1024);
193 assert_eq!(slow.capacity, 64.0 * 1024.0);
194 let mid = RateLimiter::new(2 * 1024 * 1024);
195 assert_eq!(mid.capacity, MIN_CAPACITY);
196 }
197}