use crate::error::{ErrCode, FrozenError, FrozenResult};
use std::sync::{self, atomic};
pub type TEpoch = u64;
pub trait SyncTrigger: Send + Sync {
fn trigger_sync(&self) -> FrozenResult<()>;
}
#[derive(Debug)]
pub struct Completion {
current_epoch: atomic::AtomicU64,
durable_epoch: atomic::AtomicU64,
error: sync::Mutex<Option<FrozenError>>,
wait_mutex: sync::Mutex<()>,
durability_condvar: sync::Condvar,
sync_trigger: sync::RwLock<Option<sync::Weak<dyn SyncTrigger>>>,
}
impl Default for Completion {
fn default() -> Self {
Self {
current_epoch: atomic::AtomicU64::new(0),
durable_epoch: atomic::AtomicU64::new(0),
error: sync::Mutex::new(None),
wait_mutex: sync::Mutex::new(()),
durability_condvar: sync::Condvar::new(),
sync_trigger: sync::RwLock::new(None),
}
}
}
impl Completion {
#[inline]
pub fn increment_current_epoch(&self) -> TEpoch {
self.current_epoch.fetch_add(1, atomic::Ordering::AcqRel).wrapping_add(1)
}
#[inline]
pub fn mark_epoch_as_durable(&self, epoch: TEpoch) {
self.durable_epoch.fetch_max(epoch, atomic::Ordering::Release);
}
#[inline]
pub fn get_err(&self) -> Option<FrozenError> {
let lock = self.error.lock().unwrap_or_else(|e| e.into_inner());
lock.clone()
}
#[inline]
pub fn set_err(&self, new_error: FrozenError) {
let mut lock = self.error.lock().unwrap_or_else(|e| e.into_inner());
*lock = Some(new_error);
}
#[inline]
pub fn del_err(&self) {
let mut lock = self.error.lock().unwrap_or_else(|e| e.into_inner());
*lock = None;
}
#[inline]
pub fn read_current_epoch(&self) -> TEpoch {
self.current_epoch.load(atomic::Ordering::Acquire)
}
#[inline]
pub fn read_durable_epoch(&self) -> TEpoch {
self.durable_epoch.load(atomic::Ordering::Acquire)
}
#[inline]
pub fn notify_all_listeners(&self) {
self.durability_condvar.notify_all();
}
pub fn set_sync_trigger(&self, trigger: sync::Weak<dyn SyncTrigger>) {
let mut lock = self.sync_trigger.write().unwrap_or_else(|e| e.into_inner());
*lock = Some(trigger);
}
pub fn trigger_sync(&self) -> FrozenResult<()> {
let trigger = {
let lock = self.sync_trigger.read().unwrap_or_else(|e| e.into_inner());
lock.as_ref().and_then(|w| w.upgrade())
};
if let Some(trigger) = trigger {
trigger.trigger_sync()
} else {
Err(FrozenError::new(
0,
0x08,
ErrCode::new(0x0A, "failed to sync"),
"no sync trigger registered or file handle closed",
))
}
}
pub fn wait_for_epoch(&self, epoch: TEpoch) -> FrozenResult<TEpoch> {
if self.read_durable_epoch() >= epoch {
return Ok(epoch);
}
if let Some(err) = self.get_err() {
return Err(err);
}
let mut guard = self.wait_mutex.lock().unwrap_or_else(|e| e.into_inner());
loop {
if self.read_durable_epoch() >= epoch {
return Ok(epoch);
}
if let Some(err) = self.get_err() {
return Err(err);
}
guard = self.durability_condvar.wait(guard).unwrap_or_else(|e| e.into_inner());
}
}
}
#[derive(Debug, Clone)]
pub struct AckTicket {
epoch: TEpoch,
completion: sync::Arc<Completion>,
}
impl AckTicket {
#[inline]
pub const fn new(epoch: TEpoch, completion: sync::Arc<Completion>) -> Self {
Self { epoch, completion }
}
#[inline(always)]
pub const fn epoch(&self) -> TEpoch {
self.epoch
}
#[inline]
pub fn is_durable(&self) -> bool {
self.completion.read_durable_epoch() >= self.epoch
}
#[inline]
pub fn wait(&self) -> FrozenResult<TEpoch> {
self.completion.wait_for_epoch(self.epoch)
}
#[inline]
pub fn force(&self) -> FrozenResult<TEpoch> {
if self.is_durable() {
return Ok(self.epoch);
}
if let Some(err) = self.completion.get_err() {
return Err(err);
}
self.completion.trigger_sync()?;
self.completion.wait_for_epoch(self.epoch)
}
#[inline]
pub fn completion(&self) -> &sync::Arc<Completion> {
&self.completion
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::{sync::Arc, thread, time::Duration};
#[test]
fn ok_ticket_epoch_and_is_durable() {
let completion = Arc::new(Completion::default());
let ticket = AckTicket::new(0x2A, completion.clone());
assert_eq!(ticket.epoch(), 0x2A);
assert!(!ticket.is_durable());
completion.mark_epoch_as_durable(0x29);
assert!(!ticket.is_durable());
completion.mark_epoch_as_durable(0x2A);
assert!(ticket.is_durable());
completion.mark_epoch_as_durable(0x64);
assert!(ticket.is_durable());
}
#[test]
fn ok_ticket_wait_blocks_until_durable() {
let completion = Arc::new(Completion::default());
let epoch = completion.increment_current_epoch();
let ticket = AckTicket::new(epoch, completion.clone());
let t = thread::spawn({
let completion = completion.clone();
move || {
thread::sleep(Duration::from_millis(0x14));
completion.mark_epoch_as_durable(epoch);
completion.notify_all_listeners();
}
});
assert_eq!(ticket.wait().unwrap(), epoch);
t.join().unwrap();
}
#[test]
fn err_ticket_wait_returns_error() {
let completion = Arc::new(Completion::default());
let epoch = completion.increment_current_epoch();
let ticket = AckTicket::new(epoch, completion.clone());
let err = FrozenError::new(1, 2, ErrCode::new(3, "test"), "io failure");
let t = thread::spawn({
let completion = completion.clone();
let err = err.clone();
move || {
thread::sleep(Duration::from_millis(0x14));
completion.set_err(err);
completion.notify_all_listeners();
}
});
let res = ticket.wait();
assert!(res.is_err());
assert_eq!(res.unwrap_err().reason, 3);
t.join().unwrap();
}
struct MockTrigger {
completion: Arc<Completion>,
synced: atomic::AtomicBool,
}
impl SyncTrigger for MockTrigger {
fn trigger_sync(&self) -> FrozenResult<()> {
self.synced.store(true, atomic::Ordering::Release);
let curr = self.completion.read_current_epoch();
self.completion.mark_epoch_as_durable(curr);
self.completion.notify_all_listeners();
Ok(())
}
}
#[test]
fn ok_ticket_force_with_trigger() {
let completion = Arc::new(Completion::default());
let trigger = Arc::new(MockTrigger {
completion: completion.clone(),
synced: atomic::AtomicBool::new(false),
});
let weak = Arc::downgrade(&trigger) as sync::Weak<dyn SyncTrigger>;
completion.set_sync_trigger(weak);
let epoch = completion.increment_current_epoch();
let ticket = AckTicket::new(epoch, completion.clone());
assert!(!ticket.is_durable());
assert_eq!(ticket.force().unwrap(), epoch);
assert!(trigger.synced.load(atomic::Ordering::Acquire));
assert!(ticket.is_durable());
}
#[test]
fn err_ticket_force_without_trigger() {
let completion = Arc::new(Completion::default());
let epoch = completion.increment_current_epoch();
let ticket = AckTicket::new(epoch, completion.clone());
let res = ticket.force();
assert!(res.is_err());
}
}