use crate::bloom::BloomFilter;
use crate::LOG_TARGET_CONFLICTS;
use bytes::Bytes;
use papaya::HashSet;
use std::collections::BTreeMap;
use std::sync::atomic::{AtomicBool, AtomicU64};
use std::sync::Arc;
use tracing::debug;
pub struct Commit {
pub(crate) keys: Arc<[Bytes]>,
pub(crate) writeset_bloom: BloomFilter,
pub(crate) merge_version: AtomicU64,
}
pub struct Merge {
pub(crate) writeset: Arc<BTreeMap<Bytes, Option<Bytes>>>,
pub(crate) applied: AtomicBool,
}
impl Commit {
#[inline]
fn min_key(&self) -> Option<&Bytes> {
self.keys.first()
}
#[inline]
fn max_key(&self) -> Option<&Bytes> {
self.keys.last()
}
#[inline]
fn contains_key(&self, key: &Bytes) -> bool {
self.keys.binary_search(key).is_ok()
}
pub fn is_disjoint_readset_bloom(&self, other: &HashSet<Bytes>, bloom: &BloomFilter) -> bool {
if bloom.is_empty() {
return true;
}
let mut any_possible = false;
for key in self.keys.iter() {
if bloom.may_contain(key) {
any_possible = true;
break;
}
}
if !any_possible {
return true;
}
self.is_disjoint_readset(other)
}
pub fn is_disjoint_readset(&self, other: &HashSet<Bytes>) -> bool {
let other = other.pin();
if !other.is_empty() {
if other.len() < self.keys.len() {
for key in other.iter() {
if self.contains_key(key) {
#[cfg(debug_assertions)]
debug!(target: LOG_TARGET_CONFLICTS, "KeyReadConflict involving {:?}", key);
return false;
}
}
} else {
for key in self.keys.iter() {
if other.contains(key) {
#[cfg(debug_assertions)]
debug!(target: LOG_TARGET_CONFLICTS, "KeyReadConflict involving {:?}", key);
return false;
}
}
}
}
true
}
pub fn is_disjoint_writeset_bloom(&self, other: &Arc<Commit>) -> bool {
match (self.min_key(), self.max_key(), other.min_key(), other.max_key()) {
(Some(self_min), Some(self_max), Some(other_min), Some(other_max)) => {
if self_max < other_min || other_max < self_min {
return true;
}
}
_ => return true,
}
let mut any_possible = false;
for key in self.keys.iter() {
if other.writeset_bloom.may_contain(key) {
any_possible = true;
break;
}
}
if !any_possible {
return true;
}
self.is_disjoint_writeset(other)
}
pub fn may_overlap_range(&self, range_start: &Bytes, range_end: &Bytes) -> bool {
match (self.min_key(), self.max_key()) {
(Some(min_key), Some(max_key)) => min_key < range_end && max_key >= range_start,
_ => false,
}
}
pub fn is_disjoint_writeset(&self, other: &Arc<Commit>) -> bool {
let mut a = self.keys.iter();
let mut b = other.keys.iter();
let mut next_a = a.next();
let mut next_b = b.next();
while let (Some(ka), Some(kb)) = (next_a, next_b) {
match ka.cmp(kb) {
std::cmp::Ordering::Less => next_a = a.next(),
std::cmp::Ordering::Greater => next_b = b.next(),
std::cmp::Ordering::Equal => {
#[cfg(debug_assertions)]
debug!(target: LOG_TARGET_CONFLICTS, "KeyWriteConflict involving {:?}", ka);
return false;
}
}
}
true
}
}
#[cfg(test)]
mod tests {
use super::*;
fn commit(input: &[&str]) -> Arc<Commit> {
let mut v: Vec<Bytes> = input.iter().map(|k| Bytes::from(k.to_string())).collect();
v.sort();
v.dedup();
let keys: Arc<[Bytes]> = v.into();
let mut writeset_bloom = BloomFilter::new();
for k in keys.iter() {
writeset_bloom.insert(k);
}
Arc::new(Commit {
keys,
writeset_bloom,
merge_version: AtomicU64::new(0),
})
}
fn readset(input: &[&str]) -> HashSet<Bytes> {
let set = HashSet::new();
{
let pin = set.pin();
for k in input {
pin.insert(Bytes::from(k.to_string()));
}
}
set
}
#[test]
fn readset_disjoint_small_readset_probes_keys() {
let c = commit(&["a", "b", "c", "d", "e"]);
assert!(c.is_disjoint_readset(&readset(&["x", "y"])));
assert!(!c.is_disjoint_readset(&readset(&["x", "c"])));
}
#[test]
fn readset_disjoint_large_readset_iterates_keys() {
let c = commit(&["m"]);
assert!(c.is_disjoint_readset(&readset(&["a", "b", "c", "d"])));
assert!(!c.is_disjoint_readset(&readset(&["a", "b", "m", "d"])));
}
#[test]
fn writeset_disjoint_two_pointer_merge() {
let a = commit(&["a", "c", "e"]);
let b = commit(&["b", "d", "f"]);
assert!(a.is_disjoint_writeset(&b));
assert!(b.is_disjoint_writeset(&a));
let c = commit(&["e", "g"]);
assert!(!a.is_disjoint_writeset(&c));
assert!(!c.is_disjoint_writeset(&a));
}
#[test]
fn writeset_bloom_pre_checks_agree_with_exact() {
let a = commit(&["a", "c", "e"]);
let b = commit(&["b", "d", "f"]);
assert!(a.is_disjoint_writeset_bloom(&b));
let c = commit(&["e", "g"]);
assert!(!a.is_disjoint_writeset_bloom(&c));
let lo = commit(&["a", "b"]);
let hi = commit(&["x", "y"]);
assert!(lo.is_disjoint_writeset_bloom(&hi));
}
}