-
Notifications
You must be signed in to change notification settings - Fork 137
/
mingw.mutex.h
500 lines (480 loc) · 16 KB
/
mingw.mutex.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
/**
* @file mingw.mutex.h
* @brief std::mutex et al implementation for MinGW
** (c) 2013-2016 by Mega Limited, Auckland, New Zealand
* @author Alexander Vassilev
*
* @copyright Simplified (2-clause) BSD License.
* You should have received a copy of the license along with this
* program.
*
* This code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* @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.
*/
#ifndef WIN32STDMUTEX_H
#define WIN32STDMUTEX_H
#if !defined(__cplusplus) || (__cplusplus < 201103L)
#error A C++11 compiler is required!
#endif
// Recursion checks on non-recursive locks have some performance penalty, and
// the C++ standard does not mandate them. The user might want to explicitly
// enable or disable such checks. If the user has no preference, enable such
// checks in debug builds, but not in release builds.
#ifdef STDMUTEX_RECURSION_CHECKS
#elif defined(NDEBUG)
#define STDMUTEX_RECURSION_CHECKS 0
#else
#define STDMUTEX_RECURSION_CHECKS 1
#endif
#include <chrono>
#include <system_error>
#include <atomic>
#include <mutex> //need for call_once()
#if STDMUTEX_RECURSION_CHECKS || !defined(NDEBUG)
#include <cstdio>
#endif
#include <sdkddkver.h> // Detect Windows version.
#if (defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR))
#pragma message "The Windows API that MinGW-w32 provides is not fully compatible\
with Microsoft's API. We'll try to work around this, but we can make no\
guarantees. This problem does not exist in MinGW-w64."
#include <windows.h> // No further granularity can be expected.
#else
#if STDMUTEX_RECURSION_CHECKS
#include <processthreadsapi.h> // For GetCurrentThreadId
#endif
#include <synchapi.h> // For InitializeCriticalSection, etc.
#include <errhandlingapi.h> // For GetLastError
#include <handleapi.h>
#endif
// Need for the implementation of invoke
#include "mingw.invoke.h"
#if !defined(_WIN32_WINNT) || (_WIN32_WINNT < 0x0501)
#error To use the MinGW-std-threads library, you will need to define the macro _WIN32_WINNT to be 0x0501 (Windows XP) or higher.
#endif
namespace mingw_stdthread
{
// The _NonRecursive class has mechanisms that do not play nice with direct
// manipulation of the native handle. This forward declaration is part of
// a friend class declaration.
#if STDMUTEX_RECURSION_CHECKS
namespace vista
{
class condition_variable;
}
#endif
// To make this namespace equivalent to the thread-related subset of std,
// pull in the classes and class templates supplied by std but not by this
// implementation.
using std::lock_guard;
using std::unique_lock;
using std::adopt_lock_t;
using std::defer_lock_t;
using std::try_to_lock_t;
using std::adopt_lock;
using std::defer_lock;
using std::try_to_lock;
class recursive_mutex
{
CRITICAL_SECTION mHandle;
public:
typedef LPCRITICAL_SECTION native_handle_type;
native_handle_type native_handle() {return &mHandle;}
recursive_mutex() noexcept : mHandle()
{
InitializeCriticalSection(&mHandle);
}
recursive_mutex (const recursive_mutex&) = delete;
recursive_mutex& operator=(const recursive_mutex&) = delete;
~recursive_mutex() noexcept
{
DeleteCriticalSection(&mHandle);
}
void lock()
{
EnterCriticalSection(&mHandle);
}
void unlock()
{
LeaveCriticalSection(&mHandle);
}
bool try_lock()
{
return (TryEnterCriticalSection(&mHandle)!=0);
}
};
#if STDMUTEX_RECURSION_CHECKS
struct _OwnerThread
{
// If this is to be read before locking, then the owner-thread variable must
// be atomic to prevent a torn read from spuriously causing errors.
std::atomic<DWORD> mOwnerThread;
constexpr _OwnerThread () noexcept : mOwnerThread(0) {}
static void on_deadlock (void)
{
using namespace std;
fprintf(stderr, "FATAL: Recursive locking of non-recursive mutex\
detected. Throwing system exception\n");
fflush(stderr);
throw system_error(make_error_code(errc::resource_deadlock_would_occur));
}
DWORD checkOwnerBeforeLock() const
{
DWORD self = GetCurrentThreadId();
if (mOwnerThread.load(std::memory_order_relaxed) == self)
on_deadlock();
return self;
}
void setOwnerAfterLock(DWORD id)
{
mOwnerThread.store(id, std::memory_order_relaxed);
}
void checkSetOwnerBeforeUnlock()
{
DWORD self = GetCurrentThreadId();
if (mOwnerThread.load(std::memory_order_relaxed) != self)
on_deadlock();
mOwnerThread.store(0, std::memory_order_relaxed);
}
};
#endif
// Though the Slim Reader-Writer (SRW) locks used here are not complete until
// Windows 7, implementing partial functionality in Vista will simplify the
// interaction with condition variables.
//Define SRWLOCK_INIT.
#if !defined(SRWLOCK_INIT)
#pragma message "SRWLOCK_INIT macro is not defined. Defining automatically."
#define SRWLOCK_INIT {0}
#endif
#if defined(_WIN32) && (WINVER >= _WIN32_WINNT_VISTA)
namespace windows7
{
class mutex
{
SRWLOCK mHandle;
// Track locking thread for error checking.
#if STDMUTEX_RECURSION_CHECKS
friend class vista::condition_variable;
_OwnerThread mOwnerThread {};
#endif
public:
typedef PSRWLOCK native_handle_type;
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant"
constexpr mutex () noexcept : mHandle(SRWLOCK_INIT) { }
#pragma GCC diagnostic pop
mutex (const mutex&) = delete;
mutex & operator= (const mutex&) = delete;
void lock (void)
{
// Note: Undefined behavior if called recursively.
#if STDMUTEX_RECURSION_CHECKS
DWORD self = mOwnerThread.checkOwnerBeforeLock();
#endif
AcquireSRWLockExclusive(&mHandle);
#if STDMUTEX_RECURSION_CHECKS
mOwnerThread.setOwnerAfterLock(self);
#endif
}
void unlock (void)
{
#if STDMUTEX_RECURSION_CHECKS
mOwnerThread.checkSetOwnerBeforeUnlock();
#endif
ReleaseSRWLockExclusive(&mHandle);
}
// TryAcquireSRW functions are a Windows 7 feature.
#if (WINVER >= _WIN32_WINNT_WIN7)
bool try_lock (void)
{
#if STDMUTEX_RECURSION_CHECKS
DWORD self = mOwnerThread.checkOwnerBeforeLock();
#endif
BOOL ret = TryAcquireSRWLockExclusive(&mHandle);
#if STDMUTEX_RECURSION_CHECKS
if (ret)
mOwnerThread.setOwnerAfterLock(self);
#endif
return ret;
}
#endif
native_handle_type native_handle (void)
{
return &mHandle;
}
};
} // Namespace windows7
#endif // Compiling for Vista
namespace xp
{
class mutex
{
CRITICAL_SECTION mHandle;
std::atomic_uchar mState;
// Track locking thread for error checking.
#if STDMUTEX_RECURSION_CHECKS
friend class vista::condition_variable;
_OwnerThread mOwnerThread {};
#endif
public:
typedef PCRITICAL_SECTION native_handle_type;
constexpr mutex () noexcept : mHandle(), mState(2) { }
mutex (const mutex&) = delete;
mutex & operator= (const mutex&) = delete;
~mutex() noexcept
{
// Undefined behavior if the mutex is held (locked) by any thread.
// Undefined behavior if a thread terminates while holding ownership of the
// mutex.
DeleteCriticalSection(&mHandle);
}
void lock (void)
{
unsigned char state = mState.load(std::memory_order_acquire);
while (state) {
if ((state == 2) && mState.compare_exchange_weak(state, 1, std::memory_order_acquire))
{
InitializeCriticalSection(&mHandle);
mState.store(0, std::memory_order_release);
break;
}
if (state == 1)
{
Sleep(0);
state = mState.load(std::memory_order_acquire);
}
}
#if STDMUTEX_RECURSION_CHECKS
DWORD self = mOwnerThread.checkOwnerBeforeLock();
#endif
EnterCriticalSection(&mHandle);
#if STDMUTEX_RECURSION_CHECKS
mOwnerThread.setOwnerAfterLock(self);
#endif
}
void unlock (void)
{
#if STDMUTEX_RECURSION_CHECKS
mOwnerThread.checkSetOwnerBeforeUnlock();
#endif
LeaveCriticalSection(&mHandle);
}
bool try_lock (void)
{
unsigned char state = mState.load(std::memory_order_acquire);
if ((state == 2) && mState.compare_exchange_strong(state, 1, std::memory_order_acquire))
{
InitializeCriticalSection(&mHandle);
mState.store(0, std::memory_order_release);
}
if (state == 1)
return false;
#if STDMUTEX_RECURSION_CHECKS
DWORD self = mOwnerThread.checkOwnerBeforeLock();
#endif
BOOL ret = TryEnterCriticalSection(&mHandle);
#if STDMUTEX_RECURSION_CHECKS
if (ret)
mOwnerThread.setOwnerAfterLock(self);
#endif
return ret;
}
native_handle_type native_handle (void)
{
return &mHandle;
}
};
} // Namespace "xp"
#if (WINVER >= _WIN32_WINNT_WIN7)
using windows7::mutex;
#else
using xp::mutex;
#endif
class recursive_timed_mutex
{
static constexpr DWORD kWaitAbandoned = 0x00000080l;
static constexpr DWORD kWaitObject0 = 0x00000000l;
static constexpr DWORD kInfinite = 0xffffffffl;
inline bool try_lock_internal (DWORD ms) noexcept
{
DWORD ret = WaitForSingleObject(mHandle, ms);
#ifndef NDEBUG
if (ret == kWaitAbandoned)
{
using namespace std;
fprintf(stderr, "FATAL: Thread terminated while holding a mutex.");
terminate();
}
#endif
return (ret == kWaitObject0) || (ret == kWaitAbandoned);
}
protected:
HANDLE mHandle;
// Track locking thread for error checking of non-recursive timed_mutex. For
// standard compliance, this must be defined in same class and at the same
// access-control level as every other variable in the timed_mutex.
#if STDMUTEX_RECURSION_CHECKS
friend class vista::condition_variable;
_OwnerThread mOwnerThread {};
#endif
public:
typedef HANDLE native_handle_type;
native_handle_type native_handle() const {return mHandle;}
recursive_timed_mutex(const recursive_timed_mutex&) = delete;
recursive_timed_mutex& operator=(const recursive_timed_mutex&) = delete;
recursive_timed_mutex(): mHandle(CreateMutex(NULL, FALSE, NULL)) {}
~recursive_timed_mutex()
{
CloseHandle(mHandle);
}
void lock()
{
DWORD ret = WaitForSingleObject(mHandle, kInfinite);
// If (ret == WAIT_ABANDONED), then the thread that held ownership was
// terminated. Behavior is undefined, but Windows will pass ownership to this
// thread.
#ifndef NDEBUG
if (ret == kWaitAbandoned)
{
using namespace std;
fprintf(stderr, "FATAL: Thread terminated while holding a mutex.");
terminate();
}
#endif
if ((ret != kWaitObject0) && (ret != kWaitAbandoned))
{
throw std::system_error(GetLastError(), std::system_category());
}
}
void unlock()
{
if (!ReleaseMutex(mHandle))
throw std::system_error(GetLastError(), std::system_category());
}
bool try_lock()
{
return try_lock_internal(0);
}
template <class Rep, class Period>
bool try_lock_for(const std::chrono::duration<Rep,Period>& dur)
{
using namespace std::chrono;
auto timeout = duration_cast<milliseconds>(dur).count();
while (timeout > 0)
{
constexpr auto kMaxStep = static_cast<decltype(timeout)>(kInfinite-1);
auto step = (timeout < kMaxStep) ? timeout : kMaxStep;
if (try_lock_internal(static_cast<DWORD>(step)))
return true;
timeout -= step;
}
return false;
}
template <class Clock, class Duration>
bool try_lock_until(const std::chrono::time_point<Clock,Duration>& timeout_time)
{
return try_lock_for(timeout_time - Clock::now());
}
};
// Override if, and only if, it is necessary for error-checking.
#if STDMUTEX_RECURSION_CHECKS
class timed_mutex: recursive_timed_mutex
{
public:
timed_mutex() = default;
timed_mutex(const timed_mutex&) = delete;
timed_mutex& operator=(const timed_mutex&) = delete;
void lock()
{
DWORD self = mOwnerThread.checkOwnerBeforeLock();
recursive_timed_mutex::lock();
mOwnerThread.setOwnerAfterLock(self);
}
void unlock()
{
mOwnerThread.checkSetOwnerBeforeUnlock();
recursive_timed_mutex::unlock();
}
template <class Rep, class Period>
bool try_lock_for(const std::chrono::duration<Rep,Period>& dur)
{
DWORD self = mOwnerThread.checkOwnerBeforeLock();
bool ret = recursive_timed_mutex::try_lock_for(dur);
if (ret)
mOwnerThread.setOwnerAfterLock(self);
return ret;
}
template <class Clock, class Duration>
bool try_lock_until(const std::chrono::time_point<Clock,Duration>& timeout_time)
{
return try_lock_for(timeout_time - Clock::now());
}
bool try_lock ()
{
return try_lock_for(std::chrono::milliseconds(0));
}
};
#else
typedef recursive_timed_mutex timed_mutex;
#endif
class once_flag
{
// When available, the SRW-based mutexes should be faster than the
// CriticalSection-based mutexes. Only try_lock will be unavailable in Vista,
// and try_lock is not used by once_flag.
#if (_WIN32_WINNT == _WIN32_WINNT_VISTA)
windows7::mutex mMutex;
#else
mutex mMutex;
#endif
std::atomic_bool mHasRun;
once_flag(const once_flag&) = delete;
once_flag& operator=(const once_flag&) = delete;
template<class Callable, class... Args>
friend void call_once(once_flag& once, Callable&& f, Args&&... args);
public:
constexpr once_flag() noexcept: mMutex(), mHasRun(false) {}
};
template<class Callable, class... Args>
void call_once(once_flag& flag, Callable&& func, Args&&... args)
{
if (flag.mHasRun.load(std::memory_order_acquire))
return;
lock_guard<decltype(flag.mMutex)> lock(flag.mMutex);
if (flag.mHasRun.load(std::memory_order_relaxed))
return;
detail::invoke(std::forward<Callable>(func),std::forward<Args>(args)...);
flag.mHasRun.store(true, std::memory_order_release);
}
} // Namespace mingw_stdthread
// Push objects into std, but only if they are not already there.
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 defined(__MINGW32__ ) && !defined(_GLIBCXX_HAS_GTHREADS)
using mingw_stdthread::recursive_mutex;
using mingw_stdthread::mutex;
using mingw_stdthread::recursive_timed_mutex;
using mingw_stdthread::timed_mutex;
using mingw_stdthread::once_flag;
using mingw_stdthread::call_once;
#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++11 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
}
#endif // WIN32STDMUTEX_H