mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-09 03:15:11 +00:00
Bug 1755823 - BaseProfilerSharedMutex and exclusive&shared RAII locks - r=canaltinova
This is a profiler-specific shared lock (aka readers-writer lock) implemented on top of RWLockImpl. Similar to BaseProfilerMutex, it records which thread is currently holding the exclusive lock. Differential Revision: https://phabricator.services.mozilla.com/D139916
This commit is contained in:
parent
2e2899b994
commit
b204f08834
@ -12,6 +12,7 @@
|
||||
#include "mozilla/Atomics.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/PlatformMutex.h"
|
||||
#include "mozilla/PlatformRWLock.h"
|
||||
#include "mozilla/BaseProfilerUtils.h"
|
||||
|
||||
namespace mozilla {
|
||||
@ -183,6 +184,96 @@ class MOZ_RAII BaseProfilerMaybeAutoLock {
|
||||
BaseProfilerMaybeMutex& mMaybeMutex;
|
||||
};
|
||||
|
||||
class BaseProfilerSharedMutex : public ::mozilla::detail::RWLockImpl {
|
||||
public:
|
||||
#ifdef DEBUG
|
||||
~BaseProfilerSharedMutex() {
|
||||
MOZ_ASSERT(!BaseProfilerThreadId::FromNumber(mOwningThreadId).IsSpecified(),
|
||||
"BaseProfilerMutex should have been unlocked when destroyed");
|
||||
}
|
||||
#endif // DEBUG
|
||||
|
||||
[[nodiscard]] bool IsLockedExclusiveOnCurrentThread() const {
|
||||
return BaseProfilerThreadId::FromNumber(mOwningThreadId) ==
|
||||
baseprofiler::profiler_current_thread_id();
|
||||
}
|
||||
|
||||
void LockExclusive() {
|
||||
const BaseProfilerThreadId tid = baseprofiler::profiler_current_thread_id();
|
||||
MOZ_ASSERT(tid.IsSpecified());
|
||||
MOZ_ASSERT(!IsLockedExclusiveOnCurrentThread(), "Recursive locking");
|
||||
::mozilla::detail::RWLockImpl::writeLock();
|
||||
MOZ_ASSERT(!BaseProfilerThreadId::FromNumber(mOwningThreadId).IsSpecified(),
|
||||
"Not unlocked properly");
|
||||
mOwningThreadId = tid.ToNumber();
|
||||
}
|
||||
|
||||
void UnlockExclusive() {
|
||||
MOZ_ASSERT(IsLockedExclusiveOnCurrentThread(),
|
||||
"Unlocking when not locked here");
|
||||
// We're still holding the mutex here, so it's safe to just reset
|
||||
// `mOwningThreadId`.
|
||||
mOwningThreadId = BaseProfilerThreadId{}.ToNumber();
|
||||
writeUnlock();
|
||||
}
|
||||
|
||||
void LockShared() { readLock(); }
|
||||
|
||||
void UnlockShared() { readUnlock(); }
|
||||
|
||||
private:
|
||||
// Thread currently owning the exclusive lock, or 0.
|
||||
// Atomic because it may be read at any time independent of the mutex.
|
||||
// Relaxed because threads only need to know if they own it already, so:
|
||||
// - If it's their id, only *they* wrote that value with a locked mutex.
|
||||
// - If it's different from their thread id it doesn't matter what other
|
||||
// number it is (0 or another id) and that it can change again at any time.
|
||||
Atomic<typename BaseProfilerThreadId::NumberType, MemoryOrdering::Relaxed>
|
||||
mOwningThreadId;
|
||||
};
|
||||
|
||||
// RAII class to lock a shared mutex exclusively.
|
||||
class MOZ_RAII BaseProfilerAutoLockExclusive {
|
||||
public:
|
||||
explicit BaseProfilerAutoLockExclusive(BaseProfilerSharedMutex& aSharedMutex)
|
||||
: mSharedMutex(aSharedMutex) {
|
||||
mSharedMutex.LockExclusive();
|
||||
}
|
||||
|
||||
BaseProfilerAutoLockExclusive(const BaseProfilerAutoLockExclusive&) = delete;
|
||||
BaseProfilerAutoLockExclusive& operator=(
|
||||
const BaseProfilerAutoLockExclusive&) = delete;
|
||||
BaseProfilerAutoLockExclusive(BaseProfilerAutoLockExclusive&&) = delete;
|
||||
BaseProfilerAutoLockExclusive& operator=(BaseProfilerAutoLockExclusive&&) =
|
||||
delete;
|
||||
|
||||
~BaseProfilerAutoLockExclusive() { mSharedMutex.UnlockExclusive(); }
|
||||
|
||||
private:
|
||||
BaseProfilerSharedMutex& mSharedMutex;
|
||||
};
|
||||
|
||||
// RAII class to lock a shared mutex non-exclusively, other
|
||||
// BaseProfilerAutoLockShared's may happen in other threads.
|
||||
class MOZ_RAII BaseProfilerAutoLockShared {
|
||||
public:
|
||||
explicit BaseProfilerAutoLockShared(BaseProfilerSharedMutex& aSharedMutex)
|
||||
: mSharedMutex(aSharedMutex) {
|
||||
mSharedMutex.LockShared();
|
||||
}
|
||||
|
||||
BaseProfilerAutoLockShared(const BaseProfilerAutoLockShared&) = delete;
|
||||
BaseProfilerAutoLockShared& operator=(const BaseProfilerAutoLockShared&) =
|
||||
delete;
|
||||
BaseProfilerAutoLockShared(BaseProfilerAutoLockShared&&) = delete;
|
||||
BaseProfilerAutoLockShared& operator=(BaseProfilerAutoLockShared&&) = delete;
|
||||
|
||||
~BaseProfilerAutoLockShared() { mSharedMutex.UnlockShared(); }
|
||||
|
||||
private:
|
||||
BaseProfilerSharedMutex& mSharedMutex;
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace baseprofiler
|
||||
} // namespace mozilla
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/BaseAndGeckoProfilerDetail.h"
|
||||
#include "mozilla/BaseProfileJSONWriter.h"
|
||||
#include "mozilla/BaseProfilerDetail.h"
|
||||
#include "mozilla/FloatingPoint.h"
|
||||
#include "mozilla/ProgressLogger.h"
|
||||
#include "mozilla/ProportionValue.h"
|
||||
@ -316,6 +317,190 @@ void TestBaseAndProfilerDetail() {
|
||||
printf("TestBaseAndProfilerDetail done\n");
|
||||
}
|
||||
|
||||
void TestSharedMutex() {
|
||||
printf("TestSharedMutex...\n");
|
||||
|
||||
mozilla::baseprofiler::detail::BaseProfilerSharedMutex sm;
|
||||
|
||||
// First round of minimal tests in this thread.
|
||||
|
||||
MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
|
||||
|
||||
sm.LockExclusive();
|
||||
MOZ_RELEASE_ASSERT(sm.IsLockedExclusiveOnCurrentThread());
|
||||
sm.UnlockExclusive();
|
||||
MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
|
||||
|
||||
sm.LockShared();
|
||||
MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
|
||||
sm.UnlockShared();
|
||||
MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
|
||||
|
||||
{
|
||||
mozilla::baseprofiler::detail::BaseProfilerAutoLockExclusive exclusiveLock{
|
||||
sm};
|
||||
MOZ_RELEASE_ASSERT(sm.IsLockedExclusiveOnCurrentThread());
|
||||
}
|
||||
MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
|
||||
|
||||
{
|
||||
mozilla::baseprofiler::detail::BaseProfilerAutoLockShared sharedLock{sm};
|
||||
MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
|
||||
}
|
||||
MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
|
||||
|
||||
// The following will run actions between two threads, to verify that
|
||||
// exclusive and shared locks work as expected.
|
||||
|
||||
// These actions will happen from top to bottom.
|
||||
// This will test all possible lock interactions.
|
||||
enum NextAction { // State of the lock:
|
||||
t1Starting, // (x=exclusive, s=shared, ?=blocked)
|
||||
t2Starting, // t1 t2
|
||||
t1LockExclusive, // x
|
||||
t2LockExclusiveAndBlock, // x x? - Can't have two exclusives.
|
||||
t1UnlockExclusive, // x
|
||||
t2UnblockedAfterT1Unlock, // x
|
||||
t1LockSharedAndBlock, // s? x - Can't have shared during excl
|
||||
t2UnlockExclusive, // s
|
||||
t1UnblockedAfterT2Unlock, // s
|
||||
t2LockShared, // s s - Can have multiple shared locks
|
||||
t1UnlockShared, // s
|
||||
t2StillLockedShared, // s
|
||||
t1LockExclusiveAndBlock, // x? s - Can't have excl during shared
|
||||
t2UnlockShared, // x
|
||||
t1UnblockedAfterT2UnlockShared, // x
|
||||
t2CheckAfterT1Lock, // x
|
||||
t1LastUnlockExclusive, // (unlocked)
|
||||
done
|
||||
};
|
||||
|
||||
// Each thread will repeatedly read this `nextAction`, and run actions that
|
||||
// target it...
|
||||
std::atomic<NextAction> nextAction{static_cast<NextAction>(0)};
|
||||
// ... and advance to the next available action (which should usually be for
|
||||
// the other thread).
|
||||
auto AdvanceAction = [&nextAction]() {
|
||||
MOZ_RELEASE_ASSERT(nextAction <= done);
|
||||
nextAction = static_cast<NextAction>(static_cast<int>(nextAction) + 1);
|
||||
};
|
||||
|
||||
std::thread t1{[&]() {
|
||||
for (;;) {
|
||||
switch (nextAction) {
|
||||
case t1Starting:
|
||||
AdvanceAction();
|
||||
break;
|
||||
case t1LockExclusive:
|
||||
MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
|
||||
sm.LockExclusive();
|
||||
MOZ_RELEASE_ASSERT(sm.IsLockedExclusiveOnCurrentThread());
|
||||
AdvanceAction();
|
||||
break;
|
||||
case t1UnlockExclusive:
|
||||
MOZ_RELEASE_ASSERT(sm.IsLockedExclusiveOnCurrentThread());
|
||||
// Advance first, before unlocking, so that t2 sees the new state.
|
||||
AdvanceAction();
|
||||
sm.UnlockExclusive();
|
||||
MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
|
||||
break;
|
||||
case t1LockSharedAndBlock:
|
||||
// Advance action before attempting to lock after t2's exclusive lock.
|
||||
AdvanceAction();
|
||||
sm.LockShared();
|
||||
// We will only acquire the lock after t1 unlocks.
|
||||
MOZ_RELEASE_ASSERT(nextAction == t1UnblockedAfterT2Unlock);
|
||||
MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
|
||||
AdvanceAction();
|
||||
break;
|
||||
case t1UnlockShared:
|
||||
MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
|
||||
sm.UnlockShared();
|
||||
MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
|
||||
AdvanceAction();
|
||||
break;
|
||||
case t1LockExclusiveAndBlock:
|
||||
MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
|
||||
// Advance action before attempting to lock after t2's shared lock.
|
||||
AdvanceAction();
|
||||
sm.LockExclusive();
|
||||
// We will only acquire the lock after t2 unlocks.
|
||||
MOZ_RELEASE_ASSERT(nextAction == t1UnblockedAfterT2UnlockShared);
|
||||
MOZ_RELEASE_ASSERT(sm.IsLockedExclusiveOnCurrentThread());
|
||||
AdvanceAction();
|
||||
break;
|
||||
case t1LastUnlockExclusive:
|
||||
MOZ_RELEASE_ASSERT(sm.IsLockedExclusiveOnCurrentThread());
|
||||
// Advance first, before unlocking, so that t2 sees the new state.
|
||||
AdvanceAction();
|
||||
sm.UnlockExclusive();
|
||||
MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
|
||||
break;
|
||||
case done:
|
||||
return;
|
||||
default:
|
||||
// Ignore other actions intended for t2.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}};
|
||||
|
||||
std::thread t2{[&]() {
|
||||
for (;;) {
|
||||
switch (nextAction) {
|
||||
case t2Starting:
|
||||
AdvanceAction();
|
||||
break;
|
||||
case t2LockExclusiveAndBlock:
|
||||
MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
|
||||
// Advance action before attempting to lock after t1's exclusive lock.
|
||||
AdvanceAction();
|
||||
sm.LockExclusive();
|
||||
// We will only acquire the lock after t1 unlocks.
|
||||
MOZ_RELEASE_ASSERT(nextAction == t2UnblockedAfterT1Unlock);
|
||||
MOZ_RELEASE_ASSERT(sm.IsLockedExclusiveOnCurrentThread());
|
||||
AdvanceAction();
|
||||
break;
|
||||
case t2UnlockExclusive:
|
||||
MOZ_RELEASE_ASSERT(sm.IsLockedExclusiveOnCurrentThread());
|
||||
// Advance first, before unlocking, so that t1 sees the new state.
|
||||
AdvanceAction();
|
||||
sm.UnlockExclusive();
|
||||
MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
|
||||
break;
|
||||
case t2LockShared:
|
||||
sm.LockShared();
|
||||
MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
|
||||
AdvanceAction();
|
||||
break;
|
||||
case t2StillLockedShared:
|
||||
AdvanceAction();
|
||||
break;
|
||||
case t2UnlockShared:
|
||||
MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
|
||||
sm.UnlockShared();
|
||||
MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
|
||||
AdvanceAction();
|
||||
break;
|
||||
case t2CheckAfterT1Lock:
|
||||
MOZ_RELEASE_ASSERT(!sm.IsLockedExclusiveOnCurrentThread());
|
||||
AdvanceAction();
|
||||
break;
|
||||
case done:
|
||||
return;
|
||||
default:
|
||||
// Ignore other actions intended for t1.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}};
|
||||
|
||||
t1.join();
|
||||
t2.join();
|
||||
|
||||
printf("TestSharedMutex done\n");
|
||||
}
|
||||
|
||||
void TestProportionValue() {
|
||||
printf("TestProportionValue...\n");
|
||||
|
||||
@ -5217,6 +5402,7 @@ int main()
|
||||
|
||||
TestProfilerUtils();
|
||||
TestBaseAndProfilerDetail();
|
||||
TestSharedMutex();
|
||||
TestProportionValue();
|
||||
TestProgressLogger();
|
||||
// Note that there are two `TestProfiler{,Markers}` functions above, depending
|
||||
|
Loading…
Reference in New Issue
Block a user