use std::collections::BTreeMap;
use std::path::PathBuf;
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use tessarikv_constants::LOG_QUEUE_ENTRIES;
use tessarikv_format::SEAL_HEADER_BYTES;
use tokio::sync::{Notify, mpsc, watch};
use super::segments::Segments;
use super::{Entry, FinishedSegment, Wait};
use crate::statistics::Statistics;
use crate::storage::Storage;
use crate::{Error, Result};
#[derive(Debug, Clone, Copy)]
pub(super) struct Progress {
written: u64,
durable: u64,
pub(super) failed: bool,
}
impl Progress {
pub(super) fn new(next_sequence: u64) -> Self {
let before = next_sequence.saturating_sub(1);
Self {
written: before,
durable: before,
failed: false,
}
}
pub(super) fn written(&self) -> u64 {
self.written
}
pub(super) fn covers(&self, last: u64, wait: Wait) -> bool {
match wait {
Wait::Written => self.written >= last,
Wait::Durable => self.durable >= last,
}
}
}
#[derive(Default)]
pub(super) struct Released {
pub(super) through: AtomicU64,
pub(super) notice: Notify,
pub(super) sync: Notify,
}
pub(crate) struct LogRelease(pub(super) Arc<Released>);
impl LogRelease {
pub(crate) fn release_through(&self, sequence: u64) {
self.0
.through
.fetch_max(sequence, std::sync::atomic::Ordering::Release);
self.0.notice.notify_one();
}
}
pub(super) struct Writer {
next: u64,
pending: BTreeMap<u64, Entry>,
segments: Option<Segments>,
released: Arc<Released>,
}
impl Writer {
pub(super) fn new(
storage: Arc<dyn Storage>,
dir: PathBuf,
log_number: u64,
next_sequence: u64,
segment_bytes: u64,
finished: Vec<FinishedSegment>,
released: Arc<Released>,
) -> Self {
Self {
next: next_sequence,
pending: BTreeMap::new(),
segments: Some(Segments::new(
storage,
dir,
log_number,
segment_bytes,
finished,
)),
released,
}
}
pub(super) fn with_seal_header(mut self, header: Option<[u8; SEAL_HEADER_BYTES]>) -> Self {
if let Some(segments) = self.segments.as_mut() {
segments.header = header;
}
self
}
pub(super) fn with_statistics(mut self, statistics: Option<Arc<Statistics>>) -> Self {
if let Some(segments) = self.segments.as_mut() {
segments.statistics = statistics;
}
self
}
pub(super) async fn run(
mut self,
mut entries: mpsc::Receiver<Entry>,
progress: watch::Sender<Progress>,
) -> Result<()> {
let result = self.serve(&mut entries, &progress).await;
if result.is_err() {
progress.send_modify(|progress| progress.failed = true);
}
result
}
async fn serve(
&mut self,
entries: &mut mpsc::Receiver<Entry>,
progress: &watch::Sender<Progress>,
) -> Result<()> {
let released = Arc::clone(&self.released);
loop {
tokio::select! {
biased;
() = released.sync.notified() => self.write_ready(progress, true).await?,
entry = entries.recv() => {
let Some(entry) = entry else { break };
self.pending.insert(entry.first, entry);
for _ in 1..LOG_QUEUE_ENTRIES {
let Ok(entry) = entries.try_recv() else { break };
self.pending.insert(entry.first, entry);
}
self.write_ready(progress, false).await?;
}
() = released.notice.notified() => self.remove_released().await?,
}
}
self.write_ready(progress, true).await
}
fn released_through(&self) -> u64 {
self.released.through.load(Ordering::Acquire)
}
async fn remove_released(&mut self) -> Result<()> {
let through = self.released_through();
let segments = self.segments.as_ref().ok_or(Error::LogFailed)?;
if !segments.has_removable(through) {
return Ok(());
}
let mut segments = self.segments.take().ok_or(Error::LogFailed)?;
let segments = tokio::task::spawn_blocking(move || {
segments.remove_through(through);
segments
})
.await
.map_err(|_| Error::LogFailed)?;
self.segments = Some(segments);
Ok(())
}
async fn write_ready(
&mut self,
progress: &watch::Sender<Progress>,
force_sync: bool,
) -> Result<()> {
let mut frames = Vec::new();
let mut sync = force_sync;
let mut last = None;
while let Some(entry) = self.pending.remove(&self.next) {
self.next = entry.last.checked_add(1).ok_or(Error::SequenceExhausted)?;
sync |= entry.durable;
last = Some(entry.last);
frames.extend(entry.frame.map(|frame| (frame, entry.last)));
}
if last.is_none() && !force_sync {
return Ok(());
}
let through = self.released_through();
let mut segments = self.segments.take().ok_or(Error::LogFailed)?;
let (segments, written) = tokio::task::spawn_blocking(move || {
let written = segments.write(frames, sync);
if written.is_ok() {
segments.remove_through(through);
}
(segments, written)
})
.await
.map_err(|_| Error::LogFailed)?;
self.segments = Some(segments);
written?;
progress.send_modify(|progress| {
if let Some(last) = last {
progress.written = last;
}
if sync {
progress.durable = progress.written;
}
});
Ok(())
}
}