use std::sync::Arc;
use topodb::{Db, EdgeId, NodeId, Op, Scope, ScopeId, TopoError};
#[test]
fn sixteen_writers_supersede_leaves_exactly_one_open_edge() {
let dir = tempfile::tempdir().unwrap();
let db = Db::open(dir.path().join("t.redb")).unwrap();
let scope = Scope::Id(ScopeId::new());
let (subject, value) = (NodeId::new(), NodeId::new());
db.submit(vec![
Op::CreateNode {
id: subject,
scope,
label: "FactKey".into(),
props: Default::default(),
},
Op::CreateNode {
id: value,
scope,
label: "Entity".into(),
props: Default::default(),
},
])
.unwrap();
let db = Arc::new(db);
let handles: Vec<_> = (0..16)
.map(|_| {
let db = db.clone();
std::thread::spawn(move || {
const MAX_ATTEMPTS: usize = 64;
for _attempt in 0..MAX_ATTEMPTS {
let open = db.open_edges_between(subject, value);
let mut ops: Vec<Op> =
open.into_iter().map(|e| Op::CloseEdge { id: e, valid_to: None }).collect();
ops.push(Op::CreateEdge {
id: EdgeId::new(),
scope,
ty: "HAS_VALUE".into(),
from: subject,
to: value,
props: Default::default(),
valid_from: None,
});
match db.submit(ops) {
Ok(_) => return,
Err(TopoError::Rejected(_)) => continue,
Err(e) => panic!("unexpected error from submit: {e}"),
}
}
panic!(
"writer thread exceeded {MAX_ATTEMPTS} retry attempts without a successful submit"
);
})
})
.collect();
for h in handles {
h.join().unwrap();
}
let after_threads = db.all_edges_between(subject, value);
assert_eq!(after_threads.len(), 16);
let open = db.open_edges_between(subject, value);
let mut final_ops: Vec<Op> = open
.into_iter()
.map(|e| Op::CloseEdge {
id: e,
valid_to: None,
})
.collect();
final_ops.push(Op::CreateEdge {
id: EdgeId::new(),
scope,
ty: "HAS_VALUE".into(),
from: subject,
to: value,
props: Default::default(),
valid_from: None,
});
db.submit(final_ops).unwrap();
let all = db.all_edges_between(subject, value);
assert_eq!(all.len(), 17);
let open = db.open_edges_between(subject, value);
assert_eq!(
open.len(),
1,
"exactly one edge must remain open after the final supersede"
);
let closed_count = all.iter().filter(|e| e.valid_to.is_some()).count();
assert_eq!(closed_count, 16, "every non-final edge must be closed");
}