// Copyright (C) 2020-2022 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 #include #include #include #include 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 lk(mutex_); } /// @brief Lock write. void writeLock() { std::unique_lock 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 lk(mutex_); state_ = 0; // Wake-up waiting threads when exiting the guard. gate1_.notify_all(); } /// @brief Lock read. void readLock() { std::unique_lock 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 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