Skip to main content

noxu_rep/
commit_durability.rs

1//! Commit durability settings for replication.
2//!
3//! specifically `Durability.ReplicaAckPolicy`.
4
5use std::time::Duration;
6
7/// Policy for how many replicas must acknowledge a commit before it
8/// is considered durable.
9///
10///
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
12pub enum ReplicaAckPolicy {
13    /// All electable replicas must acknowledge the commit.
14    All,
15
16    /// A simple majority of electable nodes (including the master)
17    /// must acknowledge the commit.
18    #[default]
19    SimpleMajority,
20
21    /// No replica acknowledgment is required. The commit returns
22    /// as soon as the master has written it locally.
23    None,
24}
25
26impl ReplicaAckPolicy {
27    /// Returns the number of acknowledgments required for the given
28    /// number of electable nodes in the group.
29    ///
30    /// - `All`: requires `electable_count - 1` acks (all replicas).
31    /// - `SimpleMajority`: requires `(electable_count / 2 + 1) - 1` acks
32    ///   (majority minus the master itself).
33    /// - `None`: requires 0 acks.
34    pub fn required_acks(&self, electable_count: u32) -> u32 {
35        match self {
36            ReplicaAckPolicy::All => {
37                if electable_count == 0 {
38                    0
39                } else {
40                    electable_count - 1
41                }
42            }
43            ReplicaAckPolicy::SimpleMajority => {
44                if electable_count <= 1 {
45                    0
46                } else {
47                    // Majority of all electable nodes, minus the master.
48                    let majority = electable_count / 2 + 1;
49                    majority - 1
50                }
51            }
52            ReplicaAckPolicy::None => 0,
53        }
54    }
55}
56
57impl std::fmt::Display for ReplicaAckPolicy {
58    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
59        match self {
60            ReplicaAckPolicy::All => write!(f, "ALL"),
61            ReplicaAckPolicy::SimpleMajority => write!(f, "SIMPLE_MAJORITY"),
62            ReplicaAckPolicy::None => write!(f, "NONE"),
63        }
64    }
65}
66
67/// Commit durability settings for replicated transactions.
68///
69/// Combines the acknowledgment policy with a timeout for waiting
70/// for replica acks.
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
72pub struct CommitDurability {
73    /// The replica acknowledgment policy.
74    pub ack_policy: ReplicaAckPolicy,
75    /// How long to wait for replica acknowledgments before giving up.
76    pub ack_timeout: Duration,
77}
78
79impl CommitDurability {
80    /// Creates a new `CommitDurability` with the given policy and timeout.
81    pub fn new(ack_policy: ReplicaAckPolicy, ack_timeout: Duration) -> Self {
82        Self { ack_policy, ack_timeout }
83    }
84
85    /// Returns the number of acknowledgments required for the given
86    /// number of electable nodes.
87    pub fn required_acks(&self, electable_count: u32) -> u32 {
88        self.ack_policy.required_acks(electable_count)
89    }
90}
91
92impl Default for CommitDurability {
93    fn default() -> Self {
94        Self {
95            ack_policy: ReplicaAckPolicy::default(),
96            ack_timeout: Duration::from_secs(5),
97        }
98    }
99}
100
101impl std::fmt::Display for CommitDurability {
102    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
103        write!(
104            f,
105            "CommitDurability(ack_policy={}, ack_timeout={:?})",
106            self.ack_policy, self.ack_timeout
107        )
108    }
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114
115    // --- ReplicaAckPolicy tests ---
116
117    #[test]
118    fn test_all_required_acks() {
119        assert_eq!(ReplicaAckPolicy::All.required_acks(0), 0);
120        assert_eq!(ReplicaAckPolicy::All.required_acks(1), 0);
121        assert_eq!(ReplicaAckPolicy::All.required_acks(2), 1);
122        assert_eq!(ReplicaAckPolicy::All.required_acks(3), 2);
123        assert_eq!(ReplicaAckPolicy::All.required_acks(5), 4);
124    }
125
126    #[test]
127    fn test_simple_majority_required_acks() {
128        assert_eq!(ReplicaAckPolicy::SimpleMajority.required_acks(0), 0);
129        assert_eq!(ReplicaAckPolicy::SimpleMajority.required_acks(1), 0);
130        // 2 electable: majority=2, minus master=1
131        assert_eq!(ReplicaAckPolicy::SimpleMajority.required_acks(2), 1);
132        // 3 electable: majority=2, minus master=1
133        assert_eq!(ReplicaAckPolicy::SimpleMajority.required_acks(3), 1);
134        // 4 electable: majority=3, minus master=2
135        assert_eq!(ReplicaAckPolicy::SimpleMajority.required_acks(4), 2);
136        // 5 electable: majority=3, minus master=2
137        assert_eq!(ReplicaAckPolicy::SimpleMajority.required_acks(5), 2);
138    }
139
140    #[test]
141    fn test_none_required_acks() {
142        assert_eq!(ReplicaAckPolicy::None.required_acks(0), 0);
143        assert_eq!(ReplicaAckPolicy::None.required_acks(1), 0);
144        assert_eq!(ReplicaAckPolicy::None.required_acks(5), 0);
145        assert_eq!(ReplicaAckPolicy::None.required_acks(100), 0);
146    }
147
148    #[test]
149    fn test_ack_policy_display() {
150        assert_eq!(ReplicaAckPolicy::All.to_string(), "ALL");
151        assert_eq!(
152            ReplicaAckPolicy::SimpleMajority.to_string(),
153            "SIMPLE_MAJORITY"
154        );
155        assert_eq!(ReplicaAckPolicy::None.to_string(), "NONE");
156    }
157
158    #[test]
159    fn test_ack_policy_default() {
160        assert_eq!(
161            ReplicaAckPolicy::default(),
162            ReplicaAckPolicy::SimpleMajority
163        );
164    }
165
166    #[test]
167    fn test_ack_policy_clone_copy_eq_hash() {
168        use std::collections::HashSet;
169        let p = ReplicaAckPolicy::All;
170        let p2 = p;
171        assert_eq!(p, p2);
172        let mut set = HashSet::new();
173        set.insert(ReplicaAckPolicy::All);
174        set.insert(ReplicaAckPolicy::SimpleMajority);
175        set.insert(ReplicaAckPolicy::None);
176        assert_eq!(set.len(), 3);
177    }
178
179    // --- CommitDurability tests ---
180
181    #[test]
182    fn test_commit_durability_new() {
183        let cd = CommitDurability::new(
184            ReplicaAckPolicy::All,
185            Duration::from_secs(10),
186        );
187        assert_eq!(cd.ack_policy, ReplicaAckPolicy::All);
188        assert_eq!(cd.ack_timeout, Duration::from_secs(10));
189    }
190
191    #[test]
192    fn test_commit_durability_required_acks() {
193        let cd = CommitDurability::new(
194            ReplicaAckPolicy::SimpleMajority,
195            Duration::from_secs(5),
196        );
197        assert_eq!(cd.required_acks(3), 1);
198        assert_eq!(cd.required_acks(5), 2);
199    }
200
201    #[test]
202    fn test_commit_durability_default() {
203        let cd = CommitDurability::default();
204        assert_eq!(cd.ack_policy, ReplicaAckPolicy::SimpleMajority);
205        assert_eq!(cd.ack_timeout, Duration::from_secs(5));
206    }
207
208    #[test]
209    fn test_commit_durability_display() {
210        let cd = CommitDurability::default();
211        let s = cd.to_string();
212        assert!(s.contains("SIMPLE_MAJORITY"));
213        assert!(s.contains("5s"));
214    }
215
216    #[test]
217    fn test_commit_durability_clone_eq() {
218        let cd = CommitDurability::default();
219        let cloned = cd;
220        assert_eq!(cd, cloned);
221    }
222}