1#![no_std]
39#![warn(missing_docs)]
40
41use core::cmp::min;
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56pub struct RateLimiter {
57 capacity: u64,
58 refill_amount: u64,
59 refill_interval: u64,
60 tokens: u64,
61 last_refill: u64,
62}
63
64impl RateLimiter {
65 pub const fn new(capacity: u64, refill_amount: u64, refill_interval: u64) -> Self {
72 let capacity = if capacity == 0 { 1 } else { capacity };
73 Self {
74 capacity,
75 refill_amount: if refill_amount == 0 { 1 } else { refill_amount },
76 refill_interval: if refill_interval == 0 {
77 1
78 } else {
79 refill_interval
80 },
81 tokens: capacity,
82 last_refill: 0,
83 }
84 }
85
86 pub const fn capacity(&self) -> u64 {
88 self.capacity
89 }
90
91 pub const fn refill_amount(&self) -> u64 {
93 self.refill_amount
94 }
95
96 pub const fn refill_interval(&self) -> u64 {
98 self.refill_interval
99 }
100
101 fn refill(&mut self, now: u64) {
103 let elapsed = now.saturating_sub(self.last_refill);
104 let batches = elapsed / self.refill_interval;
105 if batches == 0 {
106 return;
107 }
108 let added = batches.saturating_mul(self.refill_amount);
109 self.tokens = min(self.capacity, self.tokens.saturating_add(added));
110 self.last_refill = self
111 .last_refill
112 .saturating_add(batches.saturating_mul(self.refill_interval));
113 }
114
115 pub fn available(&mut self, now: u64) -> u64 {
117 self.refill(now);
118 self.tokens
119 }
120
121 pub fn try_acquire(&mut self, now: u64, tokens: u64) -> bool {
126 self.refill(now);
127 if self.tokens >= tokens {
128 self.tokens -= tokens;
129 true
130 } else {
131 false
132 }
133 }
134
135 pub fn try_acquire_one(&mut self, now: u64) -> bool {
137 self.try_acquire(now, 1)
138 }
139
140 pub fn retry_after(&mut self, now: u64, tokens: u64) -> Option<u64> {
146 if tokens > self.capacity {
147 return None;
148 }
149 self.refill(now);
150 if self.tokens >= tokens {
151 return Some(0);
152 }
153 let deficit = tokens - self.tokens;
154 let batches = deficit.div_ceil(self.refill_amount);
155 let into_interval = now.saturating_sub(self.last_refill);
156 let time_to_first = self.refill_interval.saturating_sub(into_interval);
157 Some(
158 time_to_first.saturating_add(
159 batches
160 .saturating_sub(1)
161 .saturating_mul(self.refill_interval),
162 ),
163 )
164 }
165}
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170
171 #[test]
172 fn starts_full() {
173 let mut rl = RateLimiter::new(10, 1, 100);
174 assert_eq!(rl.available(0), 10);
175 assert_eq!(rl.capacity(), 10);
176 }
177
178 #[test]
179 fn acquire_consumes_tokens() {
180 let mut rl = RateLimiter::new(10, 1, 100);
181 assert!(rl.try_acquire(0, 4));
182 assert_eq!(rl.available(0), 6);
183 }
184
185 #[test]
186 fn burst_then_denied() {
187 let mut rl = RateLimiter::new(10, 1, 100);
188 for _ in 0..10 {
189 assert!(rl.try_acquire_one(0));
190 }
191 assert!(!rl.try_acquire_one(0));
192 }
193
194 #[test]
195 fn refills_over_time_capped_at_capacity() {
196 let mut rl = RateLimiter::new(10, 1, 100);
197 assert!(rl.try_acquire(0, 10));
198 assert_eq!(rl.available(0), 0);
199 assert_eq!(rl.available(250), 2); assert_eq!(rl.available(10_000), 10); }
202
203 #[test]
204 fn partial_interval_does_not_refill_and_keeps_remainder() {
205 let mut rl = RateLimiter::new(10, 1, 100);
206 assert!(rl.try_acquire(0, 10));
207 assert_eq!(rl.available(50), 0); assert_eq!(rl.available(100), 1); }
210
211 #[test]
212 fn refill_amount_greater_than_one() {
213 let mut rl = RateLimiter::new(100, 5, 10);
214 assert!(rl.try_acquire(0, 100));
215 assert_eq!(rl.available(30), 15); }
217
218 #[test]
219 fn acquire_more_than_capacity_always_fails() {
220 let mut rl = RateLimiter::new(5, 1, 100);
221 assert!(!rl.try_acquire(0, 6));
222 assert_eq!(rl.available(0), 5); }
224
225 #[test]
226 fn retry_after_zero_when_available() {
227 let mut rl = RateLimiter::new(10, 1, 100);
228 assert_eq!(rl.retry_after(0, 5), Some(0));
229 }
230
231 #[test]
232 fn retry_after_none_when_above_capacity() {
233 let mut rl = RateLimiter::new(10, 1, 100);
234 assert_eq!(rl.retry_after(0, 11), None);
235 }
236
237 #[test]
238 fn retry_after_full_interval_when_empty() {
239 let mut rl = RateLimiter::new(10, 1, 100);
240 assert!(rl.try_acquire(0, 10));
241 assert_eq!(rl.retry_after(0, 1), Some(100));
242 assert_eq!(rl.retry_after(0, 3), Some(300));
243 }
244
245 #[test]
246 fn retry_after_accounts_for_elapsed_partial_interval() {
247 let mut rl = RateLimiter::new(10, 1, 100);
248 assert!(rl.try_acquire(0, 10));
249 assert_eq!(rl.retry_after(60, 1), Some(40));
251 assert_eq!(rl.retry_after(60, 2), Some(140));
252 }
253
254 #[test]
255 fn backwards_clock_does_not_panic_or_refill() {
256 let mut rl = RateLimiter::new(10, 1, 100);
257 assert!(rl.try_acquire(10_000, 10));
258 assert!(!rl.try_acquire(5_000, 1)); assert_eq!(rl.available(5_000), 0);
260 }
261
262 #[test]
263 fn zero_config_is_clamped() {
264 let rl = RateLimiter::new(0, 0, 0);
265 assert_eq!(rl.capacity(), 1);
266 assert_eq!(rl.refill_amount(), 1);
267 assert_eq!(rl.refill_interval(), 1);
268 }
269}