mod configuration;
pub use configuration::{CONFIGURATION_BYTES, Configuration};
#[cfg(test)]
mod tests;
use crate::driver::ValidationTicket;
use crate::{
JournalGeneration, PipelineLimits, Prefix, PreparedOperation, SyncTicket, WriteTicket,
};
use ozzy_journal::progress::{JournalProgress, JournalSnapshot, ProgressError};
use std::collections::VecDeque;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Snapshot {
pub journal: JournalSnapshot,
pub accepted: Prefix,
pub committed: Prefix,
pub applied: Prefix,
pub pending_operations: usize,
pub pending_body_bytes: usize,
}
#[derive(Debug)]
pub struct Driver {
configuration: Configuration,
journal: JournalProgress,
accepted: Prefix,
committed: Prefix,
applied: Prefix,
operations: VecDeque<PreparedOperation>,
body_bytes: usize,
limits: PipelineLimits,
}
impl Driver {
pub const fn configuration(&self) -> Configuration {
self.configuration
}
pub const fn limits(&self) -> PipelineLimits {
self.limits
}
pub fn recover(
configuration: Configuration,
generation: JournalGeneration,
recovered: Prefix,
limits: PipelineLimits,
) -> Result<Self, Error> {
if generation.0 == 0 || (recovered.op.0 == 0) != (recovered.digest == crate::Digest::ZERO) {
return Err(Error::Lineage);
}
if limits.max_operations == 0
|| limits.max_operations > u32::MAX as usize
|| limits.max_body_bytes == 0
|| limits.max_body_bytes > isize::MAX as usize
{
return Err(Error::Limits);
}
Ok(Self {
configuration,
journal: JournalProgress::recover(generation, recovered.op),
accepted: recovered,
committed: recovered,
applied: recovered,
operations: VecDeque::with_capacity(limits.max_operations),
body_bytes: 0,
limits,
})
}
pub fn snapshot(&self) -> Snapshot {
Snapshot {
journal: self.journal.snapshot(),
accepted: self.accepted,
committed: self.committed,
applied: self.applied,
pending_operations: self.operations.len(),
pending_body_bytes: self.body_bytes,
}
}
pub fn begin_validation(&self) -> Result<ValidationTicket, Error> {
let journal = self.journal.snapshot();
if journal.faulted {
return Err(ProgressError::Faulted.into());
}
Ok(ValidationTicket {
local: self.configuration.broker(),
scope: self.configuration.scope(),
generation: journal.generation,
accepted: self.accepted,
committed: self.committed,
applied: self.applied,
})
}
pub fn prepare_validated(
&mut self,
ticket: ValidationTicket,
operations: &[PreparedOperation],
) -> Result<WriteTicket, Error> {
if !ticket.same_images_as(self.begin_validation()?) {
return Err(Error::StaleValidation);
}
if operations.is_empty() {
return Err(ProgressError::EmptyGroup.into());
}
if operations.len() > self.limits.max_operations - self.operations.len() {
return Err(Error::Capacity);
}
let mut end = self.accepted;
let mut bytes = 0usize;
let scope = self.configuration.scope();
for operation in operations {
if operation.group_id != scope.group_id
|| operation.configuration_epoch != scope.configuration_epoch
|| operation.original_view != 0
|| end.op.0.checked_add(1) != Some(operation.prefix.op.0)
|| operation.previous_digest != end.digest
{
return Err(Error::Lineage);
}
bytes = bytes
.checked_add(operation.body_bytes)
.ok_or(Error::Capacity)?;
end = operation.prefix;
}
if bytes > self.limits.max_body_bytes - self.body_bytes {
return Err(Error::Capacity);
}
let write = self.journal.admit(operations.len() as u64)?;
self.operations.extend(operations);
self.body_bytes += bytes;
self.accepted = end;
Ok(write)
}
pub fn complete_write(&mut self, ticket: WriteTicket) -> Result<(), Error> {
Ok(self.journal.complete_write(ticket)?)
}
pub fn begin_sync(&self) -> Result<SyncTicket, Error> {
Ok(self.journal.begin_sync()?)
}
pub fn complete_sync(&mut self, ticket: SyncTicket) -> Result<(), Error> {
self.journal.complete_sync(ticket)?;
self.confirm_durable()
}
pub fn complete_durable_write(&mut self, ticket: WriteTicket) -> Result<(), Error> {
self.journal.complete_durable_write(ticket)?;
self.confirm_durable()
}
fn confirm_durable(&mut self) -> Result<(), Error> {
let through = self.journal.snapshot().durable;
if through > self.committed.op {
self.committed = self
.operations
.iter()
.find(|operation| operation.prefix.op == through)
.ok_or(Error::Lineage)?
.prefix;
}
Ok(())
}
pub fn apply_through(&mut self, through: Prefix) -> Result<(), Error> {
self.begin_validation()?;
if through == self.applied {
return Ok(());
}
if through.op <= self.applied.op
|| through.op > self.committed.op
|| !self
.operations
.iter()
.any(|operation| operation.prefix == through)
{
return Err(Error::ApplyBeyondDurable);
}
self.body_bytes -= crate::release_through(&mut self.operations, through.op);
self.applied = through;
Ok(())
}
pub fn fail(&mut self, generation: JournalGeneration) -> Result<(), Error> {
Ok(self.journal.fail(generation)?)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
pub enum Error {
#[error("invalid single-broker configuration")]
Configuration,
#[error("invalid single-broker configuration encoding or checksum")]
Encoding,
#[error("invalid local partition pipeline limits")]
Limits,
#[error("local partition capacity exhausted")]
Capacity,
#[error("local partition history mismatch")]
Lineage,
#[error("stale local partition validation")]
StaleValidation,
#[error("local application exceeds its durable prefix")]
ApplyBeyondDurable,
#[error(transparent)]
Journal(#[from] ProgressError),
}