1use std::{borrow::Cow, time::Instant};
2
3use saluki_common::collections::FastHashMap;
4use saluki_error::{generic_error, GenericError};
5use saluki_metrics::static_metrics;
6use tokio::sync::mpsc;
7
8use super::Dispatchable;
9use crate::{components::ComponentContext, topology::OutputName};
10
11static_metrics!(
14 name => DispatcherMetrics,
15 prefix => component,
16 labels => [component_id: String, component_type: &'static str, output: String],
17 metrics => [
18 counter(events_sent_total),
19 trace_histogram(send_latency_seconds),
20 counter(events_discarded_total),
21 ],
22);
23
24impl DispatcherMetrics {
25 fn default_output(context: ComponentContext) -> Self {
26 Self::with_output_name(context, "_default")
27 }
28
29 fn named_output(context: ComponentContext, output_name: &str) -> Self {
30 Self::with_output_name(context, output_name)
31 }
32
33 fn with_output_name(context: ComponentContext, output_name: &str) -> Self {
34 Self::new(
35 context.component_id().to_string(),
36 context.component_type().as_str(),
37 output_name.to_string(),
38 )
39 }
40}
41
42pub trait DispatchBuffer: Dispatchable + Default {
44 type Item;
46
47 fn len(&self) -> usize;
49
50 fn is_full(&self) -> bool;
52
53 fn try_push(&mut self, item: Self::Item) -> Option<Self::Item>;
57}
58
59struct DispatchTarget<T> {
60 metrics: DispatcherMetrics,
61 senders: Vec<mpsc::Sender<T>>,
62}
63
64impl<T> DispatchTarget<T>
65where
66 T: Dispatchable,
67{
68 fn default_output(context: ComponentContext) -> Self {
69 Self {
70 metrics: DispatcherMetrics::default_output(context),
71 senders: Vec::new(),
72 }
73 }
74
75 fn named_output(context: ComponentContext, output_name: &str) -> Self {
76 Self {
77 metrics: DispatcherMetrics::named_output(context, output_name),
78 senders: Vec::new(),
79 }
80 }
81
82 fn add_sender(&mut self, sender: mpsc::Sender<T>) {
83 self.senders.push(sender);
84 }
85
86 async fn send(&self, item: T) -> Result<(), GenericError> {
87 if self.senders.is_empty() {
88 let item_count = item.item_count() as u64;
90 self.metrics.events_discarded_total().increment(item_count);
91 saluki_antithesis::sometimes!(
94 true,
95 "events discarded on a zero-sender output",
96 { "items": item_count }
97 );
98 return Ok(());
99 }
100
101 let start = Instant::now();
102 let item_count = item.item_count();
103
104 saluki_antithesis::always_gt!(self.senders.len(), 0, "dispatcher fanout has at least one sender");
106 let cloned_sends = self.senders.len() - 1;
107 for sender in &self.senders[0..cloned_sends] {
108 sender
109 .send(item.clone())
110 .await
111 .map_err(|_| generic_error!("Failed to send to output."))?;
112 }
113
114 let last_sender = &self.senders[cloned_sends];
116 last_sender
117 .send(item)
118 .await
119 .map_err(|_| generic_error!("Failed to send to output."))?;
120
121 let elapsed = start.elapsed();
122
123 self.metrics.send_latency_seconds().record(elapsed);
126
127 let total_events_sent = (self.senders.len() * item_count) as u64;
128 self.metrics.events_sent_total().increment(total_events_sent);
129
130 Ok(())
131 }
132}
133
134pub struct BufferedDispatcher<'a, T> {
141 metrics: &'a DispatcherMetrics,
142 flushed_len: usize,
143 buffer: Option<T>,
144 target: &'a DispatchTarget<T>,
145}
146
147impl<'a, T> BufferedDispatcher<'a, T> {
148 fn new(target: &'a DispatchTarget<T>) -> Self {
149 Self {
150 metrics: &target.metrics,
151 flushed_len: 0,
152 buffer: None,
153 target,
154 }
155 }
156}
157
158impl<T> BufferedDispatcher<'_, T>
159where
160 T: DispatchBuffer,
161{
162 async fn try_flush_buffer(&self, buffer: T) -> Result<(), GenericError> {
163 let buffer_len = buffer.len();
164 if buffer_len > 0 {
165 self.target.send(buffer).await
166 } else {
167 Ok(())
168 }
169 }
170
171 pub async fn push(&mut self, item: T::Item) -> Result<(), GenericError> {
178 if let Some(old_buffer) = self.buffer.take_if(|b| b.is_full()) {
180 self.try_flush_buffer(old_buffer).await?;
181 }
182
183 let buffer = self.buffer.get_or_insert_default();
188 if buffer.try_push(item).is_some() {
189 return Err(generic_error!("Dispatch buffer already full after acquisition."));
190 }
191
192 self.flushed_len += 1;
193
194 Ok(())
195 }
196
197 pub async fn send_all<I>(mut self, items: I) -> Result<usize, GenericError>
206 where
207 I: IntoIterator<Item = T::Item>,
208 {
209 for item in items {
210 self.push(item).await?;
211 }
212
213 self.flush().await
214 }
215
216 pub async fn flush(mut self) -> Result<usize, GenericError> {
225 if let Some(old_buffer) = self.buffer.take() {
226 self.try_flush_buffer(old_buffer).await?;
227 }
228
229 self.metrics.events_sent_total().increment(self.flushed_len as u64);
232
233 Ok(self.flushed_len)
234 }
235}
236
237pub struct Dispatcher<T>
243where
244 T: Dispatchable,
245{
246 context: ComponentContext,
247 default: Option<DispatchTarget<T>>,
248 targets: FastHashMap<Cow<'static, str>, DispatchTarget<T>>,
249}
250
251impl<T> Dispatcher<T>
252where
253 T: Dispatchable,
254{
255 pub fn new(context: ComponentContext) -> Self {
257 Self {
258 context,
259 default: None,
260 targets: FastHashMap::default(),
261 }
262 }
263
264 pub fn add_output(&mut self, output_name: OutputName) -> Result<(), GenericError> {
270 match output_name {
271 OutputName::Default => {
272 if self.default.is_some() {
273 return Err(generic_error!("Default output already exists."));
274 }
275
276 self.default = Some(DispatchTarget::default_output(self.context.clone()));
277 }
278 OutputName::Given(name) => {
279 if self.targets.contains_key(&name) {
280 return Err(generic_error!("Output '{}' already exists.", name));
281 }
282 let target = DispatchTarget::named_output(self.context.clone(), &name);
283 self.targets.insert(name, target);
284 }
285 }
286
287 Ok(())
288 }
289
290 pub fn attach_sender_to_output(
296 &mut self, output_name: &OutputName, sender: mpsc::Sender<T>,
297 ) -> Result<(), GenericError> {
298 let target = match output_name {
299 OutputName::Default => self
300 .default
301 .as_mut()
302 .ok_or_else(|| generic_error!("No default output declared."))?,
303 OutputName::Given(name) => self
304 .targets
305 .get_mut(name)
306 .ok_or_else(|| generic_error!("Output '{}' does not exist.", name))?,
307 };
308 target.add_sender(sender);
309
310 Ok(())
311 }
312
313 fn get_default_output(&self) -> Result<&DispatchTarget<T>, GenericError> {
314 self.default
315 .as_ref()
316 .ok_or_else(|| generic_error!("No default output declared."))
317 }
318
319 fn get_named_output(&self, name: &str) -> Result<&DispatchTarget<T>, GenericError> {
320 self.targets
321 .get(name)
322 .ok_or_else(|| generic_error!("No output named '{}' declared.", name))
323 }
324
325 pub fn is_default_output_connected(&self) -> bool {
327 self.default.as_ref().is_some_and(|target| !target.senders.is_empty())
328 }
329
330 pub fn is_named_output_connected(&self, name: &str) -> bool {
332 self.targets.get(name).is_some_and(|target| !target.senders.is_empty())
333 }
334
335 pub async fn dispatch(&self, item: T) -> Result<(), GenericError> {
341 self.dispatch_inner(None, item).await
342 }
343
344 pub async fn dispatch_named<N>(&self, output_name: N, item: T) -> Result<(), GenericError>
350 where
351 N: AsRef<str>,
352 {
353 self.dispatch_inner(Some(output_name.as_ref()), item).await
354 }
355
356 async fn dispatch_inner(&self, output_name: Option<&str>, item: T) -> Result<(), GenericError> {
357 let target = match output_name {
358 None => self.get_default_output()?,
359 Some(name) => self.get_named_output(name)?,
360 };
361
362 target.send(item).await?;
363
364 Ok(())
365 }
366}
367
368impl<T> Dispatcher<T>
369where
370 T: DispatchBuffer,
371{
372 pub fn buffered(&self) -> Result<BufferedDispatcher<'_, T>, GenericError> {
382 self.get_default_output().map(BufferedDispatcher::new)
383 }
384
385 pub fn buffered_named<N>(&self, output_name: N) -> Result<BufferedDispatcher<'_, T>, GenericError>
395 where
396 N: AsRef<str>,
397 {
398 self.get_named_output(output_name.as_ref()).map(BufferedDispatcher::new)
399 }
400
401 pub async fn dispatch_one(&self, item: T::Item) -> Result<(), GenericError> {
407 self.dispatch_one_inner(None, item).await
408 }
409
410 pub async fn dispatch_one_named<N>(&self, output_name: N, item: T::Item) -> Result<(), GenericError>
416 where
417 N: AsRef<str>,
418 {
419 self.dispatch_one_inner(Some(output_name.as_ref()), item).await
420 }
421
422 async fn dispatch_one_inner(&self, output_name: Option<&str>, item: T::Item) -> Result<(), GenericError> {
423 let target = match output_name {
424 None => self.get_default_output()?,
425 Some(name) => self.get_named_output(name)?,
426 };
427
428 let mut buffer = T::default();
429 if buffer.try_push(item).is_some() {
430 return Err(generic_error!("Default-constructed buffer rejected a single item."));
431 }
432 target.send(buffer).await
433 }
434}
435
436#[cfg(test)]
437mod tests {
438 use std::ops::Deref;
441
442 use metrics::{Key, Label};
443 use metrics_util::{
444 debugging::{DebugValue, DebuggingRecorder, Snapshotter},
445 CompositeKey, MetricKind,
446 };
447 use ordered_float::OrderedFloat;
448
449 use super::*;
450
451 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
452 struct SingleEvent<T>(T);
453
454 impl<T: Clone + Copy> Dispatchable for SingleEvent<T> {
455 fn item_count(&self) -> usize {
456 1
457 }
458 }
459
460 impl<T: Clone + Copy> From<T> for SingleEvent<T> {
461 fn from(value: T) -> Self {
462 Self(value)
463 }
464 }
465
466 #[derive(Clone, Debug, Eq, PartialEq)]
467 struct FixedUsizeVec<const N: usize> {
468 data: [usize; N],
469 len: usize,
470 }
471
472 impl<const N: usize> Default for FixedUsizeVec<N> {
473 fn default() -> Self {
474 Self { data: [0; N], len: 0 }
475 }
476 }
477
478 impl<const N: usize> Deref for FixedUsizeVec<N> {
479 type Target = [usize];
480
481 fn deref(&self) -> &Self::Target {
482 &self.data
483 }
484 }
485
486 impl<const N: usize> Dispatchable for FixedUsizeVec<N> {
487 fn item_count(&self) -> usize {
488 self.len
489 }
490 }
491
492 impl<const N: usize> DispatchBuffer for FixedUsizeVec<N> {
493 type Item = usize;
494
495 fn len(&self) -> usize {
496 self.len
497 }
498
499 fn is_full(&self) -> bool {
500 self.len == N
501 }
502
503 fn try_push(&mut self, item: Self::Item) -> Option<Self::Item> {
504 if self.is_full() {
505 Some(item)
506 } else {
507 self.data[self.len] = item;
508 self.len += 1;
509 None
510 }
511 }
512 }
513
514 fn unbuffered_dispatcher<T: Dispatchable>() -> Dispatcher<T> {
515 let component_context = ComponentContext::test_source("dispatcher_test");
516 Dispatcher::new(component_context)
517 }
518
519 fn buffered_dispatcher<T: DispatchBuffer>() -> Dispatcher<T> {
520 unbuffered_dispatcher()
521 }
522
523 fn add_dispatcher_default_output<T: Dispatchable, const N: usize>(
524 dispatcher: &mut Dispatcher<T>, senders: [mpsc::Sender<T>; N],
525 ) {
526 dispatcher
527 .add_output(OutputName::Default)
528 .expect("default output should not be added yet");
529 for sender in senders {
530 dispatcher
531 .attach_sender_to_output(&OutputName::Default, sender)
532 .expect("default output should be added");
533 }
534 }
535
536 fn add_dispatcher_named_output<T: Dispatchable, const N: usize>(
537 dispatcher: &mut Dispatcher<T>, output_name: &'static str, senders: [mpsc::Sender<T>; N],
538 ) {
539 dispatcher
540 .add_output(OutputName::Given(output_name.into()))
541 .expect("named output should not be added yet");
542 for sender in senders {
543 dispatcher
544 .attach_sender_to_output(&OutputName::Given(output_name.into()), sender)
545 .expect("named output should be added");
546 }
547 }
548
549 fn get_dispatcher_metric_ckey(
550 kind: MetricKind, name: &'static str, output_name: &'static str, tags: &[(&'static str, &'static str)],
551 ) -> CompositeKey {
552 let mut labels = vec![
553 Label::from_static_parts("component_id", "dispatcher_test"),
554 Label::from_static_parts("component_type", "source"),
555 Label::from_static_parts("output", output_name),
556 ];
557
558 for tag in tags {
559 labels.push(Label::from_static_parts(tag.0, tag.1));
560 }
561
562 let key = Key::from_parts(name, labels);
563 CompositeKey::new(kind, key)
564 }
565
566 fn get_output_metrics(snapshotter: &Snapshotter, output_name: &'static str) -> (u64, u64, Vec<OrderedFloat<f64>>) {
567 let events_sent_key = get_dispatcher_metric_ckey(
568 MetricKind::Counter,
569 DispatcherMetrics::events_sent_total_name(),
570 output_name,
571 &[],
572 );
573 let events_discarded_key = get_dispatcher_metric_ckey(
574 MetricKind::Counter,
575 DispatcherMetrics::events_discarded_total_name(),
576 output_name,
577 &[],
578 );
579 let send_latency_key = get_dispatcher_metric_ckey(
580 MetricKind::Histogram,
581 DispatcherMetrics::send_latency_seconds_name(),
582 output_name,
583 &[],
584 );
585
586 let current_metrics = snapshotter.snapshot().into_hashmap();
588 let (_, _, events_sent) = current_metrics
589 .get(&events_sent_key)
590 .expect("should have events sent metric");
591 let (_, _, events_discarded) = current_metrics
592 .get(&events_discarded_key)
593 .expect("should have events discarded metric");
594 let (_, _, send_latency) = current_metrics
595 .get(&send_latency_key)
596 .expect("should have send latency metric");
597
598 let events_sent = match events_sent {
599 DebugValue::Counter(value) => *value,
600 _ => panic!("unexpected metric type for events sent"),
601 };
602
603 let events_discarded = match events_discarded {
604 DebugValue::Counter(value) => *value,
605 _ => panic!("unexpected metric type for events discarded"),
606 };
607
608 let send_latency = match send_latency {
609 DebugValue::Histogram(value) => value.clone(),
610 _ => panic!("unexpected metric type for send latency"),
611 };
612
613 (events_sent, events_discarded, send_latency)
614 }
615
616 #[tokio::test]
617 async fn default_output() {
618 let mut dispatcher = unbuffered_dispatcher::<SingleEvent<usize>>();
620
621 let (tx, mut rx) = mpsc::channel(1);
622 add_dispatcher_default_output(&mut dispatcher, [tx]);
623
624 let input_item = 42.into();
626
627 dispatcher.dispatch(input_item).await.unwrap();
628
629 let output_item = rx.try_recv().expect("input item should have been dispatched");
630 assert_eq!(output_item, input_item);
631 }
632
633 #[tokio::test]
634 async fn named_output() {
635 let mut dispatcher = unbuffered_dispatcher::<SingleEvent<usize>>();
637
638 let output_name = "special";
639 let (tx, mut rx) = mpsc::channel(1);
640 add_dispatcher_named_output(&mut dispatcher, output_name, [tx]);
641
642 let input_item = 42.into();
644
645 dispatcher.dispatch_named(output_name, input_item).await.unwrap();
646
647 let output_item = rx.try_recv().expect("input item should have been dispatched");
648 assert_eq!(output_item, input_item);
649 }
650
651 #[tokio::test]
652 async fn default_output_multiple_senders() {
653 let mut dispatcher = unbuffered_dispatcher::<SingleEvent<usize>>();
655
656 let (tx1, mut rx1) = mpsc::channel(1);
657 let (tx2, mut rx2) = mpsc::channel(1);
658 add_dispatcher_default_output(&mut dispatcher, [tx1, tx2]);
659
660 let input_item = 42.into();
662
663 dispatcher.dispatch(input_item).await.unwrap();
664
665 let output_item1 = rx1.try_recv().expect("input item should have been dispatched");
666 let output_item2 = rx2.try_recv().expect("input item should have been dispatched");
667 assert_eq!(output_item1, input_item);
668 assert_eq!(output_item2, input_item);
669 }
670
671 #[tokio::test]
672 async fn named_output_multiple_senders() {
673 let mut dispatcher = unbuffered_dispatcher::<SingleEvent<usize>>();
675
676 let output_name = "special";
677 let (tx1, mut rx1) = mpsc::channel(1);
678 let (tx2, mut rx2) = mpsc::channel(1);
679 add_dispatcher_named_output(&mut dispatcher, output_name, [tx1, tx2]);
680
681 let input_item = 42.into();
683
684 dispatcher.dispatch_named(output_name, input_item).await.unwrap();
685
686 let output_item1 = rx1.try_recv().expect("input item should have been dispatched");
687 let output_item2 = rx2.try_recv().expect("input item should have been dispatched");
688 assert_eq!(output_item1, input_item);
689 assert_eq!(output_item2, input_item);
690 }
691
692 #[tokio::test]
693 async fn default_output_not_set() {
694 let dispatcher = unbuffered_dispatcher::<SingleEvent<()>>();
696
697 let result = dispatcher.dispatch(().into()).await;
698 assert!(result.is_err());
699 }
700
701 #[tokio::test]
702 async fn named_output_not_set() {
703 let dispatcher = unbuffered_dispatcher::<SingleEvent<()>>();
705
706 let result = dispatcher.dispatch_named("non_existent", ().into()).await;
707 assert!(result.is_err());
708 }
709
710 #[tokio::test]
711 async fn default_output_buffered_partial() {
712 let mut dispatcher = buffered_dispatcher::<FixedUsizeVec<4>>();
714
715 let (tx, mut rx) = mpsc::channel(1);
716 add_dispatcher_default_output(&mut dispatcher, [tx]);
717
718 let input_item = 42;
720
721 let mut buffered = dispatcher.buffered().unwrap();
722 buffered.push(input_item).await.unwrap();
723
724 let flushed_len = buffered.flush().await.unwrap();
725 assert_eq!(flushed_len, 1);
726
727 let output_item = rx.try_recv().expect("input item should have been dispatched");
728 assert_eq!(output_item.len(), 1);
729 assert_eq!(output_item[0], input_item);
730 }
731
732 #[tokio::test]
733 async fn named_output_buffered_partial() {
734 let mut dispatcher = buffered_dispatcher::<FixedUsizeVec<4>>();
736
737 let output_name = "buffered_partial";
738 let (tx, mut rx) = mpsc::channel(1);
739 add_dispatcher_named_output(&mut dispatcher, output_name, [tx]);
740
741 let input_item = 42;
743
744 let mut buffered = dispatcher.buffered_named(output_name).unwrap();
745 buffered.push(input_item).await.unwrap();
746
747 let flushed_len = buffered.flush().await.unwrap();
748 assert_eq!(flushed_len, 1);
749
750 let output_item = rx.try_recv().expect("input item should have been dispatched");
751 assert_eq!(output_item.len(), 1);
752 assert_eq!(output_item[0], input_item);
753 }
754
755 #[tokio::test]
756 async fn default_output_buffered_overflow() {
757 let mut dispatcher = buffered_dispatcher::<FixedUsizeVec<4>>();
759
760 let (tx, mut rx) = mpsc::channel(2);
761 add_dispatcher_default_output(&mut dispatcher, [tx]);
762
763 let input_items: Vec<usize> = vec![1, 2, 3, 4, 5, 6];
768
769 let mut buffered = dispatcher.buffered().unwrap();
770
771 for item in &input_items {
772 buffered.push(*item).await.unwrap();
773 }
774
775 let flushed_len = buffered.flush().await.unwrap();
776 assert_eq!(flushed_len, input_items.len());
777
778 let output_item1 = rx.try_recv().expect("input item should have been dispatched");
779 assert_eq!(output_item1.len(), 4);
780 assert_eq!(output_item1[0..4], input_items[0..4]);
781
782 let output_item2 = rx.try_recv().expect("input item should have been dispatched");
783 assert_eq!(output_item2.len(), 2);
784 assert_eq!(output_item2[0..2], input_items[4..6]);
785 }
786
787 #[tokio::test]
788 async fn named_output_buffered_overflow() {
789 let mut dispatcher = buffered_dispatcher::<FixedUsizeVec<4>>();
791
792 let output_name = "buffered_overflow";
793 let (tx, mut rx) = mpsc::channel(2);
794 add_dispatcher_named_output(&mut dispatcher, output_name, [tx]);
795
796 let input_items: Vec<usize> = vec![1, 2, 3, 4, 5, 6];
801
802 let mut buffered = dispatcher.buffered_named(output_name).unwrap();
803
804 for item in &input_items {
805 buffered.push(*item).await.unwrap();
806 }
807
808 let flushed_len = buffered.flush().await.unwrap();
809 assert_eq!(flushed_len, input_items.len());
810
811 let output_item1 = rx.try_recv().expect("input item should have been dispatched");
812 assert_eq!(output_item1.len(), 4);
813 assert_eq!(output_item1[0..4], input_items[0..4]);
814
815 let output_item2 = rx.try_recv().expect("input item should have been dispatched");
816 assert_eq!(output_item2.len(), 2);
817 assert_eq!(output_item2[0..2], input_items[4..6]);
818 }
819
820 #[tokio::test]
821 async fn default_output_buffered_partial_multiple_senders() {
822 let mut dispatcher = buffered_dispatcher::<FixedUsizeVec<4>>();
824
825 let (tx1, mut rx1) = mpsc::channel(1);
826 let (tx2, mut rx2) = mpsc::channel(1);
827 add_dispatcher_default_output(&mut dispatcher, [tx1, tx2]);
828
829 let input_item = 42;
831
832 let mut buffered = dispatcher.buffered().unwrap();
833 buffered.push(input_item).await.unwrap();
834
835 let flushed_len = buffered.flush().await.unwrap();
836 assert_eq!(flushed_len, 1);
837
838 let output_item1 = rx1.try_recv().expect("input item should have been dispatched");
839 assert_eq!(output_item1.len(), 1);
840 assert_eq!(output_item1[0], input_item);
841
842 let output_item2 = rx2.try_recv().expect("input item should have been dispatched");
843 assert_eq!(output_item2.len(), 1);
844 assert_eq!(output_item2[0], input_item);
845 }
846
847 #[tokio::test]
848 async fn named_output_buffered_partial_multiple_senders() {
849 let mut dispatcher = buffered_dispatcher::<FixedUsizeVec<4>>();
851
852 let output_name = "buffered_partial";
853 let (tx1, mut rx1) = mpsc::channel(1);
854 let (tx2, mut rx2) = mpsc::channel(1);
855 add_dispatcher_named_output(&mut dispatcher, output_name, [tx1, tx2]);
856
857 let input_item = 42;
859
860 let mut buffered = dispatcher.buffered_named(output_name).unwrap();
861 buffered.push(input_item).await.unwrap();
862
863 let flushed_len = buffered.flush().await.unwrap();
864 assert_eq!(flushed_len, 1);
865
866 let output_item1 = rx1.try_recv().expect("input item should have been dispatched");
867 assert_eq!(output_item1.len(), 1);
868 assert_eq!(output_item1[0], input_item);
869
870 let output_item2 = rx2.try_recv().expect("input item should have been dispatched");
871 assert_eq!(output_item2.len(), 1);
872 assert_eq!(output_item2[0], input_item);
873 }
874
875 #[tokio::test]
876 async fn default_output_no_senders() {
877 let mut dispatcher = unbuffered_dispatcher::<SingleEvent<u32>>();
879
880 dispatcher
882 .add_output(OutputName::Default)
883 .expect("should be able to add default output");
884
885 let test_event = 42.into();
887 let result = dispatcher.dispatch(test_event).await;
888 assert!(
889 result.is_ok(),
890 "dispatch to default output with no senders should succeed"
891 );
892 }
893
894 #[tokio::test]
895 async fn named_output_no_senders() {
896 let mut dispatcher = unbuffered_dispatcher::<SingleEvent<u32>>();
898
899 dispatcher
901 .add_output(OutputName::Given("errors".into()))
902 .expect("should be able to add named output");
903
904 let test_event = 42.into();
906 let result = dispatcher.dispatch_named("errors", test_event).await;
907 assert!(
908 result.is_ok(),
909 "dispatch to named output with no senders should succeed"
910 );
911 }
912
913 #[tokio::test]
914 async fn metrics_default_output_disconnected() {
915 let recorder = DebuggingRecorder::new();
916 let snapshotter = recorder.snapshotter();
917 let dispatcher = metrics::with_local_recorder(&recorder, || {
918 let mut dispatcher = buffered_dispatcher::<FixedUsizeVec<4>>();
919 dispatcher
920 .add_output(OutputName::Default)
921 .expect("should not fail to add default output");
922 dispatcher
923 });
924
925 let mut single_item = FixedUsizeVec::<4>::default();
927 assert_eq!(None, single_item.try_push(42));
928 let single_item_item_count = single_item.item_count() as u64;
929
930 dispatcher
931 .dispatch(single_item.clone())
932 .await
933 .expect("should not fail to dispatch");
934
935 let (events_sent, events_discarded, send_latencies) = get_output_metrics(&snapshotter, "_default");
936 assert_eq!(events_sent, 0);
937 assert_eq!(events_discarded, single_item_item_count);
938 assert!(send_latencies.is_empty());
939
940 let mut multiple_items = FixedUsizeVec::<4>::default();
942 assert_eq!(None, multiple_items.try_push(42));
943 assert_eq!(None, multiple_items.try_push(12345));
944 assert_eq!(None, multiple_items.try_push(1337));
945 let multiple_items_item_count = multiple_items.item_count() as u64;
946
947 dispatcher
948 .dispatch(multiple_items.clone())
949 .await
950 .expect("should not fail to dispatch");
951
952 let (events_sent, events_discarded, send_latencies) = get_output_metrics(&snapshotter, "_default");
953 assert_eq!(events_sent, 0);
954 assert_eq!(events_discarded, multiple_items_item_count);
955 assert!(send_latencies.is_empty());
956 }
957
958 #[tokio::test]
959 async fn metrics_named_output_disconnected() {
960 let output_name = "some_output";
961
962 let recorder = DebuggingRecorder::new();
963 let snapshotter = recorder.snapshotter();
964 let dispatcher = metrics::with_local_recorder(&recorder, || {
965 let mut dispatcher = buffered_dispatcher::<FixedUsizeVec<4>>();
966 dispatcher
967 .add_output(OutputName::Given(output_name.into()))
968 .expect("should not fail to add named output");
969 dispatcher
970 });
971
972 let mut single_item = FixedUsizeVec::<4>::default();
974 assert_eq!(None, single_item.try_push(42));
975 let single_item_item_count = single_item.item_count() as u64;
976
977 dispatcher
978 .dispatch_named(output_name, single_item.clone())
979 .await
980 .expect("should not fail to dispatch");
981
982 let (events_sent, events_discarded, send_latencies) = get_output_metrics(&snapshotter, output_name);
983 assert_eq!(events_sent, 0);
984 assert_eq!(events_discarded, single_item_item_count);
985 assert!(send_latencies.is_empty());
986
987 let mut multiple_items = FixedUsizeVec::<4>::default();
989 assert_eq!(None, multiple_items.try_push(42));
990 assert_eq!(None, multiple_items.try_push(12345));
991 assert_eq!(None, multiple_items.try_push(1337));
992 let multiple_items_item_count = multiple_items.item_count() as u64;
993
994 dispatcher
995 .dispatch_named(output_name, multiple_items.clone())
996 .await
997 .expect("should not fail to dispatch");
998
999 let (events_sent, events_discarded, send_latencies) = get_output_metrics(&snapshotter, output_name);
1000 assert_eq!(events_sent, 0);
1001 assert_eq!(events_discarded, multiple_items_item_count);
1002 assert!(send_latencies.is_empty());
1003 }
1004
1005 #[tokio::test]
1006 async fn is_default_output_connected_behavior() {
1007 let mut dispatcher = unbuffered_dispatcher::<SingleEvent<u32>>();
1008
1009 assert!(
1011 !dispatcher.is_default_output_connected(),
1012 "should return false when no default output exists"
1013 );
1014
1015 dispatcher
1017 .add_output(OutputName::Default)
1018 .expect("should be able to add default output");
1019 assert!(
1020 !dispatcher.is_default_output_connected(),
1021 "should return false when default output exists but has no senders"
1022 );
1023
1024 let (tx, _rx) = mpsc::channel(1);
1026 dispatcher
1027 .attach_sender_to_output(&OutputName::Default, tx)
1028 .expect("should be able to attach sender");
1029 assert!(
1030 dispatcher.is_default_output_connected(),
1031 "should return true when default output has senders attached"
1032 );
1033 }
1034
1035 #[tokio::test]
1036 async fn is_named_output_connected_behavior() {
1037 let mut dispatcher = unbuffered_dispatcher::<SingleEvent<u32>>();
1038 let output_name = "test_output";
1039
1040 assert!(
1042 !dispatcher.is_named_output_connected(output_name),
1043 "should return false when named output doesn't exist"
1044 );
1045
1046 dispatcher
1048 .add_output(OutputName::Given(output_name.into()))
1049 .expect("should be able to add named output");
1050 assert!(
1051 !dispatcher.is_named_output_connected(output_name),
1052 "should return false when named output exists but has no senders"
1053 );
1054
1055 let (tx, _rx) = mpsc::channel(1);
1057 dispatcher
1058 .attach_sender_to_output(&OutputName::Given(output_name.into()), tx)
1059 .expect("should be able to attach sender");
1060 assert!(
1061 dispatcher.is_named_output_connected(output_name),
1062 "should return true when named output has senders attached"
1063 );
1064
1065 assert!(
1067 !dispatcher.is_named_output_connected("nonexistent_output"),
1068 "should return false for nonexistent output"
1069 );
1070 }
1071
1072 #[tokio::test]
1073 async fn default_output_dispatch_one() {
1074 let mut dispatcher = buffered_dispatcher::<FixedUsizeVec<4>>();
1076
1077 let (tx, mut rx) = mpsc::channel(1);
1078 add_dispatcher_default_output(&mut dispatcher, [tx]);
1079
1080 let input_item = 42;
1081
1082 dispatcher.dispatch_one(input_item).await.unwrap();
1083
1084 let output_item = rx.try_recv().expect("input item should have been dispatched");
1085 assert_eq!(output_item.len(), 1);
1086 assert_eq!(output_item[0], input_item);
1087 }
1088
1089 #[tokio::test]
1090 async fn named_output_dispatch_one() {
1091 let mut dispatcher = buffered_dispatcher::<FixedUsizeVec<4>>();
1093
1094 let output_name = "single";
1095 let (tx, mut rx) = mpsc::channel(1);
1096 add_dispatcher_named_output(&mut dispatcher, output_name, [tx]);
1097
1098 let input_item = 42;
1099
1100 dispatcher.dispatch_one_named(output_name, input_item).await.unwrap();
1101
1102 let output_item = rx.try_recv().expect("input item should have been dispatched");
1103 assert_eq!(output_item.len(), 1);
1104 assert_eq!(output_item[0], input_item);
1105 }
1106
1107 #[tokio::test]
1108 async fn default_output_dispatch_one_not_set() {
1109 let dispatcher = buffered_dispatcher::<FixedUsizeVec<4>>();
1111
1112 let result = dispatcher.dispatch_one(42).await;
1113 assert!(result.is_err());
1114 }
1115
1116 #[tokio::test]
1117 async fn named_output_dispatch_one_not_set() {
1118 let dispatcher = buffered_dispatcher::<FixedUsizeVec<4>>();
1120
1121 let result = dispatcher.dispatch_one_named("nonexistent", 42).await;
1122 assert!(result.is_err());
1123 }
1124}