use super::{Action, DriverError, ReplicaDriver, Role};
use crate::wire::Control;
use crate::{Scope, Status, ViewChangeError};
impl ReplicaDriver {
pub(super) fn receive_exit(&mut self, voter: usize, scope: Scope) -> Result<(), DriverError> {
match self.role.as_ref().expect("owned role") {
Role::Normal(normal) if normal.snapshot().status == Status::Faulted => {
return Err(ViewChangeError::Faulted.into());
}
Role::ViewChanging(changing)
if changing.normal_snapshot().status == Status::Faulted =>
{
return Err(ViewChangeError::Faulted.into());
}
Role::Installing(installing) => installing.require_pending()?,
_ => {}
}
if scope.view < self.scope().view {
if self.promise_pending() && self.now >= self.exit_echo_at[voter] {
self.exit_echo_at[voter] = self.now + self.timing.retransmit;
let asked = match self.exit_echo {
Some((old, asked)) if old == scope => asked,
_ => 0,
};
self.exit_echo = Some((scope, asked | 1 << voter));
}
return Ok(());
}
if scope != self.scope() {
return Ok(());
}
if let Some(normal) = self.normal() {
let snapshot = normal.snapshot();
if !self.normal_timed_out(&snapshot) {
self.withdraw_exit();
}
}
self.exit_votes |= 1 << voter;
self.advance_if_exit_quorum()
}
pub(super) fn request_exit(&mut self) -> Result<(), DriverError> {
self.exit_retry_at.get_or_insert(self.now);
self.exit_votes |= 1 << self.configuration.voter_index(self.local)?;
self.advance_if_exit_quorum()
}
fn advance_if_exit_quorum(&mut self) -> Result<(), DriverError> {
if self.exit_votes.count_ones() >= 2 {
if self
.normal()
.is_none_or(|normal| !normal.snapshot().ready_for_appends)
{
self.election_delay = self
.election_delay
.saturating_mul(2)
.min(self.timing.max_election_timeout);
}
self.next_view()?;
}
Ok(())
}
pub(super) fn withdraw_exit(&mut self) {
let local = self
.configuration
.voter_index(self.local)
.expect("configured local voter");
self.exit_votes &= !(1 << local);
self.exit_retry_at = None;
}
fn promise_pending(&self) -> bool {
matches!(
self.role.as_ref().expect("owned role"),
Role::ViewChanging(changing) if changing.promised_view() != changing.scope().view
)
}
pub(super) fn exit_echo_action(&mut self) -> Option<Action> {
let (scope, asked) = self.exit_echo.take()?;
let voter = asked.trailing_zeros() as usize;
let remaining = asked & !(1 << voter);
if remaining != 0 {
self.exit_echo = Some((scope, remaining));
}
Some(Action::Send {
to: self.configuration.voters()[voter],
message: Control::ExitView(scope),
})
}
pub(super) fn exit_action(&mut self) -> Option<Action> {
if self.now < self.exit_retry_at? {
return None;
}
self.exit_retry_at = Some(self.now + self.timing.retransmit);
Some(Action::Broadcast(Control::ExitView(self.scope())))
}
}