rama_utils/rate/
limiter.rs1use super::{Acquire, Rate, TokenBucket};
2
3use parking_lot::Mutex;
4use std::{
5 future::Future,
6 pin::Pin,
7 sync::Arc,
8 task::{Context, Poll},
9 time::Duration,
10};
11use tokio::sync::{Notify, futures::OwnedNotified};
12use tokio::time::Instant;
13
14#[derive(Debug, Clone)]
31pub struct RateLimiter {
32 inner: Arc<Inner>,
33}
34
35#[derive(Debug)]
40#[must_use = "futures do nothing unless awaited or polled"]
41pub struct RefundWait(Pin<Box<OwnedNotified>>);
42
43impl RefundWait {
44 fn new(notify: Arc<Notify>) -> Self {
45 let mut notified = Box::pin(notify.notified_owned());
46 notified.as_mut().enable();
47 Self(notified)
48 }
49}
50
51impl Future for RefundWait {
52 type Output = ();
53
54 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
55 self.0.as_mut().poll(cx)
56 }
57}
58
59#[derive(Debug)]
60struct Inner {
61 bucket: Mutex<TokenBucket>,
62 epoch: Instant,
63 rate: Rate,
64 burst: u64,
65 refunds: Arc<Notify>,
66}
67
68impl RateLimiter {
69 #[must_use]
76 pub fn new(rate: Rate, burst: u64) -> Self {
77 Self::from_bucket(TokenBucket::new(rate, burst))
78 }
79
80 #[must_use]
83 pub fn from_rate(rate: Rate) -> Self {
84 Self::from_bucket(TokenBucket::from_rate(rate))
85 }
86
87 fn from_bucket(bucket: TokenBucket) -> Self {
88 Self {
89 inner: Arc::new(Inner {
90 rate: bucket.rate(),
91 burst: bucket.burst(),
92 bucket: Mutex::new(bucket),
93 epoch: Instant::now(),
94 refunds: Arc::new(Notify::new()),
95 }),
96 }
97 }
98
99 #[must_use]
101 pub fn rate(&self) -> Rate {
102 self.inner.rate
103 }
104
105 #[must_use]
107 pub fn burst(&self) -> u64 {
108 self.inner.burst
109 }
110
111 fn now_nanos(&self) -> u64 {
112 u64::try_from(
113 Instant::now()
114 .saturating_duration_since(self.inner.epoch)
115 .as_nanos(),
116 )
117 .unwrap_or(u64::MAX)
118 }
119
120 pub fn try_acquire(&self, n: u64) -> Acquire {
126 self.inner.bucket.lock().try_acquire(self.now_nanos(), n)
127 }
128
129 pub async fn acquire(&self, n: u64) {
140 let mut remaining = n;
141 while remaining > 0 {
142 let want = remaining.min(self.inner.burst);
143 let refunded = self.inner.refunds.clone().notified_owned();
144 tokio::pin!(refunded);
145 refunded.as_mut().enable();
146 match self.try_acquire(want) {
147 Acquire::Granted => {
148 remaining -= want;
149 }
150 Acquire::RetryAt(at) => {
151 tokio::select! {
152 () = tokio::time::sleep_until(self.deadline(at)) => {}
153 () = refunded.as_mut() => {}
154 }
155 }
156 Acquire::Never => {
157 debug_assert!(false, "burst-clamped chunk reported Acquire::Never");
160 break;
161 }
162 }
163 }
164 }
165
166 pub fn refund(&self, n: u64) {
169 self.inner.bucket.lock().refund(n);
170 if n > 0 {
171 self.inner.refunds.notify_waiters();
172 }
173 }
174
175 pub fn notified_on_refund(&self) -> RefundWait {
181 RefundWait::new(self.inner.refunds.clone())
182 }
183
184 #[must_use]
187 pub fn deadline(&self, retry_at_nanos: u64) -> Instant {
188 self.inner
189 .epoch
190 .checked_add(Duration::from_nanos(retry_at_nanos))
191 .unwrap_or_else(|| {
193 Instant::now()
194 .checked_add(Duration::from_hours(8_760))
195 .unwrap_or_else(Instant::now)
196 })
197 }
198}
199
200#[cfg(test)]
201mod tests {
202 use super::*;
203
204 #[tokio::test(start_paused = true)]
205 async fn acquire_paces_exactly() {
206 let limiter = RateLimiter::from_rate(Rate::per_sec(10));
207 limiter.acquire(10).await; let start = Instant::now();
210 for i in 1..=5u64 {
211 limiter.acquire(1).await;
212 assert_eq!(start.elapsed(), Duration::from_millis(i * 100));
213 }
214 }
215
216 #[tokio::test(start_paused = true)]
217 async fn acquire_chunks_oversized_requests() {
218 let limiter = RateLimiter::new(Rate::per_sec(10), 10);
219
220 let start = Instant::now();
221 limiter.acquire(25).await;
224 assert_eq!(start.elapsed(), Duration::from_millis(1_500));
225 }
226
227 #[tokio::test(start_paused = true)]
228 async fn try_acquire_never_waits() {
229 let limiter = RateLimiter::new(Rate::per_sec(2), 2);
230 assert_eq!(limiter.try_acquire(2), Acquire::Granted);
231
232 let retry_at = match limiter.try_acquire(1) {
233 Acquire::RetryAt(at) => at,
234 other => panic!("expected retry-at, got {other:?}"),
235 };
236 assert_eq!(
237 limiter.deadline(retry_at),
238 Instant::now() + Duration::from_millis(500)
239 );
240 assert_eq!(limiter.try_acquire(3), Acquire::Never);
241 }
242
243 #[tokio::test(start_paused = true)]
244 async fn clones_share_the_budget() {
245 let limiter = RateLimiter::new(Rate::per_sec(10), 2);
246 let clone = limiter.clone();
247
248 assert_eq!(limiter.rate(), Rate::per_sec(10));
249 assert_eq!(limiter.burst(), 2);
250
251 assert_eq!(limiter.try_acquire(1), Acquire::Granted);
252 assert_eq!(clone.try_acquire(1), Acquire::Granted);
253 assert!(matches!(clone.try_acquire(1), Acquire::RetryAt(_)));
254
255 limiter.refund(1);
256 assert_eq!(clone.try_acquire(1), Acquire::Granted);
257 }
258
259 #[tokio::test(start_paused = true)]
260 async fn a_refund_wakes_a_waiter_before_its_old_deadline() {
261 let limiter = RateLimiter::new(Rate::per_sec(10), 10);
262 limiter.acquire(10).await;
263
264 let waiting_limiter = limiter.clone();
265 let start = Instant::now();
266 let waiting = tokio::spawn(async move { waiting_limiter.acquire(10).await });
267 tokio::task::yield_now().await;
268
269 limiter.refund(10);
270 waiting.await.unwrap();
271 assert_eq!(start.elapsed(), Duration::ZERO);
272 }
273
274 #[tokio::test]
275 async fn refund_of_zero_does_not_wake_listeners() {
276 let limiter = RateLimiter::new(Rate::per_sec(10), 10);
277 let waiting_limiter = limiter.clone();
278 let waiting = tokio::spawn(async move { waiting_limiter.notified_on_refund().await });
279 tokio::task::yield_now().await;
280
281 limiter.refund(0);
282 tokio::task::yield_now().await;
283 assert!(!waiting.is_finished());
284
285 limiter.refund(1);
286 waiting.await.unwrap();
287 }
288
289 #[tokio::test(start_paused = true)]
290 async fn cancelling_oversized_acquire_does_not_mint_shared_budget() {
291 let limiter = RateLimiter::new(Rate::per_sec(10), 10);
292 limiter.acquire(10).await; let acquire_limiter = limiter.clone();
295 let task = tokio::spawn(async move { acquire_limiter.acquire(20).await });
296 tokio::task::yield_now().await;
297
298 tokio::time::advance(Duration::from_secs(1)).await;
302 tokio::task::yield_now().await;
303 tokio::time::advance(Duration::from_millis(500)).await;
304 tokio::task::yield_now().await;
305 assert_eq!(limiter.try_acquire(5), Acquire::Granted);
306
307 task.abort();
308 let _cancelled = task.await;
309
310 assert!(matches!(limiter.try_acquire(1), Acquire::RetryAt(_)));
313 }
314
315 #[tokio::test(start_paused = true)]
316 async fn acquire_within_burst_is_cancel_safe() {
317 let limiter = RateLimiter::new(Rate::per_sec(10), 10);
318 limiter.acquire(10).await; let cancelled = tokio::time::timeout(Duration::from_millis(500), limiter.acquire(10)).await;
323 cancelled.unwrap_err();
324 assert_eq!(limiter.try_acquire(5), Acquire::Granted);
326 }
327
328 #[tokio::test(start_paused = true)]
329 async fn concurrent_waiters_all_proceed() {
330 let limiter = RateLimiter::from_rate(Rate::per_sec(10));
331 limiter.acquire(10).await;
332
333 let start = Instant::now();
334 let mut set = tokio::task::JoinSet::new();
335 for _ in 0..5 {
336 let limiter = limiter.clone();
337 set.spawn(async move { limiter.acquire(1).await });
338 }
339 while let Some(res) = set.join_next().await {
340 res.unwrap();
341 }
342 assert_eq!(start.elapsed(), Duration::from_millis(500));
344 }
345}