mod error;
mod recovery;
mod selection;
pub use error::ViewChangeError;
use ozzy_proto::NodeId;
use crate::{
Configuration, Digest, JournalGeneration, NormalReplica, OpNumber, Prefix, ReplicaSnapshot,
ReplicationError, Scope, Status, SyncTicket, VoterIndex, VoterSet, Voters, WriteTicket,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FrozenLog {
pub last_normal_view: u64,
pub accepted: Prefix,
pub committed: Prefix,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RecoveredState {
pub scope: Scope,
pub log: FrozenLog,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PromiseTicket {
scope: Scope,
generation: JournalGeneration,
log: FrozenLog,
}
impl PromiseTicket {
pub const fn scope(self) -> Scope {
self.scope
}
pub const fn generation(self) -> JournalGeneration {
self.generation
}
pub const fn log(self) -> FrozenLog {
self.log
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct StartViewChange {
pub scope: Scope,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DoViewChange {
pub scope: Scope,
pub generation: JournalGeneration,
pub log: FrozenLog,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LogSource {
pub voter: NodeId,
pub generation: JournalGeneration,
pub accepted: Prefix,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SelectedView {
scope: Scope,
source: LogSource,
committed: Prefix,
retained: Prefix,
voters: VoterSet,
}
impl SelectedView {
pub const fn scope(self) -> Scope {
self.scope
}
pub const fn source(self) -> LogSource {
self.source
}
pub const fn committed(self) -> Prefix {
self.committed
}
pub(crate) const fn retained(self) -> Prefix {
self.retained
}
pub const fn voter_mask(self) -> u8 {
self.voters.mask()
}
}
#[derive(Debug)]
pub struct ViewChange {
pub(crate) normal: NormalReplica,
pub(crate) configuration: Configuration,
pub(crate) local: VoterIndex,
view: u64,
promise: Promise,
pending: Option<PromiseTicket>,
starts: VoterSet,
reports: Voters<Option<DoViewChange>>,
pub(crate) selected: Option<SelectedView>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Promise {
Prior(u64),
Completed(PromiseTicket),
}
impl Promise {
const fn view(self) -> u64 {
match self {
Self::Prior(view) => view,
Self::Completed(ticket) => ticket.scope.view,
}
}
}
impl ViewChange {
pub fn history_sources(&self) -> [Option<LogSource>; 3] {
VoterIndex::ALL.map(|voter| {
self.reports[voter].map(|report| LogSource {
voter: self.configuration.voter(voter),
generation: report.generation,
accepted: report.log.accepted,
})
})
}
pub fn lookup_ops(&self) -> [Option<OpNumber>; 8] {
let local = self.normal.snapshot();
let mut ops = [None; 8];
ops[6] = Some(local.committed.op);
ops[7] = Some(local.accepted.op);
let (slots, _) = ops.as_chunks_mut::<2>();
for ([committed, accepted], report) in slots.iter_mut().zip(self.reports.values()) {
if let Some(report) = report {
*committed = Some(report.log.committed.op);
*accepted = Some(report.log.accepted.op);
}
}
ops
}
pub(crate) fn begin(
mut normal: NormalReplica,
configuration: Configuration,
local: VoterIndex,
view: u64,
) -> Result<Self, ViewChangeError> {
normal.fence();
if normal.snapshot().status == Status::Faulted {
return Err(ViewChangeError::Faulted);
}
if view <= configuration.scope().view {
return Err(ViewChangeError::ViewNotHigher);
}
Ok(Self {
normal,
configuration,
local,
view,
promise: Promise::Prior(configuration.scope().view),
pending: None,
starts: VoterSet::EMPTY,
reports: Voters::splat(None),
selected: None,
})
}
pub fn normal_snapshot(&self) -> ReplicaSnapshot {
self.normal.snapshot()
}
pub fn scope(&self) -> Scope {
Scope {
view: self.view,
..self.configuration.scope()
}
}
pub const fn promised_view(&self) -> u64 {
self.promise.view()
}
pub fn advance_view(&mut self, view: u64) -> Result<(), ViewChangeError> {
self.require_active()?;
if view <= self.view {
return Err(ViewChangeError::ViewNotHigher);
}
self.view = view;
self.starts = VoterSet::EMPTY;
self.reports = Voters::splat(None);
self.selected = None;
Ok(())
}
pub fn complete_write(&mut self, ticket: WriteTicket) -> Result<(), ViewChangeError> {
Ok(self.normal.complete_write(ticket)?)
}
pub fn begin_sync(&self) -> Result<SyncTicket, ViewChangeError> {
Ok(self.normal.begin_sync()?)
}
pub fn complete_sync(&mut self, ticket: SyncTicket) -> Result<(), ViewChangeError> {
Ok(self.normal.complete_sync(ticket)?)
}
pub fn fail_io(&mut self, generation: JournalGeneration) -> Result<(), ViewChangeError> {
Ok(self.normal.fail_io(generation)?)
}
pub fn begin_promise(&mut self) -> Result<PromiseTicket, ViewChangeError> {
self.require_active()?;
if self.pending.is_some() {
return Err(ViewChangeError::PromisePending);
}
if self.promise.view() == self.view {
return Err(ViewChangeError::AlreadyPromised);
}
let snapshot = self.normal.snapshot();
if snapshot.journal.durable != snapshot.accepted.op {
return Err(ViewChangeError::StoragePending);
}
let ticket = PromiseTicket {
scope: self.scope(),
generation: snapshot.journal.generation,
log: FrozenLog {
last_normal_view: snapshot.scope.view,
accepted: snapshot.accepted,
committed: snapshot.committed,
},
};
self.pending = Some(ticket);
Ok(ticket)
}
pub fn complete_promise(&mut self, ticket: PromiseTicket) -> Result<(), ViewChangeError> {
self.require_active()?;
if self.promise == Promise::Completed(ticket) {
return Ok(());
}
self.require_ticket(ticket)?;
self.pending = None;
self.promise = Promise::Completed(ticket);
if ticket.scope.view == self.view {
self.starts.insert(self.local);
}
Ok(())
}
pub fn fail_promise(&mut self, ticket: PromiseTicket) -> Result<(), ViewChangeError> {
self.require_active()?;
self.require_ticket(ticket)?;
self.fail_io(ticket.generation)
}
pub fn start_message(&self) -> Result<StartViewChange, ViewChangeError> {
self.require_promised()?;
Ok(StartViewChange {
scope: self.scope(),
})
}
pub fn receive_start(
&mut self,
from: NodeId,
message: StartViewChange,
) -> Result<(), ViewChangeError> {
self.require_scope(message.scope)?;
let voter = self.configuration.voter_index(from)?;
if voter == self.local {
return Err(ReplicationError::WrongRole.into());
}
self.starts.insert(voter);
Ok(())
}
pub fn report(&mut self) -> Result<DoViewChange, ViewChangeError> {
let promise = self.require_promised()?;
if self.starts.len() < 2 {
return Err(ViewChangeError::StartQuorumMissing);
}
let report = DoViewChange {
scope: self.scope(),
generation: promise.generation,
log: promise.log,
};
self.reports[self.local] = Some(report);
Ok(report)
}
pub fn receive_report(
&mut self,
from: NodeId,
report: DoViewChange,
) -> Result<(), ViewChangeError> {
self.require_scope(report.scope)?;
self.require_primary()?;
let voter = self.configuration.voter_index(from)?;
if voter == self.local {
return Err(ReplicationError::WrongRole.into());
}
validate_report(report)?;
if let Some(previous) = self.reports[voter] {
if previous.log != report.log {
return Err(self.conflict());
}
return Ok(());
}
if self.selected.is_some() {
return Err(ViewChangeError::SelectionFrozen);
}
self.reports[voter] = Some(report);
Ok(())
}
fn require_active(&self) -> Result<(), ViewChangeError> {
if self.normal.snapshot().status == Status::Faulted {
return Err(ViewChangeError::Faulted);
}
Ok(())
}
pub(crate) fn require_scope(&self, scope: Scope) -> Result<(), ViewChangeError> {
self.require_active()?;
if scope != self.scope() {
return Err(ReplicationError::ScopeMismatch.into());
}
Ok(())
}
pub(crate) fn require_primary(&self) -> Result<(), ViewChangeError> {
if VoterIndex::primary(self.view) != self.local {
return Err(ReplicationError::WrongRole.into());
}
Ok(())
}
pub(crate) fn require_promised(&self) -> Result<PromiseTicket, ViewChangeError> {
self.require_active()?;
match self.promise {
Promise::Completed(ticket) if ticket.scope.view == self.view => Ok(ticket),
Promise::Prior(_) | Promise::Completed(_) => Err(ViewChangeError::PromiseRequired),
}
}
fn require_ticket(&self, ticket: PromiseTicket) -> Result<(), ViewChangeError> {
if self.pending != Some(ticket) {
return Err(ViewChangeError::StalePromise);
}
Ok(())
}
pub(crate) fn conflict(&mut self) -> ViewChangeError {
self.normal.fault();
ViewChangeError::ConflictingHistory
}
}
pub(crate) fn valid_prefix(prefix: Prefix) -> bool {
prefix.op.0 != u64::MAX && ((prefix.op.0 == 0) == (prefix.digest == Digest::ZERO))
}
fn validate_report(report: DoViewChange) -> Result<(), ViewChangeError> {
let log = report.log;
if log.last_normal_view >= report.scope.view
|| !valid_prefix(log.accepted)
|| !valid_prefix(log.committed)
|| log.committed.op > log.accepted.op
|| (log.committed.op == log.accepted.op && log.committed != log.accepted)
{
return Err(ViewChangeError::InvalidReport);
}
Ok(())
}