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 // SharedContextMutex.h: Classes for protecting Shared Context access and EGLImage siblings. 7 8 #ifndef LIBANGLE_SHARED_CONTEXT_MUTEX_H_ 9 #define LIBANGLE_SHARED_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_SHARED_CONTEXT_MUTEX) 23 constexpr bool kIsSharedContextMutexEnabled = true; 24 #else 25 constexpr bool kIsSharedContextMutexEnabled = false; 26 #endif 27 28 class ContextMutex : angle::NonCopyable 29 { 30 public: 31 // Below group of methods are not thread safe and requires external synchronization. 32 // Note: since release*() may call onDestroy(), additional synchronization requirements may be 33 // enforced by concrete implementations. addRef()34 ANGLE_INLINE void addRef() { ++mRefCount; } release()35 ANGLE_INLINE void release() { release(UnlockBehaviour::kNotUnlock); } releaseAndUnlock()36 ANGLE_INLINE void releaseAndUnlock() { release(UnlockBehaviour::kUnlock); } isReferenced()37 ANGLE_INLINE bool isReferenced() const { return mRefCount > 0; } 38 39 virtual bool try_lock() = 0; 40 virtual void lock() = 0; 41 virtual void unlock() = 0; 42 43 protected: 44 virtual ~ContextMutex(); 45 46 enum class UnlockBehaviour 47 { 48 kNotUnlock, 49 kUnlock 50 }; 51 52 void release(UnlockBehaviour unlockBehaviour); 53 54 virtual void onDestroy(UnlockBehaviour unlockBehaviour); 55 56 protected: 57 size_t mRefCount = 0; 58 }; 59 60 struct ContextMutexMayBeNullTag final 61 {}; 62 constexpr ContextMutexMayBeNullTag kContextMutexMayBeNull; 63 64 // Prevents destruction while locked, uses mMutex to protect addRef()/releaseAndUnlock() calls. 65 class [[nodiscard]] ScopedContextMutexAddRefLock final : angle::NonCopyable 66 { 67 public: 68 ANGLE_INLINE ScopedContextMutexAddRefLock() = default; ScopedContextMutexAddRefLock(ContextMutex * mutex)69 ANGLE_INLINE explicit ScopedContextMutexAddRefLock(ContextMutex *mutex) { lock(mutex); } ScopedContextMutexAddRefLock(ContextMutex * mutex,ContextMutexMayBeNullTag)70 ANGLE_INLINE ScopedContextMutexAddRefLock(ContextMutex *mutex, ContextMutexMayBeNullTag) 71 { 72 if (mutex != nullptr) 73 { 74 lock(mutex); 75 } 76 } ~ScopedContextMutexAddRefLock()77 ANGLE_INLINE ~ScopedContextMutexAddRefLock() 78 { 79 if (mMutex != nullptr) 80 { 81 mMutex->releaseAndUnlock(); 82 } 83 } 84 85 private: 86 void lock(ContextMutex *mutex); 87 88 private: 89 ContextMutex *mMutex = nullptr; 90 }; 91 92 class [[nodiscard]] ScopedContextMutexLock final 93 { 94 public: 95 ANGLE_INLINE ScopedContextMutexLock() = default; ScopedContextMutexLock(ContextMutex * mutex,gl::Context * context)96 ANGLE_INLINE ScopedContextMutexLock(ContextMutex *mutex, gl::Context *context) 97 : mMutex(mutex), mContext(context) 98 { 99 ASSERT(mutex != nullptr); 100 ASSERT(context != nullptr); 101 mutex->lock(); 102 } ScopedContextMutexLock(ContextMutex * mutex,gl::Context * context,ContextMutexMayBeNullTag)103 ANGLE_INLINE ScopedContextMutexLock(ContextMutex *mutex, 104 gl::Context *context, 105 ContextMutexMayBeNullTag) 106 : mMutex(mutex), mContext(context) 107 { 108 if (ANGLE_LIKELY(mutex != nullptr)) 109 { 110 ASSERT(context != nullptr); 111 mutex->lock(); 112 } 113 } ~ScopedContextMutexLock()114 ANGLE_INLINE ~ScopedContextMutexLock() 115 { 116 if (ANGLE_LIKELY(mMutex != nullptr)) 117 { 118 ASSERT(IsContextMutexStateConsistent(mContext)); 119 mMutex->unlock(); 120 } 121 } 122 ScopedContextMutexLock(ScopedContextMutexLock && other)123 ANGLE_INLINE ScopedContextMutexLock(ScopedContextMutexLock &&other) 124 : mMutex(other.mMutex), mContext(other.mContext) 125 { 126 other.mMutex = nullptr; 127 } 128 ANGLE_INLINE ScopedContextMutexLock &operator=(ScopedContextMutexLock &&other) 129 { 130 std::swap(mMutex, other.mMutex); 131 mContext = other.mContext; 132 return *this; 133 } 134 135 private: 136 static bool IsContextMutexStateConsistent(gl::Context *context); 137 138 private: 139 ContextMutex *mMutex = nullptr; 140 gl::Context *mContext = nullptr; 141 }; 142 143 // Mutex may be locked only by a single thread. Other threads may only check the status. 144 class SingleContextMutex final : public ContextMutex 145 { 146 public: isLocked(std::memory_order order)147 ANGLE_INLINE bool isLocked(std::memory_order order) const { return mState.load(order) > 0; } 148 149 // ContextMutex 150 bool try_lock() override; 151 void lock() override; 152 void unlock() override; 153 154 private: 155 std::atomic_int mState{0}; 156 }; 157 158 // Note: onDestroy() method must be protected by "this" mutex, since onDestroy() is called from 159 // release*() methods, these methods must also be protected by "this" mutex. 160 template <class Mutex> 161 class SharedContextMutex final : public ContextMutex 162 { 163 public: 164 SharedContextMutex(); 165 ~SharedContextMutex() override; 166 167 // Merges mutexes so they work as one. 168 // At the end, only single "root" mutex will be locked. 169 // Does nothing if two mutexes are the same or already merged (have same "root" mutex). 170 // Note: synchronization requirements for addRef()/release*() calls for merged mutexes are 171 // the same as for the single unmerged mutex. For example: can't call at the same time 172 // mutexA.addRef() and mutexB.release() if they are merged. 173 static void Merge(SharedContextMutex *lockedMutex, SharedContextMutex *otherMutex); 174 175 // Returns current "root" mutex. 176 // Warning! Result is only stable if mutex is locked, while may change any time if unlocked. 177 // May be used to compare against already locked "root" mutex. getRoot()178 ANGLE_INLINE SharedContextMutex *getRoot() { return mRoot.load(std::memory_order_relaxed); } 179 180 // ContextMutex 181 bool try_lock() override; 182 void lock() override; 183 void unlock() override; 184 185 private: 186 SharedContextMutex *doTryLock(); 187 SharedContextMutex *doLock(); 188 void doUnlock(); 189 190 // All methods below must be protected by "this" mutex ("stable root" in "this" instance). 191 192 void setNewRoot(SharedContextMutex *newRoot); 193 void addLeaf(SharedContextMutex *leaf); 194 void removeLeaf(SharedContextMutex *leaf); 195 196 // ContextMutex 197 void onDestroy(UnlockBehaviour unlockBehaviour) override; 198 199 private: 200 Mutex mMutex; 201 // Used when ASSERT() and/or recursion are/is enabled. 202 std::atomic<angle::ThreadId> mOwnerThreadId; 203 // Used only when recursion is enabled. 204 uint32_t mLockLevel; 205 206 // mRoot and mLeaves tree structure details: 207 // - used to implement primary functionality of this class; 208 // - initially, all mutexes are "root"s; 209 // - "root" mutex has "mRoot == this"; 210 // - "root" mutex stores unreferenced pointers to all its leaves (used in merging); 211 // - "leaf" mutex holds reference (addRef) to the current "root" mutex in the mRoot; 212 // - "leaf" mutex has empty mLeaves; 213 // - "leaf" mutex can't become a "root" mutex; 214 // - before locking the mMutex, "this" is an "unstable root" or a "leaf"; 215 // - the implementation always locks mRoot's mMutex ("unstable root"); 216 // - if after locking the mMutex "mRoot != this", then "this" is/become a "leaf"; 217 // - otherwise, "this" is a locked "stable root" - lock is successful. 218 std::atomic<SharedContextMutex *> mRoot; 219 std::set<SharedContextMutex *> mLeaves; 220 221 // mOldRoots is used to solve a particular problem (below example does not use mRank): 222 // - have "leaf" mutex_2 with a reference to mutex_1 "root"; 223 // - the mutex_1 has no other references (only in the mutex_2); 224 // - have other mutex_3 "root"; 225 // - mutex_1 pointer is cached on the stack during locking of mutex_2 (thread A); 226 // - merge mutex_3 and mutex_2 (thread B): 227 // * now "leaf" mutex_2 stores reference to mutex_3 "root"; 228 // * old "root" mutex_1 becomes a "leaf" of mutex_3; 229 // * old "root" mutex_1 has no references and gets destroyed. 230 // - invalid pointer to destroyed mutex_1 stored on the stack and in the mLeaves of mutex_3; 231 // - to fix this problem, references to old "root"s are kept in the mOldRoots vector. 232 std::vector<SharedContextMutex *> mOldRoots; 233 234 // mRank is used to fix a problem of indefinite grows of mOldRoots: 235 // - merge mutex_2 and mutex_1 -> mutex_2 is "root" of mutex_1 (mOldRoots == 0); 236 // - destroy mutex_2; 237 // - merge mutex_3 and mutex_1 -> mutex_3 is "root" of mutex_1 (mOldRoots == 1); 238 // - destroy mutex_3; 239 // - merge mutex_4 and mutex_1 -> mutex_4 is "root" of mutex_1 (mOldRoots == 2); 240 // - destroy mutex_4; 241 // - continuing this pattern can lead to indefinite grows of mOldRoots, while pick number of 242 // mutexes is only 2. 243 // Fix details using mRank: 244 // - initially "mRank == 0" and only relevant for "root" mutexes; 245 // - merging mutexes with equal mRank of their "root"s, will use first (lockedMutex) "root" 246 // mutex as a new "root" and increase its mRank by 1; 247 // - otherwise, "root" mutex with a highest rank will be used without changing the mRank; 248 // - this way, "stronger" (with a higher mRank) "root" mutex will "protect" its "leaves" from 249 // "mRoot" replacement and therefore - mOldRoots grows. 250 // Lets look at the problematic pattern with the mRank: 251 // - merge mutex_2 and mutex_1 -> mutex_2 is "root" (mRank == 1) of mutex_1 (mOldRoots == 0); 252 // - destroy mutex_2; 253 // - merge mutex_3 and mutex_1 -> mutex_2 is "root" (mRank == 1) of mutex_3 (mOldRoots == 0); 254 // - destroy mutex_3; 255 // - merge mutex_4 and mutex_1 -> mutex_2 is "root" (mRank == 1) of mutex_4 (mOldRoots == 0); 256 // - destroy mutex_4; 257 // - no mOldRoots grows at all; 258 // - minumum number of mutexes to reach mOldRoots size of N => 2^(N+1). 259 uint32_t mRank; 260 }; 261 262 class ContextMutexManager 263 { 264 public: 265 virtual ~ContextMutexManager() = default; 266 267 virtual ContextMutex *create() = 0; 268 virtual void merge(ContextMutex *lockedMutex, ContextMutex *otherMutex) = 0; 269 virtual ContextMutex *getRootMutex(ContextMutex *mutex) = 0; 270 }; 271 272 template <class Mutex> 273 class SharedContextMutexManager final : public ContextMutexManager 274 { 275 public: 276 ContextMutex *create() override; 277 void merge(ContextMutex *lockedMutex, ContextMutex *otherMutex) override; 278 ContextMutex *getRootMutex(ContextMutex *mutex) override; 279 }; 280 281 } // namespace egl 282 283 #endif // LIBANGLE_SHARED_CONTEXT_MUTEX_H_ 284