diff --git a/patch-db/src/locker/bookkeeper.rs b/patch-db/src/locker/bookkeeper.rs index 84991c8..1cbc040 100644 --- a/patch-db/src/locker/bookkeeper.rs +++ b/patch-db/src/locker/bookkeeper.rs @@ -7,7 +7,7 @@ use crate::{ LockSet, }, }; -use imbl::{ordset, OrdMap, OrdSet}; +use imbl::{ordmap, ordset, OrdMap, OrdSet}; use tokio::sync::oneshot; use tracing::{debug, error, info, warn}; @@ -217,15 +217,14 @@ fn kill_deadlocked(request_queue: &mut VecDeque<(Request, OrdSet)>, tr } } -fn deadlock_scan<'a>(queue: &'a VecDeque<(Request, OrdSet)>) -> Vec<&'a Request> { +pub(super) fn deadlock_scan<'a>( + queue: &'a VecDeque<(Request, OrdSet)>, +) -> Vec<&'a Request> { let (wait_map, mut req_map) = queue .iter() .map(|(req, set)| ((&req.lock_info.handle_id, set, req))) .fold( - ( - OrdMap::<&'a HandleId, &'a OrdSet>::new(), - OrdMap::<&'a HandleId, &'a Request>::new(), - ), + (ordmap! {}, ordmap! {}), |(mut wmap, mut rmap), (id, wset, req)| { ( { @@ -239,25 +238,6 @@ fn deadlock_scan<'a>(queue: &'a VecDeque<(Request, OrdSet)>) -> Vec<&' ) }, ); - fn path_to<'a>( - graph: &OrdMap<&'a HandleId, &'a OrdSet>, - root: &'a HandleId, - node: &'a HandleId, - ) -> OrdSet<&'a HandleId> { - if node == root { - return ordset![root]; - } - match graph.get(node) { - None => ordset![], - Some(s) => s - .iter() - .find_map(|h| Some(path_to(graph, root, h)).filter(|s| s.is_empty())) - .map_or(ordset![], |mut s| { - s.insert(node); - s - }), - } - } for (root, wait_set) in wait_map.iter() { let cycle = wait_set .iter() @@ -276,3 +256,23 @@ fn deadlock_scan<'a>(queue: &'a VecDeque<(Request, OrdSet)>) -> Vec<&' } vec![] } + +pub(super) fn path_to<'a>( + graph: &OrdMap<&'a HandleId, &'a OrdSet>, + root: &'a HandleId, + node: &'a HandleId, +) -> OrdSet<&'a HandleId> { + if node == root { + return ordset![root]; + } + match graph.get(node) { + None => ordset![], + Some(s) => s + .iter() + .find_map(|h| Some(path_to(graph, root, h)).filter(|s| s.is_empty())) + .map_or(ordset![], |mut s| { + s.insert(node); + s + }), + } +} diff --git a/patch-db/src/locker/mod.rs b/patch-db/src/locker/mod.rs index e957bef..31b60fa 100644 --- a/patch-db/src/locker/mod.rs +++ b/patch-db/src/locker/mod.rs @@ -3,6 +3,8 @@ mod bookkeeper; mod log_utils; mod natural; mod order_enforcer; +#[cfg(test)] +pub(crate) mod proptest; mod trie; use imbl::{ordmap, ordset, OrdMap, OrdSet}; @@ -66,23 +68,6 @@ impl Locker { ptr: JsonPointer, lock_type: LockType, ) -> Result { - // Local Definitions - struct CancelGuard { - lock_info: Option, - channel: Option>, - recv: oneshot::Receiver>, - } - impl Drop for CancelGuard { - fn drop(&mut self) { - if let (Some(lock_info), Some(channel)) = - (self.lock_info.take(), self.channel.take()) - { - self.recv.close(); - let _ = channel.send(lock_info); - } - } - } - // Pertinent Logic let lock_info = LockInfo { handle_id, @@ -107,6 +92,20 @@ impl Locker { cancel_guard.channel.take(); res } +} // Local Definitions +#[derive(Debug)] +struct CancelGuard { + lock_info: Option, + channel: Option>, + recv: oneshot::Receiver>, +} +impl Drop for CancelGuard { + fn drop(&mut self) { + if let (Some(lock_info), Some(channel)) = (self.lock_info.take(), self.channel.take()) { + self.recv.close(); + let _ = channel.send(lock_info); + } + } } #[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord)] diff --git a/patch-db/src/locker/proptest.rs b/patch-db/src/locker/proptest.rs index b9dd515..1eb2999 100644 --- a/patch-db/src/locker/proptest.rs +++ b/patch-db/src/locker/proptest.rs @@ -1,30 +1,31 @@ #[cfg(test)] -mod proptest { +mod tests { use std::collections::HashMap; use json_ptr::JsonPointer; + use tokio::sync::oneshot; use crate::handle::HandleId; - use crate::locker::{Guard, LockType}; + use crate::locker::{CancelGuard, Guard, LockInfo, LockType, Request}; use proptest::prelude::*; - enum Action { - Acquire { - lock_type: LockType, - ptr: JsonPointer, - }, - Release(JsonPointer), - } + // enum Action { + // Acquire { + // lock_type: LockType, + // ptr: JsonPointer, + // }, + // Release(JsonPointer), + // } - struct Session { - // session id - id: HandleId, - // list of actions and whether or not they have been completed (await returns before test freezes state) - actions: Vec<(Action, bool)>, - // lookup table for (json pointers, action indices) -> release action - guard: HashMap<(JsonPointer, usize), Guard>, - } - type Traversal = Vec; + // struct Session { + // // session id + // id: HandleId, + // // list of actions and whether or not they have been completed (await returns before test freezes state) + // actions: Vec<(Action, bool)>, + // // lookup table for (json pointers, action indices) -> release action + // guard: HashMap<(JsonPointer, usize), Guard>, + // } + // type Traversal = Vec; // randomly select the type of lock we are requesting fn arb_lock_type() -> BoxedStrategy { @@ -36,20 +37,63 @@ mod proptest { .boxed() } - // randomly generate session ids prop_compose! { - fn arb_handle_id()(i in any::()) -> HandleId { + fn arb_handle_id(n: u64)(x in 0..n) -> HandleId { HandleId { - id: i, + id: x, + #[cfg(feature = "trace")] + trace: None, } } } - // the test trie we will be using is an arbitrarily deep binary tree of L and R paths. This will be sufficient to - // test sibling concurrency and won't introduce any unnecessary complexity to the suite. This is the primitive fork - // choice generator - fn arb_json_fork_choice() -> BoxedStrategy { - prop_oneof![Just('L'), Just('R'),].boxed() + fn arb_json_ptr(max_size: usize) -> BoxedStrategy { + (1..max_size) + .prop_flat_map(|n| { + let s = proptest::bool::ANY.prop_map(|b| if b { "b" } else { "a" }); + proptest::collection::vec_deque(s, n).prop_flat_map(|v| { + let mut ptr = JsonPointer::default(); + for seg in v { + ptr.push_end(seg); + } + Just(ptr) + }) + }) + .boxed() + } + + fn arb_lock_info(session_bound: u64, ptr_max_size: usize) -> BoxedStrategy { + arb_handle_id(session_bound) + .prop_flat_map(move |handle_id| { + arb_json_ptr(ptr_max_size).prop_flat_map(move |ptr| { + let handle_id = handle_id.clone(); + arb_lock_type().prop_map(move |ty| LockInfo { + handle_id: handle_id.clone(), + ty, + ptr: ptr.clone(), + }) + }) + }) + .boxed() + } + + prop_compose! { + fn arb_request(session_bound: u64, ptr_max_size: usize)(li in arb_lock_info(session_bound, ptr_max_size)) -> (Request, CancelGuard) { + let (cancel_send, cancel_recv) = oneshot::channel(); + let (guard_send, guard_recv) = oneshot::channel(); + let r = Request { + lock_info: li.clone(), + cancel: Some(cancel_recv), + completion: guard_send, + + }; + let c = CancelGuard { + lock_info: Some(li), + channel: Some(cancel_send), + recv: guard_recv, + }; + (r, c) + } } proptest! {