use std::{
fmt,
sync::{Arc, LazyLock},
time::Duration,
};
use gxhash::HashMap as GxHashMap;
use super::txn_lock_table::{TxnKeyLockGuard, TxnLockTable};
static GLOBAL_LOCK_TABLE: LazyLock<Arc<TxnLockTable>> =
LazyLock::new(|| Arc::new(TxnLockTable::new()));
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[repr(u8)]
pub enum LockType {
None = 0,
Shared = 1,
Exclusive = 2,
}
#[derive(Debug, Clone, Copy)]
pub struct TxnKeyEntry {
pub key_hash: i64,
pub lock_type: LockType,
}
impl TxnKeyEntry {
pub fn new(key_hash: i64, lock_type: LockType) -> Self {
Self {
key_hash,
lock_type,
}
}
}
impl fmt::Display for TxnKeyEntry {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let key_hash_sign = if self.key_hash < 0 { "-" } else { "" };
let lock_str = match self.lock_type {
LockType::None => "-",
LockType::Shared => "s",
LockType::Exclusive => "x",
};
write!(
f,
"{}{}:{}",
key_hash_sign,
self.key_hash.unsigned_abs(),
lock_str
)
}
}
struct LockPlanSlot {
key_hash: i64,
exclusive: bool,
}
pub struct TxnKeyEntries {
keys: Vec<TxnKeyEntry>,
unified_store_key_locked: bool,
pub phase: i32,
held_locks: Vec<TxnKeyLockGuard>,
}
impl TxnKeyEntries {
pub fn new(initial_count: usize) -> Self {
Self {
keys: Vec::with_capacity(initial_count),
unified_store_key_locked: false,
phase: 0,
held_locks: Vec::new(),
}
}
pub fn is_read_only(&self) -> bool {
!self.keys.iter().any(|k| k.lock_type == LockType::Exclusive)
}
pub fn count(&self) -> usize {
self.keys.len()
}
pub fn get_key_hash(&self, index: usize) -> i64 {
self.keys[index].key_hash
}
pub fn add_key(&mut self, key_hash: i64, lock_type: LockType) {
self.keys.push(TxnKeyEntry {
key_hash,
lock_type,
});
}
fn lock_plan(&mut self) -> Vec<LockPlanSlot> {
self
.keys
.sort_by(super::txn_key_entry_comparison::TxnKeyEntryComparison::compare);
let mut plan: Vec<LockPlanSlot> = Vec::with_capacity(self.keys.len());
let mut slot_by_stripe: GxHashMap<usize, usize> = GxHashMap::default();
for entry in &self.keys {
let stripe = GLOBAL_LOCK_TABLE.stripe_index(entry.key_hash);
let exclusive = entry.lock_type == LockType::Exclusive;
match slot_by_stripe.get(&stripe) {
Some(&slot_idx) => plan[slot_idx].exclusive |= exclusive,
None => {
slot_by_stripe.insert(stripe, plan.len());
plan.push(LockPlanSlot {
key_hash: entry.key_hash,
exclusive,
});
}
}
}
plan.sort_unstable_by_key(|slot| GLOBAL_LOCK_TABLE.stripe_index(slot.key_hash));
plan
}
#[cfg(test)]
fn test_lock_plan_stripes(&mut self) -> Vec<usize> {
self
.lock_plan()
.iter()
.map(|slot| GLOBAL_LOCK_TABLE.stripe_index(slot.key_hash))
.collect()
}
pub fn lock_all_keys(&mut self) {
self.phase = 1;
let plan = self.lock_plan();
if !plan.is_empty() {
let lock_table = Arc::clone(&*GLOBAL_LOCK_TABLE);
self.held_locks.extend(
plan
.into_iter()
.map(|slot| lock_table.lock_key(slot.key_hash, slot.exclusive)),
);
self.unified_store_key_locked = true;
}
self.phase = 0;
}
pub fn try_lock_all_keys(&mut self, lock_timeout: Duration) -> bool {
self.phase = 1;
let plan = self.lock_plan();
if !plan.is_empty() {
let lock_table = Arc::clone(&*GLOBAL_LOCK_TABLE);
for slot in plan {
match lock_table.try_lock_key_for(slot.key_hash, slot.exclusive, lock_timeout) {
Some(guard) => self.held_locks.push(guard),
None => {
self.held_locks.clear();
self.unified_store_key_locked = false;
self.phase = 0;
return false;
}
}
}
self.unified_store_key_locked = true;
}
self.phase = 0;
true
}
pub fn unlock_all_keys(&mut self) {
self.phase = 2;
if self.unified_store_key_locked && !self.keys.is_empty() {
self.held_locks.clear();
}
self.keys.clear();
self.unified_store_key_locked = false;
self.phase = 0;
}
pub fn get_lockset(&self) -> String {
let mut sb = String::new();
for entry in &self.keys {
sb.push_str(&entry.to_string());
}
if !sb.is_empty() {
let phase_str = match self.phase {
0 => "none",
1 => "lock",
_ => "unlock",
};
sb.push_str(&format!(" (phase: {phase_str}))"));
}
sb
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::transaction::txn_key_entry_comparison::TxnKeyEntryComparison;
fn entries(pairs: &[(i64, LockType)]) -> TxnKeyEntries {
let mut e = TxnKeyEntries::new(4);
for &(hash, ty) in pairs {
e.add_key(hash, ty);
}
e
}
#[test]
fn add_and_read_back() {
let mut e = TxnKeyEntries::new(2);
let hash = TxnKeyEntryComparison::key_hash(b"k");
e.add_key(hash, LockType::Exclusive);
assert_eq!(e.count(), 1);
assert_eq!(e.get_key_hash(0), hash);
assert!(!e.is_read_only());
}
#[test]
fn shared_keys_are_read_only() {
let e = entries(&[(1, LockType::Shared), (2, LockType::Shared)]);
assert!(e.is_read_only());
}
#[test]
fn lock_and_unlock_roundtrip() {
let mut e = entries(&[(1, LockType::Exclusive), (2, LockType::Shared)]);
e.lock_all_keys();
assert!(e.count() > 0);
e.unlock_all_keys();
assert_eq!(e.count(), 0);
}
#[test]
fn try_lock_contention_fails_and_releases() {
const KEY_A: i64 = 9;
const KEY_B: i64 = 1 << 21;
let mut holder = entries(&[(KEY_A, LockType::Exclusive)]);
holder.lock_all_keys();
let mut contender = entries(&[(KEY_A, LockType::Exclusive), (KEY_B, LockType::Shared)]);
assert!(!contender.try_lock_all_keys(Duration::from_millis(5)));
let mut probe = entries(&[(KEY_B, LockType::Exclusive)]);
assert!(probe.try_lock_all_keys(Duration::from_millis(5)));
probe.unlock_all_keys();
holder.unlock_all_keys();
assert!(contender.try_lock_all_keys(Duration::from_millis(5)));
}
#[test]
fn duplicate_hashes_collapse_to_strongest_lock() {
let mut e = entries(&[(7, LockType::Shared), (7, LockType::Exclusive)]);
e.lock_all_keys();
e.unlock_all_keys();
}
#[test]
fn lock_plan_is_ordered_by_stripe_not_hash() {
let mut e = entries(&[
(0x3FF0_0000, LockType::Exclusive),
(0x4000_0000, LockType::Exclusive),
]);
assert_eq!(e.test_lock_plan_stripes(), vec![0, 1023]);
}
#[test]
fn crossing_stripe_orders_lock_without_deadlock() {
use std::thread;
let sets: Vec<Vec<(i64, LockType)>> = vec![
vec![
(0x3FF0_0000, LockType::Exclusive),
(0x4000_0000, LockType::Exclusive),
],
vec![
(0x0000_0000, LockType::Exclusive),
(0x7FF0_0000, LockType::Exclusive),
],
];
let handles: Vec<_> = sets
.into_iter()
.map(|set| {
thread::spawn(move || {
for _ in 0..64 {
let mut e = entries(&set);
e.lock_all_keys();
e.unlock_all_keys();
}
})
})
.collect();
for handle in handles {
handle.join().expect("竞争事务不得死锁");
}
}
#[test]
fn lockset_string_matches_csharp_shape() {
let e = entries(&[(-3, LockType::Exclusive), (5, LockType::Shared)]);
assert_eq!(e.get_lockset(), "-3:x5:s (phase: none))");
}
}