diff --git a/src/lib/dhcpsrv/resource_handler.cc b/src/lib/dhcpsrv/resource_handler.cc index cbd1c099b8..d49aa0eb9b 100644 --- a/src/lib/dhcpsrv/resource_handler.cc +++ b/src/lib/dhcpsrv/resource_handler.cc @@ -85,9 +85,8 @@ ResourceHandler::unLock(Lease::Type type, const asiolink::IOAddress& addr) { auto key = boost::make_tuple(type, addr.toBytes()); auto it = owned_.find(key); if (it == owned_.end()) { - isc_throw(InvalidParameter, - "does not owne " << Lease::typeToText(type) << " " - << addr.toText()); + isc_throw(NotFound, "does not own " << Lease::typeToText(type) + << " " << addr.toText()); } { lock_guard lock_(mutex_); diff --git a/src/lib/dhcpsrv/tests/resource_handler_unittest.cc b/src/lib/dhcpsrv/tests/resource_handler_unittest.cc index 1fe6e4c1c0..532429671a 100644 --- a/src/lib/dhcpsrv/tests/resource_handler_unittest.cc +++ b/src/lib/dhcpsrv/tests/resource_handler_unittest.cc @@ -9,6 +9,7 @@ #include using namespace isc; +using namespace isc::asiolink; using namespace isc::dhcp; namespace { @@ -33,4 +34,476 @@ TEST(ResourceHandleTest, empty4) { } } +// Verifies behavior with one handler. +TEST(ResourceHandleTest, one) { + IOAddress addr("2001:db8::1"); + + try { + // Get a resource handler. + ResourceHandler resource_handler; + + // Try to lock it. + bool busy = false; + EXPECT_NO_THROW(busy = !resource_handler.tryLock(Lease::TYPE_NA, addr)); + + // Should return false (free). + EXPECT_FALSE(busy); + } catch (const std::exception& ex) { + ADD_FAILURE() << "unexpected exception: " << ex.what(); + } +} + +// Verifies behavior with one IPv4 handler. +TEST(ResourceHandleTest, one4) { + IOAddress addr("192.0.2.1"); + + try { + // Get a resource handler. + ResourceHandler4 resource_handler; + + // Try to lock it. + bool busy = false; + EXPECT_NO_THROW(busy = !resource_handler.tryLock4(addr)); + + // Should return false (free). + EXPECT_FALSE(busy); + } catch (const std::exception& ex) { + ADD_FAILURE() << "unexpected exception: " << ex.what(); + } +} + +// Verifies behavior with two handlers. +TEST(ResourceHandleTest, two) { + IOAddress addr("2001:db8::"); + + try { + // Get a resource handler. + ResourceHandler resource_handler; + + // Try to lock it. + bool busy = false; + EXPECT_NO_THROW(busy = !resource_handler.tryLock(Lease::TYPE_PD, addr)); + + // Should return false (free). + EXPECT_FALSE(busy); + + // Get a second resource handler. + ResourceHandler resource_handler2; + + // Try to lock it. + EXPECT_NO_THROW(busy = !resource_handler2.tryLock(Lease::TYPE_PD, addr)); + + // Should return true (busy); + EXPECT_TRUE(busy); + } catch (const std::exception& ex) { + ADD_FAILURE() << "unexpected exception: " << ex.what(); + } +} + +// Verifies behavior with two IPv4 handlers. +TEST(ResourceHandleTest, two4) { + IOAddress addr("192.0.2.1"); + + try { + // Get a resource handler. + ResourceHandler4 resource_handler; + + // Try to lock it. + bool busy = false; + EXPECT_NO_THROW(busy = !resource_handler.tryLock4(addr)); + + // Should return false (free). + EXPECT_FALSE(busy); + + // Get a second resource handler. + ResourceHandler4 resource_handler2; + + // Try to lock it. + EXPECT_NO_THROW(busy = !resource_handler2.tryLock4(addr)); + + // Should return true (busy); + EXPECT_TRUE(busy); + } catch (const std::exception& ex) { + ADD_FAILURE() << "unexpected exception: " << ex.what(); + } +} + +// Verifies behavior with two handlers in different blocks (sequence). +TEST(ResourceHandleTest, sequence) { + IOAddress addr("2001:db8::1"); + + try { + // Get a resource handler. + ResourceHandler resource_handler; + + // Try to lock it. + bool busy = false; + EXPECT_NO_THROW(busy = !resource_handler.tryLock(Lease::TYPE_NA, addr)); + + // Should return false (free). + EXPECT_FALSE(busy); + } catch (const std::exception& ex) { + ADD_FAILURE() << "unexpected exception: " << ex.what(); + } + + try { + // Get a second resource handler. + ResourceHandler resource_handler2; + + // Try to lock it. + bool busy = false; + EXPECT_NO_THROW(busy = !resource_handler2.tryLock(Lease::TYPE_NA, addr)); + + // Should return false (free) + EXPECT_FALSE(busy); + } catch (const std::exception& ex) { + ADD_FAILURE() << "unexpected exception: " << ex.what(); + } +} + +// Verifies behavior with two IPv4 handlers in different blocks (sequence). +TEST(ResourceHandleTest, sequence4) { + IOAddress addr("192.0.2.1"); + + try { + // Get a resource handler. + ResourceHandler4 resource_handler; + + // Try to lock it. + bool busy = false; + EXPECT_NO_THROW(busy = !resource_handler.tryLock4(addr)); + + // Should return false (free). + EXPECT_FALSE(busy); + } catch (const std::exception& ex) { + ADD_FAILURE() << "unexpected exception: " << ex.what(); + } + + try { + // Get a second resource handler. + ResourceHandler4 resource_handler2; + + // Try to lock it. + bool busy = false; + EXPECT_NO_THROW(busy = !resource_handler2.tryLock4(addr)); + + // Should return false (free) + EXPECT_FALSE(busy); + } catch (const std::exception& ex) { + ADD_FAILURE() << "unexpected exception: " << ex.what(); + } +} + +// Verifies behavior with two handlers for different addresses. +TEST(ResourceHandleTest, differentAddress) { + IOAddress addr("2001:db8::1"); + IOAddress addr2("2001:db8::2"); + + try { + // Get a resource handler. + ResourceHandler resource_handler; + + // Try to lock it. + bool busy = false; + EXPECT_NO_THROW(busy = !resource_handler.tryLock(Lease::TYPE_NA, addr)); + + // Should return false (free). + EXPECT_FALSE(busy); + + // Get a second resource handler. + ResourceHandler resource_handler2; + + // Try to lock it. + EXPECT_NO_THROW(busy = !resource_handler2.tryLock(Lease::TYPE_NA, addr2)); + + // Should return false (free). + EXPECT_FALSE(busy); + } catch (const std::exception& ex) { + ADD_FAILURE() << "unexpected exception: " << ex.what(); + } +} + +// Verifies behavior with two IPv4 handlers. +TEST(ResourceHandleTest, differentAddress4) { + IOAddress addr("192.0.2.1"); + IOAddress addr2("192.0.2.2"); + + try { + // Get a resource handler. + ResourceHandler4 resource_handler; + + // Try to lock it. + bool busy = false; + EXPECT_NO_THROW(busy = !resource_handler.tryLock4(addr)); + + // Should return false (free). + EXPECT_FALSE(busy); + + // Get a second resource handler. + ResourceHandler4 resource_handler2; + + // Try to lock it. + EXPECT_NO_THROW(busy = !resource_handler2.tryLock4(addr2)); + + // Should return false (free). + EXPECT_FALSE(busy); + } catch (const std::exception& ex) { + ADD_FAILURE() << "unexpected exception: " << ex.what(); + } +} + +// Verifies behavior with two handlers for different types. +TEST(ResourceHandleTest, differentTypes) { + IOAddress addr("2001:db8::"); + + try { + // Get a resource handler. + ResourceHandler resource_handler; + + // Try to lock it. + bool busy = false; + EXPECT_NO_THROW(busy = !resource_handler.tryLock(Lease::TYPE_NA, addr)); + + // Should return false (free). + EXPECT_FALSE(busy); + + // Get a second resource handler. + ResourceHandler resource_handler2; + + // Try to lock it. + EXPECT_NO_THROW(busy = !resource_handler2.tryLock(Lease::TYPE_PD, addr)); + + // Should return false (free). + EXPECT_FALSE(busy); + } catch (const std::exception& ex) { + ADD_FAILURE() << "unexpected exception: " << ex.what(); + } +} + +// Verifies behavior of the isLocked predicate. +TEST(ResourceHandleTest, isLocked) { + IOAddress addr("2001:db8::1"); + IOAddress addr2("2001:db8::2"); + IOAddress addr3("2001:db8::3"); + + try { + // Get a resource handler. + ResourceHandler resource_handler; + + // Try to lock it. + bool busy = false; + EXPECT_NO_THROW(busy = !resource_handler.tryLock(Lease::TYPE_NA, addr)); + + // Should return false (free). + EXPECT_FALSE(busy); + + // Get a second resource handler. + ResourceHandler resource_handler2; + + // Try to lock it. + EXPECT_NO_THROW(busy = !resource_handler2.tryLock(Lease::TYPE_NA, addr2)); + + // Should return false (free). + EXPECT_FALSE(busy); + + // Check ownership. + EXPECT_TRUE(resource_handler.isLocked(Lease::TYPE_NA, addr)); + EXPECT_FALSE(resource_handler.isLocked(Lease::TYPE_NA, addr2)); + EXPECT_FALSE(resource_handler.isLocked(Lease::TYPE_NA, addr3)); + EXPECT_FALSE(resource_handler.isLocked(Lease::TYPE_PD, addr)); + EXPECT_FALSE(resource_handler2.isLocked(Lease::TYPE_NA, addr)); + EXPECT_TRUE(resource_handler2.isLocked(Lease::TYPE_NA, addr2)); + EXPECT_FALSE(resource_handler2.isLocked(Lease::TYPE_NA, addr3)); + EXPECT_FALSE(resource_handler2.isLocked(Lease::TYPE_PD, addr2)); + } catch (const std::exception& ex) { + ADD_FAILURE() << "unexpected exception: " << ex.what(); + } +} + +// Verifies behavior with two IPv4 handlers. +TEST(ResourceHandleTest, isLocked4) { + IOAddress addr("192.0.2.1"); + IOAddress addr2("192.0.2.2"); + IOAddress addr3("192.0.2.3"); + + try { + // Get a resource handler. + ResourceHandler4 resource_handler; + + // Try to lock it. + bool busy = false; + EXPECT_NO_THROW(busy = !resource_handler.tryLock4(addr)); + + // Should return false (free). + EXPECT_FALSE(busy); + + // Get a second resource handler. + ResourceHandler4 resource_handler2; + + // Try to lock it. + EXPECT_NO_THROW(busy = !resource_handler2.tryLock4(addr2)); + + // Should return false (free). + EXPECT_FALSE(busy); + + // Check ownership. + EXPECT_TRUE(resource_handler.isLocked4(addr)); + EXPECT_FALSE(resource_handler.isLocked4(addr2)); + EXPECT_FALSE(resource_handler.isLocked4(addr3)); + EXPECT_FALSE(resource_handler2.isLocked4(addr)); + EXPECT_TRUE(resource_handler2.isLocked4(addr2)); + EXPECT_FALSE(resource_handler2.isLocked4(addr3)); + + // ResourceHandler4 derives from ResourceHandler. + EXPECT_FALSE(resource_handler.isLocked(Lease::TYPE_NA, addr)); + EXPECT_FALSE(resource_handler2.isLocked(Lease::TYPE_NA, addr2)); + } catch (const std::exception& ex) { + ADD_FAILURE() << "unexpected exception: " << ex.what(); + } +} + +// Verifies that double tryLock call for the same resource returns busy. +TEST(ResourceHandleTest, doubleTryLock) { + IOAddress addr("2001:db8::"); + + try { + // Get a resource handler. + ResourceHandler resource_handler; + + // Try to lock it. + bool busy = false; + EXPECT_NO_THROW(busy = !resource_handler.tryLock(Lease::TYPE_PD, addr)); + + // Should return false (free). + EXPECT_FALSE(busy); + + // Try to lock it again. + EXPECT_NO_THROW(busy = !resource_handler.tryLock(Lease::TYPE_PD, addr)); + + // Should return true (busy); + EXPECT_TRUE(busy); + } catch (const std::exception& ex) { + ADD_FAILURE() << "unexpected exception: " << ex.what(); + } +} + +// Verifies that double tryLock call for the same resource returns busy (v4). +TEST(ResourceHandleTest, doubleTryLock4) { + IOAddress addr("192.0.2.1"); + + try { + // Get a resource handler. + ResourceHandler4 resource_handler; + + // Try to lock it. + bool busy = false; + EXPECT_NO_THROW(busy = !resource_handler.tryLock4(addr)); + + // Should return false (free). + EXPECT_FALSE(busy); + + // Try to lock it again. + EXPECT_NO_THROW(busy = !resource_handler.tryLock4(addr)); + + // Should return true (busy); + EXPECT_TRUE(busy); + } catch (const std::exception& ex) { + ADD_FAILURE() << "unexpected exception: " << ex.what(); + } +} + +// Verifies behavior of the unLock method. +TEST(ResourceHandleTest, unLock) { + IOAddress addr("2001:db8::1"); + + try { + // Get a resource handler. + ResourceHandler resource_handler; + + // Try to lock it. + bool busy = false; + EXPECT_NO_THROW(busy = !resource_handler.tryLock(Lease::TYPE_NA, addr)); + + // Should return false (free). + EXPECT_FALSE(busy); + + // The resource is owned by us. + EXPECT_TRUE(resource_handler.isLocked(Lease::TYPE_NA, addr)); + + // Try to unlock it. + EXPECT_NO_THROW(resource_handler.unLock(Lease::TYPE_NA, addr)); + + // The resource is no longer owned by us. + EXPECT_FALSE(resource_handler.isLocked(Lease::TYPE_NA, addr)); + + // Get a second resource handler. + ResourceHandler resource_handler2; + + // Try to lock it by the second handler. + EXPECT_NO_THROW(busy = !resource_handler2.tryLock(Lease::TYPE_NA, addr)); + + // Should return false (free). + EXPECT_FALSE(busy); + + // The resource is owned by the second handler. + EXPECT_FALSE(resource_handler.isLocked(Lease::TYPE_NA, addr)); + EXPECT_TRUE(resource_handler2.isLocked(Lease::TYPE_NA, addr)); + + // Only the owner is allowed to release a resource. + EXPECT_THROW(resource_handler.unLock(Lease::TYPE_NA, addr), NotFound); + EXPECT_NO_THROW(resource_handler2.unLock(Lease::TYPE_NA, addr)); + // Once. + EXPECT_THROW(resource_handler2.unLock(Lease::TYPE_NA, addr), NotFound); + } catch (const std::exception& ex) { + ADD_FAILURE() << "unexpected exception: " << ex.what(); + } +} + +// Verifies behavior of the unLock method. +TEST(ResourceHandleTest, unLock4) { + IOAddress addr("192.0.2.1"); + + try { + // Get a resource handler. + ResourceHandler4 resource_handler; + + // Try to lock it. + bool busy = false; + EXPECT_NO_THROW(busy = !resource_handler.tryLock4(addr)); + + // Should return false (free). + EXPECT_FALSE(busy); + + // The resource is owned by us. + EXPECT_TRUE(resource_handler.isLocked4(addr)); + + // Try to unlock it. + EXPECT_NO_THROW(resource_handler.unLock4(addr)); + + // The resource is no longer owned by us. + EXPECT_FALSE(resource_handler.isLocked4(addr)); + + // Get a second resource handler + ResourceHandler4 resource_handler2; + + // Try to lock it by the second handler. + EXPECT_NO_THROW(busy = !resource_handler2.tryLock4(addr)); + + // Should return false (free). + EXPECT_FALSE(busy); + + // The resource is owned by the second handler. + EXPECT_FALSE(resource_handler.isLocked4(addr)); + EXPECT_TRUE(resource_handler2.isLocked4(addr)); + + // Only the owner is allowed to release a resource. + EXPECT_THROW(resource_handler.unLock4(addr), NotFound); + EXPECT_NO_THROW(resource_handler2.unLock4(addr)); + // Once. + EXPECT_THROW(resource_handler2.unLock4(addr), NotFound); + } catch (const std::exception& ex) { + ADD_FAILURE() << "unexpected exception: " << ex.what(); + } +} + } // end of anonymous namespace diff --git a/src/lib/util/Makefile.am b/src/lib/util/Makefile.am index 0d75edf847..fe269446cb 100644 --- a/src/lib/util/Makefile.am +++ b/src/lib/util/Makefile.am @@ -22,6 +22,7 @@ libkea_util_la_SOURCES += pid_file.h pid_file.cc libkea_util_la_SOURCES += pointer_util.h libkea_util_la_SOURCES += process_spawn.h process_spawn.cc libkea_util_la_SOURCES += range_utilities.h +libkea_util_la_SOURCES += readwrite_mutex.h libkea_util_la_SOURCES += signal_set.cc signal_set.h libkea_util_la_SOURCES += staged_value.h libkea_util_la_SOURCES += state_model.cc state_model.h @@ -67,6 +68,7 @@ libkea_util_include_HEADERS = \ pointer_util.h \ process_spawn.h \ range_utilities.h \ + readwrite_mutex.h \ signal_set.h \ staged_value.h \ state_model.h \ diff --git a/src/lib/util/readwrite_mutex.h b/src/lib/util/readwrite_mutex.h new file mode 100644 index 0000000000..2a49cc7aba --- /dev/null +++ b/src/lib/util/readwrite_mutex.h @@ -0,0 +1,187 @@ +// 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/. + +#ifndef READWRITE_MUTEX_H +#define READWRITE_MUTEX_H + +/// @file readwrite_mutex.h +/// +/// Standard implementation of read-write mutexes with writer preference +/// using C++11 mutex and condition variable. +/// As we need only the RAII wrappers implement only used methods. + +#include +#include +#include +#include +#include + +namespace isc { +namespace util { + +/// @brief Read-Write Mutex. +/// +/// The code is based on Howard Hinnant's reference implementation +/// for C++17 shared_mutex. +class ReadWriteMutex : public boost::noncopyable { +public: + + /// Constants. + + /// @brief The write entered flag (higher bit so 2^31). + static const unsigned WRITE_ENTERED = + 1U << (sizeof(unsigned) * CHAR_BIT - 1); + + /// @brief The maximum number of readers (flag complement so 2^30 - 1). + static const unsigned MAX_READERS = ~WRITE_ENTERED; + + /// @brief Constructor. + ReadWriteMutex() : state_(0) { + } + + /// @brief Destructor. + /// + /// @note: do not check that state is 0 as there is nothing very + /// useful to do in this case... + virtual ~ReadWriteMutex() { + std::lock_guard lk(mutex_); + } + + /// @brief Lock write. + void lock_write() { + std::unique_lock lk(mutex_); + // Wait until the write entered flag can be set. + gate1_.wait(lk, [=]() { return (!writeEntered()); }); + state_ |= WRITE_ENTERED; + // Wait until there are no more readers. + gate2_.wait(lk, [=]() { return (readers() == 0);}); + } + + /// @brief Unlock write. + /// + /// @note: do not check that WRITE_ENTERED was set. + void unlock_write() { + std::lock_guard lk(mutex_); + state_ = 0; + // Wake-up readers when exiting the guard. + gate1_.notify_all(); + } + + /// @brief Lock read. + void lock_read() { + std::unique_lock lk(mutex_); + // Wait if there is a writer or if readers overflow. + gate1_.wait(lk, [=]() { return (state_ < MAX_READERS); }); + ++state_; + } + + /// @brief Unlock read. + /// + /// @note: do not check that there is a least one reader. + void unlock_read() { + std::lock_guard lk(mutex_); + unsigned prev = state_--; + if (writeEntered()) { + if (readers() == 0) { + // Last reader: wake up a waiting writer. + gate2_.notify_one(); + } + } else { + if (prev == MAX_READERS) { + // Reader overflow: wake up one waiting reader. + gate1_.notify_one(); + } + } + } + +private: + + /// Helpers. + + /// @brief Check if the write entered flag is set. + bool writeEntered() const { + return (state_ & WRITE_ENTERED); + } + + /// @brief Return the number of readers. + unsigned readers() const { + return (state_ & MAX_READERS); + } + + /// Members. + + /// @brief Mutex. + /// + /// Used to protect the state and in condition variables. + std::mutex mutex_; + + /// @brief First condition variable. + /// + /// Used to block while the write entered flag is set or readers overflow. + std::condition_variable gate1_; + + /// @brief Second condition variable. + /// + /// Used to block writers until the reader count decrements to zero. + std::condition_variable gate2_; + + /// @brief State. + /// + /// Used to handle the write entered flag and the reader count. + unsigned state_; +}; + +/// @brief Read mutex RAII handler. +/// +/// The constructor acquires the lock, the destructor releases it. +class ReadLockGuard : public boost::noncopyable { +public: + + /// @brief Constructor. + /// + /// @param rw_mutex The read mutex. + ReadLockGuard(ReadWriteMutex& rw_mutex) : rw_mutex_(rw_mutex) { + rw_mutex_.lock_read(); + } + + /// @brief Destructor. + virtual ~ReadLockGuard() { + rw_mutex_.unlock_read(); + } + +private: + /// @brief The read-write mutex. + ReadWriteMutex& rw_mutex_; + +}; + +/// @brief Write mutex RAII handler. +/// +/// The constructor acquires the lock, the destructor releases it. +class WriteLockGuard : public boost::noncopyable { +public: + + /// @brief Constructor. + /// + /// @param rw_mutex The write mutex. + WriteLockGuard(ReadWriteMutex& rw_mutex) : rw_mutex_(rw_mutex) { + rw_mutex_.lock_write(); + } + + /// @brief Destructor. + virtual ~WriteLockGuard() { + rw_mutex_.unlock_write(); + } + +private: + /// @brief The read-write mutex. + ReadWriteMutex& rw_mutex_; +}; + +} // namespace util +} // namespace isc + +#endif // READWRITE_MUTEX_H diff --git a/src/lib/util/tests/Makefile.am b/src/lib/util/tests/Makefile.am index 02750dd921..6c5e9638af 100644 --- a/src/lib/util/tests/Makefile.am +++ b/src/lib/util/tests/Makefile.am @@ -56,6 +56,7 @@ run_unittests_SOURCES += strutil_unittest.cc run_unittests_SOURCES += thread_pool_unittest.cc run_unittests_SOURCES += time_utilities_unittest.cc run_unittests_SOURCES += range_utilities_unittest.cc +run_unittests_SOURCES += readwrite_mutex_unittest.cc run_unittests_SOURCES += signal_set_unittest.cc run_unittests_SOURCES += stopwatch_unittest.cc run_unittests_SOURCES += versioned_csv_file_unittest.cc 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 0000000000..c868dcdd99 --- /dev/null +++ b/src/lib/util/tests/readwrite_mutex_unittest.cc @@ -0,0 +1,689 @@ +// 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 + +#include + +#include + +#include +#include +#include + +using namespace isc::util; +using namespace std; + +namespace { + +// Verify basic read lock guard. +TEST(ReadWriteMutexTest, basicRead) { + ReadWriteMutex rw_mutex; + ReadLockGuard lock(rw_mutex); +} + +// Verify basic write lock guard. +TEST(ReadWriteMutexTest, basicWrite) { + ReadWriteMutex rw_mutex; + WriteLockGuard lock(rw_mutex); +} + +// Verify read lock guard using a thread. +TEST(ReadWriteMutexTest, read) { + mutex mutex; + ReadWriteMutex rw_mutex; + bool started = false; + bool work = false; + bool done = false; + bool terminate = false; + + // Create a thread. + thread thread( + [&mutex, &rw_mutex, &started, &work, &done, &terminate] () + mutable -> void { + // Signal the thread started. + { + lock_guard lock(mutex); + started = true; + } + // Wait to work. + for (;;) { + bool ready = false; + { + lock_guard lock(mutex); + ready = work; + } + if (ready) + break; + usleep(100); + } + { + // Enter a read lock guard. + ReadLockGuard rwlock(rw_mutex); + { + // Signal the thread holds the guard. + lock_guard lock(mutex); + done = true; + } + // Wait to terminate. + for (;;) { + lock_guard lock(mutex); + if (terminate) { + return; + } + } + } + }); + + // Wait thread to start. + for (;;) { + bool ready = false; + { + lock_guard lock(mutex); + ready = started; + } + if (ready) { + break; + } + usleep(100); + } + + // Signal the thread to work. + { + lock_guard lock(mutex); + work = true; + } + + // Wait thread to hold the read lock guard. + for (;;) { + bool ready = false; + { + lock_guard lock(mutex); + ready = done; + } + if (ready) { + break; + } + usleep(100); + } + + // Signal the thread to terminate. + { + lock_guard lock(mutex); + terminate = true; + } + + // Join the thread. + thread.join(); +} + +// Verify write lock guard using a thread. +TEST(ReadWriteMutexTest, write) { + mutex mutex; + ReadWriteMutex rw_mutex; + bool started = false; + bool work = false; + bool done = false; + bool terminate = false; + + // Create a thread. + thread thread( + [&mutex, &rw_mutex, &started, &work, &done, &terminate] () + mutable -> void { + // Signal the thread started. + { + lock_guard lock(mutex); + started = true; + } + // Wait to work. + for (;;) { + bool ready = false; + { + lock_guard lock(mutex); + ready = work; + } + if (ready) + break; + usleep(100); + } + { + // Enter a write lock guard. + WriteLockGuard rwlock(rw_mutex); + { + // Signal the thread holds the guard. + lock_guard lock(mutex); + done = true; + } + // Wait to terminate. + for (;;) { + lock_guard lock(mutex); + if (terminate) { + return; + } + } + } + }); + + // Wait thread to start. + for (;;) { + bool ready = false; + { + lock_guard lock(mutex); + ready = started; + } + if (ready) { + break; + } + usleep(100); + } + + // Signal the thread to work. + { + lock_guard lock(mutex); + work = true; + } + + // Wait thread to hold the write lock guard. + for (;;) { + bool ready = false; + { + lock_guard lock(mutex); + ready = done; + } + if (ready) { + break; + } + usleep(100); + } + + // Signal the thread to terminate. + { + lock_guard lock(mutex); + terminate = true; + } + + // Join the thread. + thread.join(); +} + +// Verify read lock guard can be acquired by multiple threads. +TEST(ReadWriteMutexTest, readRead) { + mutex mutex; + ReadWriteMutex rw_mutex; + bool started = false; + bool work = false; + bool done = false; + bool terminate = false; + + // Create a thread. + thread thread( + [&mutex, &rw_mutex, &started, &work, &done, &terminate] () + mutable -> void { + // Signal the thread started. + { + lock_guard lock(mutex); + started = true; + } + // Wait to work. + for (;;) { + bool ready = false; + { + lock_guard lock(mutex); + ready = work; + } + if (ready) + break; + usleep(100); + } + { + // Enter a read lock guard. + ReadLockGuard rwlock(rw_mutex); + { + // Signal the thread holds the guard. + lock_guard lock(mutex); + done = true; + } + // Wait to terminate. + for (;;) { + lock_guard lock(mutex); + if (terminate) { + return; + } + } + } + }); + + // Enter a read load guard. + ReadLockGuard rwlock(rw_mutex); + + // Wait thread to start.. + for (;;) { + bool ready = false; + { + lock_guard lock(mutex); + ready = started; + } + if (ready) { + break; + } + usleep(100); + } + + // Signal the thread to work. + { + lock_guard lock(mutex); + work = true; + } + + // Wait thread to hold the read lock guard. + for (;;) { + bool ready = false; + { + lock_guard lock(mutex); + ready = done; + } + if (ready) { + break; + } + usleep(100); + } + + // Signal the thread to terminate. + { + lock_guard lock(mutex); + terminate = true; + } + + // Join the thread. + thread.join(); +} + +// Verify write lock guard is exclusive of a reader. +TEST(ReadWriteMutexTest, readWrite) { + mutex mutex; + ReadWriteMutex rw_mutex; + bool started = false; + bool work = false; + bool done = false; + bool terminate = false; + + // Create a thread. + thread thread( + [&mutex, &rw_mutex, &started, &work, &done, &terminate] () + mutable -> void { + // Signal the thread started. + { + lock_guard lock(mutex); + started = true; + } + // Wait to work. + for (;;) { + bool ready = false; + { + lock_guard lock(mutex); + ready = work; + } + if (ready) + break; + usleep(100); + } + { + // Enter a write lock guard. + WriteLockGuard rwlock(rw_mutex); + { + // Signal the thread holds the guard. + lock_guard lock(mutex); + done = true; + } + // Wait to terminate. + for (;;) { + lock_guard lock(mutex); + if (terminate) { + return; + } + } + } + }); + + // Wait thread to start. + for (;;) { + bool ready = false; + { + lock_guard lock(mutex); + ready = started; + } + if (ready) { + break; + } + usleep(100); + } + + { + // Enter a read load guard. + ReadLockGuard rwlock(rw_mutex); + + // Signal the thread to work. + { + lock_guard lock(mutex); + work = true; + } + + cout << "pausing for one second\n"; + usleep(1000000); + bool ready = false; + { + lock_guard lock(mutex); + ready = done; + } + EXPECT_FALSE(ready); + } + + // Wait thread to hold the write lock guard. + for (;;) { + bool ready = false; + { + lock_guard lock(mutex); + ready = done; + } + if (ready) { + break; + } + usleep(100); + } + + // Signal the thread to terminate. + { + lock_guard lock(mutex); + terminate = true; + } + + // Join the thread. + thread.join(); +} + +// Verify write lock guard is exclusive of a writer. +TEST(ReadWriteMutexTest, writeWrite) { + mutex mutex; + ReadWriteMutex rw_mutex; + bool started = false; + bool work = false; + bool done = false; + bool terminate = false; + + // Create a thread. + thread thread( + [&mutex, &rw_mutex, &started, &work, &done, &terminate] () + mutable -> void { + // Signal the thread started. + { + lock_guard lock(mutex); + started = true; + } + // Wait to work. + for (;;) { + bool ready = false; + { + lock_guard lock(mutex); + ready = work; + } + if (ready) + break; + usleep(100); + } + { + // Enter a write lock guard. + WriteLockGuard rwlock(rw_mutex); + { + // Signal the thread holds the guard. + lock_guard lock(mutex); + done = true; + } + // Wait to terminate. + for (;;) { + lock_guard lock(mutex); + if (terminate) { + return; + } + } + } + }); + + // Wait thread to start. + for (;;) { + bool ready = false; + { + lock_guard lock(mutex); + ready = started; + } + if (ready) { + break; + } + usleep(100); + } + + { + // Enter a write lock guard. + WriteLockGuard rwlock(rw_mutex); + + // Signal the thread to work. + { + lock_guard lock(mutex); + work = true; + } + + cout << "pausing for one second\n"; + usleep(1000000); + bool ready = false; + { + lock_guard lock(mutex); + ready = done; + } + EXPECT_FALSE(ready); + } + + // Wait thread to hold the write lock guard. + for (;;) { + bool ready = false; + { + lock_guard lock(mutex); + ready = done; + } + if (ready) { + break; + } + usleep(100); + } + + // Signal the thread to terminate. + { + lock_guard lock(mutex); + terminate = true; + } + + // Join the thread. + thread.join(); +} + +// Verify that a writer has the preference. +TEST(ReadWriteMutexTest, readWriteRead) { + mutex mutex; + ReadWriteMutex rw_mutex; + bool started1 = false; + bool started2 = false; + bool work1 = false; + bool work2 = false; + bool done1 = false; + bool done2 = false; + bool terminate1 = false; + bool terminate2 = false; + + // First thread is a writer. + thread thread1( + [&mutex, &rw_mutex, &started1, &work1, &done1, &terminate1] () + mutable -> void { + // Signal the thread started. + { + lock_guard lock(mutex); + started1 = true; + } + // Wait to work. + for (;;) { + bool ready = false; + { + lock_guard lock(mutex); + ready = work1; + } + if (ready) + break; + usleep(100); + } + { + // Enter a write lock guard. + WriteLockGuard rwlock(rw_mutex); + { + // Signal the thread holds the guard. + lock_guard lock(mutex); + done1 = true; + } + // Wait to terminate. + for (;;) { + lock_guard lock(mutex); + if (terminate1) { + return; + } + } + } + }); + + // Second thread is a writer. + thread thread2( + [&mutex, &rw_mutex, &started2, &work2, &done2, &terminate2] () + mutable -> void { + // Signal the thread started. + { + lock_guard lock(mutex); + started2 = true; + } + // Wait to work. + for (;;) { + bool ready = false; + { + lock_guard lock(mutex); + ready = work2; + } + if (ready) + break; + usleep(100); + } + { + // Enter a read lock guard. + ReadLockGuard rwlock(rw_mutex); + { + // Signal the thread holds the guard. + lock_guard lock(mutex); + done2 = true; + } + // Wait to terminate. + for (;;) { + lock_guard lock(mutex); + if (terminate2) { + return; + } + } + } + }); + + // Wait threads to start. + for (;;) { + bool ready = false; + { + lock_guard lock(mutex); + ready = started1 && started2; + } + if (ready) { + break; + } + usleep(100); + } + + { + // Enter a read load guard. + ReadLockGuard rwlock(rw_mutex); + + // Signal the writer thread to work. + { + lock_guard lock(mutex); + work1 = true; + } + + cout << "pausing for one second\n"; + usleep(1000000); + bool ready = false; + { + lock_guard lock(mutex); + ready = done1; + } + EXPECT_FALSE(ready); + + // Signal the reader thread to work. + { + lock_guard lock(mutex); + work2 = true; + } + + cout << "pausing for one second\n"; + usleep(1000000); + ready = false; + { + lock_guard lock(mutex); + ready = done2; + } + EXPECT_FALSE(ready); + } + + cout << "pausing for one second\n"; + usleep(1000000); + { + bool ready = false; + { + lock_guard lock(mutex); + ready = done2; + } + EXPECT_FALSE(ready); + } + // Signal the writer thread to terminate. + { + lock_guard lock(mutex); + terminate1 = true; + } + + // Join the writer thread. + thread1.join(); + + // Wait reader thread to hold the read lock guard. + for (;;) { + bool ready = false; + { + lock_guard lock(mutex); + ready = done2; + } + if (ready) { + break; + } + usleep(100); + } + + // Signal the reader thread to terminate. + { + lock_guard lock(mutex); + terminate2 = true; + } + + // Join the thread. + thread2.join(); +} + +}