Program Listing for File shared_mutex.hpp
↰ Return to documentation for file (/tmp/ws/src/fastrtps/include/fastrtps/utils/shared_mutex.hpp
)
/*
Copyright Howard Hinnant 2007-2010. Distributed under the Boost
Software License, Version 1.0. (see http://www.boost.org/LICENSE_1_0.txt)
The original implementation has been modified to support the POSIX priorities:
PTHREAD_RWLOCK_PREFER_READER_NP
This is the default. A thread may hold multiple read
locks; that is, read locks are recursive. According to
The Single Unix Specification, the behavior is unspecified
when a reader tries to place a lock, and there is no write
lock but writers are waiting. Giving preference to the
reader, as is set by PTHREAD_RWLOCK_PREFER_READER_NP,
implies that the reader will receive the requested lock,
even if a writer is waiting. As long as there are
readers, the writer will be starved.
PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP
Setting the lock kind to this avoids writer starvation as
long as any read locking is not done in a recursive
fashion.
The C++ Standard has not yet (C++20) imposed any requirements on shared_mutex implementation thus
each platform made its own choices:
Windows & Boost defaults to PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP.
Linux & Mac defaults to PTHREAD_RWLOCK_PREFER_READER_NP.
*/
#ifndef _UTILS_SHARED_MUTEX_HPP_
#define _UTILS_SHARED_MUTEX_HPP_
#include <climits>
#include <condition_variable>
#include <map>
#include <mutex>
#include <system_error>
#include <thread>
namespace eprosima {
namespace detail {
// mimic POSIX Read-Write lock syntax
enum class shared_mutex_type
{
PTHREAD_RWLOCK_PREFER_READER_NP, PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP
};
class shared_mutex_base
{
protected:
typedef std::mutex mutex_t;
typedef std::condition_variable cond_t;
typedef unsigned count_t;
mutex_t mut_;
cond_t gate1_;
count_t state_;
static const count_t write_entered_ = 1U << (sizeof(count_t) * CHAR_BIT - 1);
static const count_t n_readers_ = ~write_entered_;
public:
shared_mutex_base()
: state_(0)
{
}
~shared_mutex_base()
{
std::lock_guard<mutex_t> _(mut_);
}
shared_mutex_base(
const shared_mutex_base&) = delete;
shared_mutex_base& operator =(
const shared_mutex_base&) = delete;
// Exclusive ownership
bool try_lock()
{
std::lock_guard<mutex_t> _(mut_);
if (state_ == 0)
{
state_ = write_entered_;
return true;
}
return false;
}
void unlock()
{
std::lock_guard<mutex_t> _(mut_);
state_ = 0;
gate1_.notify_all();
}
// Shared ownership
void lock_shared()
{
std::unique_lock<mutex_t> lk(mut_);
while ((state_ & write_entered_) || (state_ & n_readers_) == n_readers_)
{
gate1_.wait(lk);
}
count_t num_readers = (state_ & n_readers_) + 1;
state_ &= ~n_readers_;
state_ |= num_readers;
}
bool try_lock_shared()
{
std::lock_guard<mutex_t> _(mut_);
count_t num_readers = state_ & n_readers_;
if (!(state_ & write_entered_) && num_readers != n_readers_)
{
++num_readers;
state_ &= ~n_readers_;
state_ |= num_readers;
return true;
}
return false;
}
};
template<shared_mutex_type>
class shared_mutex;
// original Hinnant implementation prioritizing writers
template<>
class shared_mutex<shared_mutex_type::PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP>
: public shared_mutex_base
{
cond_t gate2_;
public:
void lock()
{
std::unique_lock<mutex_t> lk(mut_);
while (state_ & write_entered_)
{
gate1_.wait(lk);
}
state_ |= write_entered_;
while (state_ & n_readers_)
{
gate2_.wait(lk);
}
}
void unlock_shared()
{
std::lock_guard<mutex_t> _(mut_);
count_t num_readers = (state_ & n_readers_) - 1;
state_ &= ~n_readers_;
state_ |= num_readers;
if (state_ & write_entered_)
{
if (num_readers == 0)
{
gate2_.notify_one();
}
}
else if (num_readers == n_readers_ - 1)
{
gate1_.notify_one();
}
}
};
// implementation not locking readers on behalf of writers
template<>
class shared_mutex<shared_mutex_type::PTHREAD_RWLOCK_PREFER_READER_NP>
: public shared_mutex_base
{
count_t writer_waiting_ = 0;
public:
void lock()
{
std::unique_lock<mutex_t> lk(mut_);
++writer_waiting_;
while (state_ & n_readers_ || state_ & write_entered_)
{
gate1_.wait(lk);
}
state_ |= write_entered_;
--writer_waiting_;
}
void unlock_shared()
{
std::lock_guard<mutex_t> _(mut_);
count_t num_readers = (state_ & n_readers_) - 1;
state_ &= ~n_readers_;
state_ |= num_readers;
if ((writer_waiting_ && num_readers == 0)
|| (num_readers == n_readers_ - 1))
{
gate1_.notify_one();
}
}
};
// Debugger wrapper class that provides insight
template<class sm>
class debug_wrapper : public sm
{
std::mutex wm_;
// Identity of the exclusive owner if any
std::thread::id exclusive_owner_ = {};
// key_type thread_id, mapped_type number of locks
std::map<std::thread::id, unsigned int> shared_owners_;
public:
~debug_wrapper()
{
std::lock_guard<std::mutex> _(wm_);
}
// Exclusive ownership
void lock()
{
sm::lock();
std::lock_guard<std::mutex> _(wm_);
exclusive_owner_ = std::this_thread::get_id();
}
bool try_lock()
{
bool res = sm::try_lock();
std::lock_guard<std::mutex> _(wm_);
if (res)
{
exclusive_owner_ = std::this_thread::get_id();
}
return res;
}
void unlock()
{
sm::unlock();
std::lock_guard<std::mutex> _(wm_);
exclusive_owner_ = std::thread::id();
}
// Shared ownership
void lock_shared()
{
sm::lock_shared();
std::lock_guard<std::mutex> _(wm_);
++shared_owners_[std::this_thread::get_id()];
}
bool try_lock_shared()
{
bool res = sm::try_lock_shared();
std::lock_guard<std::mutex> _(wm_);
if (res)
{
++shared_owners_[std::this_thread::get_id()];
}
return res;
}
void unlock_shared()
{
sm::unlock_shared();
std::lock_guard<std::mutex> _(wm_);
auto owner = shared_owners_.find(std::this_thread::get_id());
if ( owner != shared_owners_.end() && 0 == --owner->second )
{
shared_owners_.erase(owner);
}
}
};
} // namespace detail
} // namespace eprosima
#if defined(__has_include) && __has_include(<version>)
# include <version>
#endif // if defined(__has_include) && __has_include(<version>)
// Detect if the shared_lock feature is available
#if defined(__has_include) && __has_include(<version>) && !defined(__cpp_lib_shared_mutex) || \
/* deprecated procedure if the good one is not available*/ \
( !(defined(__has_include) && __has_include(<version>)) && \
!(defined(HAVE_CXX17) && HAVE_CXX17) && __cplusplus < 201703 )
namespace eprosima {
template <class Mutex>
class shared_lock
{
public:
typedef Mutex mutex_type;
private:
mutex_type* m_;
bool owns_;
struct __nat
{
int _;
};
public:
shared_lock()
: m_(nullptr)
, owns_(false)
{
}
explicit shared_lock(
mutex_type& m)
: m_(&m)
, owns_(true)
{
m_->lock_shared();
}
shared_lock(
mutex_type& m,
std::defer_lock_t)
: m_(&m)
, owns_(false)
{
}
shared_lock(
mutex_type& m,
std::try_to_lock_t)
: m_(&m)
, owns_(m.try_lock_shared())
{
}
shared_lock(
mutex_type& m,
std::adopt_lock_t)
: m_(&m)
, owns_(true)
{
}
template <class Clock, class Duration>
shared_lock(
mutex_type& m,
const std::chrono::time_point<Clock, Duration>& abs_time)
: m_(&m)
, owns_(m.try_lock_shared_until(abs_time))
{
}
template <class Rep, class Period>
shared_lock(
mutex_type& m,
const std::chrono::duration<Rep, Period>& rel_time)
: m_(&m)
, owns_(m.try_lock_shared_for(rel_time))
{
}
~shared_lock()
{
if (owns_)
{
m_->unlock_shared();
}
}
shared_lock(
shared_lock const&) = delete;
shared_lock& operator =(
shared_lock const&) = delete;
shared_lock(
shared_lock&& sl)
: m_(sl.m_)
, owns_(sl.owns_)
{
sl.m_ = nullptr; sl.owns_ = false;
}
shared_lock& operator =(
shared_lock&& sl)
{
if (owns_)
{
m_->unlock_shared();
}
m_ = sl.m_;
owns_ = sl.owns_;
sl.m_ = nullptr;
sl.owns_ = false;
return *this;
}
explicit shared_lock(
std::unique_lock<mutex_type>&& ul)
: m_(ul.mutex())
, owns_(ul.owns_lock())
{
if (owns_)
{
m_->unlock_and_lock_shared();
}
ul.release();
}
void lock();
bool try_lock();
template <class Rep, class Period>
bool try_lock_for(
const std::chrono::duration<Rep, Period>& rel_time)
{
return try_lock_until(std::chrono::steady_clock::now() + rel_time);
}
template <class Clock, class Duration>
bool
try_lock_until(
const std::chrono::time_point<Clock, Duration>& abs_time);
void unlock();
void swap(
shared_lock&& u)
{
std::swap(m_, u.m_);
std::swap(owns_, u.owns_);
}
mutex_type* release()
{
mutex_type* r = m_;
m_ = nullptr;
owns_ = false;
return r;
}
bool owns_lock() const
{
return owns_;
}
operator int __nat::* () const {
return owns_ ? &__nat::_ : 0;
}
mutex_type* mutex() const
{
return m_;
}
};
template <class Mutex>
void
shared_lock<Mutex>::lock()
{
if (m_ == nullptr)
{
throw std::system_error(std::error_code(EPERM, std::system_category()),
"shared_lock::lock: references null mutex");
}
if (owns_)
{
throw std::system_error(std::error_code(EDEADLK, std::system_category()),
"shared_lock::lock: already locked");
}
m_->lock_shared();
owns_ = true;
}
template <class Mutex>
bool
shared_lock<Mutex>::try_lock()
{
if (m_ == nullptr)
{
throw std::system_error(std::error_code(EPERM, std::system_category()),
"shared_lock::try_lock: references null mutex");
}
if (owns_)
{
throw std::system_error(std::error_code(EDEADLK, std::system_category()),
"shared_lock::try_lock: already locked");
}
owns_ = m_->try_lock_shared();
return owns_;
}
template <class Mutex>
template <class Clock, class Duration>
bool
shared_lock<Mutex>::try_lock_until(
const std::chrono::time_point<Clock, Duration>& abs_time)
{
if (m_ == nullptr)
{
throw std::system_error(std::error_code(EPERM, std::system_category()),
"shared_lock::try_lock_until: references null mutex");
}
if (owns_)
{
throw std::system_error(std::error_code(EDEADLK, std::system_category()),
"shared_lock::try_lock_until: already locked");
}
owns_ = m_->try_lock_shared_until(abs_time);
return owns_;
}
template <class Mutex>
void
shared_lock<Mutex>::unlock()
{
if (!owns_)
{
throw std::system_error(std::error_code(EPERM, std::system_category()),
"shared_lock::unlock: not locked");
}
m_->unlock_shared();
owns_ = false;
}
template <class Mutex>
inline
void
swap(
shared_lock<Mutex>& x,
shared_lock<Mutex>& y)
{
x.swap(y);
}
} //namespace eprosima
#else // fallback to STL
#include <shared_mutex>
namespace eprosima {
using std::shared_lock;
using std::swap;
} //namespace eprosima
#endif // shared_lock selection
#ifndef USE_THIRDPARTY_SHARED_MUTEX
# if defined(_MSC_VER) && _MSVC_LANG < 202302L
# pragma message("warning: USE_THIRDPARTY_SHARED_MUTEX not defined. By default use framework version.")
# else
# warning "USE_THIRDPARTY_SHARED_MUTEX not defined. By default use framework version."
# endif // if defined(_MSC_VER) && _MSVC_LANG < 202302L
# define USE_THIRDPARTY_SHARED_MUTEX 0
#endif // ifndef USE_THIRDPARTY_SHARED_MUTEX
// Detect if the share_mutex feature is available or if the user forces it
#if defined(__has_include) && __has_include(<version>) && !defined(__cpp_lib_shared_mutex) || \
/* allow users to ignore shared_mutex framework implementation */ \
(~USE_THIRDPARTY_SHARED_MUTEX + 1) || \
/* deprecated procedure if the good one is not available*/ \
( !(defined(__has_include) && __has_include(<version>)) && \
!(defined(HAVE_CXX17) && HAVE_CXX17) && __cplusplus < 201703 )
/*
Fast-DDS defaults to PTHREAD_RWLOCK_PREFER_READER_NP for two main reasons:
- It allows reader side recursiveness. If we have two threads (T1, T2) and
called S a shared lock and E and exclusive one.
T1: S -> S
T2: E
PTHREAD_RWLOCK_PREFER_READER_NP will never deadlock. The S locks are not
influenced by the E locks.
PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP will deadlock. before T1
takes S twice. That happens because:
+ T1's second S will wait for E (writer is prioritized)
+ E will wait for T1's first S lock (writer needs atomic access)
+ T1's first S cannot unlock because is blocked in the second S.
Thus, shared_mutex<PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP> is
non-recursive.
- It prevents ABBA deadlocks with other mutexes. If we have three threads
(Ti) and P is an ordinary mutex:
T1: P -> S
T2: S -> P
T3: E
PTHREAD_RWLOCK_PREFER_READER_NP will never deadlock. The S locks are not
influenced by the E locks. Starvation issues can be managed in the user
code.
PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP will deadlock if T3 takes E
before T1 takes S. That happens because:
+ T1's S will wait for E (writer is prioritized)
+ E will wait for T2's S lock (writer needs atomic access)
+ T2's S cannot unlock because is blocked in P (owned by T1).
Thus, shared_mutex<PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP> must be
managed like an ordinary mutex in deadlock sense.
*/
namespace eprosima {
#ifdef NDEBUG
using shared_mutex = detail::shared_mutex<detail::shared_mutex_type::PTHREAD_RWLOCK_PREFER_READER_NP>;
#else
using shared_mutex =
detail::debug_wrapper<detail::shared_mutex<detail::shared_mutex_type::PTHREAD_RWLOCK_PREFER_READER_NP>>;
#endif // NDEBUG
} //namespace eprosima
#else // fallback to STL
#include <shared_mutex>
namespace eprosima {
using std::shared_mutex;
} //namespace eprosima
#endif // shared_mutex selection
#endif // _UTILS_SHARED_MUTEX_HPP_