summaryrefslogtreecommitdiffstats
path: root/xpcom/threads/RWLock.h
diff options
context:
space:
mode:
Diffstat (limited to 'xpcom/threads/RWLock.h')
-rw-r--r--xpcom/threads/RWLock.h243
1 files changed, 243 insertions, 0 deletions
diff --git a/xpcom/threads/RWLock.h b/xpcom/threads/RWLock.h
new file mode 100644
index 0000000000..e03d008631
--- /dev/null
+++ b/xpcom/threads/RWLock.h
@@ -0,0 +1,243 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// An interface for read-write locks.
+
+#ifndef mozilla_RWLock_h
+#define mozilla_RWLock_h
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/BlockingResourceBase.h"
+#include "mozilla/PlatformRWLock.h"
+#include "mozilla/ThreadSafety.h"
+
+namespace mozilla {
+
+// A RWLock is similar to a Mutex, but whereas a Mutex permits only a single
+// reader thread or a single writer thread to access a piece of data, a
+// RWLock distinguishes between readers and writers: you may have multiple
+// reader threads concurrently accessing a piece of data or a single writer
+// thread. This difference should guide your usage of RWLock: if you are not
+// reading the data from multiple threads simultaneously or you are writing
+// to the data roughly as often as read from it, then Mutex will suit your
+// purposes just fine.
+//
+// You should be using the AutoReadLock and AutoWriteLock classes, below,
+// for RAII read locking and write locking, respectively. If you really must
+// take a read lock manually, call the ReadLock method; to relinquish that
+// read lock, call the ReadUnlock method. Similarly, WriteLock and WriteUnlock
+// perform the same operations, but for write locks.
+//
+// It is unspecified what happens when a given thread attempts to acquire the
+// same lock in multiple ways; some underlying implementations of RWLock do
+// support acquiring a read lock multiple times on a given thread, but you
+// should not rely on this behavior.
+//
+// It is unspecified whether RWLock gives priority to waiting readers or
+// a waiting writer when unlocking.
+class MOZ_CAPABILITY("rwlock") RWLock : public detail::RWLockImpl,
+ public BlockingResourceBase {
+ public:
+ explicit RWLock(const char* aName);
+
+#ifdef DEBUG
+ bool LockedForWritingByCurrentThread();
+ [[nodiscard]] bool TryReadLock() MOZ_SHARED_TRYLOCK_FUNCTION(true);
+ void ReadLock() MOZ_ACQUIRE_SHARED();
+ void ReadUnlock() MOZ_RELEASE_SHARED();
+ [[nodiscard]] bool TryWriteLock() MOZ_TRY_ACQUIRE(true);
+ void WriteLock() MOZ_CAPABILITY_ACQUIRE();
+ void WriteUnlock() MOZ_EXCLUSIVE_RELEASE();
+#else
+ [[nodiscard]] bool TryReadLock() MOZ_SHARED_TRYLOCK_FUNCTION(true) {
+ return detail::RWLockImpl::tryReadLock();
+ }
+ void ReadLock() MOZ_ACQUIRE_SHARED() { detail::RWLockImpl::readLock(); }
+ void ReadUnlock() MOZ_RELEASE_SHARED() { detail::RWLockImpl::readUnlock(); }
+ [[nodiscard]] bool TryWriteLock() MOZ_TRY_ACQUIRE(true) {
+ return detail::RWLockImpl::tryWriteLock();
+ }
+ void WriteLock() MOZ_CAPABILITY_ACQUIRE() { detail::RWLockImpl::writeLock(); }
+ void WriteUnlock() MOZ_EXCLUSIVE_RELEASE() {
+ detail::RWLockImpl::writeUnlock();
+ }
+#endif
+
+ private:
+ RWLock() = delete;
+ RWLock(const RWLock&) = delete;
+ RWLock& operator=(const RWLock&) = delete;
+
+#ifdef DEBUG
+ // We record the owning thread for write locks only.
+ PRThread* mOwningThread;
+#endif
+};
+
+// We only use this once; not sure we can add thread safety attributions here
+template <typename T>
+class MOZ_RAII BaseAutoTryReadLock {
+ public:
+ explicit BaseAutoTryReadLock(T& aLock)
+ : mLock(aLock.TryReadLock() ? &aLock : nullptr) {}
+
+ ~BaseAutoTryReadLock() {
+ if (mLock) {
+ mLock->ReadUnlock();
+ }
+ }
+
+ explicit operator bool() const { return mLock; }
+
+ private:
+ BaseAutoTryReadLock() = delete;
+ BaseAutoTryReadLock(const BaseAutoTryReadLock&) = delete;
+ BaseAutoTryReadLock& operator=(const BaseAutoTryReadLock&) = delete;
+
+ T* mLock;
+};
+
+template <typename T>
+class MOZ_SCOPED_CAPABILITY MOZ_RAII BaseAutoReadLock {
+ public:
+ explicit BaseAutoReadLock(T& aLock) MOZ_ACQUIRE_SHARED(aLock)
+ : mLock(&aLock) {
+ MOZ_ASSERT(mLock, "null lock");
+ mLock->ReadLock();
+ }
+
+ // Not MOZ_RELEASE_SHARED(), which would make sense - apparently this trips
+ // over a bug in clang's static analyzer and it says it expected an
+ // exclusive unlock.
+ ~BaseAutoReadLock() MOZ_RELEASE_GENERIC() { mLock->ReadUnlock(); }
+
+ private:
+ BaseAutoReadLock() = delete;
+ BaseAutoReadLock(const BaseAutoReadLock&) = delete;
+ BaseAutoReadLock& operator=(const BaseAutoReadLock&) = delete;
+
+ T* mLock;
+};
+
+// XXX Mutex attributions?
+template <typename T>
+class MOZ_RAII BaseAutoTryWriteLock {
+ public:
+ explicit BaseAutoTryWriteLock(T& aLock)
+ : mLock(aLock.TryWriteLock() ? &aLock : nullptr) {}
+
+ ~BaseAutoTryWriteLock() {
+ if (mLock) {
+ mLock->WriteUnlock();
+ }
+ }
+
+ explicit operator bool() const { return mLock; }
+
+ private:
+ BaseAutoTryWriteLock() = delete;
+ BaseAutoTryWriteLock(const BaseAutoTryWriteLock&) = delete;
+ BaseAutoTryWriteLock& operator=(const BaseAutoTryWriteLock&) = delete;
+
+ T* mLock;
+};
+
+template <typename T>
+class MOZ_SCOPED_CAPABILITY MOZ_RAII BaseAutoWriteLock final {
+ public:
+ explicit BaseAutoWriteLock(T& aLock) MOZ_CAPABILITY_ACQUIRE(aLock)
+ : mLock(&aLock) {
+ MOZ_ASSERT(mLock, "null lock");
+ mLock->WriteLock();
+ }
+
+ ~BaseAutoWriteLock() MOZ_CAPABILITY_RELEASE() { mLock->WriteUnlock(); }
+
+ private:
+ BaseAutoWriteLock() = delete;
+ BaseAutoWriteLock(const BaseAutoWriteLock&) = delete;
+ BaseAutoWriteLock& operator=(const BaseAutoWriteLock&) = delete;
+
+ T* mLock;
+};
+
+// Read try-lock and unlock a RWLock with RAII semantics. Much preferred to
+// bare calls to TryReadLock() and ReadUnlock().
+typedef BaseAutoTryReadLock<RWLock> AutoTryReadLock;
+
+// Read lock and unlock a RWLock with RAII semantics. Much preferred to bare
+// calls to ReadLock() and ReadUnlock().
+typedef BaseAutoReadLock<RWLock> AutoReadLock;
+
+// Write try-lock and unlock a RWLock with RAII semantics. Much preferred to
+// bare calls to TryWriteLock() and WriteUnlock().
+typedef BaseAutoTryWriteLock<RWLock> AutoTryWriteLock;
+
+// Write lock and unlock a RWLock with RAII semantics. Much preferred to bare
+// calls to WriteLock() and WriteUnlock().
+typedef BaseAutoWriteLock<RWLock> AutoWriteLock;
+
+class MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS MOZ_CAPABILITY("rwlock")
+ StaticRWLock {
+ public:
+ // In debug builds, check that mLock is initialized for us as we expect by
+ // the compiler. In non-debug builds, don't declare a constructor so that
+ // the compiler can see that the constructor is trivial.
+#ifdef DEBUG
+ StaticRWLock() { MOZ_ASSERT(!mLock); }
+#endif
+
+ [[nodiscard]] bool TryReadLock() MOZ_SHARED_TRYLOCK_FUNCTION(true) {
+ return Lock()->TryReadLock();
+ }
+ void ReadLock() MOZ_ACQUIRE_SHARED() { Lock()->ReadLock(); }
+ void ReadUnlock() MOZ_RELEASE_SHARED() { Lock()->ReadUnlock(); }
+ [[nodiscard]] bool TryWriteLock() MOZ_TRY_ACQUIRE(true) {
+ return Lock()->TryWriteLock();
+ }
+ void WriteLock() MOZ_CAPABILITY_ACQUIRE() { Lock()->WriteLock(); }
+ void WriteUnlock() MOZ_EXCLUSIVE_RELEASE() { Lock()->WriteUnlock(); }
+
+ private:
+ [[nodiscard]] RWLock* Lock() MOZ_RETURN_CAPABILITY(*mLock) {
+ if (mLock) {
+ return mLock;
+ }
+
+ RWLock* lock = new RWLock("StaticRWLock");
+ if (!mLock.compareExchange(nullptr, lock)) {
+ delete lock;
+ }
+
+ return mLock;
+ }
+
+ Atomic<RWLock*> mLock;
+
+ // Disallow copy constructor, but only in debug mode. We only define
+ // a default constructor in debug mode (see above); if we declared
+ // this constructor always, the compiler wouldn't generate a trivial
+ // default constructor for us in non-debug mode.
+#ifdef DEBUG
+ StaticRWLock(const StaticRWLock& aOther);
+#endif
+
+ // Disallow these operators.
+ StaticRWLock& operator=(StaticRWLock* aRhs) = delete;
+ static void* operator new(size_t) noexcept(true) = delete;
+ static void operator delete(void*) = delete;
+};
+
+typedef BaseAutoTryReadLock<StaticRWLock> StaticAutoTryReadLock;
+typedef BaseAutoReadLock<StaticRWLock> StaticAutoReadLock;
+typedef BaseAutoTryWriteLock<StaticRWLock> StaticAutoTryWriteLock;
+typedef BaseAutoWriteLock<StaticRWLock> StaticAutoWriteLock;
+
+} // namespace mozilla
+
+#endif // mozilla_RWLock_h