1use std::collections::{BTreeMap, BTreeSet};
19
20use crafty_proto::{
21 AppendEntries, AppendEntriesReply, CatalogCommand, EntryPayload, InstallSnapshot,
22 InstallSnapshotReply, LogEntry, LogId, LogIndex, Membership, NodeId,
23 QueueAutoscalePolicyCommand, RaftRpc, RaftRpcReply, RequestVote, RequestVoteReply, Round,
24 SagaJournalCommand, Term, TwoPhaseAbortCommand, TwoPhaseJournalCommand, TwoPhasePrepareCommand,
25};
26
27use crate::config::Configuration;
28use crate::failure_detector::{
29 AckWindowLiveness, FailureDetectorKind, PhiAccrualLiveness, ReachabilityConfig,
30};
31use crate::log::Log;
32use crate::rng::Rng;
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub enum Role {
37 Follower,
39 PreCandidate,
42 Candidate,
44 Leader,
46}
47
48#[derive(Debug, Clone)]
50pub struct Config {
51 pub election_timeout_min: u64,
53 pub election_timeout_max: u64,
55 pub heartbeat_interval: u64,
57 pub seed: u64,
59 pub reachability: ReachabilityConfig,
61}
62
63impl Default for Config {
64 fn default() -> Self {
65 Self {
66 election_timeout_min: 10,
67 election_timeout_max: 20,
68 heartbeat_interval: 3,
69 seed: 0,
70 reachability: ReachabilityConfig::default(),
71 }
72 }
73}
74
75#[derive(Debug, Clone, PartialEq, Eq)]
77pub struct Committed {
78 pub index: LogIndex,
80 pub command: Vec<u8>,
82}
83
84#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
86pub struct ReadId(pub u64);
87
88#[derive(Debug, Clone, PartialEq, Eq)]
90pub enum Output {
91 Send(NodeId, RaftRpc),
93 Reply(NodeId, RaftRpcReply),
95 Apply(Committed),
97 RoleChanged(Role),
99 ReadReady {
102 id: ReadId,
104 index: LogIndex,
106 },
107 ReadFailed {
110 id: ReadId,
112 },
113 LoadSnapshot {
116 index: LogIndex,
118 data: Vec<u8>,
120 },
121 CatalogApplied {
123 index: LogIndex,
125 command: CatalogCommand,
127 },
128 SagaJournalApplied {
130 index: LogIndex,
132 command: SagaJournalCommand,
134 },
135 TwoPhasePrepareApplied {
137 index: LogIndex,
139 command: TwoPhasePrepareCommand,
141 },
142 TwoPhaseAbortApplied {
144 index: LogIndex,
146 command: TwoPhaseAbortCommand,
148 },
149 TwoPhaseJournalApplied {
151 index: LogIndex,
153 command: TwoPhaseJournalCommand,
155 },
156 QueueAutoscalePolicyApplied {
158 index: LogIndex,
160 command: QueueAutoscalePolicyCommand,
162 },
163}
164
165#[derive(Debug, Clone, Copy, PartialEq, Eq)]
168pub struct NotLeader {
169 pub leader: Option<NodeId>,
171}
172
173#[derive(Debug, Clone, Copy, PartialEq, Eq)]
175pub enum MembershipError {
176 NotLeader {
178 leader: Option<NodeId>,
180 },
181 InProgress,
183 EmptyVoters,
185}
186
187#[derive(Debug, Clone, Copy, PartialEq, Eq)]
189pub enum CatalogProposeError {
190 NotLeader {
192 leader: Option<NodeId>,
194 },
195}
196
197#[derive(Debug, Clone, PartialEq, Eq)]
203pub struct Persist {
204 pub term: Term,
206 pub voted_for: Option<NodeId>,
208 pub hard_state_dirty: bool,
211 pub truncate_from: Option<LogIndex>,
214 pub entries: Vec<LogEntry>,
216}
217
218#[derive(Debug, Clone, PartialEq, Eq)]
224pub struct SnapshotState {
225 pub last_included: LogId,
227 pub membership: Membership,
230 pub data: Vec<u8>,
232}
233
234#[derive(Debug, Clone)]
236struct PendingRead {
237 id: ReadId,
238 index: LogIndex,
239 round: Round,
240 acks: BTreeSet<NodeId>,
241}
242
243#[derive(Debug, Clone)]
246struct StoredSnapshot {
247 last_index: LogIndex,
248 last_term: Term,
249 membership: Membership,
250 data: Vec<u8>,
251}
252
253#[derive(Debug, Clone)]
255pub struct RaftNode {
256 id: NodeId,
257 initial: Membership,
258 config: Config,
259
260 current_term: Term,
262 voted_for: Option<NodeId>,
263 log: Log,
264
265 persisted_term: Term,
270 persisted_vote: Option<NodeId>,
271 log_dirty_from: Option<LogIndex>,
272
273 role: Role,
275 leader_id: Option<NodeId>,
276 commit_index: LogIndex,
277 last_applied: LogIndex,
278
279 votes: BTreeSet<NodeId>,
281
282 next_index: BTreeMap<NodeId, LogIndex>,
284 match_index: BTreeMap<NodeId, LogIndex>,
285 sent_upper: BTreeMap<NodeId, LogIndex>,
286 heartbeat_round: Round,
287 pending_reads: Vec<PendingRead>,
288 snapshot: Option<StoredSnapshot>,
289
290 last_ack_clock: BTreeMap<NodeId, u64>,
296 ack_liveness: AckWindowLiveness,
297 phi_liveness: PhiAccrualLiveness,
298
299 lease_round: Round,
305 lease_round_clock: u64,
306 lease_acks: BTreeSet<NodeId>,
307 lease_expiry: u64,
308
309 elapsed: u64,
311 heartbeat_elapsed: u64,
312 election_timeout: u64,
313 logical_clock: u64,
316 rng: Rng,
317
318 outbox: Vec<Output>,
319}
320
321impl RaftNode {
322 #[must_use]
324 pub fn new(id: NodeId, members: impl IntoIterator<Item = NodeId>, config: Config) -> Self {
325 let mut voters: Vec<NodeId> = members.into_iter().collect();
326 voters.sort();
327 voters.dedup();
328 let membership = Membership {
329 voters,
330 voters_outgoing: Vec::new(),
331 learners: Vec::new(),
332 };
333 Self::with_membership(id, membership, config)
334 }
335
336 #[must_use]
339 pub fn with_membership(id: NodeId, membership: Membership, config: Config) -> Self {
340 let mut rng = Rng::new(config.seed ^ id.0 ^ 0x9E37_79B9_7F4A_7C15);
341 let election_timeout = rng.range(config.election_timeout_min, config.election_timeout_max);
342 let phi_threshold = config.reachability.phi_threshold;
343 Self {
344 id,
345 initial: membership,
346 config,
347 current_term: Term::ZERO,
348 voted_for: None,
349 log: Log::default(),
350 persisted_term: Term::ZERO,
351 persisted_vote: None,
352 log_dirty_from: None,
353 role: Role::Follower,
354 leader_id: None,
355 commit_index: LogIndex::ZERO,
356 last_applied: LogIndex::ZERO,
357 votes: BTreeSet::new(),
358 next_index: BTreeMap::new(),
359 match_index: BTreeMap::new(),
360 sent_upper: BTreeMap::new(),
361 heartbeat_round: Round::ZERO,
362 pending_reads: Vec::new(),
363 snapshot: None,
364 last_ack_clock: BTreeMap::new(),
365 ack_liveness: AckWindowLiveness::default(),
366 phi_liveness: PhiAccrualLiveness::new(phi_threshold),
367 lease_round: Round::ZERO,
368 lease_round_clock: 0,
369 lease_acks: BTreeSet::new(),
370 lease_expiry: 0,
371 elapsed: 0,
372 heartbeat_elapsed: 0,
373 election_timeout,
374 logical_clock: 0,
375 rng,
376 outbox: Vec::new(),
377 }
378 }
379
380 #[must_use]
391 pub fn restore(
392 id: NodeId,
393 members: impl IntoIterator<Item = NodeId>,
394 config: Config,
395 term: Term,
396 voted_for: Option<NodeId>,
397 entries: impl IntoIterator<Item = LogEntry>,
398 ) -> Self {
399 let mut node = Self::new(id, members, config);
400 node.current_term = term;
401 node.voted_for = voted_for;
402 for entry in entries {
403 node.log.push_entry(entry);
404 }
405 node.persisted_term = term;
408 node.persisted_vote = voted_for;
409 node.log_dirty_from = None;
410 node
411 }
412
413 #[must_use]
425 pub fn restore_with_snapshot(
426 id: NodeId,
427 members: impl IntoIterator<Item = NodeId>,
428 config: Config,
429 term: Term,
430 voted_for: Option<NodeId>,
431 snapshot: SnapshotState,
432 entries: impl IntoIterator<Item = LogEntry>,
433 ) -> Self {
434 let mut node = Self::new(id, members, config);
435 node.current_term = term;
436 node.voted_for = voted_for;
437 let last = snapshot.last_included;
438 node.log.install_snapshot(last.index, last.term);
439 node.snapshot = Some(StoredSnapshot {
440 last_index: last.index,
441 last_term: last.term,
442 membership: snapshot.membership,
443 data: snapshot.data,
444 });
445 for entry in entries {
446 node.log.push_entry(entry);
447 }
448 node.commit_index = last.index;
451 node.last_applied = last.index;
452 node.persisted_term = term;
453 node.persisted_vote = voted_for;
454 node.log_dirty_from = None;
455 node
456 }
457
458 #[must_use]
462 pub fn id(&self) -> NodeId {
463 self.id
464 }
465 #[must_use]
467 pub fn role(&self) -> Role {
468 self.role
469 }
470 #[must_use]
472 pub fn is_leader(&self) -> bool {
473 self.role == Role::Leader
474 }
475 #[must_use]
477 pub fn current_term(&self) -> Term {
478 self.current_term
479 }
480 #[must_use]
482 pub fn leader_id(&self) -> Option<NodeId> {
483 self.leader_id
484 }
485 #[must_use]
487 pub fn commit_index(&self) -> LogIndex {
488 self.commit_index
489 }
490 #[must_use]
492 pub fn last_applied(&self) -> LogIndex {
493 self.last_applied
494 }
495 #[must_use]
497 pub fn last_log_index(&self) -> LogIndex {
498 self.log.last_index()
499 }
500 #[must_use]
502 pub fn voted_for(&self) -> Option<NodeId> {
503 self.voted_for
504 }
505 #[must_use]
507 pub fn term_at(&self, idx: LogIndex) -> Option<Term> {
508 self.log.term_at(idx)
509 }
510 #[must_use]
514 pub(crate) fn configuration(&self) -> Configuration {
515 let membership = self
516 .log
517 .last_membership()
518 .map(|(_, m)| m)
519 .or_else(|| self.snapshot.as_ref().map(|s| &s.membership))
520 .unwrap_or(&self.initial);
521 Configuration::from_membership(membership)
522 }
523
524 #[must_use]
526 pub fn committed_membership(&self) -> crafty_proto::Membership {
527 self.configuration().to_membership()
528 }
529
530 #[must_use]
532 pub fn snapshot_index(&self) -> LogIndex {
533 self.log.snapshot_index()
534 }
535
536 #[must_use]
538 pub fn compactable_entries(&self) -> u64 {
539 self.last_applied
540 .0
541 .saturating_sub(self.log.snapshot_index().0)
542 }
543
544 #[must_use]
546 pub fn compactable_log_bytes(&self) -> u64 {
547 self.log.bytes_up_to(self.last_applied)
548 }
549
550 #[must_use]
556 pub fn stored_snapshot(&self) -> Option<SnapshotState> {
557 self.snapshot.as_ref().map(|s| SnapshotState {
558 last_included: LogId::new(s.last_term, s.last_index),
559 membership: s.membership.clone(),
560 data: s.data.clone(),
561 })
562 }
563 #[must_use]
565 pub fn log_entries_from(&self, from: LogIndex) -> Vec<LogEntry> {
566 self.log.entries_from(from).to_vec()
567 }
568
569 #[must_use]
571 pub fn voters(&self) -> Vec<NodeId> {
572 self.configuration().voters()
573 }
574 #[must_use]
576 pub fn is_joint(&self) -> bool {
577 self.configuration().is_joint()
578 }
579
580 #[must_use]
582 pub fn take_outputs(&mut self) -> Vec<Output> {
583 std::mem::take(&mut self.outbox)
584 }
585
586 #[must_use]
593 pub fn take_persist(&mut self) -> Option<Persist> {
594 let hard_state_dirty =
595 self.current_term != self.persisted_term || self.voted_for != self.persisted_vote;
596 let log_from = self.log_dirty_from.take();
597 if !hard_state_dirty && log_from.is_none() {
598 return None;
599 }
600 self.persisted_term = self.current_term;
601 self.persisted_vote = self.voted_for;
602 let (truncate_from, entries) = match log_from {
603 Some(from) => {
606 let from = LogIndex(from.0.max(self.log.snapshot_index().0 + 1));
607 (Some(from), self.log.entries_from(from).to_vec())
608 }
609 None => (None, Vec::new()),
610 };
611 Some(Persist {
612 term: self.current_term,
613 voted_for: self.voted_for,
614 hard_state_dirty,
615 truncate_from,
616 entries,
617 })
618 }
619
620 fn mark_log_dirty(&mut self, from: LogIndex) {
624 self.log_dirty_from = Some(match self.log_dirty_from {
625 Some(cur) if cur.0 <= from.0 => cur,
626 _ => from,
627 });
628 }
629
630 fn log_append(&mut self, term: Term, payload: EntryPayload) -> LogIndex {
632 let idx = self.log.append(term, payload);
633 self.mark_log_dirty(idx);
634 idx
635 }
636
637 fn log_push(&mut self, entry: LogEntry) {
639 let idx = entry.index;
640 self.log.push_entry(entry);
641 self.mark_log_dirty(idx);
642 }
643
644 fn log_truncate_from(&mut self, idx: LogIndex) {
646 self.log.truncate_from(idx);
647 self.mark_log_dirty(idx);
648 }
649
650 fn config_index(&self) -> LogIndex {
653 self.log
654 .last_membership()
655 .map_or(LogIndex::ZERO, |(idx, _)| idx)
656 }
657
658 fn is_voter(&self, id: NodeId) -> bool {
659 self.configuration().is_voter(id)
660 }
661
662 fn peers(&self) -> Vec<NodeId> {
663 self.configuration().peers(self.id)
664 }
665
666 fn quorum_ok(&self, acked: &BTreeSet<NodeId>) -> bool {
668 self.configuration().has_quorum(acked)
669 }
670
671 fn quorum_of_votes(&self) -> bool {
672 let votes = self.votes.clone();
673 self.quorum_ok(&votes)
674 }
675
676 pub fn tick(&mut self) {
680 self.logical_clock += 1;
681 if self.role == Role::Leader {
682 self.update_liveness();
683 self.heartbeat_elapsed += 1;
684 if self.heartbeat_elapsed >= self.config.heartbeat_interval {
685 self.heartbeat_elapsed = 0;
686 self.broadcast_append();
687 }
688 } else {
689 self.elapsed += 1;
690 if self.elapsed >= self.election_timeout {
691 self.start_pre_election();
692 }
693 }
694 }
695
696 pub fn campaign(&mut self) {
699 self.start_real_election();
700 }
701
702 pub fn receive(&mut self, from: NodeId, rpc: RaftRpc) {
704 match rpc {
705 RaftRpc::RequestVote(rv) => self.handle_request_vote(from, &rv),
706 RaftRpc::AppendEntries(ae) => self.handle_append_entries(from, &ae),
707 RaftRpc::InstallSnapshot(is) => self.handle_install_snapshot(from, is),
708 }
709 }
710
711 pub fn receive_reply(&mut self, from: NodeId, reply: RaftRpcReply) {
713 let term = match &reply {
714 RaftRpcReply::RequestVote(r) => r.term,
715 RaftRpcReply::AppendEntries(r) => r.term,
716 RaftRpcReply::InstallSnapshot(r) => r.term,
717 };
718 if term > self.current_term {
719 self.become_follower(term);
720 return;
721 }
722 match reply {
723 RaftRpcReply::RequestVote(r) => self.handle_vote_reply(from, &r),
724 RaftRpcReply::AppendEntries(r) => self.handle_append_reply(from, &r),
725 RaftRpcReply::InstallSnapshot(r) => self.handle_snapshot_reply(from, &r),
726 }
727 }
728
729 pub fn propose(&mut self, command: Vec<u8>) -> Result<LogIndex, NotLeader> {
735 if self.role != Role::Leader {
736 return Err(NotLeader {
737 leader: self.leader_id,
738 });
739 }
740 let idx = self.log_append(self.current_term, EntryPayload::Command(command));
741 self.broadcast_append();
742 self.maybe_advance_commit();
743 Ok(idx)
744 }
745
746 pub fn read_index(&mut self, id: ReadId) -> Result<(), NotLeader> {
754 if self.role != Role::Leader {
755 return Err(NotLeader {
756 leader: self.leader_id,
757 });
758 }
759 self.broadcast_append();
761 let round = self.heartbeat_round;
762 let mut acks = BTreeSet::new();
763 acks.insert(self.id);
764 self.pending_reads.push(PendingRead {
765 id,
766 index: self.commit_index,
767 round,
768 acks,
769 });
770 self.try_complete_reads();
771 Ok(())
772 }
773
774 #[must_use]
781 pub fn compact(&mut self, up_to: LogIndex, data: Vec<u8>) -> bool {
782 if up_to <= self.log.snapshot_index() || up_to > self.last_applied {
783 return false;
784 }
785 let Some(term) = self.log.term_at(up_to) else {
786 return false;
787 };
788 let membership = self.membership_at(up_to);
789 self.log.compact(up_to, term);
790 self.snapshot = Some(StoredSnapshot {
791 last_index: up_to,
792 last_term: term,
793 membership,
794 data,
795 });
796 true
797 }
798
799 fn membership_at(&self, idx: LogIndex) -> Membership {
802 for i in (self.log.snapshot_index().0 + 1..=idx.0).rev() {
803 if let Some(EntryPayload::Membership(m)) = self.log.get(LogIndex(i)).map(|e| &e.payload)
804 {
805 return m.clone();
806 }
807 }
808 self.snapshot
809 .as_ref()
810 .map_or_else(|| self.initial.clone(), |s| s.membership.clone())
811 }
812
813 pub fn propose_membership(
821 &mut self,
822 new_voters: impl IntoIterator<Item = NodeId>,
823 learners: impl IntoIterator<Item = NodeId>,
824 ) -> Result<LogIndex, MembershipError> {
825 if self.role != Role::Leader {
826 return Err(MembershipError::NotLeader {
827 leader: self.leader_id,
828 });
829 }
830 let current = self.configuration();
831 if current.is_joint() || self.config_index() > self.commit_index {
832 return Err(MembershipError::InProgress);
833 }
834 let mut voters: Vec<NodeId> = new_voters.into_iter().collect();
835 voters.sort();
836 voters.dedup();
837 if voters.is_empty() {
838 return Err(MembershipError::EmptyVoters);
839 }
840 let mut learners: Vec<NodeId> = learners.into_iter().collect();
841 learners.sort();
842 learners.dedup();
843 learners.retain(|l| !voters.contains(l));
844
845 let joint = Membership {
846 voters,
847 voters_outgoing: current.voters(),
848 learners,
849 };
850 let idx = self.log_append(self.current_term, EntryPayload::Membership(joint));
851 self.broadcast_append();
852 self.maybe_advance_commit();
853 Ok(idx)
854 }
855
856 pub fn propose_catalog(
861 &mut self,
862 command: CatalogCommand,
863 ) -> Result<LogIndex, CatalogProposeError> {
864 if self.role != Role::Leader {
865 return Err(CatalogProposeError::NotLeader {
866 leader: self.leader_id,
867 });
868 }
869 let idx = self.log_append(self.current_term, EntryPayload::Catalog(command));
870 self.broadcast_append();
871 self.maybe_advance_commit();
872 Ok(idx)
873 }
874
875 pub fn propose_saga_journal(
880 &mut self,
881 command: SagaJournalCommand,
882 ) -> Result<LogIndex, CatalogProposeError> {
883 if self.role != Role::Leader {
884 return Err(CatalogProposeError::NotLeader {
885 leader: self.leader_id,
886 });
887 }
888 let idx = self.log_append(self.current_term, EntryPayload::SagaJournal(command));
889 self.broadcast_append();
890 self.maybe_advance_commit();
891 Ok(idx)
892 }
893
894 pub fn propose_two_phase_prepare(
899 &mut self,
900 command: TwoPhasePrepareCommand,
901 ) -> Result<LogIndex, CatalogProposeError> {
902 if self.role != Role::Leader {
903 return Err(CatalogProposeError::NotLeader {
904 leader: self.leader_id,
905 });
906 }
907 let idx = self.log_append(self.current_term, EntryPayload::TwoPhasePrepare(command));
908 self.broadcast_append();
909 self.maybe_advance_commit();
910 Ok(idx)
911 }
912
913 pub fn propose_two_phase_abort(
918 &mut self,
919 command: TwoPhaseAbortCommand,
920 ) -> Result<LogIndex, CatalogProposeError> {
921 if self.role != Role::Leader {
922 return Err(CatalogProposeError::NotLeader {
923 leader: self.leader_id,
924 });
925 }
926 let idx = self.log_append(self.current_term, EntryPayload::TwoPhaseAbort(command));
927 self.broadcast_append();
928 self.maybe_advance_commit();
929 Ok(idx)
930 }
931
932 pub fn propose_two_phase_journal(
937 &mut self,
938 command: TwoPhaseJournalCommand,
939 ) -> Result<LogIndex, CatalogProposeError> {
940 if self.role != Role::Leader {
941 return Err(CatalogProposeError::NotLeader {
942 leader: self.leader_id,
943 });
944 }
945 let idx = self.log_append(self.current_term, EntryPayload::TwoPhaseJournal(command));
946 self.broadcast_append();
947 self.maybe_advance_commit();
948 Ok(idx)
949 }
950
951 pub fn propose_queue_autoscale_policy(
956 &mut self,
957 command: QueueAutoscalePolicyCommand,
958 ) -> Result<LogIndex, CatalogProposeError> {
959 if self.role != Role::Leader {
960 return Err(CatalogProposeError::NotLeader {
961 leader: self.leader_id,
962 });
963 }
964 let idx = self.log_append(
965 self.current_term,
966 EntryPayload::QueueAutoscalePolicy(command),
967 );
968 self.broadcast_append();
969 self.maybe_advance_commit();
970 Ok(idx)
971 }
972
973 fn set_role(&mut self, role: Role) {
976 if self.role != role {
977 tracing::debug!(
978 target: "crafty::raft",
979 node = self.id.0,
980 term = self.current_term.0,
981 ?role,
982 "raft role changed"
983 );
984 self.role = role;
985 self.outbox.push(Output::RoleChanged(role));
986 }
987 }
988
989 fn become_follower(&mut self, term: Term) {
990 if term > self.current_term {
991 self.current_term = term;
992 self.voted_for = None;
993 }
994 self.votes.clear();
995 self.fail_pending_reads();
996 self.lease_expiry = 0;
999 self.lease_acks.clear();
1000 self.set_role(Role::Follower);
1001 }
1002
1003 fn start_pre_election(&mut self) {
1007 if !self.is_voter(self.id) {
1008 self.reset_election_timer();
1009 return;
1010 }
1011 self.set_role(Role::PreCandidate);
1012 self.votes.clear();
1013 self.votes.insert(self.id);
1014 self.reset_election_timer();
1015
1016 if self.quorum_of_votes() {
1017 self.start_real_election();
1018 return;
1019 }
1020
1021 let rv = RequestVote {
1023 term: self.current_term.next(),
1024 candidate_id: self.id,
1025 last_log: self.log.last_id(),
1026 pre_vote: true,
1027 };
1028 self.send_vote_requests(&rv);
1029 }
1030
1031 fn start_real_election(&mut self) {
1032 if !self.is_voter(self.id) {
1033 self.reset_election_timer();
1034 return;
1035 }
1036 self.current_term = self.current_term.next();
1037 self.set_role(Role::Candidate);
1038 self.voted_for = Some(self.id);
1039 self.votes.clear();
1040 self.votes.insert(self.id);
1041 self.leader_id = None;
1042 self.reset_election_timer();
1043
1044 if self.quorum_of_votes() {
1045 self.become_leader();
1046 return;
1047 }
1048
1049 let rv = RequestVote {
1050 term: self.current_term,
1051 candidate_id: self.id,
1052 last_log: self.log.last_id(),
1053 pre_vote: false,
1054 };
1055 self.send_vote_requests(&rv);
1056 }
1057
1058 fn send_vote_requests(&mut self, rv: &RequestVote) {
1059 for p in self.configuration().voter_peers(self.id) {
1060 self.outbox
1061 .push(Output::Send(p, RaftRpc::RequestVote(rv.clone())));
1062 }
1063 }
1064
1065 fn become_leader(&mut self) {
1066 self.set_role(Role::Leader);
1067 self.leader_id = Some(self.id);
1068 self.lease_expiry = 0;
1071 let next = self.log.last_index().next();
1072 self.next_index.clear();
1073 self.match_index.clear();
1074 self.sent_upper.clear();
1075 self.last_ack_clock.clear();
1078 for p in self.peers() {
1079 self.next_index.insert(p, next);
1080 self.match_index.insert(p, LogIndex::ZERO);
1081 }
1082 self.log_append(self.current_term, EntryPayload::Noop);
1084 self.heartbeat_elapsed = 0;
1085 self.broadcast_append();
1086 self.maybe_advance_commit();
1087 }
1088
1089 fn reset_election_timer(&mut self) {
1090 self.elapsed = 0;
1091 self.election_timeout = self.rng.range(
1092 self.config.election_timeout_min,
1093 self.config.election_timeout_max,
1094 );
1095 }
1096
1097 fn handle_request_vote(&mut self, from: NodeId, rv: &RequestVote) {
1100 if !self.is_voter(self.id) {
1101 self.reply_vote(from, false, rv.pre_vote);
1102 return;
1103 }
1104
1105 let up_to_date = rv.last_log >= self.log.last_id();
1106
1107 if rv.pre_vote {
1108 let leader_recent =
1112 self.leader_id.is_some() && self.elapsed < self.config.election_timeout_min;
1113 let granted = rv.term >= self.current_term && up_to_date && !leader_recent;
1114 self.reply_vote(from, granted, true);
1115 return;
1116 }
1117
1118 if rv.term > self.current_term {
1119 self.become_follower(rv.term);
1120 }
1121
1122 let mut granted = false;
1123 if rv.term >= self.current_term {
1124 let can_vote = self.voted_for.is_none() || self.voted_for == Some(rv.candidate_id);
1125 if can_vote && up_to_date {
1126 granted = true;
1127 self.voted_for = Some(rv.candidate_id);
1128 self.reset_election_timer();
1129 }
1130 }
1131 self.reply_vote(from, granted, false);
1132 }
1133
1134 fn reply_vote(&mut self, to: NodeId, vote_granted: bool, pre_vote: bool) {
1135 let reply = RequestVoteReply {
1136 term: self.current_term,
1137 vote_granted,
1138 pre_vote,
1139 };
1140 self.outbox
1141 .push(Output::Reply(to, RaftRpcReply::RequestVote(reply)));
1142 }
1143
1144 fn handle_vote_reply(&mut self, from: NodeId, reply: &RequestVoteReply) {
1145 if reply.pre_vote {
1146 if self.role == Role::PreCandidate && reply.vote_granted {
1147 self.votes.insert(from);
1148 if self.quorum_of_votes() {
1149 self.start_real_election();
1150 }
1151 }
1152 return;
1153 }
1154 if self.role != Role::Candidate || reply.term != self.current_term {
1155 return;
1156 }
1157 if reply.vote_granted {
1158 self.votes.insert(from);
1159 if self.quorum_of_votes() {
1160 self.become_leader();
1161 }
1162 }
1163 }
1164
1165 fn handle_append_entries(&mut self, from: NodeId, ae: &AppendEntries) {
1168 if ae.term < self.current_term {
1169 self.reply_append(from, false, None, None, ae.round);
1170 return;
1171 }
1172
1173 if ae.term > self.current_term {
1174 self.become_follower(ae.term);
1175 } else if self.role != Role::Follower {
1176 self.set_role(Role::Follower);
1177 }
1178 self.leader_id = Some(ae.leader_id);
1179 self.reset_election_timer();
1180
1181 if ae.prev_log.index.0 > 0 {
1183 match self.log.term_at(ae.prev_log.index) {
1184 None => {
1185 let hint = self.log.last_index().next();
1186 self.reply_append(from, false, Some(hint), None, ae.round);
1187 return;
1188 }
1189 Some(t) if t != ae.prev_log.term => {
1190 let first = self.log.first_index_of_term(t).unwrap_or(ae.prev_log.index);
1191 self.reply_append(from, false, Some(first), Some(t), ae.round);
1192 return;
1193 }
1194 _ => {}
1195 }
1196 }
1197
1198 let mut idx = ae.prev_log.index;
1200 for entry in &ae.entries {
1201 idx = idx.next();
1202 match self.log.term_at(idx) {
1203 Some(t) if t == entry.term => {}
1204 Some(_) => {
1205 self.log_truncate_from(idx);
1206 self.log_push(LogEntry {
1207 term: entry.term,
1208 index: idx,
1209 payload: entry.payload.clone(),
1210 });
1211 }
1212 None => {
1213 self.log_push(LogEntry {
1214 term: entry.term,
1215 index: idx,
1216 payload: entry.payload.clone(),
1217 });
1218 }
1219 }
1220 }
1221
1222 if ae.leader_commit > self.commit_index {
1223 self.commit_index = ae.leader_commit.min(idx);
1224 self.apply_committed();
1225 }
1226 self.reply_append(from, true, None, None, ae.round);
1227 }
1228
1229 fn reply_append(
1230 &mut self,
1231 to: NodeId,
1232 success: bool,
1233 conflict_index: Option<LogIndex>,
1234 conflict_term: Option<Term>,
1235 round: Round,
1236 ) {
1237 let reply = AppendEntriesReply {
1238 term: self.current_term,
1239 success,
1240 conflict_index,
1241 conflict_term,
1242 round,
1243 };
1244 self.outbox
1245 .push(Output::Reply(to, RaftRpcReply::AppendEntries(reply)));
1246 }
1247
1248 fn handle_append_reply(&mut self, from: NodeId, reply: &AppendEntriesReply) {
1249 if self.role != Role::Leader || reply.term != self.current_term {
1250 return;
1251 }
1252 if reply.success {
1253 let upper = self
1254 .sent_upper
1255 .get(&from)
1256 .copied()
1257 .unwrap_or(LogIndex::ZERO);
1258 let current = self
1259 .match_index
1260 .get(&from)
1261 .copied()
1262 .unwrap_or(LogIndex::ZERO);
1263 if upper > current {
1264 self.match_index.insert(from, upper);
1265 }
1266 self.next_index.insert(from, upper.next());
1267 self.last_ack_clock.insert(from, self.logical_clock);
1269 if self.config.reachability.detector == FailureDetectorKind::PhiAccrual {
1270 self.phi_liveness.record_heartbeat(from, self.logical_clock);
1271 }
1272 self.confirm_reads(from, reply.round);
1273 if reply.round >= self.lease_round {
1274 self.lease_acks.insert(from);
1275 self.maybe_extend_lease();
1276 }
1277 self.maybe_advance_commit();
1278 self.try_complete_reads();
1279 } else {
1280 let ni = if let Some(ci) = reply.conflict_index {
1281 LogIndex(ci.0.max(1))
1282 } else {
1283 let cur = self.next_index.get(&from).copied().unwrap_or(LogIndex(1)).0;
1284 LogIndex(cur.saturating_sub(1).max(1))
1285 };
1286 self.next_index.insert(from, ni);
1287 self.send_append(from);
1288 }
1289 }
1290
1291 fn broadcast_append(&mut self) {
1294 self.heartbeat_round = self.heartbeat_round.next();
1297 self.lease_round = self.heartbeat_round;
1301 self.lease_round_clock = self.logical_clock;
1302 self.lease_acks.clear();
1303 self.lease_acks.insert(self.id);
1304 self.maybe_extend_lease();
1305 for p in self.peers() {
1306 self.send_append(p);
1307 }
1308 }
1309
1310 fn send_append(&mut self, peer: NodeId) {
1311 let ni = self
1312 .next_index
1313 .get(&peer)
1314 .copied()
1315 .unwrap_or_else(|| self.log.last_index().next());
1316 if ni.0 <= self.log.snapshot_index().0 && self.snapshot.is_some() {
1319 self.send_snapshot(peer);
1320 return;
1321 }
1322 let prev_index = LogIndex(ni.0.saturating_sub(1));
1323 let prev_term = self.log.term_at(prev_index).unwrap_or(Term::ZERO);
1324 let entries = self.log.entries_from(ni).to_vec();
1325 let upper = LogIndex(prev_index.0 + entries.len() as u64);
1326 self.sent_upper.insert(peer, upper);
1327 let ae = AppendEntries {
1328 term: self.current_term,
1329 leader_id: self.id,
1330 prev_log: LogId::new(prev_term, prev_index),
1331 entries,
1332 leader_commit: self.commit_index,
1333 round: self.heartbeat_round,
1334 };
1335 self.outbox
1336 .push(Output::Send(peer, RaftRpc::AppendEntries(ae)));
1337 }
1338
1339 fn send_snapshot(&mut self, peer: NodeId) {
1340 let Some(snap) = self.snapshot.as_ref() else {
1341 return;
1342 };
1343 let is = InstallSnapshot {
1344 term: self.current_term,
1345 leader_id: self.id,
1346 last_included: LogId::new(snap.last_term, snap.last_index),
1347 last_config: snap.membership.clone(),
1348 offset: 0,
1349 data: snap.data.clone(),
1350 done: true,
1351 };
1352 self.sent_upper.insert(peer, snap.last_index);
1353 self.outbox
1354 .push(Output::Send(peer, RaftRpc::InstallSnapshot(is)));
1355 }
1356
1357 fn maybe_advance_commit(&mut self) {
1358 if self.role != Role::Leader {
1359 return;
1360 }
1361 let last = self.log.last_index().0;
1362 let mut new_commit = self.commit_index;
1363 for n in (self.commit_index.0 + 1)..=last {
1364 let idx = LogIndex(n);
1365 if self.log.term_at(idx) != Some(self.current_term) {
1367 continue;
1368 }
1369 let mut acked: BTreeSet<NodeId> = BTreeSet::new();
1370 acked.insert(self.id);
1371 for (peer, m) in &self.match_index {
1372 if m.0 >= n {
1373 acked.insert(*peer);
1374 }
1375 }
1376 if self.quorum_ok(&acked) {
1377 new_commit = idx;
1378 }
1379 }
1380 if new_commit > self.commit_index {
1381 self.commit_index = new_commit;
1382 self.apply_committed();
1383 self.maybe_finalize_membership();
1384 self.maybe_step_down_if_removed();
1385 self.try_complete_reads();
1386 }
1387 }
1388
1389 fn apply_committed(&mut self) {
1390 while self.last_applied < self.commit_index {
1391 let next = self.last_applied.next();
1392 match self.log.get(next).map(|e| &e.payload) {
1393 Some(EntryPayload::Command(c)) => {
1394 self.outbox.push(Output::Apply(Committed {
1395 index: next,
1396 command: c.clone(),
1397 }));
1398 }
1399 Some(EntryPayload::Catalog(command)) => {
1400 self.outbox.push(Output::CatalogApplied {
1401 index: next,
1402 command: command.clone(),
1403 });
1404 }
1405 Some(EntryPayload::SagaJournal(command)) => {
1406 self.outbox.push(Output::SagaJournalApplied {
1407 index: next,
1408 command: command.clone(),
1409 });
1410 }
1411 Some(EntryPayload::TwoPhasePrepare(command)) => {
1412 self.outbox.push(Output::TwoPhasePrepareApplied {
1413 index: next,
1414 command: command.clone(),
1415 });
1416 }
1417 Some(EntryPayload::TwoPhaseAbort(command)) => {
1418 self.outbox.push(Output::TwoPhaseAbortApplied {
1419 index: next,
1420 command: command.clone(),
1421 });
1422 }
1423 Some(EntryPayload::TwoPhaseJournal(command)) => {
1424 self.outbox.push(Output::TwoPhaseJournalApplied {
1425 index: next,
1426 command: command.clone(),
1427 });
1428 }
1429 Some(EntryPayload::QueueAutoscalePolicy(command)) => {
1430 self.outbox.push(Output::QueueAutoscalePolicyApplied {
1431 index: next,
1432 command: command.clone(),
1433 });
1434 }
1435 _ => {}
1436 }
1437 self.last_applied = next;
1438 }
1439 }
1440
1441 fn confirm_reads(&mut self, from: NodeId, round: Round) {
1446 for r in &mut self.pending_reads {
1447 if round >= r.round {
1448 r.acks.insert(from);
1449 }
1450 }
1451 }
1452
1453 fn try_complete_reads(&mut self) {
1458 if self.role != Role::Leader || self.pending_reads.is_empty() {
1459 return;
1460 }
1461 if self.log.term_at(self.commit_index) != Some(self.current_term) {
1462 return;
1463 }
1464 let conf = self.configuration();
1465 let applied = self.last_applied;
1466 let mut ready = Vec::new();
1467 self.pending_reads.retain(|r| {
1468 if conf.has_quorum(&r.acks) && applied >= r.index {
1469 ready.push((r.id, r.index));
1470 false
1471 } else {
1472 true
1473 }
1474 });
1475 for (id, index) in ready {
1476 self.outbox.push(Output::ReadReady { id, index });
1477 }
1478 }
1479
1480 fn fail_pending_reads(&mut self) {
1481 for r in std::mem::take(&mut self.pending_reads) {
1482 self.outbox.push(Output::ReadFailed { id: r.id });
1483 }
1484 }
1485
1486 fn lease_ticks(&self) -> u64 {
1493 self.config.election_timeout_min / 2
1494 }
1495
1496 fn maybe_extend_lease(&mut self) {
1500 if self.role != Role::Leader {
1501 return;
1502 }
1503 if self.configuration().has_quorum(&self.lease_acks) {
1504 let candidate = self.lease_round_clock.saturating_add(self.lease_ticks());
1505 if candidate > self.lease_expiry {
1506 self.lease_expiry = candidate;
1507 }
1508 }
1509 }
1510
1511 #[must_use]
1514 pub fn lease_valid(&self) -> bool {
1515 self.role == Role::Leader && self.logical_clock < self.lease_expiry
1516 }
1517
1518 pub fn lease_read(&self) -> Result<Option<LogIndex>, NotLeader> {
1528 if self.role != Role::Leader {
1529 return Err(NotLeader {
1530 leader: self.leader_id,
1531 });
1532 }
1533 let authoritative = self.log.term_at(self.commit_index) == Some(self.current_term);
1536 if self.logical_clock < self.lease_expiry && authoritative {
1537 Ok(Some(self.commit_index))
1538 } else {
1539 Ok(None)
1540 }
1541 }
1542
1543 #[must_use]
1557 pub fn reachable(&self, window: u64) -> Vec<NodeId> {
1558 let voters = self.configuration().voters();
1559 if self.role != Role::Leader {
1560 return voters;
1561 }
1562 let now = self.logical_clock;
1563 voters
1564 .into_iter()
1565 .filter(|&v| {
1566 v == self.id
1567 || self
1568 .last_ack_clock
1569 .get(&v)
1570 .is_some_and(|&acked| now.saturating_sub(acked) <= window)
1571 })
1572 .collect()
1573 }
1574
1575 #[must_use]
1579 pub fn reachable_now(&self) -> Vec<NodeId> {
1580 let voters = self.configuration().voters();
1581 if self.role != Role::Leader {
1582 return voters;
1583 }
1584 let now = self.logical_clock;
1585 voters
1586 .into_iter()
1587 .filter(|&v| match self.config.reachability.detector {
1588 FailureDetectorKind::AckWindow => v == self.id || self.ack_liveness.is_reachable(v),
1589 FailureDetectorKind::PhiAccrual => {
1590 v == self.id || self.phi_liveness.is_reachable(v, now)
1591 }
1592 })
1593 .collect()
1594 }
1595
1596 fn update_liveness(&mut self) {
1597 if self.role != Role::Leader {
1598 return;
1599 }
1600 let voters = self.configuration().voters();
1601 let now = self.logical_clock;
1602 match self.config.reachability.detector {
1603 FailureDetectorKind::AckWindow => {
1604 let window = self
1605 .config
1606 .reachability
1607 .window(self.config.election_timeout_max);
1608 let hysteresis = self
1609 .config
1610 .reachability
1611 .hysteresis(self.config.election_timeout_min);
1612 self.ack_liveness.update(
1613 now,
1614 self.id,
1615 &voters,
1616 &self.last_ack_clock,
1617 window,
1618 hysteresis,
1619 );
1620 }
1621 FailureDetectorKind::PhiAccrual => {}
1622 }
1623 }
1624
1625 fn maybe_finalize_membership(&mut self) {
1630 if self.role != Role::Leader {
1631 return;
1632 }
1633 let conf = self.configuration();
1634 let cfg_idx = self.config_index();
1635 if conf.is_joint() && cfg_idx.0 != 0 && cfg_idx <= self.commit_index {
1636 let final_config = Membership {
1637 voters: conf.voters(),
1638 voters_outgoing: Vec::new(),
1639 learners: conf.to_membership().learners,
1640 };
1641 self.log_append(self.current_term, EntryPayload::Membership(final_config));
1642 self.broadcast_append();
1643 }
1644 }
1645
1646 fn maybe_step_down_if_removed(&mut self) {
1648 if self.role != Role::Leader {
1649 return;
1650 }
1651 let conf = self.configuration();
1652 if !conf.is_joint() && self.config_index() <= self.commit_index && !conf.is_voter(self.id) {
1653 self.become_follower(self.current_term);
1654 self.leader_id = None;
1655 }
1656 }
1657
1658 fn handle_install_snapshot(&mut self, from: NodeId, is: InstallSnapshot) {
1661 if is.term < self.current_term {
1662 self.reply_snapshot(from);
1663 return;
1664 }
1665 if is.term > self.current_term {
1666 self.become_follower(is.term);
1667 } else if self.role != Role::Follower {
1668 self.set_role(Role::Follower);
1669 }
1670 self.leader_id = Some(is.leader_id);
1671 self.reset_election_timer();
1672
1673 let last = is.last_included;
1674 if last.index.0 <= self.log.snapshot_index().0 || last.index <= self.last_applied {
1676 self.reply_snapshot(from);
1677 return;
1678 }
1679
1680 self.log.install_snapshot(last.index, last.term);
1681 self.mark_log_dirty(LogIndex(last.index.0 + 1));
1686 self.snapshot = Some(StoredSnapshot {
1687 last_index: last.index,
1688 last_term: last.term,
1689 membership: is.last_config.clone(),
1690 data: is.data.clone(),
1691 });
1692 if self.commit_index < last.index {
1693 self.commit_index = last.index;
1694 }
1695 self.last_applied = last.index;
1696 self.outbox.push(Output::LoadSnapshot {
1697 index: last.index,
1698 data: is.data,
1699 });
1700 self.reply_snapshot(from);
1701 }
1702
1703 fn reply_snapshot(&mut self, to: NodeId) {
1704 let reply = InstallSnapshotReply {
1705 term: self.current_term,
1706 };
1707 self.outbox
1708 .push(Output::Reply(to, RaftRpcReply::InstallSnapshot(reply)));
1709 }
1710
1711 fn handle_snapshot_reply(&mut self, from: NodeId, reply: &InstallSnapshotReply) {
1712 if self.role != Role::Leader || reply.term != self.current_term {
1713 return;
1714 }
1715 let upper = self
1717 .sent_upper
1718 .get(&from)
1719 .copied()
1720 .unwrap_or(LogIndex::ZERO);
1721 let current = self
1722 .match_index
1723 .get(&from)
1724 .copied()
1725 .unwrap_or(LogIndex::ZERO);
1726 if upper > current {
1727 self.match_index.insert(from, upper);
1728 }
1729 self.next_index.insert(from, upper.next());
1730 self.maybe_advance_commit();
1731 }
1732}