use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use crate::core::{Timestamp, TxnId};
#[derive(Debug)]
pub(crate) struct TxnState {
id: TxnId,
out_conflict: AtomicBool,
in_conflict: AtomicBool,
committed_at: AtomicU64,
aborted: AtomicBool,
}
impl TxnState {
pub(crate) fn new(id: TxnId) -> Arc<Self> {
Arc::new(TxnState {
id,
out_conflict: AtomicBool::new(false),
in_conflict: AtomicBool::new(false),
committed_at: AtomicU64::new(0),
aborted: AtomicBool::new(false),
})
}
pub(crate) fn id(&self) -> TxnId {
self.id
}
pub(crate) fn set_out_conflict(&self) {
self.out_conflict.store(true, Ordering::Release);
}
pub(crate) fn set_in_conflict(&self) {
self.in_conflict.store(true, Ordering::Release);
}
pub(crate) fn has_out_conflict(&self) -> bool {
self.out_conflict.load(Ordering::Acquire)
}
pub(crate) fn is_pivot(&self) -> bool {
self.in_conflict.load(Ordering::Acquire) && self.out_conflict.load(Ordering::Acquire)
}
pub(crate) fn is_committed(&self) -> bool {
self.committed_at.load(Ordering::Acquire) != 0
}
pub(crate) fn is_aborted(&self) -> bool {
self.aborted.load(Ordering::Acquire)
}
pub(crate) fn mark_committed(&self, ts: Timestamp) {
self.committed_at.store(ts.raw(), Ordering::Release);
}
pub(crate) fn mark_aborted(&self) {
self.aborted.store(true, Ordering::Release);
}
pub(crate) fn is_expired(&self, gc_watermark: Timestamp) -> bool {
if self.is_aborted() {
return true;
}
match self.committed_at.load(Ordering::Acquire) {
0 => false, ts => Timestamp(ts) <= gc_watermark,
}
}
}
#[derive(Default, Debug)]
pub(crate) struct Readers(Vec<Arc<TxnState>>);
impl Readers {
pub(crate) fn register(&mut self, state: &Arc<TxnState>) {
if !self.0.iter().any(|r| Arc::ptr_eq(r, state)) {
self.0.push(Arc::clone(state));
}
}
pub(crate) fn others(&mut self, writer: TxnId, gc_watermark: Timestamp) -> Vec<Arc<TxnState>> {
self.0.retain(|r| !r.is_expired(gc_watermark));
self.0.iter().filter(|r| r.id != writer).cloned().collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn state(id: u64) -> Arc<TxnState> {
TxnState::new(TxnId(id))
}
fn live(readers: &mut Readers, writer: TxnId) -> Vec<Arc<TxnState>> {
readers.others(writer, Timestamp(0))
}
#[test]
fn a_transaction_is_a_pivot_only_with_both_edges() {
let t = state(1);
assert!(!t.is_pivot());
t.set_out_conflict();
assert!(!t.is_pivot(), "an outgoing edge alone is not a cycle");
t.set_in_conflict();
assert!(t.is_pivot());
}
#[test]
fn committed_readers_expire_only_once_the_watermark_passes() {
let t = state(1);
assert!(!t.is_expired(Timestamp(100)), "still running");
t.mark_committed(Timestamp(50));
assert!(
!t.is_expired(Timestamp(49)),
"a live snapshot still predates it"
);
assert!(t.is_expired(Timestamp(50)));
assert!(t.is_expired(Timestamp(51)));
}
#[test]
fn aborted_readers_expire_immediately() {
let t = state(1);
t.mark_aborted();
assert!(t.is_expired(Timestamp(0)));
}
#[test]
fn registration_is_idempotent_and_excludes_the_writer() {
let mut readers = Readers::default();
let a = state(1);
let b = state(2);
assert!(
live(&mut readers, TxnId::NONE).is_empty(),
"no readers initially"
);
readers.register(&a);
readers.register(&a);
readers.register(&b);
assert_eq!(
live(&mut readers, TxnId::NONE).len(),
2,
"duplicate registration"
);
assert!(!live(&mut readers, TxnId(1)).is_empty(), "b reads it");
assert!(!live(&mut readers, TxnId(3)).is_empty());
b.mark_aborted();
assert!(live(&mut readers, TxnId(1)).is_empty());
}
}