1/// \file mingw.shared_mutex.h
2/// \brief Standard-compliant shared_mutex for MinGW
3///
4/// (c) 2017 by Nathaniel J. McClatchey, Athens OH, United States
5/// \author Nathaniel J. McClatchey
6///
7/// \copyright Simplified (2-clause) BSD License.
8///
9/// \note This file may become part of the mingw-w64 runtime package. If/when
10/// this happens, the appropriate license will be added, i.e. this code will
11/// become dual-licensed, and the current BSD 2-clause license will stay.
12/// \note Target Windows version is determined by WINVER, which is determined in
13/// <windows.h> from _WIN32_WINNT, which can itself be set by the user.
14
15//  Notes on the namespaces:
16//  - The implementation can be accessed directly in the namespace
17//    mingw_stdthread.
18//  - Objects will be brought into namespace std by a using directive. This
19//    will cause objects declared in std (such as MinGW's implementation) to
20//    hide this implementation's definitions.
21//  - To avoid poluting the namespace with implementation details, all objects
22//    to be pushed into std will be placed in mingw_stdthread::visible.
23//  The end result is that if MinGW supplies an object, it is automatically
24//  used. If MinGW does not supply an object, this implementation's version will
25//  instead be used.
26
27#ifndef MINGW_SHARED_MUTEX_H_
28#define MINGW_SHARED_MUTEX_H_
29
30#if !defined(__cplusplus) || (__cplusplus < 201103L)
31#error A C++11 compiler is required!
32#endif
33
34#include <cassert>
35//  For descriptive errors.
36#include <system_error>
37//    Implementing a shared_mutex without OS support will require atomic read-
38//  modify-write capacity.
39#include <atomic>
40//  For timing in shared_lock and shared_timed_mutex.
41#include <chrono>
42#include <limits>
43
44//    Use MinGW's shared_lock class template, if it's available. Requires C++14.
45//  If unavailable (eg. because this library is being used in C++11), then an
46//  implementation of shared_lock is provided by this header.
47#if (__cplusplus >= 201402L)
48#include_next <shared_mutex>
49#endif
50
51//  For defer_lock_t, adopt_lock_t, and try_to_lock_t
52#include <mutex>
53//  For this_thread::yield.
54#include <thread>
55
56//  Might be able to use native Slim Reader-Writer (SRW) locks.
57#ifdef _WIN32
58#include <windows.h>
59#endif
60
61namespace mingw_stdthread
62{
63//  Define a portable atomics-based shared_mutex
64namespace portable
65{
66class shared_mutex
67{
68    typedef uint_fast16_t counter_type;
69    std::atomic<counter_type> mCounter {0};
70    static constexpr counter_type kWriteBit = 1 << (std::numeric_limits<counter_type>::digits - 1);
71
72#if STDMUTEX_RECURSION_CHECKS
73//  Runtime checker for verifying owner threads. Note: Exclusive mode only.
74    _OwnerThread mOwnerThread {};
75#endif
76public:
77    typedef shared_mutex * native_handle_type;
78
79    shared_mutex () = default;
80
81//  No form of copying or moving should be allowed.
82    shared_mutex (const shared_mutex&) = delete;
83    shared_mutex & operator= (const shared_mutex&) = delete;
84
85    ~shared_mutex ()
86    {
87//  Terminate if someone tries to destroy an owned mutex.
88        assert(mCounter.load(std::memory_order_relaxed) == 0);
89    }
90
91    void lock_shared (void)
92    {
93        counter_type expected = mCounter.load(std::memory_order_relaxed);
94        do
95        {
96//  Delay if writing or if too many readers are attempting to read.
97            if (expected >= kWriteBit - 1)
98            {
99                using namespace std;
100                using namespace this_thread;
101                yield();
102                expected = mCounter.load(std::memory_order_relaxed);
103                continue;
104            }
105            if (mCounter.compare_exchange_weak(expected,
106                                               static_cast<counter_type>(expected + 1),
107                                               std::memory_order_acquire,
108                                               std::memory_order_relaxed))
109                break;
110        }
111        while (true);
112    }
113
114    bool try_lock_shared (void)
115    {
116        counter_type expected = mCounter.load(std::memory_order_relaxed) & static_cast<counter_type>(~kWriteBit);
117        if (expected + 1 == kWriteBit)
118            return false;
119        else
120            return mCounter.compare_exchange_strong( expected,
121                                                    static_cast<counter_type>(expected + 1),
122                                                    std::memory_order_acquire,
123                                                    std::memory_order_relaxed);
124    }
125
126    void unlock_shared (void)
127    {
128        using namespace std;
129#ifndef NDEBUG
130        if (!(mCounter.fetch_sub(1, memory_order_release) & static_cast<counter_type>(~kWriteBit)))
131            throw system_error(make_error_code(errc::operation_not_permitted));
132#else
133        mCounter.fetch_sub(1, memory_order_release);
134#endif
135    }
136
137//  Behavior is undefined if a lock was previously acquired.
138    void lock (void)
139    {
140#if STDMUTEX_RECURSION_CHECKS
141        DWORD self = mOwnerThread.checkOwnerBeforeLock();
142#endif
143        using namespace std;
144//  Might be able to use relaxed memory order...
145//  Wait for the write-lock to be unlocked, then claim the write slot.
146        counter_type current;
147        while ((current = mCounter.fetch_or(kWriteBit, std::memory_order_acquire)) & kWriteBit)
148            this_thread::yield();
149//  Wait for readers to finish up.
150        while (current != kWriteBit)
151        {
152            this_thread::yield();
153            current = mCounter.load(std::memory_order_acquire);
154        }
155#if STDMUTEX_RECURSION_CHECKS
156        mOwnerThread.setOwnerAfterLock(self);
157#endif
158    }
159
160    bool try_lock (void)
161    {
162#if STDMUTEX_RECURSION_CHECKS
163        DWORD self = mOwnerThread.checkOwnerBeforeLock();
164#endif
165        counter_type expected = 0;
166        bool ret = mCounter.compare_exchange_strong(expected, kWriteBit,
167                                                    std::memory_order_acquire,
168                                                    std::memory_order_relaxed);
169#if STDMUTEX_RECURSION_CHECKS
170        if (ret)
171            mOwnerThread.setOwnerAfterLock(self);
172#endif
173        return ret;
174    }
175
176    void unlock (void)
177    {
178#if STDMUTEX_RECURSION_CHECKS
179        mOwnerThread.checkSetOwnerBeforeUnlock();
180#endif
181        using namespace std;
182#ifndef NDEBUG
183        if (mCounter.load(memory_order_relaxed) != kWriteBit)
184            throw system_error(make_error_code(errc::operation_not_permitted));
185#endif
186        mCounter.store(0, memory_order_release);
187    }
188
189    native_handle_type native_handle (void)
190    {
191        return this;
192    }
193};
194
195} //  Namespace portable
196
197//    The native shared_mutex implementation primarily uses features of Windows
198//  Vista, but the features used for try_lock and try_lock_shared were not
199//  introduced until Windows 7. To allow limited use while compiling for Vista,
200//  I define the class without try_* functions in that case.
201//    Only fully-featured implementations will be placed into namespace std.
202#if defined(_WIN32) && (WINVER >= _WIN32_WINNT_VISTA)
203namespace vista
204{
205class condition_variable_any;
206}
207
208namespace windows7
209{
210//  We already #include "mingw.mutex.h". May as well reduce redundancy.
211class shared_mutex : windows7::mutex
212{
213//    Allow condition_variable_any (and only condition_variable_any) to treat a
214//  shared_mutex as its base class.
215    friend class vista::condition_variable_any;
216public:
217    using windows7::mutex::native_handle_type;
218    using windows7::mutex::lock;
219    using windows7::mutex::unlock;
220    using windows7::mutex::native_handle;
221
222    void lock_shared (void)
223    {
224        AcquireSRWLockShared(native_handle());
225    }
226
227    void unlock_shared (void)
228    {
229        ReleaseSRWLockShared(native_handle());
230    }
231
232//  TryAcquireSRW functions are a Windows 7 feature.
233#if (WINVER >= _WIN32_WINNT_WIN7)
234    bool try_lock_shared (void)
235    {
236        return TryAcquireSRWLockShared(native_handle()) != 0;
237    }
238
239    using windows7::mutex::try_lock;
240#endif
241};
242
243} //  Namespace windows7
244#endif  //  Compiling for Vista
245#if (defined(_WIN32) && (WINVER >= _WIN32_WINNT_WIN7))
246using windows7::shared_mutex;
247#else
248using portable::shared_mutex;
249#endif
250
251class shared_timed_mutex : shared_mutex
252{
253    typedef shared_mutex Base;
254public:
255    using Base::lock;
256    using Base::try_lock;
257    using Base::unlock;
258    using Base::lock_shared;
259    using Base::try_lock_shared;
260    using Base::unlock_shared;
261
262    template< class Clock, class Duration >
263    bool try_lock_until ( const std::chrono::time_point<Clock,Duration>& cutoff )
264    {
265        do
266        {
267            if (try_lock())
268                return true;
269        }
270        while (std::chrono::steady_clock::now() < cutoff);
271        return false;
272    }
273
274    template< class Rep, class Period >
275    bool try_lock_for (const std::chrono::duration<Rep,Period>& rel_time)
276    {
277        return try_lock_until(std::chrono::steady_clock::now() + rel_time);
278    }
279
280    template< class Clock, class Duration >
281    bool try_lock_shared_until ( const std::chrono::time_point<Clock,Duration>& cutoff )
282    {
283        do
284        {
285            if (try_lock_shared())
286                return true;
287        }
288        while (std::chrono::steady_clock::now() < cutoff);
289        return false;
290    }
291
292    template< class Rep, class Period >
293    bool try_lock_shared_for (const std::chrono::duration<Rep,Period>& rel_time)
294    {
295        return try_lock_shared_until(std::chrono::steady_clock::now() + rel_time);
296    }
297};
298
299#if __cplusplus >= 201402L
300using std::shared_lock;
301#else
302//    If not supplied by shared_mutex (eg. because C++14 is not supported), I
303//  supply the various helper classes that the header should have defined.
304template<class Mutex>
305class shared_lock
306{
307    Mutex * mMutex;
308    bool mOwns;
309//  Reduce code redundancy
310    void verify_lockable (void)
311    {
312        using namespace std;
313        if (mMutex == nullptr)
314            throw system_error(make_error_code(errc::operation_not_permitted));
315        if (mOwns)
316            throw system_error(make_error_code(errc::resource_deadlock_would_occur));
317    }
318public:
319    typedef Mutex mutex_type;
320
321    shared_lock (void) noexcept
322        : mMutex(nullptr), mOwns(false)
323    {
324    }
325
326    shared_lock (shared_lock<Mutex> && other) noexcept
327        : mMutex(other.mutex_), mOwns(other.owns_)
328    {
329        other.mMutex = nullptr;
330        other.mOwns = false;
331    }
332
333    explicit shared_lock (mutex_type & m)
334        : mMutex(&m), mOwns(true)
335    {
336        mMutex->lock_shared();
337    }
338
339    shared_lock (mutex_type & m, defer_lock_t) noexcept
340        : mMutex(&m), mOwns(false)
341    {
342    }
343
344    shared_lock (mutex_type & m, adopt_lock_t)
345        : mMutex(&m), mOwns(true)
346    {
347    }
348
349    shared_lock (mutex_type & m, try_to_lock_t)
350        : mMutex(&m), mOwns(m.try_lock_shared())
351    {
352    }
353
354    template< class Rep, class Period >
355    shared_lock( mutex_type& m, const std::chrono::duration<Rep,Period>& timeout_duration )
356        : mMutex(&m), mOwns(m.try_lock_shared_for(timeout_duration))
357    {
358    }
359
360    template< class Clock, class Duration >
361    shared_lock( mutex_type& m, const std::chrono::time_point<Clock,Duration>& timeout_time )
362        : mMutex(&m), mOwns(m.try_lock_shared_until(timeout_time))
363    {
364    }
365
366    shared_lock& operator= (shared_lock<Mutex> && other) noexcept
367    {
368        if (&other != this)
369        {
370            if (mOwns)
371                mMutex->unlock_shared();
372            mMutex = other.mMutex;
373            mOwns = other.mOwns;
374            other.mMutex = nullptr;
375            other.mOwns = false;
376        }
377        return *this;
378    }
379
380
381    ~shared_lock (void)
382    {
383        if (mOwns)
384            mMutex->unlock_shared();
385    }
386
387    shared_lock (const shared_lock<Mutex> &) = delete;
388    shared_lock& operator= (const shared_lock<Mutex> &) = delete;
389
390//  Shared locking
391    void lock (void)
392    {
393        verify_lockable();
394        mMutex->lock_shared();
395        mOwns = true;
396    }
397
398    bool try_lock (void)
399    {
400        verify_lockable();
401        mOwns = mMutex->try_lock_shared();
402        return mOwns;
403    }
404
405    template< class Clock, class Duration >
406    bool try_lock_until( const std::chrono::time_point<Clock,Duration>& cutoff )
407    {
408        verify_lockable();
409        do
410        {
411            mOwns = mMutex->try_lock_shared();
412            if (mOwns)
413                return mOwns;
414        }
415        while (std::chrono::steady_clock::now() < cutoff);
416        return false;
417    }
418
419    template< class Rep, class Period >
420    bool try_lock_for (const std::chrono::duration<Rep,Period>& rel_time)
421    {
422        return try_lock_until(std::chrono::steady_clock::now() + rel_time);
423    }
424
425    void unlock (void)
426    {
427        using namespace std;
428        if (!mOwns)
429            throw system_error(make_error_code(errc::operation_not_permitted));
430        mMutex->unlock_shared();
431        mOwns = false;
432    }
433
434//  Modifiers
435    void swap (shared_lock<Mutex> & other) noexcept
436    {
437        using namespace std;
438        swap(mMutex, other.mMutex);
439        swap(mOwns, other.mOwns);
440    }
441
442    mutex_type * release (void) noexcept
443    {
444        mutex_type * ptr = mMutex;
445        mMutex = nullptr;
446        mOwns = false;
447        return ptr;
448    }
449//  Observers
450    mutex_type * mutex (void) const noexcept
451    {
452        return mMutex;
453    }
454
455    bool owns_lock (void) const noexcept
456    {
457        return mOwns;
458    }
459
460    explicit operator bool () const noexcept
461    {
462        return owns_lock();
463    }
464};
465
466template< class Mutex >
467void swap( shared_lock<Mutex>& lhs, shared_lock<Mutex>& rhs ) noexcept
468{
469    lhs.swap(rhs);
470}
471#endif  //  C++11
472} //  Namespace mingw_stdthread
473
474namespace std
475{
476//    Because of quirks of the compiler, the common "using namespace std;"
477//  directive would flatten the namespaces and introduce ambiguity where there
478//  was none. Direct specification (std::), however, would be unaffected.
479//    Take the safe option, and include only in the presence of MinGW's win32
480//  implementation.
481#if (__cplusplus < 201703L) || (defined(__MINGW32__ ) && !defined(_GLIBCXX_HAS_GTHREADS))
482using mingw_stdthread::shared_mutex;
483#endif
484#if (__cplusplus < 201402L) || (defined(__MINGW32__ ) && !defined(_GLIBCXX_HAS_GTHREADS))
485using mingw_stdthread::shared_timed_mutex;
486using mingw_stdthread::shared_lock;
487#elif !defined(MINGW_STDTHREAD_REDUNDANCY_WARNING)  //  Skip repetition
488#define MINGW_STDTHREAD_REDUNDANCY_WARNING
489#pragma message "This version of MinGW seems to include a win32 port of\
490 pthreads, and probably already has C++ std threading classes implemented,\
491 based on pthreads. These classes, found in namespace std, are not overridden\
492 by the mingw-std-thread library. If you would still like to use this\
493 implementation (as it is more lightweight), use the classes provided in\
494 namespace mingw_stdthread."
495#endif
496} //  Namespace std
497#endif // MINGW_SHARED_MUTEX_H_
498