rskit_messaging/
managed_consumer.rs1use std::sync::Arc;
5use std::sync::atomic::{AtomicBool, Ordering};
6use std::time::Instant;
7
8use rskit_errors::{AppError, AppResult, ErrorCode};
9use rskit_resilience::{LinearBackoff, RetryPolicy};
10use rskit_stream::SpawnedTask;
11use tracing;
12
13use crate::handler::MessageHandler;
14use crate::metrics::{MetricsCollector, NoopMetrics};
15use crate::traits::MessageConsumer;
16
17pub struct ManagedConsumer<T: Send + Sync + Clone + 'static> {
22 inner: Arc<dyn MessageConsumer<T>>,
23 handler: Arc<dyn MessageHandler<T>>,
24 metrics: Arc<dyn MetricsCollector>,
25 recv_backoff: RetryPolicy,
26 name: String,
27 running: Arc<AtomicBool>,
28 task: parking_lot::Mutex<Option<SpawnedTask>>,
29}
30
31impl<T: Send + Sync + Clone + 'static> ManagedConsumer<T> {
32 pub fn name(&self) -> &str {
34 &self.name
35 }
36
37 pub fn is_running(&self) -> bool {
39 self.running.load(Ordering::SeqCst)
40 }
41
42 pub fn start(&self) -> AppResult<()> {
44 if self.running.load(Ordering::SeqCst) {
45 return Err(AppError::new(
46 ErrorCode::InvalidInput,
47 format!("consumer '{}' is already running", self.name),
48 ));
49 }
50
51 let consumer = self.inner.clone();
52 let handler = self.handler.clone();
53 let metrics = self.metrics.clone();
54 let recv_backoff = self.recv_backoff.clone();
55 let running = self.running.clone();
56 let name = self.name.clone();
57
58 running.store(true, Ordering::SeqCst);
59
60 let task = SpawnedTask::spawn(move |cancel| async move {
61 tracing::debug!(consumer = %name, "managed consumer loop started");
62 let mut consecutive_errors: u32 = 0;
63 loop {
64 tokio::select! {
65 () = cancel.cancelled() => {
66 tracing::info!(consumer = %name, "managed consumer cancelled");
67 break;
68 }
69 result = consumer.recv() => {
70 match result {
71 Ok(msg) => {
72 consecutive_errors = 0;
73 let topic = msg.topic.clone();
74 let start = Instant::now();
75 let handle_result = handler.handle(msg).await;
76 metrics.record_consume(
77 &topic,
78 start.elapsed(),
79 handle_result.is_ok(),
80 );
81 if let Err(e) = handle_result {
82 tracing::warn!(
83 consumer = %name,
84 error = %e,
85 "handler error"
86 );
87 }
88 }
89 Err(e) => {
90 if cancel.is_cancelled() {
91 break;
92 }
93 consecutive_errors += 1;
94
95 let err_msg = e.to_string();
96 let is_topic_missing = err_msg.contains("UnknownTopicOrPartition")
97 || err_msg.contains("UNKNOWN_TOPIC_OR_PARTITION")
98 || err_msg.contains("unknown topic");
99
100 if is_topic_missing {
101 if consecutive_errors == 1 {
103 tracing::debug!(
104 consumer = %name,
105 consecutive = consecutive_errors,
106 "topic not yet available, waiting for creation"
107 );
108 } else if consecutive_errors.is_multiple_of(30) {
109 tracing::info!(
110 consumer = %name,
111 consecutive = consecutive_errors,
112 "topic still not available"
113 );
114 }
115 } else if consecutive_errors == 1 || consecutive_errors.is_multiple_of(10) {
116 tracing::warn!(
117 consumer = %name,
118 error = %e,
119 consecutive = consecutive_errors,
120 "recv error (retrying)"
121 );
122 }
123 tokio::time::sleep(
124 recv_backoff.backoff_delay(consecutive_errors as usize),
125 )
126 .await;
127 }
128 }
129 }
130 }
131 }
132 running.store(false, Ordering::SeqCst);
133 tracing::debug!(consumer = %name, "managed consumer loop exited");
134 });
135
136 *self.task.lock() = Some(task);
137
138 Ok(())
139 }
140
141 pub async fn stop(&self) -> AppResult<()> {
143 if !self.running.load(Ordering::SeqCst) {
144 return Err(AppError::new(
145 ErrorCode::InvalidInput,
146 format!("consumer '{}' is not running", self.name),
147 ));
148 }
149
150 let task = {
151 let mut guard = self.task.lock();
152 guard.take()
153 };
154
155 if let Some(task) = task {
156 task.shutdown(std::time::Duration::from_secs(10)).await;
157 }
158
159 if let Err(e) = self.inner.close().await {
161 tracing::warn!(consumer = %self.name, error = %e, "error closing consumer");
162 }
163
164 self.running.store(false, Ordering::SeqCst);
165 tracing::debug!(consumer = %self.name, "managed consumer stopped");
166 Ok(())
167 }
168}
169
170pub struct ManagedConsumerBuilder<T: Send + Sync + Clone + 'static> {
172 inner: Arc<dyn MessageConsumer<T>>,
173 handler: Arc<dyn MessageHandler<T>>,
174 metrics: Arc<dyn MetricsCollector>,
175 recv_backoff: RetryPolicy,
176 name: String,
177}
178
179impl<T: Send + Sync + Clone + 'static> ManagedConsumerBuilder<T> {
180 pub fn new(
182 name: impl Into<String>,
183 inner: Arc<dyn MessageConsumer<T>>,
184 handler: Arc<dyn MessageHandler<T>>,
185 ) -> Self {
186 Self {
187 inner,
188 handler,
189 metrics: Arc::new(NoopMetrics),
190 recv_backoff: RetryPolicy::new()
191 .with_linear_backoff(LinearBackoff::new(
192 std::time::Duration::from_millis(500),
193 std::time::Duration::from_millis(500),
194 std::time::Duration::from_secs(5),
195 ))
196 .with_jitter(false),
197 name: name.into(),
198 }
199 }
200
201 #[must_use]
203 pub fn with_metrics(mut self, metrics: Arc<dyn MetricsCollector>) -> Self {
204 self.metrics = metrics;
205 self
206 }
207
208 #[must_use]
210 pub fn with_recv_backoff(mut self, recv_backoff: RetryPolicy) -> Self {
211 self.recv_backoff = recv_backoff;
212 self
213 }
214
215 pub fn build(self) -> ManagedConsumer<T> {
217 ManagedConsumer {
218 inner: self.inner,
219 handler: self.handler,
220 metrics: self.metrics,
221 recv_backoff: self.recv_backoff,
222 name: self.name,
223 running: Arc::new(AtomicBool::new(false)),
224 task: parking_lot::Mutex::new(None),
225 }
226 }
227}
228
229#[cfg(test)]
230mod tests {
231 use std::sync::atomic::AtomicU32;
232
233 use super::*;
234 use crate::handler::FnHandler;
235 use crate::memory::InMemoryBroker;
236 use crate::message::Message;
237 use crate::traits::MessageProducer;
238
239 #[tokio::test]
240 async fn consumer_receives_and_handles_messages() {
241 let broker = InMemoryBroker::<String>::new(16);
242 let producer = broker.producer();
243 let consumer = broker.consumer();
244
245 crate::traits::MessageConsumer::subscribe(&consumer, &["test"])
247 .await
248 .unwrap();
249
250 let counter = Arc::new(AtomicU32::new(0));
251 let c = counter.clone();
252 let handler: Arc<dyn MessageHandler<String>> =
253 Arc::new(FnHandler::new(move |_msg: Message<String>| {
254 let c = c.clone();
255 async move {
256 c.fetch_add(1, Ordering::SeqCst);
257 Ok(())
258 }
259 }));
260
261 let managed = ManagedConsumerBuilder::new("test", Arc::new(consumer), handler).build();
262
263 managed.start().unwrap();
264
265 producer
267 .send(Message::new("test", "hello".to_string()))
268 .await
269 .unwrap();
270
271 tokio::time::sleep(std::time::Duration::from_millis(100)).await;
273
274 assert_eq!(counter.load(Ordering::SeqCst), 1);
275 assert!(managed.is_running());
276
277 managed.stop().await.unwrap();
278 assert!(!managed.is_running());
279 }
280
281 #[tokio::test]
282 async fn double_start_returns_error() {
283 let broker = InMemoryBroker::<String>::new(16);
284 let consumer = broker.consumer();
285
286 let handler: Arc<dyn MessageHandler<String>> =
287 Arc::new(FnHandler::new(|_msg: Message<String>| async { Ok(()) }));
288
289 let managed = ManagedConsumerBuilder::new("test", Arc::new(consumer), handler).build();
290
291 managed.start().unwrap();
292 assert!(managed.start().is_err());
293
294 managed.stop().await.unwrap();
295 }
296
297 #[tokio::test]
298 async fn stop_when_not_running_returns_error() {
299 let broker = InMemoryBroker::<String>::new(16);
300 let consumer = broker.consumer();
301
302 let handler: Arc<dyn MessageHandler<String>> =
303 Arc::new(FnHandler::new(|_msg: Message<String>| async { Ok(()) }));
304
305 let managed = ManagedConsumerBuilder::new("test", Arc::new(consumer), handler).build();
306
307 assert!(managed.stop().await.is_err());
308 }
309}