uqa_storage/mvcc/serializable/
publication.rs1#[cfg(test)]
10mod tests;
11
12use super::{SerializableGraph, SerializableTransactionId, VersionError, VersionResult};
13use crate::{
14 mvcc::{CommitFingerprint, CommitReceipt, CommitSequence, CommitStatus, StorageTransactionId},
15 read_control::StorageReadControl,
16};
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub struct SerializablePublication {
21 participant: SerializableTransactionId,
22 transaction: StorageTransactionId,
23 fingerprint: CommitFingerprint,
24}
25
26#[derive(Clone, Copy)]
27pub(super) struct PreparedPublication {
28 pub(super) allocation: u64,
29 pub(super) fingerprint: CommitFingerprint,
30 pub(super) outcome: PublicationOutcome,
31}
32
33#[derive(Clone, Copy, PartialEq, Eq)]
34pub(super) enum PublicationOutcome {
35 Prepared,
36 Committed(CommitSequence),
37 Aborted,
38}
39
40impl PreparedPublication {
41 pub(super) fn committed(self) -> bool {
42 matches!(self.outcome, PublicationOutcome::Committed(_))
43 }
44
45 pub(super) fn aborted(self) -> bool {
46 self.outcome == PublicationOutcome::Aborted
47 }
48
49 fn matches(self, publication: SerializablePublication) -> bool {
50 self.allocation == publication.transaction.allocation()
51 && self.fingerprint == publication.fingerprint
52 }
53}
54
55impl SerializablePublication {
56 pub const fn participant(self) -> SerializableTransactionId {
57 self.participant
58 }
59
60 pub const fn transaction(self) -> StorageTransactionId {
61 self.transaction
62 }
63
64 pub const fn fingerprint(self) -> CommitFingerprint {
65 self.fingerprint
66 }
67}
68
69impl SerializableGraph {
70 pub fn retains_transaction_receipt(&self, transaction: StorageTransactionId) -> bool {
72 transaction.database() == self.database
73 && self.transactions.iter().any(|entry| {
74 entry
75 .publication
76 .is_some_and(|publication| publication.allocation == transaction.allocation())
77 })
78 }
79
80 pub fn validate_persisted_publications(
82 &self,
83 control: &StorageReadControl,
84 mut lookup: impl FnMut(StorageTransactionId) -> VersionResult<CommitStatus>,
85 ) -> VersionResult<()> {
86 control.cancellation().check()?;
87 for entry in &*self.transactions {
88 control.cancellation().check()?;
89 let Some(publication) = entry.publication else {
90 continue;
91 };
92 let transaction = StorageTransactionId::new(self.database, publication.allocation)?;
93 let expected = match publication.outcome {
94 PublicationOutcome::Committed(sequence) => CommitStatus::Committed(CommitReceipt {
95 transaction,
96 sequence,
97 fingerprint: publication.fingerprint,
98 }),
99 PublicationOutcome::Aborted => CommitStatus::Aborted,
100 PublicationOutcome::Prepared => continue,
101 };
102 match lookup(transaction)? {
103 CommitStatus::Unknown => return Err(VersionError::UnknownTransaction),
104 actual if actual != expected => return Err(VersionError::CommitMismatch),
105 _ => {}
106 }
107 }
108 Ok(())
109 }
110
111 pub fn prepare_publication(
113 &mut self,
114 participant: SerializableTransactionId,
115 transaction: StorageTransactionId,
116 fingerprint: CommitFingerprint,
117 control: &StorageReadControl,
118 ) -> VersionResult<SerializablePublication> {
119 control.check()?;
120 let position = self.position(participant)?;
121 if transaction.database() != self.database {
122 return Err(VersionError::WrongDatabase);
123 }
124 let publication = SerializablePublication {
125 participant,
126 transaction,
127 fingerprint,
128 };
129 if let Some(previous) = self.transactions[position].publication {
130 if !previous.matches(publication) {
131 return Err(VersionError::CommitMismatch);
132 }
133 } else {
134 self.check_active(participant)?;
135 for entry in &*self.transactions {
136 control.check()?;
137 if entry
138 .publication
139 .is_some_and(|previous| previous.allocation == transaction.allocation())
140 {
141 return Err(VersionError::CommitMismatch);
142 }
143 }
144 }
145 self.prepare_commit(participant, control)?;
146 self.checkpoint_changed |= self.transactions[position].publication.is_none();
147 self.transactions[position].publication = Some(PreparedPublication {
148 allocation: transaction.allocation(),
149 fingerprint,
150 outcome: PublicationOutcome::Prepared,
151 });
152 Ok(publication)
153 }
154
155 pub fn publication(
157 &self,
158 participant: SerializableTransactionId,
159 ) -> VersionResult<Option<SerializablePublication>> {
160 self.transactions[self.position(participant)?]
161 .publication
162 .map(|publication| {
163 Ok(SerializablePublication {
164 participant,
165 transaction: StorageTransactionId::new(self.database, publication.allocation)?,
166 fingerprint: publication.fingerprint,
167 })
168 })
169 .transpose()
170 }
171
172 pub fn reconcile_publications(
174 &mut self,
175 control: &StorageReadControl,
176 mut lookup: impl FnMut(StorageTransactionId) -> VersionResult<CommitStatus>,
177 ) -> VersionResult<()> {
178 for index in 0..self.transactions.len() {
179 control.check()?;
180 let entry = self.transactions[index];
181 let Some(prepared) = entry.publication else {
182 continue;
183 };
184 if prepared.outcome != PublicationOutcome::Prepared {
185 continue;
186 }
187 let participant =
188 SerializableTransactionId::new(self.database, self.coordinator, entry.id)?;
189 let transaction = StorageTransactionId::new(self.database, prepared.allocation)?;
190 let publication = SerializablePublication {
191 participant,
192 transaction,
193 fingerprint: prepared.fingerprint,
194 };
195 let status = lookup(transaction)?;
196 if self.resolve_publication(publication, status)? == CommitStatus::Unknown {
197 return Err(VersionError::UnknownTransaction);
198 }
199 }
200 Ok(())
201 }
202
203 pub fn resolve_publication(
205 &mut self,
206 publication: SerializablePublication,
207 status: CommitStatus,
208 ) -> VersionResult<CommitStatus> {
209 let position = self.position(publication.participant)?;
210 let Some(prepared) = self.transactions[position].publication else {
211 return Err(VersionError::CommitMismatch);
212 };
213 if !prepared.matches(publication) {
214 return Err(VersionError::CommitMismatch);
215 }
216 match prepared.outcome {
217 PublicationOutcome::Committed(sequence) => {
218 let receipt = CommitReceipt {
219 transaction: publication.transaction,
220 sequence,
221 fingerprint: publication.fingerprint,
222 };
223 return match status {
224 CommitStatus::Aborted => Err(VersionError::AlreadyCommitted(receipt)),
225 CommitStatus::Committed(actual) if actual != receipt => {
226 Err(VersionError::AlreadyCommitted(receipt))
227 }
228 _ => Ok(CommitStatus::Committed(receipt)),
229 };
230 }
231 PublicationOutcome::Aborted => {
232 return if matches!(status, CommitStatus::Committed(_)) {
233 Err(VersionError::AlreadyAborted(publication.transaction))
234 } else {
235 Ok(CommitStatus::Aborted)
236 };
237 }
238 PublicationOutcome::Prepared => {}
239 }
240 match status {
241 CommitStatus::Committed(receipt) => {
242 if receipt.transaction != publication.transaction
243 || receipt.fingerprint != publication.fingerprint
244 {
245 return Err(VersionError::CommitMismatch);
246 }
247 self.checkpoint_changed = true;
248 self.transactions[position]
249 .publication
250 .as_mut()
251 .expect("validated publication")
252 .outcome = PublicationOutcome::Committed(receipt.sequence);
253 self.commit(publication.participant)?;
254 }
255 CommitStatus::Aborted => {
256 self.checkpoint_changed = true;
257 self.transactions[position]
258 .publication
259 .as_mut()
260 .expect("validated publication")
261 .outcome = PublicationOutcome::Aborted;
262 self.rollback(publication.participant)?;
263 }
264 CommitStatus::Pending | CommitStatus::Unknown => {}
265 }
266 Ok(status)
267 }
268}