You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
lokinet/include/llarp/win32/threads/mingw.shared_mutex.h

537 lines
15 KiB
C++

/// \file mingw.shared_mutex.h
/// \brief Standard-compliant shared_mutex for MinGW
///
/// (c) 2017 by Nathaniel J. McClatchey, Athens OH, United States
/// \author Nathaniel J. McClatchey
///
/// \copyright Simplified (2-clause) BSD License.
///
/// \note This file may become part of the mingw-w64 runtime package. If/when
/// this happens, the appropriate license will be added, i.e. this code will
/// become dual-licensed, and the current BSD 2-clause license will stay.
/// \note Target Windows version is determined by WINVER, which is determined in
/// <windows.h> from _WIN32_WINNT, which can itself be set by the user.
// Notes on the namespaces:
// - The implementation can be accessed directly in the namespace
// mingw_stdthread.
// - Objects will be brought into namespace std by a using directive. This
// will cause objects declared in std (such as MinGW's implementation) to
// hide this implementation's definitions.
// - To avoid poluting the namespace with implementation details, all objects
// to be pushed into std will be placed in mingw_stdthread::visible.
// The end result is that if MinGW supplies an object, it is automatically
// used. If MinGW does not supply an object, this implementation's version will
// instead be used.
#ifndef MINGW_SHARED_MUTEX_H_
#define MINGW_SHARED_MUTEX_H_
#if !defined(__cplusplus) || (__cplusplus < 201103L)
#error A C++11 compiler is required!
#endif
#include <cassert>
// Use MinGW's shared_lock class template, if it's available. Requires C++14.
// If unavailable (eg. because this library is being used in C++11), then an
// implementation of shared_lock is provided by this header.
#if(__cplusplus >= 201402L)
#include <shared_mutex>
#endif
// For defer_lock_t, adopt_lock_t, and try_to_lock_t
#include "mingw.mutex.h"
// For descriptive errors.
#include <system_error>
// Implementing a shared_mutex without OS support will require atomic read-
// modify-write capacity.
#include <atomic>
// For timing in shared_lock and shared_timed_mutex.
#include <chrono>
// For this_thread::yield.
#include "mingw.thread.h"
// Might be able to use native Slim Reader-Writer (SRW) locks.
#ifdef _WIN32
#include <windows.h>
#endif
namespace mingw_stdthread
{
// Define a portable atomics-based shared_mutex
namespace portable
{
class shared_mutex
{
typedef uint_fast16_t counter_type;
std::atomic< counter_type > mCounter;
static constexpr counter_type kWriteBit = 1
<< (sizeof(counter_type) * CHAR_BIT - 1);
#if STDMUTEX_RECURSION_CHECKS
// Runtime checker for verifying owner threads. Note: Exclusive mode
// only.
_OwnerThread mOwnerThread;
#endif
public:
typedef shared_mutex* native_handle_type;
shared_mutex()
: mCounter(0)
#if STDMUTEX_RECURSION_CHECKS
, mOwnerThread()
#endif
{
}
// No form of copying or moving should be allowed.
shared_mutex(const shared_mutex&) = delete;
shared_mutex&
operator=(const shared_mutex&) = delete;
~shared_mutex()
{
// Terminate if someone tries to destroy an owned mutex.
assert(mCounter.load(std::memory_order_relaxed) == 0);
}
void
lock_shared(void)
{
counter_type expected = mCounter.load(std::memory_order_relaxed);
do
{
// Delay if writing or if too many readers are attempting to read.
if(expected >= kWriteBit - 1)
{
using namespace std;
using namespace this_thread;
yield();
expected = mCounter.load(std::memory_order_relaxed);
continue;
}
if(mCounter.compare_exchange_weak(expected, expected + 1,
std::memory_order_acquire,
std::memory_order_relaxed))
break;
} while(true);
}
bool
try_lock_shared(void)
{
counter_type expected =
mCounter.load(std::memory_order_relaxed) & (~kWriteBit);
if(expected + 1 == kWriteBit)
return false;
else
return mCounter.compare_exchange_strong(expected, expected + 1,
std::memory_order_acquire,
std::memory_order_relaxed);
}
void
unlock_shared(void)
{
using namespace std;
#ifndef NDEBUG
if(!(mCounter.fetch_sub(1, memory_order_release) & (~kWriteBit)))
throw system_error(make_error_code(errc::operation_not_permitted));
#else
mCounter.fetch_sub(1, memory_order_release);
#endif
}
// Behavior is undefined if a lock was previously acquired.
void
lock(void)
{
#if STDMUTEX_RECURSION_CHECKS
DWORD self = mOwnerThread.checkOwnerBeforeLock();
#endif
using namespace std;
// Might be able to use relaxed memory order...
// Wait for the write-lock to be unlocked, then claim the write slot.
counter_type current;
while(
(current = mCounter.fetch_or(kWriteBit, std::memory_order_acquire))
& kWriteBit)
this_thread::yield();
// Wait for readers to finish up.
while(current != kWriteBit)
{
this_thread::yield();
current = mCounter.load(std::memory_order_acquire);
}
#if STDMUTEX_RECURSION_CHECKS
mOwnerThread.setOwnerAfterLock(self);
#endif
}
bool
try_lock(void)
{
#if STDMUTEX_RECURSION_CHECKS
DWORD self = mOwnerThread.checkOwnerBeforeLock();
#endif
counter_type expected = 0;
bool ret = mCounter.compare_exchange_strong(expected, kWriteBit,
std::memory_order_acquire,
std::memory_order_relaxed);
#if STDMUTEX_RECURSION_CHECKS
if(ret)
mOwnerThread.setOwnerAfterLock(self);
#endif
return ret;
}
void
unlock(void)
{
#if STDMUTEX_RECURSION_CHECKS
mOwnerThread.checkSetOwnerBeforeUnlock();
#endif
using namespace std;
#ifndef NDEBUG
if(mCounter.load(memory_order_relaxed) != kWriteBit)
throw system_error(make_error_code(errc::operation_not_permitted));
#endif
mCounter.store(0, memory_order_release);
}
native_handle_type
native_handle(void)
{
return this;
}
};
} // Namespace portable
// The native shared_mutex implementation primarily uses features of Windows
// Vista, but the features used for try_lock and try_lock_shared were not
// introduced until Windows 7. To allow limited use while compiling for Vista,
// I define the class without try_* functions in that case.
// Only fully-featured implementations will be placed into namespace std.
#if defined(_WIN32) && (WINVER >= _WIN32_WINNT_VISTA)
namespace vista
{
class condition_variable_any;
}
namespace windows7
{
// We already #include "mingw.mutex.h". May as well reduce redundancy.
class shared_mutex : windows7::mutex
{
// Allow condition_variable_any (and only condition_variable_any) to
// treat a
// shared_mutex as its base class.
friend class vista::condition_variable_any;
public:
using windows7::mutex::lock;
using windows7::mutex::native_handle;
using windows7::mutex::native_handle_type;
using windows7::mutex::unlock;
void
lock_shared(void)
{
AcquireSRWLockShared(native_handle());
}
void
unlock_shared(void)
{
ReleaseSRWLockShared(native_handle());
}
// TryAcquireSRW functions are a Windows 7 feature.
#if(WINVER >= _WIN32_WINNT_WIN7)
bool
try_lock_shared(void)
{
return TryAcquireSRWLockShared(native_handle()) != 0;
}
using windows7::mutex::try_lock;
#endif
};
} // Namespace windows7
#endif // Compiling for Vista
#if(defined(_WIN32) && (WINVER >= _WIN32_WINNT_WIN7))
using windows7::shared_mutex;
#else
using portable::shared_mutex;
#endif
class shared_timed_mutex : shared_mutex
{
typedef shared_mutex Base;
public:
using Base::lock;
using Base::lock_shared;
using Base::try_lock;
using Base::try_lock_shared;
using Base::unlock;
using Base::unlock_shared;
template < class Clock, class Duration >
bool
try_lock_until(const std::chrono::time_point< Clock, Duration >& cutoff)
{
do
{
if(try_lock())
return true;
} while(std::chrono::steady_clock::now() < cutoff);
return false;
}
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_shared_until(
const std::chrono::time_point< Clock, Duration >& cutoff)
{
do
{
if(try_lock_shared())
return true;
} while(std::chrono::steady_clock::now() < cutoff);
return false;
}
template < class Rep, class Period >
bool
try_lock_shared_for(const std::chrono::duration< Rep, Period >& rel_time)
{
return try_lock_shared_until(std::chrono::steady_clock::now() + rel_time);
}
};
#if __cplusplus >= 201402L
using std::shared_lock;
#else
// If not supplied by shared_mutex (eg. because C++14 is not supported), I
// supply the various helper classes that the header should have defined.
template < class Mutex >
class shared_lock
{
Mutex* mMutex;
bool mOwns;
// Reduce code redundancy
void
verify_lockable(void)
{
using namespace std;
if(mMutex == nullptr)
throw system_error(make_error_code(errc::operation_not_permitted));
if(mOwns)
throw system_error(
make_error_code(errc::resource_deadlock_would_occur));
}
public:
typedef Mutex mutex_type;
shared_lock(void) noexcept : mMutex(nullptr), mOwns(false)
{
}
shared_lock(shared_lock< Mutex >&& other) noexcept
: mMutex(other.mutex_), mOwns(other.owns_)
{
other.mMutex = nullptr;
other.mOwns = false;
}
explicit shared_lock(mutex_type& m) : mMutex(&m), mOwns(true)
{
mMutex->lock_shared();
}
shared_lock(mutex_type& m, defer_lock_t) noexcept : mMutex(&m), mOwns(false)
{
}
shared_lock(mutex_type& m, adopt_lock_t) : mMutex(&m), mOwns(true)
{
}
shared_lock(mutex_type& m, try_to_lock_t)
: mMutex(&m), mOwns(m.try_lock_shared())
{
}
template < class Rep, class Period >
shared_lock(mutex_type& m,
const std::chrono::duration< Rep, Period >& timeout_duration)
: mMutex(&m), mOwns(m.try_lock_shared_for(timeout_duration))
{
}
template < class Clock, class Duration >
shared_lock(mutex_type& m,
const std::chrono::time_point< Clock, Duration >& timeout_time)
: mMutex(&m), mOwns(m.try_lock_shared_until(timeout_time))
{
}
shared_lock&
operator=(shared_lock< Mutex >&& other) noexcept
{
if(&other != this)
{
if(mOwns)
mMutex->unlock_shared();
mMutex = other.mMutex;
mOwns = other.mOwns;
other.mMutex = nullptr;
other.mOwns = false;
}
return *this;
}
~shared_lock(void)
{
if(mOwns)
mMutex->unlock_shared();
}
shared_lock(const shared_lock< Mutex >&) = delete;
shared_lock&
operator=(const shared_lock< Mutex >&) = delete;
// Shared locking
void
lock(void)
{
verify_lockable();
mMutex->lock_shared();
mOwns = true;
}
bool
try_lock(void)
{
verify_lockable();
mOwns = mMutex->try_lock_shared();
return mOwns;
}
template < class Clock, class Duration >
bool
try_lock_until(const std::chrono::time_point< Clock, Duration >& cutoff)
{
verify_lockable();
do
{
mOwns = mMutex->try_lock_shared();
if(mOwns)
return mOwns;
} while(std::chrono::steady_clock::now() < cutoff);
return false;
}
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);
}
void
unlock(void)
{
using namespace std;
if(!mOwns)
throw system_error(make_error_code(errc::operation_not_permitted));
mMutex->unlock_shared();
mOwns = false;
}
// Modifiers
void
swap(shared_lock< Mutex >& other) noexcept
{
using namespace std;
swap(mMutex, other.mMutex);
swap(mOwns, other.mOwns);
}
mutex_type*
release(void) noexcept
{
mutex_type* ptr = mMutex;
mMutex = nullptr;
mOwns = false;
return ptr;
}
// Observers
mutex_type*
mutex(void) const noexcept
{
return mMutex;
}
bool
owns_lock(void) const noexcept
{
return mOwns;
}
explicit operator bool() const noexcept
{
return owns_lock();
}
};
template < class Mutex >
void
swap(shared_lock< Mutex >& lhs, shared_lock< Mutex >& rhs) noexcept
{
lhs.swap(rhs);
}
#endif // C++11
} // Namespace mingw_stdthread
namespace std
{
// Because of quirks of the compiler, the common "using namespace std;"
// directive would flatten the namespaces and introduce ambiguity where there
// was none. Direct specification (std::), however, would be unaffected.
// Take the safe option, and include only in the presence of MinGW's win32
// implementation.
#if(__cplusplus < 201703L) \
|| (defined(__MINGW32__) && !defined(_GLIBCXX_HAS_GTHREADS))
using mingw_stdthread::shared_mutex;
#endif
#if(__cplusplus < 201402L) \
|| (defined(__MINGW32__) && !defined(_GLIBCXX_HAS_GTHREADS))
using mingw_stdthread::shared_lock;
using mingw_stdthread::shared_timed_mutex;
#elif !defined(MINGW_STDTHREAD_REDUNDANCY_WARNING) // Skip repetition
#define MINGW_STDTHREAD_REDUNDANCY_WARNING
#pragma message \
"This version of MinGW seems to include a win32 port of\
pthreads, and probably already has C++ std threading classes implemented,\
based on pthreads. These classes, found in namespace std, are not overridden\
by the mingw-std-thread library. If you would still like to use this\
implementation (as it is more lightweight), use the classes provided in\
namespace mingw_stdthread."
#endif
} // Namespace std
#endif // MINGW_SHARED_MUTEX_H_