]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#1147] Checkpoint: finished tools
authorFrancis Dupont <fdupont@isc.org>
Wed, 13 May 2020 14:17:10 +0000 (16:17 +0200)
committerFrancis Dupont <fdupont@isc.org>
Tue, 26 May 2020 09:51:57 +0000 (11:51 +0200)
src/lib/dhcpsrv/resource_handler.cc
src/lib/dhcpsrv/tests/resource_handler_unittest.cc
src/lib/util/Makefile.am
src/lib/util/readwrite_mutex.h [new file with mode: 0644]
src/lib/util/tests/Makefile.am
src/lib/util/tests/readwrite_mutex_unittest.cc [new file with mode: 0644]

index cbd1c099b8a0ba969a7c136deebd092fbe2b919d..d49aa0eb9bc22ea8070e06fa091d763af1f74b96 100644 (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_);
index 1fe6e4c1c0cee8569bccf6d12624d33dd40ddb8c..532429671aed77c479eb8f3b2bb1040c9de2125e 100644 (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
index 0d75edf8473becd30c6be3565ec6815d8b988d49..fe269446cb3fae9c2a63cd100f2c19c18bb1ad27 100644 (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 \
diff --git a/src/lib/util/readwrite_mutex.h b/src/lib/util/readwrite_mutex.h
new file mode 100644 (file)
index 0000000..2a49cc7
--- /dev/null
@@ -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
index 02750dd92148ba4fe0d0f607a2b1053eafb759d6..6c5e9638af4f3887f0024e9bc505e97a901618f9 100644 (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
diff --git a/src/lib/util/tests/readwrite_mutex_unittest.cc b/src/lib/util/tests/readwrite_mutex_unittest.cc
new file mode 100644 (file)
index 0000000..c868dcd
--- /dev/null
@@ -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();
+}
+
+}