Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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/. */
#ifndef ProfilerThreadRegistry_h
#define ProfilerThreadRegistry_h
#include "mozilla/BaseProfilerDetail.h"
#include "mozilla/ProfilerThreadRegistration.h"
#include "mozilla/Vector.h"
namespace mozilla::profiler {
class ThreadRegistry {
private:
using RegistryMutex = baseprofiler::detail::BaseProfilerSharedMutex;
using RegistryLockExclusive =
baseprofiler::detail::BaseProfilerAutoLockExclusive;
using RegistryLockShared = baseprofiler::detail::BaseProfilerAutoLockShared;
public:
// Aliases to data accessors (removing the ThreadRegistration prefix).
using UnlockedConstReader = ThreadRegistrationUnlockedConstReader;
using UnlockedConstReaderAndAtomicRW =
ThreadRegistrationUnlockedConstReaderAndAtomicRW;
using UnlockedRWForLockedProfiler =
ThreadRegistrationUnlockedRWForLockedProfiler;
using UnlockedReaderAndAtomicRWOnThread =
ThreadRegistrationUnlockedReaderAndAtomicRWOnThread;
using LockedRWFromAnyThread = ThreadRegistrationLockedRWFromAnyThread;
using LockedRWOnThread = ThreadRegistrationLockedRWOnThread;
// Off-thread access through the registry, providing the following data
// accessors: UnlockedConstReader, UnlockedConstReaderAndAtomicRW,
// UnlockedRWForLockedProfiler, and LockedRWFromAnyThread.
// (See ThreadRegistration class for ON-thread access.)
// Reference-like class pointing at a ThreadRegistration.
// It should only exist while sRegistryMutex is locked.
class OffThreadRef {
public:
// const UnlockedConstReader
[[nodiscard]] const UnlockedConstReader& UnlockedConstReaderCRef() const {
return mThreadRegistration->mData;
}
template <typename F>
auto WithUnlockedConstReader(F&& aF) const {
return std::forward<F>(aF)(UnlockedConstReaderCRef());
}
// const UnlockedConstReaderAndAtomicRW
[[nodiscard]] const UnlockedConstReaderAndAtomicRW&
UnlockedConstReaderAndAtomicRWCRef() const {
return mThreadRegistration->mData;
}
template <typename F>
auto WithUnlockedConstReaderAndAtomicRW(F&& aF) const {
return std::forward<F>(aF)(UnlockedConstReaderAndAtomicRWCRef());
}
// UnlockedConstReaderAndAtomicRW
[[nodiscard]] UnlockedConstReaderAndAtomicRW&
UnlockedConstReaderAndAtomicRWRef() {
return mThreadRegistration->mData;
}
template <typename F>
auto WithUnlockedConstReaderAndAtomicRW(F&& aF) {
return std::forward<F>(aF)(UnlockedConstReaderAndAtomicRWRef());
}
// const UnlockedRWForLockedProfiler
[[nodiscard]] const UnlockedRWForLockedProfiler&
UnlockedRWForLockedProfilerCRef() const {
return mThreadRegistration->mData;
}
template <typename F>
auto WithUnlockedRWForLockedProfiler(F&& aF) const {
return std::forward<F>(aF)(UnlockedRWForLockedProfilerCRef());
}
// UnlockedRWForLockedProfiler
[[nodiscard]] UnlockedRWForLockedProfiler&
UnlockedRWForLockedProfilerRef() {
return mThreadRegistration->mData;
}
template <typename F>
auto WithUnlockedRWForLockedProfiler(F&& aF) {
return std::forward<F>(aF)(UnlockedRWForLockedProfilerRef());
}
// const LockedRWFromAnyThread through ConstRWFromAnyThreadWithLock
class ConstRWFromAnyThreadWithLock {
public:
[[nodiscard]] const LockedRWFromAnyThread& DataCRef() const {
return mLockedRWFromAnyThread;
}
[[nodiscard]] const LockedRWFromAnyThread* operator->() const {
return &mLockedRWFromAnyThread;
}
ConstRWFromAnyThreadWithLock(
const LockedRWFromAnyThread& aLockedRWFromAnyThread,
ThreadRegistration::DataMutex& aDataMutex)
: mLockedRWFromAnyThread(aLockedRWFromAnyThread),
mDataLock(aDataMutex) {}
private:
const LockedRWFromAnyThread& mLockedRWFromAnyThread;
ThreadRegistration::DataLock mDataLock;
};
[[nodiscard]] ConstRWFromAnyThreadWithLock ConstLockedRWFromAnyThread()
const {
return ConstRWFromAnyThreadWithLock{mThreadRegistration->mData,
mThreadRegistration->mDataMutex};
}
template <typename F>
auto WithConstLockedRWFromAnyThread(F&& aF) const {
ConstRWFromAnyThreadWithLock lockedData = ConstLockedRWFromAnyThread();
return std::forward<F>(aF)(lockedData.DataCRef());
}
// LockedRWFromAnyThread through RWFromAnyThreadWithLock
class RWFromAnyThreadWithLock {
public:
[[nodiscard]] const LockedRWFromAnyThread& DataCRef() const {
return mLockedRWFromAnyThread;
}
[[nodiscard]] LockedRWFromAnyThread& DataRef() {
return mLockedRWFromAnyThread;
}
[[nodiscard]] const LockedRWFromAnyThread* operator->() const {
return &mLockedRWFromAnyThread;
}
[[nodiscard]] LockedRWFromAnyThread* operator->() {
return &mLockedRWFromAnyThread;
}
// In some situations, it may be useful to do some on-thread operations if
// we are indeed on this thread now. The lock is still held here; caller
// should not use this pointer longer than this RWFromAnyThreadWithLock.
[[nodiscard]] LockedRWOnThread* GetLockedRWOnThread() {
if (mLockedRWFromAnyThread.Info().ThreadId() ==
profiler_current_thread_id()) {
// mLockedRWFromAnyThread references a subclass of the
// ThreadRegistration's mData, so it's safe to downcast it to another
// hierarchy level of the object.
return &static_cast<LockedRWOnThread&>(mLockedRWFromAnyThread);
}
return nullptr;
}
private:
friend class OffThreadRef;
RWFromAnyThreadWithLock(LockedRWFromAnyThread& aLockedRWFromAnyThread,
ThreadRegistration::DataMutex& aDataMutex)
: mLockedRWFromAnyThread(aLockedRWFromAnyThread),
mDataLock(aDataMutex) {}
LockedRWFromAnyThread& mLockedRWFromAnyThread;
ThreadRegistration::DataLock mDataLock;
};
[[nodiscard]] RWFromAnyThreadWithLock GetLockedRWFromAnyThread() {
return RWFromAnyThreadWithLock{mThreadRegistration->mData,
mThreadRegistration->mDataMutex};
}
template <typename F>
auto WithLockedRWFromAnyThread(F&& aF) {
RWFromAnyThreadWithLock lockedData = GetLockedRWFromAnyThread();
return std::forward<F>(aF)(lockedData.DataRef());
}
private:
// Only ThreadRegistry should construct an OnThreadRef.
friend class ThreadRegistry;
explicit OffThreadRef(ThreadRegistration& aThreadRegistration)
: mThreadRegistration(&aThreadRegistration) {}
// If we have an ON-thread ref, it's safe to convert to an OFF-thread ref.
explicit OffThreadRef(ThreadRegistration::OnThreadRef aOnThreadRef)
: mThreadRegistration(aOnThreadRef.mThreadRegistration) {}
[[nodiscard]] bool IsPointingAt(
ThreadRegistration& aThreadRegistration) const {
return mThreadRegistration == &aThreadRegistration;
}
// Guaranted to be non-null by construction.
ThreadRegistration* mThreadRegistration;
};
// Lock the registry non-exclusively and allow iteration. E.g.:
// `for (OffThreadRef thread : LockedRegistry{}) { ... }`
// Do *not* export copies/references, as they could become dangling.
// Locking order: Profiler, ThreadRegistry, ThreadRegistration.
class LockedRegistry {
public:
LockedRegistry()
: mRegistryLock([]() -> RegistryMutex& {
MOZ_ASSERT(!IsRegistryMutexLockedOnCurrentThread(),
"Recursive locking detected");
// In DEBUG builds, *before* we attempt to lock sRegistryMutex, we
// want to check that the ThreadRegistration mutex is *not* locked
// on this thread, to avoid inversion deadlocks.
MOZ_ASSERT(!ThreadRegistration::IsDataMutexLockedOnCurrentThread());
return sRegistryMutex;
}()) {
ThreadRegistration::WithOnThreadRef(
[](ThreadRegistration::OnThreadRef aOnThreadRef) {
aOnThreadRef.mThreadRegistration
->mIsRegistryLockedSharedOnThisThread = true;
});
}
~LockedRegistry() {
ThreadRegistration::WithOnThreadRef(
[](ThreadRegistration::OnThreadRef aOnThreadRef) {
aOnThreadRef.mThreadRegistration
->mIsRegistryLockedSharedOnThisThread = false;
});
}
[[nodiscard]] const OffThreadRef* begin() const {
return sRegistryContainer.begin();
}
[[nodiscard]] OffThreadRef* begin() { return sRegistryContainer.begin(); }
[[nodiscard]] const OffThreadRef* end() const {
return sRegistryContainer.end();
}
[[nodiscard]] OffThreadRef* end() { return sRegistryContainer.end(); }
private:
RegistryLockShared mRegistryLock;
};
// Call `F(OffThreadRef)` for the given aThreadId.
template <typename F>
static void WithOffThreadRef(ProfilerThreadId aThreadId, F&& aF) {
for (OffThreadRef thread : LockedRegistry{}) {
if (thread.UnlockedConstReaderCRef().Info().ThreadId() == aThreadId) {
std::forward<F>(aF)(thread);
break;
}
}
}
template <typename F, typename FallbackReturn>
[[nodiscard]] static auto WithOffThreadRefOr(ProfilerThreadId aThreadId,
F&& aF,
FallbackReturn&& aFallbackReturn)
-> decltype(std::forward<F>(aF)(std::declval<OffThreadRef>())) {
for (OffThreadRef thread : LockedRegistry{}) {
if (thread.UnlockedConstReaderCRef().Info().ThreadId() == aThreadId) {
return std::forward<F>(aF)(thread);
}
}
return std::forward<FallbackReturn>(aFallbackReturn);
}
static size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) {
LockedRegistry lockedRegistry;
// "Ex" because we don't count static objects, but we count whatever they
// allocated on the heap.
size_t bytes = sRegistryContainer.sizeOfExcludingThis(aMallocSizeOf);
for (const OffThreadRef& offThreadRef : lockedRegistry) {
bytes +=
offThreadRef.mThreadRegistration->SizeOfExcludingThis(aMallocSizeOf);
}
return bytes;
}
static size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) {
return SizeOfExcludingThis(aMallocSizeOf);
}
[[nodiscard]] static bool IsRegistryMutexLockedOnCurrentThread() {
return sRegistryMutex.IsLockedExclusiveOnCurrentThread() ||
ThreadRegistration::WithOnThreadRefOr(
[](ThreadRegistration::OnThreadRef aOnThreadRef) {
return aOnThreadRef.mThreadRegistration
->mIsRegistryLockedSharedOnThisThread;
},
false);
}
private:
using RegistryContainer = Vector<OffThreadRef>;
static RegistryContainer sRegistryContainer;
// Mutex protecting the registry.
// Locking order: Profiler, ThreadRegistry, ThreadRegistration.
static RegistryMutex sRegistryMutex;
// Only allow ThreadRegistration to (un)register itself.
friend class ThreadRegistration;
static void Register(ThreadRegistration::OnThreadRef aOnThreadRef);
static void Unregister(ThreadRegistration::OnThreadRef aOnThreadRef);
};
} // namespace mozilla::profiler
#endif // ProfilerThreadRegistry_h