1 /**
2 * The mutex module provides a primitive for maintaining mutually exclusive
3 * access.
4 *
5 * Copyright: Copyright Sean Kelly 2005 - 2009.
6 * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
7 * Authors: Sean Kelly
8 * Source: $(DRUNTIMESRC core/sync/_mutex.d)
9 */
10
11 /* Copyright Sean Kelly 2005 - 2009.
12 * Distributed under the Boost Software License, Version 1.0.
13 * (See accompanying file LICENSE or copy at
14 * http://www.boost.org/LICENSE_1_0.txt)
15 */
16 module core.sync.mutex;
17
18
19 public import core.sync.exception;
20
version(Windows)21 version (Windows)
22 {
23 import core.sys.windows.winbase /+: CRITICAL_SECTION, DeleteCriticalSection,
24 EnterCriticalSection, InitializeCriticalSection, LeaveCriticalSection,
25 TryEnterCriticalSection+/;
26 }
version(Posix)27 else version (Posix)
28 {
29 import core.sys.posix.pthread;
30 }
31 else
32 {
33 static assert(false, "Platform not supported");
34 }
35
36 ////////////////////////////////////////////////////////////////////////////////
37 // Mutex
38 //
39 // void lock();
40 // void unlock();
41 // bool tryLock();
42 ////////////////////////////////////////////////////////////////////////////////
43
44
45 /**
46 * This class represents a general purpose, recursive mutex.
47 *
48 * Implemented using `pthread_mutex` on Posix and `CRITICAL_SECTION`
49 * on Windows.
50 */
51 class Mutex :
52 Object.Monitor
53 {
54 ////////////////////////////////////////////////////////////////////////////
55 // Initialization
56 ////////////////////////////////////////////////////////////////////////////
57
58
59 /**
60 * Initializes a mutex object.
61 *
62 */
this()63 this() @trusted nothrow @nogc
64 {
65 this(true);
66 }
67
68 /// ditto
this()69 this() shared @trusted nothrow @nogc
70 {
71 this(true);
72 }
73
74 // Undocumented, useful only in Mutex.this().
75 private this(this Q)(bool _unused_) @trusted nothrow @nogc
76 if (is(Q == Mutex) || is(Q == shared Mutex))
77 {
version(Windows)78 version (Windows)
79 {
80 InitializeCriticalSection(cast(CRITICAL_SECTION*) &m_hndl);
81 }
version(Posix)82 else version (Posix)
83 {
84 import core.internal.abort : abort;
85 pthread_mutexattr_t attr = void;
86
87 !pthread_mutexattr_init(&attr) ||
88 abort("Error: pthread_mutexattr_init failed.");
89
90 scope (exit) !pthread_mutexattr_destroy(&attr) ||
91 abort("Error: pthread_mutexattr_destroy failed.");
92
93 !pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE) ||
94 abort("Error: pthread_mutexattr_settype failed.");
95
96 !pthread_mutex_init(cast(pthread_mutex_t*) &m_hndl, &attr) ||
97 abort("Error: pthread_mutex_init failed.");
98 }
99
100 m_proxy.link = this;
101 this.__monitor = cast(void*) &m_proxy;
102 }
103
104
105 /**
106 * Initializes a mutex object and sets it as the monitor for `obj`.
107 *
108 * In:
109 * `obj` must not already have a monitor.
110 */
this(Object obj)111 this(Object obj) @trusted nothrow @nogc
112 {
113 this(obj, true);
114 }
115
116 /// ditto
this(Object obj)117 this(Object obj) shared @trusted nothrow @nogc
118 {
119 this(obj, true);
120 }
121
122 // Undocumented, useful only in Mutex.this(Object).
123 private this(this Q)(Object obj, bool _unused_) @trusted nothrow @nogc
124 if (is(Q == Mutex) || is(Q == shared Mutex))
125 in
126 {
127 assert(obj !is null,
128 "The provided object must not be null.");
129 assert(obj.__monitor is null,
130 "The provided object has a monitor already set!");
131 }
132 do
133 {
134 this();
135 obj.__monitor = cast(void*) &m_proxy;
136 }
137
138
~this()139 ~this() @trusted nothrow @nogc
140 {
141 version (Windows)
142 {
143 DeleteCriticalSection(&m_hndl);
144 }
145 else version (Posix)
146 {
147 import core.internal.abort : abort;
148 !pthread_mutex_destroy(&m_hndl) ||
149 abort("Error: pthread_mutex_destroy failed.");
150 }
151 this.__monitor = null;
152 }
153
154
155 ////////////////////////////////////////////////////////////////////////////
156 // General Actions
157 ////////////////////////////////////////////////////////////////////////////
158
159
160 /**
161 * If this lock is not already held by the caller, the lock is acquired,
162 * then the internal counter is incremented by one.
163 *
164 * Note:
165 * `Mutex.lock` does not throw, but a class derived from Mutex can throw.
166 * Use `lock_nothrow` in `nothrow @nogc` code.
167 */
lock()168 @trusted void lock()
169 {
170 lock_nothrow();
171 }
172
173 /// ditto
lock()174 @trusted void lock() shared
175 {
176 lock_nothrow();
177 }
178
179 /// ditto
180 final void lock_nothrow(this Q)() nothrow @trusted @nogc
181 if (is(Q == Mutex) || is(Q == shared Mutex))
182 {
version(Windows)183 version (Windows)
184 {
185 EnterCriticalSection(&m_hndl);
186 }
version(Posix)187 else version (Posix)
188 {
189 if (pthread_mutex_lock(&m_hndl) == 0)
190 return;
191
192 SyncError syncErr = cast(SyncError) cast(void*) typeid(SyncError).initializer;
193 syncErr.msg = "Unable to lock mutex.";
194 throw syncErr;
195 }
196 }
197
198 /**
199 * Decrements the internal lock count by one. If this brings the count to
200 * zero, the lock is released.
201 *
202 * Note:
203 * `Mutex.unlock` does not throw, but a class derived from Mutex can throw.
204 * Use `unlock_nothrow` in `nothrow @nogc` code.
205 */
unlock()206 @trusted void unlock()
207 {
208 unlock_nothrow();
209 }
210
211 /// ditto
unlock()212 @trusted void unlock() shared
213 {
214 unlock_nothrow();
215 }
216
217 /// ditto
218 final void unlock_nothrow(this Q)() nothrow @trusted @nogc
219 if (is(Q == Mutex) || is(Q == shared Mutex))
220 {
version(Windows)221 version (Windows)
222 {
223 LeaveCriticalSection(&m_hndl);
224 }
version(Posix)225 else version (Posix)
226 {
227 if (pthread_mutex_unlock(&m_hndl) == 0)
228 return;
229
230 SyncError syncErr = cast(SyncError) cast(void*) typeid(SyncError).initializer;
231 syncErr.msg = "Unable to unlock mutex.";
232 throw syncErr;
233 }
234 }
235
236 /**
237 * If the lock is held by another caller, the method returns. Otherwise,
238 * the lock is acquired if it is not already held, and then the internal
239 * counter is incremented by one.
240 *
241 * Returns:
242 * true if the lock was acquired and false if not.
243 *
244 * Note:
245 * `Mutex.tryLock` does not throw, but a class derived from Mutex can throw.
246 * Use `tryLock_nothrow` in `nothrow @nogc` code.
247 */
tryLock()248 bool tryLock() @trusted
249 {
250 return tryLock_nothrow();
251 }
252
253 /// ditto
tryLock()254 bool tryLock() shared @trusted
255 {
256 return tryLock_nothrow();
257 }
258
259 /// ditto
260 final bool tryLock_nothrow(this Q)() nothrow @trusted @nogc
261 if (is(Q == Mutex) || is(Q == shared Mutex))
262 {
version(Windows)263 version (Windows)
264 {
265 return TryEnterCriticalSection(&m_hndl) != 0;
266 }
version(Posix)267 else version (Posix)
268 {
269 return pthread_mutex_trylock(&m_hndl) == 0;
270 }
271 }
272
273
274 private:
version(Windows)275 version (Windows)
276 {
277 CRITICAL_SECTION m_hndl;
278 }
version(Posix)279 else version (Posix)
280 {
281 pthread_mutex_t m_hndl;
282 }
283
284 struct MonitorProxy
285 {
286 Object.Monitor link;
287 }
288
289 MonitorProxy m_proxy;
290
291
292 package:
version(Posix)293 version (Posix)
294 {
295 pthread_mutex_t* handleAddr()
296 {
297 return &m_hndl;
298 }
299 }
300 }
301
302 ///
303 /* @safe nothrow -> see druntime PR 1726 */
304 // Test regular usage.
305 unittest
306 {
307 import core.thread : Thread;
308
309 class Resource
310 {
311 Mutex mtx;
312 int cargo;
313
this()314 this() shared @safe nothrow
315 {
316 mtx = new shared Mutex();
317 cargo = 42;
318 }
319
useResource()320 void useResource() shared @safe nothrow @nogc
321 {
322 mtx.lock_nothrow();
323 (cast() cargo) += 1;
324 mtx.unlock_nothrow();
325 }
326 }
327
328 shared Resource res = new shared Resource();
329
330 auto otherThread = new Thread(
331 {
332 foreach (i; 0 .. 10000)
333 res.useResource();
334 }).start();
335
336 foreach (i; 0 .. 10000)
337 res.useResource();
338
339 otherThread.join();
340
341 assert (res.cargo == 20042);
342 }
343
344 // Test @nogc usage.
345 @system @nogc nothrow unittest
346 {
347 import core.stdc.stdlib : malloc, free;
348 import core.lifetime : emplace;
349
350 auto mtx = cast(shared Mutex) malloc(__traits(classInstanceSize, Mutex));
351 emplace(mtx);
352
353 mtx.lock_nothrow();
354
355 { // test recursive locking
356 mtx.tryLock_nothrow();
357 mtx.unlock_nothrow();
358 }
359
360 mtx.unlock_nothrow();
361
362 // In general destorying classes like this is not
363 // safe, but since we know that the only base class
364 // of Mutex is Object and it doesn't have a dtor
365 // we can simply call the non-virtual __dtor() here.
366
367 // Ok to cast away shared because destruction
368 // should happen only from a single thread.
369 (cast(Mutex) mtx).__dtor();
370
371 // Verify that the underlying implementation has been destroyed by checking
372 // that locking is not possible. This assumes that the underlying
373 // implementation is well behaved and makes the object non-lockable upon
374 // destruction. The Bionic, DragonFly, Musl, and Solaris C runtimes don't
375 // appear to do so, so skip this test.
version(CRuntime_Bionic)376 version (CRuntime_Bionic) {} else
version(CRuntime_Musl)377 version (CRuntime_Musl) {} else
version(DragonFlyBSD)378 version (DragonFlyBSD) {} else
version(Solaris)379 version (Solaris) {} else
380 assert(!mtx.tryLock_nothrow());
381
382 free(cast(void*) mtx);
383 }
384
385 // Test single-thread (non-shared) use.
386 unittest
387 {
388 Mutex m = new Mutex();
389
390 m.lock();
391
392 m.tryLock();
393 m.unlock();
394
395 m.unlock();
396 }
397
398 unittest
399 {
400 import core.thread;
401
402 auto mutex = new Mutex;
403 int numThreads = 10;
404 int numTries = 1000;
405 int lockCount = 0;
406
testFn()407 void testFn()
408 {
409 for (int i = 0; i < numTries; ++i)
410 {
411 synchronized (mutex)
412 {
413 ++lockCount;
414 }
415 }
416 }
417
418 auto group = new ThreadGroup;
419
420 for (int i = 0; i < numThreads; ++i)
421 group.create(&testFn);
422
423 group.joinAll();
424 assert(lockCount == numThreads * numTries);
425 }
426