use txn_db::{Db, Transaction, TxnError};
const ALICE: &[u8] = b"on_call:alice";
const BOB: &[u8] = b"on_call:bob";
fn main() -> Result<(), TxnError> {
println!("under snapshot isolation:");
let si = run(Db::new(), Isolation::Snapshot)?;
report(&si);
println!("\nunder serializable isolation:");
let ser = run(Db::new(), Isolation::Serializable)?;
report(&ser);
Ok(())
}
#[derive(Clone, Copy)]
enum Isolation {
Snapshot,
Serializable,
}
struct Outcome {
alice_committed: bool,
bob_committed: bool,
on_call: usize,
}
fn run(db: Db, isolation: Isolation) -> Result<Outcome, TxnError> {
let mut seed = db.begin();
seed.put(ALICE.to_vec(), vec![1]);
seed.put(BOB.to_vec(), vec![1]);
seed.commit()?;
let mut alice = begin(&db, isolation);
let mut bob = begin(&db, isolation);
let alice_committed = try_go_off_call(&mut alice, ALICE)?;
let bob_committed = try_go_off_call(&mut bob, BOB)?;
let alice_committed = alice_committed && alice.commit().is_ok();
let bob_committed = bob_committed && bob.commit().is_ok();
let snap = db.snapshot();
let on_call = [ALICE, BOB]
.into_iter()
.filter(|doctor| {
snap.get(doctor)
.map(|v| v.as_deref() == Some(&[1]))
.unwrap_or(false)
})
.count();
Ok(Outcome {
alice_committed,
bob_committed,
on_call,
})
}
fn begin(db: &Db, isolation: Isolation) -> Transaction {
match isolation {
Isolation::Snapshot => db.begin(),
Isolation::Serializable => db.begin_serializable(),
}
}
fn try_go_off_call(tx: &mut Transaction, doctor: &[u8]) -> Result<bool, TxnError> {
let on_call = [ALICE, BOB]
.into_iter()
.filter(|d| matches!(tx.get(d), Ok(Some(v)) if v.as_ref() == [1]))
.count();
if on_call >= 2 {
tx.put(doctor.to_vec(), vec![0]);
Ok(true)
} else {
Ok(false)
}
}
fn report(outcome: &Outcome) {
println!(
" alice went off call: {}, bob went off call: {}",
outcome.alice_committed, outcome.bob_committed
);
println!(" doctors still on call: {}", outcome.on_call);
if outcome.on_call == 0 {
println!(" invariant VIOLATED — nobody is on call");
} else {
println!(" invariant held — at least one doctor on call");
}
}