1 // 2 // Copyright 2023 The ANGLE Project Authors. All rights reserved. 3 // Use of this source code is governed by a BSD-style license that can be 4 // found in the LICENSE file. 5 // 6 // ContextMutex.h: Classes for protecting Context access and EGLImage siblings. 7 8 #ifndef LIBANGLE_CONTEXT_MUTEX_H_ 9 #define LIBANGLE_CONTEXT_MUTEX_H_ 10 11 #include <atomic> 12 13 #include "common/debug.h" 14 15 namespace gl 16 { 17 class Context; 18 } 19 20 namespace egl 21 { 22 #if defined(ANGLE_ENABLE_CONTEXT_MUTEX) 23 constexpr bool kIsContextMutexEnabled = true; 24 #else 25 constexpr bool kIsContextMutexEnabled = false; 26 #endif 27 28 // Use standard mutex for now 29 using ContextMutexType = std::mutex; 30 31 class ContextMutex final : angle::NonCopyable 32 { 33 public: 34 explicit ContextMutex(ContextMutex *root = nullptr); 35 // For "leaf" mutex its "root" must be locked during destructor call. 36 ~ContextMutex(); 37 38 // Merges mutexes so they work as one. 39 // At the end, only single "root" mutex will be locked. 40 // Does nothing if two mutexes are the same or already merged (have same "root" mutex). 41 static void Merge(ContextMutex *lockedMutex, ContextMutex *otherMutex); 42 43 // Returns current "root" mutex. 44 // Warning! Result is only stable if mutex is locked, while may change any time if unlocked. 45 // May be used to compare against already locked "root" mutex. getRoot()46 ANGLE_INLINE ContextMutex *getRoot() { return mRoot.load(std::memory_order_relaxed); } getRoot()47 ANGLE_INLINE const ContextMutex *getRoot() const 48 { 49 return mRoot.load(std::memory_order_relaxed); 50 } 51 52 // Below group of methods are not thread safe and must be protected by "this" mutex instance. addRef()53 ANGLE_INLINE void addRef() { ++mRefCount; } release()54 ANGLE_INLINE void release() { release(UnlockBehaviour::kDoNotUnlock); } 55 // Must be only called on "root" mutex. releaseAndUnlock()56 ANGLE_INLINE void releaseAndUnlock() { release(UnlockBehaviour::kUnlock); } isReferenced()57 ANGLE_INLINE bool isReferenced() const { return mRefCount > 0; } 58 59 bool try_lock(); 60 void lock(); 61 void unlock(); 62 63 private: 64 enum class UnlockBehaviour 65 { 66 kDoNotUnlock, 67 kUnlock 68 }; 69 70 bool tryLockImpl(); 71 void lockImpl(); 72 void unlockImpl(); 73 74 // All methods below must be protected by "this" mutex ("stable root" in "this" instance). 75 76 void setNewRoot(ContextMutex *newRoot); 77 void addLeaf(ContextMutex *leaf); 78 void removeLeaf(ContextMutex *leaf); 79 80 void release(UnlockBehaviour unlockBehaviour); 81 82 private: 83 // mRoot and mLeaves tree structure details: 84 // - used to implement primary functionality of this class; 85 // - initially, all mutexes are "root"s; 86 // - "root" mutex has "mRoot == this"; 87 // - "root" mutex stores unreferenced pointers to all its leaves (used in merging); 88 // - "leaf" mutex holds reference (addRef) to the current "root" mutex in the mRoot; 89 // - "leaf" mutex has empty mLeaves; 90 // - "leaf" mutex can't become a "root" mutex; 91 // - before locking the mMutex, "this" is an "unstable root" or a "leaf"; 92 // - the implementation always locks mRoot's mMutex ("unstable root"); 93 // - if after locking the mMutex "mRoot != this", then "this" is/become a "leaf"; 94 // - otherwise, "this" is a locked "stable root" - lock is successful. 95 96 // mOldRoots is used to solve a particular problem (below example does not use mRank): 97 // - have "leaf" mutex_2 with a reference to mutex_1 "root"; 98 // - the mutex_1 has no other references (only in the mutex_2); 99 // - have other mutex_3 "root"; 100 // - mutex_1 pointer is cached on the stack during locking of mutex_2 (thread A); 101 // - merge mutex_3 and mutex_2 (thread B): 102 // * now "leaf" mutex_2 stores reference to mutex_3 "root"; 103 // * old "root" mutex_1 becomes a "leaf" of mutex_3; 104 // * old "root" mutex_1 has no references and gets destroyed. 105 // - invalid pointer to destroyed mutex_1 stored on the stack and in the mLeaves of mutex_3; 106 // - to fix this problem, references to old "root"s are kept in the mOldRoots vector. 107 108 // mRank is used to fix a problem of indefinite grows of mOldRoots: 109 // - merge mutex_2 and mutex_1 -> mutex_2 is "root" of mutex_1 (mOldRoots == 0); 110 // - destroy mutex_2; 111 // - merge mutex_3 and mutex_1 -> mutex_3 is "root" of mutex_1 (mOldRoots == 1); 112 // - destroy mutex_3; 113 // - merge mutex_4 and mutex_1 -> mutex_4 is "root" of mutex_1 (mOldRoots == 2); 114 // - destroy mutex_4; 115 // - continuing this pattern can lead to indefinite grows of mOldRoots, while pick number of 116 // mutexes is only 2. 117 // Fix details using mRank: 118 // - initially "mRank == 0" and only relevant for "root" mutexes; 119 // - merging mutexes with equal mRank of their "root"s, will use first (lockedMutex) "root" 120 // mutex as a new "root" and increase its mRank by 1; 121 // - otherwise, "root" mutex with a highest rank will be used without changing the mRank; 122 // - this way, "stronger" (with a higher mRank) "root" mutex will "protect" its "leaves" from 123 // "mRoot" replacement and therefore - mOldRoots grows. 124 // Lets look at the problematic pattern with the mRank: 125 // - merge mutex_2 and mutex_1 -> mutex_2 is "root" (mRank == 1) of mutex_1 (mOldRoots == 0); 126 // - destroy mutex_2; 127 // - merge mutex_3 and mutex_1 -> mutex_2 is "root" (mRank == 1) of mutex_3 (mOldRoots == 0); 128 // - destroy mutex_3; 129 // - merge mutex_4 and mutex_1 -> mutex_2 is "root" (mRank == 1) of mutex_4 (mOldRoots == 0); 130 // - destroy mutex_4; 131 // - no mOldRoots grows at all; 132 // - minumum number of mutexes to reach mOldRoots size of N => 2^(N+1). 133 134 std::atomic<ContextMutex *> mRoot; 135 ContextMutexType mMutex; 136 // Used when ASSERT() and/or recursion are/is enabled. 137 std::atomic<angle::ThreadId> mOwnerThreadId; 138 // Used only when recursion is enabled. 139 uint32_t mLockLevel; 140 size_t mRefCount; 141 142 std::set<ContextMutex *> mLeaves; 143 std::vector<ContextMutex *> mOldRoots; 144 uint32_t mRank; 145 }; 146 147 // Prevents destruction while locked, uses mMutex to protect addRef()/releaseAndUnlock() calls. 148 class [[nodiscard]] ScopedContextMutexAddRefLock final : angle::NonCopyable 149 { 150 public: 151 ANGLE_INLINE ScopedContextMutexAddRefLock() = default; ScopedContextMutexAddRefLock(ContextMutex & mutex)152 ANGLE_INLINE explicit ScopedContextMutexAddRefLock(ContextMutex &mutex) { lock(&mutex); } ScopedContextMutexAddRefLock(ContextMutex * mutex)153 ANGLE_INLINE ScopedContextMutexAddRefLock(ContextMutex *mutex) 154 { 155 if (mutex != nullptr) 156 { 157 lock(mutex); 158 } 159 } ~ScopedContextMutexAddRefLock()160 ANGLE_INLINE ~ScopedContextMutexAddRefLock() 161 { 162 if (mMutex != nullptr) 163 { 164 mMutex->releaseAndUnlock(); 165 } 166 } 167 168 private: 169 void lock(ContextMutex *mutex); 170 171 private: 172 ContextMutex *mMutex = nullptr; 173 }; 174 175 class [[nodiscard]] ScopedContextMutexLock final 176 { 177 public: 178 ANGLE_INLINE ScopedContextMutexLock() = default; ScopedContextMutexLock(ContextMutex & mutex)179 ANGLE_INLINE explicit ScopedContextMutexLock(ContextMutex &mutex) : mMutex(&mutex) 180 { 181 mutex.lock(); 182 } ScopedContextMutexLock(ContextMutex * mutex)183 ANGLE_INLINE ScopedContextMutexLock(ContextMutex *mutex) : mMutex(mutex) 184 { 185 if (ANGLE_LIKELY(mutex != nullptr)) 186 { 187 mutex->lock(); 188 } 189 } ~ScopedContextMutexLock()190 ANGLE_INLINE ~ScopedContextMutexLock() 191 { 192 if (ANGLE_LIKELY(mMutex != nullptr)) 193 { 194 mMutex->unlock(); 195 } 196 } 197 ScopedContextMutexLock(ScopedContextMutexLock && other)198 ANGLE_INLINE ScopedContextMutexLock(ScopedContextMutexLock &&other) : mMutex(other.mMutex) 199 { 200 other.mMutex = nullptr; 201 } 202 ANGLE_INLINE ScopedContextMutexLock &operator=(ScopedContextMutexLock &&other) 203 { 204 std::swap(mMutex, other.mMutex); 205 return *this; 206 } 207 208 private: 209 ContextMutex *mMutex = nullptr; 210 }; 211 212 } // namespace egl 213 214 #endif // LIBANGLE_CONTEXT_MUTEX_H_ 215