1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
//! The replica's volatile role within the current epoch and its per-role state.
use std::collections::{BTreeMap, BTreeSet};
use crate::kraft::types::{NodeId, SimInstant};
/// Per-follower replication progress tracked by a leader (for HWM).
#[derive(Debug, Clone, Default)]
pub struct ReplicaProgress {
/// Highest offset the follower has acknowledged fetching (its fetch offset).
pub fetch_offset: i64,
}
#[derive(Debug, Clone)]
pub enum Role {
/// Knows the epoch, no leader yet. May hold a non-binding pre-vote grant.
Unattached { election_deadline: SimInstant },
/// Cast a binding vote this epoch; waiting for a leader.
Voted { election_deadline: SimInstant },
/// Has a leader for the epoch; fetching from it.
Follower {
leader_id: NodeId,
fetch_deadline: SimInstant,
},
/// KIP-996 pre-vote candidate gathering non-binding grants.
Prospective {
granted: BTreeSet<NodeId>,
election_deadline: SimInstant,
},
/// Real candidacy (epoch bumped, self-voted).
Candidate {
granted: BTreeSet<NodeId>,
election_deadline: SimInstant,
},
/// Won the election; tracks follower progress for HWM.
Leader {
replicas: BTreeMap<NodeId, ReplicaProgress>,
high_watermark: i64,
/// Log end offset at the moment of promotion, i.e. where this leader's
/// `LeaderChange` / first current-epoch record sits. The HWM may only
/// advance past this offset, enforcing Raft Fig.8 leader completeness:
/// a current-epoch entry must be majority-replicated before commit.
epoch_start_offset: i64,
},
/// Stepping down; emitting `EndQuorumEpoch`.
// `Resigned` (and `Action::SendEndQuorumEpoch`) are produced by
// transport-facing paths; the core also receives `EndQuorumEpoch`, so there
// is intentionally no transition into this variant yet.
Resigned,
/// Not in the voter set; only ever fetches.
Observer {
leader_id: Option<NodeId>,
fetch_deadline: SimInstant,
},
}
impl Default for Role {
fn default() -> Self {
Role::Unattached {
election_deadline: SimInstant(0),
}
}
}
impl Role {
#[must_use]
pub fn is_leader(&self) -> bool {
matches!(self, Role::Leader { .. })
}
#[must_use]
pub fn name(&self) -> &'static str {
match self {
Role::Unattached { .. } => "Unattached",
Role::Voted { .. } => "Voted",
Role::Follower { .. } => "Follower",
Role::Prospective { .. } => "Prospective",
Role::Candidate { .. } => "Candidate",
Role::Leader { .. } => "Leader",
Role::Resigned => "Resigned",
Role::Observer { .. } => "Observer",
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use assert2::assert;
#[test]
fn role_defaults_to_unattached() {
let r = Role::default();
assert!(matches!(r, Role::Unattached { .. }));
assert!(!r.is_leader());
}
}