use std::{convert::Infallible, sync::atomic::Ordering};
use wbase::group_commit::{Enter, GroupCommitStep};
use wdev::{Device, FlushError};
use super::{
commit,
config::FsyncPolicy,
log::{WalLog, WalLogInner},
};
use crate::error::{Error, Result};
impl<D: Device> WalLogInner<D> {
pub(crate) async fn flush_and_sync_range(&self, flushed: u64, safe_tail: u64) -> Result<u64> {
if safe_tail <= flushed {
return Ok(self.committed_until_address.load(Ordering::Acquire));
}
self
.device
.flush_range_aligned(flushed, safe_tail, |start_aligned, buf| {
self.ring_buffer.read_bytes(start_aligned, buf);
Ok::<_, Infallible>(())
})
.await
.map_err(|e| match e {
FlushError::Fill(e) => match e {},
FlushError::ShortWrite { expected, written } => Error::ShortWrite { expected, written },
FlushError::Device(e) => e.into(),
})?;
if self.config.fsync == FsyncPolicy::Always
&& let Err(e) = self.device.sync_data().await
{
return Err(e.into());
}
self
.flushed_until_address
.store(safe_tail, Ordering::Release);
self
.committed_until_address
.store(safe_tail, Ordering::Release);
Ok(safe_tail)
}
}
impl<D: Device> WalLog<D> {
pub async fn commit(&self) -> Result<u64> {
let target = self.safe_tail_address();
let committed = self.committed_until_address.load(Ordering::Acquire);
if target <= committed {
return Ok(committed);
}
self.commit_to(target).await
}
pub async fn sync(&self) -> Result<()> {
Ok(self.device.sync_data().await?)
}
pub async fn commit_to(&self, target: u64) -> Result<u64> {
let committed = self.committed_until_address.load(Ordering::Acquire);
if target <= committed {
return Ok(committed);
}
match self.commit_pipeline.enter(target, || {
self.committed_until_address.load(Ordering::Acquire)
}) {
Enter::Done(committed) => return Ok(committed),
Enter::Follow(rx) => {
return self
.commit_pipeline
.wait(rx, target, || {
self.committed_until_address.load(Ordering::Acquire)
})
.await
.map_err(Error::from);
}
Enter::Lead => {}
}
let _commit_guard = self.commit_lock.lock().await;
self
.commit_pipeline
.run_leader(WalCommitStep { wal: self })
.await
}
pub async fn wait_for_commit(&self, target_addr: u64) -> Result<u64> {
let target = if target_addr == 0 {
self.tail_address.load(Ordering::Acquire)
} else {
target_addr
};
self.commit_to(target).await
}
}
struct WalCommitStep<'a, D: Device> {
wal: &'a WalLog<D>,
}
impl<D: Device> GroupCommitStep for WalCommitStep<'_, D> {
type Error = Error;
#[inline]
fn tail(&self) -> u64 {
self.wal.safe_tail_address()
}
#[inline]
fn watermark(&self) -> u64 {
self.wal.committed_until_address.load(Ordering::Acquire)
}
async fn step(&self, target: u64) -> Result<u64> {
let mut goal = target;
let last_frame = self.wal.last_commit_frame.load(Ordering::Acquire);
if goal > last_frame {
match self
.wal
.enqueue(&commit::encode_payload(commit::CommitMeta {
begin: self.wal.begin_address.load(Ordering::Acquire),
cookie: self.wal.pending_cookie.load(Ordering::Acquire),
})) {
Ok(frame_addr) => {
let frame_end = frame_addr + commit::COMMIT_FRAME_TOTAL_LEN;
self
.wal
.last_commit_frame
.store(frame_end, Ordering::Release);
goal = goal.max(frame_end);
}
Err(Error::BufferFull { .. }) => {}
Err(e) => return Err(e),
}
}
let flushed = self.wal.flushed_until_address.load(Ordering::Acquire);
if goal > flushed {
self.wal.flush_and_sync_range(flushed, goal).await
} else {
Ok(self.wal.committed_until_address.load(Ordering::Acquire))
}
}
}