diff --git a/src/third-party/discord-rpc/include/mingw-std-threads/shared_mutex b/src/third-party/discord-rpc/include/mingw-std-threads/shared_mutex new file mode 100644 index 000000000..b49df5b6f --- /dev/null +++ b/src/third-party/discord-rpc/include/mingw-std-threads/shared_mutex @@ -0,0 +1,497 @@ +/// \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 +/// 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 +// For descriptive errors. +#include +// Implementing a shared_mutex without OS support will require atomic read- +// modify-write capacity. +#include +// For timing in shared_lock and shared_timed_mutex. +#include +#include + +// 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_next +#endif + +// For defer_lock_t, adopt_lock_t, and try_to_lock_t +#include +// For this_thread::yield. +#include + +// Might be able to use native Slim Reader-Writer (SRW) locks. +#ifdef _WIN32 +#include +#endif + +namespace mingw_stdthread +{ +// Define a portable atomics-based shared_mutex +namespace portable +{ +class shared_mutex +{ + typedef uint_fast16_t counter_type; + std::atomic mCounter {0}; + static constexpr counter_type kWriteBit = 1 << (std::numeric_limits::digits - 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 () = default; + +// 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, + static_cast(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) & static_cast(~kWriteBit); + if (expected + 1 == kWriteBit) + return false; + else + return mCounter.compare_exchange_strong( expected, + static_cast(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) & static_cast(~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::native_handle_type; + using windows7::mutex::lock; + using windows7::mutex::unlock; + using windows7::mutex::native_handle; + + 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::try_lock; + using Base::unlock; + using Base::lock_shared; + using Base::try_lock_shared; + using Base::unlock_shared; + + template< class Clock, class Duration > + bool try_lock_until ( const std::chrono::time_point& 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& 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& 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& 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 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 && 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& 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& timeout_time ) + : mMutex(&m), mOwns(m.try_lock_shared_until(timeout_time)) + { + } + + shared_lock& operator= (shared_lock && 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 &) = delete; + shared_lock& operator= (const shared_lock &) = 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& 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& 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 & 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& lhs, shared_lock& 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_timed_mutex; +using mingw_stdthread::shared_lock; +#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_