Bug 1919083 - [ci] Enable os-integration variant for more suites, r=jmaher
[gecko.git] / xpcom / threads / Mutex.h
blob8edb6ee773142b35d09620d9412879890adc9c28
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/. */
7 #ifndef mozilla_Mutex_h
8 #define mozilla_Mutex_h
10 #include "mozilla/BlockingResourceBase.h"
11 #include "mozilla/ThreadSafety.h"
12 #include "mozilla/PlatformMutex.h"
13 #include "mozilla/Maybe.h"
14 #include "nsISupports.h"
17 // Provides:
19 // - Mutex, a non-recursive mutex
20 // - MutexAutoLock, an RAII class for ensuring that Mutexes are properly
21 // locked and unlocked
22 // - MutexAutoUnlock, complementary sibling to MutexAutoLock
24 // - OffTheBooksMutex, a non-recursive mutex that doesn't do leak checking
25 // - OffTheBooksMutexAuto{Lock,Unlock} - Like MutexAuto{Lock,Unlock}, but for
26 // an OffTheBooksMutex.
28 // Using MutexAutoLock/MutexAutoUnlock etc. is MUCH preferred to making bare
29 // calls to Lock and Unlock.
31 namespace mozilla {
33 /**
34 * OffTheBooksMutex is identical to Mutex, except that OffTheBooksMutex doesn't
35 * include leak checking. Sometimes you want to intentionally "leak" a mutex
36 * until shutdown; in these cases, OffTheBooksMutex is for you.
38 class MOZ_CAPABILITY("mutex") OffTheBooksMutex : public detail::MutexImpl,
39 BlockingResourceBase {
40 public:
41 /**
42 * @param aName A name which can reference this lock
43 * @returns If failure, nullptr
44 * If success, a valid Mutex* which must be destroyed
45 * by Mutex::DestroyMutex()
46 **/
47 explicit OffTheBooksMutex(const char* aName)
48 : BlockingResourceBase(aName, eMutex)
49 #ifdef DEBUG
51 mOwningThread(nullptr)
52 #endif
56 ~OffTheBooksMutex() {
57 #ifdef DEBUG
58 MOZ_ASSERT(!mOwningThread, "destroying a still-owned lock!");
59 #endif
62 #ifndef DEBUG
63 /**
64 * Lock this mutex.
65 **/
66 void Lock() MOZ_CAPABILITY_ACQUIRE() { this->lock(); }
68 /**
69 * Try to lock this mutex, returning true if we were successful.
70 **/
71 [[nodiscard]] bool TryLock() MOZ_TRY_ACQUIRE(true) { return this->tryLock(); }
73 /**
74 * Unlock this mutex.
75 **/
76 void Unlock() MOZ_CAPABILITY_RELEASE() { this->unlock(); }
78 /**
79 * Assert that the current thread owns this mutex in debug builds.
81 * Does nothing in non-debug builds.
82 **/
83 void AssertCurrentThreadOwns() const MOZ_ASSERT_CAPABILITY(this) {}
85 /**
86 * Assert that the current thread does not own this mutex.
88 * Note that this function is not implemented for debug builds *and*
89 * non-debug builds due to difficulties in dealing with memory ordering.
91 * It is therefore mostly useful as documentation.
92 **/
93 void AssertNotCurrentThreadOwns() const MOZ_ASSERT_CAPABILITY(!this) {}
95 #else
96 void Lock() MOZ_CAPABILITY_ACQUIRE();
98 [[nodiscard]] bool TryLock() MOZ_TRY_ACQUIRE(true);
99 void Unlock() MOZ_CAPABILITY_RELEASE();
101 void AssertCurrentThreadOwns() const MOZ_ASSERT_CAPABILITY(this);
102 void AssertNotCurrentThreadOwns() const MOZ_ASSERT_CAPABILITY(!this) {
103 // FIXME bug 476536
105 #endif // ifndef DEBUG
107 private:
108 OffTheBooksMutex() = delete;
109 OffTheBooksMutex(const OffTheBooksMutex&) = delete;
110 OffTheBooksMutex& operator=(const OffTheBooksMutex&) = delete;
112 friend class OffTheBooksCondVar;
114 #ifdef DEBUG
115 PRThread* mOwningThread;
116 #endif
120 * Mutex
121 * When possible, use MutexAutoLock/MutexAutoUnlock to lock/unlock this
122 * mutex within a scope, instead of calling Lock/Unlock directly.
124 class Mutex : public OffTheBooksMutex {
125 public:
126 explicit Mutex(const char* aName) : OffTheBooksMutex(aName) {
127 MOZ_COUNT_CTOR(Mutex);
130 MOZ_COUNTED_DTOR(Mutex)
132 private:
133 Mutex() = delete;
134 Mutex(const Mutex&) = delete;
135 Mutex& operator=(const Mutex&) = delete;
139 * MutexSingleWriter
141 * Mutex where a single writer exists, so that reads from the same thread
142 * will not generate data races or consistency issues.
144 * When possible, use MutexAutoLock/MutexAutoUnlock to lock/unlock this
145 * mutex within a scope, instead of calling Lock/Unlock directly.
147 * This requires an object implementing Mutex's SingleWriterLockOwner, so
148 * we can do correct-thread checks.
150 // Subclass this in the object owning the mutex
151 class SingleWriterLockOwner {
152 public:
153 SingleWriterLockOwner() = default;
154 ~SingleWriterLockOwner() = default;
156 virtual bool OnWritingThread() const = 0;
159 class MutexSingleWriter : public OffTheBooksMutex {
160 public:
161 // aOwner should be the object that contains the mutex, typically. We
162 // will use that object (which must have a lifetime the same or greater
163 // than this object) to verify that we're running on the correct thread,
164 // typically only in DEBUG builds
165 explicit MutexSingleWriter(const char* aName, SingleWriterLockOwner* aOwner)
166 : OffTheBooksMutex(aName)
167 #ifdef DEBUG
169 mOwner(aOwner)
170 #endif
172 MOZ_COUNT_CTOR(MutexSingleWriter);
173 MOZ_ASSERT(mOwner);
176 MOZ_COUNTED_DTOR(MutexSingleWriter)
179 * Statically assert that we're on the only thread that modifies data
180 * guarded by this Mutex. This allows static checking for the pattern of
181 * having a single thread modify a set of data, and read it (under lock)
182 * on other threads, and reads on the thread that modifies it doesn't
183 * require a lock. This doesn't solve the issue of some data under the
184 * Mutex following this pattern, and other data under the mutex being
185 * written from multiple threads.
187 * We could set the writing thread and dynamically check it in debug
188 * builds, but this doesn't. We could also use thread-safety/capability
189 * system to provide direct thread assertions.
191 void AssertOnWritingThread() const MOZ_ASSERT_CAPABILITY(this) {
192 MOZ_ASSERT(mOwner->OnWritingThread());
194 void AssertOnWritingThreadOrHeld() const MOZ_ASSERT_CAPABILITY(this) {
195 #ifdef DEBUG
196 if (!mOwner->OnWritingThread()) {
197 AssertCurrentThreadOwns();
199 #endif
202 private:
203 #ifdef DEBUG
204 SingleWriterLockOwner* mOwner MOZ_UNSAFE_REF(
205 "This is normally the object that contains the MonitorSingleWriter, so "
206 "we don't want to hold a reference to ourselves");
207 #endif
209 MutexSingleWriter() = delete;
210 MutexSingleWriter(const MutexSingleWriter&) = delete;
211 MutexSingleWriter& operator=(const MutexSingleWriter&) = delete;
214 namespace detail {
215 template <typename T>
216 class MOZ_RAII BaseAutoUnlock;
219 * MutexAutoLock
220 * Acquires the Mutex when it enters scope, and releases it when it leaves
221 * scope.
223 * MUCH PREFERRED to bare calls to Mutex.Lock and Unlock.
225 template <typename T>
226 class MOZ_RAII MOZ_SCOPED_CAPABILITY BaseAutoLock {
227 public:
229 * Constructor
230 * The constructor acquires the given lock. The destructor
231 * releases the lock.
233 * @param aLock A valid mozilla::Mutex* returned by
234 * mozilla::Mutex::NewMutex.
236 explicit BaseAutoLock(T aLock) MOZ_CAPABILITY_ACQUIRE(aLock) : mLock(aLock) {
237 mLock.Lock();
240 ~BaseAutoLock(void) MOZ_CAPABILITY_RELEASE() { mLock.Unlock(); }
242 // Assert that aLock is the mutex passed to the constructor and that the
243 // current thread owns the mutex. In coding patterns such as:
245 // void LockedMethod(const BaseAutoLock<T>& aProofOfLock)
246 // {
247 // aProofOfLock.AssertOwns(mMutex);
248 // ...
249 // }
251 // Without this assertion, it could be that mMutex is not actually
252 // locked. It's possible to have code like:
254 // BaseAutoLock lock(someMutex);
255 // ...
256 // BaseAutoUnlock unlock(someMutex);
257 // ...
258 // LockedMethod(lock);
260 // and in such a case, simply asserting that the mutex pointers match is not
261 // sufficient; mutex ownership must be asserted as well.
263 // Note that if you are going to use the coding pattern presented above, you
264 // should use this method in preference to using AssertCurrentThreadOwns on
265 // the mutex you expected to be held, since this method provides stronger
266 // guarantees.
267 void AssertOwns(const T& aMutex) const MOZ_ASSERT_CAPABILITY(aMutex) {
268 MOZ_ASSERT(&aMutex == &mLock);
269 mLock.AssertCurrentThreadOwns();
272 private:
273 BaseAutoLock() = delete;
274 BaseAutoLock(BaseAutoLock&) = delete;
275 BaseAutoLock& operator=(BaseAutoLock&) = delete;
276 static void* operator new(size_t) noexcept(true);
278 friend class BaseAutoUnlock<T>;
280 T mLock;
283 template <typename MutexType>
284 BaseAutoLock(MutexType&) -> BaseAutoLock<MutexType&>;
285 } // namespace detail
287 typedef detail::BaseAutoLock<Mutex&> MutexAutoLock;
288 typedef detail::BaseAutoLock<MutexSingleWriter&> MutexSingleWriterAutoLock;
289 typedef detail::BaseAutoLock<OffTheBooksMutex&> OffTheBooksMutexAutoLock;
291 // Specialization of Maybe<*AutoLock> for space efficiency and to silence
292 // thread-safety analysis, which cannot track what's going on.
293 template <class MutexType>
294 class Maybe<detail::BaseAutoLock<MutexType&>> {
295 public:
296 Maybe() : mLock(nullptr) {}
297 ~Maybe() MOZ_NO_THREAD_SAFETY_ANALYSIS {
298 if (mLock) {
299 mLock->Unlock();
303 constexpr bool isSome() const { return mLock; }
304 constexpr bool isNothing() const { return !mLock; }
306 void emplace(MutexType& aMutex) MOZ_NO_THREAD_SAFETY_ANALYSIS {
307 MOZ_RELEASE_ASSERT(!mLock);
308 mLock = &aMutex;
309 mLock->Lock();
312 void reset() MOZ_NO_THREAD_SAFETY_ANALYSIS {
313 if (mLock) {
314 mLock->Unlock();
315 mLock = nullptr;
319 private:
320 MutexType* mLock;
323 // Use if we've done AssertOnWritingThread(), and then later need to take the
324 // lock to write to a protected member. Instead of
325 // MutexSingleWriterAutoLock lock(mutex)
326 // use
327 // MutexSingleWriterAutoLockOnThread(lock, mutex)
328 #define MutexSingleWriterAutoLockOnThread(lock, mutex) \
329 MOZ_PUSH_IGNORE_THREAD_SAFETY \
330 MutexSingleWriterAutoLock lock(mutex); \
331 MOZ_POP_THREAD_SAFETY
333 namespace detail {
335 * ReleasableMutexAutoLock
336 * Acquires the Mutex when it enters scope, and releases it when it leaves
337 * scope. Allows calling Unlock (and Lock) as an alternative to
338 * MutexAutoUnlock; this can avoid an extra lock/unlock pair.
341 template <typename T>
342 class MOZ_RAII MOZ_SCOPED_CAPABILITY ReleasableBaseAutoLock {
343 public:
345 * Constructor
346 * The constructor acquires the given lock. The destructor
347 * releases the lock.
349 * @param aLock A valid mozilla::Mutex& returned by
350 * mozilla::Mutex::NewMutex.
352 explicit ReleasableBaseAutoLock(T aLock) MOZ_CAPABILITY_ACQUIRE(aLock)
353 : mLock(aLock) {
354 mLock.Lock();
355 mLocked = true;
358 ~ReleasableBaseAutoLock(void) MOZ_CAPABILITY_RELEASE() {
359 if (mLocked) {
360 Unlock();
364 void AssertOwns(const T& aMutex) const MOZ_ASSERT_CAPABILITY(aMutex) {
365 MOZ_ASSERT(&aMutex == &mLock);
366 mLock.AssertCurrentThreadOwns();
369 // Allow dropping the lock prematurely; for example to support something like:
370 // clang-format off
371 // MutexAutoLock lock(mMutex);
372 // ...
373 // if (foo) {
374 // lock.Unlock();
375 // MethodThatCantBeCalledWithLock()
376 // return;
377 // }
378 // clang-format on
379 void Unlock() MOZ_CAPABILITY_RELEASE() {
380 MOZ_ASSERT(mLocked);
381 mLock.Unlock();
382 mLocked = false;
384 void Lock() MOZ_CAPABILITY_ACQUIRE() {
385 MOZ_ASSERT(!mLocked);
386 mLock.Lock();
387 mLocked = true;
390 private:
391 ReleasableBaseAutoLock() = delete;
392 ReleasableBaseAutoLock(ReleasableBaseAutoLock&) = delete;
393 ReleasableBaseAutoLock& operator=(ReleasableBaseAutoLock&) = delete;
394 static void* operator new(size_t) noexcept(true);
396 bool mLocked;
397 T mLock;
400 template <typename MutexType>
401 ReleasableBaseAutoLock(MutexType&) -> ReleasableBaseAutoLock<MutexType&>;
402 } // namespace detail
404 typedef detail::ReleasableBaseAutoLock<Mutex&> ReleasableMutexAutoLock;
406 namespace detail {
408 * BaseAutoUnlock
409 * Releases the Mutex when it enters scope, and re-acquires it when it leaves
410 * scope.
412 * MUCH PREFERRED to bare calls to Mutex.Unlock and Lock.
414 template <typename T>
415 class MOZ_RAII MOZ_SCOPED_CAPABILITY BaseAutoUnlock {
416 public:
417 explicit BaseAutoUnlock(T aLock) MOZ_SCOPED_UNLOCK_RELEASE(aLock)
418 : mLock(aLock) {
419 mLock.Unlock();
422 explicit BaseAutoUnlock(BaseAutoLock<T>& aAutoLock)
423 /* MOZ_CAPABILITY_RELEASE(aAutoLock.mLock) */
424 : mLock(aAutoLock.mLock) {
425 NS_ASSERTION(mLock, "null lock");
426 mLock->Unlock();
429 ~BaseAutoUnlock() MOZ_SCOPED_UNLOCK_REACQUIRE() { mLock.Lock(); }
431 private:
432 BaseAutoUnlock() = delete;
433 BaseAutoUnlock(BaseAutoUnlock&) = delete;
434 BaseAutoUnlock& operator=(BaseAutoUnlock&) = delete;
435 static void* operator new(size_t) noexcept(true);
437 T mLock;
440 template <typename MutexType>
441 BaseAutoUnlock(MutexType&) -> BaseAutoUnlock<MutexType&>;
442 } // namespace detail
444 typedef detail::BaseAutoUnlock<Mutex&> MutexAutoUnlock;
445 typedef detail::BaseAutoUnlock<MutexSingleWriter&> MutexSingleWriterAutoUnlock;
446 typedef detail::BaseAutoUnlock<OffTheBooksMutex&> OffTheBooksMutexAutoUnlock;
448 namespace detail {
450 * BaseAutoTryLock
451 * Tries to acquire the Mutex when it enters scope, and releases it when it
452 * leaves scope.
454 * MUCH PREFERRED to bare calls to Mutex.TryLock and Unlock.
456 template <typename T>
457 class MOZ_RAII MOZ_SCOPED_CAPABILITY BaseAutoTryLock {
458 public:
459 explicit BaseAutoTryLock(T& aLock) MOZ_CAPABILITY_ACQUIRE(aLock)
460 : mLock(aLock.TryLock() ? &aLock : nullptr) {}
462 ~BaseAutoTryLock() MOZ_CAPABILITY_RELEASE() {
463 if (mLock) {
464 mLock->Unlock();
465 mLock = nullptr;
469 explicit operator bool() const { return mLock; }
471 private:
472 BaseAutoTryLock(BaseAutoTryLock&) = delete;
473 BaseAutoTryLock& operator=(BaseAutoTryLock&) = delete;
474 static void* operator new(size_t) noexcept(true);
476 T* mLock;
478 } // namespace detail
480 typedef detail::BaseAutoTryLock<Mutex> MutexAutoTryLock;
481 typedef detail::BaseAutoTryLock<OffTheBooksMutex> OffTheBooksMutexAutoTryLock;
483 } // namespace mozilla
485 #endif // ifndef mozilla_Mutex_h