1use std::{
2 any::Any,
3 fmt::Display,
4 marker::PhantomData,
5 sync::{Arc, PoisonError},
6};
7
8use bytes::Bytes;
9use dashmap::DashMap;
10use futures_util::{StreamExt, future::select};
11use tokio::{
12 sync::{mpsc, watch},
13 task::JoinSet,
14};
15use tokio_stream::wrappers::ReceiverStream;
16use type_states::{Init, Pattern, RoutingKey, Running, StateMarker, Stopped};
17
18mod dead_letter;
19mod delivery;
20mod retry;
21mod subscription;
22mod transport;
23
24pub use dead_letter::{DeadLetter, DeadLetterReason, DeadLetterSink};
25pub use delivery::{Delivery, HandlerError};
26pub use retry::{Backoff, RetryPolicy};
27pub use subscription::{Overflow, Subscription, SubscriptionInfo, Subscriptions};
28pub use transport::{DeliveryOutcome, Settlement, SubscriptionOutcome, TransportHandle};
29
30use subscription::{Defaults, Inbox, Route, Subscriber};
31use transport::Settler;
32
33type EventSubscriptions = Arc<DashMap<EventTopic<Pattern>, Vec<Route>>>;
34
35pub mod type_states {
38 #[derive(Debug, Clone)]
40 pub struct Init;
41 #[derive(Debug, Default, Clone, PartialEq, Eq)]
42 pub struct RoutingKey;
43 #[derive(Debug, Default, Clone, PartialEq, Eq, Hash)]
44 pub struct Pattern;
45 #[derive(Clone, Debug)]
47 pub struct Stopped {
48 pub(super) bufsize: usize,
49 }
50 #[derive(Debug)]
51 pub struct Running {
52 pub(super) message_tx: super::mpsc::Sender<super::EventMessage>,
53 pub(super) stop_tx: super::watch::Sender<bool>,
54 pub(super) handle: tokio::task::JoinHandle<()>,
55 pub(super) runtime: tokio::runtime::Handle,
56 pub(super) workers: std::sync::Mutex<super::JoinSet<()>>,
57 }
58
59 pub trait StateMarker {}
60 impl StateMarker for () {}
61 impl StateMarker for Init {}
62 impl StateMarker for RoutingKey {}
63 impl StateMarker for Pattern {}
64
65 impl StateMarker for Stopped {}
66 impl StateMarker for Running {}
67}
68
69#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
71pub enum TopicError {
72 #[error("topic cannot be empty")]
73 EmptyTopic,
74 #[error(
75 "invalid character in topic `{topic}`: topics may only contain alphanumeric characters and {allowed:?}"
76 )]
77 InvalidCharacter {
78 topic: String,
79 allowed: &'static [char],
80 },
81 #[error("found empty segment")]
82 EmptySegment,
83 #[error("invalid selection segment `{0}`")]
84 InvalidSelection(String),
85 #[error("invalid value `{value}` in selection segment `{segment}`")]
86 InvalidSelectionValue { segment: String, value: String },
87 #[error("found `,` outside of a selection segment: `{0}`")]
88 UnbracketedComma(String),
89 #[error("`*` must make up the entire segment: `{0}`")]
90 EmbeddedWildcard(String),
91}
92
93#[derive(Debug, thiserror::Error)]
95pub enum BrokerError {
96 #[error("no tokio runtime available to run the event loop")]
97 Runtime(#[from] tokio::runtime::TryCurrentError),
98}
99
100#[derive(Debug, Clone, Hash, PartialEq, Eq)]
108pub enum TopicSegment {
109 Literal(String),
110 Wildcard,
111 Selection(Vec<String>),
112}
113
114impl TopicSegment {
115 fn matches(&self, other: &TopicSegment) -> bool {
116 match (self, other) {
117 (TopicSegment::Literal(l), TopicSegment::Literal(r)) => l == r,
118 (TopicSegment::Selection(l), TopicSegment::Literal(r)) => l.binary_search(r).is_ok(),
119 (TopicSegment::Wildcard, _) => true,
120 _ => false,
121 }
122 }
123
124 fn parse(text: &str) -> Result<Self, TopicError> {
125 if text.is_empty() {
126 return Err(TopicError::EmptySegment);
127 }
128 if text == "*" {
129 return Ok(Self::Wildcard);
130 }
131 if text.starts_with('[') && text.ends_with(']') {
132 let mut values = Vec::new();
133 for value in text[1..text.len() - 1].split(',').map(str::trim) {
134 if value == "*" {
135 return Ok(Self::Wildcard);
136 }
137 if value.is_empty() || value.contains(['[', ']', '*']) {
138 return Err(TopicError::InvalidSelectionValue {
139 segment: text.to_string(),
140 value: value.to_string(),
141 });
142 }
143 values.push(value.to_string());
144 }
145 values.sort();
146 values.dedup();
147 if values.len() == 1 {
148 return Ok(Self::Literal(values.pop().expect("one value is present")));
149 }
150 return Ok(Self::Selection(values));
151 }
152 if text.contains(['[', ']']) {
153 return Err(TopicError::InvalidSelection(text.to_string()));
154 }
155 if text.contains(',') {
156 return Err(TopicError::UnbracketedComma(text.to_string()));
157 }
158 if text.contains('*') {
159 return Err(TopicError::EmbeddedWildcard(text.to_string()));
160 }
161 Ok(Self::Literal(text.to_string()))
162 }
163}
164
165#[derive(Debug, Default, Clone, Hash, PartialEq, Eq)]
176pub struct EventTopic<S: StateMarker> {
177 s: PhantomData<S>,
178 raw: String,
179 segments: Vec<TopicSegment>,
180 segment_count: usize,
181 is_wildcard: bool,
182 is_tail_matching: bool,
183}
184
185impl EventTopic<()> {
186 pub fn new(topic: impl Into<String>) -> EventTopic<Init> {
187 EventTopic {
188 s: PhantomData::<Init>,
189 raw: topic.into().to_lowercase(),
190 segments: Vec::new(),
191 segment_count: 0,
192 is_wildcard: false,
193 is_tail_matching: false,
194 }
195 }
196}
197
198impl EventTopic<Init> {
199 pub fn as_routing_key(self) -> Result<EventTopic<RoutingKey>, TopicError> {
200 self.sanitize_topic(&['.', '-', '_'])?;
201 let segments = self.parse_segments()?;
202 let new = EventTopic {
203 s: PhantomData::<RoutingKey>,
204 raw: self.raw.clone(),
205 segment_count: segments.len(),
206 segments,
207 is_tail_matching: false,
208 is_wildcard: false,
209 };
210 Ok(new)
211 }
212
213 pub fn as_subscription(self) -> Result<EventTopic<Pattern>, TopicError> {
214 self.sanitize_topic(&['.', ',', '*', '[', ']', '-', '_'])?;
215 let mut new = EventTopic {
216 s: PhantomData::<Pattern>,
217 raw: self.raw.clone(),
218 ..Default::default()
219 };
220 let segments = self.parse_segments()?;
221 if let Some(TopicSegment::Wildcard) = segments.last() {
222 new.is_tail_matching = true;
223 if segments.len() == 1 {
224 new.is_wildcard = true;
225 } else {
226 let tail_wildcards = segments
227 .iter()
228 .rev()
229 .take_while(|s| *s == &TopicSegment::Wildcard)
230 .count();
231 let real_segments = segments.len() - tail_wildcards + 1;
232 new.segments = segments.into_iter().take(real_segments).fold(
233 new.segments,
234 |mut acc, segment| {
235 acc.push(segment);
236 acc
237 },
238 );
239 }
240 } else {
241 new.segments = segments;
242 }
243 new.segment_count = new.segments.len();
244 Ok(new)
245 }
246
247 fn parse_segments(&self) -> Result<Vec<TopicSegment>, TopicError> {
248 let mut parsed_segments = Vec::new();
249 for segment in self.raw.split('.') {
250 parsed_segments.push(TopicSegment::parse(segment)?);
251 }
252 Ok(parsed_segments)
253 }
254
255 fn sanitize_topic(&self, extra_keys: &'static [char]) -> Result<(), TopicError> {
256 if self.raw.is_empty() {
257 return Err(TopicError::EmptyTopic);
258 }
259 if !self
260 .raw
261 .chars()
262 .all(|c| c.is_alphanumeric() || extra_keys.contains(&c))
263 {
264 return Err(TopicError::InvalidCharacter {
265 topic: self.raw.clone(),
266 allowed: extra_keys,
267 });
268 }
269 Ok(())
270 }
271}
272
273impl EventTopic<Pattern> {
274 #[tracing::instrument(level = "debug")]
275 fn match_topic(&self, topic: &EventTopic<RoutingKey>) -> bool {
276 if self.is_wildcard {
277 true
278 } else if self.segment_count <= topic.segment_count && self.is_tail_matching {
279 let take = self.segment_count - 1;
280 self.segments
281 .iter()
282 .take(take)
283 .zip(topic.segments.iter().take(take))
284 .all(|(a, b)| a.matches(b))
285 } else if self.segment_count == topic.segment_count {
286 self.segments
287 .iter()
288 .zip(topic.segments.iter())
289 .all(|(a, b)| a.matches(b))
290 } else {
291 false
292 }
293 }
294}
295
296impl<S> EventTopic<S>
297where
298 S: StateMarker,
299{
300 pub fn text(&self) -> &str {
301 &self.raw
302 }
303
304 pub fn segments(&self) -> &[TopicSegment] {
305 &self.segments
306 }
307}
308
309impl<S> Display for EventTopic<S>
310where
311 S: StateMarker,
312{
313 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
314 write!(f, "{}", self.raw)
315 }
316}
317
318#[derive(Debug, PartialEq, Eq)]
321pub enum EventSubmission {
322 Single(EventMessage),
323 Batch(Vec<EventMessage>),
324}
325
326impl From<EventMessage> for EventSubmission {
327 fn from(value: EventMessage) -> Self {
328 Self::Single(value)
329 }
330}
331
332impl From<&EventMessage> for EventSubmission {
334 fn from(value: &EventMessage) -> Self {
335 Self::Single(value.clone())
336 }
337}
338
339impl FromIterator<EventMessage> for EventSubmission {
340 fn from_iter<T: IntoIterator<Item = EventMessage>>(iter: T) -> Self {
341 Self::Batch(Vec::from_iter(iter))
342 }
343}
344
345#[async_trait::async_trait]
351pub trait EventEmitter: std::fmt::Debug {
352 fn get_sender(&self) -> &mpsc::Sender<EventMessage>;
353
354 async fn submit_event(&self, submission: EventSubmission) -> anyhow::Result<()> {
355 match submission {
356 EventSubmission::Single(event_message) => {
357 self.get_sender().reserve().await?.send(event_message)
358 }
359 EventSubmission::Batch(event_messages) => {
360 self.get_sender()
361 .reserve_many(event_messages.len())
362 .await?
363 .zip(event_messages)
364 .for_each(|(permit, msg)| permit.send(msg));
365 }
366 }
367 Ok(())
368 }
369}
370
371#[async_trait::async_trait]
422pub trait EventConsumer: std::fmt::Debug + Send + Sync + 'static {
423 type Topic: std::fmt::Debug + Send + Sync + 'static;
426
427 fn subscriptions(&self) -> Subscriptions<Self::Topic>;
429
430 async fn handle_event(
431 &self,
432 topic: &Self::Topic,
433 delivery: &Delivery,
434 ) -> Result<(), HandlerError>;
435}
436
437#[derive(Debug, Clone)]
443pub struct SubscriptionHandle {
444 subscriptions: Vec<(EventTopic<Pattern>, std::sync::Weak<Subscriber>)>,
445}
446
447#[derive(Debug)]
458pub struct EventBroker<S: StateMarker> {
459 runtime: S,
460 subscriptions: EventSubscriptions,
461 handle_ctrl_c: bool,
462 defaults: Defaults,
463}
464
465impl Default for EventBroker<Stopped> {
466 fn default() -> EventBroker<Stopped> {
467 EventBroker::new(64)
468 }
469}
470
471impl EventBroker<()> {
472 pub fn new(bufsize: usize) -> EventBroker<Stopped> {
473 EventBroker {
474 runtime: Stopped { bufsize },
475 subscriptions: Arc::new(DashMap::new()),
476 handle_ctrl_c: false,
477 defaults: Defaults::default(),
478 }
479 }
480}
481
482impl<S> EventBroker<S>
483where
484 S: StateMarker + 'static,
485{
486 fn register(
487 &self,
488 subscribers: Vec<Arc<Subscriber>>,
489 mut open: impl FnMut(&Arc<Subscriber>) -> Option<Inbox>,
490 ) -> SubscriptionHandle {
491 let mut handle = SubscriptionHandle {
492 subscriptions: Vec::with_capacity(subscribers.len()),
493 };
494 for subscriber in subscribers {
495 let pattern = subscriber.info.pattern().clone();
496 handle
497 .subscriptions
498 .push((pattern.clone(), Arc::downgrade(&subscriber)));
499 let inbox = open(&subscriber);
500 self.subscriptions
501 .entry(pattern)
502 .or_default()
503 .push(Route { subscriber, inbox });
504 }
505 handle
506 }
507
508 pub fn remove_topic_consumer(&self, handle: SubscriptionHandle) -> bool {
512 let mut removed = false;
513 for (pattern, subscriber) in handle.subscriptions {
514 let Some(subscriber) = subscriber.upgrade() else {
515 continue;
516 };
517 if let Some(mut routes) = self.subscriptions.get_mut(&pattern) {
518 let before = routes.len();
519 routes.retain(|route| !Arc::ptr_eq(&route.subscriber, &subscriber));
520 removed |= routes.len() < before;
521 }
522 self.subscriptions
523 .remove_if(&pattern, |_, routes| routes.is_empty());
524 }
525 removed
526 }
527
528 pub fn subscriptions(&self) -> Vec<Arc<SubscriptionInfo>> {
530 self.subscriptions
531 .iter()
532 .flat_map(|entry| {
533 entry
534 .value()
535 .iter()
536 .map(|route| route.subscriber.info.clone())
537 .collect::<Vec<_>>()
538 })
539 .collect()
540 }
541
542 pub fn find_subscriptions(&self, topic: &EventTopic<RoutingKey>) -> Vec<Arc<SubscriptionInfo>> {
544 self.matching_routes(topic, |route| Some(route.subscriber.info.clone()))
545 }
546
547 fn matching_routes<T>(
548 &self,
549 topic: &EventTopic<RoutingKey>,
550 pick: impl Fn(&Route) -> Option<T>,
551 ) -> Vec<T> {
552 let mut picked = Vec::new();
553 for entry in self.subscriptions.iter() {
554 if entry.key().match_topic(topic) {
555 picked.extend(entry.value().iter().filter_map(&pick));
556 }
557 }
558 picked
559 }
560
561 #[tracing::instrument(skip(self))]
562 async fn run_event_loop(
563 &self,
564 receiver: mpsc::Receiver<EventMessage>,
565 mut stop_rx: watch::Receiver<bool>,
566 ) {
567 let handle_ctrl_c = self.handle_ctrl_c;
568 let stop_signal = async move {
569 if handle_ctrl_c {
570 if let Err(e) = tokio::signal::ctrl_c().await {
571 tracing::error!("Failed to listen for the ctrl-c signal: {e}");
572 std::future::pending::<()>().await
573 }
574 } else {
575 std::future::pending::<()>().await
576 }
577 };
578 let stop_call = stop_rx.changed();
579 let mut stop = select(Box::pin(stop_signal), Box::pin(stop_call));
580 let mut event_stream = ReceiverStream::new(receiver);
581 let mut tasks = JoinSet::new();
582 loop {
583 tokio::select! {
584 biased;
585 _ = &mut stop => {
586 tracing::info!("Stopping event stream processing.");
587 event_stream.close();
588 break;
589 }
590 Some(result) = tasks.join_next(), if !tasks.is_empty() => {
591 if let Err(e) = result {
592 tracing::error!("Event dispatch task failed: {e}");
593 }
594 }
595 maybe_msg = event_stream.next() => match maybe_msg {
596 Some(event_msg) => self.dispatch(event_msg, &mut tasks).await,
597 None => {
598 tracing::info!("Event loop processing ended. Shutting down broker.");
599 break;
600 }
601 },
602 }
603 }
604 while let Some(event_msg) = event_stream.next().await {
607 self.dispatch(event_msg, &mut tasks).await;
608 }
609 close_inboxes(&self.subscriptions);
610 while let Some(result) = tasks.join_next().await {
611 if let Err(e) = result {
612 tracing::error!("Event dispatch task failed: {e}");
613 }
614 }
615 }
616
617 async fn dispatch(&self, event_msg: EventMessage, tasks: &mut JoinSet<()>) {
619 let inboxes = self.matching_routes(event_msg.topic(), |route| route.inbox.clone());
620 let message = Arc::new(event_msg);
621 let settler = Settler::new(&message, inboxes.len());
622 let Some(last) = inboxes.len().checked_sub(1) else {
623 if let Some(settler) = settler {
624 tasks.spawn(settler.settle());
625 }
626 return;
627 };
628 let mut settler = settler.map(Arc::new);
629 for (index, inbox) in inboxes.into_iter().enumerate() {
630 let shared_settler = if index == last {
633 settler.take()
634 } else {
635 settler.clone()
636 };
637 let delivery = Delivery {
638 message: message.clone(),
639 subscription: inbox.subscription().clone(),
640 attempt: 1,
641 settler: shared_settler,
642 };
643 inbox.deliver(delivery, tasks).await;
644 }
645 }
646}
647
648fn close_inboxes(subscriptions: &EventSubscriptions) {
650 for mut routes in subscriptions.iter_mut() {
651 for route in routes.iter_mut() {
652 route.inbox = None;
653 }
654 }
655}
656
657impl EventBroker<Stopped> {
658 pub fn with_ctrl_c_handling(mut self) -> Self {
662 self.handle_ctrl_c = true;
663 self
664 }
665
666 pub fn with_retry_policy(mut self, policy: RetryPolicy) -> Self {
669 self.defaults.retry_policy = policy;
670 self
671 }
672
673 pub fn with_dead_letter_sink(mut self, sink: Arc<dyn DeadLetterSink>) -> Self {
676 self.defaults.dead_letter_sink = Some(sink);
677 self
678 }
679
680 pub fn add_topic_consumer(
682 &self,
683 consumer: impl EventConsumer,
684 ) -> Result<SubscriptionHandle, TopicError> {
685 Ok(self.register(Subscriber::from_consumer(consumer)?, |_| None))
686 }
687
688 pub fn run(self) -> Result<EventBroker<Running>, BrokerError> {
689 let rt = tokio::runtime::Handle::try_current()?;
690 let (message_tx, message_rx) = mpsc::channel::<EventMessage>(self.runtime.bufsize);
691 let (stop_tx, stop_rx) = watch::channel(false);
692 let mut workers = JoinSet::new();
693 for mut routes in self.subscriptions.iter_mut() {
694 for route in routes.iter_mut() {
695 route.inbox = Some(route.subscriber.open(
696 self.runtime.bufsize,
697 &self.defaults,
698 &mut workers,
699 &rt,
700 ));
701 }
702 }
703 let broker = EventBroker {
704 subscriptions: self.subscriptions.clone(),
705 handle_ctrl_c: self.handle_ctrl_c,
706 defaults: self.defaults.clone(),
707 runtime: Running {
708 handle: rt.spawn(async move { self.run_event_loop(message_rx, stop_rx).await }),
709 message_tx,
710 stop_tx,
711 runtime: rt,
712 workers: std::sync::Mutex::new(workers),
713 },
714 };
715
716 Ok(broker)
717 }
718}
719
720impl EventBroker<Running> {
721 pub fn add_topic_consumer(
723 &self,
724 consumer: impl EventConsumer,
725 ) -> Result<SubscriptionHandle, TopicError> {
726 let subscribers = Subscriber::from_consumer(consumer)?;
727 let mut workers = self
728 .runtime
729 .workers
730 .lock()
731 .unwrap_or_else(PoisonError::into_inner);
732 while let Some(result) = workers.try_join_next() {
734 if let Err(e) = result {
735 tracing::error!("Subscription worker failed: {e}");
736 }
737 }
738 let inbox_capacity = self.runtime.message_tx.max_capacity();
739 Ok(self.register(subscribers, |subscriber| {
740 Some(subscriber.open(
741 inbox_capacity,
742 &self.defaults,
743 &mut workers,
744 &self.runtime.runtime,
745 ))
746 }))
747 }
748
749 pub async fn stop(self) -> EventBroker<Stopped> {
752 match self.runtime.stop_tx.send(true) {
753 Err(e) => {
754 tracing::error!("Failed to send stop signal to event loop: {e}");
755 self.runtime.handle.abort();
756 }
757 Ok(_) => {
758 if let Err(e) = self.runtime.handle.await {
759 tracing::error!("Event loop task failed to stop gracefully: {e}.");
760 }
761 }
762 }
763 close_inboxes(&self.subscriptions);
765 let mut workers = self
766 .runtime
767 .workers
768 .into_inner()
769 .unwrap_or_else(PoisonError::into_inner);
770 while let Some(result) = workers.join_next().await {
771 if let Err(e) = result {
772 tracing::error!("Subscription worker failed: {e}");
773 }
774 }
775 EventBroker {
776 runtime: Stopped {
777 bufsize: self.runtime.message_tx.max_capacity(),
778 },
779 subscriptions: self.subscriptions.clone(),
780 handle_ctrl_c: self.handle_ctrl_c,
781 defaults: self.defaults,
782 }
783 }
784
785 pub fn get_sender(&self) -> mpsc::Sender<EventMessage> {
786 self.runtime.message_tx.clone()
787 }
788}
789
790#[derive(Debug, Default)]
811pub struct EventMessage {
812 topic: EventTopic<RoutingKey>,
813 content: Bytes,
814 transport: Option<Box<dyn TransportHandle>>,
815}
816
817impl Clone for EventMessage {
818 fn clone(&self) -> Self {
819 Self {
820 topic: self.topic.clone(),
821 content: self.content.clone(),
822 transport: None,
823 }
824 }
825}
826
827impl PartialEq for EventMessage {
828 fn eq(&self, other: &Self) -> bool {
829 self.topic == other.topic && self.content == other.content
830 }
831}
832
833impl Eq for EventMessage {}
834
835impl EventMessage {
836 pub fn new(
837 topic_text: impl Into<String>,
838 content: impl Into<Bytes>,
839 ) -> Result<Self, TopicError> {
840 Ok(Self {
841 topic: EventTopic::new(topic_text.into()).as_routing_key()?,
842 content: content.into(),
843 transport: None,
844 })
845 }
846
847 pub fn topic(&self) -> &EventTopic<RoutingKey> {
848 &self.topic
849 }
850
851 pub fn content(&self) -> &Bytes {
852 &self.content
853 }
854
855 pub fn transport<T: TransportHandle>(&self) -> Option<&T> {
857 let handle: &dyn Any = self.transport.as_deref()?;
858 handle.downcast_ref()
859 }
860
861 pub fn transport_handle(&self) -> Option<&dyn TransportHandle> {
863 self.transport.as_deref()
864 }
865
866 pub fn with_topic(mut self, topic: EventTopic<RoutingKey>) -> Self {
868 self.topic = topic;
869 self
870 }
871
872 pub fn with_content(mut self, content: impl Into<Bytes>) -> Self {
874 self.content = content.into();
875 self
876 }
877
878 pub fn with_transport(mut self, handle: impl TransportHandle) -> Self {
880 self.transport = Some(Box::new(handle));
881 self
882 }
883}
884
885#[cfg(test)]
886mod tests {
887 use std::{
888 sync::{
889 Arc, Mutex,
890 atomic::{AtomicUsize, Ordering},
891 },
892 time::Duration,
893 };
894
895 use dashmap::DashMap;
896 use tokio::sync::{Semaphore, mpsc};
897
898 use super::{
899 DeadLetter, DeadLetterReason, DeadLetterSink, Delivery, DeliveryOutcome, EventBroker,
900 EventConsumer, EventEmitter, EventMessage, EventSubmission, EventTopic, HandlerError,
901 Overflow, RetryPolicy, Settlement, Subscription, Subscriptions, TopicSegment,
902 TransportHandle,
903 };
904
905 #[derive(Debug, Clone)]
907 struct Call {
908 content: String,
909 attempt: u32,
910 transport: Option<&'static str>,
911 }
912
913 type Calls = Arc<Mutex<Vec<Call>>>;
914
915 #[derive(Debug, Clone, Copy)]
916 enum Script {
917 Succeed,
918 FailAttempts(u32),
919 FailPermanently,
920 Panic,
921 }
922
923 #[derive(Debug)]
925 struct ScriptedConsumer {
926 subscription: Subscription,
927 script: Script,
928 calls: Calls,
929 }
930
931 impl ScriptedConsumer {
932 fn new(subscription: impl Into<Subscription>, script: Script) -> (Self, Calls) {
933 let calls = Calls::default();
934 let consumer = Self {
935 subscription: subscription.into(),
936 script,
937 calls: calls.clone(),
938 };
939 (consumer, calls)
940 }
941 }
942
943 #[async_trait::async_trait]
944 impl EventConsumer for ScriptedConsumer {
945 type Topic = ();
946
947 fn subscriptions(&self) -> Subscriptions<()> {
948 self.subscription.clone().into()
949 }
950
951 async fn handle_event(&self, _topic: &(), delivery: &Delivery) -> Result<(), HandlerError> {
952 let call = Call {
953 content: String::from_utf8(delivery.message().content().to_vec())?,
954 attempt: delivery.attempt(),
955 transport: delivery
956 .transport::<RecordingHandle>()
957 .map(|handle| handle.id),
958 };
959 self.calls.lock().unwrap().push(call);
960 match self.script {
961 Script::FailAttempts(failing) if delivery.attempt() <= failing => {
962 Err(anyhow::anyhow!("attempt {} failed", delivery.attempt()).into())
963 }
964 Script::Succeed | Script::FailAttempts(_) => Ok(()),
965 Script::FailPermanently => Err(HandlerError::permanent(anyhow::anyhow!(
966 "malformed payload"
967 ))),
968 Script::Panic => panic!("handler bug"),
969 }
970 }
971 }
972
973 type Settlements = Arc<Mutex<Vec<(&'static str, Settlement)>>>;
974
975 #[derive(Debug)]
977 struct RecordingHandle {
978 id: &'static str,
979 settlements: Settlements,
980 retries: Arc<AtomicUsize>,
981 }
982
983 impl RecordingHandle {
984 fn new(id: &'static str, settlements: &Settlements, retries: &Arc<AtomicUsize>) -> Self {
985 Self {
986 id,
987 settlements: settlements.clone(),
988 retries: retries.clone(),
989 }
990 }
991 }
992
993 #[async_trait::async_trait]
994 impl TransportHandle for RecordingHandle {
995 async fn settle(&self, settlement: &Settlement) -> anyhow::Result<()> {
996 self.settlements
997 .lock()
998 .unwrap()
999 .push((self.id, settlement.clone()));
1000 Ok(())
1001 }
1002
1003 async fn on_retry(&self, _attempt: u32, _delay: Duration) -> anyhow::Result<()> {
1004 self.retries.fetch_add(1, Ordering::SeqCst);
1005 Ok(())
1006 }
1007 }
1008
1009 #[derive(Debug, Default)]
1011 struct CollectingSink {
1012 letters: Mutex<Vec<DeadLetter>>,
1013 }
1014
1015 #[async_trait::async_trait]
1016 impl DeadLetterSink for CollectingSink {
1017 async fn dead_letter(&self, letter: DeadLetter) -> anyhow::Result<()> {
1018 self.letters.lock().unwrap().push(letter);
1019 Ok(())
1020 }
1021 }
1022
1023 async fn wait_until(condition: impl Fn() -> bool) {
1024 tokio::time::timeout(Duration::from_secs(5), async {
1025 while !condition() {
1026 tokio::time::sleep(Duration::from_millis(1)).await;
1027 }
1028 })
1029 .await
1030 .expect("condition was not met in time");
1031 }
1032
1033 #[test]
1034 fn test_event_submission() {
1035 let event_message = EventMessage::new("test", "1234").unwrap();
1036
1037 let sub_a = EventSubmission::from(&event_message);
1038 let sub_b = EventSubmission::from(event_message);
1039 assert_eq!(sub_a, sub_b);
1040
1041 let sub_c = [("test", "1234"), ("foo", "bar")]
1042 .iter()
1043 .map(|(topic, content)| EventMessage::new(*topic, *content).unwrap())
1044 .collect::<EventSubmission>();
1045 if let EventSubmission::Batch(val) = sub_c {
1046 assert_eq!(val.len(), 2);
1047 assert_eq!(val[0], EventMessage::new("test", "1234").unwrap());
1048 }
1049 }
1050
1051 #[test]
1052 fn test_topic_segment_parse() {
1053 let literal = TopicSegment::parse("test").unwrap();
1054 let wildcard = TopicSegment::parse("*").unwrap();
1055 let simple_selection = TopicSegment::parse("[five,four,six]").unwrap();
1056 let unsorted_and_duplicates_selection = TopicSegment::parse("[2, 1, 3, 2]").unwrap();
1057 let selection_with_wildcard = TopicSegment::parse("[one, two, *]").unwrap();
1058 let single_item_selection = TopicSegment::parse("[one]").unwrap();
1059 assert_eq!(TopicSegment::Literal("test".to_string()), literal);
1060 assert_eq!(TopicSegment::Wildcard, wildcard);
1061 assert_eq!(
1062 TopicSegment::Selection(vec![
1063 "five".to_string(),
1064 "four".to_string(),
1065 "six".to_string()
1066 ]),
1067 simple_selection
1068 );
1069 assert_eq!(
1070 TopicSegment::Selection(vec!["1".to_string(), "2".to_string(), "3".to_string(),]),
1071 unsorted_and_duplicates_selection
1072 );
1073 assert_eq!(TopicSegment::Wildcard, selection_with_wildcard);
1074 assert_eq!(
1075 TopicSegment::Literal("one".to_string()),
1076 single_item_selection
1077 );
1078 assert_eq!(
1079 TopicSegment::Literal("one".to_string()),
1080 TopicSegment::parse("[one,one]").unwrap()
1081 );
1082 }
1083
1084 #[test]
1085 fn test_topic_segment_parse_rejects_malformed_segments() {
1086 use super::TopicError;
1087
1088 assert_eq!(TopicSegment::parse(""), Err(TopicError::EmptySegment));
1089 assert_eq!(
1090 TopicSegment::parse("[a,]"),
1091 Err(TopicError::InvalidSelectionValue {
1092 segment: "[a,]".to_string(),
1093 value: String::new(),
1094 })
1095 );
1096 assert_eq!(
1097 TopicSegment::parse("[,]"),
1098 Err(TopicError::InvalidSelectionValue {
1099 segment: "[,]".to_string(),
1100 value: String::new(),
1101 })
1102 );
1103 assert_eq!(
1104 TopicSegment::parse("[]"),
1105 Err(TopicError::InvalidSelectionValue {
1106 segment: "[]".to_string(),
1107 value: String::new(),
1108 })
1109 );
1110 assert_eq!(
1111 TopicSegment::parse("[a[b]"),
1112 Err(TopicError::InvalidSelectionValue {
1113 segment: "[a[b]".to_string(),
1114 value: "a[b".to_string(),
1115 })
1116 );
1117 assert_eq!(
1118 TopicSegment::parse("[a*b,c]"),
1119 Err(TopicError::InvalidSelectionValue {
1120 segment: "[a*b,c]".to_string(),
1121 value: "a*b".to_string(),
1122 })
1123 );
1124 assert_eq!(
1125 TopicSegment::parse("a[b]"),
1126 Err(TopicError::InvalidSelection("a[b]".to_string()))
1127 );
1128 assert_eq!(
1129 TopicSegment::parse("[a"),
1130 Err(TopicError::InvalidSelection("[a".to_string()))
1131 );
1132 assert_eq!(
1133 TopicSegment::parse("a,b"),
1134 Err(TopicError::UnbracketedComma("a,b".to_string()))
1135 );
1136 assert_eq!(
1137 TopicSegment::parse("a*b"),
1138 Err(TopicError::EmbeddedWildcard("a*b".to_string()))
1139 );
1140 }
1141
1142 #[test]
1143 fn test_topic_segment_matching() {
1144 let literal = TopicSegment::parse("test").unwrap();
1145 let wildcard = TopicSegment::parse("*").unwrap();
1146 let selection = TopicSegment::parse("[five,four,six]").unwrap();
1147
1148 assert!(literal.matches(&TopicSegment::Literal("test".to_string())));
1149 assert!(!literal.matches(&TopicSegment::Literal("tset".to_string())));
1150 assert!(wildcard.matches(&TopicSegment::Literal("1234124¶áðfå".to_string())));
1151 assert!(wildcard.matches(&TopicSegment::Literal("fnord".to_string())));
1152 assert!(selection.matches(&TopicSegment::Literal("five".to_string())));
1153 assert!(selection.matches(&TopicSegment::Literal("four".to_string())));
1154 assert!(selection.matches(&TopicSegment::Literal("six".to_string())));
1155 assert!(!selection.matches(&TopicSegment::Literal("test".to_string())));
1156 assert!(!selection.matches(&TopicSegment::Literal("foo".to_string())));
1157 }
1158
1159 #[test]
1160 fn test_event_topic() {
1161 let simple_topic = EventTopic::new("test.topic").as_routing_key().unwrap();
1162 let wildcard_topic = EventTopic::new("*").as_routing_key();
1163 let selection_topic = EventTopic::new("test.[one,two,three]")
1164 .as_subscription()
1165 .unwrap();
1166 let wildcard_tail_topic = EventTopic::new("test.*").as_subscription().unwrap();
1167 let wildcard_tail_topic_multiple = EventTopic::new("test.*.*").as_subscription().unwrap();
1168 let wildcard_tail_topic_selection = EventTopic::new("test.[one,two,*]")
1169 .as_subscription()
1170 .unwrap();
1171 let wildcard_tail_topic_selection_multiple = EventTopic::new("test.[one,two,*].*")
1172 .as_subscription()
1173 .unwrap();
1174 let topic_selection_wildcard_unsorted_duplicates =
1175 EventTopic::new("test.*.[5,2,4,3,5,2,1]")
1176 .as_subscription()
1177 .unwrap();
1178
1179 assert_eq!(simple_topic.text(), "test.topic");
1180 assert!(wildcard_topic.is_err());
1181 assert_eq!(
1182 selection_topic.segments(),
1183 vec![
1184 TopicSegment::Literal("test".to_string()),
1185 TopicSegment::Selection(vec![
1186 "one".to_string(),
1187 "three".to_string(),
1188 "two".to_string(),
1189 ])
1190 ]
1191 );
1192 assert!(wildcard_tail_topic.is_tail_matching);
1193 assert!(wildcard_tail_topic.match_topic(&simple_topic));
1194 assert_eq!(
1195 wildcard_tail_topic_multiple.segments(),
1196 vec![
1197 TopicSegment::Literal("test".to_string()),
1198 TopicSegment::Wildcard
1199 ]
1200 );
1201 assert!(wildcard_tail_topic_multiple.match_topic(&simple_topic));
1202 assert!(wildcard_tail_topic_selection.is_tail_matching);
1203 assert_eq!(
1204 wildcard_tail_topic_selection.segments(),
1205 vec![
1206 TopicSegment::Literal("test".to_string()),
1207 TopicSegment::Wildcard
1208 ]
1209 );
1210 assert!(wildcard_tail_topic_selection_multiple.is_tail_matching);
1211 assert!(
1212 wildcard_tail_topic_selection_multiple
1213 .match_topic(&EventTopic::new("test.foo.bar").as_routing_key().unwrap())
1214 );
1215 assert_eq!(
1216 wildcard_tail_topic_selection_multiple.segments(),
1217 vec![
1218 TopicSegment::Literal("test".to_string()),
1219 TopicSegment::Wildcard
1220 ]
1221 );
1222 assert_eq!(
1223 topic_selection_wildcard_unsorted_duplicates
1224 .segments()
1225 .len(),
1226 3
1227 );
1228 assert!(
1229 topic_selection_wildcard_unsorted_duplicates
1230 .match_topic(&EventTopic::new("test.foo.1").as_routing_key().unwrap())
1231 );
1232 assert!(
1233 topic_selection_wildcard_unsorted_duplicates
1234 .match_topic(&EventTopic::new("test.bar.5").as_routing_key().unwrap())
1235 );
1236 assert!(
1237 !topic_selection_wildcard_unsorted_duplicates
1238 .match_topic(&EventTopic::new("test.bar.fnord").as_routing_key().unwrap())
1239 );
1240 assert_eq!(
1241 topic_selection_wildcard_unsorted_duplicates.segments(),
1242 vec![
1243 TopicSegment::Literal("test".to_string()),
1244 TopicSegment::Wildcard,
1245 TopicSegment::Selection(vec![
1246 "1".to_string(),
1247 "2".to_string(),
1248 "3".to_string(),
1249 "4".to_string(),
1250 "5".to_string()
1251 ])
1252 ]
1253 );
1254 }
1255
1256 #[tokio::test]
1257 async fn test_event_broker() {
1258 #[derive(Debug, Default, Clone)]
1259 struct TestConsumer {
1260 name: String,
1261 results: Arc<DashMap<String, Vec<EventMessage>>>,
1262 topic: String,
1263 }
1264
1265 #[async_trait::async_trait]
1266 impl EventConsumer for TestConsumer {
1267 type Topic = ();
1268
1269 fn subscriptions(&self) -> Subscriptions<()> {
1270 self.topic.as_str().into()
1271 }
1272
1273 async fn handle_event(
1274 &self,
1275 _topic: &(),
1276 delivery: &Delivery,
1277 ) -> Result<(), HandlerError> {
1278 self.results
1279 .entry(self.name.clone())
1280 .or_default()
1281 .push(delivery.message().as_ref().clone());
1282 Ok(())
1283 }
1284 }
1285
1286 #[derive(Debug, Clone)]
1287 struct TestEmitter {
1288 sender: mpsc::Sender<EventMessage>,
1289 }
1290
1291 #[async_trait::async_trait]
1292 impl EventEmitter for TestEmitter {
1293 fn get_sender(&self) -> &mpsc::Sender<EventMessage> {
1294 &self.sender
1295 }
1296 }
1297
1298 let consumer_results = Arc::new(DashMap::new());
1299
1300 let broker = EventBroker::new(10).run().unwrap();
1301 let consumer = TestConsumer {
1302 name: "consumer".to_string(),
1303 topic: String::from("test.topics.*"),
1304 results: consumer_results.clone(),
1305 };
1306 let consumer2 = TestConsumer {
1307 name: "consumer2".to_string(),
1308 topic: String::from("test.[one,two,three]"),
1309 results: consumer_results.clone(),
1310 };
1311 let consumer3 = TestConsumer {
1312 name: "consumer3".to_string(),
1313 topic: String::from("test.*.[5,2,4,3,5,2,1]"),
1314 results: consumer_results.clone(),
1315 };
1316 let consumer4 = TestConsumer {
1317 name: "consumer4".to_string(),
1318 topic: String::from("test.[should,fail]"),
1319 results: consumer_results.clone(),
1320 };
1321 broker.add_topic_consumer(consumer).unwrap();
1322 broker.add_topic_consumer(consumer2).unwrap();
1323 broker.add_topic_consumer(consumer3).unwrap();
1324 broker.add_topic_consumer(consumer4).unwrap();
1325
1326 let events = [
1327 EventMessage::new("test.one", "consumer2 stuff test1").unwrap(),
1328 EventMessage::new("test.two", "consumer2 stuff test2").unwrap(),
1329 EventMessage::new("test.three", "consumer2 stuff test3").unwrap(),
1330 EventMessage::new("test.topics.foo", "consumer stuff test1").unwrap(),
1331 EventMessage::new("test.topics.1", "consumer and consumer3 stuff test").unwrap(),
1332 EventMessage::new("test.bar.5", "consumer3 stuff test").unwrap(),
1333 EventMessage::new("test.baz.foo", "non routeable stuff test1").unwrap(),
1334 EventMessage::new("test.four", "non routeable stuff test2").unwrap(),
1335 ];
1336
1337 let emitter = TestEmitter {
1338 sender: broker.get_sender(),
1339 };
1340
1341 emitter
1342 .submit_event(EventSubmission::from_iter(events[..4].iter().cloned()))
1343 .await
1344 .unwrap();
1345 emitter
1346 .submit_event(EventSubmission::Single(events.get(4).unwrap().clone()))
1347 .await
1348 .unwrap();
1349 emitter
1350 .submit_event(EventSubmission::Batch(events[5..7].to_vec()))
1351 .await
1352 .unwrap();
1353 emitter
1354 .submit_event(EventSubmission::from(events.last().unwrap().clone()))
1355 .await
1356 .unwrap();
1357
1358 let _ = broker.stop().await;
1359
1360 let consumer_messages = consumer_results.get("consumer").unwrap();
1361 let consumer2_messages = consumer_results.get("consumer2").unwrap();
1362 let consumer3_messages = consumer_results.get("consumer3").unwrap();
1363
1364 assert!(consumer_messages.contains(events.get(3).unwrap()));
1365 assert!(consumer_messages.contains(events.get(4).unwrap()));
1366 assert!(consumer2_messages.contains(events.first().unwrap()));
1367 assert!(consumer2_messages.contains(events.get(1).unwrap()));
1368 assert!(consumer2_messages.contains(events.get(2).unwrap()));
1369 assert!(consumer3_messages.contains(events.get(4).unwrap()));
1370 assert!(consumer3_messages.contains(events.get(5).unwrap()));
1371
1372 assert!(
1373 consumer_results
1374 .iter()
1375 .flat_map(|entry| entry.value().clone())
1376 .all(|v| v != *events.last().unwrap() && v != *events.get(6).unwrap())
1377 );
1378 }
1379
1380 #[test]
1381 fn test_run_outside_runtime_fails() {
1382 assert!(EventBroker::new(1).run().is_err());
1383 }
1384
1385 #[test]
1386 fn test_remove_topic_consumer() {
1387 #[derive(Debug)]
1388 struct NoopConsumer;
1389
1390 #[async_trait::async_trait]
1391 impl EventConsumer for NoopConsumer {
1392 type Topic = ();
1393
1394 fn subscriptions(&self) -> Subscriptions<()> {
1395 "test.remove".into()
1396 }
1397
1398 async fn handle_event(
1399 &self,
1400 _topic: &(),
1401 _delivery: &Delivery,
1402 ) -> Result<(), HandlerError> {
1403 Ok(())
1404 }
1405 }
1406
1407 let broker = EventBroker::new(1);
1408 let handle = broker.add_topic_consumer(NoopConsumer).unwrap();
1409 let routing_key = EventTopic::new("test.remove").as_routing_key().unwrap();
1410 assert_eq!(broker.find_subscriptions(&routing_key).len(), 1);
1411
1412 assert!(broker.remove_topic_consumer(handle.clone()));
1413 assert!(broker.find_subscriptions(&routing_key).is_empty());
1414 assert!(broker.subscriptions().is_empty());
1415 assert!(!broker.remove_topic_consumer(handle));
1416 }
1417
1418 #[test]
1419 fn test_consumer_with_invalid_pattern_is_not_registered() {
1420 #[derive(Debug)]
1421 struct PartlyValid;
1422
1423 #[async_trait::async_trait]
1424 impl EventConsumer for PartlyValid {
1425 type Topic = u8;
1426
1427 fn subscriptions(&self) -> Subscriptions<u8> {
1428 Subscriptions::new().on(1, "valid.*").on(2, "not valid")
1429 }
1430
1431 async fn handle_event(
1432 &self,
1433 _topic: &u8,
1434 _delivery: &Delivery,
1435 ) -> Result<(), HandlerError> {
1436 Ok(())
1437 }
1438 }
1439
1440 let broker = EventBroker::new(1);
1441 assert!(broker.add_topic_consumer(PartlyValid).is_err());
1442 assert!(broker.subscriptions().is_empty());
1443 }
1444
1445 #[tokio::test]
1446 async fn test_stop_waits_for_in_flight_handlers() {
1447 #[derive(Debug)]
1448 struct SlowConsumer {
1449 results: Arc<DashMap<String, Vec<EventMessage>>>,
1450 }
1451
1452 #[async_trait::async_trait]
1453 impl EventConsumer for SlowConsumer {
1454 type Topic = ();
1455
1456 fn subscriptions(&self) -> Subscriptions<()> {
1457 "slow.*".into()
1458 }
1459
1460 async fn handle_event(
1461 &self,
1462 _topic: &(),
1463 delivery: &Delivery,
1464 ) -> Result<(), HandlerError> {
1465 tokio::time::sleep(Duration::from_millis(50)).await;
1466 self.results
1467 .entry("slow".to_string())
1468 .or_default()
1469 .push(delivery.message().as_ref().clone());
1470 Ok(())
1471 }
1472 }
1473
1474 #[derive(Debug)]
1475 struct TestEmitter {
1476 sender: mpsc::Sender<EventMessage>,
1477 }
1478
1479 #[async_trait::async_trait]
1480 impl EventEmitter for TestEmitter {
1481 fn get_sender(&self) -> &mpsc::Sender<EventMessage> {
1482 &self.sender
1483 }
1484 }
1485
1486 let results = Arc::new(DashMap::new());
1487 let broker = EventBroker::new(10);
1488 broker
1489 .add_topic_consumer(SlowConsumer {
1490 results: results.clone(),
1491 })
1492 .unwrap();
1493 let broker = broker.run().unwrap();
1494 let emitter = TestEmitter {
1495 sender: broker.get_sender(),
1496 };
1497
1498 let events = (0..3)
1499 .map(|i| EventMessage::new(format!("slow.msg{i}"), "payload").unwrap())
1500 .collect::<Vec<_>>();
1501 emitter
1502 .submit_event(events.iter().cloned().collect())
1503 .await
1504 .unwrap();
1505 tokio::task::yield_now().await;
1507
1508 let _ = broker.stop().await;
1509
1510 assert_eq!(results.get("slow").map(|v| v.len()), Some(events.len()));
1511 }
1512
1513 #[tokio::test]
1514 async fn test_multi_topic_consumer_handles_topics_individually() {
1515 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
1516 enum ShopTopic {
1517 Orders,
1518 EuPayments,
1519 }
1520
1521 #[derive(Debug, Default)]
1522 struct Shop {
1523 received: Arc<Mutex<Vec<(ShopTopic, String)>>>,
1524 }
1525
1526 #[async_trait::async_trait]
1527 impl EventConsumer for Shop {
1528 type Topic = ShopTopic;
1529
1530 fn subscriptions(&self) -> Subscriptions<ShopTopic> {
1531 Subscriptions::new()
1532 .on(ShopTopic::Orders, "orders.*")
1533 .on(ShopTopic::EuPayments, "[orders,payments].eu.*")
1534 }
1535
1536 async fn handle_event(
1537 &self,
1538 topic: &ShopTopic,
1539 delivery: &Delivery,
1540 ) -> Result<(), HandlerError> {
1541 let routing_key = delivery.message().topic().text().to_string();
1542 self.received.lock().unwrap().push((*topic, routing_key));
1543 Ok(())
1544 }
1545 }
1546
1547 let shop = Shop::default();
1548 let received = shop.received.clone();
1549
1550 let delivery = Delivery::new(
1552 EventMessage::new("orders.test", "").unwrap(),
1553 EventTopic::new("orders.*").as_subscription().unwrap(),
1554 );
1555 shop.handle_event(&ShopTopic::Orders, &delivery)
1556 .await
1557 .unwrap();
1558 assert_eq!(received.lock().unwrap().len(), 1);
1559 received.lock().unwrap().clear();
1560
1561 let broker = EventBroker::new(8).run().unwrap();
1562 let handle = broker.add_topic_consumer(shop).unwrap();
1563 let routed = broker.find_subscriptions(
1564 &EventTopic::new("orders.eu.created")
1565 .as_routing_key()
1566 .unwrap(),
1567 );
1568 assert_eq!(routed.len(), 2);
1569 assert!(routed.iter().all(|info| info.consumer().ends_with("Shop")));
1570
1571 for topic in [
1572 "orders.us.created",
1573 "orders.eu.created",
1574 "payments.eu.settled",
1575 "payments.us.settled",
1576 ] {
1577 broker
1578 .get_sender()
1579 .send(EventMessage::new(topic, "").unwrap())
1580 .await
1581 .unwrap();
1582 }
1583 let broker = broker.stop().await;
1584
1585 let mut received = received.lock().unwrap().clone();
1586 received.sort();
1587 assert_eq!(
1588 received,
1589 [
1590 (ShopTopic::Orders, "orders.eu.created".to_string()),
1591 (ShopTopic::Orders, "orders.us.created".to_string()),
1592 (ShopTopic::EuPayments, "orders.eu.created".to_string()),
1593 (ShopTopic::EuPayments, "payments.eu.settled".to_string()),
1594 ]
1595 );
1596
1597 assert!(broker.remove_topic_consumer(handle));
1598 assert!(broker.subscriptions().is_empty());
1599 }
1600
1601 #[tokio::test]
1602 async fn test_retries_until_handled_and_settles_once() {
1603 let settlements = Settlements::default();
1604 let retries = Arc::new(AtomicUsize::new(0));
1605 let (consumer, calls) = ScriptedConsumer::new(
1606 Subscription::new("jobs.*")
1607 .with_retry_policy(RetryPolicy::fixed(3, Duration::from_millis(1))),
1608 Script::FailAttempts(2),
1609 );
1610 let broker = EventBroker::new(4).run().unwrap();
1611 broker.add_topic_consumer(consumer).unwrap();
1612
1613 let message = EventMessage::new("jobs.render", "job")
1614 .unwrap()
1615 .with_transport(RecordingHandle::new("job", &settlements, &retries));
1616 broker.get_sender().send(message).await.unwrap();
1617 let _ = broker.stop().await;
1618
1619 let attempts = calls
1620 .lock()
1621 .unwrap()
1622 .iter()
1623 .map(|call| (call.attempt, call.transport))
1624 .collect::<Vec<_>>();
1625 assert_eq!(
1626 attempts,
1627 [(1, Some("job")), (2, Some("job")), (3, Some("job"))]
1628 );
1629 assert_eq!(retries.load(Ordering::SeqCst), 2);
1630 let settlements = settlements.lock().unwrap();
1631 assert_eq!(settlements.len(), 1);
1632 assert!(settlements[0].1.all_handled());
1633 }
1634
1635 #[tokio::test]
1636 async fn test_settlement_reports_every_subscription() {
1637 #[derive(Debug)]
1638 struct FailingSink;
1639
1640 #[async_trait::async_trait]
1641 impl DeadLetterSink for FailingSink {
1642 async fn dead_letter(&self, _letter: DeadLetter) -> anyhow::Result<()> {
1643 anyhow::bail!("dead letter queue unavailable")
1644 }
1645 }
1646
1647 let settlements = Settlements::default();
1648 let retries = Arc::new(AtomicUsize::new(0));
1649 let sink = Arc::new(CollectingSink::default());
1650 let broker = EventBroker::new(4)
1651 .with_dead_letter_sink(sink.clone())
1652 .run()
1653 .unwrap();
1654 let consumers = [
1655 ScriptedConsumer::new("orders.*", Script::Succeed),
1656 ScriptedConsumer::new("orders.eu.*", Script::FailPermanently),
1657 ScriptedConsumer::new(
1658 Subscription::new("orders.[eu,us].created")
1659 .with_dead_letter_sink(Arc::new(FailingSink)),
1660 Script::FailPermanently,
1661 ),
1662 ];
1663 for (consumer, _) in consumers {
1664 broker.add_topic_consumer(consumer).unwrap();
1665 }
1666
1667 let sender = broker.get_sender();
1668 let routed = EventMessage::new("orders.eu.created", "{}")
1669 .unwrap()
1670 .with_transport(RecordingHandle::new("routed", &settlements, &retries));
1671 let unrouted = EventMessage::new("invoices.created", "{}")
1672 .unwrap()
1673 .with_transport(RecordingHandle::new("unrouted", &settlements, &retries));
1674 sender.send(routed).await.unwrap();
1675 sender.send(unrouted).await.unwrap();
1676 let _ = broker.stop().await;
1677
1678 let mut settlements = settlements.lock().unwrap().clone();
1679 settlements.sort_by_key(|(id, _)| *id);
1680 let [("routed", routed), ("unrouted", unrouted)] = settlements.as_slice() else {
1681 panic!("expected one settlement per message, got {settlements:?}");
1682 };
1683 let mut outcomes = routed
1684 .outcomes()
1685 .iter()
1686 .map(|outcome| (outcome.subscription().pattern().text(), outcome.outcome()))
1687 .collect::<Vec<_>>();
1688 outcomes.sort_by_key(|(pattern, _)| *pattern);
1689 assert_eq!(
1690 outcomes,
1691 [
1692 ("orders.*", DeliveryOutcome::Handled),
1693 ("orders.[eu,us].created", DeliveryOutcome::Failed),
1694 ("orders.eu.*", DeliveryOutcome::DeadLettered),
1695 ]
1696 );
1697 assert!(!routed.all_handled() && !routed.any(DeliveryOutcome::Aborted));
1698 assert!(unrouted.is_unrouted() && !unrouted.all_handled());
1699 assert_eq!(retries.load(Ordering::SeqCst), 0);
1700
1701 let letters = sink.letters.lock().unwrap();
1702 let [letter] = letters.as_slice() else {
1703 panic!("expected one dead letter, got {letters:?}");
1704 };
1705 assert_eq!(letter.subscription().pattern().text(), "orders.eu.*");
1706 assert_eq!(letter.attempts(), 1);
1707 assert_eq!(
1708 letter
1709 .message()
1710 .transport::<RecordingHandle>()
1711 .map(|handle| handle.id),
1712 Some("routed")
1713 );
1714 assert!(
1715 matches!(letter.reason(), DeadLetterReason::HandlerFailed(error) if error.is_permanent())
1716 );
1717 }
1718
1719 #[tokio::test]
1720 async fn test_failed_deliveries_are_dead_lettered() {
1721 let sink = Arc::new(CollectingSink::default());
1722 let broker = EventBroker::new(4)
1723 .with_retry_policy(RetryPolicy::linear(
1724 2,
1725 Duration::from_millis(1),
1726 Duration::from_millis(1),
1727 ))
1728 .with_dead_letter_sink(sink.clone());
1729 let consumers = [
1730 ScriptedConsumer::new("flaky.*", Script::FailAttempts(u32::MAX)),
1731 ScriptedConsumer::new("broken.*", Script::FailPermanently),
1732 ScriptedConsumer::new("buggy.*", Script::Panic),
1733 ScriptedConsumer::new(
1734 Subscription::new("stubborn.*")
1735 .with_retry_policy(RetryPolicy::exponential(4, Duration::from_millis(1))),
1736 Script::FailAttempts(u32::MAX),
1737 ),
1738 ];
1739 for (consumer, _) in consumers {
1740 broker.add_topic_consumer(consumer).unwrap();
1741 }
1742 let broker = broker.run().unwrap();
1743
1744 for topic in ["flaky.job", "broken.job", "buggy.job", "stubborn.job"] {
1745 broker
1746 .get_sender()
1747 .send(EventMessage::new(topic, "job").unwrap())
1748 .await
1749 .unwrap();
1750 }
1751 let _ = broker.stop().await;
1752
1753 let mut letters = sink
1754 .letters
1755 .lock()
1756 .unwrap()
1757 .iter()
1758 .map(|letter| {
1759 let DeadLetterReason::HandlerFailed(error) = letter.reason() else {
1760 panic!("unexpected dead letter reason: {}", letter.reason());
1761 };
1762 (
1763 letter.subscription().pattern().text().to_string(),
1764 letter.attempts(),
1765 error.is_permanent(),
1766 error.to_string(),
1767 )
1768 })
1769 .collect::<Vec<_>>();
1770 letters.sort();
1771 assert_eq!(
1772 letters,
1773 [
1774 (
1775 "broken.*".to_string(),
1776 1,
1777 true,
1778 "malformed payload".to_string()
1779 ),
1780 (
1781 "buggy.*".to_string(),
1782 1,
1783 true,
1784 "event handler panicked: handler bug".to_string()
1785 ),
1786 (
1787 "flaky.*".to_string(),
1788 3,
1789 false,
1790 "attempt 3 failed".to_string()
1791 ),
1792 (
1793 "stubborn.*".to_string(),
1794 5,
1795 false,
1796 "attempt 5 failed".to_string()
1797 ),
1798 ]
1799 );
1800 }
1801
1802 #[tokio::test]
1803 async fn test_sequential_subscription_retries_in_order() {
1804 let (consumer, calls) = ScriptedConsumer::new(
1805 Subscription::new("sequence.*")
1806 .with_concurrency(1)
1807 .with_retry_policy(RetryPolicy::fixed(1, Duration::from_millis(2))),
1808 Script::FailAttempts(1),
1809 );
1810 let broker = EventBroker::new(8);
1811 broker.add_topic_consumer(consumer).unwrap();
1812 let broker = broker.run().unwrap();
1813
1814 for step in 0..4 {
1815 broker
1816 .get_sender()
1817 .send(EventMessage::new("sequence.step", step.to_string()).unwrap())
1818 .await
1819 .unwrap();
1820 }
1821 let _ = broker.stop().await;
1822
1823 let calls = calls
1824 .lock()
1825 .unwrap()
1826 .iter()
1827 .map(|call| (call.content.clone(), call.attempt))
1828 .collect::<Vec<_>>();
1829 let expected = (0..4)
1830 .flat_map(|step| [(step.to_string(), 1), (step.to_string(), 2)])
1831 .collect::<Vec<_>>();
1832 assert_eq!(calls, expected);
1833 }
1834
1835 #[tokio::test]
1836 async fn test_overflowing_inbox_is_dead_lettered() {
1837 #[derive(Debug)]
1838 struct GatedConsumer {
1839 gate: Arc<Semaphore>,
1840 handled: Arc<AtomicUsize>,
1841 }
1842
1843 #[async_trait::async_trait]
1844 impl EventConsumer for GatedConsumer {
1845 type Topic = ();
1846
1847 fn subscriptions(&self) -> Subscriptions<()> {
1848 Subscription::new("gated.*")
1849 .with_inbox_capacity(1)
1850 .with_concurrency(1)
1851 .with_overflow(Overflow::DeadLetter)
1852 .into()
1853 }
1854
1855 async fn handle_event(
1856 &self,
1857 _topic: &(),
1858 _delivery: &Delivery,
1859 ) -> Result<(), HandlerError> {
1860 self.gate.acquire().await?.forget();
1861 self.handled.fetch_add(1, Ordering::SeqCst);
1862 Ok(())
1863 }
1864 }
1865
1866 let gate = Arc::new(Semaphore::new(0));
1867 let handled = Arc::new(AtomicUsize::new(0));
1868 let sink = Arc::new(CollectingSink::default());
1869 let broker = EventBroker::new(8)
1870 .with_dead_letter_sink(sink.clone())
1871 .run()
1872 .unwrap();
1873 broker
1874 .add_topic_consumer(GatedConsumer {
1875 gate: gate.clone(),
1876 handled: handled.clone(),
1877 })
1878 .unwrap();
1879
1880 for job in 0..4 {
1881 broker
1882 .get_sender()
1883 .send(EventMessage::new("gated.job", job.to_string()).unwrap())
1884 .await
1885 .unwrap();
1886 }
1887 wait_until(|| sink.letters.lock().unwrap().len() >= 2).await;
1890 gate.add_permits(4);
1891 let _ = broker.stop().await;
1892
1893 let letters = sink.letters.lock().unwrap();
1894 assert_eq!(handled.load(Ordering::SeqCst) + letters.len(), 4);
1895 assert!(letters.iter().all(|letter| {
1896 matches!(letter.reason(), DeadLetterReason::InboxFull) && letter.attempts() == 0
1897 }));
1898 }
1899
1900 #[tokio::test]
1901 async fn test_subscriptions_survive_restarts_and_change_while_running() {
1902 let (first, first_calls) = ScriptedConsumer::new("lifecycle.*", Script::Succeed);
1903 let (second, second_calls) = ScriptedConsumer::new("lifecycle.*", Script::Succeed);
1904 let contents = |calls: &Calls| {
1905 calls
1906 .lock()
1907 .unwrap()
1908 .iter()
1909 .map(|call| call.content.clone())
1910 .collect::<Vec<_>>()
1911 };
1912
1913 let broker = EventBroker::new(4);
1914 let first_handle = broker.add_topic_consumer(first).unwrap();
1915 let broker = broker.run().unwrap();
1916 broker
1917 .get_sender()
1918 .send(EventMessage::new("lifecycle.event", "1").unwrap())
1919 .await
1920 .unwrap();
1921 let broker = broker.stop().await.run().unwrap();
1922
1923 broker
1924 .get_sender()
1925 .send(EventMessage::new("lifecycle.event", "2").unwrap())
1926 .await
1927 .unwrap();
1928 wait_until(|| first_calls.lock().unwrap().len() == 2).await;
1929 assert!(broker.remove_topic_consumer(first_handle));
1930 broker.add_topic_consumer(second).unwrap();
1931 broker
1932 .get_sender()
1933 .send(EventMessage::new("lifecycle.event", "3").unwrap())
1934 .await
1935 .unwrap();
1936 let _ = broker.stop().await;
1937
1938 assert_eq!(contents(&first_calls), ["1", "2"]);
1939 assert_eq!(contents(&second_calls), ["3"]);
1940 }
1941}