1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub struct JournalGeneration(pub u128);
13
14#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
16pub struct OpNumber(pub u64);
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub struct WriteTicket {
21 generation: JournalGeneration,
22 first: OpNumber,
23 through: OpNumber,
24}
25
26impl WriteTicket {
27 pub const fn generation(self) -> JournalGeneration {
29 self.generation
30 }
31 pub const fn first(self) -> OpNumber {
33 self.first
34 }
35 pub const fn through(self) -> OpNumber {
37 self.through
38 }
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub struct SyncTicket {
44 generation: JournalGeneration,
45 through: OpNumber,
46}
47
48impl SyncTicket {
49 pub const fn generation(self) -> JournalGeneration {
51 self.generation
52 }
53 pub const fn through(self) -> OpNumber {
55 self.through
56 }
57}
58
59#[derive(Debug, Clone, Copy, PartialEq, Eq)]
61pub struct JournalSnapshot {
62 pub generation: JournalGeneration,
64 pub accepted: OpNumber,
66 pub written: OpNumber,
68 pub durable: OpNumber,
70 pub faulted: bool,
72}
73
74#[derive(Debug)]
76pub struct JournalProgress {
77 state: JournalSnapshot,
78}
79
80impl JournalProgress {
81 pub const fn recover(generation: JournalGeneration, through: OpNumber) -> Self {
87 Self {
88 state: JournalSnapshot {
89 generation,
90 accepted: through,
91 written: through,
92 durable: through,
93 faulted: false,
94 },
95 }
96 }
97
98 pub const fn snapshot(&self) -> JournalSnapshot {
100 self.state
101 }
102
103 pub fn admit(&mut self, operations: u64) -> Result<WriteTicket, ProgressError> {
106 self.require_active(self.state.generation)?;
107 if operations == 0 {
108 return Err(ProgressError::EmptyGroup);
109 }
110 let through = self
111 .state
112 .accepted
113 .0
114 .checked_add(operations)
115 .ok_or(ProgressError::OpExhausted)?;
116 let ticket = WriteTicket {
117 generation: self.state.generation,
118 first: OpNumber(self.state.accepted.0 + 1),
119 through: OpNumber(through),
120 };
121 self.state.accepted = ticket.through;
122 Ok(ticket)
123 }
124
125 pub fn complete_write(&mut self, ticket: WriteTicket) -> Result<(), ProgressError> {
128 self.require_active(ticket.generation)?;
129 if ticket.through > self.state.accepted {
130 return Err(ProgressError::BeyondAccepted);
131 }
132 if ticket.through <= self.state.written {
133 return Ok(());
134 }
135 if Some(ticket.first.0) != self.state.written.0.checked_add(1) {
136 return Err(ProgressError::WriteGap);
137 }
138 self.state.written = ticket.through;
139 Ok(())
140 }
141
142 pub fn begin_sync(&self) -> Result<SyncTicket, ProgressError> {
144 self.require_active(self.state.generation)?;
145 Ok(SyncTicket {
146 generation: self.state.generation,
147 through: self.state.written,
148 })
149 }
150
151 pub fn complete_sync(&mut self, ticket: SyncTicket) -> Result<(), ProgressError> {
154 self.require_active(ticket.generation)?;
155 if ticket.through > self.state.written {
156 return Err(ProgressError::BeyondWritten);
157 }
158 self.state.durable = self.state.durable.max(ticket.through);
159 Ok(())
160 }
161
162 pub fn complete_durable_write(&mut self, ticket: WriteTicket) -> Result<(), ProgressError> {
166 self.complete_write(ticket)?;
167 self.state.durable = self.state.durable.max(ticket.through);
168 Ok(())
169 }
170
171 pub fn fail(&mut self, generation: JournalGeneration) -> Result<(), ProgressError> {
174 if generation != self.state.generation {
175 return Err(ProgressError::StaleGeneration);
176 }
177 self.fault();
178 Ok(())
179 }
180
181 pub fn fault(&mut self) {
183 self.state.faulted = true;
184 }
185
186 fn require_active(&self, generation: JournalGeneration) -> Result<(), ProgressError> {
187 if generation != self.state.generation {
188 return Err(ProgressError::StaleGeneration);
189 }
190 if self.state.faulted {
191 return Err(ProgressError::Faulted);
192 }
193 Ok(())
194 }
195}
196
197#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
199pub enum ProgressError {
200 #[error("journal generation is faulted")]
202 Faulted,
203 #[error("stale journal generation")]
205 StaleGeneration,
206 #[error("empty journal group")]
208 EmptyGroup,
209 #[error("journal operation number exhausted")]
211 OpExhausted,
212 #[error("write completion exceeds admitted prefix")]
214 BeyondAccepted,
215 #[error("write completion leaves a journal gap")]
217 WriteGap,
218 #[error("sync completion exceeds written prefix")]
220 BeyondWritten,
221}
222
223#[cfg(kani)]
224mod proofs;