use crate::record::Payload;
use crate::wire::checksum64;
use std::collections::{BTreeMap, BTreeSet};
pub type ReplicaId = u8;
pub type Op = u64;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Entry {
pub op: Op,
pub view: u64,
pub parent: u64,
pub checksum: u64,
pub body: Payload,
}
impl Entry {
pub fn new(op: Op, view: u64, parent: u64, body: Payload) -> Self {
let checksum = Self::sum(op, view, parent, &body);
Self {
op,
view,
parent,
checksum,
body,
}
}
fn sum(op: Op, view: u64, parent: u64, body: &[u8]) -> u64 {
let mut head = [0u8; 24];
head[..8].copy_from_slice(&op.to_le_bytes());
head[8..16].copy_from_slice(&view.to_le_bytes());
head[16..].copy_from_slice(&parent.to_le_bytes());
checksum64(&[&head, body])
}
pub fn valid(&self) -> bool {
self.checksum == Self::sum(self.op, self.view, self.parent, &self.body)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Msg {
Prepare {
view: u64,
entries: Vec<Entry>,
commit: Op,
},
PrepareOk {
view: u64,
op: Op,
},
Commit {
view: u64,
commit: Op,
},
StartViewChange {
view: u64,
},
DoViewChange {
view: u64,
last_normal: u64,
op: Op,
commit: Op,
entries: Vec<Entry>,
},
StartView {
view: u64,
op: Op,
commit: Op,
entries: Vec<Entry>,
},
GetEntries {
view: u64,
from: Op,
to: Op,
},
Entries {
view: u64,
entries: Vec<Entry>,
commit: Op,
},
GetRepair {
view: u64,
first: Op,
last: Op,
},
RepairEntries {
entries: Vec<Entry>,
commit: Op,
},
GetState {
view: u64,
after: Op,
},
StateOffer {
view: u64,
after: Op,
commit: Op,
through: Op,
},
NewState {
view: u64,
commit: Op,
after: Op,
entries: Vec<Entry>,
},
Ping {
view: u64,
stamp: u64,
},
Pong {
view: u64,
stamp: u64,
},
GetEpoch {
epoch: u64,
},
Epoch {
entry: Entry,
},
Reconfigure {
body: Payload,
},
}
impl Msg {
fn view(&self) -> Option<u64> {
match self {
Msg::Prepare { view, .. }
| Msg::PrepareOk { view, .. }
| Msg::Commit { view, .. }
| Msg::StartViewChange { view }
| Msg::DoViewChange { view, .. }
| Msg::StartView { view, .. }
| Msg::GetEntries { view, .. }
| Msg::Entries { view, .. }
| Msg::GetRepair { view, .. }
| Msg::GetState { view, .. }
| Msg::StateOffer { view, .. }
| Msg::NewState { view, .. }
| Msg::Ping { view, .. }
| Msg::Pong { view, .. } => Some(*view),
Msg::RepairEntries { .. }
| Msg::GetEpoch { .. }
| Msg::Epoch { .. }
| Msg::Reconfigure { .. } => None,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Action {
Send {
to: ReplicaId,
msg: Msg,
},
Persist {
entries: Vec<Entry>,
},
Replace {
after: Op,
entries: Vec<Entry>,
},
Committed {
commit: Op,
},
SaveView {
view: u64,
last_normal: u64,
commit: Op,
},
Role {
primary: bool,
view: u64,
},
SaveMembership {
membership: Membership,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Status {
Normal,
ViewChange,
Recovering,
}
#[derive(Clone, Debug, Default)]
pub struct Recovered {
pub view: u64,
pub last_normal: u64,
pub commit: Op,
pub entries: Vec<Entry>,
pub base_checksum: u64,
pub base: Op,
pub damaged: Option<(Op, Op)>,
pub membership: Option<Membership>,
pub joining: bool,
}
pub const MAX_MEMBERS: usize = 32;
pub const MAX_GROUP: usize = 9;
const NO_ONE: ReplicaId = ReplicaId::MAX;
pub fn epoch_of(view: u64) -> u64 {
view >> 32
}
pub fn first_view(epoch: u64) -> u64 {
epoch << 32
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Membership {
pub epoch: u64,
pub members: Vec<ReplicaId>,
pub entry: Option<Entry>,
pub previous: Vec<ReplicaId>,
}
impl Membership {
pub fn initial(n: u8) -> Self {
Self {
epoch: 0,
members: (0..n).collect(),
entry: None,
previous: Vec::new(),
}
}
pub fn from_entry(e: &Entry) -> Option<Self> {
let (epoch, members) = decode_reconfig(&e.body)?;
Some(Self {
epoch,
members: members.iter().map(|(id, _)| *id).collect(),
entry: Some(e.clone()),
previous: Vec::new(),
})
}
pub fn addresses(&self) -> Vec<(ReplicaId, String)> {
self.entry
.as_ref()
.and_then(|e| decode_reconfig(&e.body))
.map_or_else(Vec::new, |(_, members)| members)
}
pub fn start(&self) -> Op {
self.entry.as_ref().map_or(0, |e| e.op)
}
pub fn contains(&self, id: ReplicaId) -> bool {
self.members.contains(&id)
}
pub fn quorum(&self) -> usize {
self.members.len() / 2 + 1
}
pub fn primary_of(&self, view: u64) -> ReplicaId {
if epoch_of(view) != self.epoch || self.members.is_empty() {
return NO_ONE;
}
let i = (view - first_view(self.epoch)) % self.members.len() as u64;
self.members[i as usize]
}
pub fn next(
&self,
primary: ReplicaId,
members: &[(ReplicaId, String)],
) -> Result<Vec<(ReplicaId, String)>, &'static str> {
if members.is_empty() || members.len() > MAX_GROUP {
return Err("a group has 1 to 9 members");
}
let mut ids = BTreeSet::new();
for (id, addr) in members {
if usize::from(*id) >= MAX_MEMBERS || addr.is_empty() || !ids.insert(*id) {
return Err("member IDs must be distinct and below 32, each with an address");
}
}
let current: BTreeSet<ReplicaId> = self.members.iter().copied().collect();
if ids == current {
return Err("the membership is unchanged");
}
let staying = ids.intersection(¤t).count();
if staying < members.len() / 2 + 1 {
return Err(
"a quorum of the new members must be members now: replace a minority at a time",
);
}
let rank = |id: &ReplicaId| {
if *id == primary {
(0, 0)
} else if let Some(i) = self.members.iter().position(|m| m == id) {
(1, i)
} else {
(2, members.iter().position(|(m, _)| m == id).unwrap_or(0))
}
};
let mut ordered = members.to_vec();
ordered.sort_by_key(|(id, _)| rank(id));
Ok(ordered)
}
}
const RECONFIG: [u8; 4] = [0xF5, 0x00, b'M', 1];
pub fn is_reconfig(body: &[u8]) -> bool {
body.starts_with(&RECONFIG)
}
pub fn encode_reconfig(epoch: u64, members: &[(ReplicaId, String)]) -> Payload {
let mut out = RECONFIG.to_vec();
put_varint(&mut out, epoch);
put_varint(&mut out, members.len() as u64);
for (id, addr) in members {
out.push(*id);
put_bytes(&mut out, addr.as_bytes());
}
Payload::from(out)
}
pub fn decode_reconfig(body: &[u8]) -> Option<(u64, Vec<(ReplicaId, String)>)> {
let mut r = Reader::new(body.strip_prefix(&RECONFIG[..])?);
let epoch = r.varint().ok()?;
let n = r.varint().ok()?;
if n == 0 || n > MAX_GROUP as u64 || epoch == 0 {
return None;
}
let mut members = Vec::new();
for _ in 0..n {
let id = r.u8().ok()?;
let addr = String::from_utf8(r.bytes().ok()?.to_vec()).ok()?;
if usize::from(id) >= MAX_MEMBERS {
return None;
}
members.push((id, addr));
}
r.is_empty().then_some((epoch, members))
}
#[derive(Clone, Debug)]
struct Dvc {
last_normal: u64,
op: Op,
commit: Op,
entries: Vec<Entry>,
}
struct StateTransfer {
view: u64,
primary: ReplicaId,
after: Op,
through: Op,
commit: Op,
fetched: Vec<Entry>,
}
impl StateTransfer {
fn request(&self) -> Action {
Action::Send {
to: self.primary,
msg: Msg::GetEntries {
view: self.view,
from: self.fetched.last().map_or(self.after, |entry| entry.op) + 1,
to: self.through,
},
}
}
}
struct Adoption {
donor: ReplicaId,
through: Op,
max_commit: Op,
suffix: Vec<Entry>,
fetched: Vec<Entry>,
}
#[derive(Clone, Debug)]
pub struct Timing {
pub heartbeat_ms: u64,
pub election_ms: u64,
}
impl Default for Timing {
fn default() -> Self {
Self {
heartbeat_ms: 5,
election_ms: 40,
}
}
}
impl Timing {
pub fn validate(&self) -> Result<(), &'static str> {
if self.heartbeat_ms == 0 {
return Err("the heartbeat interval must be at least 1 ms");
}
if self.election_ms < 4 * self.heartbeat_ms {
return Err("the election timeout must be at least four heartbeat intervals");
}
if self.election_ms > 600_000 {
return Err("the election timeout must be at most ten minutes");
}
Ok(())
}
pub fn promise_ms(&self) -> u64 {
(self.election_ms / 2).max(2)
}
pub fn lease_ms(&self) -> u64 {
let p = self.promise_ms() - 1;
(p * (1_000_000 - MAX_CLOCK_DRIFT_PPM) / (1_000_000 + MAX_CLOCK_DRIFT_PPM))
.saturating_sub(1)
}
pub fn step_down_ms(&self) -> u64 {
(self.election_ms * 4).max(200)
}
fn retransmit_ms(&self) -> u64 {
self.heartbeat_ms.max(10)
}
fn repair_ms(&self) -> u64 {
(self.election_ms / 2).max(20)
}
}
pub const MAX_CLOCK_DRIFT_PPM: u64 = 10_000;
fn next_random(state: &mut u64) -> u64 {
*state = state.wrapping_add(0x9e37_79b9_7f4a_7c15);
let mut z = *state;
z = (z ^ (z >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
z = (z ^ (z >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
z ^ (z >> 31)
}
pub struct Replica {
pub id: ReplicaId,
pub n: u8,
view: u64,
last_normal: u64,
status: Status,
log: BTreeMap<Op, Entry>,
base: Op,
base_checksum: u64,
op: Op,
durable: Op,
commit: Op,
acked: Vec<Op>,
svc: BTreeSet<ReplicaId>,
dvc: BTreeMap<ReplicaId, Dvc>,
dvc_sent: bool,
adopting: Option<Adoption>,
transfer: Option<StateTransfer>,
damaged: Option<(Op, Op)>,
repair_parts: BTreeMap<Op, Entry>,
last_heard: u64,
last_beat: u64,
last_repair: u64,
timing: Timing,
jitter: u64,
rng: u64,
clock: u64,
proposed_at: u64,
progress_at: Vec<u64>,
last_svc: u64,
retired_view: Option<u64>,
promised_until: u64,
deferred_view: u64,
primary_lost: bool,
lease_acks: Vec<u64>,
heard_at: Vec<u64>,
#[cfg(test)]
pub(crate) ignore_promises: bool,
membership: Membership,
retired: bool,
joining: bool,
pending_epoch: Option<Membership>,
last_epoch_ask: u64,
epoch_catch_up: bool,
catch_up: Vec<Entry>,
told: Vec<u64>,
}
impl Replica {
pub fn new(
id: ReplicaId,
n: u8,
mut rec: Recovered,
timing: Timing,
seed: u64,
now: u64,
) -> Self {
let membership = rec
.membership
.take()
.unwrap_or_else(|| Membership::initial(n));
let joining = rec.joining;
let retired = !joining && !membership.contains(id);
let n = membership.members.len() as u8;
let mut log = BTreeMap::new();
let mut op = rec.base;
for e in rec.entries {
op = e.op;
log.insert(e.op, e);
}
let was_primary = !joining && membership.primary_of(rec.view) == id;
let status = if rec.damaged.is_some() {
Status::Recovering
} else if rec.view != rec.last_normal || was_primary || joining || retired {
Status::ViewChange
} else {
Status::Normal
};
let mut rng = seed;
let jitter = next_random(&mut rng) % timing.election_ms.max(1);
let timing_promise = timing.promise_ms();
if rec.damaged.is_some() {
op = op.max(rec.commit);
}
let mut replica = Self {
id,
n,
view: rec.view,
last_normal: rec.last_normal,
status,
log,
base: rec.base,
base_checksum: rec.base_checksum,
op,
durable: rec.damaged.map_or(op, |(first, _)| first.saturating_sub(1)),
commit: rec.commit.min(op),
acked: vec![0; MAX_MEMBERS],
svc: BTreeSet::new(),
dvc: BTreeMap::new(),
dvc_sent: false,
adopting: None,
transfer: None,
damaged: rec.damaged,
repair_parts: BTreeMap::new(),
last_heard: now,
last_beat: 0,
last_repair: 0,
timing,
jitter,
rng,
clock: now,
proposed_at: now,
progress_at: vec![now; MAX_MEMBERS],
last_svc: now,
retired_view: was_primary.then_some(rec.view),
promised_until: now.saturating_add(timing_promise),
deferred_view: 0,
primary_lost: false,
lease_acks: vec![0; MAX_MEMBERS],
heard_at: vec![now; MAX_MEMBERS],
#[cfg(test)]
ignore_promises: false,
membership,
retired,
joining,
pending_epoch: None,
last_epoch_ask: 0,
epoch_catch_up: false,
catch_up: Vec::new(),
told: vec![0; MAX_MEMBERS],
};
if !replica.joining && !replica.retired && !replica.holds_start() {
replica.epoch_catch_up = true;
replica.status = Status::Recovering;
}
replica
}
fn request_epoch_prefix(&self, out: &mut Vec<Action>) {
if let Some(start) = &self.membership.entry {
let from = self.catch_up.last().map_or(self.commit, |e| e.op) + 1;
let msg = Msg::GetEntries {
view: self.view,
from,
to: start.op,
};
self.broadcast(msg.clone(), out);
for &to in &self.membership.previous {
out.push(Action::Send {
to,
msg: msg.clone(),
});
}
}
}
fn finish_epoch_catch_up(&mut self, out: &mut Vec<Action>) {
self.epoch_catch_up = false;
self.catch_up.clear();
self.status = Status::Recovering;
self.adopting = None;
self.transfer = None;
self.last_heard = self.clock;
self.last_repair = self.clock;
self.save_view(out);
self.broadcast(
Msg::GetState {
view: self.view,
after: self.commit,
},
out,
);
}
fn catch_up_epoch(&mut self, from: ReplicaId, entries: Vec<Entry>, out: &mut Vec<Action>) {
let Some(start) = self.membership.entry.clone() else {
return;
};
if self.holds_start() || self.commit >= start.op {
return;
}
let mut last = self.catch_up.last().map_or(self.commit, |e| e.op);
let mut parent = self
.catch_up
.last()
.map(|e| e.checksum)
.or_else(|| self.checksum_at(self.commit));
let before = self.catch_up.len();
for e in entries {
if e.op <= last {
continue;
}
if e.op != last + 1 || e.op > start.op || Some(e.parent) != parent || !e.valid() {
break;
}
parent = Some(e.checksum);
last = e.op;
self.catch_up.push(e);
}
if last == start.op && parent == Some(start.checksum) {
let fetched = std::mem::take(&mut self.catch_up);
let commit = self.commit;
if self.replace(commit, fetched, out) {
self.set_commit(start.op, out);
self.pending_epoch = None;
self.finish_epoch_catch_up(out);
}
} else if self.catch_up.len() > before {
out.push(Action::Send {
to: from,
msg: Msg::GetEntries {
view: self.view,
from: last + 1,
to: start.op,
},
});
}
}
fn may_vote(&self, now: u64) -> bool {
if self.retired || self.joining || !self.holds_start() {
return false;
}
#[cfg(test)]
if self.ignore_promises {
return true;
}
now >= self.promised_until
}
fn holds_start(&self) -> bool {
let start = self.membership.start();
start == 0
|| self.base >= start
|| self.checksum_at(start) == self.membership.entry.as_ref().map(|e| e.checksum)
}
pub fn sealed(&self) -> bool {
self.log
.range(self.commit + 1..)
.any(|(_, e)| is_reconfig(&e.body))
}
fn next_view(&self) -> u64 {
(self.view + 1).max(first_view(self.membership.epoch))
}
fn quorum(&self) -> usize {
self.membership.quorum()
}
pub fn membership(&self) -> &Membership {
&self.membership
}
pub fn retired(&self) -> bool {
self.retired
}
pub fn joining(&self) -> bool {
self.joining
}
pub fn reconfigure(
&mut self,
members: &[(ReplicaId, String)],
out: &mut Vec<Action>,
) -> Result<Op, &'static str> {
if !self.is_primary() {
return Err("this replica is not the primary");
}
if self.sealed() {
return Err("a membership change is already in progress");
}
let ordered = self.membership.next(self.id, members)?;
let body = encode_reconfig(self.membership.epoch + 1, &ordered);
self.propose(body, out)
.ok_or("this replica is not the primary")
}
fn enter_epoch(&mut self, mut m: Membership, holds: bool, out: &mut Vec<Action>) {
let now = self.clock;
if !self.joining {
m.previous = self
.membership
.members
.iter()
.copied()
.filter(|id| !m.members.contains(id))
.collect::<BTreeSet<_>>()
.into_iter()
.collect();
}
self.membership = m.clone();
self.n = m.members.len() as u8;
self.joining = false;
out.push(Action::SaveMembership {
membership: m.clone(),
});
self.svc.clear();
self.dvc.clear();
self.dvc_sent = false;
self.adopting = None;
self.transfer = None;
self.deferred_view = 0;
self.primary_lost = false;
self.lease_acks = vec![0; MAX_MEMBERS];
let first = first_view(m.epoch);
if !m.contains(self.id) {
self.retired = true;
self.status = Status::ViewChange;
self.view = self.view.max(first);
self.save_view(out);
out.push(Action::Role {
primary: false,
view: self.view,
});
return;
}
self.retired = false;
if holds && self.status != Status::Recovering {
self.status = Status::ViewChange;
out.push(Action::Role {
primary: false,
view: self.view,
});
if self.may_vote(now) {
self.start_view_change(first.max(self.view + 1), now, out);
} else {
self.deferred_view = first;
}
} else {
self.view = self.view.max(first);
self.status = Status::Recovering;
self.last_repair = now;
self.last_heard = now;
self.catch_up.clear();
self.epoch_catch_up = !self.holds_start();
self.save_view(out);
out.push(Action::Role {
primary: false,
view: self.view,
});
if self.epoch_catch_up {
self.request_epoch_prefix(out);
}
self.broadcast(
Msg::GetState {
view: self.view,
after: self.commit,
},
out,
);
}
}
fn after_step(&mut self, out: &mut Vec<Action>) {
if let Some(m) = self.pending_epoch.take()
&& m.epoch > self.membership.epoch
{
self.enter_epoch(m, true, out);
}
out.retain(|a| !matches!(a, Action::Send { to, .. } if *to == NO_ONE));
}
fn ask_epoch(&mut self, from: ReplicaId, now: u64, out: &mut Vec<Action>) {
if now.saturating_sub(self.last_epoch_ask) >= self.timing.retransmit_ms() {
self.last_epoch_ask = now;
out.push(Action::Send {
to: from,
msg: Msg::GetEpoch {
epoch: if self.joining {
0
} else {
self.membership.epoch
},
},
});
}
}
fn on_epoch(&mut self, entry: Entry, out: &mut Vec<Action>) {
let Some(m) = Membership::from_entry(&entry) else {
return;
};
if !entry.valid()
|| (self.joining && !m.contains(self.id))
|| (!self.joining && m.epoch <= self.membership.epoch)
{
return;
}
let holds = !self.joining
&& (self.base >= entry.op || self.checksum_at(entry.op) == Some(entry.checksum));
if holds && self.damaged.is_none() {
self.set_commit(entry.op, out);
self.pending_epoch = None;
self.enter_epoch(m, true, out);
} else {
self.enter_epoch(m, false, out);
}
}
pub fn lease_until(&self) -> Option<u64> {
if !self.is_primary() {
return None;
}
let need = self.quorum() - 1;
let mut stamps: Vec<u64> = self
.others()
.map(|r| self.lease_acks[usize::from(r)])
.collect();
stamps.sort_unstable_by(|a, b| b.cmp(a));
let oldest = match need {
0 => return None,
k => *stamps.get(k - 1)?,
};
(oldest > 0).then(|| oldest + self.timing.lease_ms())
}
pub fn lease_valid(&self, now: u64) -> bool {
self.lease_until().is_some_and(|until| now < until)
}
pub fn peer_closed(&mut self, from: ReplicaId, now: u64) {
self.clock = self.clock.max(now);
if self.status == Status::Normal && from != self.id && from == self.primary_of(self.view) {
self.primary_lost = true;
}
}
pub fn election_due(&self, now: u64) -> bool {
match self.status {
Status::Normal if self.is_primary() => false,
Status::Normal if self.primary_lost && self.may_vote(now) => true,
Status::Normal | Status::ViewChange => {
now.saturating_sub(self.last_heard) >= self.timing.election_ms + self.jitter
}
Status::Recovering => {
self.damaged.is_none()
&& now.saturating_sub(self.last_heard)
>= self.timing.election_ms * 2 + self.jitter
}
}
}
pub fn view(&self) -> u64 {
self.view
}
pub fn timing(&self) -> &Timing {
&self.timing
}
pub fn status(&self) -> Status {
self.status
}
pub fn last_normal(&self) -> u64 {
self.last_normal
}
pub fn op(&self) -> Op {
self.op
}
pub fn commit(&self) -> Op {
self.commit
}
pub fn durable(&self) -> Op {
self.durable
}
pub fn commit_waits_on_own_durability(&self) -> bool {
if !self.is_primary() || self.durable >= self.op {
return false;
}
let q = self.quorum();
if q < 2 {
return true;
}
let mut acks: Vec<Op> = self.others().map(|r| self.acked[usize::from(r)]).collect();
acks.sort_unstable_by(|a, b| b.cmp(a));
acks.get(q - 2)
.is_some_and(|&a| a.min(self.op) > self.commit)
}
pub fn primary_of(&self, view: u64) -> ReplicaId {
if self.joining {
return NO_ONE;
}
self.membership.primary_of(view)
}
pub fn is_primary(&self) -> bool {
!self.retired && self.primary_of(self.view) == self.id && self.status == Status::Normal
}
pub fn entry(&self, op: Op) -> Option<&Entry> {
self.log.get(&op)
}
pub fn entries(&self) -> impl DoubleEndedIterator<Item = &Entry> {
self.log.values()
}
pub fn entries_after(&self, op: Op) -> impl DoubleEndedIterator<Item = &Entry> {
self.log
.range((std::ops::Bound::Excluded(op), std::ops::Bound::Unbounded))
.map(|(_, e)| e)
}
pub fn base(&self) -> Op {
self.base
}
pub fn committed_page(&self, first: Op, last: Op) -> Vec<Entry> {
self.page(first, last.min(self.commit))
}
pub fn compact_through(&mut self, op: Op, checksum: u64) -> bool {
self.compact_through_taking(op, checksum).is_some()
}
pub fn compact_through_taking(&mut self, op: Op, checksum: u64) -> Option<BTreeMap<Op, Entry>> {
if op <= self.base
|| op == Op::MAX
|| op > self.commit
|| self.checksum_at(op) != Some(checksum)
|| self.damaged.is_some()
{
return None;
}
let kept = self.log.split_off(&(op + 1));
let dropped = std::mem::replace(&mut self.log, kept);
self.base = op;
self.base_checksum = checksum;
Some(dropped)
}
pub fn accepts_checkpoint(
&self,
from: ReplicaId,
view: u64,
base: Op,
donor_commit: Op,
) -> bool {
if self.epoch_catch_up
&& !self.holds_start()
&& self.damaged.is_none()
&& self.adopting.is_none()
&& from != self.id
&& (self.membership.contains(from) || self.membership.previous.contains(&from))
&& epoch_of(view) == self.membership.epoch
&& base > self.base
&& base != Op::MAX
&& donor_commit >= base
{
return true;
}
if from == self.id
|| !self.membership.contains(from)
|| (view != self.view && (self.status != Status::Recovering || self.damaged.is_none()))
|| (view < self.view && base < self.op)
|| base <= self.base
|| base == Op::MAX
|| donor_commit < base
{
return false;
}
if let Some(a) = &self.adopting {
return a.donor == from && base <= a.through && donor_commit >= a.through;
}
self.primary_of(view) == from
}
pub fn checkpoint_suffix(&self, base: Op, checksum: u64) -> Vec<Entry> {
let mut parent = checksum;
let mut next = base.saturating_add(1);
self.log
.range(next..)
.map(|(_, e)| e)
.take_while(|e| {
if e.op != next || e.parent != parent || !e.valid() {
return false;
}
parent = e.checksum;
next = next.saturating_add(1);
true
})
.cloned()
.collect()
}
pub fn checkpoint_progress(&mut self, now: u64) {
self.last_heard = now;
}
pub fn checkpoint_matches(&self, base: Op, checksum: u64, tail: &[Entry]) -> bool {
self.checkpoint_matches_checked(base, checksum, tail, 0)
}
pub fn chain_intact(base: Op, checksum: u64, tail: &[Entry]) -> bool {
let mut parent = checksum;
for (op, e) in (base.saturating_add(1)..).zip(tail) {
if e.op != op || e.parent != parent || !e.valid() {
return false;
}
parent = e.checksum;
}
true
}
pub fn checkpoint_matches_checked(
&self,
base: Op,
checksum: u64,
tail: &[Entry],
intact: usize,
) -> bool {
if base <= self.base || base == Op::MAX {
return false;
}
let preserve = if self.damaged.is_some() {
self.op
} else if self.status == Status::Normal {
self.durable
} else {
self.commit
};
if base <= preserve && self.checksum_at(base).is_some_and(|old| old != checksum) {
return false;
}
let mut own = (preserve > base)
.then(|| self.log.range(base + 1..=preserve))
.into_iter()
.flatten()
.peekable();
let mut parent = checksum;
for (i, (op, e)) in (base + 1..).zip(tail).enumerate() {
if e.op != op || e.parent != parent || (i >= intact && !e.valid()) {
return false;
}
if op <= preserve {
while own.peek().is_some_and(|(o, _)| **o < op) {
own.next();
}
if let Some((o, old)) = own.peek()
&& **o == op
&& old.checksum != e.checksum
{
return false;
}
}
parent = e.checksum;
}
tail.last().map_or(base, |e| e.op) >= preserve.max(self.commit)
}
pub fn installed_checkpoint(
&mut self,
from: ReplicaId,
view: u64,
base: Op,
checksum: u64,
donor_commit: Op,
tail: Vec<Entry>,
now: u64,
out: &mut Vec<Action>,
) -> bool {
if !self.accepts_checkpoint(from, view, base, donor_commit)
|| !self.checkpoint_matches(base, checksum, &tail)
{
return false;
}
let last = tail.last().map_or(base, |e| e.op);
let log = tail.into_iter().map(|e| (e.op, e)).collect();
self.install_matched(from, view, base, checksum, log, last, now, out)
.is_some()
}
#[allow(clippy::too_many_arguments)]
pub fn install_matched(
&mut self,
from: ReplicaId,
view: u64,
base: Op,
checksum: u64,
log: BTreeMap<Op, Entry>,
last: Op,
now: u64,
out: &mut Vec<Action>,
) -> Option<BTreeMap<Op, Entry>> {
if self.status == Status::Normal && last < self.durable {
return None;
}
self.base = base;
self.base_checksum = checksum;
self.view = self.view.max(view);
let old = std::mem::replace(&mut self.log, log);
self.op = last;
self.durable = last;
self.commit = self.commit.max(base);
self.damaged = None;
self.repair_parts.clear();
self.transfer = None;
self.catch_up.clear();
self.last_heard = now;
self.last_repair = now;
if self.epoch_catch_up && self.status != Status::Normal {
self.status = Status::Recovering;
self.save_view(out);
return Some(old);
}
if let Some(mut a) = self.adopting.take() {
a.fetched.clear();
if self.commit == a.through {
let suffix = a
.suffix
.into_iter()
.filter(|e| e.op > self.commit)
.collect();
self.finish_adoption(self.commit, suffix, a.max_commit, out);
} else {
self.adopting = Some(a);
self.request_adoption_page(out);
}
} else if self.status == Status::Normal {
self.save_view(out);
out.push(Action::Send {
to: from,
msg: Msg::PrepareOk {
view,
op: self.durable,
},
});
} else {
self.status = Status::Recovering;
self.save_view(out);
out.push(Action::Send {
to: from,
msg: Msg::GetState {
view: self.view,
after: self.commit,
},
});
}
Some(old)
}
pub fn holds_checkpoint(
&self,
from: ReplicaId,
view: u64,
base: Op,
checksum: u64,
donor_commit: Op,
) -> bool {
self.status == Status::Normal
&& self.adopting.is_none()
&& !self.epoch_catch_up
&& self.damaged.is_none()
&& base > self.base
&& base <= self.commit
&& self.checksum_at(base) == Some(checksum)
&& self.accepts_checkpoint(from, view, base, donor_commit)
}
pub fn install_own(
&mut self,
from: ReplicaId,
view: u64,
base: Op,
checksum: u64,
donor_commit: Op,
now: u64,
out: &mut Vec<Action>,
) -> Option<BTreeMap<Op, Entry>> {
if !self.holds_checkpoint(from, view, base, checksum, donor_commit) {
return None;
}
let dropped = self.compact_through_taking(base, checksum)?;
self.view = self.view.max(view);
self.repair_parts.clear();
self.transfer = None;
self.catch_up.clear();
self.last_heard = now;
self.last_repair = now;
self.save_view(out);
out.push(Action::Send {
to: from,
msg: Msg::PrepareOk {
view,
op: self.durable,
},
});
Some(dropped)
}
fn checksum_at(&self, op: Op) -> Option<u64> {
if op == self.base {
return Some(self.base_checksum);
}
self.log.get(&op).map(|e| e.checksum)
}
fn others(&self) -> impl Iterator<Item = ReplicaId> + '_ {
self.membership
.members
.iter()
.copied()
.filter(move |r| *r != self.id)
}
fn broadcast(&self, msg: Msg, out: &mut Vec<Action>) {
for r in self.others() {
out.push(Action::Send {
to: r,
msg: msg.clone(),
});
}
}
fn page(&self, first: Op, last: Op) -> Vec<Entry> {
let mut entries = Vec::new();
let mut bytes = 0usize;
for op in first..=last.min(self.op) {
let Some(entry) = self.log.get(&op) else {
break;
};
let size = entry.body.len().saturating_add(64);
if !entries.is_empty()
&& (entries.len() == 4096 || bytes.saturating_add(size) > 1 << 20)
{
break;
}
bytes = bytes.saturating_add(size);
entries.push(entry.clone());
}
entries
}
fn range(&self, after: Op, to: Op) -> Vec<Entry> {
if after >= to {
return Vec::new();
}
self.log
.range(after + 1..=to)
.map(|(_, e)| e.clone())
.collect()
}
fn append(&mut self, entries: &[Entry], out: &mut Vec<Action>) -> usize {
let mut add = Vec::new();
for e in entries {
if e.op <= self.op {
continue;
}
if e.op != self.op + 1 || !e.valid() || Some(e.parent) != self.checksum_at(self.op) {
break;
}
self.op = e.op;
self.log.insert(e.op, e.clone());
add.push(e.clone());
}
let n = add.len();
if n > 0 {
out.push(Action::Persist { entries: add });
}
n
}
fn replace(&mut self, after: Op, entries: Vec<Entry>, out: &mut Vec<Action>) -> bool {
let after = after.max(self.base);
let Some(mut prev) = self.checksum_at(after) else {
return false;
};
for (expect, e) in (after + 1..).zip(entries.iter()) {
if e.op != expect || !e.valid() || e.parent != prev {
return false;
}
prev = e.checksum;
}
if self.op == after + entries.len() as u64
&& entries.iter().all(|e| {
self.log
.get(&e.op)
.is_some_and(|x| x.checksum == e.checksum)
})
{
return true;
}
self.log.split_off(&(after + 1));
for e in &entries {
self.log.insert(e.op, e.clone());
}
self.op = after + entries.len() as u64;
self.durable = self.durable.min(after);
self.commit = self.commit.min(self.op);
out.push(Action::Replace { after, entries });
true
}
fn set_commit(&mut self, c: Op, out: &mut Vec<Action>) {
let c = c.min(self.op);
if c > self.commit {
let old = self.commit;
self.commit = c;
out.push(Action::Committed { commit: c });
if let Some(m) = self
.log
.range(old + 1..=c)
.rev()
.find(|(_, e)| is_reconfig(&e.body))
.and_then(|(_, e)| Membership::from_entry(e))
&& m.epoch > self.membership.epoch
&& self
.pending_epoch
.as_ref()
.is_none_or(|p| p.epoch < m.epoch)
{
self.pending_epoch = Some(m);
}
}
}
fn save_view(&self, out: &mut Vec<Action>) {
out.push(Action::SaveView {
view: self.view,
last_normal: self.last_normal,
commit: self.commit,
});
}
pub fn propose(&mut self, body: Payload, out: &mut Vec<Action>) -> Option<Op> {
self.propose_many(std::iter::once(body), out)
.map(|(first, _)| first)
}
pub fn propose_many(
&mut self,
bodies: impl IntoIterator<Item = Payload>,
out: &mut Vec<Action>,
) -> Option<(Op, Op)> {
if !self.is_primary() || self.sealed() {
return None;
}
let mut parent = self.checksum_at(self.op)?;
let mut entries = Vec::new();
for body in bodies {
let e = Entry::new(self.op + 1, self.view, parent, body);
parent = e.checksum;
self.op = e.op;
self.log.insert(e.op, e.clone());
entries.push(e);
}
let (first, last) = (entries.first()?.op, self.op);
self.proposed_at = self.clock;
self.broadcast(
Msg::Prepare {
view: self.view,
entries: entries.clone(),
commit: self.commit,
},
out,
);
out.push(Action::Persist { entries });
Some((first, last))
}
pub fn on_durable(&mut self, op: Op, out: &mut Vec<Action>) {
self.durable_through(op, out);
self.after_step(out);
}
fn durable_through(&mut self, op: Op, out: &mut Vec<Action>) {
let op = op.min(self.op);
if op <= self.durable {
return;
}
self.durable = op;
if self.status != Status::Normal {
return;
}
if self.is_primary() {
self.acked[usize::from(self.id)] = self.durable;
self.recompute_commit(out);
} else {
out.push(Action::Send {
to: self.primary_of(self.view),
msg: Msg::PrepareOk {
view: self.view,
op: self.durable,
},
});
}
}
fn recompute_commit(&mut self, out: &mut Vec<Action>) {
let mut acks: Vec<Op> = self.acked.clone();
acks[usize::from(self.id)] = self.durable;
acks.sort_unstable_by(|a, b| b.cmp(a));
let q = self.quorum();
if let Some(&c) = acks.get(q - 1) {
self.set_commit(c, out);
}
}
pub fn on_tick(&mut self, now: u64, out: &mut Vec<Action>) {
self.clock = self.clock.max(now);
if self.retired {
return;
}
if self.joining {
if now.saturating_sub(self.last_epoch_ask) >= self.timing.repair_ms() {
self.last_epoch_ask = now;
self.broadcast(Msg::GetEpoch { epoch: 0 }, out);
}
return;
}
self.tick(now, out);
self.after_step(out);
}
fn tick(&mut self, now: u64, out: &mut Vec<Action>) {
match self.status {
Status::Normal if self.is_primary() => {
let quiet = self.timing.step_down_ms();
let fresh = self
.others()
.filter(|r| now.saturating_sub(self.heard_at[usize::from(*r)]) < quiet)
.count();
if fresh + 1 < self.quorum() {
self.start_view_change(self.next_view(), now, out);
return;
}
if now.saturating_sub(self.last_beat) >= self.timing.heartbeat_ms {
self.last_beat = now;
let resend = self.timing.retransmit_ms();
let backups: Vec<ReplicaId> = self.others().collect();
for to in backups {
let ack = self.acked[usize::from(to)];
let stalled = now.saturating_sub(self.proposed_at) >= resend
&& now.saturating_sub(self.progress_at[usize::from(to)]) >= resend;
let msg = if ack < self.op && stalled {
self.progress_at[usize::from(to)] = now;
Msg::Prepare {
view: self.view,
entries: self.page(ack + 1, self.op.min(ack.saturating_add(32))),
commit: self.commit,
}
} else {
Msg::Commit {
view: self.view,
commit: self.commit,
}
};
out.push(Action::Send { to, msg });
out.push(Action::Send {
to,
msg: Msg::Ping {
view: self.view,
stamp: now,
},
});
}
}
}
Status::Normal | Status::ViewChange => {
if self.adopting.is_some()
&& now.saturating_sub(self.last_repair) >= self.timing.retransmit_ms()
{
self.last_repair = now;
self.request_adoption_page(out);
}
if !self.may_vote(now) {
return;
}
if self.deferred_view > self.view {
let view = self.deferred_view;
self.start_view_change(view, now, out);
} else if self.election_due(now) {
self.start_view_change(self.next_view(), now, out);
} else {
self.maybe_send_dvc(out);
self.maybe_start_view(out);
self.retransmit_view_change(now, out);
}
}
Status::Recovering => {
if self.epoch_catch_up && self.holds_start() && self.damaged.is_none() {
self.finish_epoch_catch_up(out);
return;
}
if now.saturating_sub(self.last_repair) >= self.timing.repair_ms() {
self.last_repair = now;
self.request_repair(out);
}
if !self.may_vote(now) || self.damaged.is_some() {
return;
}
if self.deferred_view > self.view {
let view = self.deferred_view;
self.start_view_change(view, now, out);
} else if self.election_due(now) {
self.start_view_change(self.next_view(), now, out);
}
}
}
}
fn repair_gap(&self, first: Op, last: Op) -> Option<(Op, Op)> {
let missing = (first..=last)
.find(|op| !self.log.contains_key(op) && !self.repair_parts.contains_key(op))?;
let next = self.log.range(missing..=last).next().map(|(n, _)| *n);
Some((missing, next.map_or(last, |op| op - 1)))
}
fn repair_entries(
&mut self,
entries: Vec<Entry>,
committed: Op,
now: u64,
out: &mut Vec<Action>,
) {
let Some((first, last)) = self.damaged else {
return;
};
let Some((start, end)) = self.repair_gap(first, last) else {
return;
};
let prev = |n: Op| {
self.repair_parts
.get(&n)
.map(|e| e.checksum)
.or_else(|| self.checksum_at(n))
};
let Some(mut parent) = prev(start - 1) else {
return;
};
let mut expected = start;
for e in entries {
if e.op < start {
continue;
}
if e.op != expected || e.op > end || e.parent != parent || !e.valid() {
break;
}
if e.op == end {
let successor = self.log.get(&(end + 1));
if successor.is_some_and(|next| next.parent != e.checksum) {
let left = self
.log
.range(first..start)
.next_back()
.map_or(first, |(n, _)| n + 1);
self.repair_parts.retain(|op, _| *op < left || *op > end);
return;
}
if successor.is_none() && committed < end {
break;
}
}
parent = e.checksum;
expected += 1;
self.repair_parts.insert(e.op, e);
}
if expected == start {
return;
}
if let Some((next, end)) = self.repair_gap(first, last) {
self.broadcast(
Msg::GetRepair {
view: self.view,
first: next,
last: end,
},
out,
);
return;
}
let mut replacement = Vec::new();
for op in first..=self.op {
let Some(e) = self.repair_parts.get(&op).or_else(|| self.log.get(&op)) else {
return;
};
replacement.push(e.clone());
}
let commit = self.commit;
if !self.replace(first - 1, replacement, out) {
self.repair_parts.clear();
return;
}
self.damaged = None;
self.repair_parts.clear();
self.commit = commit;
self.status = if self.view == self.last_normal && self.primary_of(self.view) != self.id {
Status::Normal
} else {
Status::Recovering
};
self.save_view(out);
self.last_heard = now;
}
fn request_repair(&mut self, out: &mut Vec<Action>) {
if let Some(transfer) = &self.transfer {
out.push(transfer.request());
}
if self.epoch_catch_up && !self.holds_start() && self.damaged.is_none() {
self.request_epoch_prefix(out);
}
if let Some((first, last)) = self.damaged {
let (from, to) = self.repair_gap(first, last).unwrap_or((first, last));
self.broadcast(
Msg::GetRepair {
view: self.view,
first: from,
last: to,
},
out,
);
self.broadcast(
Msg::GetEntries {
view: self.view,
from,
to,
},
out,
);
self.broadcast(
Msg::GetState {
view: self.view,
after: self.commit.min(first - 1),
},
out,
);
} else {
self.broadcast(
Msg::GetState {
view: self.view,
after: self.commit,
},
out,
);
}
}
fn start_view_change(&mut self, view: u64, now: u64, out: &mut Vec<Action>) {
self.view = view;
self.status = Status::ViewChange;
self.primary_lost = false;
self.jitter = next_random(&mut self.rng) % self.timing.election_ms.max(1);
self.svc.clear();
self.svc.insert(self.id);
self.dvc.clear();
self.dvc_sent = false;
self.adopting = None;
self.transfer = None;
self.last_heard = now;
self.last_svc = now;
self.save_view(out);
out.push(Action::Role {
primary: false,
view,
});
self.broadcast(Msg::StartViewChange { view }, out);
self.maybe_send_dvc(out);
}
fn maybe_send_dvc(&mut self, out: &mut Vec<Action>) {
if self.dvc_sent
|| self.status != Status::ViewChange
|| self.svc.len() < self.quorum()
|| !self.may_vote(self.clock)
{
return;
}
self.dvc_sent = true;
let p = self.primary_of(self.view);
if p == self.id {
let d = self.own_dvc();
self.dvc.insert(self.id, d);
self.maybe_start_view(out);
} else {
self.send_dvc(out);
}
}
fn own_dvc(&self) -> Dvc {
Dvc {
last_normal: self.last_normal,
op: self.op,
commit: self.commit,
entries: self.range(self.commit, self.op),
}
}
fn send_dvc(&self, out: &mut Vec<Action>) {
let d = self.own_dvc();
out.push(Action::Send {
to: self.primary_of(self.view),
msg: Msg::DoViewChange {
view: self.view,
last_normal: d.last_normal,
op: d.op,
commit: d.commit,
entries: d.entries,
},
});
}
fn resend_start_view(&self, to: ReplicaId, out: &mut Vec<Action>) {
out.push(Action::Send {
to,
msg: Msg::StartView {
view: self.view,
op: self.op,
commit: self.commit,
entries: self.range(self.commit, self.op),
},
});
}
fn retransmit_view_change(&mut self, now: u64, out: &mut Vec<Action>) {
if self.status != Status::ViewChange
|| !self.svc.contains(&self.id)
|| now.saturating_sub(self.last_svc) < self.timing.retransmit_ms()
{
return;
}
self.last_svc = now;
self.broadcast(Msg::StartViewChange { view: self.view }, out);
if self.dvc_sent && self.primary_of(self.view) != self.id {
self.send_dvc(out);
}
}
fn maybe_start_view(&mut self, out: &mut Vec<Action>) {
if self.status != Status::ViewChange
|| self.primary_of(self.view) != self.id
|| self.dvc.len() < self.quorum()
|| self.adopting.is_some()
|| self.retired_view == Some(self.view)
|| !self.may_vote(self.clock)
{
return;
}
let Some((&best_id, best)) = self
.dvc
.iter()
.max_by_key(|(id, d)| (d.last_normal, d.op, std::cmp::Reverse(**id)))
else {
return;
};
let best = best.clone();
let max_commit = self.dvc.values().map(|d| d.commit).max().unwrap_or(0);
let keep = self.commit;
if keep < best.commit && best_id != self.id {
self.adopting = Some(Adoption {
donor: best_id,
through: best.commit,
max_commit,
suffix: best.entries,
fetched: Vec::new(),
});
out.push(Action::Send {
to: best_id,
msg: Msg::GetEntries {
view: self.view,
from: keep + 1,
to: best.commit,
},
});
return;
}
let mut new_log = self.range(keep, best.commit.max(keep));
new_log.retain(|e| e.op > keep);
if best_id == self.id {
new_log = self.range(keep, self.op);
} else {
new_log.extend(
best.entries
.iter()
.filter(|e| e.op > keep.max(best.commit))
.cloned(),
);
}
self.finish_adoption(keep, new_log, max_commit, out);
}
fn request_adoption_page(&self, out: &mut Vec<Action>) {
if let Some(a) = &self.adopting {
let after = a.fetched.last().map_or(self.commit, |entry| entry.op);
out.push(Action::Send {
to: a.donor,
msg: Msg::GetEntries {
view: self.view,
from: after + 1,
to: a.through,
},
});
}
}
fn finish_adoption(
&mut self,
keep: Op,
new_log: Vec<Entry>,
max_commit: Op,
out: &mut Vec<Action>,
) {
if !self.replace(keep, new_log, out) {
self.adopting = None;
return;
}
self.adopting = None;
self.status = Status::Normal;
self.epoch_catch_up = false;
self.last_normal = self.view;
self.acked = vec![0; MAX_MEMBERS];
self.progress_at = vec![self.clock; MAX_MEMBERS];
self.proposed_at = self.clock;
self.lease_acks = vec![0; MAX_MEMBERS];
self.heard_at = vec![self.clock; MAX_MEMBERS];
self.set_commit(max_commit, out);
self.save_view(out);
out.push(Action::Role {
primary: true,
view: self.view,
});
let msg = Msg::StartView {
view: self.view,
op: self.op,
commit: self.commit,
entries: self.range(self.commit, self.op),
};
self.broadcast(msg, out);
self.acked[usize::from(self.id)] = self.durable;
}
pub fn on_message(&mut self, from: ReplicaId, msg: Msg, now: u64, out: &mut Vec<Action>) {
if usize::from(from) >= MAX_MEMBERS || from == self.id {
return;
}
self.clock = self.clock.max(now);
match msg {
Msg::GetEpoch { epoch } => {
if epoch < self.membership.epoch
&& let Some(entry) = self.membership.entry.clone()
{
out.push(Action::Send {
to: from,
msg: Msg::Epoch { entry },
});
}
return;
}
Msg::Epoch { entry } => {
self.on_epoch(entry, out);
self.after_step(out);
return;
}
_ => {}
}
if self.joining {
return;
}
if msg
.view()
.is_some_and(|v| epoch_of(v) > self.membership.epoch)
{
self.ask_epoch(from, now, out);
return;
}
if (self.retired
|| !self.membership.contains(from)
|| msg
.view()
.is_some_and(|v| epoch_of(v) < self.membership.epoch))
&& let Some(entry) = self.membership.entry.clone()
&& now.saturating_sub(self.told[usize::from(from)]) >= self.timing.repair_ms()
{
self.told[usize::from(from)] = now;
out.push(Action::Send {
to: from,
msg: Msg::Epoch { entry },
});
}
if self.retired {
if let Msg::GetEntries { from: a, to: b, .. } = msg
&& self.membership.contains(from)
{
let entries = self.committed_page(a, b);
if !entries.is_empty() && self.damaged.is_none() {
out.push(Action::Send {
to: from,
msg: Msg::Entries {
view: self.view,
entries,
commit: self.commit,
},
});
}
}
return;
}
let served = self.epoch_catch_up
&& self.membership.previous.contains(&from)
&& matches!(msg, Msg::Entries { .. });
if !self.membership.contains(from) && !served {
return;
}
self.message(from, msg, now, out);
self.after_step(out);
}
fn message(&mut self, from: ReplicaId, msg: Msg, now: u64, out: &mut Vec<Action>) {
match msg {
Msg::GetEpoch { .. } | Msg::Epoch { .. } => {}
Msg::Reconfigure { body } => {
if let Some((_, members)) = decode_reconfig(&body) {
let _ = self.reconfigure(&members, out);
}
}
Msg::Prepare {
view,
entries,
commit,
} => {
if self.primary_of(view) != from {
return;
}
if self.adopt_newer(view, from, now, out)
|| self.missed_start_view(view, from, now, out)
{
return;
}
if view != self.view || self.status != Status::Normal || self.is_primary() {
return;
}
self.last_heard = now;
self.primary_lost = false;
let before = self.op;
self.append(&entries, out);
if let Some(first) = entries.first()
&& first.op > self.op + 1
{
out.push(Action::Send {
to: from,
msg: Msg::GetEntries {
view,
from: self.op + 1,
to: first.op - 1,
},
});
}
if self.op == before && self.durable == self.op {
out.push(Action::Send {
to: from,
msg: Msg::PrepareOk {
view,
op: self.durable,
},
});
}
self.set_commit(commit, out);
}
Msg::PrepareOk { view, op } => {
if view != self.view || !self.is_primary() {
return;
}
let i = usize::from(from);
self.heard_at[i] = self.heard_at[i].max(now);
if op > self.acked[i] {
self.acked[i] = op.min(self.op);
self.progress_at[i] = now;
self.recompute_commit(out);
}
}
Msg::Ping { view, stamp } => {
if self.primary_of(view) != from
|| view != self.view
|| self.status != Status::Normal
|| self.is_primary()
{
return;
}
self.last_heard = now;
self.primary_lost = false;
self.promised_until = self
.promised_until
.max(now.saturating_add(self.timing.promise_ms()));
out.push(Action::Send {
to: from,
msg: Msg::Pong { view, stamp },
});
}
Msg::Pong { view, stamp } => {
if view != self.view || !self.is_primary() || stamp > self.clock {
return;
}
let i = usize::from(from);
self.heard_at[i] = self.heard_at[i].max(now);
self.lease_acks[i] = self.lease_acks[i].max(stamp);
}
Msg::Commit { view, commit } => {
if self.primary_of(view) != from {
return;
}
if self.adopt_newer(view, from, now, out)
|| self.missed_start_view(view, from, now, out)
{
return;
}
if view != self.view || self.status != Status::Normal {
return;
}
self.last_heard = now;
self.primary_lost = false;
self.set_commit(commit, out);
if self.durable < self.op || self.op < commit {
out.push(Action::Send {
to: from,
msg: Msg::PrepareOk {
view,
op: self.durable,
},
});
if self.op < commit {
out.push(Action::Send {
to: from,
msg: Msg::GetEntries {
view,
from: self.op + 1,
to: commit,
},
});
}
}
}
Msg::StartViewChange { view } => {
if self.damaged.is_some() {
return;
}
if self.status == Status::Recovering && view <= self.view {
return;
}
if view > self.view {
if !self.may_vote(now) {
self.deferred_view = self.deferred_view.max(view);
return;
}
self.start_view_change(view, now, out);
}
if view == self.view && self.status == Status::ViewChange {
self.svc.insert(from);
self.maybe_send_dvc(out);
}
if view == self.view && self.is_primary() {
self.resend_start_view(from, out);
}
}
Msg::DoViewChange {
view,
last_normal,
op,
commit,
entries,
} => {
if self.damaged.is_some()
|| (self.status == Status::Recovering && view <= self.view)
{
return;
}
if view > self.view {
if !self.may_vote(now) {
self.deferred_view = self.deferred_view.max(view);
return;
}
self.start_view_change(view, now, out);
}
if view == self.view && self.is_primary() {
self.resend_start_view(from, out);
return;
}
if view != self.view
|| self.status != Status::ViewChange
|| self.primary_of(view) != self.id
{
return;
}
self.dvc.insert(
from,
Dvc {
last_normal,
op,
commit,
entries,
},
);
if !self.dvc_sent {
self.svc.insert(from);
self.maybe_send_dvc(out);
}
self.maybe_start_view(out);
}
Msg::StartView {
view,
op: _,
commit,
entries,
} => {
if view < self.view || (view == self.view && self.status == Status::Normal) {
return;
}
if self.primary_of(view) != from {
return;
}
if self.status == Status::Recovering && self.damaged.is_some() {
return;
}
self.view = view;
self.last_heard = now;
self.adopting = None;
let keep = self.commit;
if keep < commit {
self.status = Status::Recovering;
self.last_repair = now;
self.save_view(out);
out.push(Action::Role {
primary: false,
view,
});
out.push(Action::Send {
to: from,
msg: Msg::GetState { view, after: keep },
});
return;
}
let new_log: Vec<Entry> = entries.into_iter().filter(|e| e.op > keep).collect();
if !self.replace(keep, new_log, out) {
self.status = Status::Recovering;
self.last_repair = now;
self.save_view(out);
out.push(Action::Send {
to: from,
msg: Msg::GetState { view, after: keep },
});
return;
}
self.status = Status::Normal;
self.epoch_catch_up = false;
self.last_normal = view;
self.save_view(out);
out.push(Action::Role {
primary: false,
view,
});
self.set_commit(commit, out);
out.push(Action::Send {
to: from,
msg: Msg::PrepareOk {
view,
op: self.durable.min(self.op),
},
});
}
Msg::GetRepair {
view: _,
first,
last,
} => {
let through = if self.status == Status::Recovering {
self.op
} else {
self.durable
};
let entries = self.page(first, last.min(through));
if !entries.is_empty() {
out.push(Action::Send {
to: from,
msg: Msg::RepairEntries {
entries,
commit: self.commit,
},
});
}
}
Msg::RepairEntries { entries, commit } => {
self.repair_entries(entries, commit, now, out)
}
Msg::GetEntries {
view,
from: a,
to: b,
} => {
let through = if self.status == Status::Normal {
b
} else {
b.min(self.commit)
};
let entries = self.page(a, through);
if !entries.is_empty() && self.damaged.is_none_or(|(x, _)| entries[0].op < x) {
out.push(Action::Send {
to: from,
msg: Msg::Entries {
view: view.max(self.view),
entries,
commit: self.commit,
},
});
}
}
Msg::Entries {
view,
entries,
commit,
} => self.on_entries(from, view, entries, commit, now, out),
Msg::GetState { view, after } => {
if !self.is_primary() || view > self.view {
return;
}
if after < self.base {
return;
}
if after > self.op {
return;
}
let entries = if after < self.op {
self.page(after + 1, self.op)
} else {
Vec::new()
};
let msg = if entries.last().map_or(after, |e| e.op) == self.op {
Msg::NewState {
view: self.view,
commit: self.commit,
after,
entries,
}
} else {
Msg::StateOffer {
view: self.view,
commit: self.commit,
after,
through: self.op,
}
};
out.push(Action::Send { to: from, msg });
}
Msg::StateOffer {
view,
after,
commit,
through,
} => {
if self.primary_of(view) != from
|| view < self.view
|| through <= after
|| commit > through
|| after > self.commit
|| commit < self.commit
|| self.damaged.is_some_and(|(first, _)| after >= first)
|| (self.status == Status::Normal && view == self.view)
{
return;
}
if self
.transfer
.as_ref()
.is_none_or(|t| view > t.view || (view == t.view && t.after != after))
{
self.transfer = Some(StateTransfer {
view,
primary: from,
after,
through,
commit,
fetched: Vec::new(),
});
self.view = view;
self.status = Status::Recovering;
self.adopting = None;
self.save_view(out);
self.last_heard = now;
self.last_repair = now;
out.push(Action::Role {
primary: false,
view,
});
}
if let Some(t) = &self.transfer {
out.push(t.request());
}
}
Msg::NewState {
view,
commit,
after,
entries,
} => {
if view < self.view {
return;
}
if let Some((a, _)) = self.damaged {
if self.primary_of(view) != from || after >= a || after > self.commit {
return;
}
let new_log: Vec<Entry> =
entries.into_iter().filter(|e| e.op > after).collect();
let c = self.commit;
if commit < c || (c > after && new_log.last().is_none_or(|e| e.op < c)) {
return;
}
if !self.replace(after, new_log, out) {
return;
}
self.commit = c.min(self.op);
self.damaged = None;
self.repair_parts.clear();
self.transfer = None;
self.view = view;
self.status = Status::Normal;
self.epoch_catch_up = false;
self.last_normal = view;
self.last_heard = now;
self.save_view(out);
out.push(Action::Role {
primary: false,
view,
});
self.set_commit(commit, out);
out.push(Action::Send {
to: from,
msg: Msg::PrepareOk {
view,
op: self.durable,
},
});
return;
}
if self.status == Status::Normal && view == self.view {
return;
}
if self.primary_of(view) != from || after > self.commit {
return;
}
let keep = self.commit.max(after);
let new_log: Vec<Entry> = entries.into_iter().filter(|e| e.op > keep).collect();
if !self.replace(keep, new_log, out) {
return;
}
self.transfer = None;
self.view = view;
self.status = Status::Normal;
self.epoch_catch_up = false;
self.last_normal = view;
self.last_heard = now;
self.save_view(out);
out.push(Action::Role {
primary: false,
view,
});
self.set_commit(commit, out);
out.push(Action::Send {
to: from,
msg: Msg::PrepareOk {
view,
op: self.durable,
},
});
}
}
}
fn adopt_newer(&mut self, view: u64, from: ReplicaId, now: u64, out: &mut Vec<Action>) -> bool {
if view <= self.view {
return false;
}
if self.damaged.is_some() {
return true;
}
self.view = view;
self.transfer = None;
self.adopting = None;
self.status = Status::Recovering;
self.last_repair = now;
self.save_view(out);
out.push(Action::Role {
primary: false,
view,
});
out.push(Action::Send {
to: from,
msg: Msg::GetState {
view,
after: self.commit,
},
});
true
}
fn missed_start_view(
&mut self,
view: u64,
from: ReplicaId,
now: u64,
out: &mut Vec<Action>,
) -> bool {
if view != self.view || self.status != Status::ViewChange || from != self.primary_of(view) {
return false;
}
self.status = Status::Recovering;
self.last_repair = now;
out.push(Action::Send {
to: from,
msg: Msg::GetState {
view,
after: self.commit,
},
});
true
}
fn on_entries(
&mut self,
from: ReplicaId,
view: u64,
entries: Vec<Entry>,
commit: Op,
now: u64,
out: &mut Vec<Action>,
) {
if let Some(t) = &self.transfer
&& t.primary == from
&& t.view == view
&& self.view == view
{
let anchor = self.checksum_at(t.after);
let t = self.transfer.as_mut().expect("receiving state");
let before = t.fetched.len();
for entry in entries {
let after = t.fetched.last().map_or(t.after, |e| e.op);
if entry.op <= after {
continue;
}
let checksum = t.fetched.last().map(|e| e.checksum).or(anchor);
if entry.op != after + 1
|| entry.op > t.through
|| Some(entry.parent) != checksum
|| !entry.valid()
{
break;
}
t.fetched.push(entry);
}
if before == t.fetched.len() {
return;
}
self.last_heard = now;
self.last_repair = now;
if t.fetched.last().is_some_and(|e| e.op == t.through) {
let t = self.transfer.take().expect("receiving state");
self.on_message(
from,
Msg::NewState {
view,
after: t.after,
commit: t.commit,
entries: t.fetched,
},
now,
out,
);
} else {
out.push(t.request())
}
return;
}
if self.damaged.is_some() {
self.repair_entries(entries, commit, now, out);
return;
}
if self.epoch_catch_up && !self.holds_start() {
self.catch_up_epoch(from, entries, out);
return;
}
if view != self.view {
return;
}
if self.adopting.is_some() {
let keep = self.commit;
let anchor = self.checksum_at(keep);
let a = self.adopting.as_mut().expect("adoption in progress");
if from != a.donor || commit < a.through {
return;
}
let before = a.fetched.len();
for e in entries {
let after = a.fetched.last().map_or(keep, |entry| entry.op);
if e.op <= after {
continue;
}
let parent = a.fetched.last().map(|entry| entry.checksum).or(anchor);
if e.op != after + 1 || e.op > a.through || Some(e.parent) != parent || !e.valid() {
break;
}
a.fetched.push(e);
}
if a.fetched.len() != before {
self.last_heard = now;
}
if a.fetched.last().is_some_and(|entry| entry.op == a.through) {
let mut a = self.adopting.take().expect("adoption in progress");
a.fetched
.extend(a.suffix.into_iter().filter(|e| e.op > a.through));
self.finish_adoption(keep, a.fetched, a.max_commit, out);
} else if a.fetched.len() != before {
self.last_repair = now;
self.request_adoption_page(out);
}
return;
}
if self.status != Status::Normal || from != self.primary_of(view) {
return;
}
let before = self.op;
self.append(&entries, out);
if self.op > before {
self.set_commit(commit, out);
}
}
}
use crate::wire::{DecodeError, DecodeResult, Reader, put_bytes, put_u64, put_varint};
pub fn encode_entry(e: &Entry, out: &mut Vec<u8>) {
put_varint(out, e.op);
put_varint(out, e.view);
put_u64(out, e.parent);
put_u64(out, e.checksum);
put_bytes(out, &e.body);
}
pub fn decode_entry(r: &mut Reader<'_>) -> DecodeResult<Entry> {
let op = r.varint()?;
let view = r.varint()?;
let parent = r.u64()?;
let checksum = r.u64()?;
let body: Payload = std::sync::Arc::from(r.bytes()?);
let e = Entry {
op,
view,
parent,
checksum,
body,
};
if !e.valid() {
return Err(DecodeError("entry checksum mismatch"));
}
Ok(e)
}
fn put_entries(out: &mut Vec<u8>, es: &[Entry]) {
put_varint(out, es.len() as u64);
for e in es {
encode_entry(e, out);
}
}
fn get_entries(r: &mut Reader<'_>) -> DecodeResult<Vec<Entry>> {
let n = r.varint()?;
if n > 1 << 20 {
return Err(DecodeError("too many entries"));
}
(0..n).map(|_| decode_entry(r)).collect()
}
pub fn encode_msg(m: &Msg, out: &mut Vec<u8>) {
match m {
Msg::Prepare {
view,
entries,
commit,
} => {
out.push(1);
put_varint(out, *view);
put_varint(out, *commit);
put_entries(out, entries);
}
Msg::PrepareOk { view, op } => {
out.push(2);
put_varint(out, *view);
put_varint(out, *op);
}
Msg::Commit { view, commit } => {
out.push(3);
put_varint(out, *view);
put_varint(out, *commit);
}
Msg::StartViewChange { view } => {
out.push(4);
put_varint(out, *view);
}
Msg::DoViewChange {
view,
last_normal,
op,
commit,
entries,
} => {
out.push(5);
put_varint(out, *view);
put_varint(out, *last_normal);
put_varint(out, *op);
put_varint(out, *commit);
put_entries(out, entries);
}
Msg::StartView {
view,
op,
commit,
entries,
} => {
out.push(6);
put_varint(out, *view);
put_varint(out, *op);
put_varint(out, *commit);
put_entries(out, entries);
}
Msg::GetEntries { view, from, to } => {
out.push(7);
put_varint(out, *view);
put_varint(out, *from);
put_varint(out, *to);
}
Msg::Entries {
view,
entries,
commit,
} => {
out.push(8);
put_varint(out, *view);
put_varint(out, *commit);
put_entries(out, entries);
}
Msg::GetRepair { view, first, last } => {
out.push(12);
put_varint(out, *view);
put_varint(out, *first);
put_varint(out, *last);
}
Msg::RepairEntries { entries, commit } => {
out.push(13);
put_varint(out, *commit);
put_entries(out, entries);
}
Msg::StateOffer {
view,
after,
commit,
through,
} => {
out.push(11);
put_varint(out, *view);
put_varint(out, *after);
put_varint(out, *commit);
put_varint(out, *through);
}
Msg::GetState { view, after } => {
out.push(9);
put_varint(out, *view);
put_varint(out, *after);
}
Msg::NewState {
view,
commit,
after,
entries,
} => {
out.push(10);
put_varint(out, *view);
put_varint(out, *commit);
put_varint(out, *after);
put_entries(out, entries);
}
Msg::Ping { view, stamp } => {
out.push(14);
put_varint(out, *view);
put_varint(out, *stamp);
}
Msg::Pong { view, stamp } => {
out.push(15);
put_varint(out, *view);
put_varint(out, *stamp);
}
Msg::GetEpoch { epoch } => {
out.push(16);
put_varint(out, *epoch);
}
Msg::Epoch { entry } => {
out.push(17);
encode_entry(entry, out);
}
Msg::Reconfigure { body } => {
out.push(18);
put_bytes(out, body);
}
}
}
pub fn decode_msg(bytes: &[u8]) -> DecodeResult<Msg> {
let mut r = Reader::new(bytes);
let m = match r.u8()? {
1 => {
let view = r.varint()?;
let commit = r.varint()?;
Msg::Prepare {
view,
commit,
entries: get_entries(&mut r)?,
}
}
2 => Msg::PrepareOk {
view: r.varint()?,
op: r.varint()?,
},
3 => Msg::Commit {
view: r.varint()?,
commit: r.varint()?,
},
4 => Msg::StartViewChange { view: r.varint()? },
5 => Msg::DoViewChange {
view: r.varint()?,
last_normal: r.varint()?,
op: r.varint()?,
commit: r.varint()?,
entries: get_entries(&mut r)?,
},
6 => Msg::StartView {
view: r.varint()?,
op: r.varint()?,
commit: r.varint()?,
entries: get_entries(&mut r)?,
},
7 => Msg::GetEntries {
view: r.varint()?,
from: r.varint()?,
to: r.varint()?,
},
8 => {
let view = r.varint()?;
let commit = r.varint()?;
Msg::Entries {
view,
commit,
entries: get_entries(&mut r)?,
}
}
9 => Msg::GetState {
view: r.varint()?,
after: r.varint()?,
},
10 => {
let view = r.varint()?;
let commit = r.varint()?;
let after = r.varint()?;
Msg::NewState {
view,
commit,
after,
entries: get_entries(&mut r)?,
}
}
11 => Msg::StateOffer {
view: r.varint()?,
after: r.varint()?,
commit: r.varint()?,
through: r.varint()?,
},
12 => Msg::GetRepair {
view: r.varint()?,
first: r.varint()?,
last: r.varint()?,
},
13 => Msg::RepairEntries {
commit: r.varint()?,
entries: get_entries(&mut r)?,
},
14 => Msg::Ping {
view: r.varint()?,
stamp: r.varint()?,
},
15 => Msg::Pong {
view: r.varint()?,
stamp: r.varint()?,
},
16 => Msg::GetEpoch { epoch: r.varint()? },
17 => Msg::Epoch {
entry: decode_entry(&mut r)?,
},
18 => Msg::Reconfigure {
body: std::sync::Arc::from(r.bytes()?),
},
_ => return Err(DecodeError("unknown replication message")),
};
if !r.is_empty() {
return Err(DecodeError("trailing bytes in replication message"));
}
Ok(m)
}
#[cfg(test)]
mod sim;