/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// Internal Base Profiler utilities.
#ifndef BaseProfilerDetail_h
#define BaseProfilerDetail_h
#include "mozilla/Atomics.h"
#include "mozilla/Attributes.h"
#include "mozilla/Maybe.h"
#include "mozilla/PlatformMutex.h"
#include "mozilla/PlatformRWLock.h"
#include "mozilla/BaseProfilerUtils.h"
namespace mozilla {
namespace baseprofiler {
namespace detail {
// Thin shell around mozglue PlatformMutex, for Base Profiler internal use.
class MOZ_CAPABILITY(
"mutex") BaseProfilerMutex
:
private ::mozilla::detail::MutexImpl {
public:
BaseProfilerMutex() : ::mozilla::detail::MutexImpl() {}
explicit BaseProfilerMutex(
const char* aName)
: ::mozilla::detail::MutexImpl(), mName(aName) {}
BaseProfilerMutex(
const BaseProfilerMutex&) =
delete;
BaseProfilerMutex&
operator=(
const BaseProfilerMutex&) =
delete;
BaseProfilerMutex(BaseProfilerMutex&&) =
delete;
BaseProfilerMutex&
operator=(BaseProfilerMutex&&) =
delete;
#ifdef DEBUG
~BaseProfilerMutex() {
MOZ_ASSERT(!BaseProfilerThreadId::FromNumber(mOwningThreadId).IsSpecified(),
"BaseProfilerMutex should have been unlocked when destroyed");
}
#endif // DEBUG
[[nodiscard]]
bool IsLockedOnCurrentThread()
const {
return BaseProfilerThreadId::FromNumber(mOwningThreadId) ==
baseprofiler::profiler_current_thread_id();
}
void AssertCurrentThreadOwns()
const MOZ_ASSERT_CAPABILITY(
this) {
MOZ_ASSERT(IsLockedOnCurrentThread());
}
void Lock() MOZ_CAPABILITY_ACQUIRE() {
const BaseProfilerThreadId tid = baseprofiler::profiler_current_thread_id();
MOZ_ASSERT(tid.IsSpecified());
MOZ_ASSERT(!IsLockedOnCurrentThread(),
"Recursive locking");
::mozilla::detail::MutexImpl::lock();
MOZ_ASSERT(!BaseProfilerThreadId::FromNumber(mOwningThreadId).IsSpecified(),
"Not unlocked properly");
mOwningThreadId = tid.ToNumber();
}
[[nodiscard]]
bool TryLock() MOZ_TRY_ACQUIRE(
true) {
const BaseProfilerThreadId tid = baseprofiler::profiler_current_thread_id();
MOZ_ASSERT(tid.IsSpecified());
MOZ_ASSERT(!IsLockedOnCurrentThread(),
"Recursive locking");
if (!::mozilla::detail::MutexImpl::tryLock()) {
// Failed to lock, nothing more to do.
return false;
}
MOZ_ASSERT(!BaseProfilerThreadId::FromNumber(mOwningThreadId).IsSpecified(),
"Not unlocked properly");
mOwningThreadId = tid.ToNumber();
return true;
}
void Unlock() MOZ_CAPABILITY_RELEASE() {
MOZ_ASSERT(IsLockedOnCurrentThread(),
"Unlocking when not locked here");
// We're still holding the mutex here, so it's safe to just reset
// `mOwningThreadId`.
mOwningThreadId = BaseProfilerThreadId{}.ToNumber();
::mozilla::detail::MutexImpl::unlock();
}
const char* GetName()
const {
return mName; }
private:
// Thread currently owning the 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;
const char* mName = nullptr;
};
// RAII class to lock a mutex.
class MOZ_RAII BaseProfilerAutoLock {
public:
explicit BaseProfilerAutoLock(BaseProfilerMutex& aMutex) : mMutex(aMutex) {
mMutex.Lock();
}
BaseProfilerAutoLock(
const BaseProfilerAutoLock&) =
delete;
BaseProfilerAutoLock&
operator=(
const BaseProfilerAutoLock&) =
delete;
BaseProfilerAutoLock(BaseProfilerAutoLock&&) =
delete;
BaseProfilerAutoLock&
operator=(BaseProfilerAutoLock&&) =
delete;
~BaseProfilerAutoLock() { mMutex.Unlock(); }
private:
BaseProfilerMutex& mMutex;
};
// Thin shell around mozglue PlatformMutex, for Base Profiler internal use.
// Actual mutex may be disabled at construction time.
class BaseProfilerMaybeMutex :
private ::mozilla::detail::MutexImpl {
public:
explicit BaseProfilerMaybeMutex(
bool aActivate) {
if (aActivate) {
mMaybeMutex.emplace();
}
}
BaseProfilerMaybeMutex(
const BaseProfilerMaybeMutex&) =
delete;
BaseProfilerMaybeMutex&
operator=(
const BaseProfilerMaybeMutex&) =
delete;
BaseProfilerMaybeMutex(BaseProfilerMaybeMutex&&) =
delete;
BaseProfilerMaybeMutex&
operator=(BaseProfilerMaybeMutex&&) =
delete;
~BaseProfilerMaybeMutex() =
default;
bool IsActivated()
const {
return mMaybeMutex.isSome(); }
[[nodiscard]]
bool IsActivatedAndLockedOnCurrentThread()
const {
if (!IsActivated()) {
// Not activated, so we can never be locked.
return false;
}
return mMaybeMutex->IsLockedOnCurrentThread();
}
void AssertCurrentThreadOwns()
const {
#ifdef DEBUG
if (IsActivated()) {
mMaybeMutex->AssertCurrentThreadOwns();
}
#endif // DEBUG
}
MOZ_PUSH_IGNORE_THREAD_SAFETY
void Lock() {
if (IsActivated()) {
mMaybeMutex->Lock();
}
}
void Unlock() {
if (IsActivated()) {
mMaybeMutex->Unlock();
}
}
MOZ_POP_THREAD_SAFETY
private:
Maybe<BaseProfilerMutex> mMaybeMutex;
};
// RAII class to lock a mutex.
class MOZ_RAII BaseProfilerMaybeAutoLock {
public:
explicit BaseProfilerMaybeAutoLock(BaseProfilerMaybeMutex& aMaybeMutex)
: mMaybeMutex(aMaybeMutex) {
mMaybeMutex.Lock();
}
BaseProfilerMaybeAutoLock(
const BaseProfilerMaybeAutoLock&) =
delete;
BaseProfilerMaybeAutoLock&
operator=(
const BaseProfilerMaybeAutoLock&) =
delete;
BaseProfilerMaybeAutoLock(BaseProfilerMaybeAutoLock&&) =
delete;
BaseProfilerMaybeAutoLock&
operator=(BaseProfilerMaybeAutoLock&&) =
delete;
~BaseProfilerMaybeAutoLock() { mMaybeMutex.Unlock(); }
private:
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
#endif // BaseProfilerDetail_h