summaryrefslogtreecommitdiffstats
path: root/tools/profiler/public/ProfilerThreadRegistry.h
diff options
context:
space:
mode:
Diffstat (limited to 'tools/profiler/public/ProfilerThreadRegistry.h')
-rw-r--r--tools/profiler/public/ProfilerThreadRegistry.h321
1 files changed, 321 insertions, 0 deletions
diff --git a/tools/profiler/public/ProfilerThreadRegistry.h b/tools/profiler/public/ProfilerThreadRegistry.h
new file mode 100644
index 0000000000..4d0fd3ef68
--- /dev/null
+++ b/tools/profiler/public/ProfilerThreadRegistry.h
@@ -0,0 +1,321 @@
+/* -*- 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