| // Copyright 2023 The Dawn & Tint Authors | |
| // | |
| // Redistribution and use in source and binary forms, with or without | |
| // modification, are permitted provided that the following conditions are met: | |
| // | |
| // 1. Redistributions of source code must retain the above copyright notice, this | |
| // list of conditions and the following disclaimer. | |
| // | |
| // 2. Redistributions in binary form must reproduce the above copyright notice, | |
| // this list of conditions and the following disclaimer in the documentation | |
| // and/or other materials provided with the distribution. | |
| // | |
| // 3. Neither the name of the copyright holder nor the names of its | |
| // contributors may be used to endorse or promote products derived from | |
| // this software without specific prior written permission. | |
| // | |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
| // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
| // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
| // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | |
| // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
| // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
| // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
| // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |
| // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| #ifndef SRC_DAWN_COMMON_MUTEXPROTECTED_H_ | |
| #define SRC_DAWN_COMMON_MUTEXPROTECTED_H_ | |
| #include <chrono> | |
| #include <condition_variable> | |
| #include <mutex> | |
| #include <optional> | |
| #include <shared_mutex> | |
| #include <type_traits> | |
| #include <utility> | |
| #include "src/dawn/common/Compiler.h" | |
| #include "src/dawn/common/Defer.h" | |
| #include "src/dawn/common/Mutex.h" | |
| #include "src/dawn/common/Ref.h" | |
| #include "src/dawn/common/StackAllocated.h" | |
| #include "src/dawn/common/Time.h" | |
| #include "src/utils/non_movable.h" | |
| namespace dawn { | |
| template <typename T, template <typename, typename> class Guard, typename Traits> | |
| class MutexProtected; | |
| // Used by MutexCondVarProtected below where sometimes, it's useful to be able to specify which type | |
| // of notify scope we want. | |
| enum class NotifyType { | |
| All, | |
| One, | |
| None, | |
| }; | |
| template <typename T, template <typename, typename, NotifyType> class Guard, typename Traits> | |
| class MutexCondVarProtected; | |
| namespace detail { | |
| template <typename T> | |
| struct MutexProtectedTraits { | |
| using MutexType = std::mutex; | |
| template <typename Unused> | |
| using LockType = std::unique_lock<std::mutex>; | |
| using ObjectType = T; | |
| static constexpr bool kSupportsTryLock = true; | |
| static MutexType CreateMutex() { return std::mutex(); } | |
| static std::mutex& GetMutex(MutexType& m) { return m; } | |
| static ObjectType* GetObj(T* const obj) { return obj; } | |
| static const ObjectType* GetObj(const T* const obj) { return obj; } | |
| static std::optional<LockType<T>> TryLock(MutexType& mutex) { | |
| LockType<T> lock(GetMutex(mutex), std::try_to_lock); | |
| if (!lock.owns_lock()) { | |
| return std::nullopt; | |
| } | |
| return lock; | |
| } | |
| }; | |
| template <typename T> | |
| struct MutexProtectedTraits<Ref<T>> { | |
| using MutexType = Ref<Mutex>; | |
| template <typename Unused> | |
| using LockType = Mutex::AutoLock; | |
| using ObjectType = T; | |
| static constexpr bool kSupportsTryLock = false; | |
| static MutexType CreateMutex() { return AcquireRef(new Mutex()); } | |
| static Mutex* GetMutex(MutexType& m) { return m.Get(); } | |
| static ObjectType* GetObj(Ref<T>* const obj) { return obj->Get(); } | |
| static const ObjectType* GetObj(const Ref<T>* const obj) { return obj->Get(); } | |
| }; | |
| template <typename T> | |
| struct MutexRefProtectedTraits { | |
| using MutexType = Ref<Mutex>; | |
| template <typename Unused> | |
| using LockType = Mutex::AutoLock; | |
| using ObjectType = T; | |
| static constexpr bool kSupportsTryLock = false; | |
| static MutexType CreateMutex() { return AcquireRef(new Mutex()); } | |
| static Mutex* GetMutex(MutexType& m) { return m.Get(); } | |
| static ObjectType* GetObj(T* const obj) { return obj; } | |
| static const ObjectType* GetObj(const T* const obj) { return obj; } | |
| }; | |
| template <typename T> | |
| struct MutexRecursiveProtectedTraits { | |
| using MutexType = Ref<RecursiveMutex>; | |
| template <typename Unused> | |
| using LockType = RecursiveMutex::AutoLock; | |
| using ObjectType = T; | |
| static constexpr bool kSupportsTryLock = false; | |
| static MutexType CreateMutex() { return AcquireRef(new RecursiveMutex()); } | |
| static RecursiveMutex* GetMutex(MutexType& m) { return m.Get(); } | |
| static ObjectType* GetObj(T* const obj) { return obj; } | |
| static const ObjectType* GetObj(const T* const obj) { return obj; } | |
| }; | |
| template <typename T> | |
| struct MutexRWProtectedTraits { | |
| using MutexType = std::shared_mutex; | |
| template <typename U> | |
| using LockType = std::conditional_t<std::is_const_v<U>, | |
| std::shared_lock<std::shared_mutex>, | |
| std::unique_lock<std::shared_mutex>>; | |
| using ObjectType = T; | |
| static MutexType CreateMutex() { return std::shared_mutex(); } | |
| static std::shared_mutex& GetMutex(MutexType& m) { return m; } | |
| static ObjectType* GetObj(T* const obj) { return obj; } | |
| static const ObjectType* GetObj(const T* const obj) { return obj; } | |
| }; | |
| template <typename T, typename Traits> | |
| class Guard; | |
| template <typename T, typename Traits, NotifyType NotifyT> | |
| class CondVarGuard; | |
| // Guard class is a wrapping class that gives access to a protected resource after acquiring the | |
| // lock related to it. For the lifetime of this class, the lock is held. | |
| template <typename T, typename Traits> | |
| class DAWN_SCOPED_LOCKABLE Guard : public NonMovable, StackAllocated { | |
| public: | |
| // It's the programmer's burden to not save the pointer/reference and reuse it without the lock. | |
| auto* operator->() const { return Get(); } | |
| auto& operator*() const { return *Get(); } | |
| void Defer(std::function<void()> f) { | |
| DAWN_ASSERT(mDefer); | |
| mDefer->Append(std::move(f)); | |
| } | |
| protected: | |
| Guard() : mLock() {} | |
| Guard(T* obj, typename Traits::MutexType& mutex, class Defer* defer = nullptr) | |
| : mLock(Traits::GetMutex(mutex)), mObj(obj), mDefer(defer) {} | |
| Guard(T* obj, typename Traits::template LockType<T>&& lock, class Defer* defer = nullptr) | |
| : mLock(std::move(lock)), mObj(obj), mDefer(defer) {} | |
| Guard(Guard&& other) | |
| : mLock(std::move(other.mLock)), | |
| mObj(std::move(other.mObj)), | |
| mDefer(std::move(other.mDefer)) { | |
| other.mObj = nullptr; | |
| } | |
| Guard& operator=(Guard&& other) { | |
| if (this != &other) { | |
| mLock = std::move(other.mLock); | |
| mObj = std::move(other.mObj); | |
| mDefer = std::move(other.mDefer); | |
| other.mObj = nullptr; | |
| other.mDefer = nullptr; | |
| } | |
| return *this; | |
| } | |
| Guard(const Guard& other) = delete; | |
| Guard& operator=(const Guard& other) = delete; | |
| auto* Get() const { return Traits::GetObj(mObj); } | |
| private: | |
| using NonConstT = typename std::remove_const<T>::type; | |
| friend class MutexProtected<NonConstT, Guard, Traits>; | |
| // Currently need to explicitly list the notify types because we can't partially specialize | |
| // friend classes. | |
| friend class CondVarGuard<T, Traits, NotifyType::All>; | |
| friend class CondVarGuard<T, Traits, NotifyType::One>; | |
| friend class CondVarGuard<T, Traits, NotifyType::None>; | |
| typename Traits::template LockType<T> mLock; | |
| T* mObj = nullptr; | |
| class Defer* mDefer = nullptr; | |
| }; | |
| // CondVarGuard is a different guard class that internally holds a Guard, but provides additional | |
| // functionality w.r.t condition variables. Specifically, the non-const version of this Guard will | |
| // automatically call a notify function on the underlying condition variable so that calls to | |
| // |Wait*()| will unblock when |Pred| is true. | |
| template <typename T, typename Traits, NotifyType NotifyT = NotifyType::All> | |
| class CondVarGuard : public NonMovable, StackAllocated { | |
| public: | |
| // It's the programmer's burden to not save the pointer/reference and reuse it without the lock. | |
| auto* operator->() const { return mGuard.Get(); } | |
| auto& operator*() const { return *mGuard.Get(); } | |
| template <typename Predicate> | |
| void Wait(Predicate pred) { | |
| DAWN_ASSERT(mNotifyScope.cv); | |
| mNotifyScope.cv->wait(mGuard.mLock, [&] { return pred((*Get())); }); | |
| } | |
| template <typename Predicate> | |
| bool WaitFor(Nanoseconds timeout, Predicate pred) { | |
| DAWN_ASSERT(mNotifyScope.cv); | |
| if (timeout < kMaxDurationNanos) { | |
| return mNotifyScope.cv->wait_for( | |
| mGuard.mLock, std::chrono::nanoseconds(static_cast<uint64_t>(timeout)), | |
| [&] { return pred(*Get()); }); | |
| } else { | |
| Wait(pred); | |
| return true; | |
| } | |
| } | |
| protected: | |
| CondVarGuard(T* obj, Traits::MutexType& mutex, std::condition_variable* cv) | |
| : mGuard(obj, mutex), mNotifyScope(cv) {} | |
| auto* Get() const { return mGuard.Get(); } | |
| private: | |
| using NonConstT = typename std::remove_const<T>::type; | |
| friend class MutexCondVarProtected<NonConstT, CondVarGuard, Traits>; | |
| struct NotifyScopeBase : public StackAllocated { | |
| explicit NotifyScopeBase(std::condition_variable* cv) : cv(cv) { DAWN_ASSERT(cv); } | |
| std::condition_variable* cv = nullptr; | |
| }; | |
| template <NotifyType U> | |
| struct NotifyScope : NotifyScopeBase { | |
| using NotifyScopeBase::NotifyScopeBase; | |
| ~NotifyScope() { | |
| if constexpr (!std::is_const_v<T>) { | |
| if constexpr (U == NotifyType::All) { | |
| this->cv->notify_all(); | |
| } else if constexpr (U == NotifyType::One) { | |
| this->cv->notify_one(); | |
| } | |
| } | |
| } | |
| }; | |
| // Note that the Guard must be before the NotifyScope so that the C++ member destruction order | |
| // signals the condition variable before unlocking the mutex. This keeps all uses of the | |
| // condition variable guarded by the mutex. | |
| Guard<T, Traits> mGuard; | |
| NotifyScope<NotifyT> mNotifyScope; | |
| }; | |
| } // namespace detail | |
| // Wrapping class used for object members to ensure usage of the resource is protected with a mutex. | |
| // Example usage: | |
| // class Allocator { | |
| // public: | |
| // Allocation Allocate(); | |
| // void Deallocate(Allocation&); | |
| // }; | |
| // class AllocatorUser { | |
| // public: | |
| // void OnlyAllocate() { | |
| // auto allocation = mAllocator->Allocate(); | |
| // } | |
| // void AtomicAllocateDeallocate() { | |
| // // Operations: | |
| // // - acquire lock | |
| // // - Allocate, Deallocate | |
| // // - release lock | |
| // mAllocator.Use([](auto allocator) { | |
| // auto allocation = allocator->Allocate(); | |
| // allocator->Deallocate(allocation); | |
| // }); | |
| // } | |
| // void NonAtomicAllocateDeallocate() { | |
| // // Operations: | |
| // // - acquire lock, Allocate, release lock | |
| // // - acquire lock, Deallocate, release lock | |
| // auto allocation = mAllocator->Allocate(); | |
| // mAllocator->Deallocate(allocation); | |
| // } | |
| // private: | |
| // MutexProtected<Allocator> mAllocator; | |
| // }; | |
| template <typename T, | |
| template <typename, typename> class Guard = detail::Guard, | |
| typename Traits = detail::MutexProtectedTraits<T>> | |
| class MutexProtected { | |
| public: | |
| using Usage = Guard<T, Traits>; | |
| using ConstUsage = Guard<const T, Traits>; | |
| template <typename... Args> | |
| requires(sizeof...(Args) != 1 || | |
| !(std::is_same_v<std::decay_t<Args>, MutexProtected> && ...)) | |
| explicit(false) MutexProtected(Args&&... args) | |
| : mMutex(Traits::CreateMutex()), mObj(std::forward<Args>(args)...) {} | |
| virtual ~MutexProtected() = default; | |
| MutexProtected(const MutexProtected&) | |
| requires std::copy_constructible<typename Traits::MutexType> && std::copy_constructible<T> | |
| = default; | |
| MutexProtected& operator=(const MutexProtected&) | |
| requires std::is_copy_assignable_v<typename Traits::MutexType> && | |
| std::is_copy_assignable_v<T> | |
| = default; | |
| MutexProtected(MutexProtected&&) | |
| requires std::move_constructible<typename Traits::MutexType> && std::move_constructible<T> | |
| = default; | |
| MutexProtected& operator=(MutexProtected&&) | |
| requires std::is_move_assignable_v<typename Traits::MutexType> && | |
| std::is_move_assignable_v<T> | |
| = default; | |
| Usage operator->() { return Usage(&mObj, mMutex); } | |
| template <typename Fn> | |
| auto Use(Fn&& fn) { | |
| return fn(Usage(&mObj, mMutex)); | |
| } | |
| ConstUsage operator->() const { return ConstUsage(&mObj, mMutex); } | |
| template <typename Fn> | |
| auto ConstUse(Fn&& fn) const { | |
| return fn(ConstUsage(&mObj, mMutex)); | |
| } | |
| template <typename Fn> | |
| auto Use(Fn&& fn) const { | |
| return ConstUse(fn); | |
| } | |
| std::optional<Usage> TryUse() | |
| requires Traits::kSupportsTryLock | |
| { | |
| auto maybeLock = Traits::TryLock(mMutex); | |
| if (!maybeLock.has_value()) { | |
| return std::nullopt; | |
| } | |
| return Usage(&mObj, std::move(*maybeLock), nullptr); | |
| } | |
| template <typename Fn> | |
| auto UseWithDefer(Fn&& fn) { | |
| Defer defer; | |
| return fn(Usage(&mObj, mMutex, &defer)); | |
| } | |
| private: | |
| mutable Traits::MutexType mMutex; | |
| T mObj; | |
| }; | |
| // A moveable version of MutexProtected. | |
| template <typename T> | |
| using MutexRefProtected = MutexProtected<T, detail::Guard, detail::MutexRefProtectedTraits<T>>; | |
| // A recursive version of MutexProtected. | |
| template <typename T> | |
| using MutexRecursiveProtected = | |
| MutexProtected<T, detail::Guard, detail::MutexRecursiveProtectedTraits<T>>; | |
| // A read-write version of MutexProtected. | |
| template <typename T> | |
| using MutexRWProtected = MutexProtected<T, detail::Guard, detail::MutexRWProtectedTraits<T>>; | |
| // Wrapping class for object members to provide the protections with a mutex of a MutexProtected | |
| // with some additional helpers to allow waiting with a conditional variable as well. The general | |
| // usage should look the same as MutexProtected above, with additional usages like the following | |
| // example: | |
| // class Example { | |
| // public: | |
| // void Complete() { | |
| // mDone.Use([](auto done) { | |
| // // Do something | |
| // mDone = true; | |
| // }); | |
| // } | |
| // void WaitUntilDone() { | |
| // mDone.Use([](auto done) { | |
| // done.Wait([](auto& done) { return done; }); | |
| // }); | |
| // } | |
| // private: | |
| // MutexCondVarProtected<bool> mDone = false; | |
| // }; | |
| template <typename T, | |
| template <typename, typename, NotifyType> class Guard = detail::CondVarGuard, | |
| typename Traits = detail::MutexProtectedTraits<T>> | |
| class MutexCondVarProtected { | |
| public: | |
| using Usage = Guard<T, Traits, NotifyType::All>; | |
| using ConstUsage = Guard<const T, Traits, NotifyType::None>; | |
| template <typename... Args> | |
| requires(sizeof...(Args) != 1 || | |
| !(std::is_same_v<std::decay_t<Args>, MutexCondVarProtected> && ...)) | |
| explicit(false) MutexCondVarProtected(Args&&... args) | |
| : mMutex(Traits::CreateMutex()), mObj(std::forward<Args>(args)...) {} | |
| virtual ~MutexCondVarProtected() = default; | |
| MutexCondVarProtected(const MutexCondVarProtected&) | |
| requires std::copy_constructible<typename Traits::MutexType> && std::copy_constructible<T> | |
| = default; | |
| MutexCondVarProtected& operator=(const MutexCondVarProtected&) | |
| requires std::is_copy_assignable_v<typename Traits::MutexType> && | |
| std::is_copy_assignable_v<T> | |
| = default; | |
| MutexCondVarProtected(MutexCondVarProtected&&) | |
| requires std::move_constructible<typename Traits::MutexType> && std::move_constructible<T> | |
| = default; | |
| MutexCondVarProtected& operator=(MutexCondVarProtected&&) | |
| requires std::is_move_assignable_v<typename Traits::MutexType> && | |
| std::is_move_assignable_v<T> | |
| = default; | |
| Usage operator->() { return Usage(&mObj, mMutex, &mCv); } | |
| template <NotifyType NotifyT = NotifyType::All, typename Fn> | |
| auto Use(Fn&& fn) { | |
| return fn(Guard<T, Traits, NotifyT>(&mObj, mMutex, &mCv)); | |
| } | |
| // Note that unlike in MutexProtected where |Use| and |ConstUse| guarantee the lock for the | |
| // entire critical section, if a user calls |Wait| within |Fn|, the lock may be released and | |
| // reacquired in order for another thread to update the condition. | |
| ConstUsage operator->() const { return ConstUsage(&mObj, mMutex, &mCv); } | |
| template <typename Fn> | |
| auto ConstUse(Fn&& fn) const { | |
| return fn(ConstUsage(&mObj, mMutex, &mCv)); | |
| } | |
| template <typename Fn> | |
| auto Use(Fn&& fn) const { | |
| return ConstUse(fn); | |
| } | |
| private: | |
| mutable Traits::MutexType mMutex; | |
| mutable std::condition_variable mCv; | |
| T mObj; | |
| }; | |
| } // namespace dawn | |
| #endif // SRC_DAWN_COMMON_MUTEXPROTECTED_H_ |