1use std::time::Instant;
4
5pub struct TokenBucket {
10 capacity: f64,
11 tokens: f64,
12 last_refill: Instant,
13 rate: f64,
14}
15
16impl TokenBucket {
17 pub fn new(rate: f64, burst: usize) -> Self {
21 Self {
22 capacity: burst as f64,
23 tokens: burst as f64,
24 last_refill: Instant::now(),
25 rate,
26 }
27 }
28
29 pub fn allow(&mut self) -> bool {
31 let now = Instant::now();
32 saluki_antithesis::always_or_unreachable!(
33 now >= self.last_refill,
34 "token-bucket refill clock did not move backward"
35 );
36 let elapsed = now.duration_since(self.last_refill).as_secs_f64();
37 self.tokens = (self.tokens + elapsed * self.rate).min(self.capacity);
38 self.last_refill = now.max(self.last_refill);
39 if self.tokens >= 1.0 {
40 self.tokens -= 1.0;
41 true
42 } else {
43 false
44 }
45 }
46}
47
48#[cfg(test)]
49mod tests {
50 use std::time::Duration;
51
52 use super::TokenBucket;
53
54 #[test]
55 fn full_bucket_allows_up_to_burst() {
56 let burst = 5;
57 let mut bucket = TokenBucket::new(1.0, burst);
58 for _ in 0..burst {
59 assert!(bucket.allow());
60 }
61 assert!(!bucket.allow());
62 }
63
64 #[test]
65 fn empty_bucket_refills_over_time() {
66 let mut bucket = TokenBucket::new(100.0, 1);
67 assert!(bucket.allow()); assert!(!bucket.allow()); std::thread::sleep(Duration::from_millis(20)); assert!(bucket.allow());
72 }
73
74 #[test]
75 fn refill_does_not_exceed_capacity() {
76 let burst = 3;
77 let mut bucket = TokenBucket::new(1000.0, burst);
78 assert!(bucket.allow());
79 assert!(bucket.allow());
80 assert!(bucket.allow());
81 assert!(!bucket.allow());
82
83 std::thread::sleep(Duration::from_millis(50)); for _ in 0..burst {
85 assert!(bucket.allow());
86 }
87 assert!(!bucket.allow());
88 }
89
90 #[test]
91 fn zero_rate_never_refills() {
92 let mut bucket = TokenBucket::new(0.0, 1);
93 assert!(bucket.allow()); assert!(!bucket.allow());
95 std::thread::sleep(Duration::from_millis(20));
96 assert!(!bucket.allow()); }
98}