]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#883, !506] added LockGuard unit tests
authorRazvan Becheriu <razvan@isc.org>
Tue, 10 Sep 2019 10:22:49 +0000 (13:22 +0300)
committerRazvan Becheriu <razvan@isc.org>
Fri, 8 Nov 2019 13:52:29 +0000 (15:52 +0200)
src/lib/util/threads/tests/lock_guard_unittest.cc [new file with mode: 0644]

diff --git a/src/lib/util/threads/tests/lock_guard_unittest.cc b/src/lib/util/threads/tests/lock_guard_unittest.cc
new file mode 100644 (file)
index 0000000..5ba8bde
--- /dev/null
@@ -0,0 +1,266 @@
+// Copyright (C) 2018-2019 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <gtest/gtest.h>
+
+#include <util/threads/lock_guard.h>
+#include <exceptions/exceptions.h>
+
+#include <exceptions/exceptions.h>
+
+#include <condition_variable>
+#include <list>
+#include <mutex>
+#include <set>
+#include <thread>
+
+using namespace isc::util;
+using namespace isc::util::thread;
+using namespace std;
+
+namespace {
+
+/// @brief test mutex class used to check the internal state of a 'fictional'
+/// mutex so that the functionality of the LockGuard can be tested
+/// the test mutex can be recursive which means that a lock can be called on the
+/// same thread and not resulting in a dead lock
+class TestMutex {
+public:
+    /// @brief Constructor
+    ///
+    /// @param recursive sets the mutex as recursive mutex
+    TestMutex(bool recursive = false) : lock_(0), lock_count_(0), unlock_count_(0),
+        dead_lock_(false), recursive_(recursive) {
+    }
+
+    /// @brief lock the mutex
+    void lock() {
+        lock_guard<mutex> lk(mutex_);
+        if (lock_ >= 1) {
+            // mutex is already locked
+            if (!recursive_) {
+                // lock on a non-recursive mutex resulting in a dead lock
+                dead_lock_ = true;
+                isc_throw(isc::InvalidOperation,
+                          "lock on already locked mutex resulting in deadlock");
+            } else {
+                // lock on a recursive mutex
+                if (this_thread::get_id() != id_) {
+                    // lock on a recursive mutex on a different thread resulting
+                    // in a dead lock
+                    dead_lock_ = true;
+                    isc_throw(isc::InvalidOperation,
+                              "recursive lock on a different thread on already "
+                              "locked mutex resulting in deadlock");
+                }
+            }
+        }
+        // increment the total number of locks
+        lock_count_++;
+        // increment the lock state
+        lock_++;
+        // save the thread id
+        id_ = this_thread::get_id();
+    }
+
+    /// @brief unlock the mutex
+    void unlock() {
+        lock_guard<mutex> lk(mutex_);
+        if (lock_ <= 0) {
+            // unlock an unlocked mutex
+            isc_throw(isc::InvalidOperation, "unlock on non locked mutex "
+                      "resulting in undefined behavior");
+        }
+        if (lock_ == 1) {
+            // only one thread has the lock
+            // self healing mutex reseting the dead lock flag
+            dead_lock_ = false;
+            // reset the thread id
+            id_ = std::thread::id();
+        }
+        // increment the total number of unlocks
+        unlock_count_++;
+        // decrement the lock state
+        lock_--;
+    }
+
+    /// @brief get the mutex lock state
+    ///
+    /// @return the mutex lock state
+    int32_t getLock() {
+        lock_guard<mutex> lk(mutex_);
+        return lock_;
+    }
+
+    /// @brief get the number of locks performed on mutex
+    ///
+    /// @return the mutex number of locks
+    uint32_t getLockCount() {
+        lock_guard<mutex> lk(mutex_);
+        return lock_count_;
+    }
+
+    /// @brief get the number of unlocks performed on mutex
+    ///
+    /// @return the mutex number of unlocks
+    uint32_t getUnlockCount() {
+        lock_guard<mutex> lk(mutex_);
+        return unlock_count_;
+    }
+
+    /// @brief get the mutex dead lock state
+    ///
+    /// @return the mutex dead lock state
+    bool getDeadLock() {
+        lock_guard<mutex> lk(mutex_);
+        return dead_lock_;
+    }
+
+    /// @brief test the internal state of the mutex
+    ///
+    /// @param expected_lock check equality of this value with lock state
+    /// @param expected_lock_count check equality of this value with lock count
+    /// @param expected_unlock_count check equality of this value with unlock count
+    /// @param expected_dead_lock check equality of this value with dead lock state
+    void testMutexState(int32_t expected_lock,
+                        uint32_t expected_lock_count,
+                        uint32_t expected_unlock_count,
+                        bool expected_dead_lock) {
+        ASSERT_EQ(getLock(), expected_lock);
+        ASSERT_EQ(getLockCount(), expected_lock_count);
+        ASSERT_EQ(getUnlockCount(), expected_unlock_count);
+        ASSERT_EQ(getDeadLock(), expected_dead_lock);
+    }
+
+private:
+    /// @brief internal lock state of the mutex
+    int32_t lock_;
+
+    /// @brief total number of locks performed on the mutex
+    uint32_t lock_count_;
+
+    /// @brief total number of unlocks performed on the mutex
+    uint32_t unlock_count_;
+
+    /// @brief state which indicated that the mutex in in dead lock
+    bool dead_lock_;
+
+    /// @brief flag to indicate if the mutex is recursive or not
+    bool recursive_;
+
+    /// @brief mutex used to keep the internal state consistent
+    mutex mutex_;
+
+    /// @brief the id of the thread holding the mutex
+    std::thread::id id_;
+};
+
+/// @brief Test Fixture for testing isc:util::thread::LockGuard
+class LockGuardTest : public ::testing::Test {
+public:
+    void run() {
+        {
+            // make sure this thread has started and it is accounted for
+            lock_guard<mutex> lk(mutex_);
+            ids_.emplace(this_thread::get_id());
+            ++count_;
+            // wake main thread if it is waiting for this thread to start
+            cv_.notify_all();
+        }
+    }
+
+    void reset() {
+        count_ = 0;
+    }
+
+    uint32_t count() {
+        return count_;
+    }
+
+    std::mutex mutex_;
+
+    condition_variable cv_;
+
+    uint32_t count_;
+
+    set<std::thread::id> ids_;
+
+    list<shared_ptr<std::thread>> threads_;
+};
+
+TEST_F(LockGuardTest, testLock) {
+    reset();
+    // make sure we have all threads running when performing all the checks
+    {
+        unique_lock<mutex> lck(mutex_);
+        for (uint32_t i = 0; i < 2; ++i) {
+            threads_.push_back(make_shared<std::thread>(&LockGuardTest::run, this));
+        }
+        cv_.wait(lck, [&]{ return count() == 2; });
+    }
+    for (auto thread : threads_) {
+        thread->join();
+    }
+    threads_.clear();
+    ASSERT_EQ(count_, 2);
+    ASSERT_EQ(ids_.size(), 2);
+
+    shared_ptr<TestMutex> test_mutex;
+    // test non-recursive lock
+    test_mutex = make_shared<TestMutex>();
+    test_mutex->testMutexState(0, 0, 0, false);
+    {
+        // call LockGuard constructor which locks mutex
+        LockGuard<TestMutex> lock(test_mutex.get());
+        // expect lock 1 lock_count 1 unlock_count 0 dead_lock false
+        test_mutex->testMutexState(1, 1, 0, false);
+        {
+            // call LockGuard constructor which locks mutex resulting in an
+            // exception as the mutex is already locked (dead lock)
+            EXPECT_THROW(LockGuard<TestMutex> lock(test_mutex.get()), isc::InvalidOperation);
+            // expect lock 1 lock_count 1 unlock_count 0 dead_lock true
+            // you should not be able to get here...using a real mutex
+            test_mutex->testMutexState(1, 1, 0, true);
+        }
+        // expect lock 1 lock_count 1 unlock_count 0 dead_lock true
+        // you should not be able to get here...using a real mutex
+        test_mutex->testMutexState(1, 1, 0, true);
+    }
+    // expect lock 0 lock_count 1 unlock_count 1 dead_lock false
+    // the implementation is self healing when completely unlocking the mutex
+    test_mutex->testMutexState(0, 1, 1, false);
+    // test recursive lock
+    test_mutex = make_shared<TestMutex>(true);
+    test_mutex->testMutexState(0, 0, 0, false);
+    {
+        // call LockGuard constructor which locks mutex
+        LockGuard<TestMutex> lock(test_mutex.get());
+        // expect lock 1 lock_count 1 unlock_count 0 dead_lock false
+        test_mutex->testMutexState(1, 1, 0, false);
+        {
+            // call LockGuard constructor which locks mutex but does not block
+            // as this is done on the same thread and the mutex is recursive
+            EXPECT_NO_THROW(LockGuard<TestMutex> lock(test_mutex.get()));
+            // expect lock 1 lock_count 2 unlock_count 1 dead_lock false
+            // the destructor was already called in EXPECT_NO_THROW scope
+            test_mutex->testMutexState(1, 2, 1, false);
+        }
+        // expect lock 1 lock_count 2 unlock_count 1 dead_lock false
+        test_mutex->testMutexState(1, 2, 1, false);
+    }
+    // expect lock 0 lock_count 2 unlock_count 2 dead_lock false
+    test_mutex->testMutexState(0, 2, 2, false);
+    // test LockGuart with no mutex
+    {
+        LockGuard<TestMutex> lock(nullptr);
+        {
+            EXPECT_NO_THROW(LockGuard<TestMutex> lock(nullptr));
+        }
+    }
+    }
+}