////////////////////////////////////////////////////////////////////////////////////////// // A multi-platform support c++11 library with focus on asynchronous socket I/O for any // client application. ////////////////////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2021 halx99 (halx99 at live dot com) // Copyright Vicente J. Botet Escriba 2012. // Copyright Howard Hinnant 2007-2010. // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // // see also: https://github.com/boostorg/thread/blob/develop/include/boost/thread/v2/shared_mutex.hpp // #ifndef YASIO__SHARED_MUTEX_HPP #define YASIO__SHARED_MUTEX_HPP #include "yasio/compiler/feature_test.hpp" /// The shared_mutex workaround on c++11 #if YASIO__HAS_CXX17 && !defined(__APPLE__) # include #else # include # include # include # define yaso__throw_error(e) YASIO__THROW0(std::system_error(std::make_error_code(e), "")) namespace cxx17 { // CLASS TEMPLATE shared_mutex class shared_mutex { typedef std::mutex mutex_t; typedef std::condition_variable cond_t; typedef unsigned count_t; mutex_t mut_; cond_t gate1_; // the gate2_ condition variable is only used by functions that // have taken write_entered_ but are waiting for no_readers() cond_t gate2_; count_t state_; static const count_t write_entered_ = 1U << (sizeof(count_t) * CHAR_BIT - 1); static const count_t n_readers_ = ~write_entered_; bool no_writer() const { return (state_ & write_entered_) == 0; } bool one_writer() const { return (state_ & write_entered_) != 0; } bool no_writer_no_readers() const { // return (state_ & write_entered_) == 0 && // (state_ & n_readers_) == 0; return state_ == 0; } bool no_writer_no_max_readers() const { return (state_ & write_entered_) == 0 && (state_ & n_readers_) != n_readers_; } bool no_readers() const { return (state_ & n_readers_) == 0; } bool one_or_more_readers() const { return (state_ & n_readers_) > 0; } shared_mutex(shared_mutex const&) = delete; shared_mutex& operator=(shared_mutex const&) = delete; public: shared_mutex() : state_(0) {} ~shared_mutex() { std::lock_guard _(mut_); } // Exclusive ownership void lock() { std::unique_lock lk(mut_); gate1_.wait(lk, std::bind(&shared_mutex::no_writer, std::ref(*this))); state_ |= write_entered_; gate2_.wait(lk, std::bind(&shared_mutex::no_readers, std::ref(*this))); } bool try_lock() { std::unique_lock lk(mut_); if (!no_writer_no_readers()) { return false; } state_ = write_entered_; return true; } void unlock() { std::lock_guard _(mut_); assert(one_writer()); assert(no_readers()); state_ = 0; // notify all since multiple *lock_shared*() calls may be able // to proceed in response to this notification gate1_.notify_all(); } // Shared ownership void lock_shared() { std::unique_lock lk(mut_); gate1_.wait(lk, std::bind(&shared_mutex::no_writer_no_max_readers, std::ref(*this))); count_t num_readers = (state_ & n_readers_) + 1; state_ &= ~n_readers_; state_ |= num_readers; } bool try_lock_shared() { std::unique_lock lk(mut_); if (!no_writer_no_max_readers()) { return false; } count_t num_readers = (state_ & n_readers_) + 1; state_ &= ~n_readers_; state_ |= num_readers; return true; } void unlock_shared() { std::lock_guard _(mut_); assert(one_or_more_readers()); count_t num_readers = (state_ & n_readers_) - 1; state_ &= ~n_readers_; state_ |= num_readers; if (no_writer()) { if (num_readers == n_readers_ - 1) gate1_.notify_one(); } else { if (num_readers == 0) gate2_.notify_one(); } } }; // CLASS TEMPLATE shared_lock template class shared_lock { public: using mutex_type = _Mutex; shared_lock() : _Pmtx(nullptr), _Owns(false) {} explicit shared_lock(mutex_type& _Mtx) : _Pmtx(YASIO__STD addressof(_Mtx)), _Owns(true) { // construct with mutex and lock shared _Mtx.lock_shared(); } explicit shared_lock(mutex_type& _Mtx, YASIO__STD defer_lock_t) : _Pmtx(YASIO__STD addressof(_Mtx)), _Owns(false) {} // // construct with unlocked mutex explicit shared_lock(mutex_type& _Mtx, YASIO__STD try_to_lock_t) : _Pmtx(YASIO__STD addressof(_Mtx)), _Owns(_Mtx.try_lock_shared()) {} // construct with mutex and try to lock shared explicit shared_lock(mutex_type& _Mtx, YASIO__STD adopt_lock_t) : _Pmtx(YASIO__STD addressof(_Mtx)), _Owns(true) {} // construct with mutex and adopt owership ~shared_lock() { if (_Owns) _Pmtx->unlock_shared(); } shared_lock(shared_lock&& _Other) : _Pmtx(_Other._Pmtx), _Owns(_Other._Owns) { _Other._Pmtx = nullptr; _Other._Owns = false; } shared_lock& operator=(shared_lock&& _Right) { if (_Owns) _Pmtx->unlock_shared(); _Pmtx = _Right._Pmtx; _Owns = _Right._Owns; _Right._Pmtx = nullptr; _Right._Owns = false; return *this; } shared_lock(const shared_lock&) = delete; shared_lock& operator=(const shared_lock&) = delete; void lock() { // lock the mutex _Validate(); _Pmtx->lock_shared(); _Owns = true; } bool try_lock() { // try to lock the mutex _Validate(); _Owns = _Pmtx->try_lock_shared(); return _Owns; } void unlock() { // try to unlock the mutex if (!_Pmtx || !_Owns) yaso__throw_error(std::errc::operation_not_permitted); _Pmtx->unlock_shared(); _Owns = false; } // MUTATE void swap(shared_lock& _Right) { YASIO__STD swap(_Pmtx, _Right._Pmtx); YASIO__STD swap(_Owns, _Right._Owns); } mutex_type* release() { _Mutex* _Res = _Pmtx; _Pmtx = nullptr; _Owns = false; return _Res; } // OBSERVE bool owns_lock() const { return _Owns; } explicit operator bool() const { return _Owns; } mutex_type* mutex() const { return _Pmtx; } private: _Mutex* _Pmtx; bool _Owns; void _Validate() const { // check if the mutex can be locked if (!_Pmtx) yaso__throw_error(std::errc::operation_not_permitted); if (_Owns) yaso__throw_error(std::errc::resource_deadlock_would_occur); } }; } // namespace cxx17 #endif #endif