summaryrefslogtreecommitdiffstats
path: root/xpcom/threads/ThreadBound.h
diff options
context:
space:
mode:
Diffstat (limited to 'xpcom/threads/ThreadBound.h')
-rw-r--r--xpcom/threads/ThreadBound.h143
1 files changed, 143 insertions, 0 deletions
diff --git a/xpcom/threads/ThreadBound.h b/xpcom/threads/ThreadBound.h
new file mode 100644
index 0000000000..4d5e0088b5
--- /dev/null
+++ b/xpcom/threads/ThreadBound.h
@@ -0,0 +1,143 @@
+/* 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/. */
+
+// A class for values only accessible from a single designated thread.
+
+#ifndef mozilla_ThreadBound_h
+#define mozilla_ThreadBound_h
+
+#include "mozilla/Atomics.h"
+#include "prthread.h"
+
+#include <type_traits>
+
+namespace mozilla {
+
+template <typename T>
+class ThreadBound;
+
+namespace detail {
+
+template <bool Condition, typename T>
+struct AddConstIf {
+ using type = T;
+};
+
+template <typename T>
+struct AddConstIf<true, T> {
+ using type = typename std::add_const<T>::type;
+};
+
+} // namespace detail
+
+// A ThreadBound<T> is a T that can only be accessed by a specific
+// thread. To enforce this rule, the inner T is only accessible
+// through a non-copyable, immovable accessor object.
+// Given a ThreadBound<T> threadBoundData, it can be accessed like so:
+//
+// auto innerData = threadBoundData.Access();
+// innerData->DoStuff();
+//
+// Trying to access a ThreadBound<T> from a different thread will
+// trigger a MOZ_DIAGNOSTIC_ASSERT.
+// The encapsulated T is constructed during the construction of the
+// enclosing ThreadBound<T> by forwarding all of the latter's
+// constructor parameters to the former. A newly constructed
+// ThreadBound<T> is bound to the thread it's constructed in. It's
+// possible to rebind the data to some otherThread by calling
+//
+// threadBoundData.Transfer(otherThread);
+//
+// on the thread that threadBoundData is currently bound to, as long
+// as it's not currently being accessed. (Trying to rebind from
+// another thread or while an accessor exists will trigger an
+// assertion.)
+//
+// Note: A ThreadBound<T> may be destructed from any thread, not just
+// its designated thread at the time the destructor is invoked.
+template <typename T>
+class ThreadBound final {
+ public:
+ template <typename... Args>
+ explicit ThreadBound(Args&&... aArgs)
+ : mData(std::forward<Args>(aArgs)...),
+ mThread(PR_GetCurrentThread()),
+ mAccessCount(0) {}
+
+ ~ThreadBound() { AssertIsNotCurrentlyAccessed(); }
+
+ void Transfer(const PRThread* const aDest) {
+ AssertIsCorrectThread();
+ AssertIsNotCurrentlyAccessed();
+ mThread = aDest;
+ }
+
+ private:
+ T mData;
+
+ // This member is (potentially) accessed by multiple threads and is
+ // thus the first point of synchronization between them.
+ Atomic<const PRThread*, ReleaseAcquire> mThread;
+
+ // In order to support nested accesses (e.g. from different stack
+ // frames) it's necessary to maintain a counter of the existing
+ // accessor. Since it's possible to access a const ThreadBound, the
+ // counter is mutable. It's atomic because accessing it synchronizes
+ // access to mData (see comment in Accessor's constructor).
+ using AccessCountType = Atomic<int, ReleaseAcquire>;
+ mutable AccessCountType mAccessCount;
+
+ public:
+ template <bool IsConst>
+ class MOZ_STACK_CLASS Accessor final {
+ using DataType = typename detail::AddConstIf<IsConst, T>::type;
+
+ public:
+ explicit Accessor(
+ typename detail::AddConstIf<IsConst, ThreadBound>::type& aThreadBound)
+ : mData(aThreadBound.mData), mAccessCount(aThreadBound.mAccessCount) {
+ aThreadBound.AssertIsCorrectThread();
+
+ // This load/store serves as a memory fence that guards mData
+ // against accesses that would trip the thread assertion.
+ // (Otherwise one of the loads in the caller's instruction
+ // stream might be scheduled before the assertion.)
+ ++mAccessCount;
+ }
+
+ Accessor(const Accessor&) = delete;
+ Accessor(Accessor&&) = delete;
+ Accessor& operator=(const Accessor&) = delete;
+ Accessor& operator=(Accessor&&) = delete;
+
+ ~Accessor() { --mAccessCount; }
+
+ DataType* operator->() { return &mData; }
+
+ private:
+ DataType& mData;
+ AccessCountType& mAccessCount;
+ };
+
+ auto Access() { return Accessor<false>{*this}; }
+
+ auto Access() const { return Accessor<true>{*this}; }
+
+ private:
+ bool IsCorrectThread() const { return mThread == PR_GetCurrentThread(); }
+
+ bool IsNotCurrentlyAccessed() const { return mAccessCount == 0; }
+
+#define MOZ_DEFINE_THREAD_BOUND_ASSERT(predicate) \
+ void Assert##predicate() const { MOZ_DIAGNOSTIC_ASSERT(predicate()); }
+
+ MOZ_DEFINE_THREAD_BOUND_ASSERT(IsCorrectThread)
+ MOZ_DEFINE_THREAD_BOUND_ASSERT(IsNotCurrentlyAccessed)
+
+#undef MOZ_DEFINE_THREAD_BOUND_ASSERT
+};
+
+} // namespace mozilla
+
+#endif // mozilla_ThreadBound_h