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