// // 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 &threadId, angle::ThreadId *currentThreadIdOut) { *currentThreadIdOut = angle::GetCurrentThreadId(); return (threadId.load(std::memory_order_relaxed) == *currentThreadIdOut); } [[maybe_unused]] bool TryUpdateThreadId(std::atomic *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