use std::{sync::Arc, time::Duration};
use wtxn::{
LockType, TransactionManager, TxnKeyEntries, TxnKeyEntryComparison, TxnLockTable, TxnState,
WatchVersionMap,
};
fn assert_send_sync<T: Send + Sync>() {}
fn manager_on(table: TxnLockTable) -> TransactionManager {
TransactionManager::new(table, Arc::new(WatchVersionMap::new(64)), None)
}
#[test]
fn lock_faces_are_thread_safe_by_derivation() {
assert_send_sync::<TxnLockTable>();
assert_send_sync::<TxnKeyEntries>();
assert_send_sync::<TransactionManager>();
}
fn bucket_of(table: &TxnLockTable, key: &[u8]) -> usize {
table.bucket_index_for_hash(TxnKeyEntryComparison::key_hash(key))
}
#[test]
fn second_instance_commits_same_key_while_first_holds_lock() {
const KEY: &[u8] = b"cross-instance-key";
let table_a = TxnLockTable::new();
let table_b = TxnLockTable::new();
let bucket_a = bucket_of(&table_a, KEY);
let bucket_b = bucket_of(&table_b, KEY);
assert_eq!(bucket_a, bucket_b, "同构实例同键应映射同主桶下标");
let mut txn_a = manager_on(table_a.clone());
txn_a.save_key_entry_to_lock(KEY, LockType::Exclusive);
assert!(
txn_a.run(true, false, Duration::ZERO),
"A 实例自身加锁应成功"
);
assert!(
!table_a.try_lock_exclusive(bucket_a),
"A 实例持锁期间本实例主桶必须持闩"
);
let mut txn_b = manager_on(table_b.clone());
txn_b.save_key_entry_to_lock(KEY, LockType::Exclusive);
assert!(
txn_b.run(true, true, Duration::from_millis(1)),
"B 实例同键事务不得被 A 实例持锁阻塞"
);
assert_eq!(txn_b.state, TxnState::Running);
txn_b.commit(false).unwrap();
assert_eq!(txn_b.state, TxnState::None, "B 实例应正常提交收尾");
assert!(
table_b.try_lock_exclusive(bucket_b),
"B 实例提交后本实例主桶必须空闲"
);
table_b.unlock_exclusive(bucket_b);
assert!(
!table_a.try_lock_exclusive(bucket_a),
"另一实例的提交不得释放本实例闩位"
);
txn_a.commit(false).unwrap();
assert!(table_a.try_lock_exclusive(bucket_a), "A 提交后主桶必须空闲");
table_a.unlock_exclusive(bucket_a);
}
#[test]
fn same_instance_sessions_still_conflict() {
const KEY: &[u8] = b"intra-instance-key";
let table = TxnLockTable::new();
let bucket = bucket_of(&table, KEY);
let mut holder = manager_on(table.clone());
holder.save_key_entry_to_lock(KEY, LockType::Exclusive);
assert!(holder.run(true, false, Duration::ZERO));
let mut contender = manager_on(table.clone());
contender.save_key_entry_to_lock(KEY, LockType::Exclusive);
assert!(
!contender.run(true, true, Duration::from_millis(5)),
"同实例内并发事务必须被锁表拦下"
);
assert_eq!(contender.state, TxnState::None, "锁失败路径须复位事务状态");
assert!(
!table.try_lock_exclusive(bucket),
"竞争失败不得误放持有者闩位"
);
holder.commit(false).unwrap();
assert!(
contender.run(true, true, Duration::from_millis(5)),
"持有者释放后竞争方必须可通过"
);
contender.commit(false).unwrap();
assert!(table.try_lock_exclusive(bucket), "双方提交后主桶必须空闲");
table.unlock_exclusive(bucket);
}
#[test]
fn partial_acquire_rolls_back_prefix_latches() {
const LOW: i64 = 0;
const HIGH: i64 = 1;
let table = TxnLockTable::new();
let low_bucket = table.bucket_index_for_hash(LOW);
let high_bucket = table.bucket_index_for_hash(HIGH);
assert_ne!(low_bucket, high_bucket, "用例前提:两键分属不同主桶");
let mut blocker = TxnKeyEntries::new(1, table.clone());
blocker.add_key(HIGH, LockType::Exclusive);
blocker.lock_all_keys();
let mut contender = TxnKeyEntries::new(2, table.clone());
contender.add_key(LOW, LockType::Exclusive);
contender.add_key(HIGH, LockType::Exclusive);
assert!(
!contender.try_lock_all_keys(Duration::from_millis(5)),
"后段主桶被占,整计划必须失败"
);
assert!(
!table.try_lock_exclusive(high_bucket),
"阻塞者闩位不得被竞争失败误放"
);
let mut probe = TxnKeyEntries::new(1, table.clone());
probe.add_key(LOW, LockType::Exclusive);
assert!(probe.try_lock_all_keys(Duration::from_millis(1)));
probe.unlock_all_keys();
blocker.unlock_all_keys();
assert!(contender.try_lock_all_keys(Duration::from_millis(1)));
contender.unlock_all_keys();
}
#[test]
fn cloned_handle_shares_one_lock_face_and_new_handle_is_isolated() {
let table = TxnLockTable::new();
let shared = table.clone();
let bucket = table.bucket_index_for_hash(1234);
assert!(table.try_lock_shared(bucket));
assert!(
!shared.try_lock_exclusive(bucket),
"克隆句柄必须指向同一索引的同一主桶"
);
table.unlock_shared(bucket);
assert!(
shared.try_lock_exclusive(bucket),
"释放后克隆句柄视角下主桶必须空闲"
);
shared.unlock_exclusive(bucket);
let other = TxnLockTable::new();
assert!(other.try_lock_exclusive(bucket), "新实例句柄自带独立锁面");
other.unlock_exclusive(bucket);
}