use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
use std::sync::Arc;
#[derive(Debug, Default)]
pub struct StaleState {
rag_pending: AtomicU32,
change_seq: AtomicU64,
reindexed_seq: AtomicU64,
}
impl StaleState {
#[must_use]
pub fn new() -> Arc<Self> {
Arc::new(Self::default())
}
pub fn note_change(&self) {
self.change_seq.fetch_add(1, Ordering::Relaxed);
}
pub fn note_reindex(&self, caught_up_to: u64, rag_pending: u32) {
self.reindexed_seq.store(caught_up_to, Ordering::Relaxed);
self.rag_pending.store(rag_pending, Ordering::Relaxed);
}
#[must_use]
pub fn change_seq(&self) -> u64 {
self.change_seq.load(Ordering::Relaxed)
}
pub fn clear_rag_pending(&self) {
self.rag_pending.store(0, Ordering::Relaxed);
}
#[must_use]
pub fn snapshot(&self) -> StaleSnapshot {
StaleSnapshot {
rag_pending: self.rag_pending.load(Ordering::Relaxed),
change_seq: self.change_seq.load(Ordering::Relaxed),
reindexed_seq: self.reindexed_seq.load(Ordering::Relaxed),
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct StaleSnapshot {
pub rag_pending: u32,
pub change_seq: u64,
pub reindexed_seq: u64,
}
impl StaleSnapshot {
#[must_use]
pub fn rag_stale(&self) -> bool {
self.rag_pending > 0
}
#[must_use]
pub fn structural_stale(&self) -> bool {
self.change_seq > self.reindexed_seq
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn change_then_no_reindex_is_structurally_stale() {
let s = StaleState::new();
s.note_change();
assert!(s.snapshot().structural_stale());
}
#[test]
fn reindex_caught_up_to_change_clears_structural_staleness() {
let s = StaleState::new();
s.note_change();
let seq = s.change_seq();
s.note_reindex(seq, 0);
assert!(!s.snapshot().structural_stale());
}
#[test]
fn change_arriving_mid_reindex_stays_stale() {
let s = StaleState::new();
s.note_change(); let caught = s.change_seq();
s.note_change(); s.note_reindex(caught, 0);
assert!(
s.snapshot().structural_stale(),
"the mid-reindex change is not yet covered"
);
}
#[test]
fn pending_embeddings_are_rag_stale_until_cleared() {
let s = StaleState::new();
s.note_reindex(0, 5);
assert!(s.snapshot().rag_stale());
s.clear_rag_pending();
assert!(!s.snapshot().rag_stale());
}
#[test]
fn fresh_state_is_not_stale() {
let snap = StaleState::new().snapshot();
assert!(!snap.rag_stale());
assert!(!snap.structural_stale());
}
}