diff options
Diffstat (limited to 'src/lib/util/readwrite_mutex.h')
-rw-r--r-- | src/lib/util/readwrite_mutex.h | 187 |
1 files changed, 187 insertions, 0 deletions
diff --git a/src/lib/util/readwrite_mutex.h b/src/lib/util/readwrite_mutex.h new file mode 100644 index 0000000..f8766d5 --- /dev/null +++ b/src/lib/util/readwrite_mutex.h @@ -0,0 +1,187 @@ +// Copyright (C) 2020 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef READWRITE_MUTEX_H +#define READWRITE_MUTEX_H + +/// @file readwrite_mutex.h +/// +/// Standard implementation of read-write mutexes with writer preference +/// using C++11 mutex and condition variable. +/// As we need only the RAII wrappers implement only used methods. + +#include <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^31 - 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 writeLock() { + 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 writeUnlock() { + std::lock_guard<std::mutex> lk(mutex_); + state_ = 0; + // Wake-up waiting threads when exiting the guard. + gate1_.notify_all(); + } + + /// @brief Lock read. + void readLock() { + 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 readUnlock() { + 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_.readLock(); + } + + /// @brief Destructor. + virtual ~ReadLockGuard() { + rw_mutex_.readUnlock(); + } + +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_.writeLock(); + } + + /// @brief Destructor. + virtual ~WriteLockGuard() { + rw_mutex_.writeUnlock(); + } + +private: + /// @brief The read-write mutex. + ReadWriteMutex& rw_mutex_; +}; + +} // namespace util +} // namespace isc + +#endif // READWRITE_MUTEX_H |