summaryrefslogtreecommitdiffstats
path: root/src/lib/util/tests/unlock_guard_unittests.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/util/tests/unlock_guard_unittests.cc')
-rw-r--r--src/lib/util/tests/unlock_guard_unittests.cc236
1 files changed, 236 insertions, 0 deletions
diff --git a/src/lib/util/tests/unlock_guard_unittests.cc b/src/lib/util/tests/unlock_guard_unittests.cc
new file mode 100644
index 0000000..65d868b
--- /dev/null
+++ b/src/lib/util/tests/unlock_guard_unittests.cc
@@ -0,0 +1,236 @@
+// Copyright (C) 2020-2021 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/unlock_guard.h>
+#include <exceptions/exceptions.h>
+
+#include <mutex>
+#include <thread>
+
+using namespace isc::util;
+using namespace std;
+
+namespace {
+
+/// @brief test mutex class used to check the internal state of a 'fictional'
+/// mutex so that the functionality of the UnlockGuard can be tested
+/// @note 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), dead_lock_(false),
+ lock_count_(0), unlock_count_(0), 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,
+ "recursive lock on already locked mutex resulting in "
+ "dead lock");
+ } 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 dead lock");
+ }
+ }
+ }
+ // 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 resetting 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 mutex dead lock state
+ ///
+ /// @return the mutex dead lock state
+ bool getDeadLock() {
+ lock_guard<mutex> lk(mutex_);
+ return dead_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 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 state which indicates that the mutex is in dead lock
+ bool dead_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 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::UnlockGuard
+class UnlockGuardTest : public ::testing::Test {
+};
+
+/// @brief test TestMutex functionality with non-recursive mutex, and recursive
+/// mutex
+TEST_F(UnlockGuardTest, testMutex) {
+ shared_ptr<TestMutex> test_mutex;
+ // test non-recursive lock
+ test_mutex = make_shared<TestMutex>();
+ test_mutex->testMutexState(0, 0, 0, false);
+ {
+ // call lock_guard constructor which locks mutex
+ lock_guard<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 lock_guard constructor which locks mutex resulting in an
+ // exception as the mutex is already locked (dead lock)
+ EXPECT_THROW(lock_guard<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 lock_guard constructor which locks mutex
+ lock_guard<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 lock_guard constructor which locks mutex but does not block
+ // as this is done on the same thread and the mutex is recursive
+ EXPECT_NO_THROW(lock_guard<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);
+}
+
+/// @brief test UnlockGuard functionality with non-recursive mutex
+TEST_F(UnlockGuardTest, testUnlockGuard) {
+ shared_ptr<TestMutex> test_mutex;
+ // test non-recursive lock
+ test_mutex = make_shared<TestMutex>();
+ test_mutex->testMutexState(0, 0, 0, false);
+ {
+ // call lock_guard constructor which locks mutex
+ lock_guard<TestMutex> lock(*test_mutex.get());
+ // expect lock 1 lock_count 1 unlock_count 0 dead_lock false
+ test_mutex->testMutexState(1, 1, 0, false);
+ {
+ UnlockGuard<TestMutex> unlock_guard(*test_mutex.get());
+ // expect lock 0 lock_count 1 unlock_count 1 dead_lock false
+ test_mutex->testMutexState(0, 1, 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);
+}
+
+} // namespace