use std::collections::BTreeMap;
use std::error::Error;
use std::path::Path;
use std::sync::Arc;
use std::thread;
use std::time::SystemTime;
use super::{Database, DatabaseConfig, ReadOnlyDatabase};
use crate::tree::{Hash, empty_root_hash};
type BoxError = Box<dyn Error>;
fn config_for(path: &Path, shard_count: usize) -> DatabaseConfig {
DatabaseConfig {
data_dir: path.to_path_buf(),
shard_count,
distributed: None,
executor_threads: None,
}
}
fn wal_bytes(data_dir: &Path, id: usize) -> Result<Vec<u8>, BoxError> {
let path = data_dir.join(format!("shard-{id}")).join("shard.wal");
match std::fs::read(&path) {
Ok(bytes) => Ok(bytes),
Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(Vec::new()),
Err(error) => Err(error.into()),
}
}
fn wal_mtime(data_dir: &Path, id: usize) -> Result<Option<SystemTime>, BoxError> {
let path = data_dir.join(format!("shard-{id}")).join("shard.wal");
match std::fs::metadata(&path) {
Ok(metadata) => Ok(Some(metadata.modified()?)),
Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(None),
Err(error) => Err(error.into()),
}
}
fn materialise_all(db: &Database, shard_count: usize) -> Result<(), BoxError> {
for shard_id in 0..shard_count {
let _handle = db.handle_for_shard(shard_id)?;
}
Ok(())
}
#[test]
fn zero_dirty_commit_does_nothing() -> Result<(), BoxError> {
let dir = tempfile::tempdir()?;
let data_dir = dir.path().join("db");
let shard_count = 16;
let db = Database::create(config_for(&data_dir, shard_count))?;
materialise_all(&db, shard_count)?;
let roots_first = db.commit()?;
let jobs_after_marker = db.executor().dispatched_jobs();
let workers = db.executor().worker_count();
let bytes_before: Vec<Vec<u8>> = (0..shard_count)
.map(|id| wal_bytes(&data_dir, id))
.collect::<Result<_, _>>()?;
let mtime_before: Vec<Option<SystemTime>> = (0..shard_count)
.map(|id| wal_mtime(&data_dir, id))
.collect::<Result<_, _>>()?;
let roots_second = db.commit()?;
assert_eq!(roots_second, roots_first, "zero-dirty roots are identical");
assert_eq!(
db.executor().dispatched_jobs(),
jobs_after_marker,
"a zero-dirty commit dispatches NO executor job (no shard is dirty)"
);
assert_eq!(
db.executor().worker_count(),
workers,
"the pool creates no thread after startup (bounded census)"
);
for (id, (expected_bytes, expected_mtime)) in bytes_before.iter().zip(&mtime_before).enumerate()
{
assert_eq!(
&wal_bytes(&data_dir, id)?,
expected_bytes,
"shard {id} WAL must be byte-identical after a zero-dirty commit"
);
assert_eq!(
wal_mtime(&data_dir, id)?,
*expected_mtime,
"shard {id} WAL mtime must be unchanged after a zero-dirty commit"
);
}
Ok(())
}
#[test]
fn one_dirty_among_64_dispatches_exactly_one_job() -> Result<(), BoxError> {
let dir = tempfile::tempdir()?;
let data_dir = dir.path().join("db");
let shard_count = 64;
let db = Database::create(config_for(&data_dir, shard_count))?;
materialise_all(&db, shard_count)?;
db.commit()?; let jobs_clean = db.executor().dispatched_jobs();
let key = b"one-dirty-key".to_vec();
let dirty_shard = db.shard_for(&key);
let bytes_before: Vec<Vec<u8>> = (0..shard_count)
.map(|id| wal_bytes(&data_dir, id))
.collect::<Result<_, _>>()?;
db.put(key, b"v".to_vec())?;
db.commit()?;
assert_eq!(
db.executor().dispatched_jobs() - jobs_clean,
1,
"exactly one shard is dirty => exactly one dispatched job"
);
let mut changed = Vec::new();
for (id, before) in bytes_before.iter().enumerate() {
if &wal_bytes(&data_dir, id)? != before {
changed.push(id);
}
}
assert_eq!(
changed,
vec![dirty_shard],
"only the dirtied shard's WAL changes; the other 63 are byte-identical"
);
Ok(())
}
#[test]
fn repeated_commit_returns_cached_non_empty_root() -> Result<(), BoxError> {
let dir = tempfile::tempdir()?;
let data_dir = dir.path().join("db");
let db = Database::create(config_for(&data_dir, 4))?;
db.put(b"cached".to_vec(), b"value".to_vec())?;
let first = db.commit()?;
let dirty_shard = db.shard_for(b"cached");
let cached_root = *first.get(&dirty_shard).ok_or("dirty shard has a root")?;
assert_ne!(
cached_root,
empty_root_hash(),
"a data-bearing shard commits to a NON-empty root"
);
let jobs_before = db.executor().dispatched_jobs();
let repeated = db.commit()?;
assert_eq!(
repeated, first,
"the repeat returns identical roots from the cell"
);
assert_eq!(
*repeated.get(&dirty_shard).ok_or("dirty shard present")?,
cached_root,
"the clean-skip returns the cached NON-empty root, never the empty constant"
);
assert_eq!(
db.executor().dispatched_jobs(),
jobs_before,
"the repeat is a pure clean skip — no job dispatched"
);
drop(db);
let reopened = Database::open(&data_dir)?;
assert_eq!(
reopened.get(b"cached")?,
Some(b"value".to_vec()),
"data survives reopen"
);
assert_eq!(
reopened.commit()?,
first,
"roots survive a reopen byte-identically"
);
Ok(())
}
#[test]
fn per_op_commit_then_global_commit_clean_skips() -> Result<(), BoxError> {
let dir = tempfile::tempdir()?;
let data_dir = dir.path().join("db");
let db = Database::create(config_for(&data_dir, 4))?;
db.cas(b"counter".to_vec(), None, 1)?; let shard = db.shard_for(b"counter");
let after_cas = db.commit_state_snapshot(shard);
assert!(
after_cas.committed_gen == after_cas.dirty_gen && after_cas.root.is_some(),
"a per-op cas leaves the shard clean at a published root"
);
let jobs_before = db.executor().dispatched_jobs();
let roots = db.commit()?;
assert_eq!(
db.executor().dispatched_jobs(),
jobs_before,
"the shard is already clean after the per-op commit — global commit skips it"
);
assert_eq!(
*roots.get(&shard).ok_or("shard root present")?,
after_cas.root.ok_or("cas published a root")?,
"the clean skip returns the per-op committed root"
);
Ok(())
}
#[test]
fn clean_fast_path_second_commit_writes_no_storage() -> Result<(), BoxError> {
let dir = tempfile::tempdir()?;
let data_dir = dir.path().join("db");
let db = Database::create(config_for(&data_dir, 1))?;
db.put(b"k".to_vec(), b"v".to_vec())?;
let handle = db.handle_for_shard(0)?;
let root_first = handle.commit(db.timeout())?; let bytes_before = wal_bytes(&data_dir, 0)?;
let root_second = handle.commit(db.timeout())?;
assert_eq!(
root_second, root_first,
"the fast path returns the cached root"
);
assert_eq!(
wal_bytes(&data_dir, 0)?,
bytes_before,
"the clean fast path performs zero storage mutation (WAL byte-identical)"
);
Ok(())
}
#[test]
fn observer_agrees_after_materialised_empty_commit() -> Result<(), BoxError> {
let dir = tempfile::tempdir()?;
let data_dir = dir.path().join("db");
let db = Database::create(config_for(&data_dir, 4))?;
let _handle = db.handle_for_shard(0)?;
let roots = db.commit()?;
assert_eq!(
*roots.get(&0).ok_or("shard 0 root present")?,
empty_root_hash(),
"the materialised-empty shard commits to the empty root"
);
let observer = ReadOnlyDatabase::open(&data_dir)?;
assert_eq!(
observer.committed_root(0)?,
Some(empty_root_hash()),
"observer agrees: a materialised-empty shard reports Some(empty), not None"
);
assert_eq!(
observer.committed_root(3)?,
None,
"a never-materialised shard is never-committed (observer None)"
);
Ok(())
}
#[test]
fn commit_state_cell_has_stable_pointer_identity() -> Result<(), BoxError> {
let (_dir, db) = {
let dir = tempfile::tempdir()?;
let db = Database::create(config_for(&dir.path().join("db"), 4))?;
(dir, db)
};
let seam = db.seam();
let cell_a = seam.commit_state(2);
let cell_b = seam.commit_state(2);
assert!(
Arc::ptr_eq(&cell_a, &cell_b),
"the same shard id resolves to the SAME cell Arc (pointer identity)"
);
let other = seam.commit_state(3);
assert!(
!Arc::ptr_eq(&cell_a, &other),
"distinct shard ids get distinct cells"
);
Ok(())
}
#[test]
fn commit_from_emission_thread_is_refused() -> Result<(), BoxError> {
let dir = tempfile::tempdir()?;
let data_dir = dir.path().join("db");
let db = Arc::new(Database::create(config_for(&data_dir, 2))?);
let inner = Arc::clone(&db);
let outcome: Arc<std::sync::Mutex<Option<bool>>> = Arc::new(std::sync::Mutex::new(None));
let sink = Arc::clone(&outcome);
let _subscription = db.subscribe_root_advance(move |_advance| {
let refused = matches!(
inner.commit(),
Err(crate::db::DatabaseError::WriteDuringRootAdvanceEmission)
);
if let Ok(mut slot) = sink.lock() {
*slot = Some(refused);
}
});
db.put(b"k".to_vec(), b"v".to_vec())?;
db.commit()?;
let observed = *outcome.lock().map_err(|_| "callback outcome poisoned")?;
assert_eq!(
observed,
Some(true),
"a commit from inside a root-advance callback must be refused"
);
Ok(())
}
#[test]
fn post_wal_commit_barrier_holds_marker_durable_publication_pending() -> Result<(), BoxError> {
use crate::shard::commit_state::{SeamBarrier, arm_seam_barrier};
let dir = tempfile::tempdir()?;
let data_dir = dir.path().join("db");
let db = Arc::new(Database::create(config_for(&data_dir, 1))?);
db.put(b"k".to_vec(), b"v".to_vec())?;
let cell = db.seam().commit_state(0);
let barrier = arm_seam_barrier(&cell, SeamBarrier::PostWalCommit);
let committer = {
let db = Arc::clone(&db);
thread::spawn(move || db.commit().map(|_| ()).map_err(|error| error.to_string()))
};
barrier.wait_until_reached();
let wal_path = data_dir.join("shard-0").join("shard.wal");
let contents = crate::wal::DurableWal::read_file(&wal_path)?;
assert!(
contents.committed_root().is_some(),
"the WAL marker is durable at the barrier"
);
let pending = db.commit_state_snapshot(0);
assert!(
pending.committed_gen != pending.dirty_gen,
"publication pending ⇒ the cell still classifies DIRTY (never falsely clean)"
);
barrier.release();
committer.join().map_err(|_| "committer panicked")??;
let converged = db.commit_state_snapshot(0);
assert!(
converged.committed_gen == converged.dirty_gen && converged.root.is_some(),
"after the publish the shard converges to clean at the published root"
);
Ok(())
}
#[test]
fn recovery_barrier_reader_classifies_dirty_then_converges() -> Result<(), BoxError> {
use crate::shard::commit_state::{SeamBarrier, arm_seam_barrier};
let dir = tempfile::tempdir()?;
let data_dir = dir.path().join("db");
let committed_root;
{
let db = Database::create(config_for(&data_dir, 1))?;
db.put(b"k".to_vec(), b"v".to_vec())?;
committed_root = *db.commit()?.get(&0).ok_or("committed root")?;
}
let db = Arc::new(Database::open(&data_dir)?);
let cell = db.seam().commit_state(0);
let barrier = arm_seam_barrier(&cell, SeamBarrier::Recovery);
let materialiser = {
let db = Arc::clone(&db);
thread::spawn(move || {
db.handle_for_shard(0)
.map(|_| ())
.map_err(|error| error.to_string())
})
};
barrier.wait_until_reached();
let during = db.commit_state_snapshot(0);
assert!(
during.recovering,
"the recovering flag is set during the pause"
);
assert!(
cell.classify().dirty,
"a reader mid-recovery classifies the shard DIRTY (no clean-skip)"
);
barrier.release();
materialiser.join().map_err(|_| "materialiser panicked")??;
let after = db.commit_state_snapshot(0);
assert!(
after.committed_gen == after.dirty_gen && after.root == Some(committed_root),
"recovery converges to clean at the recovered marker root"
);
assert_eq!(
db.get(b"k")?,
Some(b"v".to_vec()),
"the committed data is intact after the paused recovery"
);
Ok(())
}
#[test]
fn wal_replacement_failure_keeps_shard_dirty_then_retry_converges() -> Result<(), BoxError> {
use crate::wal::durable::{WalReplaceFailure, arm_replacement_failure};
let dir = tempfile::tempdir()?;
let data_dir = dir.path().join("db");
let db = Database::create(config_for(&data_dir, 1))?;
db.put(b"k".to_vec(), b"v".to_vec())?; let dirty = db.commit_state_snapshot(0);
assert!(
dirty.committed_gen != dirty.dirty_gen,
"the buffered put classifies dirty before the commit"
);
let wal_path = data_dir.join("shard-0").join("shard.wal");
arm_replacement_failure(&wal_path, WalReplaceFailure::BeforePersist);
assert!(
db.commit().is_err(),
"the WAL replacement failure surfaces as a commit error"
);
let after_failure = db.commit_state_snapshot(0);
assert!(
after_failure.committed_gen != after_failure.dirty_gen,
"an ordinary commit failure retains the buffer => shard STAYS DIRTY"
);
db.commit()?;
let converged = db.commit_state_snapshot(0);
assert!(
converged.committed_gen == converged.dirty_gen && converged.root.is_some(),
"the retry commits the retained buffer => clean at a published root"
);
assert_eq!(
db.get(b"k")?,
Some(b"v".to_vec()),
"the write is durable after convergence"
);
Ok(())
}
#[test]
fn concurrent_commits_on_markerless_shard_publish_one_marker() -> Result<(), BoxError> {
let dir = tempfile::tempdir()?;
let data_dir = dir.path().join("db");
let db = Arc::new(Database::create(config_for(&data_dir, 4))?);
let _handle = db.handle_for_shard(0)?;
let barrier = Arc::new(std::sync::Barrier::new(2));
let mut roots = Vec::new();
let handles: Vec<_> = (0..2)
.map(|_| {
let db = Arc::clone(&db);
let barrier = Arc::clone(&barrier);
thread::spawn(move || -> Result<BTreeMap<usize, Hash>, String> {
barrier.wait();
db.commit().map_err(|error| error.to_string())
})
})
.collect();
for handle in handles {
roots.push(handle.join().map_err(|_| "committer panicked")??);
}
let (first, second) = (&roots[0], &roots[1]);
assert_eq!(
first, second,
"both racing commits return the identical root vector"
);
assert_eq!(
*first.get(&0).ok_or("shard 0 root present")?,
empty_root_hash(),
"the marker-less empty shard commits to the empty root for both callers"
);
let jobs_before = db.executor().dispatched_jobs();
db.commit()?;
assert_eq!(
db.executor().dispatched_jobs(),
jobs_before,
"after the one-time marker publication the shard is clean — no further job"
);
let observer = ReadOnlyDatabase::open(&data_dir)?;
assert_eq!(
observer.committed_root(0)?,
Some(empty_root_hash()),
"observer agrees the shard is committed-empty after the concurrent commits"
);
Ok(())
}
#[test]
fn write_racing_commit_is_never_lost_both_interleavings() -> Result<(), BoxError> {
let dir_a = tempfile::tempdir()?;
let db_a = Database::create(config_for(&dir_a.path().join("db"), 4))?;
let shard_a = db_a.shard_for(b"key-a");
let _handle = db_a.handle_for_shard(shard_a)?;
let pre_write = *db_a.commit()?.get(&shard_a).ok_or("pre-write root")?;
db_a.put(b"key-a".to_vec(), b"va".to_vec())?; let round = db_a.commit()?; assert_ne!(
*round.get(&shard_a).ok_or("shard root present")?,
pre_write,
"interleaving A: the write is committed in the SAME round (root advanced)"
);
assert_eq!(
db_a.get(b"key-a")?,
Some(b"va".to_vec()),
"interleaving A: the write is durable"
);
let dir_b = tempfile::tempdir()?;
let db_b = Database::create(config_for(&dir_b.path().join("db"), 4))?;
let shard_b = db_b.shard_for(b"key-b");
let _handle_b = db_b.handle_for_shard(shard_b)?;
let before = *db_b.commit()?.get(&shard_b).ok_or("pre-write root b")?;
db_b.put(b"key-b".to_vec(), b"vb".to_vec())?; let next = db_b.commit()?; assert_eq!(
before,
empty_root_hash(),
"interleaving B: the pre-write commit returned the empty pre-write root"
);
assert_ne!(
*next.get(&shard_b).ok_or("shard root present b")?,
before,
"interleaving B: the NEXT commit picks up the write (root advanced)"
);
assert_eq!(
db_b.get(b"key-b")?,
Some(b"vb".to_vec()),
"interleaving B: the write is never lost"
);
Ok(())
}
#[test]
fn bounded_threads_hold_under_sustained_load() -> Result<(), BoxError> {
let dir = tempfile::tempdir()?;
let data_dir = dir.path().join("db");
let shard_count = 8;
let db = Arc::new(Database::create(config_for(&data_dir, shard_count))?);
materialise_all(&db, shard_count)?;
db.commit()?;
let workers_at_start = db.executor().worker_count();
let ceiling = db.executor().max_admitted_batches() * shard_count;
let committers = 6;
let iterations = 25;
let barrier = Arc::new(std::sync::Barrier::new(committers));
let handles: Vec<_> = (0..committers)
.map(|thread_id| {
let db = Arc::clone(&db);
let barrier = Arc::clone(&barrier);
thread::spawn(move || -> Result<(), String> {
barrier.wait();
for round in 0..iterations {
let key = format!("t{thread_id}-r{round}").into_bytes();
db.put(key, vec![thread_id as u8])
.map_err(|e| e.to_string())?;
db.commit().map_err(|e| e.to_string())?;
assert!(
db.executor().queued_and_running() <= ceiling,
"queued+running exceeded the admission ceiling under load"
);
}
Ok(())
})
})
.collect();
for handle in handles {
handle.join().map_err(|_| "committer panicked")??;
}
assert_eq!(
db.executor().worker_count(),
workers_at_start,
"the executor spawned no thread after startup under sustained load (census delta = 0)"
);
Ok(())
}
#[test]
fn mixed_dirty_clean_assembly_is_total_and_stable() -> Result<(), BoxError> {
let dir = tempfile::tempdir()?;
let data_dir = dir.path().join("db");
let shard_count = 8;
let db = Database::create(config_for(&data_dir, shard_count))?;
db.put(b"alpha".to_vec(), b"1".to_vec())?;
db.put(b"beta".to_vec(), b"2".to_vec())?;
let first: BTreeMap<usize, Hash> = db.commit()?;
assert_eq!(
first.len(),
shard_count,
"every slot is present (total assembly)"
);
db.put(b"alpha".to_vec(), b"1b".to_vec())?;
let second = db.commit()?;
assert_eq!(second.len(), shard_count);
for id in 0..shard_count {
assert!(
second.contains_key(&id),
"slot {id} present after the second commit"
);
if id != db.shard_for(b"alpha") {
assert_eq!(
second.get(&id),
first.get(&id),
"an untouched shard's root is stable across commits"
);
}
}
Ok(())
}