use fathomdb_embedder_api::{Embedder, EmbedderError, EmbedderIdentity, Vector};
use fathomdb_engine::{Engine, EngineError, InitialState, LifecycleState, PreparedWrite, SourceId};
use fathomdb_schema::SQLITE_SUFFIX;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::Arc;
use std::time::{Duration, Instant};
use tempfile::TempDir;
#[derive(Debug)]
struct CountingDelayEmbedder {
identity: EmbedderIdentity,
calls: Arc<AtomicUsize>,
delay: Duration,
}
impl CountingDelayEmbedder {
fn new(calls: Arc<AtomicUsize>, delay: Duration) -> Self {
Self { identity: EmbedderIdentity::new("deterministic", "rev-a", 384), calls, delay }
}
}
impl Embedder for CountingDelayEmbedder {
fn identity(&self) -> EmbedderIdentity {
self.identity.clone()
}
fn embed(&self, _text: &str) -> Result<Vector, EmbedderError> {
self.calls.fetch_add(1, Ordering::SeqCst);
if !self.delay.is_zero() {
std::thread::sleep(self.delay);
}
let mut v = vec![0.0_f32; self.identity.dimension as usize];
v[0] = 1.0;
Ok(v)
}
}
fn governed_node(logical_id: &str, body_tag: &str) -> PreparedWrite {
PreparedWrite::Node {
kind: "doc".to_string(),
body: format!(r#"{{"summary":"tc90 {body_tag}"}}"#),
source_id: SourceId::new("test:fixture").expect("source id"),
logical_id: Some(logical_id.to_string()),
state: InitialState::Active,
reason: None,
valid_from: None,
valid_until: None,
}
}
const SUBJECTS: usize = 8;
const TRANSITIONS: usize = 120;
const STRESS_TRANSITIONS: usize = 40;
const STRESS_BUDGET: Duration = Duration::from_secs(120);
struct ArmConfig {
label: &'static str,
second_writer: bool,
writer_threads: usize,
burst: usize,
writer_pace_ms: u64,
transitions: usize,
embed_delay_ms: u64,
budget: Duration,
}
struct ArmOutcome {
label: &'static str,
attempted: usize,
truncated: bool,
first_storage_failure: Option<(usize, EngineError)>,
other_error_kinds: Vec<String>,
storage_errors: usize,
scheduler_errors: usize,
other_errors: usize,
competing_writes: usize,
competing_write_errors: usize,
embed_calls: usize,
slowest_ok_ms: u128,
failure_wall_ms: u128,
}
impl ArmOutcome {
fn report(&self) {
println!(
"TC90 arm={} attempted={} truncated={} storage_errors={} scheduler_errors={} \
other_errors={} competing_writes={} competing_write_errors={} embed_calls={} \
slowest_ok_ms={} failure_wall_ms={} other_error_kinds={:?}",
self.label,
self.attempted,
self.truncated,
self.storage_errors,
self.scheduler_errors,
self.other_errors,
self.competing_writes,
self.competing_write_errors,
self.embed_calls,
self.slowest_ok_ms,
self.failure_wall_ms,
self.other_error_kinds,
);
}
}
fn run_arm(config: &ArmConfig) -> ArmOutcome {
let label = config.label;
let dir = TempDir::new().expect("tempdir");
let path = dir.path().join(format!("tc90_{label}{SQLITE_SUFFIX}"));
let calls = Arc::new(AtomicUsize::new(0));
let opened = Engine::open_with_embedder_for_test(
&path,
Arc::new(CountingDelayEmbedder::new(
calls.clone(),
Duration::from_millis(config.embed_delay_ms),
)),
)
.expect("open");
let engine = Arc::new(opened.engine);
if config.second_writer {
engine.configure_vector_kind_for_test("doc").expect("vector kind");
}
for s in 0..SUBJECTS {
engine
.write(&[governed_node(&format!("tc90-subject-{s}"), &format!("subject {s}"))])
.expect("seed write");
}
engine.drain(60_000).expect("seed drain");
let stop = Arc::new(AtomicBool::new(false));
let competing_writes = Arc::new(AtomicUsize::new(0));
let competing_errors = Arc::new(AtomicUsize::new(0));
let burst = config.burst;
let pace = config.writer_pace_ms;
let mut writers = Vec::with_capacity(config.writer_threads);
for t in 0..config.writer_threads {
let engine = Arc::clone(&engine);
let stop = Arc::clone(&stop);
let writes = Arc::clone(&competing_writes);
let errors = Arc::clone(&competing_errors);
writers.push(std::thread::spawn(move || {
let mut i = 0_usize;
while !stop.load(Ordering::Relaxed) {
let batch: Vec<PreparedWrite> = (0..burst)
.map(|b| {
governed_node(
&format!("tc90-load-{label}-{t}-{i}-{b}"),
&format!("load {label} {t} {i} {b}"),
)
})
.collect();
match engine.write(&batch) {
Ok(_) => {
writes.fetch_add(burst, Ordering::SeqCst);
}
Err(_) => {
errors.fetch_add(1, Ordering::SeqCst);
}
}
i += 1;
if pace > 0 {
std::thread::sleep(Duration::from_millis(pace));
}
}
}));
}
let mut attempted = 0_usize;
let mut first_storage_failure = None;
let mut storage_errors = 0_usize;
let mut scheduler_errors = 0_usize;
let mut other_errors = 0_usize;
let mut other_error_kinds: Vec<String> = Vec::new();
let mut slowest_ok_ms = 0_u128;
let mut failure_wall_ms = 0_u128;
let mut truncated = false;
let mut subject_states = [LifecycleState::Active; SUBJECTS];
let loop_deadline = Instant::now() + config.budget;
for i in 0..config.transitions {
if Instant::now() >= loop_deadline {
truncated = true;
break;
}
let slot = i % SUBJECTS;
let subject = format!("tc90-subject-{slot}");
let to_state = match subject_states[slot] {
LifecycleState::Active => LifecycleState::Deleted,
_ => LifecycleState::Active,
};
let started = Instant::now();
let result = engine.transition(&subject, to_state, None);
let elapsed = started.elapsed().as_millis();
attempted += 1;
match result {
Ok(()) => {
subject_states[slot] = to_state;
slowest_ok_ms = slowest_ok_ms.max(elapsed);
}
Err(err) => match err {
EngineError::Storage => {
storage_errors += 1;
if first_storage_failure.is_none() {
failure_wall_ms = elapsed;
first_storage_failure = Some((i, err));
}
}
EngineError::Scheduler => scheduler_errors += 1,
other => {
other_errors += 1;
let kind = format!("{other:?}");
if !other_error_kinds.contains(&kind) {
other_error_kinds.push(kind);
}
}
},
}
}
stop.store(true, Ordering::Relaxed);
for writer in writers {
let _ = writer.join();
}
let _ = engine.drain(60_000);
let embed_calls = calls.load(Ordering::SeqCst);
let _ = engine.close();
let outcome = ArmOutcome {
label,
attempted,
truncated,
first_storage_failure,
other_error_kinds,
storage_errors,
scheduler_errors,
other_errors,
competing_writes: competing_writes.load(Ordering::SeqCst),
competing_write_errors: competing_errors.load(Ordering::SeqCst),
embed_calls,
slowest_ok_ms,
failure_wall_ms,
};
outcome.report();
outcome
}
#[test]
#[ignore = "TC-90 characterization arm — run explicitly with --ignored; see the design doc"]
fn tc90_repro_transition_loop_races_projection_worker() {
let outcome = run_arm(&ArmConfig {
label: "repro",
second_writer: true,
writer_threads: 1,
burst: 1,
writer_pace_ms: 3,
embed_delay_ms: 2,
transitions: TRANSITIONS,
budget: Duration::from_secs(120),
});
assert!(
outcome.embed_calls > 0,
"non-vacuity: the projection worker must have embedded at least one row, else there \
was no second writer and the promote window never existed"
);
assert_protocol_ran(&outcome, TRANSITIONS);
if let Some((i, err)) = &outcome.first_storage_failure {
panic!(
"TC-90 REPRO: transition {i} of {} failed with {err:?} after {} ms \
(slowest OK transition {} ms); storage={} scheduler={} other={} kinds={:?}",
outcome.attempted,
outcome.failure_wall_ms,
outcome.slowest_ok_ms,
outcome.storage_errors,
outcome.scheduler_errors,
outcome.other_errors,
outcome.other_error_kinds,
);
}
}
#[test]
#[ignore = "TC-90 characterization arm — run explicitly with --ignored; see the design doc"]
fn tc90_control_transition_loop_without_second_writer() {
let outcome = run_arm(&ArmConfig {
label: "control",
second_writer: false,
writer_threads: 1,
burst: 1,
writer_pace_ms: 3,
embed_delay_ms: 2,
transitions: TRANSITIONS,
budget: Duration::from_secs(120),
});
assert_eq!(
outcome.embed_calls, 0,
"the control's defining property: with no vector kind enrolled the worker performs no \
embed and therefore never commits — there is no second writer"
);
assert_protocol_ran(&outcome, TRANSITIONS);
if let Some((i, err)) = &outcome.first_storage_failure {
panic!(
"TC-90 CONTROL FAILED — this is a FINDING, not a flake: transition {i} of {} \
failed with {err:?} after {} ms with NO second writer present; \
storage={} scheduler={} other={} kinds={:?}",
outcome.attempted,
outcome.failure_wall_ms,
outcome.storage_errors,
outcome.scheduler_errors,
outcome.other_errors,
outcome.other_error_kinds,
);
}
}
fn assert_protocol_ran(outcome: &ArmOutcome, expected_transitions: usize) {
assert_eq!(
outcome.attempted, expected_transitions,
"non-vacuity: the arm must have issued the FULL protocol it claims to measure — \
{} of {expected_transitions} transitions issued (truncated={})",
outcome.attempted, outcome.truncated,
);
assert!(
!outcome.truncated,
"non-vacuity: the loop hit its wall-clock budget before issuing every transition, so \
this run measured a SHORTER protocol than the one on the record. Do not loosen this \
assertion to make it pass — a budget hit is itself a finding (design doc §2.5: under \
saturating load `transition`'s own `drain` can burn 30 s per call).",
);
assert!(
outcome.competing_writes > 0,
"non-vacuity: the competing writer(s) must have landed rows, else nothing ever re-armed \
the dispatcher and there was no load to compare against"
);
assert_eq!(
outcome.scheduler_errors, 0,
"the arm must be measuring the PROMOTE RACE, not drain starvation: {} of {} transitions \
returned `EngineError::Scheduler` (a burnt 30 s drain timeout, design doc §2.5). A run \
dominated by these has not measured TC-90 at all. `writer_pace_ms` is the knob — 1 ms is \
near-saturating but still lets `drain` reach idle; 0 ms does not.",
outcome.scheduler_errors, outcome.attempted,
);
assert_eq!(
outcome.other_errors, 0,
"the arm must produce no error variant other than `Storage`: {} seen, kinds={:?}. \
`IllegalTransition` here would mean the harness's own subject-state tracking has \
regressed into the self-loop cascade artifact described at `subject_states`, which \
contaminates the counter under measurement.",
outcome.other_errors, outcome.other_error_kinds,
);
}
#[test]
#[ignore = "TC-90 characterization arm — run explicitly with --ignored; see the design doc"]
fn tc90_stress_transition_loop_under_saturating_burst_load() {
let outcome = run_arm(&ArmConfig {
label: "stress",
second_writer: true,
writer_threads: 2,
burst: 24,
writer_pace_ms: 1,
embed_delay_ms: 0,
transitions: STRESS_TRANSITIONS,
budget: STRESS_BUDGET,
});
assert!(
outcome.embed_calls > 0,
"non-vacuity: the projection worker must have embedded at least one row, else there \
was no second writer and the promote window never existed"
);
assert_protocol_ran(&outcome, STRESS_TRANSITIONS);
assert_eq!(
outcome.storage_errors,
0,
"TC-90 STRESS: {} of {} transitions failed with `EngineError::Storage` \
(first at index {:?} after {} ms) — the promote race IS reachable; \
scheduler={} other={} kinds={:?}",
outcome.storage_errors,
outcome.attempted,
outcome.first_storage_failure.as_ref().map(|(i, _)| *i),
outcome.failure_wall_ms,
outcome.scheduler_errors,
outcome.other_errors,
outcome.other_error_kinds,
);
}
#[test]
#[ignore = "TC-90 characterization arm — run explicitly with --ignored; see the design doc"]
fn tc90_stress_control_without_second_writer() {
let outcome = run_arm(&ArmConfig {
label: "stress_control",
second_writer: false,
writer_threads: 2,
burst: 24,
writer_pace_ms: 1,
embed_delay_ms: 0,
transitions: STRESS_TRANSITIONS,
budget: STRESS_BUDGET,
});
assert_eq!(
outcome.embed_calls, 0,
"the control's defining property: no vector kind ⇒ no embed ⇒ no worker commit"
);
assert_protocol_ran(&outcome, STRESS_TRANSITIONS);
assert_eq!(
outcome.storage_errors,
0,
"TC-90 STRESS CONTROL FAILED — a FINDING, not a flake: {} storage errors over {} \
transitions with NO second writer present; scheduler={} other={} kinds={:?}",
outcome.storage_errors,
outcome.attempted,
outcome.scheduler_errors,
outcome.other_errors,
outcome.other_error_kinds,
);
}
static TRANSITION_HANDLER_CALLS: AtomicUsize = AtomicUsize::new(0);
fn transition_busy_handler(_attempts: i32) -> bool {
TRANSITION_HANDLER_CALLS.fetch_add(1, Ordering::SeqCst);
std::thread::sleep(Duration::from_millis(5));
true
}
fn seeded_engine_db(dir: &TempDir, name: &str, logical_id: &str) -> std::path::PathBuf {
let path = dir.path().join(format!("{name}{SQLITE_SUFFIX}"));
let calls = Arc::new(AtomicUsize::new(0));
let opened = Engine::open_with_embedder_for_test(
&path,
Arc::new(CountingDelayEmbedder::new(calls, Duration::ZERO)),
)
.expect("open");
opened.engine.write(&[governed_node(logical_id, "mechanism seed")]).expect("seed");
opened.engine.drain(60_000).expect("drain");
opened.engine.close().expect("close");
path
}
#[test]
fn tc90_mechanism_transition_sql_shape_on_real_schema_is_busy_5() {
let dir = TempDir::new().expect("tempdir");
let path = seeded_engine_db(&dir, "tc90_mech_sql", "tc90-mech");
let a = rusqlite::Connection::open(&path).expect("open a");
let b = rusqlite::Connection::open(&path).expect("open b");
TRANSITION_HANDLER_CALLS.store(0, Ordering::SeqCst);
a.busy_timeout(Duration::from_secs(5)).expect("busy timeout a");
a.busy_handler(Some(transition_busy_handler)).expect("busy handler a");
b.execute_batch("BEGIN IMMEDIATE;").expect("b takes the write lock");
b.execute(
"INSERT OR IGNORE INTO _fathomdb_projection_terminal(write_cursor, state) VALUES(?1, ?2)",
rusqlite::params![9_999_999_i64, "up_to_date"],
)
.expect("b writes under its lock");
a.execute_batch("BEGIN DEFERRED;").expect("begin deferred");
let state: String = a
.query_row(
"SELECT state, write_cursor, body FROM canonical_nodes \
WHERE logical_id = ?1 AND superseded_at IS NULL",
rusqlite::params!["tc90-mech"],
|r| r.get::<_, String>(0),
)
.expect("the transition read must find the seeded row");
assert_eq!(state, "active", "fixture sanity: the seeded row is active");
let started = Instant::now();
let err = a
.execute(
"UPDATE canonical_nodes SET state = ?1, reason = ?2 \
WHERE logical_id = ?3 AND superseded_at IS NULL",
rusqlite::params!["deleted", None::<String>, "tc90-mech"],
)
.expect_err("the promotion must fail while B holds the write lock");
let elapsed = started.elapsed();
let sqlite_err =
err.sqlite_error().unwrap_or_else(|| panic!("expected a SqliteFailure, got {err:?}"));
println!(
"TC90-MECH sql_shape primary={:?} extended={} handler_calls={} elapsed_ms={}",
sqlite_err.code,
sqlite_err.extended_code,
TRANSITION_HANDLER_CALLS.load(Ordering::SeqCst),
elapsed.as_millis(),
);
assert_eq!(
sqlite_err.code,
rusqlite::ErrorCode::DatabaseBusy,
"the primary code of a refused promotion is SQLITE_BUSY"
);
assert_eq!(
sqlite_err.extended_code,
rusqlite::ffi::SQLITE_BUSY,
"and the EXTENDED code is plain 5, not SQLITE_BUSY_SNAPSHOT (517) — the same \
correction TC-57 §0 had to make. Got {}",
sqlite_err.extended_code
);
assert_eq!(
TRANSITION_HANDLER_CALLS.load(Ordering::SeqCst),
0,
"THE POINT: SQLite skips the busy handler when promoting a read transaction \
(deadlock avoidance), so no `busy_timeout` value could retry `transition` either"
);
assert!(
elapsed < Duration::from_millis(500),
"and it fails instantly rather than after any backoff (took {elapsed:?})"
);
let _ = a.execute_batch("ROLLBACK;");
let _ = b.execute_batch("ROLLBACK;");
}
#[test]
fn tc90_mechanism_engine_transition_under_held_write_lock_fails_immediately() {
let dir = TempDir::new().expect("tempdir");
let path = dir.path().join(format!("tc90_mech_engine{SQLITE_SUFFIX}"));
let calls = Arc::new(AtomicUsize::new(0));
let opened = Engine::open_with_embedder_for_test(
&path,
Arc::new(CountingDelayEmbedder::new(calls, Duration::ZERO)),
)
.expect("open");
let engine = &opened.engine;
engine.write(&[governed_node("tc90-held", "held-lock subject")]).expect("seed");
engine.drain(60_000).expect("drain");
let blocker = rusqlite::Connection::open(&path).expect("open blocker");
blocker.execute_batch("BEGIN IMMEDIATE;").expect("blocker takes the write lock");
blocker
.execute(
"INSERT OR IGNORE INTO _fathomdb_projection_terminal(write_cursor, state) \
VALUES(?1, ?2)",
rusqlite::params![9_999_998_i64, "up_to_date"],
)
.expect("blocker writes under its lock");
let started = Instant::now();
let result = engine.transition("tc90-held", LifecycleState::Deleted, None);
let elapsed = started.elapsed();
println!(
"TC90-MECH engine_transition result={:?} elapsed_ms={}",
result.as_ref().err(),
elapsed.as_millis()
);
assert!(
matches!(result, Err(EngineError::Storage)),
"TC-90: `Engine::transition` under a held WAL write lock returns the opaque unit \
variant `EngineError::Storage`; got {result:?}"
);
assert!(
elapsed < Duration::from_millis(2_000),
"and it returns IMMEDIATELY — the busy handler is skipped for a promotion, so no \
backoff happens (took {elapsed:?})"
);
let _ = blocker.execute_batch("ROLLBACK;");
drop(blocker);
let _ = engine.close();
}
#[test]
fn tc90_mechanism_control_engine_write_under_held_write_lock_survives() {
const HOLD: Duration = Duration::from_millis(900);
const READY_TIMEOUT: Duration = Duration::from_secs(30);
let dir = TempDir::new().expect("tempdir");
let path = dir.path().join(format!("tc90_mech_control{SQLITE_SUFFIX}"));
let calls = Arc::new(AtomicUsize::new(0));
let opened = Engine::open_with_embedder_for_test(
&path,
Arc::new(CountingDelayEmbedder::new(calls, Duration::ZERO)),
)
.expect("open");
let engine = &opened.engine;
engine.write(&[governed_node("tc90-control-seed", "control seed")]).expect("seed");
engine.drain(60_000).expect("drain");
let (ready_tx, ready_rx) = std::sync::mpsc::channel::<()>();
let (ack_tx, ack_rx) = std::sync::mpsc::channel::<()>();
let holder = {
let path = path.clone();
std::thread::spawn(move || {
let blocker = rusqlite::Connection::open(&path).expect("open blocker");
blocker.execute_batch("BEGIN IMMEDIATE;").expect("blocker takes the write lock");
blocker
.execute(
"INSERT OR IGNORE INTO _fathomdb_projection_terminal(write_cursor, state) \
VALUES(?1, ?2)",
rusqlite::params![9_999_997_i64, "up_to_date"],
)
.expect("blocker writes under its lock");
ready_tx.send(()).expect("blocker signals that it holds the write lock");
ack_rx
.recv_timeout(READY_TIMEOUT)
.expect("the writer must acknowledge before the hold is timed");
std::thread::sleep(HOLD);
blocker.execute_batch("ROLLBACK;").expect("blocker releases");
})
};
ready_rx
.recv_timeout(READY_TIMEOUT)
.expect("the blocker must signal that it HOLDS the write lock before the writer starts");
let started = Instant::now();
ack_tx.send(()).expect("the writer is timing; the holder may now start its hold");
let result = engine.write(&[governed_node("tc90-control-2", "control write")]);
let elapsed = started.elapsed();
holder.join().expect("the blocker thread must not panic");
println!("TC90-MECH control_write ok={} elapsed_ms={}", result.is_ok(), elapsed.as_millis());
assert!(
result.is_ok(),
"the `BEGIN IMMEDIATE` writer must SURVIVE the identical contention that refuses \
`transition`'s promotion — got {result:?}"
);
assert!(
elapsed >= HOLD / 2,
"and it must have WAITED for the lock ({elapsed:?} < half of {HOLD:?}), which is the \
proof the busy handler was consulted rather than skipped. The blocker held the lock \
from BEFORE this timer started and did not begin counting its {HOLD:?} hold until \
AFTER it, so the release cannot precede `started + {HOLD:?}` on any schedule: a short \
elapsed here is an engine result, not a lost scheduling race"
);
let _ = engine.close();
}