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<mutex> lock_(mutex_);
#include <gtest/gtest.h>
using namespace isc;
+using namespace isc::asiolink;
using namespace isc::dhcp;
namespace {
}
}
+// 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
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
pointer_util.h \
process_spawn.h \
range_utilities.h \
+ readwrite_mutex.h \
signal_set.h \
staged_value.h \
state_model.h \
--- /dev/null
+// 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 <exceptions/exceptions.h>
+#include <boost/noncopyable.hpp>
+#include <climits>
+#include <condition_variable>
+#include <mutex>
+
+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<std::mutex> lk(mutex_);
+ }
+
+ /// @brief Lock write.
+ void lock_write() {
+ std::unique_lock<std::mutex> 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<std::mutex> lk(mutex_);
+ state_ = 0;
+ // Wake-up readers when exiting the guard.
+ gate1_.notify_all();
+ }
+
+ /// @brief Lock read.
+ void lock_read() {
+ std::unique_lock<std::mutex> 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<std::mutex> 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
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
--- /dev/null
+// 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 <iostream>
+#include <thread>
+#include <unistd.h>
+
+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<std::mutex> lock(mutex);
+ started = true;
+ }
+ // Wait to work.
+ for (;;) {
+ bool ready = false;
+ {
+ lock_guard<std::mutex> 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<std::mutex> lock(mutex);
+ done = true;
+ }
+ // Wait to terminate.
+ for (;;) {
+ lock_guard<std::mutex> lock(mutex);
+ if (terminate) {
+ return;
+ }
+ }
+ }
+ });
+
+ // Wait thread to start.
+ for (;;) {
+ bool ready = false;
+ {
+ lock_guard<std::mutex> lock(mutex);
+ ready = started;
+ }
+ if (ready) {
+ break;
+ }
+ usleep(100);
+ }
+
+ // Signal the thread to work.
+ {
+ lock_guard<std::mutex> lock(mutex);
+ work = true;
+ }
+
+ // Wait thread to hold the read lock guard.
+ for (;;) {
+ bool ready = false;
+ {
+ lock_guard<std::mutex> lock(mutex);
+ ready = done;
+ }
+ if (ready) {
+ break;
+ }
+ usleep(100);
+ }
+
+ // Signal the thread to terminate.
+ {
+ lock_guard<std::mutex> 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<std::mutex> lock(mutex);
+ started = true;
+ }
+ // Wait to work.
+ for (;;) {
+ bool ready = false;
+ {
+ lock_guard<std::mutex> 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<std::mutex> lock(mutex);
+ done = true;
+ }
+ // Wait to terminate.
+ for (;;) {
+ lock_guard<std::mutex> lock(mutex);
+ if (terminate) {
+ return;
+ }
+ }
+ }
+ });
+
+ // Wait thread to start.
+ for (;;) {
+ bool ready = false;
+ {
+ lock_guard<std::mutex> lock(mutex);
+ ready = started;
+ }
+ if (ready) {
+ break;
+ }
+ usleep(100);
+ }
+
+ // Signal the thread to work.
+ {
+ lock_guard<std::mutex> lock(mutex);
+ work = true;
+ }
+
+ // Wait thread to hold the write lock guard.
+ for (;;) {
+ bool ready = false;
+ {
+ lock_guard<std::mutex> lock(mutex);
+ ready = done;
+ }
+ if (ready) {
+ break;
+ }
+ usleep(100);
+ }
+
+ // Signal the thread to terminate.
+ {
+ lock_guard<std::mutex> 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<std::mutex> lock(mutex);
+ started = true;
+ }
+ // Wait to work.
+ for (;;) {
+ bool ready = false;
+ {
+ lock_guard<std::mutex> 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<std::mutex> lock(mutex);
+ done = true;
+ }
+ // Wait to terminate.
+ for (;;) {
+ lock_guard<std::mutex> lock(mutex);
+ if (terminate) {
+ return;
+ }
+ }
+ }
+ });
+
+ // Enter a read load guard.
+ ReadLockGuard rwlock(rw_mutex);
+
+ // Wait thread to start..
+ for (;;) {
+ bool ready = false;
+ {
+ lock_guard<std::mutex> lock(mutex);
+ ready = started;
+ }
+ if (ready) {
+ break;
+ }
+ usleep(100);
+ }
+
+ // Signal the thread to work.
+ {
+ lock_guard<std::mutex> lock(mutex);
+ work = true;
+ }
+
+ // Wait thread to hold the read lock guard.
+ for (;;) {
+ bool ready = false;
+ {
+ lock_guard<std::mutex> lock(mutex);
+ ready = done;
+ }
+ if (ready) {
+ break;
+ }
+ usleep(100);
+ }
+
+ // Signal the thread to terminate.
+ {
+ lock_guard<std::mutex> 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<std::mutex> lock(mutex);
+ started = true;
+ }
+ // Wait to work.
+ for (;;) {
+ bool ready = false;
+ {
+ lock_guard<std::mutex> 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<std::mutex> lock(mutex);
+ done = true;
+ }
+ // Wait to terminate.
+ for (;;) {
+ lock_guard<std::mutex> lock(mutex);
+ if (terminate) {
+ return;
+ }
+ }
+ }
+ });
+
+ // Wait thread to start.
+ for (;;) {
+ bool ready = false;
+ {
+ lock_guard<std::mutex> 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<std::mutex> lock(mutex);
+ work = true;
+ }
+
+ cout << "pausing for one second\n";
+ usleep(1000000);
+ bool ready = false;
+ {
+ lock_guard<std::mutex> lock(mutex);
+ ready = done;
+ }
+ EXPECT_FALSE(ready);
+ }
+
+ // Wait thread to hold the write lock guard.
+ for (;;) {
+ bool ready = false;
+ {
+ lock_guard<std::mutex> lock(mutex);
+ ready = done;
+ }
+ if (ready) {
+ break;
+ }
+ usleep(100);
+ }
+
+ // Signal the thread to terminate.
+ {
+ lock_guard<std::mutex> 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<std::mutex> lock(mutex);
+ started = true;
+ }
+ // Wait to work.
+ for (;;) {
+ bool ready = false;
+ {
+ lock_guard<std::mutex> 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<std::mutex> lock(mutex);
+ done = true;
+ }
+ // Wait to terminate.
+ for (;;) {
+ lock_guard<std::mutex> lock(mutex);
+ if (terminate) {
+ return;
+ }
+ }
+ }
+ });
+
+ // Wait thread to start.
+ for (;;) {
+ bool ready = false;
+ {
+ lock_guard<std::mutex> 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<std::mutex> lock(mutex);
+ work = true;
+ }
+
+ cout << "pausing for one second\n";
+ usleep(1000000);
+ bool ready = false;
+ {
+ lock_guard<std::mutex> lock(mutex);
+ ready = done;
+ }
+ EXPECT_FALSE(ready);
+ }
+
+ // Wait thread to hold the write lock guard.
+ for (;;) {
+ bool ready = false;
+ {
+ lock_guard<std::mutex> lock(mutex);
+ ready = done;
+ }
+ if (ready) {
+ break;
+ }
+ usleep(100);
+ }
+
+ // Signal the thread to terminate.
+ {
+ lock_guard<std::mutex> 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<std::mutex> lock(mutex);
+ started1 = true;
+ }
+ // Wait to work.
+ for (;;) {
+ bool ready = false;
+ {
+ lock_guard<std::mutex> 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<std::mutex> lock(mutex);
+ done1 = true;
+ }
+ // Wait to terminate.
+ for (;;) {
+ lock_guard<std::mutex> 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<std::mutex> lock(mutex);
+ started2 = true;
+ }
+ // Wait to work.
+ for (;;) {
+ bool ready = false;
+ {
+ lock_guard<std::mutex> 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<std::mutex> lock(mutex);
+ done2 = true;
+ }
+ // Wait to terminate.
+ for (;;) {
+ lock_guard<std::mutex> lock(mutex);
+ if (terminate2) {
+ return;
+ }
+ }
+ }
+ });
+
+ // Wait threads to start.
+ for (;;) {
+ bool ready = false;
+ {
+ lock_guard<std::mutex> 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<std::mutex> lock(mutex);
+ work1 = true;
+ }
+
+ cout << "pausing for one second\n";
+ usleep(1000000);
+ bool ready = false;
+ {
+ lock_guard<std::mutex> lock(mutex);
+ ready = done1;
+ }
+ EXPECT_FALSE(ready);
+
+ // Signal the reader thread to work.
+ {
+ lock_guard<std::mutex> lock(mutex);
+ work2 = true;
+ }
+
+ cout << "pausing for one second\n";
+ usleep(1000000);
+ ready = false;
+ {
+ lock_guard<std::mutex> lock(mutex);
+ ready = done2;
+ }
+ EXPECT_FALSE(ready);
+ }
+
+ cout << "pausing for one second\n";
+ usleep(1000000);
+ {
+ bool ready = false;
+ {
+ lock_guard<std::mutex> lock(mutex);
+ ready = done2;
+ }
+ EXPECT_FALSE(ready);
+ }
+ // Signal the writer thread to terminate.
+ {
+ lock_guard<std::mutex> 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<std::mutex> lock(mutex);
+ ready = done2;
+ }
+ if (ready) {
+ break;
+ }
+ usleep(100);
+ }
+
+ // Signal the reader thread to terminate.
+ {
+ lock_guard<std::mutex> lock(mutex);
+ terminate2 = true;
+ }
+
+ // Join the thread.
+ thread2.join();
+}
+
+}