oximedia_distributed/
twopc.rs1use std::collections::{HashMap, HashSet};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum TwoPhaseState {
12 Idle,
14 Preparing,
16 Committing,
18 Aborting,
20 Committed,
22 Aborted,
24}
25
26pub struct TwoPhaseCoordinator {
31 state: TwoPhaseState,
33 participants: Vec<u64>,
35 votes: HashMap<u64, bool>,
37 acks: HashSet<u64>,
39}
40
41impl TwoPhaseCoordinator {
42 #[must_use]
44 pub fn new() -> Self {
45 Self {
46 state: TwoPhaseState::Idle,
47 participants: Vec::new(),
48 votes: HashMap::new(),
49 acks: HashSet::new(),
50 }
51 }
52
53 #[must_use]
55 pub fn state(&self) -> TwoPhaseState {
56 self.state
57 }
58
59 pub fn prepare(&mut self, participants: &[u64]) -> bool {
70 self.participants = participants.to_vec();
71 self.votes.clear();
72 self.acks.clear();
73
74 if participants.is_empty() {
75 self.state = TwoPhaseState::Preparing;
77 return true;
78 }
79
80 self.state = TwoPhaseState::Preparing;
81
82 for &p in participants {
84 self.votes.insert(p, true);
85 }
86
87 self.all_voted_yes()
88 }
89
90 pub fn record_vote(&mut self, id: u64, vote: bool) -> bool {
96 if self.state != TwoPhaseState::Preparing {
97 return false;
98 }
99 if !self.participants.contains(&id) {
100 return false;
101 }
102 self.votes.insert(id, vote);
103 true
104 }
105
106 pub fn commit(&mut self) -> bool {
114 if self.state != TwoPhaseState::Preparing || !self.all_voted_yes() {
115 return false;
116 }
117 self.state = TwoPhaseState::Committing;
118 for &p in &self.participants {
120 self.acks.insert(p);
121 }
122 self.state = TwoPhaseState::Committed;
123 true
124 }
125
126 pub fn abort(&mut self) -> bool {
131 match self.state {
132 TwoPhaseState::Idle | TwoPhaseState::Preparing | TwoPhaseState::Committing => {
133 self.state = TwoPhaseState::Aborting;
134 for &p in &self.participants {
136 self.acks.insert(p);
137 }
138 self.state = TwoPhaseState::Aborted;
139 true
140 }
141 _ => false,
142 }
143 }
144
145 pub fn reset(&mut self) {
147 self.state = TwoPhaseState::Idle;
148 self.participants.clear();
149 self.votes.clear();
150 self.acks.clear();
151 }
152
153 fn all_voted_yes(&self) -> bool {
155 if self.participants.is_empty() {
156 return true;
157 }
158 self.participants
159 .iter()
160 .all(|p| self.votes.get(p).copied().unwrap_or(false))
161 }
162
163 #[must_use]
165 pub fn yes_vote_count(&self) -> usize {
166 self.votes.values().filter(|&&v| v).count()
167 }
168
169 #[must_use]
171 pub fn no_vote_count(&self) -> usize {
172 self.votes.values().filter(|&&v| !v).count()
173 }
174}
175
176impl Default for TwoPhaseCoordinator {
177 fn default() -> Self {
178 Self::new()
179 }
180}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185
186 #[test]
187 fn test_new_state_is_idle() {
188 let coord = TwoPhaseCoordinator::new();
189 assert_eq!(coord.state(), TwoPhaseState::Idle);
190 }
191
192 #[test]
193 fn test_prepare_transitions_to_preparing() {
194 let mut coord = TwoPhaseCoordinator::new();
195 let result = coord.prepare(&[1, 2]);
196 assert!(result); assert_eq!(coord.state(), TwoPhaseState::Preparing);
198 }
199
200 #[test]
201 fn test_commit_after_prepare_succeeds() {
202 let mut coord = TwoPhaseCoordinator::new();
203 coord.prepare(&[1, 2, 3]);
204 let ok = coord.commit();
205 assert!(ok);
206 assert_eq!(coord.state(), TwoPhaseState::Committed);
207 }
208
209 #[test]
210 fn test_abort_after_prepare() {
211 let mut coord = TwoPhaseCoordinator::new();
212 coord.prepare(&[1, 2]);
213 let ok = coord.abort();
214 assert!(ok);
215 assert_eq!(coord.state(), TwoPhaseState::Aborted);
216 }
217
218 #[test]
219 fn test_no_vote_prevents_commit() {
220 let mut coord = TwoPhaseCoordinator::new();
221 coord.prepare(&[1, 2, 3]);
222 coord.record_vote(2, false); let ok = coord.commit();
224 assert!(!ok);
225 assert_eq!(coord.state(), TwoPhaseState::Preparing); }
227
228 #[test]
229 fn test_abort_after_no_vote() {
230 let mut coord = TwoPhaseCoordinator::new();
231 coord.prepare(&[1, 2]);
232 coord.record_vote(1, false);
233 coord.abort();
234 assert_eq!(coord.state(), TwoPhaseState::Aborted);
235 }
236
237 #[test]
238 fn test_abort_terminal_state_returns_false() {
239 let mut coord = TwoPhaseCoordinator::new();
240 coord.prepare(&[1]);
241 coord.commit();
242 assert_eq!(coord.state(), TwoPhaseState::Committed);
243 let ok = coord.abort();
244 assert!(!ok);
245 }
246
247 #[test]
248 fn test_reset_allows_reuse() {
249 let mut coord = TwoPhaseCoordinator::new();
250 coord.prepare(&[1]);
251 coord.commit();
252 coord.reset();
253 assert_eq!(coord.state(), TwoPhaseState::Idle);
254 coord.prepare(&[2, 3]);
255 assert_eq!(coord.state(), TwoPhaseState::Preparing);
256 coord.commit();
257 assert_eq!(coord.state(), TwoPhaseState::Committed);
258 }
259
260 #[test]
261 fn test_record_vote_unknown_participant() {
262 let mut coord = TwoPhaseCoordinator::new();
263 coord.prepare(&[1]);
264 let ok = coord.record_vote(99, true); assert!(!ok);
266 }
267
268 #[test]
269 fn test_record_vote_wrong_state() {
270 let mut coord = TwoPhaseCoordinator::new();
271 let ok = coord.record_vote(1, true); assert!(!ok);
273 }
274
275 #[test]
276 fn test_empty_participants_commit() {
277 let mut coord = TwoPhaseCoordinator::new();
278 coord.prepare(&[]);
279 coord.commit();
280 assert_eq!(coord.state(), TwoPhaseState::Committed);
281 }
282
283 #[test]
284 fn test_vote_counts() {
285 let mut coord = TwoPhaseCoordinator::new();
286 coord.prepare(&[1, 2, 3]);
287 coord.record_vote(3, false);
288 assert_eq!(coord.yes_vote_count(), 2);
289 assert_eq!(coord.no_vote_count(), 1);
290 }
291}