| // | |
| // Copyright 2023 The ANGLE Project Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| // | |
| // ContextMutex.cpp: Classes for protecting Context access and EGLImage siblings. | |
| #include "libANGLE/ContextMutex.h" | |
| #include "common/system_utils.h" | |
| #include "libANGLE/Context.h" | |
| namespace egl | |
| { | |
| namespace | |
| { | |
| [[maybe_unused]] bool CheckThreadIdCurrent(const std::atomic<angle::ThreadId> &threadId, | |
| angle::ThreadId *currentThreadIdOut) | |
| { | |
| *currentThreadIdOut = angle::GetCurrentThreadId(); | |
| return (threadId.load(std::memory_order_relaxed) == *currentThreadIdOut); | |
| } | |
| [[maybe_unused]] bool TryUpdateThreadId(std::atomic<angle::ThreadId> *threadId, | |
| angle::ThreadId oldThreadId, | |
| angle::ThreadId newThreadId) | |
| { | |
| const bool ok = (threadId->load(std::memory_order_relaxed) == oldThreadId); | |
| if (ok) | |
| { | |
| threadId->store(newThreadId, std::memory_order_relaxed); | |
| } | |
| return ok; | |
| } | |
| } // namespace | |
| // ScopedContextMutexAddRefLock | |
| void ScopedContextMutexAddRefLock::lock(ContextMutex *mutex) | |
| { | |
| ASSERT(mutex != nullptr); | |
| ASSERT(mMutex == nullptr); | |
| // lock() before addRef() - using mMutex as synchronization | |
| mutex->lock(); | |
| // Take the "root" mutex after the lock. | |
| mMutex = mutex->getRoot(); | |
| ASSERT(mMutex->isReferenced()); | |
| mMutex->addRef(); | |
| } | |
| // ContextMutex | |
| ContextMutex::ContextMutex(ContextMutex *root) | |
| : mRoot(this), mOwnerThreadId(angle::InvalidThreadId()), mLockLevel(0), mRefCount(0), mRank(0) | |
| { | |
| if (root != nullptr) | |
| { | |
| setNewRoot(root); | |
| } | |
| } | |
| ContextMutex::~ContextMutex() | |
| { | |
| ASSERT(mLockLevel == 0); | |
| ASSERT(mRefCount == 0); | |
| ASSERT(mLeaves.empty()); | |
| ContextMutex *const root = getRoot(); | |
| if (this == root) | |
| { | |
| ASSERT(mOldRoots.empty()); | |
| } | |
| else | |
| { | |
| for (ContextMutex *oldRoot : mOldRoots) | |
| { | |
| ASSERT(oldRoot->getRoot() == root); | |
| ASSERT(oldRoot->mLeaves.empty()); | |
| oldRoot->release(); | |
| } | |
| root->removeLeaf(this); | |
| root->release(); | |
| } | |
| } | |
| void ContextMutex::Merge(ContextMutex *lockedMutex, ContextMutex *otherMutex) | |
| { | |
| ASSERT(lockedMutex != nullptr); | |
| ASSERT(otherMutex != nullptr); | |
| // Since lockedMutex is locked, its "root" pointer is stable. | |
| ContextMutex *lockedRoot = lockedMutex->getRoot(); | |
| ContextMutex *otherLockedRoot = nullptr; | |
| // Mutex merging will update the structure of both mutexes, therefore both mutexes must be | |
| // locked before continuing. First mutex is already locked, need to lock the other mutex. | |
| // Because other thread may perform merge with same mutexes reversed, we can't simply lock | |
| // otherMutex - this may cause a deadlock. Additionally, otherMutex may have same "root" (same | |
| // mutex or already merged), not only merging is unnecessary, but locking otherMutex will | |
| // guarantee a deadlock. | |
| for (;;) | |
| { | |
| // First, check that "root" of otherMutex is the same as "root" of lockedMutex. | |
| // lockedRoot is stable by definition and it is safe to compare with "unstable root". | |
| ContextMutex *otherRoot = otherMutex->getRoot(); | |
| if (otherRoot == lockedRoot) | |
| { | |
| // Do nothing if two mutexes are the same/merged. | |
| return; | |
| } | |
| // Second, try to lock otherMutex "root" (can't use lock()/lockImpl(), see above comment). | |
| if (otherRoot->tryLockImpl()) | |
| { | |
| otherLockedRoot = otherRoot->getRoot(); | |
| // otherMutex "root" can't become lockedMutex "root". For that to happen, lockedMutex | |
| // must be locked from some other thread first, which is impossible, since it is already | |
| // locked by this thread. | |
| ASSERT(otherLockedRoot != lockedRoot); | |
| // Lock is successful. Both mutexes are locked - can proceed with the merge... | |
| break; | |
| } | |
| // Lock was unsuccessful - unlock and retry... | |
| // May use "unlockImpl()" because lockedRoot is a "stable root" mutex. | |
| // Note: lock will be preserved in case of the recursive lock. | |
| lockedRoot->unlockImpl(); | |
| // Sleep random amount to allow one of the thread acquire the lock next time... | |
| std::this_thread::sleep_for(std::chrono::microseconds(rand() % 91 + 10)); | |
| // Because lockedMutex was unlocked, its "root" might have been changed. Below line will | |
| // reacquire the lock and update lockedRoot pointer. | |
| lockedMutex->lock(); | |
| lockedRoot = lockedMutex->getRoot(); | |
| } | |
| // Decide the new "root". See mRank comment for more details... | |
| ContextMutex *oldRoot = otherLockedRoot; | |
| ContextMutex *newRoot = lockedRoot; | |
| if (oldRoot->mRank > newRoot->mRank) | |
| { | |
| std::swap(oldRoot, newRoot); | |
| } | |
| else if (oldRoot->mRank == newRoot->mRank) | |
| { | |
| ++newRoot->mRank; | |
| } | |
| ASSERT(newRoot->isReferenced()); | |
| // Update the structure | |
| for (ContextMutex *const leaf : oldRoot->mLeaves) | |
| { | |
| ASSERT(leaf->getRoot() == oldRoot); | |
| leaf->setNewRoot(newRoot); | |
| } | |
| oldRoot->mLeaves.clear(); | |
| oldRoot->setNewRoot(newRoot); | |
| // Leave only the "merged" mutex locked. "oldRoot" already merged, need to use "unlockImpl()" | |
| oldRoot->unlockImpl(); | |
| // Merge from recursive lock is unexpected. Handle such cases anyway to be safe. | |
| while (oldRoot->mLockLevel > 0) | |
| { | |
| newRoot->lockImpl(); | |
| oldRoot->unlockImpl(); | |
| } | |
| } | |
| void ContextMutex::setNewRoot(ContextMutex *newRoot) | |
| { | |
| ContextMutex *const oldRoot = getRoot(); | |
| ASSERT(newRoot != oldRoot); | |
| mRoot.store(newRoot, std::memory_order_relaxed); | |
| newRoot->addRef(); | |
| newRoot->addLeaf(this); | |
| if (oldRoot != this) | |
| { | |
| mOldRoots.emplace_back(oldRoot); | |
| } | |
| } | |
| void ContextMutex::addLeaf(ContextMutex *leaf) | |
| { | |
| ASSERT(this == getRoot()); | |
| ASSERT(leaf->getRoot() == this); | |
| ASSERT(leaf->mLeaves.empty()); | |
| ASSERT(mLeaves.count(leaf) == 0); | |
| mLeaves.emplace(leaf); | |
| } | |
| void ContextMutex::removeLeaf(ContextMutex *leaf) | |
| { | |
| ASSERT(this == getRoot()); | |
| ASSERT(leaf->getRoot() == this); | |
| ASSERT(leaf->mLeaves.empty()); | |
| ASSERT(mLeaves.count(leaf) == 1); | |
| mLeaves.erase(leaf); | |
| } | |
| void ContextMutex::release(UnlockBehaviour unlockBehaviour) | |
| { | |
| ASSERT(isReferenced()); | |
| const bool needDelete = (--mRefCount == 0); | |
| if (unlockBehaviour == UnlockBehaviour::kUnlock) | |
| { | |
| ASSERT(this == getRoot()); | |
| unlockImpl(); | |
| } | |
| if (needDelete) | |
| { | |
| delete this; | |
| } | |
| } | |
| bool ContextMutex::try_lock() | |
| { | |
| return getRoot()->tryLockImpl(); | |
| } | |
| void ContextMutex::lock() | |
| { | |
| getRoot()->lockImpl(); | |
| } | |
| void ContextMutex::unlock() | |
| { | |
| ContextMutex *const root = getRoot(); | |
| // "root" is currently locked so "root->getRoot()" will return stable result. | |
| ASSERT(root == root->getRoot()); | |
| root->unlockImpl(); | |
| } | |
| #if defined(ANGLE_ENABLE_CONTEXT_MUTEX_RECURSION) | |
| bool ContextMutex::tryLockImpl() | |
| { | |
| const angle::ThreadId threadId = angle::GetCurrentThreadId(); | |
| if (ANGLE_UNLIKELY(!mMutex.try_lock())) | |
| { | |
| if (ANGLE_UNLIKELY(mOwnerThreadId.load(std::memory_order_relaxed) == threadId)) | |
| { | |
| ASSERT(this == getRoot()); | |
| ASSERT(mLockLevel > 0); | |
| ++mLockLevel; | |
| return true; | |
| } | |
| return false; | |
| } | |
| ASSERT(mOwnerThreadId.load(std::memory_order_relaxed) == angle::InvalidThreadId()); | |
| ASSERT(mLockLevel == 0); | |
| ContextMutex *const root = getRoot(); | |
| if (ANGLE_UNLIKELY(this != root)) | |
| { | |
| // Unlock, so only the "stable root" mutex remains locked | |
| mMutex.unlock(); | |
| return root->tryLockImpl(); | |
| } | |
| mOwnerThreadId.store(threadId, std::memory_order_relaxed); | |
| mLockLevel = 1; | |
| return true; | |
| } | |
| void ContextMutex::lockImpl() | |
| { | |
| const angle::ThreadId threadId = angle::GetCurrentThreadId(); | |
| if (ANGLE_UNLIKELY(!mMutex.try_lock())) | |
| { | |
| if (ANGLE_UNLIKELY(mOwnerThreadId.load(std::memory_order_relaxed) == threadId)) | |
| { | |
| ASSERT(this == getRoot()); | |
| ASSERT(mLockLevel > 0); | |
| ++mLockLevel; | |
| return; | |
| } | |
| mMutex.lock(); | |
| } | |
| ASSERT(mOwnerThreadId.load(std::memory_order_relaxed) == angle::InvalidThreadId()); | |
| ASSERT(mLockLevel == 0); | |
| ContextMutex *const root = getRoot(); | |
| if (ANGLE_UNLIKELY(this != root)) | |
| { | |
| // Unlock, so only the "stable root" mutex remains locked | |
| mMutex.unlock(); | |
| root->lockImpl(); | |
| } | |
| else | |
| { | |
| mOwnerThreadId.store(threadId, std::memory_order_relaxed); | |
| mLockLevel = 1; | |
| } | |
| } | |
| void ContextMutex::unlockImpl() | |
| { | |
| ASSERT(mOwnerThreadId.load(std::memory_order_relaxed) == angle::GetCurrentThreadId()); | |
| ASSERT(mLockLevel > 0); | |
| if (ANGLE_LIKELY(--mLockLevel == 0)) | |
| { | |
| mOwnerThreadId.store(angle::InvalidThreadId(), std::memory_order_relaxed); | |
| mMutex.unlock(); | |
| } | |
| } | |
| #else | |
| bool ContextMutex::tryLockImpl() | |
| { | |
| angle::ThreadId currentThreadId; | |
| ASSERT(!CheckThreadIdCurrent(mOwnerThreadId, ¤tThreadId)); | |
| if (mMutex.try_lock()) | |
| { | |
| ContextMutex *const root = getRoot(); | |
| if (ANGLE_UNLIKELY(this != root)) | |
| { | |
| // Unlock, so only the "stable root" mutex remains locked | |
| mMutex.unlock(); | |
| return root->tryLockImpl(); | |
| } | |
| ASSERT(TryUpdateThreadId(&mOwnerThreadId, angle::InvalidThreadId(), currentThreadId)); | |
| return true; | |
| } | |
| return false; | |
| } | |
| void ContextMutex::lockImpl() | |
| { | |
| angle::ThreadId currentThreadId; | |
| ASSERT(!CheckThreadIdCurrent(mOwnerThreadId, ¤tThreadId)); | |
| mMutex.lock(); | |
| ContextMutex *const root = getRoot(); | |
| if (ANGLE_UNLIKELY(this != root)) | |
| { | |
| // Unlock, so only the "stable root" mutex remains locked | |
| mMutex.unlock(); | |
| root->lockImpl(); | |
| } | |
| else | |
| { | |
| ASSERT(TryUpdateThreadId(&mOwnerThreadId, angle::InvalidThreadId(), currentThreadId)); | |
| } | |
| } | |
| void ContextMutex::unlockImpl() | |
| { | |
| ASSERT( | |
| TryUpdateThreadId(&mOwnerThreadId, angle::GetCurrentThreadId(), angle::InvalidThreadId())); | |
| mMutex.unlock(); | |
| } | |
| #endif | |
| } // namespace egl |