summaryrefslogtreecommitdiffstats
path: root/xpcom/threads/Monitor.h
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /xpcom/threads/Monitor.h
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.zip
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--xpcom/threads/Monitor.h316
1 files changed, 316 insertions, 0 deletions
diff --git a/xpcom/threads/Monitor.h b/xpcom/threads/Monitor.h
new file mode 100644
index 0000000000..7bb91f9ad7
--- /dev/null
+++ b/xpcom/threads/Monitor.h
@@ -0,0 +1,316 @@
+/* -*- 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/. */
+
+#ifndef mozilla_Monitor_h
+#define mozilla_Monitor_h
+
+#include "mozilla/CondVar.h"
+#include "mozilla/Mutex.h"
+
+namespace mozilla {
+
+/**
+ * Monitor provides a *non*-reentrant monitor: *not* a Java-style
+ * monitor. If your code needs support for reentrancy, use
+ * ReentrantMonitor instead. (Rarely should reentrancy be needed.)
+ *
+ * Instead of directly calling Monitor methods, it's safer and simpler
+ * to instead use the RAII wrappers MonitorAutoLock and
+ * MonitorAutoUnlock.
+ */
+class MOZ_CAPABILITY("monitor") Monitor {
+ public:
+ explicit Monitor(const char* aName)
+ : mMutex(aName), mCondVar(mMutex, "[Monitor.mCondVar]") {}
+
+ ~Monitor() = default;
+
+ void Lock() MOZ_CAPABILITY_ACQUIRE() { mMutex.Lock(); }
+ [[nodiscard]] bool TryLock() MOZ_TRY_ACQUIRE(true) {
+ return mMutex.TryLock();
+ }
+ void Unlock() MOZ_CAPABILITY_RELEASE() { mMutex.Unlock(); }
+
+ void Wait() MOZ_REQUIRES(this) { mCondVar.Wait(); }
+ CVStatus Wait(TimeDuration aDuration) MOZ_REQUIRES(this) {
+ return mCondVar.Wait(aDuration);
+ }
+
+ void Notify() { mCondVar.Notify(); }
+ void NotifyAll() { mCondVar.NotifyAll(); }
+
+ void AssertCurrentThreadOwns() const MOZ_ASSERT_CAPABILITY(this) {
+ mMutex.AssertCurrentThreadOwns();
+ }
+ void AssertNotCurrentThreadOwns() const MOZ_ASSERT_CAPABILITY(!this) {
+ mMutex.AssertNotCurrentThreadOwns();
+ }
+
+ private:
+ Monitor() = delete;
+ Monitor(const Monitor&) = delete;
+ Monitor& operator=(const Monitor&) = delete;
+
+ Mutex mMutex;
+ CondVar mCondVar;
+};
+
+/**
+ * MonitorSingleWriter
+ *
+ * Monitor where a single writer exists, so that reads from the same thread
+ * will not generate data races or consistency issues.
+ *
+ * When possible, use MonitorAutoLock/MonitorAutoUnlock to lock/unlock this
+ * monitor within a scope, instead of calling Lock/Unlock directly.
+ *
+ * This requires an object implementing Mutex's SingleWriterLockOwner, so
+ * we can do correct-thread checks.
+ */
+
+class MonitorSingleWriter : public Monitor {
+ public:
+ // aOwner should be the object that contains the mutex, typically. We
+ // will use that object (which must have a lifetime the same or greater
+ // than this object) to verify that we're running on the correct thread,
+ // typically only in DEBUG builds
+ explicit MonitorSingleWriter(const char* aName, SingleWriterLockOwner* aOwner)
+ : Monitor(aName)
+#ifdef DEBUG
+ ,
+ mOwner(aOwner)
+#endif
+ {
+ MOZ_COUNT_CTOR(MonitorSingleWriter);
+ MOZ_ASSERT(mOwner);
+ }
+
+ MOZ_COUNTED_DTOR(MonitorSingleWriter)
+
+ void AssertOnWritingThread() const MOZ_ASSERT_CAPABILITY(this) {
+ MOZ_ASSERT(mOwner->OnWritingThread());
+ }
+ void AssertOnWritingThreadOrHeld() const MOZ_ASSERT_CAPABILITY(this) {
+#ifdef DEBUG
+ if (!mOwner->OnWritingThread()) {
+ AssertCurrentThreadOwns();
+ }
+#endif
+ }
+
+ private:
+#ifdef DEBUG
+ SingleWriterLockOwner* mOwner MOZ_UNSAFE_REF(
+ "This is normally the object that contains the MonitorSingleWriter, so "
+ "we don't want to hold a reference to ourselves");
+#endif
+
+ MonitorSingleWriter() = delete;
+ MonitorSingleWriter(const MonitorSingleWriter&) = delete;
+ MonitorSingleWriter& operator=(const MonitorSingleWriter&) = delete;
+};
+
+/**
+ * Lock the monitor for the lexical scope instances of this class are
+ * bound to (except for MonitorAutoUnlock in nested scopes).
+ *
+ * The monitor must be unlocked when instances of this class are
+ * created.
+ */
+namespace detail {
+template <typename T>
+class MOZ_SCOPED_CAPABILITY MOZ_STACK_CLASS BaseMonitorAutoLock {
+ public:
+ explicit BaseMonitorAutoLock(T& aMonitor) MOZ_CAPABILITY_ACQUIRE(aMonitor)
+ : mMonitor(&aMonitor) {
+ mMonitor->Lock();
+ }
+
+ ~BaseMonitorAutoLock() MOZ_CAPABILITY_RELEASE() { mMonitor->Unlock(); }
+ // It's very hard to mess up MonitorAutoLock lock(mMonitor); ... lock.Wait().
+ // The only way you can fail to hold the lock when you call lock.Wait() is to
+ // use MonitorAutoUnlock. For now we'll ignore that case.
+ void Wait() {
+ mMonitor->AssertCurrentThreadOwns();
+ mMonitor->Wait();
+ }
+ CVStatus Wait(TimeDuration aDuration) {
+ mMonitor->AssertCurrentThreadOwns();
+ return mMonitor->Wait(aDuration);
+ }
+
+ void Notify() { mMonitor->Notify(); }
+ void NotifyAll() { mMonitor->NotifyAll(); }
+
+ // Assert that aLock is the monitor passed to the constructor and that the
+ // current thread owns the monitor. In coding patterns such as:
+ //
+ // void LockedMethod(const BaseAutoLock<T>& aProofOfLock)
+ // {
+ // aProofOfLock.AssertOwns(mMonitor);
+ // ...
+ // }
+ //
+ // Without this assertion, it could be that mMonitor is not actually
+ // locked. It's possible to have code like:
+ //
+ // BaseAutoLock lock(someMonitor);
+ // ...
+ // BaseAutoUnlock unlock(someMonitor);
+ // ...
+ // LockedMethod(lock);
+ //
+ // and in such a case, simply asserting that the monitor pointers match is not
+ // sufficient; monitor ownership must be asserted as well.
+ //
+ // Note that if you are going to use the coding pattern presented above, you
+ // should use this method in preference to using AssertCurrentThreadOwns on
+ // the mutex you expected to be held, since this method provides stronger
+ // guarantees.
+ void AssertOwns(const T& aMonitor) const MOZ_ASSERT_CAPABILITY(aMonitor) {
+ MOZ_ASSERT(&aMonitor == mMonitor);
+ mMonitor->AssertCurrentThreadOwns();
+ }
+
+ private:
+ BaseMonitorAutoLock() = delete;
+ BaseMonitorAutoLock(const BaseMonitorAutoLock&) = delete;
+ BaseMonitorAutoLock& operator=(const BaseMonitorAutoLock&) = delete;
+ static void* operator new(size_t) noexcept(true);
+
+ friend class MonitorAutoUnlock;
+
+ protected:
+ T* mMonitor;
+};
+} // namespace detail
+typedef detail::BaseMonitorAutoLock<Monitor> MonitorAutoLock;
+typedef detail::BaseMonitorAutoLock<MonitorSingleWriter>
+ MonitorSingleWriterAutoLock;
+
+// clang-format off
+// Use if we've done AssertOnWritingThread(), and then later need to take the
+// lock to write to a protected member. Instead of
+// MutexSingleWriterAutoLock lock(mutex)
+// use
+// MutexSingleWriterAutoLockOnThread(lock, mutex)
+// clang-format on
+#define MonitorSingleWriterAutoLockOnThread(lock, monitor) \
+ MOZ_PUSH_IGNORE_THREAD_SAFETY \
+ MonitorSingleWriterAutoLock lock(monitor); \
+ MOZ_POP_THREAD_SAFETY
+
+/**
+ * Unlock the monitor for the lexical scope instances of this class
+ * are bound to (except for MonitorAutoLock in nested scopes).
+ *
+ * The monitor must be locked by the current thread when instances of
+ * this class are created.
+ */
+namespace detail {
+template <typename T>
+class MOZ_STACK_CLASS MOZ_SCOPED_CAPABILITY BaseMonitorAutoUnlock {
+ public:
+ explicit BaseMonitorAutoUnlock(T& aMonitor)
+ MOZ_SCOPED_UNLOCK_RELEASE(aMonitor)
+ : mMonitor(&aMonitor) {
+ mMonitor->Unlock();
+ }
+
+ ~BaseMonitorAutoUnlock() MOZ_SCOPED_UNLOCK_REACQUIRE() { mMonitor->Lock(); }
+
+ private:
+ BaseMonitorAutoUnlock() = delete;
+ BaseMonitorAutoUnlock(const BaseMonitorAutoUnlock&) = delete;
+ BaseMonitorAutoUnlock& operator=(const BaseMonitorAutoUnlock&) = delete;
+ static void* operator new(size_t) noexcept(true);
+
+ T* mMonitor;
+};
+} // namespace detail
+typedef detail::BaseMonitorAutoUnlock<Monitor> MonitorAutoUnlock;
+typedef detail::BaseMonitorAutoUnlock<MonitorSingleWriter>
+ MonitorSingleWriterAutoUnlock;
+
+/**
+ * Lock the monitor for the lexical scope instances of this class are
+ * bound to (except for MonitorAutoUnlock in nested scopes).
+ *
+ * The monitor must be unlocked when instances of this class are
+ * created.
+ */
+class MOZ_SCOPED_CAPABILITY MOZ_STACK_CLASS ReleasableMonitorAutoLock {
+ public:
+ explicit ReleasableMonitorAutoLock(Monitor& aMonitor)
+ MOZ_CAPABILITY_ACQUIRE(aMonitor)
+ : mMonitor(&aMonitor) {
+ mMonitor->Lock();
+ mLocked = true;
+ }
+
+ ~ReleasableMonitorAutoLock() MOZ_CAPABILITY_RELEASE() {
+ if (mLocked) {
+ mMonitor->Unlock();
+ }
+ }
+
+ // See BaseMonitorAutoLock::Wait
+ void Wait() {
+ mMonitor->AssertCurrentThreadOwns(); // someone could have called Unlock()
+ mMonitor->Wait();
+ }
+ CVStatus Wait(TimeDuration aDuration) {
+ mMonitor->AssertCurrentThreadOwns();
+ return mMonitor->Wait(aDuration);
+ }
+
+ void Notify() {
+ MOZ_ASSERT(mLocked);
+ mMonitor->Notify();
+ }
+ void NotifyAll() {
+ MOZ_ASSERT(mLocked);
+ mMonitor->NotifyAll();
+ }
+
+ // Allow dropping the lock prematurely; for example to support something like:
+ // clang-format off
+ // MonitorAutoLock lock(mMonitor);
+ // ...
+ // if (foo) {
+ // lock.Unlock();
+ // MethodThatCantBeCalledWithLock()
+ // return;
+ // }
+ // clang-format on
+ void Unlock() MOZ_CAPABILITY_RELEASE() {
+ MOZ_ASSERT(mLocked);
+ mMonitor->Unlock();
+ mLocked = false;
+ }
+ void Lock() MOZ_CAPABILITY_ACQUIRE() {
+ MOZ_ASSERT(!mLocked);
+ mMonitor->Lock();
+ mLocked = true;
+ }
+ void AssertCurrentThreadOwns() const MOZ_ASSERT_CAPABILITY() {
+ mMonitor->AssertCurrentThreadOwns();
+ }
+
+ private:
+ bool mLocked;
+ Monitor* mMonitor;
+
+ ReleasableMonitorAutoLock() = delete;
+ ReleasableMonitorAutoLock(const ReleasableMonitorAutoLock&) = delete;
+ ReleasableMonitorAutoLock& operator=(const ReleasableMonitorAutoLock&) =
+ delete;
+ static void* operator new(size_t) noexcept(true);
+};
+
+} // namespace mozilla
+
+#endif // mozilla_Monitor_h