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, ¤tThreadId));
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, ¤tThreadId));
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