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