use std::collections::VecDeque;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::Mutex;
use exocortex_wire::cluster::v1::InvalidationEnvelope;
#[derive(Debug, Clone)]
pub enum Replay {
Fresh(Vec<InvalidationEnvelope>),
TooOld,
}
pub trait ChangeLog: Send + Sync + 'static {
fn append(&self, envelope: InvalidationEnvelope);
fn replay_since(&self, since_lsn: u64) -> Replay;
fn replay_floor(&self) -> u64;
fn frontier(&self) -> Option<u64>;
}
pub const REPLAY_CAPACITY_DEFAULT: usize = 4096;
fn envelope_lsn(env: &InvalidationEnvelope) -> u64 {
env.inv.as_ref().map(|i| i.backend_lsn).unwrap_or(0)
}
pub struct RingChangeLog {
ring: Mutex<VecDeque<InvalidationEnvelope>>,
cap: usize,
observed_anything: AtomicBool,
max_observed_lsn: AtomicU64,
}
impl RingChangeLog {
pub fn new() -> Self {
Self::with_capacity(REPLAY_CAPACITY_DEFAULT)
}
pub fn with_capacity(cap: usize) -> Self {
Self {
ring: Mutex::new(VecDeque::with_capacity(cap.max(1))),
cap: cap.max(1),
observed_anything: AtomicBool::new(false),
max_observed_lsn: AtomicU64::new(0),
}
}
}
impl Default for RingChangeLog {
fn default() -> Self {
Self::new()
}
}
impl ChangeLog for RingChangeLog {
fn append(&self, envelope: InvalidationEnvelope) {
let lsn = envelope_lsn(&envelope);
self.observed_anything.store(true, Ordering::SeqCst);
self.max_observed_lsn.fetch_max(lsn, Ordering::SeqCst);
let mut ring = self.ring.lock().unwrap();
if ring.len() == self.cap {
ring.pop_front();
}
ring.push_back(envelope);
}
fn replay_since(&self, since_lsn: u64) -> Replay {
let ring = self.ring.lock().unwrap();
let Some(oldest) = ring.front() else {
if self.observed_anything.load(Ordering::SeqCst)
&& since_lsn < self.max_observed_lsn.load(Ordering::SeqCst)
{
return Replay::TooOld;
}
return Replay::Fresh(vec![]);
};
let floor = envelope_lsn(oldest);
if since_lsn.saturating_add(1) < floor {
return Replay::TooOld;
}
Replay::Fresh(
ring.iter()
.filter(|e| envelope_lsn(e) > since_lsn)
.cloned()
.collect(),
)
}
fn replay_floor(&self) -> u64 {
self.ring
.lock()
.unwrap()
.front()
.map(envelope_lsn)
.unwrap_or(1)
}
fn frontier(&self) -> Option<u64> {
self.observed_anything
.load(Ordering::SeqCst)
.then(|| self.max_observed_lsn.load(Ordering::SeqCst))
}
}