From: Razvan Becheriu Date: Tue, 10 Sep 2019 10:22:49 +0000 (+0300) Subject: [#883, !506] added LockGuard unit tests X-Git-Url: http://git.ipfire.org/gitweb/gitweb.cgi?a=commitdiff_plain;h=8693cea1599a3c0b1b697d46088795b9e094a867;p=thirdparty%2Fkea.git [#883, !506] added LockGuard unit tests --- 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 index 0000000000..5ba8bdebba --- /dev/null +++ b/src/lib/util/threads/tests/lock_guard_unittest.cc @@ -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 + +#include + +#include +#include + +#include + +#include +#include +#include +#include +#include + +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 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 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 lk(mutex_); + return lock_; + } + + /// @brief get the number of locks performed on mutex + /// + /// @return the mutex number of locks + uint32_t getLockCount() { + lock_guard 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 lk(mutex_); + return unlock_count_; + } + + /// @brief get the mutex dead lock state + /// + /// @return the mutex dead lock state + bool getDeadLock() { + lock_guard 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 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 ids_; + + list> threads_; +}; + +TEST_F(LockGuardTest, testLock) { + reset(); + // make sure we have all threads running when performing all the checks + { + unique_lock lck(mutex_); + for (uint32_t i = 0; i < 2; ++i) { + threads_.push_back(make_shared(&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 test_mutex; + // test non-recursive lock + test_mutex = make_shared(); + test_mutex->testMutexState(0, 0, 0, false); + { + // call LockGuard constructor which locks mutex + LockGuard 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 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(true); + test_mutex->testMutexState(0, 0, 0, false); + { + // call LockGuard constructor which locks mutex + LockGuard 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 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 lock(nullptr); + { + EXPECT_NO_THROW(LockGuard lock(nullptr)); + } + } + } +}