use std::sync::{Arc, Mutex, PoisonError};
use crate::tree::Hash;
#[derive(Clone, Copy, Debug)]
pub struct CommitSnapshot {
pub root: Option<Hash>,
pub dirty_gen: u64,
pub committed_gen: u64,
pub incarnation: u64,
pub recovering: bool,
pub unreconciled: bool,
pub advance_gen: u64,
}
#[derive(Clone, Copy, Debug)]
pub struct Classification {
pub dirty: bool,
pub root: Option<Hash>,
}
impl CommitSnapshot {
const fn initial() -> Self {
Self {
root: None,
dirty_gen: 0,
committed_gen: 0,
incarnation: 0,
recovering: false,
unreconciled: false,
advance_gen: 0,
}
}
}
#[derive(Debug)]
pub struct ShardCommitState(Mutex<CommitSnapshot>);
impl ShardCommitState {
pub fn new() -> Arc<Self> {
Arc::new(Self(Mutex::new(CommitSnapshot::initial())))
}
fn write<R>(&self, edit: impl FnOnce(&mut CommitSnapshot) -> R) -> R {
let mut guard = self.0.lock().unwrap_or_else(PoisonError::into_inner);
let out = edit(&mut guard);
drop(guard);
self.0.clear_poison();
out
}
const fn set_dirty(snapshot: &mut CommitSnapshot, buffer_empty: bool) {
if buffer_empty {
snapshot.committed_gen = snapshot.dirty_gen;
} else if snapshot.committed_gen == snapshot.dirty_gen {
snapshot.dirty_gen = snapshot.committed_gen.wrapping_add(1);
}
}
pub fn begin_recovery(&self) {
self.write(|snapshot| {
snapshot.recovering = true;
snapshot.incarnation = snapshot.incarnation.wrapping_add(1);
});
}
pub fn publish_recovered(&self, root: Option<Hash>, buffer_empty: bool) {
self.write(|snapshot| {
snapshot.root = root;
snapshot.recovering = false;
snapshot.unreconciled = false;
Self::set_dirty(snapshot, buffer_empty);
});
}
pub fn sync_from_buffer(&self, buffer_empty: bool) {
self.write(|snapshot| Self::set_dirty(snapshot, buffer_empty));
}
pub fn publish_committed(&self, root: Option<Hash>, advanced: bool) -> Option<u64> {
self.write(|snapshot| {
snapshot.root = root;
snapshot.committed_gen = snapshot.dirty_gen;
snapshot.recovering = false;
snapshot.unreconciled = false;
if advanced {
snapshot.advance_gen = snapshot.advance_gen.wrapping_add(1);
Some(snapshot.advance_gen)
} else {
None
}
})
}
pub fn classify(&self) -> Classification {
match self.0.lock() {
Ok(snapshot) => Self::classify_snapshot(&snapshot, false),
Err(poison) => Self::classify_snapshot(&poison.into_inner(), true),
}
}
const fn classify_snapshot(snapshot: &CommitSnapshot, poisoned: bool) -> Classification {
Classification {
dirty: poisoned
|| snapshot.recovering
|| snapshot.unreconciled
|| snapshot.committed_gen != snapshot.dirty_gen
|| snapshot.root.is_none(),
root: snapshot.root,
}
}
pub fn is_unreconciled(&self) -> bool {
match self.0.lock() {
Ok(snapshot) => snapshot.unreconciled,
Err(_poison) => true,
}
}
pub fn mark_unreconciled(&self) {
self.write(|snapshot| snapshot.unreconciled = true);
}
#[cfg(test)]
pub fn snapshot(&self) -> CommitSnapshot {
match self.0.lock() {
Ok(snapshot) => *snapshot,
Err(poison) => *poison.into_inner(),
}
}
#[cfg(test)]
pub fn is_poisoned(&self) -> bool {
self.0.is_poisoned()
}
#[cfg(test)]
pub fn force_poison_for_test(&self) {
let poisoned = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let guard = self.0.lock().unwrap_or_else(PoisonError::into_inner);
let empty: [u8; 0] = [];
let index = usize::try_from(guard.incarnation).unwrap_or(0);
let _ = empty[index.wrapping_add(1)];
drop(guard);
}));
debug_assert!(poisoned.is_err(), "force_poison_for_test must unwind");
}
}
#[cfg(test)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum SeamBarrier {
PostWalCommit,
Recovery,
}
#[cfg(test)]
struct BarrierRendezvous {
reached: std::sync::mpsc::SyncSender<()>,
release: std::sync::mpsc::Receiver<()>,
}
#[cfg(test)]
static SEAM_BARRIERS: std::sync::LazyLock<
std::sync::Mutex<std::collections::HashMap<(usize, SeamBarrier), BarrierRendezvous>>,
> = std::sync::LazyLock::new(|| std::sync::Mutex::new(std::collections::HashMap::new()));
#[cfg(test)]
pub struct BarrierHandle {
reached: std::sync::mpsc::Receiver<()>,
release: std::sync::mpsc::SyncSender<()>,
}
#[cfg(test)]
impl BarrierHandle {
pub fn wait_until_reached(&self) {
let _ = self.reached.recv();
}
pub fn release(&self) {
let _ = self.release.send(());
}
}
#[cfg(test)]
pub fn arm_seam_barrier(cell: &Arc<ShardCommitState>, which: SeamBarrier) -> BarrierHandle {
let (reached_tx, reached_rx) = std::sync::mpsc::sync_channel(1);
let (release_tx, release_rx) = std::sync::mpsc::sync_channel(1);
let key = (std::sync::Arc::as_ptr(cell).addr(), which);
if let Ok(mut barriers) = SEAM_BARRIERS.lock() {
barriers.insert(
key,
BarrierRendezvous {
reached: reached_tx,
release: release_rx,
},
);
}
BarrierHandle {
reached: reached_rx,
release: release_tx,
}
}
#[cfg(test)]
pub fn pause_at_seam_barrier(cell: &Arc<ShardCommitState>, which: SeamBarrier) {
let key = (std::sync::Arc::as_ptr(cell).addr(), which);
let rendezvous = SEAM_BARRIERS
.lock()
.ok()
.and_then(|mut barriers| barriers.remove(&key));
if let Some(rendezvous) = rendezvous {
let _ = rendezvous.reached.send(());
let _ = rendezvous.release.recv();
}
}
#[cfg(test)]
#[path = "commit_state_tests.rs"]
mod tests;