• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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