xref: /aosp_15_r20/external/angle/src/libANGLE/ContextMutex.h (revision 8975f5c5ed3d1c378011245431ada316dfb6f244)
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