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