use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use super::commit::{BranchCommitError, CommitDurability, CommitRequest, commit_branch};
use super::fork::fork_registered;
use super::handle::DEFAULT_SHARD_ID;
use super::registry::BranchRegistry;
use crate::store::MemoryStore;
use crate::tree::{Hash, LeafNode, Node, TreeError, insert};
type TestResult = Result<(), BranchCommitError>;
fn build_tree(store: &mut MemoryStore, entries: &[(&[u8], &[u8])]) -> Result<Hash, TreeError> {
let leaf = LeafNode::new(Vec::new())?;
let mut root = store.put(&Node::Leaf(leaf));
for (key, value) in entries {
root = insert(store, root, key, value)?;
}
Ok(root)
}
fn volatile() -> CommitRequest<'static> {
CommitRequest {
durability: CommitDurability::Volatile,
extra_parents: &[],
timestamp: 1,
}
}
#[test]
fn every_commit_registers_the_advanced_root_before_returning() -> TestResult {
let mut store = MemoryStore::new();
let anchor = build_tree(&mut store, &[(b"base", b"tree")])?;
let registry = BranchRegistry::new();
let branch = fork_registered(anchor, ®istry);
let mut previous_head = anchor;
for round in 0u32..50 {
branch.put(DEFAULT_SHARD_ID, b"key", round.to_le_bytes())?;
commit_branch(&branch, &mut store, ®istry, volatile())?;
let head = branch.current_root();
assert_ne!(head, previous_head, "each round must advance the head");
let live = registry.live_roots();
assert!(
live.contains(&head),
"round {round}: the advanced root must be registered when commit returns"
);
assert!(
live.contains(&anchor),
"round {round}: the anchor-role pin must survive every advance"
);
if previous_head != anchor {
assert!(
!live.contains(&previous_head),
"round {round}: the superseded head must be released"
);
}
assert_eq!(
live.len(),
2,
"round {round}: the pin set must be exactly {{anchor, head}}"
);
previous_head = head;
}
Ok(())
}
#[test]
fn concurrent_live_roots_snapshots_never_observe_a_pin_gap() -> TestResult {
let mut store = MemoryStore::new();
let anchor = build_tree(&mut store, &[(b"base", b"tree")])?;
let registry = BranchRegistry::new();
let branch = fork_registered(anchor, ®istry);
branch.put(DEFAULT_SHARD_ID, b"key", 0u32.to_le_bytes())?;
commit_branch(&branch, &mut store, ®istry, volatile())?;
let stop = Arc::new(AtomicBool::new(false));
let violations = Arc::new(AtomicUsize::new(0));
let spy = {
let registry = registry.clone();
let stop = Arc::clone(&stop);
let violations = Arc::clone(&violations);
std::thread::spawn(move || {
let mut snapshots = 0u64;
while !stop.load(Ordering::Relaxed) {
let live = registry.live_roots();
if !live.contains(&anchor) || live.len() < 2 {
violations.fetch_add(1, Ordering::Relaxed);
}
snapshots += 1;
}
snapshots
})
};
for round in 1u32..400 {
branch.put(DEFAULT_SHARD_ID, b"key", round.to_le_bytes())?;
commit_branch(&branch, &mut store, ®istry, volatile())?;
}
stop.store(true, Ordering::Relaxed);
let snapshots = spy.join();
assert!(
matches!(snapshots, Ok(count) if count > 0),
"the spy must have taken at least one snapshot"
);
assert_eq!(
violations.load(Ordering::Relaxed),
0,
"a live_roots() snapshot observed a pin gap during the commit storm"
);
Ok(())
}