Hash :
3545ae0c
Author :
Date :
2023-06-09T16:11:47
Add isContextMutexStateConsistent() ASSERT Added `gl::Context::isContextMutexStateConsistent()` method. This method is primarily used to check if "SharedContextMutex" activation worked successfully. It is automatically called in the updated `ScopedContextMutexLock` before unlocking. This is to catch possible errors using ASSERT during normal ANGLE operation in applications and tests. The `ScopedContextMutexLock` is now also used instead of the `std::lock_guard<egl::ContextMutex>` in the `SCOPED_SHARE_CONTEXT_LOCK`. No performance regression observed. Important note: `lockAndActivateSharedContextMutex()` is NOT 100% safe regardless of the `kActivationDelayMicro` value, so `ASSERT` may still fail. However, failure does not necessary mean that there will be an undefined behavior, it means that UB might happen. Bug: angleproject:6957 Bug: chromium:1336126 Change-Id: Iee7357fede0d37fa315fe2cc7d27a4e30a304194 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/4610227 Reviewed-by: Shahbaz Youssefi <syoussefi@chromium.org> Reviewed-by: Geoff Lang <geofflang@chromium.org> Commit-Queue: Igor Nazarov <i.nazarov@samsung.com>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282
//
// 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.
//
// SharedContextMutex.h: Classes for protecting Shared Context access and EGLImage siblings.
#ifndef LIBANGLE_SHARED_CONTEXT_MUTEX_H_
#define LIBANGLE_SHARED_CONTEXT_MUTEX_H_
#include <atomic>
#include "common/debug.h"
namespace gl
{
class Context;
}
namespace egl
{
#if defined(ANGLE_ENABLE_SHARED_CONTEXT_MUTEX)
constexpr bool kIsSharedContextMutexEnabled = true;
#else
constexpr bool kIsSharedContextMutexEnabled = false;
#endif
class ContextMutex : angle::NonCopyable
{
public:
// Below group of methods are not thread safe and requires external synchronization.
// Note: since release*() may call onDestroy(), additional synchronization requirements may be
// enforced by concrete implementations.
ANGLE_INLINE void addRef() { ++mRefCount; }
ANGLE_INLINE void release() { release(UnlockBehaviour::kNotUnlock); }
ANGLE_INLINE void releaseAndUnlock() { release(UnlockBehaviour::kUnlock); }
ANGLE_INLINE bool isReferenced() const { return mRefCount > 0; }
virtual bool try_lock() = 0;
virtual void lock() = 0;
virtual void unlock() = 0;
protected:
virtual ~ContextMutex();
enum class UnlockBehaviour
{
kNotUnlock,
kUnlock
};
void release(UnlockBehaviour unlockBehaviour);
virtual void onDestroy(UnlockBehaviour unlockBehaviour);
protected:
size_t mRefCount = 0;
};
struct ContextMutexMayBeNullTag final
{};
constexpr ContextMutexMayBeNullTag kContextMutexMayBeNull;
// Prevents destruction while locked, uses mMutex to protect addRef()/releaseAndUnlock() calls.
class [[nodiscard]] ScopedContextMutexAddRefLock final : angle::NonCopyable
{
public:
ANGLE_INLINE ScopedContextMutexAddRefLock() = default;
ANGLE_INLINE explicit ScopedContextMutexAddRefLock(ContextMutex *mutex) { lock(mutex); }
ANGLE_INLINE ScopedContextMutexAddRefLock(ContextMutex *mutex, ContextMutexMayBeNullTag)
{
if (mutex != nullptr)
{
lock(mutex);
}
}
ANGLE_INLINE ~ScopedContextMutexAddRefLock()
{
if (mMutex != nullptr)
{
mMutex->releaseAndUnlock();
}
}
private:
void lock(ContextMutex *mutex);
private:
ContextMutex *mMutex = nullptr;
};
class [[nodiscard]] ScopedContextMutexLock final
{
public:
ANGLE_INLINE ScopedContextMutexLock() = default;
ANGLE_INLINE ScopedContextMutexLock(ContextMutex *mutex, gl::Context *context)
: mMutex(mutex), mContext(context)
{
ASSERT(mutex != nullptr);
ASSERT(context != nullptr);
mutex->lock();
}
ANGLE_INLINE ScopedContextMutexLock(ContextMutex *mutex,
gl::Context *context,
ContextMutexMayBeNullTag)
: mMutex(mutex), mContext(context)
{
if (ANGLE_LIKELY(mutex != nullptr))
{
ASSERT(context != nullptr);
mutex->lock();
}
}
ANGLE_INLINE ~ScopedContextMutexLock()
{
if (ANGLE_LIKELY(mMutex != nullptr))
{
ASSERT(IsContextMutexStateConsistent(mContext));
mMutex->unlock();
}
}
ANGLE_INLINE ScopedContextMutexLock(ScopedContextMutexLock &&other)
: mMutex(other.mMutex), mContext(other.mContext)
{
other.mMutex = nullptr;
}
ANGLE_INLINE ScopedContextMutexLock &operator=(ScopedContextMutexLock &&other)
{
std::swap(mMutex, other.mMutex);
mContext = other.mContext;
return *this;
}
private:
static bool IsContextMutexStateConsistent(gl::Context *context);
private:
ContextMutex *mMutex = nullptr;
gl::Context *mContext = nullptr;
};
// Mutex may be locked only by a single thread. Other threads may only check the status.
class SingleContextMutex final : public ContextMutex
{
public:
ANGLE_INLINE bool isLocked(std::memory_order order) const { return mState.load(order) != 0; }
// ContextMutex
bool try_lock() override;
void lock() override;
void unlock() override;
private:
std::atomic_int mState{0};
};
// Note: onDestroy() method must be protected by "this" mutex, since onDestroy() is called from
// release*() methods, these methods must also be protected by "this" mutex.
template <class Mutex>
class SharedContextMutex final : public ContextMutex
{
public:
SharedContextMutex();
~SharedContextMutex() override;
// Merges mutexes so they work as one.
// At the end, only single "root" mutex will be locked.
// Does nothing if two mutexes are the same or already merged (have same "root" mutex).
// Note: synchronization requirements for addRef()/release*() calls for merged mutexes are
// the same as for the single unmerged mutex. For example: can't call at the same time
// mutexA.addRef() and mutexB.release() if they are merged.
static void Merge(SharedContextMutex *lockedMutex, SharedContextMutex *otherMutex);
// Returns current "root" mutex.
// Warning! Result is only stable if mutex is locked, while may change any time if unlocked.
// May be used to compare against already locked "root" mutex.
ANGLE_INLINE SharedContextMutex *getRoot() { return mRoot.load(std::memory_order_relaxed); }
// ContextMutex
bool try_lock() override;
void lock() override;
void unlock() override;
private:
SharedContextMutex *doTryLock();
SharedContextMutex *doLock();
void doUnlock();
// All methods below must be protected by "this" mutex ("stable root" in "this" instance).
void setNewRoot(SharedContextMutex *newRoot);
void addLeaf(SharedContextMutex *leaf);
void removeLeaf(SharedContextMutex *leaf);
// ContextMutex
void onDestroy(UnlockBehaviour unlockBehaviour) override;
private:
Mutex mMutex;
// mRoot and mLeaves tree structure details:
// - used to implement primary functionality of this class;
// - initially, all mutexes are "root"s;
// - "root" mutex has "mRoot == this";
// - "root" mutex stores unreferenced pointers to all its leaves (used in merging);
// - "leaf" mutex holds reference (addRef) to the current "root" mutex in the mRoot;
// - "leaf" mutex has empty mLeaves;
// - "leaf" mutex can't become a "root" mutex;
// - before locking the mMutex, "this" is an "unstable root" or a "leaf";
// - the implementation always locks mRoot's mMutex ("unstable root");
// - if after locking the mMutex "mRoot != this", then "this" is/become a "leaf";
// - otherwise, "this" is a locked "stable root" - lock is successful.
std::atomic<SharedContextMutex *> mRoot;
std::set<SharedContextMutex *> mLeaves;
// mOldRoots is used to solve a particular problem (below example does not use mRank):
// - have "leaf" mutex_2 with a reference to mutex_1 "root";
// - the mutex_1 has no other references (only in the mutex_2);
// - have other mutex_3 "root";
// - mutex_1 pointer is cached on the stack during locking of mutex_2 (thread A);
// - marge mutex_2 and mutex_3 (thread B):
// * now "leaf" mutex_2 stores reference to mutex_3 "root";
// * old "root" mutex_1 becomes a "leaf" of mutex_3;
// * old "root" mutex_1 has no references and gets destroyed.
// - invalid pointer to destroyed mutex_1 stored on the stack and in the mLeaves of mutex_3;
// - to fix this problem, references to old "root"s are kept in the mOldRoots vector.
std::vector<SharedContextMutex *> mOldRoots;
// mRank is used to fix a problem of indefinite grows of mOldRoots:
// - merge mutex_1 and mutex_2 -> mutex_2 is "root" of mutex_1 (mOldRoots == 0);
// - destroy mutex_2;
// - merge mutex_1 and mutex_3 -> mutex_3 is "root" of mutex_1 (mOldRoots == 1);
// - destroy mutex_3;
// - merge mutex_1 and mutex_4 -> mutex_4 is "root" of mutex_1 (mOldRoots == 2);
// - destroy mutex_4;
// - continuing this pattern can lead to indefinite grows of mOldRoots, while pick number of
// mutexes is only 2.
// Fix details using mRank:
// - initially "mRank == 0" and only relevant for "root" mutexes;
// - merging mutexes with equal mRank of their "root"s, will use second (otherMutex) "root"
// mutex as a new "root" and increase its mRank by 1;
// - otherwise, "root" mutex with a highest rank will be used without changing the mRank;
// - this way, "stronger" (with a higher mRank) "root" mutex will "protect" its "leaves" from
// "mRoot" replacement and therefore - mOldRoots grows.
// Lets look at the problematic pattern with the mRank:
// - merge mutex_1 and mutex_2 -> mutex_2 is "root" (mRank == 1) of mutex_1 (mOldRoots == 0);
// - destroy mutex_2;
// - merge mutex_1 and mutex_3 -> mutex_2 is "root" (mRank == 1) of mutex_3 (mOldRoots == 0);
// - destroy mutex_3;
// - merge mutex_1 and mutex_4 -> mutex_2 is "root" (mRank == 1) of mutex_4 (mOldRoots == 0);
// - destroy mutex_4;
// - no mOldRoots grows at all;
// - minumum number of mutexes to reach mOldRoots size of N => 2^(N+1).
uint32_t mRank;
// Only used when ASSERT() is enabled.
std::atomic<angle::ThreadId> mOwnerThreadId;
};
class ContextMutexManager
{
public:
virtual ~ContextMutexManager() = default;
virtual ContextMutex *create() = 0;
virtual void merge(ContextMutex *lockedMutex, ContextMutex *otherMutex) = 0;
virtual ContextMutex *getRootMutex(ContextMutex *mutex) = 0;
};
template <class Mutex>
class SharedContextMutexManager final : public ContextMutexManager
{
public:
ContextMutex *create() override;
void merge(ContextMutex *lockedMutex, ContextMutex *otherMutex) override;
ContextMutex *getRootMutex(ContextMutex *mutex) override;
};
} // namespace egl
#endif // LIBANGLE_SHARED_CONTEXT_MUTEX_H_