1mod checkpoint;
10mod conflicts;
11mod coordinator;
12mod identity;
13mod liveness;
14mod observations;
15mod predicates;
16mod publication;
17#[cfg(test)]
18mod tests;
19
20use uqa_core::memory::{BudgetedVec, MemoryBudget};
21
22use super::{DatabaseId, VersionError, VersionResult};
23use crate::read_control::StorageReadControl;
24
25pub use checkpoint::records::{SerializableCheckpointKey, SerializableCheckpointRecord};
26pub(crate) use coordinator::recover;
27pub use coordinator::{
28 admit_serializable, LocalSerializableState, SerializableCoordinator, SerializableLeases,
29 SerializableOperation,
30};
31pub use identity::SerializableTransactionId;
32pub use liveness::{LocalSerializableLeases, SerializableParticipant};
33pub use observations::SerializableWriteMark;
34pub(in crate::mvcc) use predicates::OwnedPredicate;
35pub use predicates::{SerializableKeySpace, SerializablePredicate};
36pub use publication::SerializablePublication;
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40pub enum SafeSnapshot {
41 Pending,
42 Safe,
43 Unsafe,
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48pub enum SerializableStatus {
49 Active,
50 Prepared,
51 Committed,
52 Aborted,
53 Doomed,
54}
55
56#[derive(Clone, Copy, PartialEq, Eq)]
57enum ParticipantOwner {
58 Manual,
60 Leased,
61}
62
63#[derive(Clone, Copy)]
64struct Transaction {
65 id: u64,
66 snapshot: u64,
67 read_only: bool,
68 prepared: Option<u64>,
69 committed: Option<u64>,
70 aborted: bool,
71 doomed: bool,
72 owner: ParticipantOwner,
73 summarized_out: Option<u64>,
74 writes: u64,
75 publication: Option<publication::PreparedPublication>,
76}
77
78impl Transaction {
79 fn live(self) -> bool {
80 !self.aborted && self.committed.is_none()
81 }
82
83 fn relevant(self) -> bool {
84 !self.aborted && !self.doomed
85 }
86
87 fn retains_history(self, oldest: u64) -> bool {
88 !self.aborted && self.committed.is_none_or(|end| end > oldest)
89 }
90}
91
92#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
94struct Edge(u64, u64);
95
96pub struct SerializableGraph {
102 database: DatabaseId,
103 coordinator: [u8; 16],
104 last_allocation: u64,
105 clock: u64,
106 pending_finishes: u64,
107 transactions: BudgetedVec<Transaction>,
108 outgoing: BudgetedVec<Edge>,
109 incoming: BudgetedVec<Edge>,
110 predicates: observations::Observations,
111 checkpoint_changed: bool,
112 checkpoint_records: BudgetedVec<SerializableCheckpointKey>,
113}
114
115impl SerializableGraph {
116 pub fn new(
118 database: DatabaseId,
119 coordinator: [u8; 16],
120 memory: &MemoryBudget,
121 ) -> VersionResult<Self> {
122 identity::validate_coordinator(coordinator)?;
123 Ok(Self {
124 database,
125 coordinator,
126 last_allocation: 0,
127 clock: 0,
128 pending_finishes: 0,
129 transactions: BudgetedVec::new(memory),
130 outgoing: BudgetedVec::new(memory),
131 incoming: BudgetedVec::new(memory),
132 predicates: observations::Observations::new(memory),
133 checkpoint_changed: true,
134 checkpoint_records: BudgetedVec::new(memory),
135 })
136 }
137
138 pub fn admit(
140 &mut self,
141 read_only: bool,
142 control: &StorageReadControl,
143 ) -> VersionResult<SerializableTransactionId> {
144 control.check()?;
145 let allocation = self
146 .last_allocation
147 .checked_add(1)
148 .ok_or(VersionError::SequenceExhausted)?;
149 let transaction =
150 SerializableTransactionId::new(self.database, self.coordinator, allocation)?;
151 self.begin(transaction, read_only, control)?;
152 Ok(transaction)
153 }
154
155 fn begin(
156 &mut self,
157 transaction: SerializableTransactionId,
158 read_only: bool,
159 control: &StorageReadControl,
160 ) -> VersionResult<()> {
161 control.check()?;
162 self.validate_identity(transaction)?;
163 let position = self
164 .transactions
165 .binary_search_by_key(&transaction.allocation(), |entry| entry.id)
166 .err()
167 .ok_or(VersionError::InvalidEncoding(
168 "duplicate serializable transaction",
169 ))?;
170 self.reserve_order(false)?;
171 self.transactions.push(Transaction {
172 id: transaction.allocation(),
173 snapshot: self.clock + 1,
174 read_only,
175 prepared: None,
176 committed: None,
177 aborted: false,
178 doomed: false,
179 owner: ParticipantOwner::Manual,
180 summarized_out: None,
181 writes: 0,
182 publication: None,
183 })?;
184 self.checkpoint_changed = true;
185 self.transactions[position..].rotate_right(1);
186 self.clock += 1;
187 self.last_allocation = self.last_allocation.max(transaction.allocation());
188 Ok(())
189 }
190
191 pub fn status(
193 &self,
194 transaction: SerializableTransactionId,
195 ) -> VersionResult<SerializableStatus> {
196 let entry = self.transactions[self.position(transaction)?];
197 Ok(if entry.committed.is_some() {
198 SerializableStatus::Committed
199 } else if entry.aborted {
200 SerializableStatus::Aborted
201 } else if entry.doomed {
202 SerializableStatus::Doomed
203 } else if entry.prepared.is_some() {
204 SerializableStatus::Prepared
205 } else {
206 SerializableStatus::Active
207 })
208 }
209
210 pub fn check_active(&self, transaction: SerializableTransactionId) -> VersionResult<()> {
212 let entry = self.transactions[self.position(transaction)?];
213 if entry.doomed {
214 return Err(VersionError::SerializationConflict { transaction });
215 }
216 if !entry.live() {
217 return Err(VersionError::TransactionFinished);
218 }
219 if entry.prepared.is_some() {
220 return Err(VersionError::TransactionSealed);
221 }
222 Ok(())
223 }
224
225 pub fn observe_rw(
227 &mut self,
228 observer: SerializableTransactionId,
229 reader: SerializableTransactionId,
230 writer: SerializableTransactionId,
231 control: &StorageReadControl,
232 ) -> VersionResult<()> {
233 let Some(action) = self.plan_dependency(observer, reader, writer, control)? else {
234 return Ok(());
235 };
236 self.reserve_dependencies(usize::from(action.victim.is_none()))?;
238 self.publish_dependency(action)
239 }
240
241 pub fn prepare_commit(
243 &mut self,
244 transaction: SerializableTransactionId,
245 control: &StorageReadControl,
246 ) -> VersionResult<()> {
247 control.check()?;
248 let position = self.position(transaction)?;
249 let entry = self.transactions[position];
250 if entry.prepared.is_some() && entry.live() && !entry.doomed {
251 return Ok(());
252 }
253 self.check_active(transaction)?;
254 self.reserve_order(true)?;
255 let incoming = edge_range(&self.incoming, entry.id);
256 for index in incoming.clone() {
258 control.check()?;
259 let pivot = self.node(self.incoming[index].1);
260 if self.dangerous_pivot(entry.id, pivot, Some(control))? && pivot.prepared.is_some() {
261 self.transactions[position].doomed = true;
262 self.checkpoint_changed = true;
263 return Err(VersionError::SerializationConflict { transaction });
264 }
265 }
266 self.checkpoint_changed = true;
267 for index in incoming {
268 let pivot = self.node(self.incoming[index].1);
269 if self.dangerous_pivot(entry.id, pivot, None)? {
270 let position = self.allocation_position(pivot.id)?;
271 self.transactions[position].doomed = true;
272 }
273 }
274 self.clock += 1;
275 self.pending_finishes += 1;
276 self.transactions[position].prepared = Some(self.clock);
277 Ok(())
278 }
279
280 pub fn commit(&mut self, transaction: SerializableTransactionId) -> VersionResult<()> {
282 let position = self.position(transaction)?;
283 let entry = self.transactions[position];
284 if entry.committed.is_some() {
285 return Ok(());
286 }
287 if entry.aborted || entry.doomed || entry.prepared.is_none() {
288 return Err(VersionError::InvalidEncoding(
289 "serializable completion requires a prepared transaction",
290 ));
291 }
292 if entry
293 .publication
294 .is_some_and(|publication| !publication.committed())
295 {
296 return Err(VersionError::InvalidEncoding(
297 "durable serializable publication requires a confirmed receipt",
298 ));
299 }
300 self.checkpoint_changed = true;
302 self.clock += 1;
303 self.pending_finishes -= 1;
304 self.transactions[position].committed = Some(self.clock);
305 Ok(())
306 }
307
308 pub fn rollback(&mut self, transaction: SerializableTransactionId) -> VersionResult<()> {
310 let position = self.position(transaction)?;
311 let entry = &mut self.transactions[position];
312 if entry.committed.is_some() {
313 return Err(VersionError::TransactionFinished);
314 }
315 if entry
316 .publication
317 .is_some_and(|publication| !publication.aborted())
318 {
319 return Err(VersionError::InvalidEncoding(
320 "durable serializable publication requires a confirmed abort",
321 ));
322 }
323 if !entry.aborted {
324 if entry.prepared.is_some() {
325 self.pending_finishes -= 1;
326 }
327 entry.aborted = true;
328 self.checkpoint_changed = true;
329 }
330 self.checkpoint_changed |= self.predicates.forget(transaction.allocation());
331 Ok(())
332 }
333
334 pub fn safe_snapshot(
336 &self,
337 transaction: SerializableTransactionId,
338 control: &StorageReadControl,
339 ) -> VersionResult<SafeSnapshot> {
340 control.check()?;
341 self.check_active(transaction)?;
342 let reader = self.transactions[self.position(transaction)?];
343 if !reader.read_only {
344 return Err(VersionError::InvalidEncoding(
345 "safe-snapshot check requires a read-only transaction",
346 ));
347 }
348 let mut pending = false;
349 for entry in &*self.transactions {
350 control.check()?;
351 if entry.read_only
352 || !entry.relevant()
353 || entry.snapshot > reader.snapshot
354 || entry.committed.is_some_and(|end| end <= reader.snapshot)
355 {
356 continue;
357 }
358 if entry.live() {
359 pending = true;
360 } else if self
361 .earliest_out(*entry, control)?
362 .is_some_and(|order| order <= reader.snapshot)
363 {
364 return Ok(SafeSnapshot::Unsafe);
365 }
366 }
367 Ok(if pending {
368 SafeSnapshot::Pending
369 } else {
370 SafeSnapshot::Safe
371 })
372 }
373
374 pub fn reclaim(&mut self) {
376 let oldest = self
377 .transactions
378 .iter()
379 .filter(|entry| entry.live())
380 .map(|entry| entry.snapshot)
381 .min();
382 let Some(oldest) = oldest else {
383 self.checkpoint_changed |= retain_copy(&mut self.transactions, |entry| {
384 entry.owner == ParticipantOwner::Leased
385 });
386 self.checkpoint_changed |= !self.outgoing.is_empty();
387 self.outgoing = BudgetedVec::new(self.outgoing.budget());
388 self.incoming = BudgetedVec::new(self.incoming.budget());
389 self.checkpoint_changed |= self.predicates.clear();
390 return;
391 };
392 for index in 0..self.transactions.len() {
393 let entry = self.transactions[index];
394 let mut earliest = entry.summarized_out;
395 for edge in &self.outgoing[edge_range(&self.outgoing, entry.id)] {
396 let target = self.node(edge.1);
397 if target.relevant() && target.committed.is_some_and(|end| end <= oldest) {
398 if let Some(order) = target.prepared {
399 earliest = Some(earliest.map_or(order, |old| old.min(order)));
400 }
401 }
402 }
403 self.checkpoint_changed |= entry.summarized_out != earliest;
404 self.transactions[index].summarized_out = earliest;
405 }
406 self.checkpoint_changed |= retain_copy(&mut self.transactions, |entry| {
407 entry.owner == ParticipantOwner::Leased || entry.retains_history(oldest)
408 });
409 let retained = &self.transactions;
410 let retain_edge = |edge: Edge| {
411 retained
412 .binary_search_by_key(&edge.0, |entry| entry.id)
413 .is_ok_and(|position| retained[position].retains_history(oldest))
414 && retained
415 .binary_search_by_key(&edge.1, |entry| entry.id)
416 .is_ok_and(|position| retained[position].retains_history(oldest))
417 };
418 self.checkpoint_changed |= retain_copy(&mut self.outgoing, retain_edge);
419 retain_copy(&mut self.incoming, retain_edge);
420 self.checkpoint_changed |= self.predicates.reclaim(&self.transactions, oldest);
421 }
422
423 fn reserve_order(&self, preparing: bool) -> VersionResult<()> {
424 self.clock
425 .checked_add(self.pending_finishes)
426 .and_then(|order| order.checked_add(1 + u64::from(preparing)))
427 .ok_or(VersionError::SequenceExhausted)?;
428 Ok(())
429 }
430
431 fn validate_identity(&self, transaction: SerializableTransactionId) -> VersionResult<()> {
432 if transaction.database() != self.database {
433 return Err(VersionError::WrongDatabase);
434 }
435 if transaction.coordinator() != self.coordinator {
436 return Err(VersionError::WrongSerializableCoordinator);
437 }
438 Ok(())
439 }
440
441 fn position(&self, transaction: SerializableTransactionId) -> VersionResult<usize> {
442 self.validate_identity(transaction)?;
443 self.allocation_position(transaction.allocation())
444 }
445
446 fn allocation_position(&self, allocation: u64) -> VersionResult<usize> {
447 self.transactions
448 .binary_search_by_key(&allocation, |entry| entry.id)
449 .map_err(|_| VersionError::UnknownTransaction)
450 }
451
452 fn node(&self, allocation: u64) -> Transaction {
453 self.transactions[self
454 .allocation_position(allocation)
455 .expect("retained SSI edge must name a retained transaction")]
456 }
457}
458
459fn edge_range(edges: &[Edge], allocation: u64) -> std::ops::Range<usize> {
460 edges.partition_point(|edge| edge.0 < allocation)
461 ..edges.partition_point(|edge| edge.0 <= allocation)
462}
463
464fn insert_edge(edges: &mut BudgetedVec<Edge>, edge: Edge) -> VersionResult<()> {
465 if let Err(position) = edges.binary_search(&edge) {
466 edges.push(edge)?;
467 edges[position..].rotate_right(1);
468 }
469 Ok(())
470}
471
472fn retain_copy<T: Copy>(values: &mut BudgetedVec<T>, mut retain: impl FnMut(T) -> bool) -> bool {
473 let previous = values.len();
474 let mut kept = 0;
475 for index in 0..values.len() {
476 if retain(values[index]) {
477 values[kept] = values[index];
478 kept += 1;
479 }
480 }
481 values.truncate(kept);
482 if values.is_empty() {
483 *values = BudgetedVec::new(values.budget());
484 }
485 kept != previous
486}