spacedb_consistency/
strong.rs1use std::collections::HashMap;
22
23use crate::outcome::{Outcome, UnavailableReason};
24use crate::tier::Tier;
25
26#[derive(Clone, Copy, Debug, PartialEq, Eq)]
28pub enum RejectReason {
29 AlreadyClaimed,
31 Exhausted,
33 VersionConflict,
35}
36
37#[derive(Clone, Debug, PartialEq, Eq)]
41pub enum StrongResult {
42 Committed,
43 Rejected(RejectReason),
44 Unavailable(UnavailableReason),
45}
46
47impl StrongResult {
48 pub fn is_committed(&self) -> bool {
49 matches!(self, StrongResult::Committed)
50 }
51
52 pub fn is_linearizable(&self) -> bool {
54 !matches!(self, StrongResult::Unavailable(_))
55 }
56
57 pub fn consistency(&self) -> Outcome {
60 match self {
61 StrongResult::Committed | StrongResult::Rejected(_) => Outcome::Committed(Tier::Strong),
62 StrongResult::Unavailable(reason) => Outcome::Unavailable(*reason),
63 }
64 }
65}
66
67#[derive(Clone, Debug)]
68struct Member {
69 id: String,
70 online: bool,
71 store: HashMap<String, (Vec<u8>, u64)>,
73}
74
75pub struct QuorumGroup {
77 members: Vec<Member>,
78}
79
80impl QuorumGroup {
81 pub fn new<I, S>(member_ids: I) -> Self
83 where
84 I: IntoIterator<Item = S>,
85 S: Into<String>,
86 {
87 let members = member_ids
88 .into_iter()
89 .map(|id| Member {
90 id: id.into(),
91 online: true,
92 store: HashMap::new(),
93 })
94 .collect();
95 Self { members }
96 }
97
98 pub fn size(&self) -> usize {
99 self.members.len()
100 }
101
102 pub fn majority(&self) -> usize {
104 self.members.len() / 2 + 1
105 }
106
107 pub fn online_count(&self) -> usize {
108 self.members.iter().filter(|m| m.online).count()
109 }
110
111 pub fn partition(&mut self, member_id: &str) -> bool {
113 self.set_online(member_id, false)
114 }
115
116 pub fn heal(&mut self, member_id: &str) -> bool {
118 self.set_online(member_id, true)
119 }
120
121 fn set_online(&mut self, member_id: &str, online: bool) -> bool {
122 match self.members.iter_mut().find(|m| m.id == member_id) {
123 Some(m) => {
124 m.online = online;
125 true
126 }
127 None => false,
128 }
129 }
130
131 fn online_indices(&self) -> Vec<usize> {
132 (0..self.members.len())
133 .filter(|&i| self.members[i].online)
134 .collect()
135 }
136
137 pub fn read(&self, key: &str) -> Result<(Option<Vec<u8>>, u64), UnavailableReason> {
141 let online = self.online_indices();
142 if online.len() < self.majority() {
143 return Err(UnavailableReason::QuorumUnreachable);
144 }
145 let best = online
146 .iter()
147 .filter_map(|&i| self.members[i].store.get(key))
148 .max_by_key(|(_, version)| *version);
149 Ok(match best {
150 Some((value, version)) => (Some(value.clone()), *version),
151 None => (None, 0),
152 })
153 }
154
155 pub fn cas(&mut self, key: &str, expected_version: u64, new_value: Vec<u8>) -> StrongResult {
160 let online = self.online_indices();
161 if online.len() < self.majority() {
162 return StrongResult::Unavailable(UnavailableReason::QuorumUnreachable);
163 }
164 let current = online
165 .iter()
166 .filter_map(|&i| self.members[i].store.get(key).map(|(_, v)| *v))
167 .max()
168 .unwrap_or(0);
169 if current != expected_version {
170 return StrongResult::Rejected(RejectReason::VersionConflict);
171 }
172 let new_version = expected_version + 1;
173 for &i in &online {
174 self.members[i]
175 .store
176 .insert(key.to_string(), (new_value.clone(), new_version));
177 }
178 StrongResult::Committed
179 }
180
181 pub fn claim_unique(&mut self, key: &str, owner: &[u8]) -> StrongResult {
184 let (current, version) = match self.read(key) {
185 Ok(read) => read,
186 Err(reason) => return StrongResult::Unavailable(reason),
187 };
188 if current.is_some() {
189 return StrongResult::Rejected(RejectReason::AlreadyClaimed);
190 }
191 self.cas(key, version, owner.to_vec())
192 }
193
194 pub fn init_seats(&mut self, key: &str, count: u64) -> StrongResult {
196 let (_, version) = match self.read(key) {
197 Ok(read) => read,
198 Err(reason) => return StrongResult::Unavailable(reason),
199 };
200 self.cas(key, version, count.to_le_bytes().to_vec())
201 }
202
203 pub fn acquire_seat(&mut self, key: &str) -> StrongResult {
206 let (current, version) = match self.read(key) {
207 Ok(read) => read,
208 Err(reason) => return StrongResult::Unavailable(reason),
209 };
210 let remaining = decode_count(current.as_deref());
211 if remaining == 0 {
212 return StrongResult::Rejected(RejectReason::Exhausted);
213 }
214 self.cas(key, version, (remaining - 1).to_le_bytes().to_vec())
215 }
216
217 pub fn seats_remaining(&self, key: &str) -> Result<u64, UnavailableReason> {
219 let (current, _) = self.read(key)?;
220 Ok(decode_count(current.as_deref()))
221 }
222}
223
224fn decode_count(bytes: Option<&[u8]>) -> u64 {
225 match bytes {
226 Some(b) if b.len() == 8 => u64::from_le_bytes(b.try_into().unwrap()),
227 _ => 0,
228 }
229}