Skip to main content

primitives/utils/rate_limiter/
token_bucket.rs

1//! Token Bucket Rate Limiter
2//!
3//!  This module provides an implementation of a token bucket rate limiter.
4//!  Tokens are replenished at a fixed rate, and actions can only proceed if enough
5//!  tokens are available.
6
7use 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/// Configuration for the Tocken Bucket Limiter
21#[derive(Debug, Clone, Copy)]
22pub struct TokenBucketConfig {
23    /// Initial number of tokens in the bucket
24    pub initial_tokens: u64,
25    /// Number of tokens to add per replenishment interval
26    pub tokens_per_interval: u64,
27    /// How often to replenish tokens
28    pub replenish_interval: Duration,
29    /// Maximum number of tokens the bucket can hold (capacity)
30    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
44/// Token bucket rate limiter implementation
45pub 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    /// Create a new token bucket rate limiter with the given configuration
64    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    /// Create a new token bucket rate limiter with the given configuration, and
75    /// start the replenishment task
76    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    /// Get the current configuration
84    pub async fn get_config(&self) -> TokenBucketConfig {
85        let config_guard = self.config.read().await;
86        *config_guard
87    }
88
89    /// Update the rate limiter configuration dynamically
90    /// The new configuration will take effect on the next replenishment cycle
91    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    /// Set the token refreshment rate
97    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    /// Set the replenishment interval
103    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    /// Set the maximum number of tokens
109    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    // ---------------------- Multiple Rate Limiters ------------------------ //
115
116    /// Get the current configuration for multiple rate limiters
117    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    /// Update the rate limiter configuration dynamically for multiple limiters
129    /// The new configuration will take effect on the next replenishment cycle
130    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    /// Set the token refreshment rate for multiple limiters
143    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    /// Set the replenishment interval for multiple limiters
156    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    /// Set the maximum number of tokens for multiple limiters
169    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                // Check shutdown flag
191                if shutdown_flag.load(Ordering::Acquire) {
192                    break;
193                }
194
195                // Read current config
196                let current_config = *config.read().await;
197
198                // Wait for the replenishment interval
199                tokio::time::sleep(current_config.replenish_interval).await;
200
201                // Replenish tokens atomically (add tokens up to max_tokens)
202                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    /// Try to consume a specified number of tokens
229    /// Returns true if successful, false if not enough tokens available
230    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    /// Get the number of available tokens
243    fn available_tokens(&self) -> u64 {
244        self.tokens.load(Ordering::Acquire)
245    }
246}
247
248impl Drop for TokenBucket {
249    fn drop(&mut self) {
250        // Signal shutdown
251        self.shutdown_flag.store(true, Ordering::Release);
252        // Abort the task if it's still running
253        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        // Should succeed
292        assert!(limiter.try_consume(20));
293        assert_eq!(limiter.available_tokens(), 30);
294
295        // Should succeed again
296        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        // Should succeed
312        assert!(limiter.try_consume(5));
313        assert_eq!(limiter.available_tokens(), 5);
314
315        // Should fail (not enough tokens)
316        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        // Consume all tokens
332        assert!(limiter.try_consume(10));
333        assert_eq!(limiter.available_tokens(), 0);
334
335        // Wait for replenishment
336        sleep(Duration::from_millis(150)).await;
337
338        // Tokens should be replenished
339        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        // Wait for replenishment
355        sleep(Duration::from_millis(150)).await;
356
357        // Tokens should not exceed max_tokens
358        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        // Consume some tokens
375        assert!(limiter.try_consume(10));
376        assert_eq!(limiter.available_tokens(), 0);
377
378        // Update config with faster replenishment
379        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        // Wait for replenishment with new config
388        sleep(Duration::from_millis(150)).await;
389
390        // Should have more tokens now
391        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        // Spawn multiple concurrent consumers
409        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        // Wait for all tasks to complete
421        for handle in handles {
422            handle.await.unwrap();
423        }
424
425        // All tokens should have been consumed or some replenished
426        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        // Consume all initial tokens
443        assert!(limiter.try_consume(5));
444
445        // Try to consume more - should fail
446        assert!(!limiter.try_consume(5));
447
448        // Wait for replenishment
449        while limiter.available_tokens() < 5 {
450            sleep(Duration::from_millis(10)).await;
451        }
452
453        let elapsed = start.elapsed();
454
455        // Should have taken at least one replenish interval
456        assert!(elapsed >= Duration::from_millis(100));
457
458        // Now we should be able to consume again
459        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}