• 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.cpp: Classes for protecting Shared Context access and EGLImage siblings.
7 
8 #include "libANGLE/SharedContextMutex.h"
9 
10 #include "common/system_utils.h"
11 #include "libANGLE/Context.h"
12 
13 namespace egl
14 {
15 
16 namespace
17 {
CheckThreadIdCurrent(const std::atomic<angle::ThreadId> & threadId,angle::ThreadId * currentThreadIdOut)18 [[maybe_unused]] bool CheckThreadIdCurrent(const std::atomic<angle::ThreadId> &threadId,
19                                            angle::ThreadId *currentThreadIdOut)
20 {
21     *currentThreadIdOut = angle::GetCurrentThreadId();
22     return (threadId.load(std::memory_order_relaxed) == *currentThreadIdOut);
23 }
24 
TryUpdateThreadId(std::atomic<angle::ThreadId> * threadId,angle::ThreadId oldThreadId,angle::ThreadId newThreadId)25 [[maybe_unused]] bool TryUpdateThreadId(std::atomic<angle::ThreadId> *threadId,
26                                         angle::ThreadId oldThreadId,
27                                         angle::ThreadId newThreadId)
28 {
29     const bool ok = (threadId->load(std::memory_order_relaxed) == oldThreadId);
30     if (ok)
31     {
32         threadId->store(newThreadId, std::memory_order_relaxed);
33     }
34     return ok;
35 }
36 }  // namespace
37 
38 // ContextMutex
~ContextMutex()39 ContextMutex::~ContextMutex()
40 {
41     ASSERT(mRefCount == 0);
42 }
43 
onDestroy(UnlockBehaviour unlockBehaviour)44 void ContextMutex::onDestroy(UnlockBehaviour unlockBehaviour)
45 {
46     if (unlockBehaviour == UnlockBehaviour::kUnlock)
47     {
48         unlock();
49     }
50 }
51 
release(UnlockBehaviour unlockBehaviour)52 void ContextMutex::release(UnlockBehaviour unlockBehaviour)
53 {
54     ASSERT(isReferenced());
55     if (--mRefCount == 0)
56     {
57         onDestroy(unlockBehaviour);
58         delete this;
59     }
60     else if (unlockBehaviour == UnlockBehaviour::kUnlock)
61     {
62         unlock();
63     }
64 }
65 
66 // ScopedContextMutexAddRefLock
lock(ContextMutex * mutex)67 void ScopedContextMutexAddRefLock::lock(ContextMutex *mutex)
68 {
69     ASSERT(mutex != nullptr);
70     ASSERT(mMutex == nullptr);
71     mMutex = mutex;
72     // lock() before addRef() - using mMutex as synchronization
73     mMutex->lock();
74     // This lock alone must not cause mutex destruction
75     ASSERT(mMutex->isReferenced());
76     mMutex->addRef();
77 }
78 
79 // ScopedContextMutexLock
IsContextMutexStateConsistent(gl::Context * context)80 bool ScopedContextMutexLock::IsContextMutexStateConsistent(gl::Context *context)
81 {
82     ASSERT(context != nullptr);
83     return context->isContextMutexStateConsistent();
84 }
85 
86 // SingleContextMutex
try_lock()87 bool SingleContextMutex::try_lock()
88 {
89     UNREACHABLE();
90     return false;
91 }
92 
93 #if defined(ANGLE_ENABLE_CONTEXT_MUTEX_RECURSION)
lock()94 void SingleContextMutex::lock()
95 {
96     const int oldValue = mState.fetch_add(1, std::memory_order_relaxed);
97     ASSERT(oldValue >= 0);
98 }
99 
unlock()100 void SingleContextMutex::unlock()
101 {
102     const int oldValue = mState.fetch_sub(1, std::memory_order_release);
103     ASSERT(oldValue > 0);
104 }
105 #else
lock()106 void SingleContextMutex::lock()
107 {
108     ASSERT(!isLocked(std::memory_order_relaxed));
109     mState.store(1, std::memory_order_relaxed);
110 }
111 
unlock()112 void SingleContextMutex::unlock()
113 {
114     ASSERT(isLocked(std::memory_order_relaxed));
115     mState.store(0, std::memory_order_release);
116 }
117 #endif
118 
119 // SharedContextMutex
120 template <class Mutex>
try_lock()121 bool SharedContextMutex<Mutex>::try_lock()
122 {
123     SharedContextMutex *const root = getRoot();
124     return (root->doTryLock() != nullptr);
125 }
126 
127 template <class Mutex>
lock()128 void SharedContextMutex<Mutex>::lock()
129 {
130     SharedContextMutex *const root = getRoot();
131     (void)root->doLock();
132 }
133 
134 template <class Mutex>
unlock()135 void SharedContextMutex<Mutex>::unlock()
136 {
137     SharedContextMutex *const root = getRoot();
138     // "root" is currently locked so "root->getRoot()" will return stable result.
139     ASSERT(root == root->getRoot());
140     root->doUnlock();
141 }
142 
143 #if defined(ANGLE_ENABLE_CONTEXT_MUTEX_RECURSION)
144 template <class Mutex>
doTryLock()145 ANGLE_INLINE SharedContextMutex<Mutex> *SharedContextMutex<Mutex>::doTryLock()
146 {
147     const angle::ThreadId threadId = angle::GetCurrentThreadId();
148     if (ANGLE_UNLIKELY(!mMutex.try_lock()))
149     {
150         if (ANGLE_UNLIKELY(mOwnerThreadId.load(std::memory_order_relaxed) == threadId))
151         {
152             ASSERT(this == getRoot());
153             ASSERT(mLockLevel > 0);
154             ++mLockLevel;
155             return this;
156         }
157         return nullptr;
158     }
159     ASSERT(mOwnerThreadId.load(std::memory_order_relaxed) == angle::InvalidThreadId());
160     ASSERT(mLockLevel == 0);
161     SharedContextMutex *const root = getRoot();
162     if (ANGLE_UNLIKELY(this != root))
163     {
164         // Unlock, so only the "stable root" mutex remains locked
165         mMutex.unlock();
166         SharedContextMutex *const lockedRoot = root->doTryLock();
167         ASSERT(lockedRoot == nullptr || lockedRoot == getRoot());
168         return lockedRoot;
169     }
170     mOwnerThreadId.store(threadId, std::memory_order_relaxed);
171     mLockLevel = 1;
172     return this;
173 }
174 
175 template <class Mutex>
doLock()176 ANGLE_INLINE SharedContextMutex<Mutex> *SharedContextMutex<Mutex>::doLock()
177 {
178     const angle::ThreadId threadId = angle::GetCurrentThreadId();
179     if (ANGLE_UNLIKELY(!mMutex.try_lock()))
180     {
181         if (ANGLE_UNLIKELY(mOwnerThreadId.load(std::memory_order_relaxed) == threadId))
182         {
183             ASSERT(this == getRoot());
184             ASSERT(mLockLevel > 0);
185             ++mLockLevel;
186             return this;
187         }
188         mMutex.lock();
189     }
190     ASSERT(mOwnerThreadId.load(std::memory_order_relaxed) == angle::InvalidThreadId());
191     ASSERT(mLockLevel == 0);
192     SharedContextMutex *const root = getRoot();
193     if (ANGLE_UNLIKELY(this != root))
194     {
195         // Unlock, so only the "stable root" mutex remains locked
196         mMutex.unlock();
197         SharedContextMutex *const lockedRoot = root->doLock();
198         ASSERT(lockedRoot == getRoot());
199         return lockedRoot;
200     }
201     mOwnerThreadId.store(threadId, std::memory_order_relaxed);
202     mLockLevel = 1;
203     return this;
204 }
205 
206 template <class Mutex>
doUnlock()207 ANGLE_INLINE void SharedContextMutex<Mutex>::doUnlock()
208 {
209     ASSERT(mOwnerThreadId.load(std::memory_order_relaxed) == angle::GetCurrentThreadId());
210     ASSERT(mLockLevel > 0);
211     if (ANGLE_LIKELY(--mLockLevel == 0))
212     {
213         mOwnerThreadId.store(angle::InvalidThreadId(), std::memory_order_relaxed);
214         mMutex.unlock();
215     }
216 }
217 #else
218 template <class Mutex>
doTryLock()219 ANGLE_INLINE SharedContextMutex<Mutex> *SharedContextMutex<Mutex>::doTryLock()
220 {
221     angle::ThreadId currentThreadId;
222     ASSERT(!CheckThreadIdCurrent(mOwnerThreadId, &currentThreadId));
223     if (mMutex.try_lock())
224     {
225         SharedContextMutex *const root = getRoot();
226         if (ANGLE_UNLIKELY(this != root))
227         {
228             // Unlock, so only the "stable root" mutex remains locked
229             mMutex.unlock();
230             SharedContextMutex *const lockedRoot = root->doTryLock();
231             ASSERT(lockedRoot == nullptr || lockedRoot == getRoot());
232             return lockedRoot;
233         }
234         ASSERT(TryUpdateThreadId(&mOwnerThreadId, angle::InvalidThreadId(), currentThreadId));
235         return this;
236     }
237     return nullptr;
238 }
239 
240 template <class Mutex>
doLock()241 ANGLE_INLINE SharedContextMutex<Mutex> *SharedContextMutex<Mutex>::doLock()
242 {
243     angle::ThreadId currentThreadId;
244     ASSERT(!CheckThreadIdCurrent(mOwnerThreadId, &currentThreadId));
245     mMutex.lock();
246     SharedContextMutex *const root = getRoot();
247     if (ANGLE_UNLIKELY(this != root))
248     {
249         // Unlock, so only the "stable root" mutex remains locked
250         mMutex.unlock();
251         SharedContextMutex *const lockedRoot = root->doLock();
252         ASSERT(lockedRoot == getRoot());
253         return lockedRoot;
254     }
255     ASSERT(TryUpdateThreadId(&mOwnerThreadId, angle::InvalidThreadId(), currentThreadId));
256     return this;
257 }
258 
259 template <class Mutex>
doUnlock()260 ANGLE_INLINE void SharedContextMutex<Mutex>::doUnlock()
261 {
262     ASSERT(
263         TryUpdateThreadId(&mOwnerThreadId, angle::GetCurrentThreadId(), angle::InvalidThreadId()));
264     mMutex.unlock();
265 }
266 #endif
267 
268 template <class Mutex>
SharedContextMutex()269 SharedContextMutex<Mutex>::SharedContextMutex()
270     : mOwnerThreadId(angle::InvalidThreadId()), mLockLevel(0), mRoot(this), mRank(0)
271 {}
272 
273 template <class Mutex>
~SharedContextMutex()274 SharedContextMutex<Mutex>::~SharedContextMutex()
275 {
276     ASSERT(mLockLevel == 0);
277     ASSERT(this == getRoot());
278     ASSERT(mOldRoots.empty());
279     ASSERT(mLeaves.empty());
280 }
281 
282 template <class Mutex>
Merge(SharedContextMutex * lockedMutex,SharedContextMutex * otherMutex)283 void SharedContextMutex<Mutex>::Merge(SharedContextMutex *lockedMutex,
284                                       SharedContextMutex *otherMutex)
285 {
286     ASSERT(lockedMutex != nullptr);
287     ASSERT(otherMutex != nullptr);
288 
289     // Since lockedMutex is locked, its "root" pointer is stable.
290     SharedContextMutex *lockedRoot      = lockedMutex->getRoot();
291     SharedContextMutex *otherLockedRoot = nullptr;
292 
293     // Mutex merging will update the structure of both mutexes, therefore both mutexes must be
294     // locked before continuing. First mutex is already locked, need to lock the other mutex.
295     // Because other thread may perform merge with same mutexes reversed, we can't simply lock
296     // otherMutex - this may cause a deadlock. Additionally, otherMutex may have same "root" (same
297     // mutex or already merged), not only merging is unnecessary, but locking otherMutex will
298     // guarantee a deadlock.
299 
300     for (;;)
301     {
302         // First, check that "root" of otherMutex is the same as "root" of lockedMutex.
303         // lockedRoot is stable by definition and it is safe to compare with "unstable root".
304         SharedContextMutex *otherRoot = otherMutex->getRoot();
305         if (otherRoot == lockedRoot)
306         {
307             // Do nothing if two mutexes are the same/merged.
308             return;
309         }
310         // Second, try to lock otherMutex "root" (can't use lock()/doLock(), see above comment).
311         otherLockedRoot = otherRoot->doTryLock();
312         if (otherLockedRoot != nullptr)
313         {
314             // otherMutex "root" can't become lockedMutex "root". For that to happen, lockedMutex
315             // must be locked from some other thread first, which is impossible, since it is already
316             // locked by this thread.
317             ASSERT(otherLockedRoot != lockedRoot);
318             // Lock is successful. Both mutexes are locked - can proceed with the merge...
319             break;
320         }
321         // Lock was unsuccessful - unlock and retry...
322         // May use "doUnlock()" because lockedRoot is a "stable root" mutex.
323         // Note: lock will be preserved in case of the recursive lock.
324         lockedRoot->doUnlock();
325         // Sleep random amount to allow one of the thread acquire the lock next time...
326         std::this_thread::sleep_for(std::chrono::microseconds(rand() % 91 + 10));
327         // Because lockedMutex was unlocked, its "root" might have been changed. Below line will
328         // reacquire the lock and update lockedRoot pointer.
329         lockedRoot = lockedRoot->getRoot()->doLock();
330     }
331 
332     // Mutexes that are not reference counted is not supported.
333     ASSERT(lockedRoot->isReferenced());
334     ASSERT(otherLockedRoot->isReferenced());
335 
336     // Decide the new "root". See mRank comment for more details...
337 
338     SharedContextMutex *oldRoot = otherLockedRoot;
339     SharedContextMutex *newRoot = lockedRoot;
340 
341     if (oldRoot->mRank > newRoot->mRank)
342     {
343         std::swap(oldRoot, newRoot);
344     }
345     else if (oldRoot->mRank == newRoot->mRank)
346     {
347         ++newRoot->mRank;
348     }
349 
350     // Update the structure
351     for (SharedContextMutex *const leaf : oldRoot->mLeaves)
352     {
353         ASSERT(leaf->getRoot() == oldRoot);
354         leaf->setNewRoot(newRoot);
355     }
356     oldRoot->mLeaves.clear();
357     oldRoot->setNewRoot(newRoot);
358 
359     // Leave only the "merged" mutex locked. "oldRoot" already merged, need to use "doUnlock()"
360     oldRoot->doUnlock();
361 
362     // Merge from recursive lock is unexpected. Handle such cases anyway to be safe.
363     while (oldRoot->mLockLevel > 0)
364     {
365         newRoot->doLock();
366         oldRoot->doUnlock();
367     }
368 }
369 
370 template <class Mutex>
setNewRoot(SharedContextMutex * newRoot)371 void SharedContextMutex<Mutex>::setNewRoot(SharedContextMutex *newRoot)
372 {
373     SharedContextMutex *const oldRoot = getRoot();
374 
375     ASSERT(newRoot != oldRoot);
376     mRoot.store(newRoot, std::memory_order_relaxed);
377     newRoot->addRef();
378 
379     newRoot->addLeaf(this);
380 
381     if (oldRoot != this)
382     {
383         mOldRoots.emplace_back(oldRoot);
384     }
385 }
386 
387 template <class Mutex>
addLeaf(SharedContextMutex * leaf)388 void SharedContextMutex<Mutex>::addLeaf(SharedContextMutex *leaf)
389 {
390     ASSERT(this == getRoot());
391     ASSERT(leaf->getRoot() == this);
392     ASSERT(leaf->mLeaves.empty());
393     ASSERT(mLeaves.count(leaf) == 0);
394     mLeaves.emplace(leaf);
395 }
396 
397 template <class Mutex>
removeLeaf(SharedContextMutex * leaf)398 void SharedContextMutex<Mutex>::removeLeaf(SharedContextMutex *leaf)
399 {
400     ASSERT(this == getRoot());
401     ASSERT(leaf->getRoot() == this);
402     ASSERT(leaf->mLeaves.empty());
403     ASSERT(mLeaves.count(leaf) == 1);
404     mLeaves.erase(leaf);
405 }
406 
407 template <class Mutex>
onDestroy(UnlockBehaviour unlockBehaviour)408 void SharedContextMutex<Mutex>::onDestroy(UnlockBehaviour unlockBehaviour)
409 {
410     ASSERT(mRefCount == 0);
411     ASSERT(mLeaves.empty());
412 
413     SharedContextMutex *const root = getRoot();
414     if (this == root)
415     {
416         ASSERT(mOldRoots.empty());
417         if (unlockBehaviour == UnlockBehaviour::kUnlock)
418         {
419             doUnlock();
420         }
421     }
422     else
423     {
424         for (SharedContextMutex *oldRoot : mOldRoots)
425         {
426             ASSERT(oldRoot->getRoot() == root);
427             ASSERT(oldRoot->mLeaves.empty());
428             oldRoot->release();
429         }
430         mOldRoots.clear();
431 
432         root->removeLeaf(this);
433 
434         root->release(unlockBehaviour);
435         mRoot.store(this, std::memory_order_relaxed);
436     }
437 }
438 
439 template class SharedContextMutex<std::mutex>;
440 
441 // SharedContextMutexManager
442 template <class Mutex>
create()443 ContextMutex *SharedContextMutexManager<Mutex>::create()
444 {
445     return new SharedContextMutex<Mutex>();
446 }
447 
448 template <class Mutex>
merge(ContextMutex * lockedMutex,ContextMutex * otherMutex)449 void SharedContextMutexManager<Mutex>::merge(ContextMutex *lockedMutex, ContextMutex *otherMutex)
450 {
451     SharedContextMutex<Mutex>::Merge(static_cast<SharedContextMutex<Mutex> *>(lockedMutex),
452                                      static_cast<SharedContextMutex<Mutex> *>(otherMutex));
453 }
454 
455 template <class Mutex>
getRootMutex(ContextMutex * mutex)456 ContextMutex *SharedContextMutexManager<Mutex>::getRootMutex(ContextMutex *mutex)
457 {
458     return static_cast<SharedContextMutex<Mutex> *>(mutex)->getRoot();
459 }
460 
461 template class SharedContextMutexManager<std::mutex>;
462 
463 }  // namespace egl
464