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:
@@ -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_);
|
||||
|
@@ -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
|
||||
|
@@ -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 \
|
||||
|
187
src/lib/util/readwrite_mutex.h
Normal file
187
src/lib/util/readwrite_mutex.h
Normal 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
|
@@ -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
|
||||
|
689
src/lib/util/tests/readwrite_mutex_unittest.cc
Normal file
689
src/lib/util/tests/readwrite_mutex_unittest.cc
Normal 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();
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user