2
0
mirror of https://gitlab.isc.org/isc-projects/kea synced 2025-08-30 13:37:55 +00:00

[#1147] Checkpoint: finished tools

This commit is contained in:
Francis Dupont
2020-05-13 16:17:10 +02:00
parent 8a69ac46be
commit d2ec1fcfc8
6 changed files with 1354 additions and 3 deletions

View File

@@ -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<mutex> lock_(mutex_);

View File

@@ -9,6 +9,7 @@
#include <gtest/gtest.h>
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

View File

@@ -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 \

View File

@@ -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 <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

View File

@@ -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

View File

@@ -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 <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();
}
}