1use crate::core::types::MsgId;
20
21use super::message::Msg;
22use super::msg_type::MsgType;
23
24pub const MAX_REPLICAS_PER_DC: usize = 3;
26
27#[derive(Debug)]
29struct GoodResponse {
30 msg: Box<Msg>,
31 checksum: u32,
32}
33
34#[derive(Copy, Clone, Debug, Eq, PartialEq)]
36pub enum QuorumOutcome {
37 Pending,
39 Achieved,
41 Mismatched,
44 Failed,
46}
47
48#[derive(Debug)]
53pub struct ResponseMgr {
54 is_read: bool,
55 max_responses: u8,
56 quorum_responses: u8,
57 error_responses: u8,
58 good: Vec<GoodResponse>,
59 err_rsp: Option<Box<Msg>>,
60 msg_id: MsgId,
61 msg_type: MsgType,
62 dc_name: Option<String>,
63}
64
65impl ResponseMgr {
66 #[must_use]
84 pub fn new(req: &Msg, max_responses: u8, dc_name: Option<String>) -> Self {
85 debug_assert!(max_responses >= 1);
86 debug_assert!(usize::from(max_responses) <= MAX_REPLICAS_PER_DC);
87 let max_responses = max_responses.max(1);
88 Self {
89 is_read: req.flags().is_read,
90 max_responses,
91 quorum_responses: max_responses / 2 + 1,
92 error_responses: 0,
93 good: Vec::with_capacity(MAX_REPLICAS_PER_DC),
94 err_rsp: None,
95 msg_id: req.id(),
96 msg_type: req.ty(),
97 dc_name,
98 }
99 }
100
101 #[must_use]
110 pub fn is_read(&self) -> bool {
111 self.is_read
112 }
113
114 #[must_use]
116 pub fn max_responses(&self) -> u8 {
117 self.max_responses
118 }
119
120 #[must_use]
123 pub fn quorum_responses(&self) -> u8 {
124 self.quorum_responses
125 }
126
127 #[must_use]
129 pub fn good_responses(&self) -> u8 {
130 u8::try_from(self.good.len()).unwrap_or(u8::MAX)
131 }
132
133 #[must_use]
135 pub fn error_responses(&self) -> u8 {
136 self.error_responses
137 }
138
139 #[must_use]
141 pub fn pending_responses(&self) -> u8 {
142 self.max_responses
143 .saturating_sub(self.good_responses())
144 .saturating_sub(self.error_responses)
145 }
146
147 #[must_use]
149 pub fn dc_name(&self) -> Option<&str> {
150 self.dc_name.as_deref()
151 }
152
153 #[must_use]
155 pub fn msg_id(&self) -> MsgId {
156 self.msg_id
157 }
158
159 #[must_use]
161 pub fn msg_type(&self) -> MsgType {
162 self.msg_type
163 }
164
165 pub fn submit_response(&mut self, rsp: Msg, checksum: u32) {
185 if rsp.flags().is_error {
186 self.error_responses = self.error_responses.saturating_add(1);
187 if self.err_rsp.is_none() {
188 self.err_rsp = Some(Box::new(rsp));
189 }
190 return;
191 }
192 if self.good.len() < MAX_REPLICAS_PER_DC {
193 self.good.push(GoodResponse {
194 msg: Box::new(rsp),
195 checksum,
196 });
197 }
198 }
199
200 #[must_use]
218 pub fn outcome(&self) -> QuorumOutcome {
219 let good = self.good_responses();
220 let pending = self.pending_responses();
221
222 if good < self.quorum_responses {
223 if pending + good < self.quorum_responses {
224 return QuorumOutcome::Failed;
225 }
226 return QuorumOutcome::Pending;
227 }
228
229 if self.is_quorum_achieved() {
230 return QuorumOutcome::Achieved;
231 }
232
233 if pending == 0 {
234 QuorumOutcome::Mismatched
235 } else {
236 QuorumOutcome::Pending
237 }
238 }
239
240 #[must_use]
253 pub fn is_done(&self) -> bool {
254 !matches!(self.outcome(), QuorumOutcome::Pending)
255 }
256
257 fn is_quorum_achieved(&self) -> bool {
258 let good = self.good_responses();
259 if self.quorum_responses == 1 && good == self.quorum_responses {
260 return true;
261 }
262 if good < self.quorum_responses {
263 return false;
264 }
265 let chk0 = self.good[0].checksum;
266 let chk1 = self.good[1].checksum;
267 if chk0 == chk1 {
268 return true;
269 }
270 if good < 3 {
271 return false;
272 }
273 let chk2 = self.good[2].checksum;
274 chk1 == chk2 || chk0 == chk2
275 }
276
277 #[must_use]
287 pub fn error_response(&self) -> Option<&Msg> {
288 self.err_rsp.as_deref()
289 }
290
291 pub fn good_iter(&self) -> impl Iterator<Item = (&Msg, u32)> {
294 self.good.iter().map(|g| (g.msg.as_ref(), g.checksum))
295 }
296
297 #[must_use]
320 pub fn pick_response(&self) -> Option<&Msg> {
321 if !matches!(self.outcome(), QuorumOutcome::Achieved) {
322 return None;
323 }
324 match self.good.len() {
325 1 | 2 => Some(self.good[0].msg.as_ref()),
326 3 => {
327 let c0 = self.good[0].checksum;
328 let c1 = self.good[1].checksum;
329 let c2 = self.good[2].checksum;
330 if c0 == c1 {
331 Some(self.good[0].msg.as_ref())
332 } else if c1 == c2 {
333 Some(self.good[1].msg.as_ref())
334 } else if c0 == c2 {
335 Some(self.good[0].msg.as_ref())
336 } else {
337 None
338 }
339 }
340 _ => None,
341 }
342 }
343}
344
345#[cfg(test)]
346mod tests {
347 use super::*;
348 use crate::msg::{Msg, MsgType};
349
350 fn req() -> Msg {
351 let mut m = Msg::new(1, MsgType::ReqRedisGet, true);
352 m.flags_mut().is_read = true;
353 m
354 }
355
356 fn rsp(id: u64, is_error: bool) -> Msg {
357 let mut m = Msg::new(id, MsgType::RspRedisStatus, false);
358 m.flags_mut().is_error = is_error;
359 m
360 }
361
362 #[test]
363 fn dc_one_one_good() {
364 let mut mgr = ResponseMgr::new(&req(), 1, Some("dc1".into()));
365 assert_eq!(mgr.outcome(), QuorumOutcome::Pending);
366 mgr.submit_response(rsp(2, false), 1);
367 assert_eq!(mgr.outcome(), QuorumOutcome::Achieved);
368 assert!(mgr.pick_response().is_some());
369 }
370
371 #[test]
372 fn dc_one_single_error() {
373 let mut mgr = ResponseMgr::new(&req(), 1, None);
374 mgr.submit_response(rsp(2, true), 0);
375 assert_eq!(mgr.outcome(), QuorumOutcome::Failed);
376 assert!(mgr.error_response().is_some());
377 assert!(mgr.pick_response().is_none());
378 }
379
380 #[test]
381 fn dc_quorum_two_matching() {
382 let mut mgr = ResponseMgr::new(&req(), 2, None);
383 assert_eq!(mgr.quorum_responses(), 2);
384 mgr.submit_response(rsp(2, false), 7);
385 assert_eq!(mgr.outcome(), QuorumOutcome::Pending);
386 mgr.submit_response(rsp(3, false), 7);
387 assert_eq!(mgr.outcome(), QuorumOutcome::Achieved);
388 }
389
390 #[test]
391 fn dc_quorum_two_mismatched_no_third_response() {
392 let mut mgr = ResponseMgr::new(&req(), 2, None);
393 mgr.submit_response(rsp(2, false), 7);
394 mgr.submit_response(rsp(3, false), 9);
395 assert_eq!(mgr.outcome(), QuorumOutcome::Mismatched);
396 }
397
398 #[test]
399 fn dc_quorum_one_good_one_error_fails() {
400 let mut mgr = ResponseMgr::new(&req(), 2, None);
401 mgr.submit_response(rsp(2, false), 7);
402 mgr.submit_response(rsp(3, true), 0);
403 assert_eq!(mgr.outcome(), QuorumOutcome::Failed);
404 }
405
406 #[test]
407 fn dc_safe_quorum_three_all_match() {
408 let mut mgr = ResponseMgr::new(&req(), 3, None);
409 assert_eq!(mgr.quorum_responses(), 2);
410 for id in 2..=4 {
411 mgr.submit_response(rsp(id, false), 11);
412 }
413 assert_eq!(mgr.outcome(), QuorumOutcome::Achieved);
414 assert_eq!(mgr.pick_response().unwrap().id(), 2);
415 }
416
417 #[test]
418 fn dc_safe_quorum_two_match_one_dissent() {
419 let mut mgr = ResponseMgr::new(&req(), 3, None);
420 mgr.submit_response(rsp(2, false), 5);
421 mgr.submit_response(rsp(3, false), 5);
422 assert_eq!(mgr.outcome(), QuorumOutcome::Achieved);
424 mgr.submit_response(rsp(4, false), 9);
425 assert_eq!(mgr.outcome(), QuorumOutcome::Achieved);
426 }
427
428 #[test]
429 fn dc_safe_quorum_three_disagreeing_mismatched() {
430 let mut mgr = ResponseMgr::new(&req(), 3, None);
431 mgr.submit_response(rsp(2, false), 1);
432 mgr.submit_response(rsp(3, false), 2);
433 assert_eq!(mgr.outcome(), QuorumOutcome::Pending);
435 mgr.submit_response(rsp(4, false), 3);
436 assert_eq!(mgr.outcome(), QuorumOutcome::Mismatched);
437 assert!(mgr.pick_response().is_none());
438 }
439
440 #[test]
441 fn dc_safe_quorum_two_errors_fail_immediately() {
442 let mut mgr = ResponseMgr::new(&req(), 3, None);
443 mgr.submit_response(rsp(2, true), 0);
444 mgr.submit_response(rsp(3, true), 0);
445 assert_eq!(mgr.outcome(), QuorumOutcome::Failed);
446 }
447
448 #[test]
449 fn dc_safe_quorum_three_errors_fail() {
450 let mut mgr = ResponseMgr::new(&req(), 3, None);
451 for id in 2..=4 {
452 mgr.submit_response(rsp(id, true), 0);
453 }
454 assert_eq!(mgr.outcome(), QuorumOutcome::Failed);
455 }
456
457 #[test]
458 fn dc_safe_quorum_one_dissent_picks_majority() {
459 let mut mgr = ResponseMgr::new(&req(), 3, None);
460 mgr.submit_response(rsp(2, false), 1);
461 mgr.submit_response(rsp(3, false), 2);
462 mgr.submit_response(rsp(4, false), 2);
463 assert_eq!(mgr.outcome(), QuorumOutcome::Achieved);
464 assert_eq!(mgr.pick_response().unwrap().id(), 3);
466 }
467
468 #[test]
469 fn excess_good_responses_are_dropped() {
470 let mut mgr = ResponseMgr::new(&req(), 3, None);
471 for id in 2..=10 {
472 mgr.submit_response(rsp(id, false), 1);
473 }
474 assert_eq!(mgr.good_responses() as usize, MAX_REPLICAS_PER_DC);
475 }
476
477 #[test]
478 fn accessors_report_construction_state() {
479 let mgr = ResponseMgr::new(&req(), 3, Some("dc-east".into()));
481 assert!(mgr.is_read());
482 assert_eq!(mgr.max_responses(), 3);
483 assert_eq!(mgr.error_responses(), 0);
484 assert_eq!(mgr.dc_name(), Some("dc-east"));
485 assert_eq!(mgr.msg_id(), 1);
486 assert_eq!(mgr.msg_type(), MsgType::ReqRedisGet);
487 assert!(mgr.error_response().is_none());
488 let anon = ResponseMgr::new(&req(), 1, None);
490 assert_eq!(anon.dc_name(), None);
491 }
492
493 #[test]
494 fn good_iter_yields_msg_and_checksum_pairs() {
495 let mut mgr = ResponseMgr::new(&req(), 3, None);
497 mgr.submit_response(rsp(2, false), 7);
498 mgr.submit_response(rsp(3, false), 9);
499 let pairs: Vec<(u64, u32)> = mgr.good_iter().map(|(m, c)| (m.id(), c)).collect();
500 assert_eq!(pairs, vec![(2, 7), (3, 9)]);
501 }
502
503 #[test]
504 fn pick_response_three_way_first_and_third_match() {
505 let mut mgr = ResponseMgr::new(&req(), 3, None);
508 mgr.submit_response(rsp(2, false), 5);
509 mgr.submit_response(rsp(3, false), 9);
510 mgr.submit_response(rsp(4, false), 5);
511 assert_eq!(mgr.outcome(), QuorumOutcome::Achieved);
512 assert_eq!(mgr.pick_response().unwrap().id(), 2);
513 }
514}