rskit_messaging/
runner.rs1use std::sync::Arc;
4use std::time::{Duration, Instant};
5
6use rskit_errors::{AppError, AppResult, ErrorCode};
7use rskit_stream::SpawnedTask;
8use tracing;
9
10use crate::handler::MessageHandler;
11use crate::metrics::{MetricsCollector, NoopMetrics};
12use crate::traits::MessageConsumer;
13
14pub struct ConsumerRunner<T: Send + Sync + Clone + 'static> {
20 consumer: Arc<dyn MessageConsumer<T>>,
21 handler: Arc<dyn MessageHandler<T>>,
22 metrics: Arc<dyn MetricsCollector>,
23 task: parking_lot::Mutex<Option<SpawnedTask>>,
24 shutdown_timeout: Duration,
25}
26
27impl<T: Send + Sync + Clone + 'static> ConsumerRunner<T> {
28 pub fn new(consumer: Arc<dyn MessageConsumer<T>>, handler: Arc<dyn MessageHandler<T>>) -> Self {
30 Self {
31 consumer,
32 handler,
33 metrics: Arc::new(NoopMetrics),
34 task: parking_lot::Mutex::new(None),
35 shutdown_timeout: Duration::from_secs(10),
36 }
37 }
38
39 #[must_use]
41 pub fn with_metrics(mut self, metrics: Arc<dyn MetricsCollector>) -> Self {
42 self.metrics = metrics;
43 self
44 }
45
46 #[must_use]
48 pub const fn with_shutdown_timeout(mut self, timeout: Duration) -> Self {
49 self.shutdown_timeout = timeout;
50 self
51 }
52
53 pub fn is_running(&self) -> bool {
55 let guard = self.task.lock();
56 guard.as_ref().is_some_and(|t| !t.is_finished())
57 }
58
59 pub fn run(&self) -> AppResult<()> {
61 {
62 let guard = self.task.lock();
63 if guard.as_ref().is_some_and(|t| !t.is_finished()) {
64 return Err(AppError::new(
65 ErrorCode::InvalidInput,
66 "runner is already running",
67 ));
68 }
69 }
70
71 let consumer = self.consumer.clone();
72 let handler = self.handler.clone();
73 let metrics = self.metrics.clone();
74
75 let task = SpawnedTask::spawn(move |cancel| async move {
76 tracing::info!("consumer runner started");
77 loop {
78 tokio::select! {
79 () = cancel.cancelled() => {
80 tracing::info!("consumer runner cancelled");
81 break;
82 }
83 result = consumer.recv() => {
84 match result {
85 Ok(msg) => {
86 let topic = msg.topic.clone();
87 let start = Instant::now();
88 let handle_result = handler.handle(msg).await;
89 metrics.record_consume(
90 &topic,
91 start.elapsed(),
92 handle_result.is_ok(),
93 );
94 if let Err(e) = handle_result {
95 tracing::warn!(error = %e, "handler error in runner");
96 }
97 }
98 Err(e) => {
99 if cancel.is_cancelled() {
100 break;
101 }
102 tracing::error!(error = %e, "recv error in runner");
103 tokio::time::sleep(Duration::from_millis(100)).await;
104 }
105 }
106 }
107 }
108 }
109 tracing::info!("consumer runner exited");
110 });
111
112 *self.task.lock() = Some(task);
113
114 Ok(())
115 }
116
117 pub async fn stop(&self) -> AppResult<()> {
119 let task = {
120 let mut guard = self.task.lock();
121 guard.take()
122 };
123
124 let Some(task) = task else {
125 return Err(AppError::new(
126 ErrorCode::InvalidInput,
127 "runner is not running",
128 ));
129 };
130
131 task.shutdown(self.shutdown_timeout).await;
132 tracing::info!("consumer runner stopped");
133 Ok(())
134 }
135}
136
137#[cfg(test)]
138mod tests {
139 use std::sync::atomic::{AtomicU32, Ordering};
140
141 use super::*;
142 use crate::handler::FnHandler;
143 use crate::memory::InMemoryBroker;
144 use crate::message::Message;
145 use crate::traits::{self, MessageProducer};
146
147 #[tokio::test]
148 async fn runner_start_and_stop() {
149 let broker = InMemoryBroker::<String>::new(16);
150 let consumer = broker.consumer();
151
152 let handler: Arc<dyn MessageHandler<String>> =
153 Arc::new(FnHandler::new(|_msg: Message<String>| async { Ok(()) }));
154
155 let runner = ConsumerRunner::new(Arc::new(consumer), handler);
156
157 runner.run().unwrap();
158 assert!(runner.is_running());
159
160 runner.stop().await.unwrap();
161 assert!(!runner.is_running());
163 }
164
165 #[tokio::test]
166 async fn runner_processes_messages() {
167 let broker = InMemoryBroker::<String>::new(16);
168 let producer = broker.producer();
169 let consumer = broker.consumer();
170
171 traits::MessageConsumer::subscribe(&consumer, &["topic"])
172 .await
173 .unwrap();
174
175 let counter = Arc::new(AtomicU32::new(0));
176 let c = counter.clone();
177 let handler: Arc<dyn MessageHandler<String>> =
178 Arc::new(FnHandler::new(move |_msg: Message<String>| {
179 let c = c.clone();
180 async move {
181 c.fetch_add(1, Ordering::SeqCst);
182 Ok(())
183 }
184 }));
185
186 let runner = ConsumerRunner::new(Arc::new(consumer), handler);
187 runner.run().unwrap();
188
189 producer
190 .send(Message::new("topic", "msg1".to_string()))
191 .await
192 .unwrap();
193 producer
194 .send(Message::new("topic", "msg2".to_string()))
195 .await
196 .unwrap();
197
198 tokio::time::sleep(Duration::from_millis(150)).await;
199 assert_eq!(counter.load(Ordering::SeqCst), 2);
200
201 runner.stop().await.unwrap();
202 }
203
204 #[tokio::test]
205 async fn double_run_returns_error() {
206 let broker = InMemoryBroker::<String>::new(16);
207 let consumer = broker.consumer();
208
209 let handler: Arc<dyn MessageHandler<String>> =
210 Arc::new(FnHandler::new(|_msg: Message<String>| async { Ok(()) }));
211
212 let runner = ConsumerRunner::new(Arc::new(consumer), handler);
213
214 runner.run().unwrap();
215 assert!(runner.run().is_err());
216
217 runner.stop().await.unwrap();
218 }
219
220 #[tokio::test]
221 async fn stop_when_not_running_returns_error() {
222 let broker = InMemoryBroker::<String>::new(16);
223 let consumer = broker.consumer();
224
225 let handler: Arc<dyn MessageHandler<String>> =
226 Arc::new(FnHandler::new(|_msg: Message<String>| async { Ok(()) }));
227
228 let runner = ConsumerRunner::new(Arc::new(consumer), handler);
229 assert!(runner.stop().await.is_err());
230 }
231}