primitives/utils/rate_limiter/
token_bucket.rs1use std::{
8 sync::{
9 atomic::{AtomicBool, AtomicU64, Ordering},
10 Arc,
11 },
12 time::Duration,
13};
14
15use futures::future::join_all;
16use tokio::{sync::RwLock, task::JoinHandle};
17
18use crate::utils::RateLimiter;
19
20#[derive(Debug, Clone, Copy)]
22pub struct TokenBucketConfig {
23 pub initial_tokens: u64,
25 pub tokens_per_interval: u64,
27 pub replenish_interval: Duration,
29 pub max_tokens: u64,
31}
32
33impl Default for TokenBucketConfig {
34 fn default() -> Self {
35 Self {
36 initial_tokens: 100,
37 tokens_per_interval: 10,
38 replenish_interval: Duration::from_secs(1),
39 max_tokens: 100,
40 }
41 }
42}
43
44pub struct TokenBucket {
46 tokens: Arc<AtomicU64>,
47 config: Arc<RwLock<TokenBucketConfig>>,
48 task_handle: Option<JoinHandle<()>>,
49 shutdown_flag: Arc<AtomicBool>,
50}
51
52impl std::fmt::Debug for TokenBucket {
53 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54 f.debug_struct("TokenBucket")
55 .field("tokens", &self.tokens.load(Ordering::Acquire))
56 .field("config", &self.config.blocking_read())
57 .field("shutdown", &self.shutdown_flag.load(Ordering::Acquire))
58 .finish()
59 }
60}
61
62impl TokenBucket {
63 pub fn new(config: TokenBucketConfig) -> Self {
65 let tokens = Arc::new(AtomicU64::new(config.initial_tokens));
66 Self {
67 tokens,
68 config: Arc::new(RwLock::new(config)),
69 task_handle: None,
70 shutdown_flag: Arc::new(AtomicBool::new(false)),
71 }
72 }
73
74 pub fn initialize(config: TokenBucketConfig) -> Self {
77 let mut limiter = Self::new(config);
78 let handle = limiter.start();
79 limiter.task_handle = Some(handle);
80 limiter
81 }
82
83 pub async fn get_config(&self) -> TokenBucketConfig {
85 let config_guard = self.config.read().await;
86 *config_guard
87 }
88
89 pub async fn update_config(&self, new_config: TokenBucketConfig) {
92 let mut config_guard = self.config.write().await;
93 *config_guard = new_config;
94 }
95
96 pub async fn set_tokens_per_interval(&self, tokens_per_interval: u64) {
98 let mut config_guard = self.config.write().await;
99 config_guard.tokens_per_interval = tokens_per_interval;
100 }
101
102 pub async fn set_replenish_interval(&self, replenish_interval: Duration) {
104 let mut config_guard = self.config.write().await;
105 config_guard.replenish_interval = replenish_interval;
106 }
107
108 pub async fn set_max_tokens(&self, max_tokens: u64) {
110 let mut config_guard = self.config.write().await;
111 config_guard.max_tokens = max_tokens;
112 }
113
114 pub async fn get_config_all<'a, I: IntoIterator<Item = &'a Self>>(
118 rate_limiters: I,
119 ) -> Vec<TokenBucketConfig> {
120 join_all(
121 rate_limiters
122 .into_iter()
123 .map(|limiter| async move { limiter.get_config().await }),
124 )
125 .await
126 }
127
128 pub async fn update_config_all<'a, I: IntoIterator<Item = &'a Self>>(
131 rate_limiters: I,
132 new_config: TokenBucketConfig,
133 ) {
134 join_all(
135 rate_limiters
136 .into_iter()
137 .map(|limiter| limiter.update_config(new_config)),
138 )
139 .await;
140 }
141
142 pub async fn set_tokens_per_interval_all<'a, I: IntoIterator<Item = &'a Self>>(
144 rate_limiters: I,
145 tokens_per_interval: u64,
146 ) {
147 join_all(
148 rate_limiters
149 .into_iter()
150 .map(|limiter| limiter.set_tokens_per_interval(tokens_per_interval)),
151 )
152 .await;
153 }
154
155 pub async fn set_replenish_interval_all<'a, I: IntoIterator<Item = &'a Self>>(
157 rate_limiters: I,
158 replenish_interval: Duration,
159 ) {
160 join_all(
161 rate_limiters
162 .into_iter()
163 .map(|limiter| limiter.set_replenish_interval(replenish_interval)),
164 )
165 .await;
166 }
167
168 pub async fn set_max_tokens_all<'a, I: IntoIterator<Item = &'a Self>>(
170 rate_limiters: I,
171 max_tokens: u64,
172 ) {
173 join_all(
174 rate_limiters
175 .into_iter()
176 .map(|limiter| limiter.set_max_tokens(max_tokens)),
177 )
178 .await;
179 }
180}
181
182impl TokenBucket {
183 pub fn start(&mut self) -> JoinHandle<()> {
184 let tokens = self.tokens.clone();
185 let config = self.config.clone();
186 let shutdown_flag = self.shutdown_flag.clone();
187
188 tokio::spawn(async move {
189 loop {
190 if shutdown_flag.load(Ordering::Acquire) {
192 break;
193 }
194
195 let current_config = *config.read().await;
197
198 tokio::time::sleep(current_config.replenish_interval).await;
200
201 let _ = tokens.fetch_update(Ordering::AcqRel, Ordering::Acquire, |current| {
203 let new_value = std::cmp::min(
204 current.saturating_add(current_config.tokens_per_interval),
205 current_config.max_tokens,
206 );
207 Some(new_value)
208 });
209 }
210 })
211 }
212
213 pub async fn stop(&mut self) {
214 self.shutdown_flag.store(true, Ordering::Release);
215 if let Some(handle) = self.task_handle.take() {
216 let _ = handle.await;
217 }
218 }
219
220 pub fn get_tokens(&self) -> &Arc<AtomicU64> {
221 &self.tokens
222 }
223}
224
225impl RateLimiter for TokenBucket {
226 type TokenType = u64;
227
228 fn try_consume(&self, tokens: u64) -> bool {
231 self.tokens
232 .fetch_update(Ordering::AcqRel, Ordering::Acquire, |current| {
233 if current >= tokens {
234 Some(current - tokens)
235 } else {
236 None
237 }
238 })
239 .is_ok()
240 }
241
242 fn available_tokens(&self) -> u64 {
244 self.tokens.load(Ordering::Acquire)
245 }
246}
247
248impl Drop for TokenBucket {
249 fn drop(&mut self) {
250 self.shutdown_flag.store(true, Ordering::Release);
252 if let Some(handle) = self.task_handle.take() {
254 handle.abort();
255 }
256 }
257}
258
259#[cfg(test)]
260mod tests {
261 use std::time::Instant;
262
263 use tokio::time::sleep;
264
265 use super::*;
266
267 #[tokio::test]
268 async fn test_initial_tokens() {
269 let config = TokenBucketConfig {
270 initial_tokens: 50,
271 tokens_per_interval: 10,
272 replenish_interval: Duration::from_millis(100),
273 max_tokens: 100,
274 };
275
276 let limiter = TokenBucket::initialize(config);
277 assert_eq!(limiter.available_tokens(), 50);
278 }
279
280 #[tokio::test]
281 async fn test_try_consume_success() {
282 let config = TokenBucketConfig {
283 initial_tokens: 50,
284 tokens_per_interval: 10,
285 replenish_interval: Duration::from_secs(1),
286 max_tokens: 100,
287 };
288
289 let limiter = TokenBucket::initialize(config);
290
291 assert!(limiter.try_consume(20));
293 assert_eq!(limiter.available_tokens(), 30);
294
295 assert!(limiter.try_consume(30));
297 assert_eq!(limiter.available_tokens(), 0);
298 }
299
300 #[tokio::test]
301 async fn test_try_consume_failure() {
302 let config = TokenBucketConfig {
303 initial_tokens: 10,
304 tokens_per_interval: 5,
305 replenish_interval: Duration::from_secs(1),
306 max_tokens: 100,
307 };
308
309 let limiter = TokenBucket::initialize(config);
310
311 assert!(limiter.try_consume(5));
313 assert_eq!(limiter.available_tokens(), 5);
314
315 assert!(!limiter.try_consume(10));
317 assert_eq!(limiter.available_tokens(), 5);
318 }
319
320 #[tokio::test]
321 async fn test_token_replenishment() {
322 let config = TokenBucketConfig {
323 initial_tokens: 10,
324 tokens_per_interval: 20,
325 replenish_interval: Duration::from_millis(100),
326 max_tokens: 100,
327 };
328
329 let limiter = TokenBucket::initialize(config);
330
331 assert!(limiter.try_consume(10));
333 assert_eq!(limiter.available_tokens(), 0);
334
335 sleep(Duration::from_millis(150)).await;
337
338 let tokens = limiter.available_tokens();
340 assert!(tokens >= 20, "Expected at least 20 tokens, got {tokens}");
341 }
342
343 #[tokio::test]
344 async fn test_max_tokens_cap() {
345 let config = TokenBucketConfig {
346 initial_tokens: 90,
347 tokens_per_interval: 20,
348 replenish_interval: Duration::from_millis(100),
349 max_tokens: 100,
350 };
351
352 let limiter = TokenBucket::initialize(config);
353
354 sleep(Duration::from_millis(150)).await;
356
357 let tokens = limiter.available_tokens();
359 assert!(tokens <= 100, "Tokens exceeded max: {tokens}");
360 assert_eq!(tokens, 100, "Expected tokens to be capped at 100");
361 }
362
363 #[tokio::test]
364 async fn test_dynamic_config_update() {
365 let config = TokenBucketConfig {
366 initial_tokens: 10,
367 tokens_per_interval: 5,
368 replenish_interval: Duration::from_millis(100),
369 max_tokens: 50,
370 };
371
372 let limiter = TokenBucket::initialize(config);
373
374 assert!(limiter.try_consume(10));
376 assert_eq!(limiter.available_tokens(), 0);
377
378 let new_config = TokenBucketConfig {
380 initial_tokens: 10,
381 tokens_per_interval: 30,
382 replenish_interval: Duration::from_millis(100),
383 max_tokens: 50,
384 };
385 limiter.update_config(new_config).await;
386
387 sleep(Duration::from_millis(150)).await;
389
390 let tokens = limiter.available_tokens();
392 assert!(tokens >= 30, "Expected at least 30 tokens, got {tokens}");
393 }
394
395 #[tokio::test]
396 async fn test_concurrent_consumption() {
397 let config = TokenBucketConfig {
398 initial_tokens: 1000,
399 tokens_per_interval: 100,
400 replenish_interval: Duration::from_millis(100),
401 max_tokens: 1000,
402 };
403
404 let limiter = TokenBucket::initialize(config);
405 let tokens = limiter.get_tokens();
406 let mut handles = vec![];
407
408 for _ in 0..10 {
410 let tokens = tokens.clone();
411 let handle = tokio::spawn(async move {
412 for _ in 0..10 {
413 tokens.try_consume(10);
414 sleep(Duration::from_millis(5)).await;
415 }
416 });
417 handles.push(handle);
418 }
419
420 for handle in handles {
422 handle.await.unwrap();
423 }
424
425 let tokens = limiter.available_tokens();
427 assert!(tokens <= 1000, "Tokens exceeded max");
428 }
429
430 #[tokio::test]
431 async fn test_rate_limiting_behavior() {
432 let config = TokenBucketConfig {
433 initial_tokens: 5,
434 tokens_per_interval: 5,
435 replenish_interval: Duration::from_millis(100),
436 max_tokens: 10,
437 };
438
439 let limiter = TokenBucket::initialize(config);
440 let start = Instant::now();
441
442 assert!(limiter.try_consume(5));
444
445 assert!(!limiter.try_consume(5));
447
448 while limiter.available_tokens() < 5 {
450 sleep(Duration::from_millis(10)).await;
451 }
452
453 let elapsed = start.elapsed();
454
455 assert!(elapsed >= Duration::from_millis(100));
457
458 assert!(limiter.try_consume(5));
460 }
461
462 #[tokio::test]
463 async fn test_get_config() {
464 let config = TokenBucketConfig {
465 initial_tokens: 42,
466 tokens_per_interval: 13,
467 replenish_interval: Duration::from_millis(250),
468 max_tokens: 200,
469 };
470
471 let limiter = TokenBucket::initialize(config);
472 let retrieved_config = limiter.get_config().await;
473
474 assert_eq!(retrieved_config.initial_tokens, 42);
475 assert_eq!(retrieved_config.tokens_per_interval, 13);
476 assert_eq!(
477 retrieved_config.replenish_interval,
478 Duration::from_millis(250)
479 );
480 assert_eq!(retrieved_config.max_tokens, 200);
481 }
482}