mod frame;
mod segments;
mod writer;
#[cfg(test)]
pub(crate) mod fault;
#[cfg(test)]
mod release_tests;
#[cfg(test)]
mod sync_gate;
#[cfg(test)]
mod tests;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use tessarikv_constants::LOG_QUEUE_ENTRIES;
use tessarikv_format::{FileSealer, SealHeader};
use tokio::sync::{mpsc, watch};
use tokio::task::JoinHandle;
use crate::statistics::Statistics;
use crate::storage::{Storage, StorageLock};
use crate::{Error, Result};
pub(crate) use frame::Frame;
pub(crate) use writer::LogRelease;
use writer::{Progress, Released, Writer};
pub(crate) fn segment_path(dir: &Path, log_number: u64) -> PathBuf {
dir.join(format!("{log_number:020}.log"))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct FinishedSegment {
pub(crate) log_number: u64,
pub(crate) last: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Wait {
Written,
Durable,
}
pub(crate) enum Record {
Frame {
frame: Frame,
wait: Wait,
},
Skip,
}
struct Entry {
first: u64,
last: u64,
frame: Option<Frame>,
durable: bool,
}
pub(crate) struct Slot<'a>(mpsc::Permit<'a, Entry>);
impl Slot<'_> {
pub(crate) fn send(self, first: u64, last: u64, record: Record) -> Option<Wait> {
let (frame, wait) = match record {
Record::Frame { frame, wait } => (Some(frame), Some(wait)),
Record::Skip => (None, None),
};
self.0.send(Entry {
first,
last,
frame,
durable: wait == Some(Wait::Durable),
});
wait
}
}
pub(crate) struct LogWriter {
entries: mpsc::Sender<Entry>,
progress: watch::Receiver<Progress>,
released: Arc<Released>,
task: JoinHandle<Result<()>>,
sealer: Option<Arc<FileSealer>>,
}
#[derive(Default)]
pub(crate) struct LogExtras {
pub(crate) seal: Option<(SealHeader, Arc<FileSealer>)>,
pub(crate) statistics: Option<Arc<Statistics>>,
pub(crate) lock: Option<Arc<dyn StorageLock>>,
}
impl LogWriter {
pub(crate) fn start(
storage: Arc<dyn Storage>,
dir: PathBuf,
log_number: u64,
next_sequence: u64,
segment_bytes: u64,
finished: Vec<FinishedSegment>,
extras: LogExtras,
) -> Result<Self> {
let LogExtras {
seal,
statistics,
lock,
} = extras;
let runtime = tokio::runtime::Handle::try_current().map_err(|_| Error::Unsupported {
what: "a database outside a tokio runtime",
})?;
let (entries, receiver) = mpsc::channel(LOG_QUEUE_ENTRIES);
let (publisher, progress) = watch::channel(Progress::new(next_sequence));
let released = Arc::new(Released::default());
let writer = Writer::new(
storage,
dir,
log_number,
next_sequence,
segment_bytes,
finished,
Arc::clone(&released),
)
.with_seal_header(seal.as_ref().map(|(header, _)| header.encode()))
.with_statistics(statistics);
let task = runtime.spawn(async move {
let _held = lock;
writer.run(receiver, publisher).await
});
Ok(Self {
entries,
progress,
released,
task,
sealer: seal.map(|(_, sealer)| sealer),
})
}
pub(crate) fn sealer(&self) -> Option<&FileSealer> {
self.sealer.as_deref()
}
pub(crate) async fn reserve(&self) -> Result<Slot<'_>> {
self.entries
.reserve()
.await
.map(Slot)
.map_err(|_| self.stopped())
}
pub(crate) async fn wait(&self, last: u64, wait: Wait) -> Result<()> {
let mut progress = self.progress.clone();
let reached = progress
.wait_for(|progress| progress.covers(last, wait) || progress.failed)
.await
.map(|progress| progress.covers(last, wait));
match reached {
Ok(true) => Ok(()),
Ok(false) => Err(Error::LogFailed),
Err(_) => Err(self.stopped()),
}
}
#[cfg(test)]
pub(crate) async fn append(&self, first: u64, last: u64, record: Record) -> Result<()> {
match self.reserve().await?.send(first, last, record) {
Some(wait) => self.wait(last, wait).await,
None => Ok(()),
}
}
pub(crate) async fn sync(&self) -> Result<()> {
let written = self.progress.borrow().written();
self.released.sync.notify_one();
self.wait(written, Wait::Durable).await
}
pub(crate) fn releaser(&self) -> LogRelease {
LogRelease(Arc::clone(&self.released))
}
pub(crate) async fn close(self) -> Result<()> {
drop(self.entries);
self.task.await.map_err(|_| Error::LogFailed)?
}
pub(crate) fn failed(&self) -> bool {
self.progress.borrow().failed
}
fn stopped(&self) -> Error {
if self.progress.borrow().failed {
Error::LogFailed
} else {
Error::Closed
}
}
}