use super::{Side, Space};
use std::cell::RefCell;
use std::collections::HashSet;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(super) enum Held {
Value {
relid: u32,
space: Space,
side: Side,
hash: u32,
},
Intent {
relid: u32,
space: Space,
side: Side,
},
Escalated {
relid: u32,
space: Space,
side: Side,
},
}
#[derive(Debug, Default)]
pub(super) struct Registry {
log: Vec<Held>,
index: HashSet<Held>,
}
impl Registry {
pub(super) fn holds(&self, lock: &Held) -> bool {
self.index.contains(lock)
}
pub(super) fn record(&mut self, lock: Held) -> bool {
let new = self.index.insert(lock);
if new {
self.log.push(lock);
}
new
}
pub(super) fn values(&self, relid: u32, space: Space, side: Side) -> usize {
self.log
.iter()
.filter(|h| {
matches!(h, Held::Value { relid: r, space: p, side: s, .. }
if *r == relid && *p == space && *s == side)
})
.count()
}
pub(super) fn escalated(&self, relid: u32, space: Space, side: Side) -> bool {
self.holds(&Held::Escalated { relid, space, side })
}
pub(super) fn should_escalate(
&self,
relid: u32,
space: Space,
side: Side,
new: usize,
threshold: i32,
) -> bool {
match usize::try_from(threshold) {
Err(_) => false,
Ok(0) => true,
Ok(limit) => self.values(relid, space, side) + new > limit,
}
}
pub(super) const fn mark(&self) -> usize {
self.log.len()
}
pub(super) fn rollback(&mut self, mark: usize) {
for lock in self.log.drain(mark.min(self.log.len())..) {
self.index.remove(&lock);
}
}
pub(super) fn clear(&mut self) {
self.log.clear();
self.index.clear();
}
}
thread_local! {
pub(super) static REGISTRY: RefCell<Registry> = RefCell::new(Registry::default());
}
#[cfg(test)]
mod tests {
use super::*;
const USERS: u32 = 16_384;
const V: Space = Space::Values;
fn value(hash: u32) -> Held {
Held::Value {
relid: USERS,
space: V,
side: Side::Writer,
hash,
}
}
#[test]
fn a_value_is_recorded_once() {
let mut r = Registry::default();
assert!(r.record(value(1)));
assert!(!r.record(value(1)));
assert_eq!(r.values(USERS, V, Side::Writer), 1);
}
#[test]
fn values_are_counted_per_relation_and_side() {
let mut r = Registry::default();
r.record(value(1));
r.record(value(2));
r.record(Held::Value {
relid: USERS,
space: V,
side: Side::Refresh,
hash: 3,
});
r.record(Held::Value {
relid: USERS + 1,
space: V,
side: Side::Writer,
hash: 4,
});
r.record(Held::Value {
relid: USERS,
space: Space::Keys,
side: Side::Writer,
hash: 5,
});
assert_eq!(r.values(USERS, V, Side::Writer), 2);
assert_eq!(r.values(USERS, V, Side::Refresh), 1);
assert_eq!(r.values(USERS, Space::Keys, Side::Writer), 1);
}
#[test]
fn escalation_follows_the_threshold() {
let mut r = Registry::default();
for hash in 0..3 {
r.record(value(hash));
}
assert!(!r.should_escalate(USERS, V, Side::Writer, 1, 4));
assert!(r.should_escalate(USERS, V, Side::Writer, 2, 4));
assert!(r.should_escalate(USERS, V, Side::Writer, 1, 0));
assert!(!r.should_escalate(USERS, V, Side::Writer, 1_000_000, -1));
assert!(!r.should_escalate(USERS, V, Side::Refresh, 4, 4));
assert!(!r.should_escalate(USERS, Space::Keys, Side::Writer, 4, 4));
}
#[test]
fn rollback_forgets_what_came_after_the_mark() {
let mut r = Registry::default();
r.record(value(1));
let mark = r.mark();
r.record(value(2));
r.record(Held::Escalated {
relid: USERS,
space: V,
side: Side::Writer,
});
r.rollback(mark);
assert!(r.holds(&value(1)));
assert!(!r.holds(&value(2)));
assert!(!r.escalated(USERS, V, Side::Writer));
assert!(r.record(value(2)));
}
#[test]
fn a_lock_held_before_the_mark_survives_its_rollback() {
let mut r = Registry::default();
r.record(value(1));
let mark = r.mark();
assert!(!r.record(value(1)));
r.rollback(mark);
assert!(r.holds(&value(1)));
}
}