use std::time::Duration;
use ozzy_proto::NodeId;
use super::{Action, DriverError, ReplicaDriver, Role};
use crate::wire::Control;
use crate::{
Commit, Digest, InstallOutcome, InstallTicket, InstallingView, JournalGeneration, LogSource,
Prefix, PreparedOperation, ReceivedStart, ReplicationError, Scope, StartView, ViewChangeError,
VoterSet,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ActivationTicket {
local: NodeId,
scope: Scope,
generation: JournalGeneration,
through: Prefix,
applied: Prefix,
}
impl ActivationTicket {
pub const fn local(self) -> NodeId {
self.local
}
pub const fn scope(self) -> Scope {
self.scope
}
pub const fn generation(self) -> JournalGeneration {
self.generation
}
pub const fn through(self) -> Prefix {
self.through
}
pub const fn applied(self) -> Prefix {
self.applied
}
}
impl ReplicaDriver {
pub(super) fn activation_action(&mut self) -> Result<Option<Action>, DriverError> {
if self.activation_pending.is_empty() {
return Ok(None);
}
let Some(start) = self.require_normal()?.start_view()? else {
self.activation_pending = VoterSet::EMPTY;
return Ok(None);
};
let Some(voter) = self.activation_pending.pop_first() else {
return Ok(None);
};
Ok(Some(Action::Send {
to: self.configuration.voter(voter),
message: Control::Commit(Commit {
scope: start.scope,
committed: start.accepted,
}),
}))
}
pub fn begin_activation(&self) -> Result<ActivationTicket, DriverError> {
let normal = self.normal().ok_or(ReplicationError::NotNormal)?;
let through = normal.activation_prefix()?;
let snapshot = normal.snapshot();
Ok(ActivationTicket {
local: self.configuration.voter(self.local),
scope: snapshot.scope,
generation: snapshot.journal.generation,
through,
applied: snapshot.applied,
})
}
pub fn complete_activation(&mut self, ticket: ActivationTicket) -> Result<(), DriverError> {
if self.begin_activation().ok() != Some(ticket) {
return Err(DriverError::StaleValidation);
}
self.apply_through(ticket.through)
}
pub fn installation_ticket(&self) -> Option<InstallTicket> {
match &self.role {
Role::Installing(installing) => Some(installing.ticket()),
Role::Normal(_) | Role::ViewChanging(_) | Role::Fenced(_) => None,
}
}
pub fn begin_primary_install(
&mut self,
generation: JournalGeneration,
) -> Result<InstallTicket, DriverError> {
self.begin_install(None, generation, |_, _| None)
}
pub fn begin_backup_install(
&mut self,
from: NodeId,
start: StartView,
generation: JournalGeneration,
lookup: impl FnMut(LogSource, Prefix) -> Option<Digest>,
) -> Result<InstallTicket, DriverError> {
self.begin_install(Some(ReceivedStart { from, start }), generation, lookup)
}
fn begin_install(
&mut self,
backup: Option<ReceivedStart>,
generation: JournalGeneration,
lookup: impl FnMut(LogSource, Prefix) -> Option<Digest>,
) -> Result<InstallTicket, DriverError> {
self.changing()?;
let Role::ViewChanging(changing) = self.take_role() else {
return Err(ViewChangeError::Faulted.into());
};
let admitted = match backup {
Some(received) => changing.begin_backup_install(received, generation, lookup),
None => changing.begin_primary_install(generation),
};
match admitted {
Ok(installing) => {
let ticket = installing.ticket();
self.role = Role::Installing(installing);
self.election_deadline = self.now.saturating_add(self.election_delay);
Ok(ticket)
}
Err(rejected) => {
let error = rejected.error();
self.role = Role::ViewChanging(rejected.into_view_change());
Err(error.into())
}
}
}
pub fn validate_install_suffix(
&mut self,
operations: &[PreparedOperation],
) -> Result<(), DriverError> {
Ok(self.installing_mut()?.validate_suffix(operations)?)
}
pub fn history_progress(&mut self, now: Duration) -> Result<(), DriverError> {
self.observe_time(now)?;
if matches!(self.role, Role::ViewChanging(_) | Role::Installing(_)) {
self.election_deadline = self
.election_deadline
.max(now.saturating_add(self.election_delay));
}
Ok(())
}
pub fn complete_installation(
&mut self,
ticket: InstallTicket,
applied: Prefix,
now: Duration,
) -> Result<(), DriverError> {
self.observe_time(now)?;
let outcome = self.installing_mut()?.complete(ticket, applied)?;
let changing = matches!(outcome, InstallOutcome::ViewChanging(_));
self.role = match outcome {
InstallOutcome::Normal(normal) => Role::Normal(normal),
InstallOutcome::ViewChanging(changing) => Role::ViewChanging(changing),
};
self.contact_deadline = now.saturating_add(self.timing.primary_timeout);
self.persistence_deadline = now.saturating_add(self.timing.primary_timeout);
self.progress_deadline = now.saturating_add(self.progress_timeout());
self.election_deadline = now.saturating_add(self.election_delay);
self.retry_at = now;
self.start_pending = changing;
self.report_pending = changing;
self.commit_pending = false;
self.activation_pending = VoterSet::EMPTY;
Ok(())
}
pub fn complete_abandon(
&mut self,
ticket: InstallTicket,
now: Duration,
) -> Result<(), DriverError> {
self.observe_time(now)?;
let changing = self.installing_mut()?.complete_abandon(ticket)?;
self.role = Role::ViewChanging(changing);
self.election_deadline = now.saturating_add(self.election_delay);
self.retry_at = now;
self.start_pending = true;
self.report_pending = true;
self.commit_pending = false;
self.activation_pending = VoterSet::EMPTY;
Ok(())
}
fn installing_mut(&mut self) -> Result<&mut InstallingView, DriverError> {
match &mut self.role {
Role::Installing(installing) => Ok(installing),
Role::Normal(_) | Role::ViewChanging(_) => Err(ReplicationError::WrongRole.into()),
Role::Fenced(_) => Err(ViewChangeError::Faulted.into()),
}
}
}