1use std::{
23 any::Any,
24 cell::{RefCell, UnsafeCell},
25 collections::VecDeque,
26 fmt::Debug,
27 marker::PhantomData,
28 num::{NonZeroU64, NonZeroUsize},
29 rc::Rc,
30};
31
32use nautilus_core::{UnixNanos, correctness::FAILED};
33use serde::{Deserialize, Serialize};
34use ustr::Ustr;
35
36use crate::{
37 actor::{
38 Actor,
39 registry::{register_actor, try_get_actor_unchecked, with_actor_registry},
40 },
41 clock::Clock,
42 msgbus::{self, Endpoint, Handler, MStr, ShareableMessageHandler},
43 timer::{TimeEvent, TimeEventCallback},
44};
45
46const MAX_INITIAL_TIMESTAMPS_CAPACITY: usize = 1024;
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
54#[serde(deny_unknown_fields)]
55pub struct RateLimit {
56 limit: NonZeroUsize,
57 interval_ns: NonZeroU64,
58}
59
60impl RateLimit {
61 pub fn new_checked(limit: usize, interval_ns: u64) -> anyhow::Result<Self> {
67 let limit = NonZeroUsize::new(limit)
68 .ok_or_else(|| anyhow::anyhow!("Invalid limit: {limit} (must be non-zero)"))?;
69 let interval_ns = NonZeroU64::new(interval_ns).ok_or_else(|| {
70 anyhow::anyhow!("Invalid interval_ns: {interval_ns} (must be non-zero)")
71 })?;
72 Ok(Self { limit, interval_ns })
73 }
74
75 #[must_use]
81 pub fn new(limit: usize, interval_ns: u64) -> Self {
82 Self::new_checked(limit, interval_ns).expect(FAILED)
83 }
84
85 #[must_use]
87 pub const fn limit(&self) -> usize {
88 self.limit.get()
89 }
90
91 #[must_use]
93 pub const fn interval_ns(&self) -> u64 {
94 self.interval_ns.get()
95 }
96}
97
98pub struct Throttler<T, F> {
116 clock: Rc<RefCell<dyn Clock>>,
117 actor_id: Ustr,
118 timer_name: Ustr,
119 limit: NonZeroUsize,
120 interval_ns: NonZeroU64,
121 large_limit: bool,
122 buffer: VecDeque<T>,
123 timestamps: VecDeque<UnixNanos>,
124 is_limiting: bool,
125 recv_count: usize,
126 sent_count: usize,
127 output_send: F,
128 output_drop: Option<F>,
129}
130
131impl<T, F> Actor for Throttler<T, F>
132where
133 T: 'static + Debug,
134 F: Fn(T) + 'static,
135{
136 fn id(&self) -> Ustr {
137 self.actor_id
138 }
139
140 fn handle(&mut self, _msg: &dyn Any) {}
141
142 fn as_any(&self) -> &dyn Any {
143 self
144 }
145}
146
147impl<T, F> Debug for Throttler<T, F>
148where
149 T: Debug,
150{
151 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
152 f.debug_struct(stringify!(Throttler))
153 .field("actor_id", &self.actor_id)
154 .field("timer_name", &self.timer_name)
155 .field("limit", &self.limit())
156 .field("interval_ns", &self.interval_ns())
157 .field("buffer", &self.buffer)
158 .field("timestamps", &self.timestamps)
159 .field("is_limiting", &self.is_limiting)
160 .field("recv_count", &self.recv_count)
161 .field("sent_count", &self.sent_count)
162 .finish()
163 }
164}
165
166impl<T, F> Throttler<T, F>
167where
168 T: Debug,
169{
170 #[inline]
175 pub fn new(
176 rate_limit: RateLimit,
177 clock: Rc<RefCell<dyn Clock>>,
178 timer_name: &str,
179 output_send: F,
180 output_drop: Option<F>,
181 actor_id: Ustr,
182 ) -> Self {
183 Self {
184 clock,
185 actor_id,
186 timer_name: Ustr::from(format!("{timer_name}-{actor_id}").as_str()),
187 limit: rate_limit.limit,
188 interval_ns: rate_limit.interval_ns,
189 large_limit: rate_limit.limit.get() > MAX_INITIAL_TIMESTAMPS_CAPACITY,
190 buffer: VecDeque::new(),
191 timestamps: VecDeque::with_capacity(
192 rate_limit.limit.get().min(MAX_INITIAL_TIMESTAMPS_CAPACITY),
193 ),
194 is_limiting: false,
195 recv_count: 0,
196 sent_count: 0,
197 output_send,
198 output_drop,
199 }
200 }
201
202 #[inline]
215 pub(crate) fn set_timer(&self, callback: Option<TimeEventCallback>) {
216 let delta = self.delta_next();
217 self.set_timer_after(delta, callback);
218 }
219
220 #[inline]
221 fn set_timer_after(&self, delta: u64, callback: Option<TimeEventCallback>) {
222 let mut clock = self.clock.borrow_mut();
223 if clock.timer_exists(&self.timer_name) {
224 clock.cancel_timer(&self.timer_name);
225 }
226 let alert_ts = clock.timestamp_ns() + delta;
227
228 clock
229 .set_time_alert_ns(self.timer_name.as_str(), alert_ts, callback, Some(true))
230 .expect(FAILED);
231 }
232
233 #[inline]
238 pub fn delta_next(&self) -> u64 {
239 match self.timestamps.get(self.limit.get() - 1) {
240 Some(ts) => {
241 let diff = self
242 .clock
243 .borrow()
244 .timestamp_ns()
245 .as_u64()
246 .saturating_sub(ts.as_u64());
247 self.interval_ns.get().saturating_sub(diff)
248 }
249 None => 0,
250 }
251 }
252
253 #[inline]
254 fn delta_next_at(&self, now: UnixNanos) -> u64 {
255 match self.timestamps.get(self.limit.get() - 1) {
256 Some(ts) => {
257 let diff = now.as_u64().saturating_sub(ts.as_u64());
258 self.interval_ns.get().saturating_sub(diff)
259 }
260 None => 0,
261 }
262 }
263
264 #[inline]
267 pub fn reset(&mut self) {
268 self.cancel_timer_internal();
269 self.buffer.clear();
270 self.recv_count = 0;
271 self.sent_count = 0;
272 self.is_limiting = false;
273 self.timestamps.clear();
274 }
275}
276
277impl<T, F> Throttler<T, F> {
278 fn cancel_timer_internal(&self) {
285 if let Ok(mut clock) = self.clock.try_borrow_mut() {
286 clock.cancel_timer(&self.timer_name);
287 }
288 }
289
290 fn count_in_window(&self, now: UnixNanos) -> usize {
293 let interval_start = now.as_i64() - self.interval_ns.get() as i64;
294
295 if let Some(oldest) = self.timestamps.back()
296 && oldest.as_i64() > interval_start
297 {
298 return self.timestamps.len();
299 }
300
301 match self.timestamps.front() {
302 Some(newest) if newest.as_i64() > interval_start => {}
303 _ => return 0,
304 }
305
306 self.timestamps
307 .iter()
308 .take_while(|&&ts| ts.as_i64() > interval_start)
309 .count()
310 }
311
312 #[inline]
314 pub const fn limit(&self) -> usize {
315 self.limit.get()
316 }
317
318 #[inline]
320 pub const fn interval_ns(&self) -> u64 {
321 self.interval_ns.get()
322 }
323
324 #[inline]
326 pub const fn rate_limit(&self) -> RateLimit {
327 RateLimit {
328 limit: self.limit,
329 interval_ns: self.interval_ns,
330 }
331 }
332
333 #[inline]
335 pub fn qsize(&self) -> usize {
336 self.buffer.len()
337 }
338
339 #[inline]
341 pub fn used(&self) -> f64 {
342 if self.timestamps.is_empty() {
343 return 0.0;
344 }
345 let messages_in_current_interval = self.count_in_window(self.clock.borrow().timestamp_ns());
346 (messages_in_current_interval as f64) / (self.limit.get() as f64)
347 }
348
349 #[inline]
351 pub const fn is_limiting(&self) -> bool {
352 self.is_limiting
353 }
354
355 #[inline]
357 pub const fn recv_count(&self) -> usize {
358 self.recv_count
359 }
360
361 #[inline]
363 pub const fn sent_count(&self) -> usize {
364 self.sent_count
365 }
366}
367
368impl<T, F> Throttler<T, F>
369where
370 T: 'static + Debug,
371 F: Fn(T) + 'static,
372{
373 pub fn to_actor(self) -> Rc<UnsafeCell<Self>> {
374 let process_handler = ThrottlerProcess::<T, F>::new(self.actor_id);
376 msgbus::register_any(
377 process_handler.id().as_str().into(),
378 ShareableMessageHandler::from(Rc::new(process_handler) as Rc<dyn Handler<dyn Any>>),
379 );
380
381 register_actor(self)
383 }
384
385 pub fn dispose(&mut self) {
394 self.cancel_timer_internal();
395 msgbus::deregister_any(process_endpoint(self.actor_id));
396 with_actor_registry(|registry| {
397 registry.remove(&self.actor_id);
398 });
399 }
400
401 #[inline]
402 pub(crate) fn send_msg(&mut self, msg: T) {
403 let now = self.clock.borrow().timestamp_ns();
404
405 if self.timestamps.len() >= self.limit.get() {
406 self.timestamps.pop_back();
407 }
408 self.timestamps.push_front(now);
409
410 self.sent_count += 1;
411 (self.output_send)(msg);
412 }
413
414 #[inline]
422 pub fn try_reserve(&mut self, count: usize) -> bool {
423 self.recv_count += count;
424
425 if count == 0 {
426 return true;
427 }
428
429 let now = self.clock.borrow().timestamp_ns();
430 let delta = self.delta_next_at(now);
431 if self.is_limiting && delta == 0 && self.buffer.is_empty() {
432 self.is_limiting = false;
433 }
434
435 if self.is_limiting {
436 return false;
437 }
438
439 let used = self.count_in_window(now);
440
441 if self.limit.get().saturating_sub(used) < count {
442 self.is_limiting = true;
443
444 if delta > 0 {
445 self.set_timer_after(delta, Some(throttler_resume::<T, F>(self.actor_id)));
446 }
447 return false;
448 }
449
450 for _ in 0..count {
451 if self.timestamps.len() >= self.limit.get() {
452 self.timestamps.pop_back();
453 }
454 self.timestamps.push_front(now);
455 }
456 self.sent_count += count;
457 true
458 }
459
460 #[inline]
461 pub(crate) fn limit_msg(&mut self, msg: T) {
462 if self.output_drop.is_none() {
463 self.buffer.push_front(msg);
464 log::debug!("Buffering {}", self.buffer.len());
465
466 if !self.is_limiting {
467 log::debug!("Limiting");
468 let cb = Some(ThrottlerProcess::<T, F>::new(self.actor_id).get_timer_callback());
469 let delta = self.delta_next();
470 self.set_timer_after(delta, cb);
471 self.is_limiting = true;
472 }
473 } else {
474 log::debug!("Dropping");
475
476 if let Some(drop) = &self.output_drop {
477 drop(msg);
478 }
479
480 if !self.is_limiting {
481 log::debug!("Limiting");
482 let delta = self.delta_next();
483 self.set_timer_after(delta, Some(throttler_resume::<T, F>(self.actor_id)));
484 self.is_limiting = true;
485 }
486 }
487 }
488
489 #[inline]
490 pub fn send(&mut self, msg: T)
491 where
492 T: 'static,
493 F: Fn(T) + 'static,
494 {
495 self.recv_count += 1;
496
497 if self.large_limit && self.timestamps.len() < self.limit.get() && !self.is_limiting {
498 self.send_msg(msg);
499 return;
500 }
501
502 let delta = if self.is_limiting && !self.buffer.is_empty() {
503 0
504 } else {
505 self.delta_next()
506 };
507
508 if self.is_limiting && delta == 0 && self.buffer.is_empty() {
513 self.is_limiting = false;
514 }
515
516 if self.is_limiting || delta > 0 {
517 self.limit_msg(msg);
518 } else {
519 self.send_msg(msg);
520 }
521 }
522}
523
524fn process_endpoint(actor_id: Ustr) -> MStr<Endpoint> {
527 MStr::endpoint(format!("{actor_id}_process")).expect(FAILED)
528}
529
530struct ThrottlerProcess<T, F> {
535 actor_id: Ustr,
536 endpoint: MStr<Endpoint>,
537 phantom_t: PhantomData<T>,
538 phantom_f: PhantomData<F>,
539}
540
541impl<T, F> ThrottlerProcess<T, F>
542where
543 T: Debug,
544{
545 pub(crate) fn new(actor_id: Ustr) -> Self {
546 Self {
547 actor_id,
548 endpoint: process_endpoint(actor_id),
549 phantom_t: PhantomData,
550 phantom_f: PhantomData,
551 }
552 }
553
554 pub(crate) fn get_timer_callback(&self) -> TimeEventCallback {
555 let endpoint = self.endpoint;
556 TimeEventCallback::from(move |event: TimeEvent| {
557 msgbus::send_any(endpoint, &(event));
558 })
559 }
560}
561
562impl<T, F> Handler<dyn Any> for ThrottlerProcess<T, F>
563where
564 T: 'static + Debug,
565 F: Fn(T) + 'static,
566{
567 fn id(&self) -> Ustr {
568 *self.endpoint
569 }
570
571 fn handle(&self, _message: &dyn Any) {
572 let Some(mut throttler) = try_get_actor_unchecked::<Throttler<T, F>>(&self.actor_id) else {
575 return;
576 };
577
578 while let Some(msg) = throttler.buffer.pop_back() {
579 throttler.send_msg(msg);
580
581 if !throttler.buffer.is_empty() && throttler.delta_next() > 0 {
585 throttler.is_limiting = true;
586 throttler.set_timer(Some(self.get_timer_callback()));
587 return;
588 }
589 }
590
591 throttler.is_limiting = false;
592 }
593}
594
595impl<T, F> Drop for Throttler<T, F> {
596 fn drop(&mut self) {
597 self.cancel_timer_internal();
601 }
602}
603
604pub fn throttler_resume<T, F>(actor_id: Ustr) -> TimeEventCallback
610where
611 T: 'static + Debug,
612 F: Fn(T) + 'static,
613{
614 TimeEventCallback::from(move |_event: TimeEvent| {
615 if let Some(mut throttler) = try_get_actor_unchecked::<Throttler<T, F>>(&actor_id) {
616 throttler.is_limiting = false;
617 }
618 })
619}
620
621#[cfg(test)]
622mod tests {
623 use std::{
624 cell::{RefCell, UnsafeCell},
625 rc::Rc,
626 };
627
628 use nautilus_core::UUID4;
629 use rstest::{fixture, rstest};
630 use ustr::Ustr;
631
632 use super::{MAX_INITIAL_TIMESTAMPS_CAPACITY, RateLimit, Throttler, ThrottlerProcess};
633 use crate::{
634 clock::{Clock, TestClock},
635 msgbus::{self, Handler},
636 };
637 type SharedThrottler = Rc<UnsafeCell<Throttler<u64, Box<dyn Fn(u64)>>>>;
638
639 #[derive(Clone)]
644 struct TestThrottler {
645 throttler: SharedThrottler,
646 clock: Rc<RefCell<TestClock>>,
647 interval: u64,
648 }
649
650 #[allow(unsafe_code)]
651 impl TestThrottler {
652 #[expect(clippy::mut_from_ref)]
653 pub(crate) fn get_throttler(&self) -> &mut Throttler<u64, Box<dyn Fn(u64)>> {
654 unsafe { &mut *self.throttler.get() }
655 }
656 }
657
658 fn timer_count_with_prefix(
662 throttler: &Throttler<u64, Box<dyn Fn(u64)>>,
663 prefix: &str,
664 ) -> usize {
665 throttler
666 .clock
667 .borrow()
668 .timer_names()
669 .iter()
670 .filter(|name| name.starts_with(prefix))
671 .count()
672 }
673
674 #[allow(unsafe_code)]
675 #[expect(clippy::mut_from_ref)]
676 fn access_shared(t: &SharedThrottler) -> &mut Throttler<u64, Box<dyn Fn(u64)>> {
677 unsafe { &mut *t.get() }
678 }
679
680 #[rstest]
681 #[case(0, 1_000)]
682 #[case(1_000, 0)]
683 fn test_rate_limit_new_checked_rejects_zero(#[case] limit: usize, #[case] interval_ns: u64) {
684 assert!(RateLimit::new_checked(limit, interval_ns).is_err());
685 }
686
687 #[rstest]
688 #[case(0, 1_000)]
689 #[case(1_000, 0)]
690 #[should_panic]
691 fn test_rate_limit_new_panics_on_zero(#[case] limit: usize, #[case] interval_ns: u64) {
692 let _ = RateLimit::new(limit, interval_ns);
693 }
694
695 #[rstest]
696 fn test_rate_limit_new_checked_accepts_positive() {
697 let rate = RateLimit::new_checked(5, 10).unwrap();
698
699 assert_eq!(rate.limit(), 5);
700 assert_eq!(rate.interval_ns(), 10);
701 }
702
703 #[fixture]
704 pub fn test_throttler_buffered() -> TestThrottler {
705 let output_send: Box<dyn Fn(u64)> = Box::new(|msg: u64| {
706 log::debug!("Sent: {msg}");
707 });
708 let clock = Rc::new(RefCell::new(TestClock::new()));
709 let inner_clock = Rc::clone(&clock);
710 let rate_limit = RateLimit::new(5, 10);
711 let interval = rate_limit.interval_ns();
712 let actor_id = Ustr::from(UUID4::new().as_str());
713
714 TestThrottler {
715 throttler: Throttler::new(
716 rate_limit,
717 clock,
718 "buffer_timer",
719 output_send,
720 None,
721 actor_id,
722 )
723 .to_actor(),
724 clock: inner_clock,
725 interval,
726 }
727 }
728
729 #[fixture]
730 pub fn test_throttler_unbuffered() -> TestThrottler {
731 let output_send: Box<dyn Fn(u64)> = Box::new(|msg: u64| {
732 log::debug!("Sent: {msg}");
733 });
734 let output_drop: Box<dyn Fn(u64)> = Box::new(|msg: u64| {
735 log::debug!("Dropped: {msg}");
736 });
737 let clock = Rc::new(RefCell::new(TestClock::new()));
738 let inner_clock = Rc::clone(&clock);
739 let rate_limit = RateLimit::new(5, 10);
740 let interval = rate_limit.interval_ns();
741 let actor_id = Ustr::from(UUID4::new().as_str());
742
743 TestThrottler {
744 throttler: Throttler::new(
745 rate_limit,
746 clock,
747 "dropper_timer",
748 output_send,
749 Some(output_drop),
750 actor_id,
751 )
752 .to_actor(),
753 clock: inner_clock,
754 interval,
755 }
756 }
757
758 #[rstest]
759 fn test_buffering_send_to_limit_becomes_throttled(test_throttler_buffered: TestThrottler) {
760 let throttler = test_throttler_buffered.get_throttler();
761 for _ in 0..6 {
762 throttler.send(42);
763 }
764 assert_eq!(throttler.qsize(), 1);
765
766 assert!(throttler.is_limiting);
767 assert_eq!(throttler.recv_count, 6);
768 assert_eq!(throttler.sent_count, 5);
769 assert_eq!(timer_count_with_prefix(throttler, "buffer_timer"), 1);
770 }
771
772 #[rstest]
773 fn test_buffering_used_when_sent_to_limit_returns_one(test_throttler_buffered: TestThrottler) {
774 let throttler = test_throttler_buffered.get_throttler();
775
776 for _ in 0..5 {
777 throttler.send(42);
778 }
779
780 assert_eq!(throttler.used(), 1.0);
781 assert_eq!(throttler.recv_count, 5);
782 assert_eq!(throttler.sent_count, 5);
783 }
784
785 #[rstest]
786 fn test_buffering_used_when_half_interval_from_limit_returns_one(
787 test_throttler_buffered: TestThrottler,
788 ) {
789 let throttler = test_throttler_buffered.get_throttler();
790
791 for _ in 0..5 {
792 throttler.send(42);
793 }
794
795 let half_interval = test_throttler_buffered.interval / 2;
796 {
798 let mut clock = test_throttler_buffered.clock.borrow_mut();
799 clock.advance_time(half_interval.into(), true);
800 }
801
802 assert_eq!(throttler.used(), 1.0);
803 assert_eq!(throttler.recv_count, 5);
804 assert_eq!(throttler.sent_count, 5);
805 }
806
807 #[rstest]
808 fn test_buffering_used_before_limit_when_halfway_returns_half(
809 test_throttler_buffered: TestThrottler,
810 ) {
811 let throttler = test_throttler_buffered.get_throttler();
812
813 for _ in 0..3 {
814 throttler.send(42);
815 }
816
817 assert_eq!(throttler.used(), 0.6);
818 assert_eq!(throttler.recv_count, 3);
819 assert_eq!(throttler.sent_count, 3);
820 }
821
822 #[rstest]
823 fn test_try_reserve_counts_messages_without_output(test_throttler_buffered: TestThrottler) {
824 let throttler = test_throttler_buffered.get_throttler();
825
826 assert!(throttler.try_reserve(3));
827
828 assert_eq!(throttler.used(), 0.6);
829 assert_eq!(throttler.recv_count, 3);
830 assert_eq!(throttler.sent_count, 3);
831 assert_eq!(throttler.qsize(), 0);
832 }
833
834 #[rstest]
835 fn test_try_reserve_rejects_when_full_batch_exceeds_limit(
836 test_throttler_buffered: TestThrottler,
837 ) {
838 let throttler = test_throttler_buffered.get_throttler();
839
840 assert!(throttler.try_reserve(3));
841 assert!(!throttler.try_reserve(3));
842
843 assert_eq!(throttler.used(), 0.6);
844 assert_eq!(throttler.recv_count, 6);
845 assert_eq!(throttler.sent_count, 3);
846 assert_eq!(throttler.qsize(), 0);
847 assert!(throttler.is_limiting);
848 assert_eq!(timer_count_with_prefix(throttler, "buffer_timer"), 0);
852
853 assert!(throttler.try_reserve(2));
854
855 assert_eq!(throttler.used(), 1.0);
856 assert_eq!(throttler.recv_count, 8);
857 assert_eq!(throttler.sent_count, 5);
858 assert_eq!(throttler.qsize(), 0);
859 assert!(!throttler.is_limiting);
860 }
861
862 #[rstest]
863 fn test_try_reserve_rejects_batch_larger_than_limit() {
864 let clock: Rc<RefCell<dyn Clock>> = Rc::new(RefCell::new(TestClock::new()));
865 let mut throttler = Throttler::<u64, Box<dyn Fn(u64)>>::new(
866 RateLimit::new(5, 10),
867 clock,
868 "reserve_over_limit",
869 Box::new(|_| ()) as Box<dyn Fn(u64)>,
870 None,
871 Ustr::from("reserve-over-limit-actor"),
872 );
873
874 assert!(!throttler.try_reserve(6));
875
876 assert_eq!(throttler.used(), 0.0);
877 assert_eq!(throttler.recv_count, 6);
878 assert_eq!(throttler.sent_count, 0);
879 assert_eq!(throttler.qsize(), 0);
880 assert!(throttler.is_limiting);
881 assert_eq!(throttler.clock.borrow().timer_count(), 0);
882 }
883
884 #[rstest]
885 fn test_try_reserve_zero_count_is_noop() {
886 let clock: Rc<RefCell<dyn Clock>> = Rc::new(RefCell::new(TestClock::new()));
887 let mut throttler = Throttler::<u64, Box<dyn Fn(u64)>>::new(
888 RateLimit::new(5, 10),
889 clock,
890 "reserve_zero",
891 Box::new(|_| ()) as Box<dyn Fn(u64)>,
892 None,
893 Ustr::from("reserve-zero-actor"),
894 );
895
896 assert!(throttler.try_reserve(0));
897
898 assert_eq!(throttler.used(), 0.0);
899 assert_eq!(throttler.recv_count, 0);
900 assert_eq!(throttler.sent_count, 0);
901 assert_eq!(throttler.qsize(), 0);
902 assert!(!throttler.is_limiting);
903 assert_eq!(throttler.clock.borrow().timer_count(), 0);
904 }
905
906 #[rstest]
907 fn test_buffering_refresh_when_at_limit_sends_remaining_items(
908 test_throttler_buffered: TestThrottler,
909 ) {
910 let throttler = test_throttler_buffered.get_throttler();
911
912 for _ in 0..6 {
913 throttler.send(42);
914 }
915
916 {
918 let mut clock = test_throttler_buffered.clock.borrow_mut();
919 let time_events = clock.advance_time(test_throttler_buffered.interval.into(), true);
920 for each_event in clock.match_handlers(time_events) {
921 drop(clock); each_event.callback.call(each_event.event);
924
925 clock = test_throttler_buffered.clock.borrow_mut();
927 }
928 }
929
930 assert_eq!(throttler.used(), 0.2);
932 assert_eq!(throttler.recv_count, 6);
933 assert_eq!(throttler.sent_count, 6);
934 assert_eq!(throttler.qsize(), 0);
935 }
936
937 #[rstest]
938 fn test_buffering_send_message_after_buffering_message(test_throttler_buffered: TestThrottler) {
939 let throttler = test_throttler_buffered.get_throttler();
940
941 for _ in 0..6 {
942 throttler.send(43);
943 }
944
945 {
947 let mut clock = test_throttler_buffered.clock.borrow_mut();
948 let time_events = clock.advance_time(test_throttler_buffered.interval.into(), true);
949 for each_event in clock.match_handlers(time_events) {
950 drop(clock); each_event.callback.call(each_event.event);
953
954 clock = test_throttler_buffered.clock.borrow_mut();
956 }
957 }
958
959 for _ in 0..6 {
960 throttler.send(42);
961 }
962
963 assert_eq!(throttler.used(), 1.0);
965 assert_eq!(throttler.recv_count, 12);
966 assert_eq!(throttler.sent_count, 10);
967 assert_eq!(throttler.qsize(), 2);
968 }
969
970 #[rstest]
971 fn test_buffering_send_message_after_halfway_after_buffering_message(
972 test_throttler_buffered: TestThrottler,
973 ) {
974 let throttler = test_throttler_buffered.get_throttler();
975
976 for _ in 0..6 {
977 throttler.send(42);
978 }
979
980 {
982 let mut clock = test_throttler_buffered.clock.borrow_mut();
983 let time_events = clock.advance_time(test_throttler_buffered.interval.into(), true);
984 for each_event in clock.match_handlers(time_events) {
985 drop(clock); each_event.callback.call(each_event.event);
988
989 clock = test_throttler_buffered.clock.borrow_mut();
991 }
992 }
993
994 for _ in 0..3 {
995 throttler.send(42);
996 }
997
998 assert_eq!(throttler.used(), 0.8);
1000 assert_eq!(throttler.recv_count, 9);
1001 assert_eq!(throttler.sent_count, 9);
1002 assert_eq!(throttler.qsize(), 0);
1003 }
1004
1005 #[rstest]
1006 fn test_dropping_send_sends_message_to_handler(test_throttler_unbuffered: TestThrottler) {
1007 let throttler = test_throttler_unbuffered.get_throttler();
1008 throttler.send(42);
1009
1010 assert!(!throttler.is_limiting);
1011 assert_eq!(throttler.recv_count, 1);
1012 assert_eq!(throttler.sent_count, 1);
1013 }
1014
1015 #[rstest]
1016 fn test_dropping_send_to_limit_drops_message(test_throttler_unbuffered: TestThrottler) {
1017 let throttler = test_throttler_unbuffered.get_throttler();
1018 for _ in 0..6 {
1019 throttler.send(42);
1020 }
1021 assert_eq!(throttler.qsize(), 0);
1022
1023 assert!(throttler.is_limiting);
1024 assert_eq!(throttler.used(), 1.0);
1025 assert_eq!(throttler.clock.borrow().timer_count(), 1);
1026 assert_eq!(timer_count_with_prefix(throttler, "dropper_timer"), 1);
1027 assert_eq!(throttler.recv_count, 6);
1028 assert_eq!(throttler.sent_count, 5);
1029 }
1030
1031 #[rstest]
1032 fn test_dropping_advance_time_when_at_limit_dropped_message(
1033 test_throttler_unbuffered: TestThrottler,
1034 ) {
1035 let throttler = test_throttler_unbuffered.get_throttler();
1036 for _ in 0..6 {
1037 throttler.send(42);
1038 }
1039
1040 {
1042 let mut clock = test_throttler_unbuffered.clock.borrow_mut();
1043 let time_events = clock.advance_time(test_throttler_unbuffered.interval.into(), true);
1044 for each_event in clock.match_handlers(time_events) {
1045 drop(clock); each_event.callback.call(each_event.event);
1048
1049 clock = test_throttler_unbuffered.clock.borrow_mut();
1051 }
1052 }
1053
1054 assert_eq!(throttler.clock.borrow().timer_count(), 0);
1055 assert!(!throttler.is_limiting);
1056 assert_eq!(throttler.used(), 0.0);
1057 assert_eq!(throttler.recv_count, 6);
1058 assert_eq!(throttler.sent_count, 5);
1059 }
1060
1061 #[rstest]
1062 fn test_dropping_send_message_after_dropping_message(test_throttler_unbuffered: TestThrottler) {
1063 let throttler = test_throttler_unbuffered.get_throttler();
1064 for _ in 0..6 {
1065 throttler.send(42);
1066 }
1067
1068 {
1070 let mut clock = test_throttler_unbuffered.clock.borrow_mut();
1071 let time_events = clock.advance_time(test_throttler_unbuffered.interval.into(), true);
1072 for each_event in clock.match_handlers(time_events) {
1073 drop(clock); each_event.callback.call(each_event.event);
1076
1077 clock = test_throttler_unbuffered.clock.borrow_mut();
1079 }
1080 }
1081
1082 throttler.send(42);
1083
1084 assert_eq!(throttler.used(), 0.2);
1085 assert_eq!(throttler.clock.borrow().timer_count(), 0);
1086 assert!(!throttler.is_limiting);
1087 assert_eq!(throttler.recv_count, 7);
1088 assert_eq!(throttler.sent_count, 6);
1089 }
1090
1091 #[rstest]
1092 fn test_embedded_dropping_auto_resets_after_window_without_actor_callback() {
1093 let clock: Rc<RefCell<TestClock>> = Rc::new(RefCell::new(TestClock::new()));
1094 let sent = Rc::new(RefCell::new(0));
1095 let dropped = Rc::new(RefCell::new(0));
1096
1097 let sent_cb = {
1098 let sent = Rc::clone(&sent);
1099 Box::new(move |_| *sent.borrow_mut() += 1) as Box<dyn Fn(u64)>
1100 };
1101 let drop_cb = {
1102 let dropped = Rc::clone(&dropped);
1103 Box::new(move |_| *dropped.borrow_mut() += 1) as Box<dyn Fn(u64)>
1104 };
1105
1106 let mut throttler = Throttler::new(
1107 RateLimit::new(5, 10),
1108 Rc::clone(&clock) as Rc<RefCell<dyn Clock>>,
1109 "embedded_drop_timer",
1110 sent_cb,
1111 Some(drop_cb),
1112 Ustr::from("embedded-drop-actor"),
1113 );
1114
1115 for _ in 0..6 {
1116 throttler.send(42);
1117 }
1118 let events = clock.borrow_mut().advance_time(10.into(), true);
1119 throttler.send(42);
1120
1121 assert_eq!(events.len(), 1);
1122 assert_eq!(*sent.borrow(), 6);
1123 assert_eq!(*dropped.borrow(), 1);
1124 assert_eq!(throttler.recv_count, 7);
1125 assert_eq!(throttler.sent_count, 6);
1126 assert!(!throttler.is_limiting);
1127 assert_eq!(throttler.clock.borrow().timer_count(), 0);
1128 }
1129
1130 #[rstest]
1131 fn test_large_limit_fast_path_admits_until_limit_then_limits() {
1132 let clock: Rc<RefCell<dyn Clock>> = Rc::new(RefCell::new(TestClock::new()));
1133 let limit = MAX_INITIAL_TIMESTAMPS_CAPACITY + 1;
1134 let mut throttler = Throttler::<u64, Box<dyn Fn(u64)>>::new(
1135 RateLimit::new(limit, 10),
1136 clock,
1137 "large_limit_timer",
1138 Box::new(|_| ()) as Box<dyn Fn(u64)>,
1139 None,
1140 Ustr::from("large-limit-actor"),
1141 );
1142
1143 for _ in 0..limit {
1144 throttler.send(42);
1145 }
1146 throttler.send(42);
1147
1148 assert_eq!(throttler.used(), 1.0);
1149 assert_eq!(throttler.recv_count, limit + 1);
1150 assert_eq!(throttler.sent_count, limit);
1151 assert_eq!(throttler.qsize(), 1);
1152 assert!(throttler.is_limiting);
1153 assert_eq!(throttler.clock.borrow().timer_count(), 1);
1154 }
1155
1156 #[rstest]
1157 fn test_new_preserves_rate_limit() {
1158 let clock: Rc<RefCell<dyn Clock>> = Rc::new(RefCell::new(TestClock::new()));
1159 let rate_limit = RateLimit::new(5, 10);
1160
1161 let throttler = Throttler::<u64, Box<dyn Fn(u64)>>::new(
1162 rate_limit,
1163 clock,
1164 "rate_limit",
1165 Box::new(|_| ()) as Box<dyn Fn(u64)>,
1166 None,
1167 Ustr::from("rate-limit-actor"),
1168 );
1169
1170 assert_eq!(throttler.rate_limit(), rate_limit);
1171 assert_eq!(throttler.limit(), 5);
1172 assert_eq!(throttler.interval_ns(), 10);
1173 }
1174
1175 #[rstest]
1176 fn test_debug_output_includes_identity_and_state() {
1177 let clock: Rc<RefCell<dyn Clock>> = Rc::new(RefCell::new(TestClock::new()));
1178 let actor_id = Ustr::from("debug-actor");
1179 let mut throttler = Throttler::<u64, Box<dyn Fn(u64)>>::new(
1180 RateLimit::new(5, 10),
1181 clock,
1182 "debug_timer",
1183 Box::new(|_| ()) as Box<dyn Fn(u64)>,
1184 None,
1185 actor_id,
1186 );
1187
1188 throttler.send(42);
1189
1190 let debug = format!("{throttler:?}");
1191 let timer_name = Ustr::from("debug_timer-debug-actor");
1192
1193 assert!(debug.contains(&format!("actor_id: {actor_id:?}")));
1194 assert!(debug.contains(&format!("timer_name: {timer_name:?}")));
1195 assert!(debug.contains("limit: 5"));
1196 assert!(debug.contains("interval_ns: 10"));
1197 assert!(debug.contains("is_limiting: false"));
1198 assert!(debug.contains("recv_count: 1"));
1199 assert!(debug.contains("sent_count: 1"));
1200 }
1201
1202 #[rstest]
1203 fn test_reset_clears_state_and_cancels_timer(test_throttler_buffered: TestThrottler) {
1204 let throttler = test_throttler_buffered.get_throttler();
1205
1206 for _ in 0..6 {
1207 throttler.send(42);
1208 }
1209 assert_eq!(timer_count_with_prefix(throttler, "buffer_timer"), 1);
1210 assert_eq!(throttler.qsize(), 1);
1211
1212 throttler.reset();
1213
1214 assert_eq!(throttler.qsize(), 0);
1215 assert_eq!(throttler.recv_count, 0);
1216 assert_eq!(throttler.sent_count, 0);
1217 assert!(!throttler.is_limiting);
1218 assert!(throttler.timestamps.is_empty());
1219 assert_eq!(timer_count_with_prefix(throttler, "buffer_timer"), 0);
1220 assert_eq!(throttler.clock.borrow().timer_count(), 0);
1221 }
1222
1223 #[rstest]
1224 fn test_two_throttlers_share_clock_without_timer_collision() {
1225 let clock: Rc<RefCell<TestClock>> = Rc::new(RefCell::new(TestClock::new()));
1226 let interval = 10u64;
1227
1228 let mk = |base: &str| -> SharedThrottler {
1229 let clock: Rc<RefCell<dyn Clock>> = Rc::clone(&clock) as Rc<RefCell<dyn Clock>>;
1230 Throttler::new(
1231 RateLimit::new(5, interval),
1232 clock,
1233 base,
1234 Box::new(|_| ()) as Box<dyn Fn(u64)>,
1235 None,
1236 Ustr::from(UUID4::new().as_str()),
1237 )
1238 .to_actor()
1239 };
1240
1241 let t1 = mk("shared_timer");
1242 let t2 = mk("shared_timer");
1243
1244 {
1247 let t1 = access_shared(&t1);
1248
1249 for _ in 0..6 {
1250 t1.send(42);
1251 }
1252 }
1253 {
1254 let t2 = access_shared(&t2);
1255
1256 for _ in 0..6 {
1257 t2.send(42);
1258 }
1259 }
1260
1261 let clock_ref = clock.borrow();
1262 let names = clock_ref.timer_names();
1263 let shared_count = names
1264 .iter()
1265 .filter(|n| n.starts_with("shared_timer"))
1266 .count();
1267 assert_eq!(
1268 shared_count, 2,
1269 "two distinct timers expected, found {names:?}"
1270 );
1271 }
1272
1273 #[rstest]
1274 fn test_try_reserve_then_send_interleaved(test_throttler_buffered: TestThrottler) {
1275 let throttler = test_throttler_buffered.get_throttler();
1276
1277 assert!(throttler.try_reserve(3));
1280 throttler.send(42);
1281
1282 assert_eq!(throttler.recv_count, 4);
1283 assert_eq!(throttler.sent_count, 4);
1284 assert_eq!(throttler.used(), 0.8);
1285 assert!(!throttler.is_limiting);
1286 }
1287
1288 #[rstest]
1289 fn test_dispose_cancels_timer_and_deregisters_endpoint(test_throttler_buffered: TestThrottler) {
1290 let throttler = test_throttler_buffered.get_throttler();
1291
1292 for _ in 0..6 {
1293 throttler.send(42);
1294 }
1295 let actor_id = throttler.actor_id;
1296 let endpoint_name = format!("{actor_id}_process");
1297 assert_eq!(timer_count_with_prefix(throttler, "buffer_timer"), 1);
1298 assert!(msgbus::has_endpoint(&endpoint_name));
1299
1300 throttler.dispose();
1301
1302 assert_eq!(throttler.clock.borrow().timer_count(), 0);
1303 assert!(
1304 !msgbus::has_endpoint(&endpoint_name),
1305 "dispose must deregister the process endpoint"
1306 );
1307 }
1308
1309 use proptest::prelude::*;
1314
1315 #[derive(Clone, Debug)]
1316 enum ThrottlerInput {
1317 SendMessage(u64),
1318 AdvanceClock(u8),
1319 }
1320
1321 fn throttler_input_strategy() -> impl Strategy<Value = ThrottlerInput> {
1323 prop_oneof![
1324 2 => prop::bool::ANY.prop_map(|_| ThrottlerInput::SendMessage(42)),
1325 8 => prop::num::u8::ANY.prop_map(|v| ThrottlerInput::AdvanceClock(v % 5 + 5)),
1326 ]
1327 }
1328
1329 fn throttler_test_strategy() -> impl Strategy<Value = Vec<ThrottlerInput>> {
1331 prop::collection::vec(throttler_input_strategy(), 10..=150)
1332 }
1333
1334 fn test_throttler_with_inputs(inputs: Vec<ThrottlerInput>, test_throttler: &TestThrottler) {
1335 let test_clock = test_throttler.clock.clone();
1336 let interval = test_throttler.interval;
1337 let throttler = test_throttler.get_throttler();
1338 let mut sent_count = 0;
1339
1340 for input in inputs {
1341 match input {
1342 ThrottlerInput::SendMessage(msg) => {
1343 throttler.send(msg);
1344 sent_count += 1;
1345 }
1346 ThrottlerInput::AdvanceClock(duration) => {
1347 let mut clock_ref = test_clock.borrow_mut();
1348 let current_time = clock_ref.get_time_ns();
1349 let time_events =
1350 clock_ref.advance_time(current_time + u64::from(duration), true);
1351 for each_event in clock_ref.match_handlers(time_events) {
1352 drop(clock_ref);
1353 each_event.callback.call(each_event.event);
1354 clock_ref = test_clock.borrow_mut();
1355 }
1356 }
1357 }
1358
1359 let buffered_messages = throttler.qsize() > 0;
1364 let now = throttler.clock.borrow().timestamp_ns().as_u64();
1365 let limit_filled_within_interval = throttler
1366 .timestamps
1367 .get(throttler.limit() - 1)
1368 .is_some_and(|&ts| (now - ts.as_u64()) < interval);
1369 let expected_limiting = buffered_messages && limit_filled_within_interval;
1370 assert_eq!(throttler.is_limiting, expected_limiting);
1371
1372 assert_eq!(sent_count, throttler.sent_count + throttler.qsize());
1374 }
1375
1376 for i in 1..=100u64 {
1380 if throttler.qsize() == 0 {
1381 break;
1382 }
1383 let advance_to = interval * 100 * i;
1384 let time_events = test_clock
1385 .borrow_mut()
1386 .advance_time(advance_to.into(), true);
1387 let mut clock_ref = test_clock.borrow_mut();
1388 for each_event in clock_ref.match_handlers(time_events) {
1389 drop(clock_ref);
1390 each_event.callback.call(each_event.event);
1391 clock_ref = test_clock.borrow_mut();
1392 }
1393 }
1394 assert_eq!(throttler.qsize(), 0);
1395 }
1396
1397 #[rstest]
1398 fn prop_test() {
1399 proptest!(|(inputs in throttler_test_strategy())| {
1402 let test_throttler = test_throttler_buffered();
1403 test_throttler_with_inputs(inputs, &test_throttler);
1404 });
1405 }
1406
1407 #[rstest]
1408 fn prop_test_dropping() {
1409 proptest!(|(inputs in throttler_test_strategy())| {
1413 let clock = Rc::new(RefCell::new(TestClock::new()));
1414 let sent: Rc<RefCell<usize>> = Rc::new(RefCell::new(0));
1415 let dropped: Rc<RefCell<usize>> = Rc::new(RefCell::new(0));
1416
1417 let sent_cb = {
1418 let sent = Rc::clone(&sent);
1419 Box::new(move |_| *sent.borrow_mut() += 1) as Box<dyn Fn(u64)>
1420 };
1421 let drop_cb = {
1422 let dropped = Rc::clone(&dropped);
1423 Box::new(move |_| *dropped.borrow_mut() += 1) as Box<dyn Fn(u64)>
1424 };
1425
1426 let interval = 10u64;
1427 let throttler = Throttler::new(
1428 RateLimit::new(5, interval),
1429 Rc::clone(&clock) as Rc<RefCell<dyn Clock>>,
1430 "prop_drop_timer",
1431 sent_cb,
1432 Some(drop_cb),
1433 Ustr::from(UUID4::new().as_str()),
1434 )
1435 .to_actor();
1436 let throttler = access_shared(&throttler);
1437
1438 for input in inputs {
1439 match input {
1440 ThrottlerInput::SendMessage(msg) => {
1441 throttler.send(msg);
1442 }
1443 ThrottlerInput::AdvanceClock(duration) => {
1444 let mut clock_ref = clock.borrow_mut();
1445 let current_time = clock_ref.get_time_ns();
1446 let time_events =
1447 clock_ref.advance_time(current_time + u64::from(duration), true);
1448 for each_event in clock_ref.match_handlers(time_events) {
1449 drop(clock_ref);
1450 each_event.callback.call(each_event.event);
1451 clock_ref = clock.borrow_mut();
1452 }
1453 }
1454 }
1455
1456 let sent_now = *sent.borrow();
1457 let dropped_now = *dropped.borrow();
1458 assert_eq!(sent_now + dropped_now, throttler.recv_count);
1460 assert_eq!(throttler.sent_count, sent_now);
1461 assert!(throttler.qsize() == 0, "drop mode must never buffer");
1462 }
1463 });
1464 }
1465
1466 #[rstest]
1467 fn test_throttler_process_id_returns_ustr() {
1468 let actor_id = Ustr::from("test_throttler");
1471 let process = ThrottlerProcess::<String, fn(String)>::new(actor_id);
1472
1473 let handler_id: Ustr = process.id();
1475
1476 assert!(handler_id.as_str().contains("test_throttler_process"));
1478 assert!(!handler_id.is_empty());
1479
1480 let _type_check: Ustr = handler_id;
1482 }
1483}