1use std::{
2 fmt,
3 future::Future,
4 pin::Pin,
5 task::{Context, Poll, ready},
6};
7
8use pin_project_lite::pin_project;
9use rama_utils::rate::{Acquire, Rate, RateLimiter, RefundWait};
10use tokio::time::{Sleep, sleep_until};
11
12use crate::bytes::{Bytes, BytesMut};
13use crate::futures::{Sink, Stream};
14
15pin_project! {
16 #[derive(Debug)]
42 pub struct PacedSink<S, C = ()> {
43 #[pin]
44 sink: S,
45 limiter: RateLimiter,
46 debt: u64,
47 sleep: Option<Pin<Box<Sleep>>>,
48 sleeping: bool,
49 refund_wait: Option<RefundWait>,
50 cost: C,
51 }
52}
53
54impl<S> PacedSink<S> {
55 pub fn new(sink: S, rate: Rate) -> Self {
58 Self::with_limiter(sink, RateLimiter::from_rate(rate))
59 }
60
61 pub fn with_limiter(sink: S, limiter: RateLimiter) -> Self {
65 Self {
66 sink,
67 limiter,
68 debt: 0,
69 sleep: None,
70 sleeping: false,
71 refund_wait: None,
72 cost: (),
73 }
74 }
75
76 rama_utils::macros::generate_set_and_with! {
77 pub fn burst(mut self, burst: u64) -> Self {
82 self.limiter = RateLimiter::new(self.limiter.rate(), burst);
83 self
84 }
85 }
86}
87
88impl<S, C> PacedSink<S, C> {
89 pub fn with_cost_fn<F>(self, cost_fn: F) -> PacedSink<S, CostFn<F>> {
94 PacedSink {
95 sink: self.sink,
96 limiter: self.limiter,
97 debt: self.debt,
98 sleep: self.sleep,
99 sleeping: self.sleeping,
100 refund_wait: self.refund_wait,
101 cost: CostFn(cost_fn),
102 }
103 }
104
105 #[must_use]
108 pub fn limiter(&self) -> &RateLimiter {
109 &self.limiter
110 }
111
112 pub fn into_inner(self) -> S {
114 self.sink
115 }
116
117 pub fn get_ref(&self) -> &S {
119 &self.sink
120 }
121}
122
123pub trait ItemCost<I> {
128 fn cost_of(&self, item: &I) -> u64;
130}
131
132impl<I: DatagramCost> ItemCost<I> for () {
133 fn cost_of(&self, item: &I) -> u64 {
134 item.cost()
135 }
136}
137
138pub struct CostFn<F>(F);
141
142impl<F> fmt::Debug for CostFn<F> {
143 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
144 f.debug_struct("CostFn").finish()
145 }
146}
147
148impl<I, F: Fn(&I) -> u64> ItemCost<I> for CostFn<F> {
149 fn cost_of(&self, item: &I) -> u64 {
150 (self.0)(item)
151 }
152}
153
154pub trait DatagramCost {
160 fn cost(&self) -> u64;
162}
163
164impl DatagramCost for Bytes {
165 fn cost(&self) -> u64 {
166 self.len() as u64
167 }
168}
169
170impl DatagramCost for BytesMut {
171 fn cost(&self) -> u64 {
172 self.len() as u64
173 }
174}
175
176impl DatagramCost for Vec<u8> {
177 fn cost(&self) -> u64 {
178 self.len() as u64
179 }
180}
181
182impl DatagramCost for Box<[u8]> {
183 fn cost(&self) -> u64 {
184 self.len() as u64
185 }
186}
187
188impl DatagramCost for &[u8] {
189 fn cost(&self) -> u64 {
190 self.len() as u64
191 }
192}
193
194impl DatagramCost for String {
195 fn cost(&self) -> u64 {
196 self.len() as u64
197 }
198}
199
200impl DatagramCost for &str {
201 fn cost(&self) -> u64 {
202 self.len() as u64
203 }
204}
205
206impl<T: DatagramCost, A> DatagramCost for (T, A) {
207 fn cost(&self) -> u64 {
208 self.0.cost()
209 }
210}
211
212fn poll_debt(
213 limiter: &RateLimiter,
214 debt: &mut u64,
215 sleep: &mut Option<Pin<Box<Sleep>>>,
216 sleeping: &mut bool,
217 refund_wait: &mut Option<RefundWait>,
218 cx: &mut Context<'_>,
219) -> Poll<()> {
220 while *debt > 0 {
221 if refund_wait
222 .as_mut()
223 .is_some_and(|wait| Pin::new(wait).poll(cx).is_ready())
224 {
225 *refund_wait = None;
226 *sleeping = false;
227 continue;
228 }
229 let want = (*debt).min(limiter.burst());
230 let mut acquire = limiter.try_acquire(want);
231 if matches!(acquire, Acquire::RetryAt(_)) && refund_wait.is_none() {
232 *refund_wait = Some(limiter.notified_on_refund());
233 if refund_wait
234 .as_mut()
235 .is_some_and(|wait| Pin::new(wait).poll(cx).is_ready())
236 {
237 *refund_wait = None;
238 *sleeping = false;
239 continue;
240 }
241 acquire = limiter.try_acquire(want);
242 }
243 match acquire {
244 Acquire::Granted => {
245 *refund_wait = None;
246 *sleeping = false;
247 *debt -= want;
248 }
249 Acquire::RetryAt(at) => {
250 let deadline = limiter.deadline(at);
251 let sleep = sleep.get_or_insert_with(|| Box::pin(sleep_until(deadline)));
252 if !*sleeping {
253 sleep.as_mut().reset(deadline);
254 *sleeping = true;
255 }
256 ready!(sleep.as_mut().poll(cx));
257 *sleeping = false;
258 }
259 Acquire::Never => {
260 debug_assert!(false, "burst-clamped repayment reported Acquire::Never");
261 *debt = 0;
262 }
263 }
264 }
265 Poll::Ready(())
266}
267
268impl<S, I, C> Sink<I> for PacedSink<S, C>
269where
270 S: Sink<I>,
271 C: ItemCost<I>,
272{
273 type Error = S::Error;
274
275 fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
276 let this = self.project();
277 ready!(poll_debt(
278 this.limiter,
279 this.debt,
280 this.sleep,
281 this.sleeping,
282 this.refund_wait,
283 cx,
284 ));
285 this.sink.poll_ready(cx)
286 }
287
288 fn start_send(self: Pin<&mut Self>, item: I) -> Result<(), Self::Error> {
289 let this = self.project();
290 let cost = this.cost.cost_of(&item);
293 this.sink.start_send(item)?;
294 *this.debt = this.debt.saturating_add(cost);
295 Ok(())
296 }
297
298 fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
299 let this = self.project();
300 ready!(poll_debt(
301 this.limiter,
302 this.debt,
303 this.sleep,
304 this.sleeping,
305 this.refund_wait,
306 cx,
307 ));
308 this.sink.poll_flush(cx)
309 }
310
311 fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
312 let this = self.project();
313 ready!(poll_debt(
314 this.limiter,
315 this.debt,
316 this.sleep,
317 this.sleeping,
318 this.refund_wait,
319 cx,
320 ));
321 this.sink.poll_close(cx)
322 }
323}
324
325impl<S, C> Stream for PacedSink<S, C>
326where
327 S: Stream,
328{
329 type Item = S::Item;
330
331 fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
332 self.project().sink.poll_next(cx)
333 }
334
335 #[inline(always)]
336 fn size_hint(&self) -> (usize, Option<usize>) {
337 self.sink.size_hint()
338 }
339}
340
341#[cfg(test)]
342mod tests {
343 use super::*;
344 use crate::futures::SinkExt;
345 use std::convert::Infallible;
346 use std::time::Duration;
347 use tokio::time::Instant;
348
349 #[derive(Debug, Default)]
350 struct VecSink {
351 items: Vec<Bytes>,
352 }
353
354 impl Sink<Bytes> for VecSink {
355 type Error = Infallible;
356
357 fn poll_ready(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
358 Poll::Ready(Ok(()))
359 }
360
361 fn start_send(self: Pin<&mut Self>, item: Bytes) -> Result<(), Self::Error> {
362 self.get_mut().items.push(item);
363 Ok(())
364 }
365
366 fn poll_flush(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
367 Poll::Ready(Ok(()))
368 }
369
370 fn poll_close(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
371 Poll::Ready(Ok(()))
372 }
373 }
374
375 #[derive(Debug, Default)]
376 struct PendingSink {
377 ready_polls: usize,
378 close_polls: usize,
379 }
380
381 impl Sink<Bytes> for PendingSink {
382 type Error = Infallible;
383
384 fn poll_ready(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
385 self.get_mut().ready_polls += 1;
386 Poll::Pending
387 }
388
389 fn start_send(self: Pin<&mut Self>, _: Bytes) -> Result<(), Self::Error> {
390 Ok(())
391 }
392
393 fn poll_flush(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
394 Poll::Pending
395 }
396
397 fn poll_close(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
398 self.get_mut().close_polls += 1;
399 Poll::Pending
400 }
401 }
402
403 #[test]
404 fn costers_report_exact_costs() {
405 let cost = CostFn(|value: &usize| (*value as u64) + 2);
406 assert_eq!(format!("{cost:?}"), "CostFn");
407 assert_eq!(cost.cost_of(&5), 7);
408
409 let bytes_mut = BytesMut::from(&b"abc"[..]);
410 let vec = b"abc".to_vec();
411 let boxed = Vec::from(&b"abc"[..]).into_boxed_slice();
412 let slice: &[u8] = b"abc";
413 let string = String::from("abc");
414 let str_slice: &str = "abc";
415
416 assert_eq!(bytes_mut.cost(), 3);
417 assert_eq!(vec.cost(), 3);
418 assert_eq!(boxed.cost(), 3);
419 assert_eq!(slice.cost(), 3);
420 assert_eq!(string.cost(), 3);
421 assert_eq!(str_slice.cost(), 3);
422 assert_eq!((str_slice, "address").cost(), 3);
423 }
424
425 #[test]
426 fn sink_readiness_and_close_are_delegated() {
427 let mut sink = Box::pin(PacedSink::new(PendingSink::default(), Rate::per_sec(1)));
428 let mut cx = Context::from_waker(std::task::Waker::noop());
429
430 assert!(
431 <PacedSink<PendingSink> as Sink<Bytes>>::poll_ready(sink.as_mut(), &mut cx)
432 .is_pending()
433 );
434 assert_eq!(sink.as_ref().get_ref().get_ref().ready_polls, 1);
435
436 assert!(
437 <PacedSink<PendingSink> as Sink<Bytes>>::poll_close(sink.as_mut(), &mut cx)
438 .is_pending()
439 );
440 assert_eq!(sink.as_ref().get_ref().get_ref().close_polls, 1);
441 }
442
443 #[test]
444 fn stream_size_hint_is_delegated() {
445 let paced = PacedSink::new(crate::futures::stream::iter([1u8, 2, 3]), Rate::per_sec(1));
446 assert_eq!(Stream::size_hint(&paced), (3, Some(3)));
447 }
448
449 #[tokio::test(start_paused = true)]
450 async fn paces_by_byte_cost() {
451 let mut sink = PacedSink::new(VecSink::default(), Rate::per_sec(1_000));
452 let item = || Bytes::from_static(&[0u8; 500]);
453
454 let start = Instant::now();
455 sink.send(item()).await.unwrap();
457 sink.send(item()).await.unwrap();
458 assert_eq!(start.elapsed(), Duration::ZERO);
459
460 sink.send(item()).await.unwrap();
462 assert_eq!(start.elapsed(), Duration::from_millis(500));
463
464 sink.send(item()).await.unwrap();
465 assert_eq!(start.elapsed(), Duration::from_millis(1_000));
466
467 sink.send(item()).await.unwrap();
468 assert_eq!(start.elapsed(), Duration::from_millis(1_500));
469
470 assert_eq!(sink.get_ref().items.len(), 5);
471 }
472
473 #[tokio::test(start_paused = true)]
474 async fn paces_items_with_cost_fn() {
475 let mut sink =
477 PacedSink::new(VecSink::default(), Rate::per_sec(2)).with_cost_fn(|_: &Bytes| 1);
478
479 let start = Instant::now();
480 for _ in 0..3 {
481 sink.send(Bytes::from_static(b"whatever")).await.unwrap();
482 }
483 assert_eq!(start.elapsed(), Duration::from_millis(500));
484
485 sink.send(Bytes::from_static(b"...")).await.unwrap();
486 assert_eq!(start.elapsed(), Duration::from_millis(1_000));
487
488 sink.send(Bytes::from_static(b"...")).await.unwrap();
489 assert_eq!(start.elapsed(), Duration::from_millis(1_500));
490 }
491
492 #[tokio::test(start_paused = true)]
493 async fn oversized_items_repay_in_chunks() {
494 let mut sink = PacedSink::new(VecSink::default(), Rate::per_sec(100)).with_burst(100);
495
496 let start = Instant::now();
497 sink.send(Bytes::from(vec![0u8; 250])).await.unwrap();
499 assert_eq!(start.elapsed(), Duration::from_millis(1_500));
501
502 sink.send(Bytes::from_static(b"x")).await.unwrap();
503 assert_eq!(start.elapsed(), Duration::from_millis(1_510));
504 }
505
506 #[tokio::test(start_paused = true)]
507 async fn shared_limiter_is_aggregate() {
508 let limiter = RateLimiter::from_rate(Rate::per_sec(1_000));
509 let mut sink_a = PacedSink::with_limiter(VecSink::default(), limiter.clone());
510 let mut sink_b = PacedSink::with_limiter(VecSink::default(), limiter);
511
512 let start = Instant::now();
513 sink_a.send(Bytes::from(vec![0u8; 800])).await.unwrap();
514 sink_b.send(Bytes::from(vec![0u8; 800])).await.unwrap();
515 assert_eq!(start.elapsed(), Duration::from_millis(600));
516
517 sink_a.send(Bytes::from(vec![0u8; 100])).await.unwrap();
518 assert_eq!(start.elapsed(), Duration::from_millis(700));
519
520 sink_b.send(Bytes::from(vec![0u8; 100])).await.unwrap();
521 assert_eq!(start.elapsed(), Duration::from_millis(800));
522 }
523
524 #[tokio::test(start_paused = true)]
525 async fn a_grant_replaces_a_stale_deadline() {
526 let limiter = RateLimiter::new(Rate::per_sec(100), 100);
527 assert_eq!(limiter.try_acquire(100), Acquire::Granted);
528
529 let mut debt = 100;
530 let mut sleep = None;
531 let mut sleeping = false;
532 let mut refund_wait = None;
533 let mut cx = Context::from_waker(std::task::Waker::noop());
534 assert!(
535 poll_debt(
536 &limiter,
537 &mut debt,
538 &mut sleep,
539 &mut sleeping,
540 &mut refund_wait,
541 &mut cx,
542 )
543 .is_pending()
544 );
545
546 tokio::time::advance(Duration::from_millis(10)).await;
547 debt = 1;
548 assert!(
549 poll_debt(
550 &limiter,
551 &mut debt,
552 &mut sleep,
553 &mut sleeping,
554 &mut refund_wait,
555 &mut cx,
556 )
557 .is_ready()
558 );
559 assert!(!sleeping);
560
561 debt = 10;
562 let start = Instant::now();
563 std::future::poll_fn(|cx| {
564 poll_debt(
565 &limiter,
566 &mut debt,
567 &mut sleep,
568 &mut sleeping,
569 &mut refund_wait,
570 cx,
571 )
572 })
573 .await;
574 assert_eq!(start.elapsed(), Duration::from_millis(100));
575 }
576
577 #[tokio::test(start_paused = true)]
578 async fn a_shared_refund_wakes_debt_immediately() {
579 let limiter = RateLimiter::new(Rate::per_sec(100), 100);
580 assert_eq!(limiter.try_acquire(100), Acquire::Granted);
581
582 let waiter_limiter = limiter.clone();
583 let waiter = tokio::spawn(async move {
584 let mut debt = 100;
585 let mut sleep = None;
586 let mut sleeping = false;
587 let mut refund_wait = None;
588 std::future::poll_fn(|cx| {
589 poll_debt(
590 &waiter_limiter,
591 &mut debt,
592 &mut sleep,
593 &mut sleeping,
594 &mut refund_wait,
595 cx,
596 )
597 })
598 .await;
599 });
600 tokio::task::yield_now().await;
601 assert!(!waiter.is_finished());
602
603 let start = Instant::now();
604 limiter.refund(100);
605 tokio::task::yield_now().await;
606 assert!(waiter.is_finished());
607 waiter.await.unwrap();
608 assert_eq!(start.elapsed(), Duration::ZERO);
609 }
610
611 #[tokio::test(start_paused = true)]
612 async fn final_send_spends_shared_budget_before_flush_completes() {
613 let limiter = RateLimiter::new(Rate::per_sec(100), 100);
614 let mut sink = PacedSink::with_limiter(VecSink::default(), limiter.clone());
615
616 sink.send(Bytes::from(vec![0u8; 50])).await.unwrap();
617 drop(sink);
618
619 assert_eq!(limiter.try_acquire(50), Acquire::Granted);
620 assert!(matches!(limiter.try_acquire(1), Acquire::RetryAt(_)));
621 }
622
623 #[tokio::test(start_paused = true)]
624 async fn failed_start_send_charges_no_debt() {
625 struct FlakySink {
627 reject_next: bool,
628 items: Vec<Bytes>,
629 }
630
631 impl Sink<Bytes> for FlakySink {
632 type Error = &'static str;
633
634 fn poll_ready(
635 self: Pin<&mut Self>,
636 _: &mut Context<'_>,
637 ) -> Poll<Result<(), Self::Error>> {
638 Poll::Ready(Ok(()))
639 }
640
641 fn start_send(self: Pin<&mut Self>, item: Bytes) -> Result<(), Self::Error> {
642 let this = self.get_mut();
643 if this.reject_next {
644 this.reject_next = false;
645 return Err("rejected");
646 }
647 this.items.push(item);
648 Ok(())
649 }
650
651 fn poll_flush(
652 self: Pin<&mut Self>,
653 _: &mut Context<'_>,
654 ) -> Poll<Result<(), Self::Error>> {
655 Poll::Ready(Ok(()))
656 }
657
658 fn poll_close(
659 self: Pin<&mut Self>,
660 _: &mut Context<'_>,
661 ) -> Poll<Result<(), Self::Error>> {
662 Poll::Ready(Ok(()))
663 }
664 }
665
666 let mut sink = PacedSink::new(
667 FlakySink {
668 reject_next: true,
669 items: Vec::new(),
670 },
671 Rate::per_sec(100),
672 )
673 .with_burst(100);
674
675 let start = Instant::now();
676 let err = sink.send(Bytes::from(vec![0u8; 1_000])).await.unwrap_err();
678 assert_eq!(err, "rejected");
679
680 sink.send(Bytes::from(vec![0u8; 50])).await.unwrap();
683 assert_eq!(start.elapsed(), Duration::ZERO);
684 assert_eq!(sink.get_ref().items.len(), 1);
685 }
686
687 #[tokio::test(start_paused = true)]
688 async fn stream_is_passed_through() {
689 use crate::futures::StreamExt;
690
691 let inner = crate::futures::stream::iter([1u8, 2, 3]);
692 let paced = PacedSink::new(inner, Rate::per_sec(1));
694 let items: Vec<_> = paced.collect().await;
695 assert_eq!(items, [1, 2, 3]);
696 }
697}