diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:15:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:15:43 +0000 |
commit | f5f56e1a1c4d9e9496fcb9d81131066a964ccd23 (patch) | |
tree | 49e44c6f87febed37efb953ab5485aa49f6481a7 /src/lib/util/tests/readwrite_mutex_unittest.cc | |
parent | Initial commit. (diff) | |
download | isc-kea-upstream.tar.xz isc-kea-upstream.zip |
Adding upstream version 2.4.1.upstream/2.4.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/lib/util/tests/readwrite_mutex_unittest.cc')
-rw-r--r-- | src/lib/util/tests/readwrite_mutex_unittest.cc | 470 |
1 files changed, 470 insertions, 0 deletions
diff --git a/src/lib/util/tests/readwrite_mutex_unittest.cc b/src/lib/util/tests/readwrite_mutex_unittest.cc new file mode 100644 index 0000000..6b4af5f --- /dev/null +++ b/src/lib/util/tests/readwrite_mutex_unittest.cc @@ -0,0 +1,470 @@ +// Copyright (C) 2020 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 <util/readwrite_mutex.h> + +#include <gtest/gtest.h> + +#include <boost/shared_ptr.hpp> +#include <boost/make_shared.hpp> + +#include <chrono> +#include <iostream> +#include <thread> +#include <unistd.h> + +using namespace isc::util; +using namespace std; + +namespace { + +/// @brief Test Fixture for testing read-write mutexes. +/// +/// Each not basic test follows the same schema: +/// @code +/// main thread work thread +/// <- started +/// work -> +/// enter guard +/// <- done +/// terminate -> +/// return +/// join +/// @endcode +class ReadWriteMutexTest : public ::testing::Test { +public: + + /// @brief Read-write mutex. + ReadWriteMutex rw_mutex_; + + /// @brief Synchronization objects for work threads. + struct Sync { + bool started = false; + mutex started_mtx; + condition_variable started_cv; + bool work = false; + mutex work_mtx; + condition_variable work_cv; + bool done = false; + mutex done_mtx; + condition_variable done_cv; + bool terminate = false; + mutex terminate_mtx; + condition_variable terminate_cv; + } syncr_, syncw_; + + /// @brief Body of the reader. + /// + /// @param rw_mutex The read-write mutex. + /// @param syncr The reader synchronization object. + void reader(ReadWriteMutex& rw_mutex, Sync& syncr) { + // Take mutex to wait for main thread signals. + unique_lock<mutex> terminate_lock(syncr.terminate_mtx); + + // Signal the thread started. + { + lock_guard<mutex> lock(syncr.started_mtx); + syncr.started = true; + } + + // Wait for work. + { + unique_lock<mutex> work_lock(syncr.work_mtx); + // When this thread starts waiting, the main thread is resumed. + syncr.started_cv.notify_one(); + syncr.work_cv.wait(work_lock, [&](){ return syncr.work; }); + } + + { + // Enter a read lock guard. + ReadLockGuard rwlock(rw_mutex); + + // Signal the thread holds the guard. + { + lock_guard<mutex> done_lock(syncr.done_mtx); + syncr.done = true; + } + syncr.done_cv.notify_one(); + } + + // Wait to terminate. + syncr.terminate_cv.wait(terminate_lock, [&](){ return syncr.terminate; }); + } + + /// @brief Body of the writer. + /// + /// @param rw_mutex The read-write mutex. + /// @param syncw The writer synchronization object. + void writer(ReadWriteMutex& rw_mutex, Sync& syncw) { + // Take mutex to wait for main thread signals. + unique_lock<mutex> terminate_lock(syncw.terminate_mtx); + + // Signal the thread started. + { + lock_guard<mutex> lock(syncw.started_mtx); + syncw.started = true; + } + + // Wait for work. + { + unique_lock<mutex> work_lock(syncw.work_mtx); + // When this thread starts waiting, the main thread is resumed. + syncw.started_cv.notify_one(); + syncw.work_cv.wait(work_lock, [&](){ return syncw.work; }); + } + + { + // Enter a write lock guard. + WriteLockGuard rwlock(rw_mutex); + + // Signal the thread holds the guard. + { + lock_guard<mutex> done_lock(syncw.done_mtx); + syncw.done = true; + } + syncw.done_cv.notify_one(); + } + + // Wait to terminate. + syncw.terminate_cv.wait(terminate_lock, [&](){ return syncw.terminate; }); + } +}; + +// Verify basic read lock guard. +TEST_F(ReadWriteMutexTest, basicRead) { + ReadLockGuard lock(rw_mutex_); +} + +// Verify basic write lock guard. +TEST_F(ReadWriteMutexTest, basicWrite) { + WriteLockGuard lock(rw_mutex_); +} + +// Verify read lock guard using a thread. +TEST_F(ReadWriteMutexTest, read) { + // Take mutex to wait for work thread signals. + boost::shared_ptr<std::thread> thread; + { + unique_lock<mutex> started_lock(syncr_.started_mtx); + + // Create a work thread. + thread = boost::make_shared<std::thread>([this](){ reader(rw_mutex_, syncr_); }); + + // Wait work thread to start. + syncr_.started_cv.wait(started_lock, [this](){ return syncr_.started; }); + + unique_lock<mutex> done_lock(syncr_.done_mtx); + + // Signal the thread to work. + { + lock_guard<mutex> work_lock(syncr_.work_mtx); + syncr_.work = true; + } + syncr_.work_cv.notify_one(); + + // Wait thread to hold the read lock. + syncr_.done_cv.wait(done_lock, [this](){ return syncr_.done; }); + } + + // Signal the thread to terminate. + { + lock_guard<mutex> terminate_lock(syncr_.terminate_mtx); + syncr_.terminate = true; + } + syncr_.terminate_cv.notify_one(); + + // Join the thread. + thread->join(); +} + +// Verify write lock guard using a thread. +TEST_F(ReadWriteMutexTest, write) { + // Take mutex to wait for work thread signals. + boost::shared_ptr<std::thread> thread; + { + unique_lock<mutex> started_lock(syncw_.started_mtx); + + // Create a work thread. + thread = boost::make_shared<std::thread>([this](){ writer(rw_mutex_, syncw_); }); + + // Wait work thread to start. + syncw_.started_cv.wait(started_lock, [this](){ return syncw_.started; }); + + unique_lock<mutex> done_lock(syncw_.done_mtx); + + // Signal the thread to work. + { + lock_guard<mutex> work_lock(syncw_.work_mtx); + syncw_.work = true; + } + syncw_.work_cv.notify_one(); + + // Wait thread to hold the write lock. + syncw_.done_cv.wait(done_lock, [this](){ return syncw_.done; }); + } + + // Signal the thread to terminate. + { + lock_guard<mutex> terminate_lock(syncw_.terminate_mtx); + syncw_.terminate = true; + } + syncw_.terminate_cv.notify_one(); + + // Join the thread. + thread->join(); +} + +// Verify read lock guard can be acquired by multiple threads. +TEST_F(ReadWriteMutexTest, readRead) { + // Take mutex to wait for work thread signals. + boost::shared_ptr<std::thread> thread; + { + unique_lock<mutex> started_lock(syncr_.started_mtx); + + // Create a work thread. + thread = boost::make_shared<std::thread>([this](){ reader(rw_mutex_, syncr_); }); + + // Enter a read lock guard. + ReadLockGuard rwlock(rw_mutex_); + + // Wait work thread to start. + syncr_.started_cv.wait(started_lock, [this](){ return syncr_.started; }); + + unique_lock<mutex> done_lock(syncr_.done_mtx); + + // Signal the thread to work. + { + lock_guard<mutex> work_lock(syncr_.work_mtx); + syncr_.work = true; + } + syncr_.work_cv.notify_one(); + + // Wait thread to hold the read lock. + syncr_.done_cv.wait(done_lock, [this](){ return syncr_.done; }); + } + + // Signal the thread to terminate. + { + lock_guard<mutex> terminate_lock(syncr_.terminate_mtx); + syncr_.terminate = true; + } + syncr_.terminate_cv.notify_one(); + + // Join the thread. + thread->join(); +} + +// Verify write lock guard is exclusive of a reader. +TEST_F(ReadWriteMutexTest, readWrite) { + // Take mutex to wait for work thread signals. + boost::shared_ptr<std::thread> thread; + { + unique_lock<mutex> started_lock(syncw_.started_mtx); + + // Create a work thread. + thread = boost::make_shared<std::thread>([this](){ writer(rw_mutex_, syncw_); }); + + // Wait work thread to start. + syncw_.started_cv.wait(started_lock, [this](){ return syncw_.started; }); + + unique_lock<mutex> done_lock(syncw_.done_mtx); + + { + // Enter a read lock guard. + ReadLockGuard rwlock(rw_mutex_); + + // Signal the thread to work. + { + lock_guard<mutex> work_lock(syncw_.work_mtx); + syncw_.work = true; + } + syncw_.work_cv.notify_one(); + + // Verify the work thread is waiting for the write lock. + cout << "pausing for one second" << std::endl; + bool ret = syncw_.done_cv.wait_for(done_lock, chrono::seconds(1), [this](){ return syncw_.done; }); + + EXPECT_FALSE(syncw_.done); + EXPECT_FALSE(ret); + + // Exiting the read lock guard. + } + + // Wait thread to hold the write lock. + syncw_.done_cv.wait(done_lock, [this](){ return syncw_.done; }); + } + + // Signal the thread to terminate. + { + lock_guard<mutex> terminate_lock(syncw_.terminate_mtx); + syncw_.terminate = true; + } + syncw_.terminate_cv.notify_one(); + + // Join the thread. + thread->join(); +} + +// Verify write lock guard is exclusive of a writer. +TEST_F(ReadWriteMutexTest, writeWrite) { + // Take mutex to wait for work thread signals. + boost::shared_ptr<std::thread> thread; + { + unique_lock<mutex> started_lock(syncw_.started_mtx); + + // Create a work thread. + thread = boost::make_shared<std::thread>([this](){ writer(rw_mutex_, syncw_); }); + + // Wait work thread to start. + syncw_.started_cv.wait(started_lock, [this](){ return syncw_.started; }); + + unique_lock<mutex> done_lock(syncw_.done_mtx); + + { + // Enter a write lock guard. + WriteLockGuard rwlock(rw_mutex_); + + // Signal the thread to work. + { + lock_guard<mutex> work_lock(syncw_.work_mtx); + syncw_.work = true; + } + syncw_.work_cv.notify_one(); + + // Verify the work thread is waiting for the write lock. + cout << "pausing for one second" << std::endl; + bool ret = syncw_.done_cv.wait_for(done_lock, chrono::seconds(1), [this](){ return syncw_.done; }); + + EXPECT_FALSE(syncw_.done); + EXPECT_FALSE(ret); + + // Exiting the write lock guard. + } + + // Wait thread to hold the write lock. + syncw_.done_cv.wait(done_lock, [this](){ return syncw_.done; }); + } + + // Signal the thread to terminate. + { + lock_guard<mutex> terminate_lock(syncw_.terminate_mtx); + syncw_.terminate = true; + } + syncw_.terminate_cv.notify_one(); + + // Join the thread. + thread->join(); +} + +// Verify that a writer has the preference. +TEST_F(ReadWriteMutexTest, readWriteRead) { + // Take mutex to wait for work thread signals. + boost::shared_ptr<std::thread> threadw; + { + unique_lock<mutex> startedw_lock(syncw_.started_mtx); + + // First thread is a writer. + threadw = boost::make_shared<std::thread>([this](){ writer(rw_mutex_, syncw_); }); + + // Wait work thread to start. + syncw_.started_cv.wait(startedw_lock, [this](){ return syncw_.started; }); + } + + boost::shared_ptr<std::thread> threadr; + { + unique_lock<mutex> startedr_lock(syncr_.started_mtx); + + // Second thread is a reader. + threadr = boost::make_shared<std::thread>([this](){ reader(rw_mutex_, syncr_); }); + + // Wait work thread to start. + syncr_.started_cv.wait(startedr_lock, [this](){ return syncr_.started; }); + } + + { + unique_lock<mutex> donew_lock(syncw_.done_mtx); + { + // Enter a read lock guard. + ReadLockGuard rwlock(rw_mutex_); + + // Signal the writer thread to work. + { + lock_guard<mutex> work_lock(syncw_.work_mtx); + syncw_.work = true; + } + syncw_.work_cv.notify_one(); + + // Verify the writer thread is waiting for the write lock. + cout << "pausing for one second" << std::endl; + bool ret = syncw_.done_cv.wait_for(donew_lock, chrono::seconds(1), [this](){ return syncw_.done; }); + + EXPECT_FALSE(syncw_.done); + EXPECT_FALSE(ret); + + { + unique_lock<mutex> doner_lock(syncr_.done_mtx); + + // Signal the reader thread to work. + { + lock_guard<mutex> work_lock(syncr_.work_mtx); + syncr_.work = true; + } + syncr_.work_cv.notify_one(); + + // Verify the reader thread is waiting for the read lock. + cout << "pausing for one second" << std::endl; + bool ret = syncr_.done_cv.wait_for(doner_lock, chrono::seconds(1), [this](){ return syncr_.done; }); + + EXPECT_FALSE(syncr_.done); + EXPECT_FALSE(ret); + } + // Exiting the read lock guard. + } + + { + unique_lock<mutex> doner_lock(syncr_.done_mtx); + // Verify the reader thread is still waiting for the read lock. + cout << "pausing for one second" << std::endl; + bool ret = syncr_.done_cv.wait_for(doner_lock, chrono::seconds(1), [this](){ return syncr_.done; }); + + EXPECT_FALSE(syncr_.done); + EXPECT_FALSE(ret); + } + + // Wait writer thread to hold the write lock. + syncw_.done_cv.wait(donew_lock, [this](){ return syncw_.done; }); + } + + { + unique_lock<mutex> doner_lock(syncr_.done_mtx); + // Wait reader thread to hold the read lock. + syncr_.done_cv.wait(doner_lock, [this](){ return syncr_.done; }); + } + + // Signal the writer thread to terminate. + { + lock_guard<mutex> terminate_lock(syncw_.terminate_mtx); + syncw_.terminate = true; + } + syncw_.terminate_cv.notify_one(); + + // Join the writer thread. + threadw->join(); + + // Signal the reader thread to terminate. + { + lock_guard<mutex> terminate_lock(syncr_.terminate_mtx); + syncr_.terminate = true; + } + syncr_.terminate_cv.notify_one(); + + // Join the reader thread. + threadr->join(); +} + +} |