1use {
12 super::QuorumStateMachine,
13 crate::{action::Action, types::SimInstant},
14};
15
16impl QuorumStateMachine {
17 pub fn resign_for_shutdown(&mut self, now: SimInstant) -> Vec<Action> {
24 if self.role.is_leader() && !self.graceful_shutdown_complete() {
25 self.transition_to_resigned(now)
26 } else {
27 Vec::new()
28 }
29 }
30
31 #[must_use]
39 pub fn graceful_shutdown_complete(&self) -> bool {
40 !self.is_voter()
41 || self.state.voters.len() == 1
42 || self.known_leader().is_some_and(|leader| leader != self.me)
43 }
44}
45
46#[cfg(test)]
47mod tests {
48 use {
49 super::*,
50 crate::{
51 action::TimerKind,
52 core::{
53 election_jitter_ms,
54 test_support::{FakeLog, machine, three_voter_machine, win_election},
55 },
56 event::Event,
57 role::Role,
58 types::NodeId,
59 },
60 assert2::check,
61 };
62
63 fn leader_after_fetches(
66 log: &FakeLog,
67 fetched_by_2: i64,
68 fetched_by_3: i64,
69 ) -> QuorumStateMachine {
70 let mut m = three_voter_machine();
71 win_election(&mut m, log, &[NodeId(2), NodeId(3)], SimInstant(2000));
72 for (from, fetch_offset) in [(NodeId(2), fetched_by_2), (NodeId(3), fetched_by_3)] {
73 m.on_event(
74 Event::ReceiveFetch {
75 from,
76 fetch_epoch: 1,
77 fetch_offset,
78 },
79 log,
80 SimInstant(2100),
81 );
82 }
83 m
84 }
85
86 #[yare::parameterized(
90 third_voter_ahead = { 2, 5, vec![NodeId(3), NodeId(2)] },
91 second_voter_ahead = { 5, 2, vec![NodeId(2), NodeId(3)] },
92 tied = { 4, 4, vec![NodeId(2), NodeId(3)] },
93 )]
94 fn a_leader_resigns_to_its_most_caught_up_followers(
95 fetched_by_2: i64,
96 fetched_by_3: i64,
97 preferred_successors: Vec<NodeId>,
98 ) {
99 let log = FakeLog::new(5, 1);
100 let mut m = leader_after_fetches(&log, fetched_by_2, fetched_by_3);
101
102 let actions = m.resign_for_shutdown(SimInstant(3000));
103
104 check!(
105 actions
106 == vec![
107 Action::SendEndQuorumEpoch {
108 epoch: 1,
109 preferred_successors,
110 },
111 Action::PersistQuorumState,
112 Action::TransitionedTo("Resigned"),
113 Action::ResetTimer {
114 kind: TimerKind::Election,
115 deadline: SimInstant(3000 + 1000 + election_jitter_ms(NodeId(1), 1, 1000)),
116 },
117 ]
118 );
119 check!(
120 (
121 m.role().clone(),
122 m.quorum_state().leader_id,
123 m.quorum_state().leader_epoch
124 ) == (Role::Resigned, None, 1)
125 );
126 check!(!m.graceful_shutdown_complete());
127 }
128
129 #[test]
132 fn only_a_leader_resigns_for_shutdown() {
133 let log = FakeLog::new(5, 1);
134 let mut resigned = leader_after_fetches(&log, 5, 5);
135 resigned.resign_for_shutdown(SimInstant(3000));
136 let mut sole = machine(NodeId(1), &[NodeId(1)]);
137 win_election(&mut sole, &log, &[], SimInstant(2000));
138 let cases = [
139 ("unattached voter", three_voter_machine()),
140 (
141 "observer",
142 machine(NodeId(4), &[NodeId(1), NodeId(2), NodeId(3)]),
143 ),
144 ("resigned leader", resigned),
145 ("sole voter leading", sole),
146 ];
147 for (label, mut m) in cases {
148 let before = m.clone();
149 let actions = m.resign_for_shutdown(SimInstant(4000));
150 check!((label, actions, m == before) == (label, Vec::new(), true));
151 }
152 }
153
154 #[test]
158 fn a_shutdown_completes_once_nothing_is_left_to_hand_over() {
159 let log = FakeLog::new(5, 1);
160 let mut follower = three_voter_machine();
161 follower.on_event(
162 Event::ReceiveBeginQuorumEpoch {
163 leader_id: NodeId(2),
164 leader_epoch: 4,
165 },
166 &log,
167 SimInstant(10),
168 );
169 let mut prospective = follower.clone();
170 prospective.on_event(Event::FetchTimeout, &log, SimInstant(20));
171 let mut resigned = leader_after_fetches(&log, 5, 5);
172 resigned.resign_for_shutdown(SimInstant(3000));
173 let mut successor_known = resigned.clone();
174 successor_known.on_event(
175 Event::ReceiveBeginQuorumEpoch {
176 leader_id: NodeId(3),
177 leader_epoch: 2,
178 },
179 &log,
180 SimInstant(3100),
181 );
182 let cases = [
183 (
184 "observer",
185 machine(NodeId(4), &[NodeId(1), NodeId(2), NodeId(3)]),
186 true,
187 ),
188 ("only voter", machine(NodeId(1), &[NodeId(1)]), true),
189 ("follower", follower, true),
190 ("prospective keeping its leader", prospective, true),
191 ("successor known", successor_known, true),
192 ("unattached voter", three_voter_machine(), false),
193 ("leader", leader_after_fetches(&log, 5, 5), false),
194 ("resigned leader", resigned, false),
195 ];
196 for (label, m, complete) in cases {
197 check!((label, m.graceful_shutdown_complete()) == (label, complete));
198 }
199 }
200}