use crate::Error;
use commonware_utils::sync::Mutex;
use std::sync::atomic::{AtomicU64, Ordering};
#[derive(Default)]
pub(crate) struct Tracker {
written: AtomicU64,
completed: AtomicU64,
synced: AtomicU64,
failed: Mutex<Option<Error>>,
#[cfg(test)]
skipped: AtomicU64,
}
impl Tracker {
pub(crate) fn write(&self) {
self.written.fetch_add(1, Ordering::AcqRel);
}
pub(crate) fn complete(&self) {
self.completed.fetch_add(1, Ordering::AcqRel);
}
pub(crate) fn begin_sync(&self) -> u64 {
self.completed.load(Ordering::Acquire)
}
pub(crate) fn end_sync(&self, seen: u64) -> Result<(), Error> {
let failed = self.failed.lock();
if let Some(error) = failed.as_ref() {
return Err(error.clone());
}
self.synced.fetch_max(seen, Ordering::AcqRel);
Ok(())
}
pub(crate) fn is_dirty(&self) -> bool {
self.written.load(Ordering::Acquire) != self.synced.load(Ordering::Acquire)
}
pub(crate) fn poison(&self, error: &Error) {
let mut failed = self.failed.lock();
if failed.is_none() {
*failed = Some(error.clone());
}
}
pub(crate) fn failure(&self) -> Option<Error> {
self.failed.lock().clone()
}
}
#[cfg(test)]
impl Tracker {
pub(crate) fn skip_sync(&self) {
self.skipped.fetch_add(1, Ordering::AcqRel);
}
pub(crate) fn skipped(&self) -> u64 {
self.skipped.load(Ordering::Acquire)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_synced_writes_are_clean() {
let tracker = Tracker::default();
assert!(!tracker.is_dirty());
tracker.write();
assert!(tracker.is_dirty());
tracker.complete();
let seen = tracker.begin_sync();
tracker.end_sync(seen).unwrap();
assert!(!tracker.is_dirty());
}
#[test]
fn test_unlanded_write_is_not_credited() {
let tracker = Tracker::default();
tracker.write();
let seen = tracker.begin_sync();
tracker.complete();
tracker.end_sync(seen).unwrap();
assert!(tracker.is_dirty());
let later = tracker.begin_sync();
tracker.end_sync(later).unwrap();
assert!(!tracker.is_dirty());
}
#[test]
fn test_write_racing_sync_stays_dirty() {
let tracker = Tracker::default();
tracker.write();
tracker.complete();
let seen = tracker.begin_sync();
tracker.write();
tracker.complete();
tracker.end_sync(seen).unwrap();
assert!(tracker.is_dirty());
let later = tracker.begin_sync();
tracker.end_sync(later).unwrap();
tracker.end_sync(seen).unwrap();
assert!(!tracker.is_dirty());
}
#[test]
fn test_first_failure_is_retained() {
let tracker = Tracker::default();
assert!(tracker.failure().is_none());
tracker.poison(&Error::Closed);
tracker.poison(&Error::ReadFailed);
assert!(matches!(tracker.failure(), Some(Error::Closed)));
}
}