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 fn snapshot(&self) -> ReplicaSnapshot {
212 ReplicaSnapshot {
213 scope: self.configuration.scope(),
214 status: self.status,
215 journal: self.journal.snapshot(),
216 accepted: self.accepted,
217 committed: self.committed,
218 applied: self.applied,
219 pending_operations: self.operations.len(),
220 pending_body_bytes: self.body_bytes,
221 live_base: self.live_base(),
222 retained_limits: self.retained_limits(),
223 journal_tail: self.journal_tail,
224 backups: self.is_primary().then(|| {
225 self.local
226 .others()
227 .map(|voter| (self.configuration.voter(voter), self.acknowledged[voter]))
228 }),
229 known_commit: self.known_commit,
230 ready_for_appends: self.ready_for_appends(),
231 }
232 }
233
234 fn live_base(&self) -> OpNumber {
236 self.known_commit.max(self.retained_base.op)
237 }
238
239 fn retained_limits(&self) -> PipelineLimits {
246 if self.is_primary() {
247 self.limits
248 } else {
249 self.limits.backup()
250 }
251 }
252
253 pub fn complete_write(&mut self, ticket: WriteTicket) -> Result<(), ReplicationError> {
255 self.journal.complete_write(ticket)?;
256 self.reclaim()
257 }
258
259 pub fn stored_through(&self) -> OpNumber {
262 self.journal.snapshot().durable
263 }
264
265 pub fn begin_sync(&self) -> Result<SyncTicket, ReplicationError> {
267 Ok(self.journal.begin_sync()?)
268 }
269
270 pub fn complete_sync(&mut self, ticket: SyncTicket) -> Result<(), ReplicationError> {
273 self.journal.complete_sync(ticket)?;
274 self.advance_commit()?;
275 self.reclaim()
276 }
277
278 pub fn complete_durable_write(&mut self, ticket: WriteTicket) -> Result<(), ReplicationError> {
280 self.journal.complete_durable_write(ticket)?;
281 self.advance_commit()?;
282 self.reclaim()
283 }
284
285 pub fn apply_through(&mut self, through: Prefix) -> Result<(), ReplicationError> {
291 self.require_normal()?;
292 if through.op < self.applied.op {
293 return Err(ReplicationError::HistoryUnavailable);
294 }
295 if through.op > self.committed.op {
296 return Err(ReplicationError::ApplyBeyondCommit);
297 }
298 if self.prefix_at(through.op)? != through {
299 return Err(self.conflict());
300 }
301 self.applied = through;
302 self.reclaim()?;
303 if self.journal_tail.is_some_and(|tail| through.op >= tail.op) {
304 self.journal_tail = None;
305 }
306 Ok(())
307 }
308
309 pub fn fence(&mut self) {
313 if self.status == Status::Normal {
314 self.status = Status::Fenced;
315 }
316 }
317
318 pub fn fail_io(&mut self, generation: JournalGeneration) -> Result<(), ReplicationError> {
320 self.journal.fail(generation)?;
321 self.fault();
322 Ok(())
323 }
324
325 pub(crate) fn fault(&mut self) {
327 self.journal.fault();
328 self.status = Status::Faulted;
329 }
330
331 fn is_primary(&self) -> bool {
332 self.configuration.voter(self.local)
333 == self.configuration.primary(self.configuration.scope.view)
334 }
335
336 fn backup_acknowledged(&self) -> OpNumber {
338 let [left, right] = self.local.others();
339 self.acknowledged[left].max(self.acknowledged[right])
340 }
341
342 fn ready_for_appends(&self) -> bool {
343 self.status == Status::Normal
344 && self.journal_tail.is_none()
345 && (!self.is_primary()
346 || (self.applied.op >= self.activation_tail
347 && self.journal.snapshot().durable >= self.activation_tail
348 && self.backup_acknowledged() >= self.activation_tail))
349 }
350
351 fn require_normal(&self) -> Result<(), ReplicationError> {
352 if self.status != Status::Normal {
353 return Err(ReplicationError::NotNormal);
354 }
355 Ok(())
356 }
357
358 fn require_scope(&self, scope: Scope) -> Result<(), ReplicationError> {
359 self.require_normal()?;
360 if scope != self.configuration.scope() {
361 return Err(ReplicationError::ScopeMismatch);
362 }
363 Ok(())
364 }
365
366 fn require_primary(&self, from: NodeId) -> Result<(), ReplicationError> {
367 self.configuration.voter_index(from)?;
368 if from != self.configuration.primary(self.configuration.scope.view) {
369 return Err(ReplicationError::WrongRole);
370 }
371 Ok(())
372 }
373
374 fn prefix_at(&self, op: OpNumber) -> Result<Prefix, ReplicationError> {
375 if op == self.applied.op {
376 return Ok(self.applied);
377 }
378 if op == self.retained_base.op {
379 return Ok(self.retained_base);
380 }
381 if op < self.retained_base.op {
382 return Err(ReplicationError::HistoryUnavailable);
383 }
384 if op > self.accepted.op {
385 return Err(ReplicationError::HistoryGap);
386 }
387 if let Some(tail) = self.journal_tail {
388 if op == tail.op {
389 return Ok(tail);
390 }
391 if op == self.committed.op {
392 return Ok(self.committed);
393 }
394 return Err(ReplicationError::HistoryUnavailable);
397 }
398 op.0.checked_sub(self.retained_base.op.0)
400 .and_then(|distance| distance.checked_sub(1))
401 .and_then(|index| usize::try_from(index).ok())
402 .and_then(|index| self.operations.get(index))
403 .map(|operation| operation.prefix)
404 .ok_or(ReplicationError::HistoryUnavailable)
405 }
406
407 fn reclaim(&mut self) -> Result<(), ReplicationError> {
408 let through = self.applied.op.min(self.stored_through());
409 if through <= self.retained_base.op {
410 return Ok(());
411 }
412 let prefix = self.prefix_at(through)?;
413 self.body_bytes -= crate::release_through(&mut self.operations, through);
414 self.retained_base = prefix;
415 Ok(())
416 }
417
418 fn advance_commit(&mut self) -> Result<(), ReplicationError> {
419 if self.status != Status::Normal {
420 return Ok(());
421 }
422 let available = self.journal.snapshot().durable;
423 let eligible = if self.is_primary() {
424 available.min(self.backup_acknowledged())
426 } else {
427 available.min(self.known_commit)
428 };
429 if eligible > self.committed.op {
430 self.committed = self.prefix_at(eligible)?;
431 }
432 Ok(())
433 }
434
435 fn conflict(&mut self) -> ReplicationError {
436 self.fault();
437 ReplicationError::ConflictingHistory
438 }
439}