1use {
4 crate::{
5 Action, Epoch, Event, LogEnd, NodeId, QuorumStateMachine, Role, SimInstant, SuccessorRank,
6 },
7 std::collections::{BTreeSet, VecDeque},
8};
9
10pub trait PartitionMessage<Node> {
13 fn touches(&self, node: Node) -> bool;
14}
15
16pub fn partition_node<Node, Message>(
18 partitioned: &mut BTreeSet<Node>,
19 queue: &mut VecDeque<Message>,
20 node: Node,
21) where
22 Node: Copy + Ord,
23 Message: PartitionMessage<Node>,
24{
25 partitioned.insert(node);
26 queue.retain(|message| !message.touches(node));
27}
28
29pub struct StableFingerprint<F> {
31 previous: F,
32 unchanged: u32,
33}
34
35impl<F: PartialEq> StableFingerprint<F> {
36 #[must_use]
37 pub fn new(previous: F) -> Self {
38 Self {
39 previous,
40 unchanged: 0,
41 }
42 }
43
44 pub fn observe(&mut self, fingerprint: F) -> bool {
46 if fingerprint == self.previous {
47 self.unchanged = self.unchanged.saturating_add(1);
48 } else {
49 self.unchanged = 0;
50 self.previous = fingerprint;
51 }
52 self.unchanged >= 2
53 }
54}
55
56#[must_use]
58pub fn voter_set(ids: &[NodeId]) -> krabka_voters::VoterSet {
59 krabka_voters::VoterSet::from_voters(ids.iter().map(|&id| krabka_voters::Voter {
60 id,
61 directory_id: uuid::Uuid::nil(),
62 endpoints: Vec::new(),
63 kraft_version: krabka_voters::KRaftVersionRange::default(),
64 }))
65}
66
67#[must_use]
70pub fn action_messages(
71 source: NodeId,
72 action: &Action,
73 voters: &[NodeId],
74 peers: &[NodeId],
75 log_end: LogEnd,
76) -> Option<Vec<(NodeId, Event)>> {
77 let recipients = match action {
78 Action::SendVoteRequest { .. } => voters,
79 Action::SendBeginQuorumEpoch { .. } | Action::SendEndQuorumEpoch { .. } => peers,
80 Action::ReplyVote { to, epoch, granted } => {
81 return Some(vec![(*to, Event::ReceiveVoteResponse {
82 from: source,
83 epoch: *epoch,
84 vote_granted: *granted,
85 })]);
86 }
87 _ => return None,
88 };
89 Some(
90 recipients
91 .iter()
92 .copied()
93 .filter(|peer| *peer != source)
94 .map(|peer| {
95 let event = match action {
96 Action::SendVoteRequest { epoch, pre_vote } => Event::ReceiveVoteRequest {
97 from: source,
98 cluster_id: None,
99 voter_id: peer,
100 voter_directory_id: uuid::Uuid::nil(),
101 candidate_epoch: *epoch,
102 candidate: source,
103 candidate_directory_id: uuid::Uuid::nil(),
104 candidate_log_end: log_end,
105 pre_vote: *pre_vote,
106 },
107 Action::SendBeginQuorumEpoch { epoch } => Event::ReceiveBeginQuorumEpoch {
108 leader_id: source,
109 leader_epoch: *epoch,
110 },
111 Action::SendEndQuorumEpoch { epoch, .. } => Event::ReceiveEndQuorumEpoch {
112 leader_id: source,
113 leader_epoch: *epoch,
114 successor_rank: SuccessorRank::default(),
115 },
116 _ => unreachable!("message action selected above"),
117 };
118 (peer, event)
119 })
120 .collect(),
121 )
122}
123
124#[must_use]
126pub fn fetch_response(
127 leader: NodeId,
128 epoch: Epoch,
129 actions: &[Action],
130 leader_end: i64,
131 follower_end: i64,
132) -> Option<Event> {
133 let diverging = actions.iter().find_map(|action| match action {
134 Action::ReplyDivergingEpoch(point) => Some(*point),
135 _ => None,
136 });
137 (diverging.is_some() || follower_end < leader_end).then_some(Event::ReceiveFetchResponse {
138 leader_id: leader,
139 leader_epoch: epoch,
140 diverging,
141 })
142}
143
144#[derive(Debug, Clone, Copy, PartialEq, Eq)]
146pub enum SimulationTimer {
147 Election,
148 Fetch,
149 Heartbeat,
150 CheckQuorum,
151}
152
153#[must_use]
155pub fn earliest_timer(
156 nodes: impl Iterator<Item = (NodeId, [Option<SimInstant>; 4])>,
157) -> Option<(SimInstant, NodeId, SimulationTimer)> {
158 let mut best = None;
159 for (id, deadlines) in nodes {
160 for (deadline, kind) in deadlines.into_iter().zip([
161 SimulationTimer::Election,
162 SimulationTimer::Fetch,
163 SimulationTimer::Heartbeat,
164 SimulationTimer::CheckQuorum,
165 ]) {
166 if let Some(deadline) = deadline
167 && best.is_none_or(|(current, _, _)| deadline < current)
168 {
169 best = Some((deadline, id, kind));
170 }
171 }
172 }
173 best
174}
175
176pub fn clear_timer(
178 kind: SimulationTimer,
179 election: &mut Option<SimInstant>,
180 fetch: &mut Option<SimInstant>,
181 heartbeat: &mut Option<SimInstant>,
182 quorum: &mut Option<SimInstant>,
183) {
184 *match kind {
185 SimulationTimer::Election => election,
186 SimulationTimer::Fetch => fetch,
187 SimulationTimer::Heartbeat => heartbeat,
188 SimulationTimer::CheckQuorum => quorum,
189 } = None;
190}
191
192pub fn reconcile_timers(
194 role: &Role,
195 election: &mut Option<SimInstant>,
196 fetch: &mut Option<SimInstant>,
197 heartbeat: &mut Option<SimInstant>,
198 quorum: &mut Option<SimInstant>,
199 heartbeat_deadline: SimInstant,
200) {
201 match role {
202 Role::Leader { .. } => {
203 *election = None;
204 *fetch = None;
205 heartbeat.get_or_insert(heartbeat_deadline);
206 }
207 Role::Follower { .. } | Role::Observer { .. } => {
208 *election = None;
209 *heartbeat = None;
210 *quorum = None;
211 }
212 _ => {
213 *fetch = None;
214 *heartbeat = None;
215 *quorum = None;
216 }
217 }
218}
219
220#[must_use]
222pub fn reachable_leader(
223 role: &Role,
224 follower: NodeId,
225 partitioned: &BTreeSet<NodeId>,
226 is_leader: impl FnOnce(NodeId) -> bool,
227) -> Option<NodeId> {
228 let leader = match role {
229 Role::Follower { leader_id, .. }
230 | Role::Observer {
231 leader_id: Some(leader_id),
232 ..
233 } => *leader_id,
234 _ => return None,
235 };
236 (!partitioned.contains(&follower) && !partitioned.contains(&leader) && is_leader(leader))
237 .then_some(leader)
238}
239
240#[derive(Debug, Clone, PartialEq, Eq)]
242pub struct NodeFingerprint {
243 pub node: NodeId,
244 pub role: &'static str,
245 pub epoch: Epoch,
246 pub records: usize,
247 pub high_watermark: i64,
248}
249
250#[must_use]
252pub fn fingerprint(
253 id: NodeId,
254 machine: &QuorumStateMachine,
255 records: usize,
256 hwm: i64,
257) -> NodeFingerprint {
258 let hwm = match machine.role() {
259 Role::Leader { high_watermark, .. } => *high_watermark,
260 _ => hwm,
261 };
262 NodeFingerprint {
263 node: id,
264 role: machine.role().name(),
265 epoch: machine.quorum_state().leader_epoch,
266 records,
267 high_watermark: hwm,
268 }
269}
270
271pub fn append_epochs(epochs: &mut Vec<Epoch>, epoch: Epoch, count: usize) {
273 for _ in 0..count {
274 epochs.push(epoch);
275 }
276}
277
278pub fn truncate_epochs(epochs: &mut Vec<Epoch>, offset: i64) {
280 epochs.truncate(usize::try_from(offset.max(0)).unwrap_or(usize::MAX));
281}
282
283#[must_use]
288pub fn epochs_to_replicate(follower_len: usize, leader_epochs: &[Epoch]) -> &[Epoch] {
289 leader_epochs.get(follower_len..).unwrap_or_default()
290}