1use super::{Acquire, Rate};
2
3#[derive(Debug, Clone)]
29pub struct TokenBucket {
30 rate: Rate,
31 burst: u64,
32 level_scaled: u128,
34 last_refill: u64,
35}
36
37impl TokenBucket {
38 #[must_use]
47 pub fn new(rate: Rate, burst: u64) -> Self {
48 assert!(burst > 0, "TokenBucket: burst must be non-zero");
49 Self {
50 rate,
51 burst,
52 level_scaled: Self::capacity_scaled_for(rate, burst),
53 last_refill: 0,
54 }
55 }
56
57 #[must_use]
60 pub fn from_rate(rate: Rate) -> Self {
61 Self::new(rate, rate.units())
62 }
63
64 #[must_use]
66 pub const fn rate(&self) -> Rate {
67 self.rate
68 }
69
70 #[must_use]
72 pub const fn burst(&self) -> u64 {
73 self.burst
74 }
75
76 pub fn try_acquire(&mut self, now: u64, n: u64) -> Acquire {
89 if n > self.burst {
90 return Acquire::Never;
91 }
92 self.refill(now);
93
94 let need = n as u128 * self.rate.per_nanos() as u128;
95 if self.level_scaled >= need {
96 self.level_scaled -= need;
97 return Acquire::Granted;
98 }
99
100 let deficit = need - self.level_scaled;
101 let wait_nanos = deficit.div_ceil(self.rate.units() as u128);
102 let retry_at = now.saturating_add(u64::try_from(wait_nanos).unwrap_or(u64::MAX));
103 Acquire::RetryAt(retry_at)
104 }
105
106 pub fn refund(&mut self, n: u64) {
112 let scaled = n as u128 * self.rate.per_nanos() as u128;
113 self.level_scaled = self
114 .level_scaled
115 .saturating_add(scaled)
116 .min(self.capacity_scaled());
117 }
118
119 pub fn available(&mut self, now: u64) -> u64 {
122 self.refill(now);
123 (self.level_scaled / self.rate.per_nanos() as u128) as u64
125 }
126
127 fn refill(&mut self, now: u64) {
128 let elapsed = now.saturating_sub(self.last_refill);
129 if elapsed == 0 {
130 return;
131 }
132 self.level_scaled = self
133 .level_scaled
134 .saturating_add(elapsed as u128 * self.rate.units() as u128)
135 .min(self.capacity_scaled());
136 self.last_refill = now;
137 }
138
139 fn capacity_scaled(&self) -> u128 {
140 Self::capacity_scaled_for(self.rate, self.burst)
141 }
142
143 fn capacity_scaled_for(rate: Rate, burst: u64) -> u128 {
144 burst as u128 * rate.per_nanos() as u128
145 }
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151 use core::time::Duration;
152
153 const SEC: u64 = 1_000_000_000;
154
155 #[test]
156 fn starts_full_and_drains() {
157 let mut bucket = TokenBucket::from_rate(Rate::per_sec(10));
158 assert_eq!(bucket.burst(), 10);
159 for _ in 0..10 {
160 assert_eq!(bucket.try_acquire(0, 1), Acquire::Granted);
161 }
162 assert_eq!(bucket.try_acquire(0, 1), Acquire::RetryAt(SEC / 10));
163 }
164
165 #[test]
166 fn retry_at_is_exact() {
167 let mut bucket = TokenBucket::from_rate(Rate::per_sec(10));
168 assert_eq!(bucket.try_acquire(0, 10), Acquire::Granted);
169
170 let at = match bucket.try_acquire(0, 3) {
171 Acquire::RetryAt(at) => at,
172 other => panic!("expected retry-at, got {other:?}"),
173 };
174 assert_eq!(at, 3 * SEC / 10);
175 assert!(matches!(
177 bucket.try_acquire(at - 1, 3),
178 Acquire::RetryAt(again) if again == at
179 ));
180 assert_eq!(bucket.try_acquire(at, 3), Acquire::Granted);
182 }
183
184 #[test]
185 fn burst_capacity_is_a_hard_cap() {
186 let mut bucket = TokenBucket::new(Rate::per_sec(10), 4);
187 assert_eq!(bucket.try_acquire(0, 5), Acquire::Never);
188 assert_eq!(bucket.try_acquire(100 * SEC, 5), Acquire::Never);
190 assert_eq!(bucket.try_acquire(100 * SEC, 4), Acquire::Granted);
191 }
192
193 #[test]
194 fn zero_units_always_granted() {
195 let mut bucket = TokenBucket::new(Rate::per_sec(1), 1);
196 assert_eq!(bucket.try_acquire(0, 1), Acquire::Granted);
197 assert_eq!(bucket.try_acquire(0, 0), Acquire::Granted);
198 }
199
200 #[test]
201 fn available_tracks_partial_refill() {
202 let mut bucket = TokenBucket::from_rate(Rate::per_sec(10));
203 assert_eq!(bucket.available(0), 10);
204 assert_eq!(bucket.try_acquire(0, 10), Acquire::Granted);
205 assert_eq!(bucket.available(0), 0);
206 assert_eq!(bucket.available(550_000_000), 5);
207 assert_eq!(bucket.available(SEC), 10);
208 assert_eq!(bucket.available(100 * SEC), 10);
210 }
211
212 #[test]
213 fn refund_saturates_at_capacity() {
214 let mut bucket = TokenBucket::from_rate(Rate::per_sec(10));
215 assert_eq!(bucket.try_acquire(0, 3), Acquire::Granted);
216 assert_eq!(bucket.available(0), 7);
217 bucket.refund(2);
218 assert_eq!(bucket.available(0), 9);
219 bucket.refund(100);
220 assert_eq!(bucket.available(0), 10);
221 }
222
223 #[test]
224 fn fractional_refill_has_no_drift() {
225 let mut bucket = TokenBucket::from_rate(Rate::per_sec(3));
228 assert_eq!(bucket.try_acquire(0, 3), Acquire::Granted);
229
230 let mut now = 0;
231 for i in 1..=3 {
232 let at = match bucket.try_acquire(now, 1) {
233 Acquire::RetryAt(at) => at,
234 other => panic!("expected retry-at, got {other:?}"),
235 };
236 assert_eq!(at, (i * SEC as u128).div_ceil(3) as u64);
237 assert_eq!(bucket.try_acquire(at, 1), Acquire::Granted);
238 now = at;
239 }
240 assert_eq!(now, SEC);
242 assert_eq!(
244 bucket.try_acquire(now, 1),
245 Acquire::RetryAt(now + SEC / 3 + 1)
246 );
247 }
248
249 #[test]
250 fn time_regression_is_ignored() {
251 let mut bucket = TokenBucket::from_rate(Rate::per_sec(10));
252 assert_eq!(bucket.try_acquire(SEC, 10), Acquire::Granted);
253 assert_eq!(bucket.available(0), 0);
255 assert!(matches!(bucket.try_acquire(0, 1), Acquire::RetryAt(_)));
256 }
257
258 #[test]
259 fn sub_second_period() {
260 let mut bucket = TokenBucket::from_rate(Rate::new(5, Duration::from_millis(100)));
261 assert_eq!(bucket.try_acquire(0, 5), Acquire::Granted);
262 assert_eq!(bucket.try_acquire(0, 5), Acquire::RetryAt(100_000_000));
263 assert_eq!(bucket.try_acquire(100_000_000, 5), Acquire::Granted);
264 }
265
266 #[test]
267 #[should_panic(expected = "burst must be non-zero")]
268 fn zero_burst_panics() {
269 assert_eq!(TokenBucket::new(Rate::per_sec(1), 0).burst(), 0);
270 }
271
272 mod properties {
273 use super::*;
274 use quickcheck::{Arbitrary, Gen, quickcheck};
275
276 #[derive(Debug, Clone, Copy)]
277 struct Op {
278 advance_nanos: u32,
279 n: u16,
280 }
281
282 impl Arbitrary for Op {
283 fn arbitrary(g: &mut Gen) -> Self {
284 Self {
285 advance_nanos: u32::arbitrary(g),
286 n: u16::arbitrary(g),
287 }
288 }
289 }
290
291 quickcheck! {
292 fn never_exceeds_rate(units: u64, per_millis: u16, burst: u16, ops: Vec<Op>) -> bool {
294 let units = units % 1_000 + 1;
295 let per = Duration::from_millis(u64::from(per_millis) + 1);
296 let burst = u64::from(burst) + 1;
297 let rate = Rate::new(units, per);
298 let mut bucket = TokenBucket::new(rate, burst);
299
300 let mut now = 0u64;
301 let mut granted = 0u128;
302 for op in ops {
303 now = now.saturating_add(u64::from(op.advance_nanos));
304 let n = u64::from(op.n) % (burst + 1);
305 if bucket.try_acquire(now, n) == Acquire::Granted {
306 granted += u128::from(n);
307 }
308 }
309
310 let budget_scaled =
311 u128::from(burst) * u128::from(rate.per_nanos())
312 + u128::from(now) * u128::from(units);
313 granted * u128::from(rate.per_nanos()) <= budget_scaled
314 }
315
316 fn retry_at_is_tight(units: u64, per_millis: u16, burst: u16, drain: u16, n: u16) -> bool {
319 let units = units % 1_000 + 1;
320 let per = Duration::from_millis(u64::from(per_millis) + 1);
321 let burst = u64::from(burst) + 1;
322 let mut bucket = TokenBucket::new(Rate::new(units, per), burst);
323
324 let _ = bucket.try_acquire(0, u64::from(drain) % (burst + 1));
325 let n = u64::from(n) % burst + 1;
326 match bucket.try_acquire(0, n) {
327 Acquire::Granted | Acquire::Never => true,
328 Acquire::RetryAt(at) => {
329 let denied_before = at == 0
330 || matches!(bucket.clone().try_acquire(at - 1, n), Acquire::RetryAt(_));
331 let granted_at = bucket.try_acquire(at, n) == Acquire::Granted;
332 denied_before && granted_at
333 }
334 }
335 }
336
337 fn available_bounded_by_burst(units: u64, burst: u16, now: u64, refund: u64) -> bool {
339 let units = units % 1_000 + 1;
340 let burst = u64::from(burst) + 1;
341 let mut bucket = TokenBucket::new(Rate::per_sec(units), burst);
342 bucket.refund(refund);
343 bucket.available(now) <= burst
344 }
345 }
346 }
347}