use std::sync::Arc;
use crate::runtime::background::facility::recover;
const FACILITY: &str = "database index";
pub(crate) struct SnapshotCell<T> {
cell: arc_swap::ArcSwap<T>,
writer: std::sync::Mutex<()>,
}
impl<T> SnapshotCell<T> {
pub(crate) fn new(value: T) -> Self {
Self {
cell: arc_swap::ArcSwap::from_pointee(value),
writer: std::sync::Mutex::new(()),
}
}
pub(crate) fn load(&self) -> arc_swap::Guard<Arc<T>> {
self.cell.load()
}
pub(crate) fn load_full(&self) -> Arc<T> {
self.cell.load_full()
}
}
impl<T: Clone> SnapshotCell<T> {
pub(crate) fn update(&self, f: impl FnOnce(&mut T)) -> Arc<T> {
let _writing = recover(FACILITY, self.writer.lock());
let mut next = (*self.cell.load_full()).clone();
f(&mut next);
let published = Arc::new(next);
self.cell.store(published.clone());
published
}
}
#[cfg(test)]
mod tests {
use super::SnapshotCell;
use std::collections::HashMap;
#[test]
fn update_publishes_and_readers_see_it() {
let cell = SnapshotCell::new(HashMap::<String, u64>::new());
assert!(cell.load().is_empty());
cell.update(|m| {
m.insert("A".into(), 1);
});
assert_eq!(cell.load().get("A").copied(), Some(1));
}
#[test]
fn a_snapshot_taken_before_an_update_does_not_observe_it() {
let cell = SnapshotCell::new(HashMap::<String, u64>::new());
cell.update(|m| {
m.insert("A".into(), 1);
});
let before = cell.load_full();
cell.update(|m| {
m.insert("B".into(), 2);
});
assert_eq!(before.len(), 1, "the held snapshot must stay coherent");
assert_eq!(cell.load().len(), 2);
}
#[test]
fn concurrent_writers_do_not_lose_edits() {
let cell = std::sync::Arc::new(SnapshotCell::new(HashMap::<usize, usize>::new()));
let threads: Vec<_> = (0..8)
.map(|t| {
let cell = cell.clone();
std::thread::spawn(move || {
for i in 0..64 {
cell.update(|m| {
m.insert(t * 64 + i, t);
});
}
})
})
.collect();
for t in threads {
t.join().unwrap();
}
assert_eq!(cell.load().len(), 8 * 64);
}
}