#[cfg(test)]
mod tests;
use uqa_core::memory::{BudgetedVec, MemoryBudget};
use super::{
conflicts::DependencyAction, predicates::OwnedPredicate, SerializableGraph,
SerializablePredicate, SerializableTransactionId, Transaction, VersionError, VersionResult,
};
use crate::read_control::StorageReadControl;
#[derive(Debug, Clone, Copy)]
pub struct SerializableWriteMark {
transaction: SerializableTransactionId,
writes: u64,
}
pub(super) struct Observation {
pub(super) owner: u64,
pub(super) write: u64,
pub(super) predicate: OwnedPredicate,
pub(super) fingerprint: [u8; 32],
}
pub(super) struct Observations {
pub(super) reads: BudgetedVec<Observation>,
pub(super) writes: BudgetedVec<Observation>,
}
impl Observations {
pub(super) fn new(memory: &MemoryBudget) -> Self {
Self {
reads: BudgetedVec::new(memory),
writes: BudgetedVec::new(memory),
}
}
pub(super) fn clear(&mut self) -> bool {
let changed = !self.reads.is_empty() || !self.writes.is_empty();
self.reads = BudgetedVec::new(self.reads.budget());
self.writes = BudgetedVec::new(self.writes.budget());
changed
}
pub(super) fn forget(&mut self, owner: u64) -> bool {
let reads = retain(&mut self.reads, |entry| entry.owner != owner);
let writes = retain(&mut self.writes, |entry| entry.owner != owner);
reads || writes
}
pub(super) fn reclaim(&mut self, transactions: &[Transaction], oldest: u64) -> bool {
let retained = |entry: &Observation| {
transactions
.binary_search_by_key(&entry.owner, |transaction| transaction.id)
.is_ok_and(|position| transactions[position].retains_history(oldest))
};
let reads = retain(&mut self.reads, retained);
let writes = retain(&mut self.writes, retained);
reads || writes
}
fn selected(&self, writing: bool) -> &BudgetedVec<Observation> {
if writing {
&self.writes
} else {
&self.reads
}
}
fn selected_mut(&mut self, writing: bool) -> &mut BudgetedVec<Observation> {
if writing {
&mut self.writes
} else {
&mut self.reads
}
}
}
impl SerializableGraph {
pub fn observe_read(
&mut self,
transaction: SerializableTransactionId,
predicate: SerializablePredicate<'_>,
control: &StorageReadControl,
) -> VersionResult<()> {
self.observe_predicate(transaction, predicate, false, control)
}
pub fn observe_write(
&mut self,
transaction: SerializableTransactionId,
predicate: SerializablePredicate<'_>,
control: &StorageReadControl,
) -> VersionResult<()> {
self.observe_predicate(transaction, predicate, true, control)
}
pub fn write_mark(
&self,
transaction: SerializableTransactionId,
) -> VersionResult<SerializableWriteMark> {
self.check_active(transaction)?;
Ok(SerializableWriteMark {
transaction,
writes: self.transactions[self.position(transaction)?].writes,
})
}
pub fn rollback_writes(&mut self, mark: SerializableWriteMark) -> VersionResult<()> {
let entry = self.transactions[self.position(mark.transaction)?];
if !entry.live() {
return Err(VersionError::TransactionFinished);
}
if entry.prepared.is_some() {
return Err(VersionError::TransactionSealed);
}
if mark.writes > entry.writes {
return Err(VersionError::InvalidEncoding(
"invalid serializable write checkpoint",
));
}
self.checkpoint_changed |= retain(&mut self.predicates.writes, |observed| {
observed.owner != entry.id || observed.write <= mark.writes
});
Ok(())
}
fn observe_predicate(
&mut self,
transaction: SerializableTransactionId,
predicate: SerializablePredicate<'_>,
writing: bool,
control: &StorageReadControl,
) -> VersionResult<()> {
control.check()?;
self.check_active(transaction)?;
predicate.validate(writing)?;
let position = self.position(transaction)?;
let entry = self.transactions[position];
if writing && entry.read_only {
return Err(VersionError::InvalidEncoding(
"read-only serializable transaction cannot observe writes",
));
}
if predicate.is_empty() {
return Ok(());
}
for observed in object_entries(self.predicates.selected(writing), predicate.object) {
control.check()?;
if observed.owner == entry.id && observed.predicate.borrowed() == predicate {
return Ok(());
}
}
let write = if writing {
entry
.writes
.checked_add(1)
.ok_or(VersionError::SequenceExhausted)?
} else {
0
};
let owned = OwnedPredicate::new(predicate, self.transactions.budget())?;
let mut actions = BudgetedVec::<DependencyAction>::new(self.transactions.budget());
for observed in object_entries(self.predicates.selected(!writing), predicate.object) {
control.check()?;
if observed.owner == entry.id {
continue;
}
let other = observed.predicate.borrowed();
let matches = if writing {
other.overlaps_write(predicate)
} else {
predicate.overlaps_write(other)
};
if !matches {
continue;
}
let peer =
SerializableTransactionId::new(self.database, self.coordinator, observed.owner)?;
let (reader, writer) = if writing {
(peer, transaction)
} else {
(transaction, peer)
};
if let Some(action) = self.plan_dependency(transaction, reader, writer, control)? {
if let Err(index) =
actions.binary_search_by_key(&action.edge, |planned| planned.edge)
{
actions.push(action)?;
actions[index..].rotate_right(1);
}
}
}
let mut observed = Observation {
owner: entry.id,
write,
predicate: owned,
fingerprint: [0; 32],
};
observed.fingerprint = super::checkpoint::records::SerializableCheckpointRecord::predicate(
self, &observed, writing,
)
.fingerprint(control)?;
let index = self
.predicates
.selected(writing)
.binary_search_by_key(&(predicate.object, observed.fingerprint), |entry| {
(entry.predicate.object, entry.fingerprint)
})
.err()
.ok_or(VersionError::InvalidEncoding(
"serializable predicate fingerprint collision",
))?;
self.predicates.selected_mut(writing).reserve(1)?;
self.reserve_dependencies(
actions
.iter()
.filter(|action| action.victim.is_none())
.count(),
)?;
control.check()?;
let observations = self.predicates.selected_mut(writing);
observations.push(observed)?;
self.checkpoint_changed = true;
observations[index..].rotate_right(1);
for &action in &*actions {
self.publish_dependency(action)?;
}
if writing {
self.transactions[position].writes = write;
}
Ok(())
}
}
fn object_entries(observations: &[Observation], object: [u8; 16]) -> &[Observation] {
let start = observations.partition_point(|entry| entry.predicate.object < object);
let end = observations.partition_point(|entry| entry.predicate.object <= object);
&observations[start..end]
}
fn retain(
values: &mut BudgetedVec<Observation>,
mut predicate: impl FnMut(&Observation) -> bool,
) -> bool {
let previous = values.len();
let mut kept = 0;
for index in 0..values.len() {
if predicate(&values[index]) {
values.swap(kept, index);
kept += 1;
}
}
values.truncate(kept);
if values.is_empty() {
*values = BudgetedVec::new(values.budget());
}
kept != previous
}