/* 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 namespace mozilla { template class ThreadBound; namespace detail { template struct AddConstIf { using type = T; }; template struct AddConstIf { using type = typename std::add_const::type; }; } // namespace detail // A ThreadBound 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 threadBoundData, it can be accessed like so: // // auto innerData = threadBoundData.Access(); // innerData->DoStuff(); // // Trying to access a ThreadBound from a different thread will // trigger a MOZ_DIAGNOSTIC_ASSERT. // The encapsulated T is constructed during the construction of the // enclosing ThreadBound by forwarding all of the latter's // constructor parameters to the former. A newly constructed // ThreadBound 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 may be destructed from any thread, not just // its designated thread at the time the destructor is invoked. template class ThreadBound final { public: template explicit ThreadBound(Args&&... aArgs) : mData(std::forward(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 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; mutable AccessCountType mAccessCount; public: template class MOZ_STACK_CLASS Accessor final { using DataType = typename detail::AddConstIf::type; public: explicit Accessor( typename detail::AddConstIf::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{*this}; } auto Access() const { return Accessor{*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