kevy_store/stream/
claim.rs1use super::group::{ConsumerGroup, ensure_consumer};
6use super::{EntryBatch, PelEntry, StreamData, StreamId, XClaimOpts};
7use crate::StoreError;
8#[cfg(not(feature = "std"))]
9use crate::nostd_prelude::*;
10use crate::value::SmallBytes;
11
12#[derive(Debug)]
16pub struct AutoclaimResult {
17 pub next_cursor: StreamId,
20 pub claimed_ids: Vec<StreamId>,
22 pub deleted_ids: Vec<StreamId>,
27}
28
29impl StreamData {
30 pub fn claim(
34 &mut self,
35 group: &[u8],
36 new_owner: &[u8],
37 ids: &[StreamId],
38 opts: &XClaimOpts,
39 now_ms: u64,
40 ) -> Result<Vec<StreamId>, StoreError> {
41 let Some(g) = self.groups.get_mut(group) else {
42 return Err(StoreError::NoSuchKey);
43 };
44 let new_owner_smb = SmallBytes::from_slice(new_owner);
45 ensure_consumer(g, &new_owner_smb, now_ms);
46 let mut claimed = Vec::new();
47 for id in ids {
48 if !claim_one(g, &self.entries, *id, &new_owner_smb, opts, now_ms) {
49 continue;
50 }
51 claimed.push(*id);
52 }
53 Ok(claimed)
54 }
55
56 #[allow(clippy::too_many_arguments)]
61 pub fn autoclaim(
62 &mut self,
63 group: &[u8],
64 new_owner: &[u8],
65 min_idle_ms: u64,
66 start: StreamId,
67 count: usize,
68 justid: bool,
69 now_ms: u64,
70 ) -> Result<AutoclaimResult, StoreError> {
71 let opts = XClaimOpts {
72 min_idle_ms,
73 idle_override_ms: None,
74 time_override_ms: None,
75 retrycount_override: None,
76 force: false,
77 justid,
78 };
79 let candidates: Vec<StreamId> = {
80 let Some(g) = self.groups.get(group) else {
81 return Err(StoreError::NoSuchKey);
82 };
83 g.pel
84 .range(start..=StreamId::MAX)
85 .filter(|(_, p)| now_ms.saturating_sub(p.delivery_time_ms) >= min_idle_ms)
86 .take(count)
87 .map(|(id, _)| *id)
88 .collect()
89 };
90 let next_cursor = candidates.last().map_or(StreamId::MIN, |id| id.next());
91 let claimed = self.claim(group, new_owner, &candidates, &opts, now_ms)?;
92 let mut deleted = Vec::new();
93 for id in &candidates {
94 if !self.entries.contains_key(id) && !claimed.contains(id) {
95 deleted.push(*id);
96 }
97 }
98 Ok(AutoclaimResult { next_cursor, claimed_ids: claimed, deleted_ids: deleted })
99 }
100
101 pub fn payloads_for(&self, ids: &[StreamId]) -> EntryBatch {
105 ids.iter()
106 .filter_map(|id| {
107 self.entries
108 .get(id)
109 .map(|fv| (*id, fv.iter().map(|(f, v)| (f.to_vec(), v.to_vec())).collect()))
110 })
111 .collect()
112 }
113}
114
115fn claim_one(
120 g: &mut ConsumerGroup,
121 entries: &alloc::collections::BTreeMap<StreamId, Vec<(SmallBytes, SmallBytes)>>,
122 id: StreamId,
123 new_owner: &SmallBytes,
124 opts: &XClaimOpts,
125 now_ms: u64,
126) -> bool {
127 let entry_present = g.pel.contains_key(&id);
128 if !entry_present && !opts.force {
129 return false;
130 }
131 if !entries.contains_key(&id) {
132 if let Some(p) = g.pel.remove(&id)
133 && let Some(cs) = g.consumers.get_mut(p.consumer.as_slice())
134 {
135 cs.pel_count = cs.pel_count.saturating_sub(1);
136 }
137 return false;
138 }
139 if let Some(existing) = g.pel.get(&id) {
140 let idle = now_ms.saturating_sub(existing.delivery_time_ms);
141 if idle < opts.min_idle_ms {
142 return false;
143 }
144 }
145 let new_dt = opts
146 .time_override_ms
147 .or_else(|| opts.idle_override_ms.map(|i| now_ms.saturating_sub(i)))
148 .unwrap_or(now_ms);
149 let new_dc = opts.retrycount_override.unwrap_or_else(|| {
150 let base = g.pel.get(&id).map_or(0, |p| p.delivery_count);
151 if opts.justid { base.max(1) } else { base.saturating_add(1) }
152 });
153 let prev = g.pel.insert(
154 id,
155 PelEntry { consumer: new_owner.clone(), delivery_time_ms: new_dt, delivery_count: new_dc },
156 );
157 transfer_ownership_counts(g, prev.as_ref(), new_owner);
158 true
159}
160
161fn transfer_ownership_counts(
162 g: &mut ConsumerGroup,
163 prev: Option<&PelEntry>,
164 new_owner: &SmallBytes,
165) {
166 match prev {
167 Some(p) if p.consumer != *new_owner => {
168 if let Some(cs) = g.consumers.get_mut(p.consumer.as_slice()) {
169 cs.pel_count = cs.pel_count.saturating_sub(1);
170 }
171 if let Some(cs) = g.consumers.get_mut(new_owner.as_slice()) {
172 cs.pel_count = cs.pel_count.saturating_add(1);
173 }
174 }
175 Some(_) => {}
176 None => {
177 if let Some(cs) = g.consumers.get_mut(new_owner.as_slice()) {
178 cs.pel_count = cs.pel_count.saturating_add(1);
179 }
180 }
181 }
182}