mirror of
https://github.com/oxen-io/lokinet.git
synced 2024-11-07 15:20:31 +00:00
537 lines
15 KiB
C
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_
|