src/third-party/discord-rpc/include/mingw-std-threads/shared_mutex (view raw)
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_