1use std::collections::{HashMap, VecDeque};
4use std::fmt::{Debug, Formatter};
5use std::sync::Arc;
6use std::sync::atomic::{AtomicU64, Ordering};
7use std::time::Duration;
8
9use fred::clients::Pool;
10use fred::interfaces::StreamsInterface;
11use fred::types::streams::XReadValue;
12use futures::Stream;
13use futures::stream::unfold;
14use ruststream::{BatchSubscriber, Seekable, Subscriber};
15
16use crate::convert::{HEADER_PREFIX, parts_from_fields};
17use crate::deadletter::{
18 self, DELIVERY_COUNT_HEADER, IDLE_MS_HEADER, PoisonPolicy, REASON_MAX_DELIVERIES,
19};
20use crate::delay::{self, DelayConfig};
21use crate::seek::{EntryId, RedisGroupSeeker};
22use crate::{error::RedisError, message::RedisMessage, stream::ReadMode};
23
24type Entry = (String, HashMap<String, Vec<u8>>);
26
27type RawStreams = Vec<(String, Vec<(String, Vec<(String, Vec<u8>)>)>)>;
32
33const RECLAIM_START: &str = "0-0";
35
36fn duration_to_millis(d: Duration) -> u64 {
37 u64::try_from(d.as_millis()).unwrap_or(u64::MAX)
38}
39
40pub struct RedisSubscriber {
45 pool: Pool,
46 key: String,
47 group: String,
48 consumer: String,
49 count: u64,
50 block: Duration,
51 mode: ReadMode,
52 policy: PoisonPolicy,
53 delay: Option<DelayConfig>,
56 cursor: String,
58 buffer: VecDeque<Entry>,
60 generation: Arc<AtomicU64>,
63 buffer_generation: u64,
66}
67
68impl Debug for RedisSubscriber {
69 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
70 f.debug_struct("RedisSubscriber")
71 .field("key", &self.key)
72 .field("group", &self.group)
73 .field("consumer", &self.consumer)
74 .field("mode", &self.mode)
75 .finish_non_exhaustive()
76 }
77}
78
79impl RedisSubscriber {
80 #[allow(
81 clippy::too_many_arguments,
82 reason = "internal constructor mirroring the descriptor"
83 )]
84 pub(crate) fn new(
85 pool: Pool,
86 key: String,
87 group: String,
88 consumer: String,
89 count: u64,
90 block: Duration,
91 mode: ReadMode,
92 policy: PoisonPolicy,
93 delay: Option<DelayConfig>,
94 ) -> Self {
95 Self {
96 pool,
97 key,
98 group,
99 consumer,
100 count,
101 block,
102 mode,
103 policy,
104 delay,
105 cursor: RECLAIM_START.to_owned(),
106 buffer: VecDeque::new(),
107 generation: Arc::new(AtomicU64::new(0)),
108 buffer_generation: 0,
109 }
110 }
111
112 fn message(
120 &self,
121 id: String,
122 fields: HashMap<String, Vec<u8>>,
123 ) -> Result<RedisMessage, RedisError> {
124 let entry: EntryId = id.parse()?;
125 let (payload, headers) = parts_from_fields(fields);
126 Ok(RedisMessage::new(
127 self.pool.clone(),
128 self.key.clone(),
129 self.group.clone(),
130 id,
131 entry,
132 payload,
133 headers,
134 self.policy.clone(),
135 self.delay.clone(),
136 ))
137 }
138
139 fn discard_stale(&mut self) {
142 let current = self.generation.load(Ordering::Acquire);
143 if self.buffer_generation != current {
144 self.buffer.clear();
145 self.buffer_generation = current;
146 }
147 }
148
149 async fn fetch(&mut self) -> Result<(), RedisError> {
152 if let Some(cfg) = &self.delay {
155 delay::sweep_due(&self.pool, cfg, &self.key).await?;
156 }
157 let selected_at = self.generation.load(Ordering::Acquire);
161 let entries = match self.mode.clone() {
162 ReadMode::Fresh => self.fetch_fresh().await?,
163 ReadMode::Reclaim { min_idle } => self.fetch_reclaim(min_idle).await?,
164 };
165 if selected_at != self.generation.load(Ordering::Acquire) {
166 return Ok(());
168 }
169 self.buffer_generation = selected_at;
170 self.buffer.extend(entries);
171 Ok(())
172 }
173
174 async fn fetch_fresh(&self) -> Result<Vec<Entry>, RedisError> {
175 let resp: RawStreams = self
176 .pool
177 .xreadgroup(
178 self.group.as_str(),
179 self.consumer.as_str(),
180 Some(self.count),
181 Some(duration_to_millis(self.block)),
182 false,
183 self.key.as_str(),
184 ">",
185 )
186 .await
187 .map_err(RedisError::stream)?;
188 let entries = resp
189 .into_iter()
190 .find(|(key, _)| key == &self.key)
191 .map(|(_, entries)| entries)
192 .unwrap_or_default();
193 Ok(entries
194 .into_iter()
195 .map(|(id, fields)| (id, fields.into_iter().collect()))
196 .collect())
197 }
198
199 async fn fetch_reclaim(&mut self, min_idle: Duration) -> Result<Vec<Entry>, RedisError> {
200 let (cursor, entries): (String, Vec<XReadValue<String, String, Vec<u8>>>) = self
201 .pool
202 .xautoclaim_values(
203 self.key.as_str(),
204 self.group.as_str(),
205 self.consumer.as_str(),
206 duration_to_millis(min_idle),
207 self.cursor.as_str(),
208 Some(self.count),
209 false,
210 )
211 .await
212 .map_err(RedisError::stream)?;
213 self.cursor = cursor;
214 if entries.is_empty() {
216 tokio::time::sleep(self.block).await;
217 return Ok(entries);
218 }
219 if !self.policy.is_active() {
221 return Ok(entries);
222 }
223 self.enrich_reclaimed(entries).await
224 }
225
226 async fn enrich_reclaimed(&self, entries: Vec<Entry>) -> Result<Vec<Entry>, RedisError> {
229 let meta = self.pending_meta().await?;
230 let mut out = Vec::with_capacity(entries.len());
231 for (id, mut fields) in entries {
232 let (idle, count) = meta.get(&id).copied().unwrap_or((0, 0));
233 if self.policy.is_poison(count) {
234 self.dead_letter_reclaimed(&id, &fields).await?;
235 continue;
236 }
237 insert_meta_header(&mut fields, DELIVERY_COUNT_HEADER, count);
238 insert_meta_header(&mut fields, IDLE_MS_HEADER, idle);
239 out.push((id, fields));
240 }
241 Ok(out)
242 }
243
244 async fn pending_meta(&self) -> Result<HashMap<String, (u64, u64)>, RedisError> {
247 let rows: Vec<(String, String, u64, u64)> = self
248 .pool
249 .xpending(
250 self.key.as_str(),
251 self.group.as_str(),
252 (0_u64, "-", "+", self.count, self.consumer.as_str()),
253 )
254 .await
255 .map_err(RedisError::stream)?;
256 Ok(rows
257 .into_iter()
258 .map(|(id, _consumer, idle, count)| (id, (idle, count)))
259 .collect())
260 }
261
262 async fn dead_letter_reclaimed(
265 &self,
266 id: &str,
267 fields: &HashMap<String, Vec<u8>>,
268 ) -> Result<(), RedisError> {
269 let (payload, headers) = parts_from_fields(fields.clone());
270 deadletter::settle_poison_stream(
271 &self.pool,
272 &self.policy,
273 &payload,
274 &headers,
275 REASON_MAX_DELIVERIES,
276 )
277 .await
278 .map_err(RedisError::stream)?;
279 let _: i64 = self
280 .pool
281 .xack(self.key.as_str(), self.group.as_str(), id)
282 .await
283 .map_err(RedisError::stream)?;
284 Ok(())
285 }
286}
287
288fn insert_meta_header(fields: &mut HashMap<String, Vec<u8>>, name: &str, value: u64) {
291 fields.insert(
292 format!("{HEADER_PREFIX}{name}"),
293 value.to_string().into_bytes(),
294 );
295}
296
297impl Subscriber for RedisSubscriber {
298 type Message = RedisMessage;
299 type Error = RedisError;
300
301 fn stream(&mut self) -> impl Stream<Item = Result<Self::Message, Self::Error>> + Send + '_ {
309 unfold(self, |s| async move {
310 loop {
311 s.discard_stale();
312 if let Some((id, fields)) = s.buffer.pop_front() {
313 return Some((s.message(id, fields), s));
314 }
315 if let Err(err) = s.fetch().await {
317 return Some((Err(err), s));
318 }
319 }
320 })
321 }
322}
323
324impl Seekable for RedisSubscriber {
327 type Seeker = RedisGroupSeeker;
328
329 fn seeker(&self) -> RedisGroupSeeker {
330 RedisGroupSeeker::new(
331 self.pool.clone(),
332 self.key.clone(),
333 self.group.clone(),
334 Arc::clone(&self.generation),
335 )
336 }
337}
338
339impl BatchSubscriber for RedisSubscriber {
340 type Batch = Vec<RedisMessage>;
341
342 fn batches(&mut self) -> impl Stream<Item = Result<Self::Batch, Self::Error>> + Send + '_ {
350 unfold(self, |s| async move {
351 loop {
352 s.discard_stale();
353 if !s.buffer.is_empty() {
354 let entries = std::mem::take(&mut s.buffer);
357 let batch = entries
358 .into_iter()
359 .map(|(id, fields)| s.message(id, fields))
360 .collect::<Result<Vec<_>, _>>();
361 return Some((batch, s));
362 }
363 if let Err(err) = s.fetch().await {
364 return Some((Err(err), s));
365 }
366 }
367 })
368 }
369}