Skip to main content

quorum_set/quorum/
quorum_intersection_impl.rs

1use std::collections::BTreeSet;
2
3use crate::quorum::QuorumIntersection;
4
5/// Two joint configs are treated as having quorum intersection when some config of one intersects
6/// some config of the other: every quorum of a joint is also a quorum of each of its configs, so
7/// every quorum of one joint then intersects every quorum of the other. This is sufficient but not
8/// necessary for that property, so this check answers `None` for some pairs of joints whose
9/// quorums do in fact all intersect.
10impl<ID> QuorumIntersection<Vec<BTreeSet<ID>>> for Vec<BTreeSet<ID>>
11where ID: Ord + Clone
12{
13    /// Return the exact answer of the flat [`BTreeSet`] check when each joint holds one config.
14    /// For other joints, return `Some(true)` when some config of `self` intersects some config of
15    /// `other`.
16    ///
17    /// When either joint is empty, return `Some(true)`: an empty joint has no quorum, so no pair
18    /// of quorums can be disjoint.
19    ///
20    /// Return `None` for every other pair: two configs that do not intersect prove nothing about
21    /// two joints that contain them.
22    ///
23    /// Read more about extended membership change in OpenRaft:
24    /// <https://docs.rs/openraft/latest/openraft/docs/data/extended_membership/index.html>
25    fn intersects_with(&self, other: &Vec<BTreeSet<ID>>) -> Option<bool> {
26        let either_is_empty = self.is_empty() || other.is_empty();
27        if either_is_empty {
28            return Some(true);
29        }
30
31        let both_uniform = self.len() == 1 && other.len() == 1;
32        if both_uniform {
33            return self[0].intersects_with(&other[0]);
34        }
35
36        for a in self {
37            for b in other {
38                let intersects = a.intersects_with(b);
39                if intersects == Some(true) {
40                    return Some(true);
41                }
42            }
43        }
44        None
45    }
46}
47
48/// Exact quorum intersection for two flat majority configs.
49///
50/// A smallest majority of `self` first takes the IDs that `other` lacks, and then takes shared IDs
51/// only as needed. A smallest majority of `other` does the same. Two disjoint majorities exist
52/// exactly when the shared IDs are enough for both, so the check is exact and runs in O(n).
53impl<ID> QuorumIntersection<BTreeSet<ID>> for BTreeSet<ID>
54where ID: Ord + Clone
55{
56    /// Return `Some(true)` when every majority of `self` intersects every majority of `other`,
57    /// and `Some(false)` otherwise. This check never returns `None`.
58    ///
59    /// An empty [`BTreeSet`] has no quorum, so it has quorum intersection with every config.
60    fn intersects_with(&self, other: &BTreeSet<ID>) -> Option<bool> {
61        let majority_self = self.len() / 2 + 1;
62        let majority_other = other.len() / 2 + 1;
63
64        let only_self = self.difference(other).count();
65        let only_other = other.difference(self).count();
66        let shared = self.intersection(other).count();
67
68        let need_self = majority_self.saturating_sub(only_self);
69        let need_other = majority_other.saturating_sub(only_other);
70
71        Some(need_self + need_other > shared)
72    }
73}
74
75/// A joint config and a flat config are treated as having quorum intersection when some config of
76/// the joint intersects the flat config: every quorum of the joint is also a quorum of that config.
77/// This is sufficient but not necessary for that property, so this check answers `None` for some
78/// pairs whose quorums do in fact all intersect.
79impl<ID> QuorumIntersection<BTreeSet<ID>> for Vec<BTreeSet<ID>>
80where ID: Ord + Clone
81{
82    /// Return the exact answer of the flat [`BTreeSet`] check when `self` holds one config. For
83    /// other joints, return `Some(true)` when some config of `self` intersects `other`.
84    ///
85    /// Return `Some(true)` when `self` is empty: an empty joint has no quorum, so no pair of
86    /// quorums can be disjoint.
87    ///
88    /// Return `None` for every other pair.
89    fn intersects_with(&self, other: &BTreeSet<ID>) -> Option<bool> {
90        if self.is_empty() {
91            return Some(true);
92        }
93
94        let uniform = self.len() == 1;
95        if uniform {
96            return self[0].intersects_with(other);
97        }
98
99        for config in self {
100            let intersects = config.intersects_with(other);
101            if intersects == Some(true) {
102                return Some(true);
103            }
104        }
105        None
106    }
107}
108
109/// Quorum intersection is symmetric, so this check returns the answer of the joint config's check.
110impl<ID> QuorumIntersection<Vec<BTreeSet<ID>>> for BTreeSet<ID>
111where ID: Ord + Clone
112{
113    fn intersects_with(&self, other: &Vec<BTreeSet<ID>>) -> Option<bool> {
114        other.intersects_with(self)
115    }
116}