Mutex.h (8301B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #ifndef Mutex_h 8 #define Mutex_h 9 10 #if defined(XP_WIN) 11 # include <windows.h> 12 #else 13 # include <pthread.h> 14 #endif 15 #if defined(XP_DARWIN) 16 # include <os/lock.h> 17 #endif 18 19 #include "mozilla/Assertions.h" 20 #include "mozilla/Attributes.h" 21 #include "mozilla/MaybeStorageBase.h" 22 #include "mozilla/ThreadSafety.h" 23 24 #if defined(XP_DARWIN) 25 // For information about the following undocumented flags and functions see 26 // https://github.com/apple/darwin-xnu/blob/main/bsd/sys/ulock.h and 27 // https://github.com/apple/darwin-libplatform/blob/main/private/os/lock_private.h 28 # define OS_UNFAIR_LOCK_DATA_SYNCHRONIZATION (0x00010000) 29 # define OS_UNFAIR_LOCK_ADAPTIVE_SPIN (0x00040000) 30 31 extern "C" { 32 33 typedef uint32_t os_unfair_lock_options_t; 34 OS_UNFAIR_LOCK_AVAILABILITY 35 OS_EXPORT OS_NOTHROW OS_NONNULL_ALL void os_unfair_lock_lock_with_options( 36 os_unfair_lock_t lock, os_unfair_lock_options_t options); 37 } 38 #endif // defined(XP_DARWIN) 39 40 // Mutexes are based on spinlocks. We can't use normal pthread spinlocks in all 41 // places, because they require malloc()ed memory, which causes bootstrapping 42 // issues in some cases. We also can't use non-constexpr constructors, because 43 // for statics, they would fire after the first use of malloc, resetting the 44 // locks. 45 // 46 // A constexpr constructor is provided so that Mutex can be part of something 47 // that is constinit, but the mutex won't be initialised, you must still 48 // call Init() before the mutex can be used. 49 struct MOZ_CAPABILITY("mutex") Mutex { 50 #if defined(XP_WIN) 51 // MaybeStorageBase provides a constexpr constructor. 52 mozilla::detail::MaybeStorageBase<CRITICAL_SECTION> mMutex; 53 #elif defined(XP_DARWIN) 54 os_unfair_lock mMutex = OS_UNFAIR_LOCK_INIT; 55 #elif defined(XP_LINUX) && !defined(ANDROID) 56 pthread_mutex_t mMutex = PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP; 57 #else 58 pthread_mutex_t mMutex = PTHREAD_MUTEX_INITIALIZER; 59 #endif 60 61 #ifdef MOZ_DEBUG 62 bool mInitialised = false; 63 64 // Called by StaticMutex 65 explicit constexpr Mutex(bool aInitialised) : mInitialised(aInitialised) {} 66 #else 67 explicit constexpr Mutex(bool aIgnored) {} 68 #endif 69 70 // Although a constexpr constructor is provided, it will not initialise the 71 // mutex and calling Init() is required. 72 constexpr Mutex() {} 73 74 // (Re-)initializes a mutex. Returns whether initialization succeeded. 75 inline bool Init() { 76 #ifdef MOZ_DEBUG 77 mInitialised = true; 78 #endif 79 #if defined(XP_WIN) 80 if (!InitializeCriticalSectionAndSpinCount(mMutex.addr(), 5000)) { 81 return false; 82 } 83 #elif defined(XP_DARWIN) 84 mMutex = OS_UNFAIR_LOCK_INIT; 85 #elif defined(XP_LINUX) && !defined(ANDROID) 86 pthread_mutexattr_t attr; 87 if (pthread_mutexattr_init(&attr) != 0) { 88 return false; 89 } 90 pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ADAPTIVE_NP); 91 if (pthread_mutex_init(&mMutex, &attr) != 0) { 92 pthread_mutexattr_destroy(&attr); 93 return false; 94 } 95 pthread_mutexattr_destroy(&attr); 96 #else 97 if (pthread_mutex_init(&mMutex, nullptr) != 0) { 98 return false; 99 } 100 #endif 101 return true; 102 } 103 104 inline void Lock() MOZ_CAPABILITY_ACQUIRE() { 105 MOZ_ASSERT(mInitialised); 106 107 #if defined(XP_WIN) 108 EnterCriticalSection(mMutex.addr()); 109 #elif defined(XP_DARWIN) 110 // We rely on a non-public function to improve performance here. 111 // The OS_UNFAIR_LOCK_DATA_SYNCHRONIZATION flag informs the kernel that 112 // the calling thread is able to make progress even in absence of actions 113 // from other threads and the OS_UNFAIR_LOCK_ADAPTIVE_SPIN one causes the 114 // kernel to spin on a contested lock if the owning thread is running on 115 // the same physical core (presumably only on x86 CPUs given that ARM 116 // macs don't have cores capable of SMT). 117 os_unfair_lock_lock_with_options( 118 &mMutex, 119 OS_UNFAIR_LOCK_DATA_SYNCHRONIZATION | OS_UNFAIR_LOCK_ADAPTIVE_SPIN); 120 #else 121 pthread_mutex_lock(&mMutex); 122 #endif 123 } 124 125 [[nodiscard]] bool TryLock() MOZ_TRY_ACQUIRE(true); 126 127 inline void Unlock() MOZ_CAPABILITY_RELEASE() { 128 MOZ_ASSERT(mInitialised); 129 130 #if defined(XP_WIN) 131 LeaveCriticalSection(mMutex.addr()); 132 #elif defined(XP_DARWIN) 133 os_unfair_lock_unlock(&mMutex); 134 #else 135 pthread_mutex_unlock(&mMutex); 136 #endif 137 } 138 139 #if defined(XP_DARWIN) 140 static bool SpinInKernelSpace(); 141 static const bool gSpinInKernelSpace; 142 #endif // XP_DARWIN 143 }; 144 145 // Mutex that can be used for static initialization. 146 // On Windows, CRITICAL_SECTION requires a function call to be initialized, 147 // but for the initialization lock, a static initializer calling the 148 // function would be called too late. We need no-function-call 149 // initialization, which SRWLock provides. 150 // Ideally, we'd use the same type of locks everywhere, but SRWLocks 151 // everywhere incur a performance penalty. See bug 1418389. 152 #if defined(XP_WIN) 153 struct MOZ_CAPABILITY("mutex") StaticMutex { 154 SRWLOCK mMutex; 155 156 constexpr StaticMutex() : mMutex(SRWLOCK_INIT) {} 157 158 inline void Lock() MOZ_CAPABILITY_ACQUIRE() { 159 AcquireSRWLockExclusive(&mMutex); 160 } 161 162 inline void Unlock() MOZ_CAPABILITY_RELEASE() { 163 ReleaseSRWLockExclusive(&mMutex); 164 } 165 }; 166 167 #else 168 struct MOZ_CAPABILITY("mutex") StaticMutex : public Mutex { 169 constexpr StaticMutex() : Mutex(true) {} 170 }; 171 #endif 172 173 #ifdef XP_WIN 174 typedef DWORD ThreadId; 175 inline ThreadId GetThreadId() { return GetCurrentThreadId(); } 176 inline bool ThreadIdEqual(ThreadId a, ThreadId b) { return a == b; } 177 #else 178 typedef pthread_t ThreadId; 179 inline ThreadId GetThreadId() { return pthread_self(); } 180 inline bool ThreadIdEqual(ThreadId a, ThreadId b) { 181 return pthread_equal(a, b); 182 } 183 #endif 184 185 class MOZ_CAPABILITY("mutex") MaybeMutex : public Mutex { 186 public: 187 enum DoLock { 188 MUST_LOCK, 189 AVOID_LOCK_UNSAFE, 190 }; 191 192 bool Init(DoLock aDoLock) { 193 mDoLock = aDoLock; 194 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED 195 mThreadId = GetThreadId(); 196 #endif 197 return Mutex::Init(); 198 } 199 200 #ifndef XP_WIN 201 // Re initialise after fork(), assumes that mDoLock is already initialised. 202 void Reinit(pthread_t aForkingThread) { 203 if (mDoLock == MUST_LOCK) { 204 Mutex::Init(); 205 return; 206 } 207 # ifdef MOZ_DEBUG 208 // If this is an eluded lock we can only safely re-initialise it if the 209 // thread that called fork is the one that owns the lock. 210 if (pthread_equal(mThreadId, aForkingThread)) { 211 mThreadId = GetThreadId(); 212 Mutex::Init(); 213 } else { 214 // We can't guantee that whatever resource this lock protects (probably a 215 // jemalloc arena) is in a consistent state. 216 mDeniedAfterFork = true; 217 } 218 # endif 219 } 220 #endif 221 222 inline void Lock() MOZ_CAPABILITY_ACQUIRE() { 223 if (ShouldLock()) { 224 Mutex::Lock(); 225 } 226 } 227 228 inline void Unlock() MOZ_CAPABILITY_RELEASE() { 229 if (ShouldLock()) { 230 Mutex::Unlock(); 231 } 232 } 233 234 // Return true if we can use this resource from this thread, either because 235 // we'll use the lock or because this is the only thread that will access the 236 // protected resource. 237 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED 238 bool SafeOnThisThread() const { 239 return mDoLock == MUST_LOCK || ThreadIdEqual(GetThreadId(), mThreadId); 240 } 241 #endif 242 243 bool LockIsEnabled() const { return mDoLock == MUST_LOCK; } 244 245 private: 246 bool ShouldLock() { 247 #ifndef XP_WIN 248 MOZ_ASSERT(!mDeniedAfterFork); 249 #endif 250 251 if (mDoLock == MUST_LOCK) { 252 return true; 253 } 254 255 MOZ_ASSERT(SafeOnThisThread()); 256 return false; 257 } 258 259 DoLock mDoLock; 260 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED 261 ThreadId mThreadId; 262 #endif 263 #if (!defined(XP_WIN) && defined(DEBUG)) 264 bool mDeniedAfterFork = false; 265 #endif 266 }; 267 268 template <typename T> 269 struct MOZ_SCOPED_CAPABILITY MOZ_RAII AutoLock { 270 explicit AutoLock(T& aMutex) MOZ_CAPABILITY_ACQUIRE(aMutex) : mMutex(aMutex) { 271 mMutex.Lock(); 272 } 273 274 ~AutoLock() MOZ_CAPABILITY_RELEASE() { mMutex.Unlock(); } 275 276 AutoLock(const AutoLock&) = delete; 277 AutoLock(AutoLock&&) = delete; 278 279 private: 280 T& mMutex; 281 }; 282 283 using MutexAutoLock = AutoLock<Mutex>; 284 285 using MaybeMutexAutoLock = AutoLock<MaybeMutex>; 286 287 extern StaticMutex gInitLock MOZ_UNANNOTATED; 288 289 #endif