use std::collections::HashMap;
use std::time::Instant;
use crate::constants;
use crate::core::Timestamp;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum PinKind {
Claimed,
KeyHolder,
}
#[derive(Debug, Clone)]
pub(crate) struct GuardEntry {
pub(crate) greatest: Option<Timestamp>,
pub(crate) pins: u32,
pub(crate) keyholder_pins: u32,
pub(crate) exempt_until: Option<Instant>,
pub(crate) last_admitted: Instant,
pub(crate) orphaned_at: Option<Instant>,
}
impl GuardEntry {
fn pinned(&self, now: Instant) -> bool {
self.pins > 0 || self.exempt_until.is_some_and(|until| until > now)
}
fn age_deadline(&self) -> Option<Instant> {
if self.pins > 0 {
return None;
}
let base = self.orphaned_at? + constants::TS_GUARD_ORPHAN_TTL;
Some(match self.exempt_until {
Some(until) if until > base => until,
_ => base,
})
}
}
#[derive(Debug, Clone)]
pub(crate) struct ChainPin {
pub(crate) key: Vec<u8>,
pub(crate) kind: PinKind,
}
#[derive(Debug, Clone)]
pub(crate) struct GuardUndo {
key: Vec<u8>,
previous: Option<(Option<Timestamp>, Instant)>,
}
#[derive(Debug, Default)]
pub(crate) struct TimestampGuard {
entries: HashMap<Vec<u8>, GuardEntry>,
}
impl TimestampGuard {
pub(crate) fn greatest(&self, key: &[u8]) -> Option<Timestamp> {
self.entries.get(key).and_then(|e| e.greatest)
}
pub(crate) fn pins(&self, key: &[u8]) -> u32 {
self.entries.get(key).map_or(0, |e| e.pins)
}
pub(crate) fn admits(&self, key: &[u8], candidate: Timestamp) -> bool {
match self.entries.get(key) {
Some(entry) => entry.greatest.is_none_or(|g| candidate > g),
None => true,
}
}
pub(crate) fn record(&mut self, key: &[u8], candidate: Timestamp, now: Instant) -> GuardUndo {
let previous = self.entries.get(key).map(|e| (e.greatest, e.last_admitted));
match self.entries.get_mut(key) {
Some(entry) => {
entry.greatest = Some(candidate);
entry.last_admitted = now;
}
None => {
self.entries.insert(
key.to_vec(),
GuardEntry {
greatest: Some(candidate),
pins: 0,
keyholder_pins: 0,
exempt_until: None,
last_admitted: now,
orphaned_at: Some(now),
},
);
}
}
self.evict_if_over_cap(now);
GuardUndo {
key: key.to_vec(),
previous,
}
}
pub(crate) fn revert(&mut self, undo: GuardUndo) {
match undo.previous {
Some((greatest, last_admitted)) => {
if let Some(entry) = self.entries.get_mut(&undo.key) {
entry.greatest = greatest;
entry.last_admitted = last_admitted;
}
}
None => {
let still_pinned = self
.entries
.get(&undo.key)
.is_some_and(|entry| entry.pins > 0);
if still_pinned {
if let Some(entry) = self.entries.get_mut(&undo.key) {
entry.greatest = None;
}
} else {
self.entries.remove(&undo.key);
}
}
}
}
pub(crate) fn pin(&mut self, key: &[u8], kind: PinKind) -> bool {
match self.entries.get_mut(key) {
Some(entry) => {
entry.pins = entry.pins.saturating_add(1);
if kind == PinKind::KeyHolder {
entry.keyholder_pins = entry.keyholder_pins.saturating_add(1);
entry.orphaned_at = None;
}
true
}
None => false,
}
}
pub(crate) fn promote_pin(&mut self, key: &[u8]) {
let Some(entry) = self.entries.get_mut(key) else {
return;
};
if entry.keyholder_pins >= entry.pins {
debug_assert!(false, "promoting a pin that was never taken");
return;
}
entry.keyholder_pins += 1;
entry.orphaned_at = None;
}
pub(crate) fn unpin(&mut self, key: &[u8], kind: PinKind, now: Instant) {
let Some(entry) = self.entries.get_mut(key) else {
return;
};
entry.pins = entry.pins.saturating_sub(1);
if kind == PinKind::KeyHolder {
entry.keyholder_pins = entry.keyholder_pins.saturating_sub(1);
}
if entry.pins == 0 && entry.greatest.is_none() && entry.exempt_until.is_none() {
self.entries.remove(key);
return;
}
if kind == PinKind::KeyHolder && entry.keyholder_pins == 0 {
entry.orphaned_at = Some(now);
}
}
pub(crate) fn extend_exemption(&mut self, key: &[u8], until: Instant) {
let Some(entry) = self.entries.get_mut(key) else {
return;
};
if entry.exempt_until.is_none_or(|current| until > current) {
entry.exempt_until = Some(until);
}
}
pub(crate) fn age_orphans(&mut self, now: Instant) {
self.entries
.retain(|_, entry| entry.age_deadline().is_none_or(|deadline| deadline > now));
}
pub(crate) fn next_orphan_deadline(&self) -> Option<Instant> {
self.entries
.values()
.filter_map(GuardEntry::age_deadline)
.min()
}
fn evict_if_over_cap(&mut self, now: Instant) {
while self
.entries
.values()
.filter(|entry| !entry.pinned(now))
.count()
> constants::TS_GUARD_ORPHAN_CAP
{
let victim = self
.entries
.iter()
.filter(|(_, entry)| !entry.pinned(now))
.min_by(|a, b| {
a.1.last_admitted
.cmp(&b.1.last_admitted)
.then_with(|| a.0.cmp(b.0))
})
.map(|(key, _)| key.clone());
match victim {
Some(key) => {
self.entries.remove(&key);
}
None => break,
}
}
}
}