#ifndef ROCKSDB_LITE
-#include "utilities/transactions/lock/point/point_lock_manager.h"
-
-#include "file/file_util.h"
-#include "port/port.h"
-#include "port/stack_trace.h"
-#include "rocksdb/utilities/transaction_db.h"
-#include "test_util/testharness.h"
-#include "test_util/testutil.h"
-#include "utilities/transactions/pessimistic_transaction_db.h"
-#include "utilities/transactions/transaction_db_mutex_impl.h"
+#include "utilities/transactions/lock/point/point_lock_manager_test.h"
namespace ROCKSDB_NAMESPACE {
-class MockColumnFamilyHandle : public ColumnFamilyHandle {
- public:
- explicit MockColumnFamilyHandle(ColumnFamilyId cf_id) : cf_id_(cf_id) {}
-
- ~MockColumnFamilyHandle() override {}
-
- const std::string& GetName() const override { return name_; }
-
- ColumnFamilyId GetID() const override { return cf_id_; }
-
- Status GetDescriptor(ColumnFamilyDescriptor*) override {
- return Status::OK();
- }
-
- const Comparator* GetComparator() const override { return nullptr; }
-
- private:
- ColumnFamilyId cf_id_;
- std::string name_ = "MockCF";
-};
-
-class PointLockManagerTest : public testing::Test {
- public:
- void SetUp() override {
- env_ = Env::Default();
- db_dir_ = test::PerThreadDBPath("point_lock_manager_test");
- ASSERT_OK(env_->CreateDir(db_dir_));
- mutex_factory_ = std::make_shared<TransactionDBMutexFactoryImpl>();
-
- Options opt;
- opt.create_if_missing = true;
- TransactionDBOptions txn_opt;
- txn_opt.transaction_lock_timeout = 0;
- txn_opt.custom_mutex_factory = mutex_factory_;
- ASSERT_OK(TransactionDB::Open(opt, txn_opt, db_dir_, &db_));
-
- locker_.reset(new PointLockManager(
- static_cast<PessimisticTransactionDB*>(db_), txn_opt));
- }
-
- void TearDown() override {
- delete db_;
- EXPECT_OK(DestroyDir(env_, db_dir_));
- }
-
- PessimisticTransaction* NewTxn(
- TransactionOptions txn_opt = TransactionOptions()) {
- Transaction* txn = db_->BeginTransaction(WriteOptions(), txn_opt);
- return reinterpret_cast<PessimisticTransaction*>(txn);
- }
-
- protected:
- Env* env_;
- std::unique_ptr<PointLockManager> locker_;
-
- private:
- std::string db_dir_;
- std::shared_ptr<TransactionDBMutexFactory> mutex_factory_;
- TransactionDB* db_;
-};
-
+// This test is not applicable for Range Lock manager as Range Lock Manager
+// operates on Column Families, not their ids.
TEST_F(PointLockManagerTest, LockNonExistingColumnFamily) {
MockColumnFamilyHandle cf(1024);
locker_->RemoveColumnFamily(&cf);
}
}
+ // Cleanup
+ locker_->UnLock(txn1, 1024, "k1", env_);
+ locker_->UnLock(txn1, 2048, "k1", env_);
+ locker_->UnLock(txn2, 1024, "k2", env_);
+ locker_->UnLock(txn2, 2048, "k2", env_);
+
delete txn1;
delete txn2;
}
auto txn2 = NewTxn();
ASSERT_OK(locker_->TryLock(txn2, 1, "k", env_, true));
+ // Cleanup
+ locker_->UnLock(txn2, 1, "k", env_);
+
delete txn1;
delete txn2;
}
auto txn2 = NewTxn();
ASSERT_OK(locker_->TryLock(txn2, 1, "k", env_, true));
- delete txn1;
- delete txn2;
-}
-
-TEST_F(PointLockManagerTest, ReentrantExclusiveLock) {
- // Tests that a txn can acquire exclusive lock on the same key repeatedly.
- MockColumnFamilyHandle cf(1);
- locker_->AddColumnFamily(&cf);
- auto txn = NewTxn();
- ASSERT_OK(locker_->TryLock(txn, 1, "k", env_, true));
- ASSERT_OK(locker_->TryLock(txn, 1, "k", env_, true));
- delete txn;
-}
-
-TEST_F(PointLockManagerTest, ReentrantSharedLock) {
- // Tests that a txn can acquire shared lock on the same key repeatedly.
- MockColumnFamilyHandle cf(1);
- locker_->AddColumnFamily(&cf);
- auto txn = NewTxn();
- ASSERT_OK(locker_->TryLock(txn, 1, "k", env_, false));
- ASSERT_OK(locker_->TryLock(txn, 1, "k", env_, false));
- delete txn;
-}
-
-TEST_F(PointLockManagerTest, LockUpgrade) {
- // Tests that a txn can upgrade from a shared lock to an exclusive lock.
- MockColumnFamilyHandle cf(1);
- locker_->AddColumnFamily(&cf);
- auto txn = NewTxn();
- ASSERT_OK(locker_->TryLock(txn, 1, "k", env_, false));
- ASSERT_OK(locker_->TryLock(txn, 1, "k", env_, true));
- delete txn;
-}
-
-TEST_F(PointLockManagerTest, LockDowngrade) {
- // Tests that a txn can acquire a shared lock after acquiring an exclusive
- // lock on the same key.
- MockColumnFamilyHandle cf(1);
- locker_->AddColumnFamily(&cf);
- auto txn = NewTxn();
- ASSERT_OK(locker_->TryLock(txn, 1, "k", env_, true));
- ASSERT_OK(locker_->TryLock(txn, 1, "k", env_, false));
- delete txn;
-}
-
-TEST_F(PointLockManagerTest, LockConflict) {
- // Tests that lock conflicts lead to lock timeout.
- MockColumnFamilyHandle cf(1);
- locker_->AddColumnFamily(&cf);
- auto txn1 = NewTxn();
- auto txn2 = NewTxn();
-
- {
- // exclusive-exclusive conflict.
- ASSERT_OK(locker_->TryLock(txn1, 1, "k1", env_, true));
- auto s = locker_->TryLock(txn2, 1, "k1", env_, true);
- ASSERT_TRUE(s.IsTimedOut());
- }
-
- {
- // exclusive-shared conflict.
- ASSERT_OK(locker_->TryLock(txn1, 1, "k2", env_, true));
- auto s = locker_->TryLock(txn2, 1, "k2", env_, false);
- ASSERT_TRUE(s.IsTimedOut());
- }
-
- {
- // shared-exclusive conflict.
- ASSERT_OK(locker_->TryLock(txn1, 1, "k2", env_, false));
- auto s = locker_->TryLock(txn2, 1, "k2", env_, true);
- ASSERT_TRUE(s.IsTimedOut());
- }
-
- delete txn1;
- delete txn2;
-}
-
-port::Thread BlockUntilWaitingTxn(std::function<void()> f) {
- std::atomic<bool> reached(false);
- ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
- "PointLockManager::AcquireWithTimeout:WaitingTxn",
- [&](void* /*arg*/) { reached.store(true); });
- ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
+ // Cleanup
+ locker_->UnLock(txn2, 1, "k", env_);
- port::Thread t(f);
-
- while (!reached.load()) {
- std::this_thread::sleep_for(std::chrono::milliseconds(100));
- }
- ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
- ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
-
- return t;
-}
-
-TEST_F(PointLockManagerTest, SharedLocks) {
- // Tests that shared locks can be concurrently held by multiple transactions.
- MockColumnFamilyHandle cf(1);
- locker_->AddColumnFamily(&cf);
- auto txn1 = NewTxn();
- auto txn2 = NewTxn();
- ASSERT_OK(locker_->TryLock(txn1, 1, "k", env_, false));
- ASSERT_OK(locker_->TryLock(txn2, 1, "k", env_, false));
delete txn1;
delete txn2;
}
-TEST_F(PointLockManagerTest, Deadlock) {
- // Tests that deadlock can be detected.
- // Deadlock scenario:
- // txn1 exclusively locks k1, and wants to lock k2;
- // txn2 exclusively locks k2, and wants to lock k1.
- MockColumnFamilyHandle cf(1);
- locker_->AddColumnFamily(&cf);
- TransactionOptions txn_opt;
- txn_opt.deadlock_detect = true;
- txn_opt.lock_timeout = 1000000;
- auto txn1 = NewTxn(txn_opt);
- auto txn2 = NewTxn(txn_opt);
-
- ASSERT_OK(locker_->TryLock(txn1, 1, "k1", env_, true));
- ASSERT_OK(locker_->TryLock(txn2, 1, "k2", env_, true));
-
- // txn1 tries to lock k2, will block forever.
- port::Thread t = BlockUntilWaitingTxn([&]() {
- // block because txn2 is holding a lock on k2.
- locker_->TryLock(txn1, 1, "k2", env_, true);
- });
-
- auto s = locker_->TryLock(txn2, 1, "k1", env_, true);
- ASSERT_TRUE(s.IsBusy());
- ASSERT_EQ(s.subcode(), Status::SubCode::kDeadlock);
-
- std::vector<DeadlockPath> deadlock_paths = locker_->GetDeadlockInfoBuffer();
- ASSERT_EQ(deadlock_paths.size(), 1u);
- ASSERT_FALSE(deadlock_paths[0].limit_exceeded);
-
- std::vector<DeadlockInfo> deadlocks = deadlock_paths[0].path;
- ASSERT_EQ(deadlocks.size(), 2u);
-
- ASSERT_EQ(deadlocks[0].m_txn_id, txn1->GetID());
- ASSERT_EQ(deadlocks[0].m_cf_id, 1u);
- ASSERT_TRUE(deadlocks[0].m_exclusive);
- ASSERT_EQ(deadlocks[0].m_waiting_key, "k2");
-
- ASSERT_EQ(deadlocks[1].m_txn_id, txn2->GetID());
- ASSERT_EQ(deadlocks[1].m_cf_id, 1u);
- ASSERT_TRUE(deadlocks[1].m_exclusive);
- ASSERT_EQ(deadlocks[1].m_waiting_key, "k1");
-
- locker_->UnLock(txn2, 1, "k2", env_);
- t.join();
-
- delete txn2;
- delete txn1;
-}
+// This test doesn't work with Range Lock Manager, because Range Lock Manager
+// doesn't support deadlock_detect_depth.
TEST_F(PointLockManagerTest, DeadlockDepthExceeded) {
// Tests that when detecting deadlock, if the detection depth is exceeded,
// it must have another txn waiting on it, which is txn4 in this case.
ASSERT_OK(locker_->TryLock(txn1, 1, "k1", env_, true));
- port::Thread t1 = BlockUntilWaitingTxn([&]() {
+ port::Thread t1 = BlockUntilWaitingTxn(wait_sync_point_name_, [&]() {
ASSERT_OK(locker_->TryLock(txn2, 1, "k2", env_, true));
// block because txn1 is holding a lock on k1.
locker_->TryLock(txn2, 1, "k1", env_, true);
ASSERT_OK(locker_->TryLock(txn3, 1, "k3", env_, true));
- port::Thread t2 = BlockUntilWaitingTxn([&]() {
+ port::Thread t2 = BlockUntilWaitingTxn(wait_sync_point_name_, [&]() {
// block because txn3 is holding a lock on k1.
locker_->TryLock(txn4, 1, "k3", env_, true);
});
delete txn1;
}
+INSTANTIATE_TEST_CASE_P(PointLockManager, AnyLockManagerTest,
+ ::testing::Values(nullptr));
+
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {