1mod admission;
2mod commit;
3mod installation;
4
5use std::collections::VecDeque;
6
7use ozzy_journal::progress::{JournalProgress, JournalSnapshot};
8use ozzy_proto::NodeId;
9
10use crate::{
11 Configuration, JournalGeneration, OpNumber, PipelineLimits, Prefix, PreparedOperation,
12 ReplicationError, Scope, SyncTicket, VoterIndex, Voters, WriteTicket,
13};
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum Status {
18 Normal,
20 Fenced,
22 Faulted,
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub enum Admission {
29 Duplicate,
31 Write {
33 ticket: WriteTicket,
35 first_new: usize,
37 },
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub struct ReplicaSnapshot {
43 pub scope: Scope,
45 pub status: Status,
47 pub journal: JournalSnapshot,
49 pub accepted: Prefix,
51 pub committed: Prefix,
53 pub applied: Prefix,
55 pub pending_operations: usize,
58 pub pending_body_bytes: usize,
60 pub live_base: OpNumber,
63 pub retained_limits: PipelineLimits,
65 pub journal_tail: Option<Prefix>,
68 pub backups: Option<[(NodeId, OpNumber); 2]>,
70 pub known_commit: OpNumber,
72 pub ready_for_appends: bool,
76}
77
78#[derive(Debug)]
86pub struct NormalReplica {
87 configuration: Configuration,
88 local: VoterIndex,
89 status: Status,
90 journal: JournalProgress,
91 limits: PipelineLimits,
92 operations: VecDeque<PreparedOperation>,
93 body_bytes: usize,
94 accepted: Prefix,
95 committed: Prefix,
96 applied: Prefix,
97 retained_base: Prefix,
99 known_commit: OpNumber,
100 acknowledged: Voters<OpNumber>,
101 activation_tail: OpNumber,
102 installed_view: Option<crate::StartView>,
103 journal_tail: Option<Prefix>,
104}
105
106impl NormalReplica {
107 pub const fn reclaimed_prefix(&self) -> Prefix {
109 self.retained_base
110 }
111 pub(crate) const fn driver_identity(&self) -> (Configuration, VoterIndex) {
112 (self.configuration, self.local)
113 }
114
115 pub fn into_view_change(self, view: u64) -> Result<crate::ViewChange, crate::ViewChangeError> {
121 let configuration = self.configuration;
122 let local = self.local;
123 crate::ViewChange::begin(self, configuration, local, view)
124 }
125
126 pub fn bootstrap(
133 configuration: Configuration,
134 local: NodeId,
135 generation: JournalGeneration,
136 limits: PipelineLimits,
137 ) -> Result<Self, ReplicationError> {
138 let local = configuration.voter_index(local)?;
139 if limits.max_operations == 0
140 || limits.max_body_bytes == 0
141 || u64::try_from(limits.max_operations).is_err()
142 {
143 return Err(ReplicationError::InvalidLimits);
144 }
145 let retained =
147 if configuration.primary(configuration.scope.view) == configuration.voter(local) {
148 limits
149 } else {
150 limits.backup()
151 };
152 let mut operations = VecDeque::new();
153 operations
154 .try_reserve_exact(retained.max_operations)
155 .map_err(|_| ReplicationError::Capacity)?;
156 Ok(Self {
157 configuration,
158 local,
159 status: Status::Normal,
160 journal: JournalProgress::recover(generation, OpNumber(0)),
161 limits,
162 operations,
163 body_bytes: 0,
164 accepted: Prefix::GENESIS,
165 committed: Prefix::GENESIS,
166 applied: Prefix::GENESIS,
167 retained_base: Prefix::GENESIS,
168 known_commit: OpNumber(0),
169 acknowledged: Voters::splat(OpNumber(0)),
170 activation_tail: OpNumber(0),
171 installed_view: None,
172 journal_tail: None,
173 })
174 }
175
176 pub(crate) fn recover_fenced(
179 mut configuration: Configuration,
180 local: NodeId,
181 generation: JournalGeneration,
182 log: crate::FrozenLog,
183 limits: PipelineLimits,
184 ) -> Result<Self, ReplicationError> {
185 configuration.scope.view = log.last_normal_view;
186 let mut normal = Self::bootstrap(configuration, local, generation, limits)?;
187 normal.restore_fenced(generation, log);
188 Ok(normal)
189 }
190
191 pub(crate) fn restore_fenced(&mut self, generation: JournalGeneration, log: crate::FrozenLog) {
194 self.configuration.scope.view = log.last_normal_view;
195 self.status = Status::Fenced;
196 self.journal = JournalProgress::recover(generation, log.accepted.op);
197 self.operations.clear();
198 self.body_bytes = 0;
199 self.accepted = log.accepted;
200 self.committed = log.committed;
201 self.applied = Prefix::GENESIS;
202 self.retained_base = Prefix::GENESIS;
203 self.known_commit = log.committed.op;
204 self.acknowledged = Voters::splat(OpNumber(0));
205 self.activation_tail = log.accepted.op;
206 self.installed_view = None;
207 self.journal_tail = (log.accepted != Prefix::GENESIS).then_some(log.accepted);
208 }
209
210 pub const fn scope(&self) -> Scope {
212 self.configuration.scope()
213 }
214
215 pub fn snapshot(&self) -> ReplicaSnapshot {
217 ReplicaSnapshot {
218 scope: self.scope(),
219 status: self.status,
220 journal: self.journal.snapshot(),
221 accepted: self.accepted,
222 committed: self.committed,
223 applied: self.applied,
224 pending_operations: self.operations.len(),
225 pending_body_bytes: self.body_bytes,
226 live_base: self.live_base(),
227 retained_limits: self.retained_limits(),
228 journal_tail: self.journal_tail,
229 backups: self.is_primary().then(|| {
230 self.local
231 .others()
232 .map(|voter| (self.configuration.voter(voter), self.acknowledged[voter]))
233 }),
234 known_commit: self.known_commit,
235 ready_for_appends: self.ready_for_appends(),
236 }
237 }
238
239 fn live_base(&self) -> OpNumber {
241 self.known_commit.max(self.retained_base.op)
242 }
243
244 fn retained_limits(&self) -> PipelineLimits {
251 if self.is_primary() {
252 self.limits
253 } else {
254 self.limits.backup()
255 }
256 }
257
258 pub fn complete_write(&mut self, ticket: WriteTicket) -> Result<(), ReplicationError> {
260 self.journal.complete_write(ticket)?;
261 self.reclaim()
262 }
263
264 pub fn stored_through(&self) -> OpNumber {
267 self.journal.snapshot().durable
268 }
269
270 pub fn begin_sync(&self) -> Result<SyncTicket, ReplicationError> {
272 Ok(self.journal.begin_sync()?)
273 }
274
275 pub fn complete_sync(&mut self, ticket: SyncTicket) -> Result<(), ReplicationError> {
278 self.journal.complete_sync(ticket)?;
279 self.advance_commit()?;
280 self.reclaim()
281 }
282
283 pub fn complete_durable_write(&mut self, ticket: WriteTicket) -> Result<(), ReplicationError> {
285 self.journal.complete_durable_write(ticket)?;
286 self.advance_commit()?;
287 self.reclaim()
288 }
289
290 pub fn apply_through(&mut self, through: Prefix) -> Result<(), ReplicationError> {
296 self.require_normal()?;
297 if through.op < self.applied.op {
298 return Err(ReplicationError::HistoryUnavailable);
299 }
300 if through.op > self.committed.op {
301 return Err(ReplicationError::ApplyBeyondCommit);
302 }
303 if self.prefix_at(through.op)? != through {
304 return Err(self.conflict());
305 }
306 self.applied = through;
307 self.reclaim()?;
308 if self.journal_tail.is_some_and(|tail| through.op >= tail.op) {
309 self.journal_tail = None;
310 }
311 Ok(())
312 }
313
314 pub fn fence(&mut self) {
318 if self.status == Status::Normal {
319 self.status = Status::Fenced;
320 }
321 }
322
323 pub fn fail_io(&mut self, generation: JournalGeneration) -> Result<(), ReplicationError> {
325 self.journal.fail(generation)?;
326 self.fault();
327 Ok(())
328 }
329
330 pub(crate) fn fault(&mut self) {
332 self.journal.fault();
333 self.status = Status::Faulted;
334 }
335
336 fn is_primary(&self) -> bool {
337 self.configuration.voter(self.local)
338 == self.configuration.primary(self.configuration.scope.view)
339 }
340
341 fn backup_acknowledged(&self) -> OpNumber {
343 let [left, right] = self.local.others();
344 self.acknowledged[left].max(self.acknowledged[right])
345 }
346
347 fn ready_for_appends(&self) -> bool {
348 self.status == Status::Normal
349 && self.journal_tail.is_none()
350 && (!self.is_primary()
351 || (self.applied.op >= self.activation_tail
352 && self.journal.snapshot().durable >= self.activation_tail
353 && self.backup_acknowledged() >= self.activation_tail))
354 }
355
356 fn require_normal(&self) -> Result<(), ReplicationError> {
357 if self.status != Status::Normal {
358 return Err(ReplicationError::NotNormal);
359 }
360 Ok(())
361 }
362
363 fn require_scope(&self, scope: Scope) -> Result<(), ReplicationError> {
364 self.require_normal()?;
365 if scope != self.configuration.scope() {
366 return Err(ReplicationError::ScopeMismatch);
367 }
368 Ok(())
369 }
370
371 fn require_primary(&self, from: NodeId) -> Result<(), ReplicationError> {
372 self.configuration.voter_index(from)?;
373 if from != self.configuration.primary(self.configuration.scope.view) {
374 return Err(ReplicationError::WrongRole);
375 }
376 Ok(())
377 }
378
379 fn prefix_at(&self, op: OpNumber) -> Result<Prefix, ReplicationError> {
380 if op == self.applied.op {
381 return Ok(self.applied);
382 }
383 if op == self.retained_base.op {
384 return Ok(self.retained_base);
385 }
386 if op < self.retained_base.op {
387 return Err(ReplicationError::HistoryUnavailable);
388 }
389 if op > self.accepted.op {
390 return Err(ReplicationError::HistoryGap);
391 }
392 if let Some(tail) = self.journal_tail {
393 if op == tail.op {
394 return Ok(tail);
395 }
396 if op == self.committed.op {
397 return Ok(self.committed);
398 }
399 return Err(ReplicationError::HistoryUnavailable);
402 }
403 op.0.checked_sub(self.retained_base.op.0)
405 .and_then(|distance| distance.checked_sub(1))
406 .and_then(|index| usize::try_from(index).ok())
407 .and_then(|index| self.operations.get(index))
408 .map(|operation| operation.prefix)
409 .ok_or(ReplicationError::HistoryUnavailable)
410 }
411
412 fn reclaim(&mut self) -> Result<(), ReplicationError> {
413 let through = self.applied.op.min(self.stored_through());
414 if through <= self.retained_base.op {
415 return Ok(());
416 }
417 let prefix = self.prefix_at(through)?;
418 self.body_bytes -= crate::release_through(&mut self.operations, through);
419 self.retained_base = prefix;
420 Ok(())
421 }
422
423 fn advance_commit(&mut self) -> Result<(), ReplicationError> {
424 if self.status != Status::Normal {
425 return Ok(());
426 }
427 let available = self.journal.snapshot().durable;
428 let eligible = if self.is_primary() {
429 available.min(self.backup_acknowledged())
431 } else {
432 available.min(self.known_commit)
433 };
434 if eligible > self.committed.op {
435 self.committed = self.prefix_at(eligible)?;
436 }
437 Ok(())
438 }
439
440 fn conflict(&mut self) -> ReplicationError {
441 self.fault();
442 ReplicationError::ConflictingHistory
443 }
444}