dome_throttle/
token_bucket.rs1use tokio::time::Instant;
2
3#[derive(Debug, Clone)]
9pub struct TokenBucket {
10 pub tokens: f64,
11 pub max_tokens: f64,
12 pub refill_rate: f64,
13 pub last_refill: Instant,
14}
15
16impl TokenBucket {
17 pub fn new(max_tokens: f64, refill_rate: f64) -> Self {
19 Self {
20 tokens: max_tokens,
21 max_tokens,
22 refill_rate,
23 last_refill: Instant::now(),
24 }
25 }
26
27 pub fn new_at(max_tokens: f64, refill_rate: f64, now: Instant) -> Self {
29 Self {
30 tokens: max_tokens,
31 max_tokens,
32 refill_rate,
33 last_refill: now,
34 }
35 }
36
37 pub fn try_acquire(&mut self) -> bool {
40 self.try_acquire_at(Instant::now())
41 }
42
43 pub fn try_acquire_at(&mut self, now: Instant) -> bool {
45 self.refill(now);
46 if self.tokens >= 1.0 {
47 self.tokens -= 1.0;
48 true
49 } else {
50 false
51 }
52 }
53
54 fn refill(&mut self, now: Instant) {
56 let elapsed = now.duration_since(self.last_refill);
57 let added = elapsed.as_secs_f64() * self.refill_rate;
58 if added > 0.0 {
59 self.tokens = (self.tokens + added).min(self.max_tokens);
60 self.last_refill = now;
61 }
62 }
63
64 pub fn available(&mut self) -> f64 {
66 self.refill(Instant::now());
67 self.tokens
68 }
69}
70
71#[cfg(test)]
72mod tests {
73 use super::*;
74 use std::time::Duration;
75
76 #[tokio::test(start_paused = true)]
77 async fn allows_requests_within_limit() {
78 let now = Instant::now();
79 let mut bucket = TokenBucket::new_at(5.0, 1.0, now);
80
81 for i in 0..5 {
83 assert!(bucket.try_acquire_at(now), "request {i} should be allowed");
84 }
85 }
86
87 #[tokio::test(start_paused = true)]
88 async fn denies_when_exhausted() {
89 let now = Instant::now();
90 let mut bucket = TokenBucket::new_at(3.0, 1.0, now);
91
92 assert!(bucket.try_acquire_at(now));
94 assert!(bucket.try_acquire_at(now));
95 assert!(bucket.try_acquire_at(now));
96
97 assert!(!bucket.try_acquire_at(now), "should deny when exhausted");
99 }
100
101 #[tokio::test(start_paused = true)]
102 async fn refills_over_time() {
103 let now = Instant::now();
104 let mut bucket = TokenBucket::new_at(3.0, 1.0, now);
105
106 assert!(bucket.try_acquire_at(now));
108 assert!(bucket.try_acquire_at(now));
109 assert!(bucket.try_acquire_at(now));
110 assert!(!bucket.try_acquire_at(now));
111
112 let later = now + Duration::from_secs(2);
114 assert!(bucket.try_acquire_at(later), "should allow after refill");
115 assert!(
116 bucket.try_acquire_at(later),
117 "second token should be available"
118 );
119 assert!(
120 !bucket.try_acquire_at(later),
121 "third should fail, only 2 refilled"
122 );
123 }
124
125 #[tokio::test(start_paused = true)]
126 async fn does_not_exceed_max() {
127 let now = Instant::now();
128 let mut bucket = TokenBucket::new_at(3.0, 10.0, now);
129
130 let later = now + Duration::from_secs(100);
132 bucket.refill(later);
133
134 assert!(bucket.tokens <= bucket.max_tokens);
136 assert!((bucket.tokens - 3.0).abs() < f64::EPSILON);
137 }
138
139 #[tokio::test(start_paused = true)]
140 async fn partial_refill() {
141 let now = Instant::now();
142 let mut bucket = TokenBucket::new_at(10.0, 2.0, now);
143
144 for _ in 0..5 {
146 assert!(bucket.try_acquire_at(now));
147 }
148 let later = now + Duration::from_millis(1000);
152 assert!(bucket.try_acquire_at(later)); assert!(bucket.try_acquire_at(later)); assert!(bucket.try_acquire_at(later)); assert!(bucket.try_acquire_at(later)); assert!(bucket.try_acquire_at(later)); assert!(bucket.try_acquire_at(later)); assert!(bucket.try_acquire_at(later)); assert!(!bucket.try_acquire_at(later)); }
161}