use crate::cc::{context::Context, group::TxnFact};
use crate::must_true;
use crate::utils::options::Options;
use std::sync::atomic::Ordering::Acquire;
pub(crate) mod context;
pub(crate) mod group;
pub(crate) mod log;
pub(crate) mod wal;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct SnapshotStamp {
pub start_ts: u64,
pub owner_group: Option<u8>,
}
impl SnapshotStamp {
#[inline]
pub fn txn(owner_group: u8, start_ts: u64) -> Self {
Self {
start_ts,
owner_group: Some(owner_group),
}
}
#[inline]
pub fn view(start_ts: u64) -> Self {
Self {
start_ts,
owner_group: None,
}
}
}
#[inline(always)]
fn is_settled_history(ctx: &Context, record_gid: u8) -> bool {
must_true!(
record_gid < Options::MAX_CONCURRENT_WRITE,
"impossible record group id {record_gid}"
);
record_gid as usize >= ctx.groups().len()
}
#[inline(always)]
pub(crate) fn is_retained_abort(ctx: &Context, record_gid: u8, record_txid: u64) -> bool {
!is_settled_history(ctx, record_gid)
&& ctx
.group(record_gid as usize)
.is_retained_abort(record_txid)
}
#[inline]
pub(crate) fn is_visible_to(
ctx: &Context,
snapshot: SnapshotStamp,
record_gid: u8,
record_txid: u64,
) -> bool {
if record_txid == snapshot.start_ts {
return snapshot.owner_group == Some(record_gid);
}
if record_txid >= snapshot.start_ts {
return false;
}
let safe_exclusive = ctx.safe_exclusive();
if is_settled_history(ctx, record_gid) {
return true;
}
let record_group = ctx.group(record_gid as usize);
if record_txid < safe_exclusive {
return validate_positive_proof(record_group, record_txid);
}
#[cfg(feature = "extra_check")]
crate::testing::fire_visibility_sync_point(
crate::testing::VisibilitySyncPoint::AfterProofMissBeforeExactRead,
record_txid,
);
match record_group.read_fact(record_txid) {
Some(TxnFact::Committed(commit_ts)) => commit_ts < snapshot.start_ts,
Some(TxnFact::Active(_) | TxnFact::Aborted) => false,
None => {
#[cfg(feature = "extra_check")]
crate::testing::fire_visibility_sync_point(
crate::testing::VisibilitySyncPoint::AfterExactMissBeforeSecondSafeRead,
record_txid,
);
record_txid < ctx.safe_exclusive()
}
}
}
#[inline(always)]
fn validate_positive_proof(record_group: &crate::cc::group::WriterGroup, record_txid: u64) -> bool {
loop {
let seq1 = record_group.abort_seq();
if !seq1.is_multiple_of(2) {
std::hint::spin_loop();
continue;
}
#[cfg(feature = "extra_check")]
crate::testing::fire_visibility_sync_point(
crate::testing::VisibilitySyncPoint::AfterPositiveFastPathBeforeRetainedAbortCheck,
record_txid,
);
let floor = record_group.retained_abort_floor.load(Acquire);
if record_txid < floor {
let seq2 = record_group.abort_seq();
if seq1 == seq2 {
return true;
}
continue;
}
let aborted = record_group.is_retained_abort(record_txid);
let seq2 = record_group.abort_seq();
if seq1 == seq2 {
return !aborted;
}
}
}
#[cfg(test)]
mod tests {
use super::{SnapshotStamp, is_retained_abort, is_visible_to};
use crate::RandomPath;
use crate::cc::context::{Context, GroupBoot};
use crate::error::test_support::{assert_aborted, is_abort_child, run_child_test};
use crate::meta::Sequences;
use crate::utils::data::Position;
use crate::utils::options::Options;
use std::sync::Arc;
const WIDTH: u8 = 2;
const RETIRED_GID: u8 = 5;
const LAST_PERSISTED_GID: u8 = Options::MAX_CONCURRENT_WRITE - 1;
fn test_boot(physical_wal_id: u8) -> GroupBoot {
GroupBoot {
physical_wal_id,
oldest_id: 0,
latest_id: 0,
scan_start: Position::MIN,
has_files: false,
start_id: 0,
checkpoint_floor: Position::MIN,
}
}
fn narrow_context() -> (RandomPath, Context) {
let root = RandomPath::tmp();
let mut opt = Options::new(&*root);
opt.concurrent_write = WIDTH;
let mut boots: Vec<GroupBoot> = (0..WIDTH).map(test_boot).collect();
boots.push(test_boot(Options::SHARED_ID));
let ctx = Context::new(
Arc::new(opt.validate().expect("context options must validate")),
WIDTH,
Arc::new(Sequences::default()),
&boots,
);
assert_eq!(ctx.groups().len(), WIDTH as usize);
(root, ctx)
}
#[test]
fn settled_history_group_is_visible_without_a_group_lookup() {
let (_root, ctx) = narrow_context();
for gid in [WIDTH, RETIRED_GID, LAST_PERSISTED_GID] {
assert!(
is_visible_to(&ctx, SnapshotStamp::view(100), gid, 10),
"retired gid {gid} must read as a committed historical version"
);
}
}
#[test]
fn retired_group_id_is_never_a_retained_abort() {
let (_root, ctx) = narrow_context();
ctx.group(1).recover_retained_abort(10);
assert!(
is_retained_abort(&ctx, 1, 10),
"a live group's retained abort must stay observable"
);
assert!(
!is_retained_abort(&ctx, 1, 11),
"only the aborted txid is retained"
);
for gid in [WIDTH, RETIRED_GID, LAST_PERSISTED_GID] {
assert!(
!is_retained_abort(&ctx, gid, 10),
"retired gid {gid} cannot hold a retained abort"
);
}
}
#[test]
fn impossible_record_group_id_is_rejected() {
let output = run_child_test("cc::tests::impossible_record_group_id_is_rejected_child");
assert_aborted(
&output,
&[
"fatal invariant failure",
"check=must_true",
"impossible record group id",
],
);
}
#[test]
fn impossible_record_group_id_is_rejected_child() {
if !is_abort_child() {
return;
}
let (_root, ctx) = narrow_context();
let _ = is_visible_to(
&ctx,
SnapshotStamp::view(100),
Options::MAX_CONCURRENT_WRITE,
10,
);
}
#[test]
fn own_write_and_concurrent_records_precede_the_settled_history_branch() {
let (_root, ctx) = narrow_context();
assert!(is_visible_to(
&ctx,
SnapshotStamp::txn(RETIRED_GID, 10),
RETIRED_GID,
10
));
assert!(!is_visible_to(
&ctx,
SnapshotStamp::txn(1, 10),
RETIRED_GID,
10
));
assert!(!is_visible_to(
&ctx,
SnapshotStamp::view(10),
RETIRED_GID,
10
));
}
}