use std::collections::HashMap;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
use std::time::{Duration, Instant};
pub(crate) const UNKNOWN_SETTLE: Duration = Duration::from_secs(30);
struct State {
generation: u64,
in_flight: u64,
unknown_since: Option<(u64, Instant)>,
settle: Duration,
}
pub(crate) struct KeyspaceWrites(Mutex<State>);
impl Default for KeyspaceWrites {
fn default() -> Self {
Self::with_settle(UNKNOWN_SETTLE)
}
}
impl KeyspaceWrites {
fn with_settle(settle: Duration) -> Self {
Self(Mutex::new(State {
generation: 0,
in_flight: 0,
unknown_since: None,
settle,
}))
}
fn state(&self) -> MutexGuard<'_, State> {
self.0.lock().unwrap_or_else(PoisonError::into_inner)
}
pub(crate) fn begin(self: &Arc<Self>) -> WriteGuard {
let mut s = self.state();
s.generation += 1;
s.in_flight += 1;
WriteGuard {
writes: Arc::clone(self),
began_at: s.generation,
sent: AtomicBool::new(false),
completed: false,
known_on_drop: false,
}
}
pub(crate) fn ticket(&self) -> Option<u64> {
let mut s = self.state();
if let Some((_, at)) = s.unknown_since
&& at.elapsed() >= s.settle
{
s.unknown_since = None;
}
(s.in_flight == 0 && s.unknown_since.is_none()).then_some(s.generation)
}
}
pub(crate) struct WriteGuard {
writes: Arc<KeyspaceWrites>,
began_at: u64,
sent: AtomicBool,
completed: bool,
known_on_drop: bool,
}
impl WriteGuard {
#[cfg(any(feature = "vsock-store", test))]
pub(crate) fn sent(&self) -> &AtomicBool {
&self.sent
}
#[cfg(any(feature = "vsock-store", test))]
pub(crate) fn completed(mut self) {
self.completed = true;
}
pub(crate) fn known_on_drop(mut self) -> Self {
self.known_on_drop = true;
self
}
fn outcome_unknown(&self) -> bool {
if self.completed {
return false;
}
if self.known_on_drop {
return std::thread::panicking();
}
self.sent.load(Ordering::SeqCst)
}
}
impl Drop for WriteGuard {
fn drop(&mut self) {
let unknown = self.outcome_unknown();
let mut s = self.writes.state();
s.generation += 1;
s.in_flight -= 1;
if unknown {
s.unknown_since = Some((s.generation, Instant::now()));
} else if self.completed
&& s.unknown_since
.is_some_and(|(since, _)| self.began_at > since)
{
s.unknown_since = None;
}
}
}
#[derive(Clone)]
pub(crate) struct Generations {
by_name: Arc<Mutex<HashMap<String, Arc<KeyspaceWrites>>>>,
settle: Duration,
}
impl Default for Generations {
fn default() -> Self {
Self {
by_name: Default::default(),
settle: UNKNOWN_SETTLE,
}
}
}
impl Generations {
#[cfg(all(test, feature = "vsock-store"))]
pub(crate) fn with_settle(settle: Duration) -> Self {
Self {
by_name: Default::default(),
settle,
}
}
pub(crate) fn for_keyspace(&self, name: &str) -> Arc<KeyspaceWrites> {
self.by_name
.lock()
.unwrap_or_else(PoisonError::into_inner)
.entry(name.to_string())
.or_insert_with(|| Arc::new(KeyspaceWrites::with_settle(self.settle)))
.clone()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn writes() -> Arc<KeyspaceWrites> {
Arc::new(KeyspaceWrites::default())
}
fn abandoned_after_send(w: &Arc<KeyspaceWrites>) {
let g = w.begin();
g.sent().store(true, Ordering::SeqCst);
drop(g);
}
#[test]
fn a_write_in_flight_withholds_tickets_and_retires_them() {
let w = writes();
let before = w.ticket().expect("idle");
let guard = w.begin();
assert_eq!(w.ticket(), None, "in flight: no ticket");
guard.completed();
let after = w.ticket().expect("idle again");
assert_ne!(before, after, "the write retired the old ticket");
}
#[test]
fn a_write_that_failed_before_sending_marks_nothing() {
let w = writes();
let before = w.ticket().expect("idle");
drop(w.begin()); let after = w.ticket().expect("a known not-applied outcome");
assert_ne!(before, after, "it still retires cached values");
}
#[test]
fn an_abandoned_write_withholds_tickets_until_a_later_write_completes() {
let w = writes();
abandoned_after_send(&w);
assert_eq!(w.ticket(), None);
let earlier = w.begin();
abandoned_after_send(&w);
earlier.completed();
assert_eq!(w.ticket(), None, "an earlier write proves nothing");
w.begin().completed();
assert!(w.ticket().is_some(), "a later completed write clears it");
}
#[test]
fn an_unknown_mark_settles_on_its_own() {
let w = Arc::new(KeyspaceWrites::with_settle(Duration::from_millis(100)));
abandoned_after_send(&w);
assert_eq!(w.ticket(), None, "unknown: no ticket");
std::thread::sleep(Duration::from_millis(150));
assert!(w.ticket().is_some(), "settled with no later write");
}
#[test]
fn a_local_write_dropped_normally_is_known() {
let w = writes();
drop(w.begin().known_on_drop());
assert!(w.ticket().is_some());
}
}