use std::{sync::Arc, time::Duration};
use parking_lot::{
RawRwLock, RwLock,
lock_api::{ArcRwLockReadGuard, ArcRwLockWriteGuard},
};
const STRIPE_COUNT: u64 = 1 << 10;
pub enum TxnKeyLockGuard {
Shared(ArcRwLockReadGuard<RawRwLock, ()>),
Exclusive(ArcRwLockWriteGuard<RawRwLock, ()>),
}
pub struct TxnLockTable {
stripes: Vec<Arc<RwLock<()>>>,
}
impl TxnLockTable {
pub fn new() -> Self {
Self {
stripes: (0..STRIPE_COUNT)
.map(|_| Arc::new(RwLock::new(())))
.collect(),
}
}
#[inline]
pub fn stripe_index(&self, key_hash: i64) -> usize {
((key_hash as u64 >> 20) as usize) & (self.stripes.len() - 1)
}
#[inline]
fn stripe_for(&self, key_hash: i64) -> &Arc<RwLock<()>> {
&self.stripes[self.stripe_index(key_hash)]
}
pub fn lock_key(&self, key_hash: i64, exclusive: bool) -> TxnKeyLockGuard {
let stripe = self.stripe_for(key_hash).clone();
if exclusive {
TxnKeyLockGuard::Exclusive(stripe.write_arc())
} else {
TxnKeyLockGuard::Shared(stripe.read_arc())
}
}
pub fn try_lock_key_for(
&self,
key_hash: i64,
exclusive: bool,
timeout: Duration,
) -> Option<TxnKeyLockGuard> {
let stripe = self.stripe_for(key_hash).clone();
if exclusive {
stripe
.try_write_arc_for(timeout)
.map(TxnKeyLockGuard::Exclusive)
} else {
stripe
.try_read_arc_for(timeout)
.map(TxnKeyLockGuard::Shared)
}
}
}
impl Default for TxnLockTable {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use std::thread;
use super::*;
#[test]
fn shared_locks_coexist_exclusive_excludes() {
let table = TxnLockTable::new();
let g1 = table.lock_key(1, false);
let g2 = table.lock_key(1, false);
let _ = (&g1, &g2);
assert!(
table
.try_lock_key_for(1, true, Duration::from_millis(1))
.is_none()
);
drop(g1);
drop(g2);
assert!(
table
.try_lock_key_for(1, true, Duration::from_millis(1))
.is_some()
);
}
#[test]
fn distinct_stripes_do_not_conflict() {
let table = TxnLockTable::new();
let _g = table.lock_key(0, true);
assert!(
table
.try_lock_key_for(i64::MAX, true, Duration::from_millis(1))
.is_some()
);
}
#[test]
fn lock_is_contention_visible_across_threads() {
let table = Arc::new(TxnLockTable::new());
let guard = table.lock_key(5, true);
let t_table = Arc::clone(&table);
let handle = thread::spawn(move || {
assert!(
t_table
.try_lock_key_for(5, true, Duration::from_millis(5))
.is_none()
);
});
handle.join().expect("子线程不 panic");
drop(guard);
}
}