use super::group::{ConsumerGroup, ensure_consumer};
#[path = "autoclaim.rs"]
mod autoclaim;
use super::{ClaimMode, EntryBatch, PelEntry, StreamData, StreamId};
use crate::StoreError;
#[cfg(not(feature = "std"))]
use crate::nostd_prelude::*;
use crate::value::SmallBytes;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub struct AutoclaimResult {
pub next_cursor: StreamId,
pub claimed_ids: Vec<StreamId>,
pub deleted_ids: Vec<StreamId>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[non_exhaustive]
pub struct XClaimOpts {
pub min_idle_ms: u64,
pub idle_override_ms: Option<u64>,
pub time_override_ms: Option<u64>,
pub retrycount_override: Option<u64>,
pub force: bool,
pub mode: ClaimMode,
pub last_id: Option<StreamId>,
}
impl XClaimOpts {
#[must_use]
pub fn with_min_idle_ms(mut self, ms: u64) -> Self {
self.min_idle_ms = ms;
self
}
#[must_use]
pub fn with_idle_ms(mut self, ms: u64) -> Self {
self.idle_override_ms = Some(ms);
self
}
#[must_use]
pub fn with_time_ms(mut self, unix_ms: u64) -> Self {
self.time_override_ms = Some(unix_ms);
self
}
#[must_use]
pub fn with_retrycount(mut self, n: u64) -> Self {
self.retrycount_override = Some(n);
self
}
#[must_use]
pub fn with_last_id(mut self, id: StreamId) -> Self {
self.last_id = Some(id);
self
}
#[must_use]
pub fn with_force(mut self, force: bool) -> Self {
self.force = force;
self
}
#[must_use]
pub fn with_mode(mut self, mode: ClaimMode) -> Self {
self.mode = mode;
self
}
}
impl StreamData {
pub fn claim(
&mut self,
group: &[u8],
new_owner: &[u8],
ids: &[StreamId],
opts: &XClaimOpts,
now_ms: u64,
) -> Result<Vec<StreamId>, StoreError> {
let Some(g) = self.groups.get_mut(group) else {
return Err(StoreError::NoSuchKey);
};
let new_owner_smb = SmallBytes::from_slice(new_owner);
ensure_consumer(g, &new_owner_smb, now_ms);
let mut claimed = Vec::new();
for id in ids {
if !claim_one(g, &self.entries, *id, &new_owner_smb, opts, now_ms) {
continue;
}
claimed.push(*id);
}
if let Some(last) = opts.last_id
&& last > g.last_delivered_id
{
g.last_delivered_id = last;
}
if let Some(cs) = g.consumers.get_mut(new_owner) {
cs.last_seen_ms = now_ms;
if !claimed.is_empty() {
cs.last_active_ms = Some(now_ms);
}
}
Ok(claimed)
}
pub fn payloads_for(&self, ids: &[StreamId]) -> EntryBatch {
ids.iter()
.filter_map(|id| {
self.entries
.get(id)
.map(|fv| (*id, fv.iter().map(|(f, v)| (f.to_vec(), v.to_vec())).collect()))
})
.collect()
}
}
pub(super) fn claim_one(
g: &mut ConsumerGroup,
entries: &alloc::collections::BTreeMap<StreamId, Vec<(SmallBytes, SmallBytes)>>,
id: StreamId,
new_owner: &SmallBytes,
opts: &XClaimOpts,
now_ms: u64,
) -> bool {
let entry_present = g.pel.contains_key(&id);
if !entry_present && !opts.force {
return false;
}
if !entries.contains_key(&id) {
if let Some(p) = g.pel.remove(&id)
&& let Some(cs) = g.consumers.get_mut(p.consumer.as_slice())
{
cs.pel_count = cs.pel_count.saturating_sub(1);
}
return false;
}
if let Some(existing) = g.pel.get(&id) {
let idle = now_ms.saturating_sub(existing.delivery_time_ms);
if idle < opts.min_idle_ms {
return false;
}
}
let new_dt = match (opts.time_override_ms, opts.idle_override_ms) {
(Some(t), _) => Some(t),
(None, Some(i)) => now_ms.checked_sub(i),
(None, None) => None,
}
.filter(|t| *t <= now_ms)
.unwrap_or(now_ms);
let new_dc = opts.retrycount_override.unwrap_or_else(|| {
let base = g.pel.get(&id).map_or(1, |p| p.delivery_count);
match opts.mode {
ClaimMode::JustId => base.max(1),
ClaimMode::Deliver => super::group::delivered_again(base),
}
});
let prev = g.pel.insert(
id,
PelEntry { consumer: new_owner.clone(), delivery_time_ms: new_dt, delivery_count: new_dc },
);
transfer_ownership_counts(g, prev.as_ref(), new_owner);
true
}
pub(super) fn transfer_ownership_counts(
g: &mut ConsumerGroup,
prev: Option<&PelEntry>,
new_owner: &SmallBytes,
) {
match prev {
Some(p) if p.consumer != *new_owner => {
if let Some(cs) = g.consumers.get_mut(p.consumer.as_slice()) {
cs.pel_count = cs.pel_count.saturating_sub(1);
}
if let Some(cs) = g.consumers.get_mut(new_owner.as_slice()) {
cs.pel_count = cs.pel_count.saturating_add(1);
}
}
Some(_) => {}
None => {
if let Some(cs) = g.consumers.get_mut(new_owner.as_slice()) {
cs.pel_count = cs.pel_count.saturating_add(1);
}
}
}
}