#![cfg(feature = "std")]
#![allow(clippy::unwrap_used)]
use std::collections::HashMap;
use lock_db::{LockError, LockManager, LockMode, ResourceId, TxnId};
use proptest::prelude::*;
const TXNS: u64 = 4;
const RESOURCES: u64 = 3;
#[derive(Clone, Copy, Debug)]
enum Op {
Acquire { txn: u64, res: u64, mode: LockMode },
Release { txn: u64, res: u64 },
}
fn op_strategy() -> impl Strategy<Value = Op> {
let txn = 0..TXNS;
let res = 0..RESOURCES;
prop_oneof![
(txn.clone(), res.clone(), any::<bool>()).prop_map(|(txn, res, exclusive)| Op::Acquire {
txn,
res,
mode: if exclusive {
LockMode::Exclusive
} else {
LockMode::Shared
},
}),
(txn, res).prop_map(|(txn, res)| Op::Release { txn, res }),
]
}
#[derive(Default)]
struct Model {
held: HashMap<(u64, u64), LockMode>,
}
impl Model {
fn holders_of(&self, res: u64) -> Vec<(u64, LockMode)> {
self.held
.iter()
.filter(|((_, r), _)| *r == res)
.map(|((t, _), m)| (*t, *m))
.collect()
}
fn apply_acquire(&mut self, txn: u64, res: u64, mode: LockMode) -> Result<(), LockError> {
if let Some(¤t) = self.held.get(&(txn, res)) {
if current.covers(mode) {
return Ok(());
}
let others = self
.holders_of(res)
.into_iter()
.filter(|(t, _)| *t != txn)
.count();
if others == 0 {
let _ = self.held.insert((txn, res), mode);
return Ok(());
}
return Err(LockError::Conflict);
}
let compatible = self
.holders_of(res)
.into_iter()
.all(|(_, held)| held.compatible_with(mode));
if compatible {
let _ = self.held.insert((txn, res), mode);
Ok(())
} else {
Err(LockError::Conflict)
}
}
fn apply_release(&mut self, txn: u64, res: u64) -> Result<(), LockError> {
if self.held.remove(&(txn, res)).is_some() {
Ok(())
} else {
Err(LockError::NotHeld)
}
}
fn assert_internally_consistent(&self) {
let mut by_res: HashMap<u64, Vec<LockMode>> = HashMap::new();
for ((_, res), mode) in &self.held {
by_res.entry(*res).or_default().push(*mode);
}
for modes in by_res.values() {
let exclusives = modes.iter().filter(|m| m.is_exclusive()).count();
if exclusives > 0 {
assert_eq!(exclusives, 1, "more than one exclusive holder");
assert_eq!(
modes.len(),
1,
"exclusive lock coexists with another holder"
);
}
}
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(400))]
#[test]
fn manager_matches_model(
ops in proptest::collection::vec(op_strategy(), 1..200),
shards in prop_oneof![Just(1usize), Just(4), Just(16)],
) {
let lm = LockManager::with_shards(shards);
let mut model = Model::default();
for op in ops {
match op {
Op::Acquire { txn, res, mode } => {
let want = model.apply_acquire(txn, res, mode);
let got = lm.try_acquire(TxnId::new(txn), ResourceId::new(res), mode);
prop_assert_eq!(got, want, "acquire mismatch on op {:?}", op);
}
Op::Release { txn, res } => {
let want = model.apply_release(txn, res);
let got = lm.release(TxnId::new(txn), ResourceId::new(res));
prop_assert_eq!(got, want, "release mismatch on op {:?}", op);
}
}
model.assert_internally_consistent();
for t in 0..TXNS {
for r in 0..RESOURCES {
let expected = model.held.get(&(t, r)).copied();
prop_assert_eq!(lm.mode_held(TxnId::new(t), ResourceId::new(r)), expected);
}
prop_assert_eq!(
lm.holder_count(ResourceId::new(0)),
model.holders_of(0).len()
);
}
}
}
#[test]
fn release_all_matches_model(
ops in proptest::collection::vec(op_strategy(), 1..200),
victim in 0..TXNS,
) {
let lm = LockManager::with_shards(8);
let mut model = Model::default();
for op in ops {
match op {
Op::Acquire { txn, res, mode } => {
if model.apply_acquire(txn, res, mode).is_ok() {
let _ = lm.try_acquire(TxnId::new(txn), ResourceId::new(res), mode);
}
}
Op::Release { txn, res } => {
if model.apply_release(txn, res).is_ok() {
let _ = lm.release(TxnId::new(txn), ResourceId::new(res));
}
}
}
}
let expected_count = model.held.keys().filter(|(t, _)| *t == victim).count();
let released = lm.release_all(TxnId::new(victim));
prop_assert_eq!(released, expected_count);
for r in 0..RESOURCES {
prop_assert_eq!(lm.mode_held(TxnId::new(victim), ResourceId::new(r)), None);
}
for t in 0..TXNS {
if t == victim {
continue;
}
for r in 0..RESOURCES {
let expected = model.held.get(&(t, r)).copied();
prop_assert_eq!(lm.mode_held(TxnId::new(t), ResourceId::new(r)), expected);
}
}
}
}