/* -*- Mode: C++; tab-width: 8; 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/. */ #include "ProfilerParent.h" #include "nsProfiler.h" #include "mozilla/BaseProfilerDetail.h" #include "mozilla/ClearOnShutdown.h" #include "mozilla/DataMutex.h" #include "mozilla/IOInterposer.h" #include "mozilla/ipc/Endpoint.h" #include "mozilla/Maybe.h" #include "mozilla/ProfileBufferControlledChunkManager.h" #include "mozilla/RefPtr.h" #include "mozilla/Unused.h" #include "nsTArray.h" #include "nsThreadUtils.h" #include namespace mozilla { using namespace ipc; class ProfilerParentTracker; // This class is responsible for gathering updates from chunk managers in // different process, and request for the oldest chunks to be destroyed whenever // the given memory limit is reached. class ProfileBufferGlobalController final { public: explicit ProfileBufferGlobalController(size_t aMaximumBytes); ~ProfileBufferGlobalController(); void HandleChildChunkManagerUpdate( base::ProcessId aProcessId, ProfileBufferControlledChunkManager::Update&& aUpdate); static bool IsLockedOnCurrentThread(); private: void HandleChunkManagerNonFinalUpdate( base::ProcessId aProcessId, ProfileBufferControlledChunkManager::Update&& aUpdate, ProfileBufferControlledChunkManager& aParentChunkManager); const size_t mMaximumBytes; const base::ProcessId mParentProcessId = base::GetCurrentProcId(); struct ParentChunkManagerAndPendingUpdate { ProfileBufferControlledChunkManager* mChunkManager = nullptr; ProfileBufferControlledChunkManager::Update mPendingUpdate; }; static DataMutexBase sParentChunkManagerAndPendingUpdate; size_t mUnreleasedTotalBytes = 0; struct PidAndBytes { base::ProcessId mProcessId; size_t mBytes; // For searching and sorting. bool operator==(base::ProcessId aSearchedProcessId) const { return mProcessId == aSearchedProcessId; } bool operator==(const PidAndBytes& aOther) const { return mProcessId == aOther.mProcessId; } bool operator<(base::ProcessId aSearchedProcessId) const { return mProcessId < aSearchedProcessId; } bool operator<(const PidAndBytes& aOther) const { return mProcessId < aOther.mProcessId; } }; using PidAndBytesArray = nsTArray; PidAndBytesArray mUnreleasedBytesByPid; size_t mReleasedTotalBytes = 0; struct TimeStampAndBytesAndPid { TimeStamp mTimeStamp; size_t mBytes; base::ProcessId mProcessId; // For searching and sorting. bool operator==(const TimeStampAndBytesAndPid& aOther) const { // Sort first by timestamps, and then by pid in rare cases with the same // timestamps. return mTimeStamp == aOther.mTimeStamp && mProcessId == aOther.mProcessId; } bool operator<(const TimeStampAndBytesAndPid& aOther) const { // Sort first by timestamps, and then by pid in rare cases with the same // timestamps. return mTimeStamp < aOther.mTimeStamp || (MOZ_UNLIKELY(mTimeStamp == aOther.mTimeStamp) && mProcessId < aOther.mProcessId); } }; using TimeStampAndBytesAndPidArray = nsTArray; TimeStampAndBytesAndPidArray mReleasedChunksByTime; }; /* static */ DataMutexBase ProfileBufferGlobalController::sParentChunkManagerAndPendingUpdate{ "ProfileBufferGlobalController::sParentChunkManagerAndPendingUpdate"}; // This singleton class tracks live ProfilerParent's (meaning there's a current // connection with a child process). // It also knows when the local profiler is running. // And when both the profiler is running and at least one child is present, it // creates a ProfileBufferGlobalController and forwards chunk updates to it. class ProfilerParentTracker final { public: static void StartTracking(ProfilerParent* aParent); static void StopTracking(ProfilerParent* aParent); static void ProfilerStarted(uint32_t aEntries); static void ProfilerWillStopIfStarted(); template static void Enumerate(FuncType&& aIterFunc); template static void ForChild(base::ProcessId aChildPid, FuncType&& aIterFunc); static void ForwardChildChunkManagerUpdate( base::ProcessId aProcessId, ProfileBufferControlledChunkManager::Update&& aUpdate); ProfilerParentTracker(); ~ProfilerParentTracker(); private: static void EnsureInstance(); // List of parents for currently-connected child processes. nsTArray mProfilerParents; // If non-0, the parent profiler is running, with this limit (in number of // entries.) This is needed here, because the parent profiler may start // running before child processes are known (e.g., startup profiling). uint32_t mEntries = 0; // When the profiler is running and there is at least one parent-child // connection, this is the controller that should receive chunk updates. Maybe mMaybeController; // Singleton instance, created when first needed, destroyed at Firefox // shutdown. static UniquePtr sInstance; }; ProfileBufferGlobalController::ProfileBufferGlobalController( size_t aMaximumBytes) : mMaximumBytes(aMaximumBytes) { MOZ_RELEASE_ASSERT(NS_IsMainThread()); // This is the local chunk manager for this parent process, so updates can be // handled here. ProfileBufferControlledChunkManager* parentChunkManager = profiler_get_controlled_chunk_manager(); if (NS_WARN_IF(!parentChunkManager)) { return; } { auto lockedParentChunkManagerAndPendingUpdate = sParentChunkManagerAndPendingUpdate.Lock(); lockedParentChunkManagerAndPendingUpdate->mChunkManager = parentChunkManager; } parentChunkManager->SetUpdateCallback( [this](ProfileBufferControlledChunkManager::Update&& aUpdate) { MOZ_ASSERT(!aUpdate.IsNotUpdate(), "Update callback should never be given a non-update"); auto lockedParentChunkManagerAndPendingUpdate = sParentChunkManagerAndPendingUpdate.Lock(); if (aUpdate.IsFinal()) { // Final update of the parent. // We cannot keep the chunk manager, and there's no point handling // updates anymore. Do some cleanup now, to free resources before // we're destroyed. lockedParentChunkManagerAndPendingUpdate->mChunkManager = nullptr; lockedParentChunkManagerAndPendingUpdate->mPendingUpdate.Clear(); mUnreleasedTotalBytes = 0; mUnreleasedBytesByPid.Clear(); mReleasedTotalBytes = 0; mReleasedChunksByTime.Clear(); return; } if (!lockedParentChunkManagerAndPendingUpdate->mChunkManager) { // No chunk manager, ignore updates. return; } // Special handling of parent non-final updates: // These updates are coming from *this* process, and may originate from // scopes in any thread where any lock is held, so using other locks (to // e.g., dispatch tasks or send IPCs) could trigger a deadlock. Instead, // parent updates are stored locally and handled when the next // non-parent update needs handling, see HandleChildChunkManagerUpdate. lockedParentChunkManagerAndPendingUpdate->mPendingUpdate.Fold( std::move(aUpdate)); }); } ProfileBufferGlobalController ::~ProfileBufferGlobalController() { MOZ_RELEASE_ASSERT(NS_IsMainThread()); // Extract the parent chunk manager (if still set). // This means any update after this will be ignored. ProfileBufferControlledChunkManager* parentChunkManager = []() { auto lockedParentChunkManagerAndPendingUpdate = sParentChunkManagerAndPendingUpdate.Lock(); lockedParentChunkManagerAndPendingUpdate->mPendingUpdate.Clear(); return std::exchange( lockedParentChunkManagerAndPendingUpdate->mChunkManager, nullptr); }(); if (parentChunkManager) { // We had not received a final update yet, so the chunk manager is still // valid. Reset the callback in the chunk manager, this will immediately // invoke the callback with the final empty update; see handling above. parentChunkManager->SetUpdateCallback({}); } } void ProfileBufferGlobalController::HandleChildChunkManagerUpdate( base::ProcessId aProcessId, ProfileBufferControlledChunkManager::Update&& aUpdate) { MOZ_RELEASE_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aProcessId != mParentProcessId); MOZ_ASSERT(!aUpdate.IsNotUpdate(), "HandleChildChunkManagerUpdate should not be given a non-update"); auto lockedParentChunkManagerAndPendingUpdate = sParentChunkManagerAndPendingUpdate.Lock(); if (!lockedParentChunkManagerAndPendingUpdate->mChunkManager) { // No chunk manager, ignore updates. return; } if (aUpdate.IsFinal()) { // Final update in a child process, remove all traces of that process. size_t index = mUnreleasedBytesByPid.BinaryIndexOf(aProcessId); if (index != PidAndBytesArray::NoIndex) { // We already have a value for this pid. PidAndBytes& pidAndBytes = mUnreleasedBytesByPid[index]; mUnreleasedTotalBytes -= pidAndBytes.mBytes; mUnreleasedBytesByPid.RemoveElementAt(index); } size_t released = 0; mReleasedChunksByTime.RemoveElementsBy( [&released, aProcessId](const auto& chunk) { const bool match = chunk.mProcessId == aProcessId; if (match) { released += chunk.mBytes; } return match; }); if (released != 0) { mReleasedTotalBytes -= released; } // Total can only have gone down, so there's no need to check the limit. return; } // Non-final update in child process. // Before handling the child update, we may have pending updates from the // parent, which can be processed now since we're in an IPC callback outside // of any profiler-related scope. if (!lockedParentChunkManagerAndPendingUpdate->mPendingUpdate.IsNotUpdate()) { MOZ_ASSERT( !lockedParentChunkManagerAndPendingUpdate->mPendingUpdate.IsFinal()); HandleChunkManagerNonFinalUpdate( mParentProcessId, std::move(lockedParentChunkManagerAndPendingUpdate->mPendingUpdate), *lockedParentChunkManagerAndPendingUpdate->mChunkManager); lockedParentChunkManagerAndPendingUpdate->mPendingUpdate.Clear(); } HandleChunkManagerNonFinalUpdate( aProcessId, std::move(aUpdate), *lockedParentChunkManagerAndPendingUpdate->mChunkManager); } /* static */ bool ProfileBufferGlobalController::IsLockedOnCurrentThread() { return sParentChunkManagerAndPendingUpdate.Mutex().IsLockedOnCurrentThread(); } void ProfileBufferGlobalController::HandleChunkManagerNonFinalUpdate( base::ProcessId aProcessId, ProfileBufferControlledChunkManager::Update&& aUpdate, ProfileBufferControlledChunkManager& aParentChunkManager) { MOZ_ASSERT(!aUpdate.IsFinal()); size_t index = mUnreleasedBytesByPid.BinaryIndexOf(aProcessId); if (index != PidAndBytesArray::NoIndex) { // We already have a value for this pid. PidAndBytes& pidAndBytes = mUnreleasedBytesByPid[index]; mUnreleasedTotalBytes = mUnreleasedTotalBytes - pidAndBytes.mBytes + aUpdate.UnreleasedBytes(); pidAndBytes.mBytes = aUpdate.UnreleasedBytes(); } else { // New pid. mUnreleasedBytesByPid.InsertElementSorted( PidAndBytes{aProcessId, aUpdate.UnreleasedBytes()}); mUnreleasedTotalBytes += aUpdate.UnreleasedBytes(); } size_t destroyedReleased = 0; if (!aUpdate.OldestDoneTimeStamp().IsNull()) { size_t i = 0; for (; i < mReleasedChunksByTime.Length(); ++i) { if (mReleasedChunksByTime[i].mTimeStamp >= aUpdate.OldestDoneTimeStamp()) { break; } } // Here, i is the index of the first item that's at or after // aUpdate.mOldestDoneTimeStamp, so chunks from aProcessId before that have // been destroyed. while (i != 0) { --i; const TimeStampAndBytesAndPid& item = mReleasedChunksByTime[i]; if (item.mProcessId == aProcessId) { destroyedReleased += item.mBytes; mReleasedChunksByTime.RemoveElementAt(i); } } } size_t newlyReleased = 0; for (const ProfileBufferControlledChunkManager::ChunkMetadata& chunk : aUpdate.NewlyReleasedChunksRef()) { newlyReleased += chunk.mBufferBytes; mReleasedChunksByTime.InsertElementSorted(TimeStampAndBytesAndPid{ chunk.mDoneTimeStamp, chunk.mBufferBytes, aProcessId}); } mReleasedTotalBytes = mReleasedTotalBytes - destroyedReleased + newlyReleased; #ifdef DEBUG size_t totalReleased = 0; for (const TimeStampAndBytesAndPid& item : mReleasedChunksByTime) { totalReleased += item.mBytes; } MOZ_ASSERT(mReleasedTotalBytes == totalReleased); #endif // DEBUG std::vector toDestroy; while (mUnreleasedTotalBytes + mReleasedTotalBytes > mMaximumBytes && !mReleasedChunksByTime.IsEmpty()) { // We have reached the global memory limit, and there *are* released chunks // that can be destroyed. Start with the first one, which is the oldest. const TimeStampAndBytesAndPid& oldest = mReleasedChunksByTime[0]; mReleasedTotalBytes -= oldest.mBytes; if (oldest.mProcessId == mParentProcessId) { aParentChunkManager.DestroyChunksAtOrBefore(oldest.mTimeStamp); } else { ProfilerParentTracker::ForChild( oldest.mProcessId, [timestamp = oldest.mTimeStamp](ProfilerParent* profilerParent) { Unused << profilerParent->SendDestroyReleasedChunksAtOrBefore( timestamp); }); } mReleasedChunksByTime.RemoveElementAt(0); } } UniquePtr ProfilerParentTracker::sInstance; /* static */ void ProfilerParentTracker::EnsureInstance() { MOZ_RELEASE_ASSERT(NS_IsMainThread()); if (sInstance) { return; } sInstance = MakeUnique(); // The tracker should get destroyed before threads are shutdown, because its // destruction closes extant channels, which could trigger promise rejections // that need to be dispatched to other threads. ClearOnShutdown(&sInstance, ShutdownPhase::ShutdownThreads); } /* static */ void ProfilerParentTracker::StartTracking(ProfilerParent* aProfilerParent) { MOZ_RELEASE_ASSERT(NS_IsMainThread()); EnsureInstance(); if (sInstance->mMaybeController.isNothing() && sInstance->mEntries != 0) { // There is no controller yet, but the profiler has started. // Since we're adding a ProfilerParent, it's a good time to start // controlling the global memory usage of the profiler. // (And this helps delay the Controller startup, because the parent profiler // can start *very* early in the process, when some resources like threads // are not ready yet.) sInstance->mMaybeController.emplace(size_t(sInstance->mEntries) * 8u); } sInstance->mProfilerParents.AppendElement(aProfilerParent); } /* static */ void ProfilerParentTracker::StopTracking(ProfilerParent* aParent) { MOZ_RELEASE_ASSERT(NS_IsMainThread()); if (!sInstance) { return; } sInstance->mProfilerParents.RemoveElement(aParent); } /* static */ void ProfilerParentTracker::ProfilerStarted(uint32_t aEntries) { MOZ_RELEASE_ASSERT(NS_IsMainThread()); EnsureInstance(); sInstance->mEntries = aEntries; if (sInstance->mMaybeController.isNothing() && !sInstance->mProfilerParents.IsEmpty()) { // We are already tracking child processes, so it's a good time to start // controlling the global memory usage of the profiler. sInstance->mMaybeController.emplace(size_t(sInstance->mEntries) * 8u); } } /* static */ void ProfilerParentTracker::ProfilerWillStopIfStarted() { MOZ_RELEASE_ASSERT(NS_IsMainThread()); if (!sInstance) { return; } sInstance->mEntries = 0; sInstance->mMaybeController = Nothing{}; } template /* static */ void ProfilerParentTracker::Enumerate(FuncType&& aIterFunc) { MOZ_RELEASE_ASSERT(NS_IsMainThread()); if (!sInstance) { return; } for (ProfilerParent* profilerParent : sInstance->mProfilerParents) { if (!profilerParent->mDestroyed) { aIterFunc(profilerParent); } } } template /* static */ void ProfilerParentTracker::ForChild(base::ProcessId aChildPid, FuncType&& aIterFunc) { MOZ_RELEASE_ASSERT(NS_IsMainThread()); if (!sInstance) { return; } for (ProfilerParent* profilerParent : sInstance->mProfilerParents) { if (profilerParent->mChildPid == aChildPid) { if (!profilerParent->mDestroyed) { std::forward(aIterFunc)(profilerParent); } return; } } } /* static */ void ProfilerParentTracker::ForwardChildChunkManagerUpdate( base::ProcessId aProcessId, ProfileBufferControlledChunkManager::Update&& aUpdate) { MOZ_RELEASE_ASSERT(NS_IsMainThread()); if (!sInstance || sInstance->mMaybeController.isNothing()) { return; } MOZ_ASSERT(!aUpdate.IsNotUpdate(), "No process should ever send a non-update"); sInstance->mMaybeController->HandleChildChunkManagerUpdate( aProcessId, std::move(aUpdate)); } ProfilerParentTracker::ProfilerParentTracker() { MOZ_RELEASE_ASSERT(NS_IsMainThread()); MOZ_COUNT_CTOR(ProfilerParentTracker); } ProfilerParentTracker::~ProfilerParentTracker() { MOZ_RELEASE_ASSERT(NS_IsMainThread()); MOZ_COUNT_DTOR(ProfilerParentTracker); // Close the channels of any profiler parents that haven't been destroyed. for (ProfilerParent* profilerParent : mProfilerParents.Clone()) { if (!profilerParent->mDestroyed) { // Keep the object alive until the call to Close() has completed. // Close() will trigger a call to DeallocPProfilerParent. RefPtr actor = profilerParent; actor->Close(); } } } /* static */ Endpoint ProfilerParent::CreateForProcess( base::ProcessId aOtherPid) { MOZ_RELEASE_ASSERT(NS_IsMainThread()); Endpoint parent; Endpoint child; nsresult rv = PProfiler::CreateEndpoints(base::GetCurrentProcId(), aOtherPid, &parent, &child); if (NS_FAILED(rv)) { MOZ_CRASH("Failed to create top level actor for PProfiler!"); } RefPtr actor = new ProfilerParent(aOtherPid); if (!parent.Bind(actor)) { MOZ_CRASH("Failed to bind parent actor for PProfiler!"); } // mSelfRef will be cleared in DeallocPProfilerParent. actor->mSelfRef = actor; actor->Init(); return child; } ProfilerParent::ProfilerParent(base::ProcessId aChildPid) : mChildPid(aChildPid), mDestroyed(false) { MOZ_COUNT_CTOR(ProfilerParent); MOZ_RELEASE_ASSERT(NS_IsMainThread()); } void ProfilerParent::Init() { MOZ_RELEASE_ASSERT(NS_IsMainThread()); ProfilerParentTracker::StartTracking(this); // We propagated the profiler state from the parent process to the child // process through MOZ_PROFILER_STARTUP* environment variables. // However, the profiler state might have changed in this process since then, // and now that an active communication channel has been established with the // child process, it's a good time to sync up the two profilers again. int entries = 0; Maybe duration = Nothing(); double interval = 0; mozilla::Vector filters; uint32_t features; uint64_t activeBrowsingContextID; profiler_get_start_params(&entries, &duration, &interval, &features, &filters, &activeBrowsingContextID); if (entries != 0) { ProfilerInitParams ipcParams; ipcParams.enabled() = true; ipcParams.entries() = entries; ipcParams.duration() = duration; ipcParams.interval() = interval; ipcParams.features() = features; ipcParams.activeBrowsingContextID() = activeBrowsingContextID; for (uint32_t i = 0; i < filters.length(); ++i) { ipcParams.filters().AppendElement(filters[i]); } Unused << SendEnsureStarted(ipcParams); RequestChunkManagerUpdate(); } else { Unused << SendStop(); } } ProfilerParent::~ProfilerParent() { MOZ_COUNT_DTOR(ProfilerParent); MOZ_RELEASE_ASSERT(NS_IsMainThread()); ProfilerParentTracker::StopTracking(this); } /* static */ nsTArray> ProfilerParent::GatherProfiles() { if (!NS_IsMainThread()) { return nsTArray>(); } nsTArray> results; ProfilerParentTracker::Enumerate([&](ProfilerParent* profilerParent) { results.AppendElement(profilerParent->SendGatherProfile()); }); return results; } // Magic value for ProfileBufferChunkManagerUpdate::unreleasedBytes meaning // that this is a final update from a child. constexpr static uint64_t scUpdateUnreleasedBytesFINAL = uint64_t(-1); /* static */ ProfileBufferChunkManagerUpdate ProfilerParent::MakeFinalUpdate() { return ProfileBufferChunkManagerUpdate{ uint64_t(scUpdateUnreleasedBytesFINAL), 0, TimeStamp{}, nsTArray{}}; } /* static */ bool ProfilerParent::IsLockedOnCurrentThread() { return ProfileBufferGlobalController::IsLockedOnCurrentThread(); } void ProfilerParent::RequestChunkManagerUpdate() { if (mDestroyed) { return; } RefPtr updatePromise = SendAwaitNextChunkManagerUpdate(); updatePromise->Then( GetMainThreadSerialEventTarget(), __func__, [self = RefPtr(this)]( const ProfileBufferChunkManagerUpdate& aUpdate) { if (aUpdate.unreleasedBytes() == scUpdateUnreleasedBytesFINAL) { // Special value meaning it's the final update from that child. ProfilerParentTracker::ForwardChildChunkManagerUpdate( self->mChildPid, ProfileBufferControlledChunkManager::Update(nullptr)); } else { // Not the final update, translate it. std::vector chunks; if (!aUpdate.newlyReleasedChunks().IsEmpty()) { chunks.reserve(aUpdate.newlyReleasedChunks().Length()); for (const ProfileBufferChunkMetadata& chunk : aUpdate.newlyReleasedChunks()) { chunks.emplace_back(chunk.doneTimeStamp(), chunk.bufferBytes()); } } // Let the tracker handle it. ProfilerParentTracker::ForwardChildChunkManagerUpdate( self->mChildPid, ProfileBufferControlledChunkManager::Update( aUpdate.unreleasedBytes(), aUpdate.releasedBytes(), aUpdate.oldestDoneTimeStamp(), std::move(chunks))); // This was not a final update, so start a new request. self->RequestChunkManagerUpdate(); } }, [self = RefPtr(this)]( mozilla::ipc::ResponseRejectReason aReason) { // Rejection could be for a number of reasons, assume the child will // not respond anymore, so we pretend we received a final update. ProfilerParentTracker::ForwardChildChunkManagerUpdate( self->mChildPid, ProfileBufferControlledChunkManager::Update(nullptr)); }); } /* static */ void ProfilerParent::ProfilerStarted(nsIProfilerStartParams* aParams) { if (!NS_IsMainThread()) { return; } ProfilerInitParams ipcParams; double duration; ipcParams.enabled() = true; aParams->GetEntries(&ipcParams.entries()); aParams->GetDuration(&duration); if (duration > 0.0) { ipcParams.duration() = Some(duration); } else { ipcParams.duration() = Nothing(); } aParams->GetInterval(&ipcParams.interval()); aParams->GetFeatures(&ipcParams.features()); ipcParams.filters() = aParams->GetFilters().Clone(); aParams->GetActiveBrowsingContextID(&ipcParams.activeBrowsingContextID()); ProfilerParentTracker::ProfilerStarted(ipcParams.entries()); ProfilerParentTracker::Enumerate([&](ProfilerParent* profilerParent) { Unused << profilerParent->SendStart(ipcParams); profilerParent->RequestChunkManagerUpdate(); }); } /* static */ void ProfilerParent::ProfilerWillStopIfStarted() { if (!NS_IsMainThread()) { return; } ProfilerParentTracker::ProfilerWillStopIfStarted(); } /* static */ void ProfilerParent::ProfilerStopped() { if (!NS_IsMainThread()) { return; } ProfilerParentTracker::Enumerate([](ProfilerParent* profilerParent) { Unused << profilerParent->SendStop(); }); } /* static */ void ProfilerParent::ProfilerPaused() { if (!NS_IsMainThread()) { return; } ProfilerParentTracker::Enumerate([](ProfilerParent* profilerParent) { Unused << profilerParent->SendPause(); }); } /* static */ void ProfilerParent::ProfilerResumed() { if (!NS_IsMainThread()) { return; } ProfilerParentTracker::Enumerate([](ProfilerParent* profilerParent) { Unused << profilerParent->SendResume(); }); } /* static */ void ProfilerParent::ProfilerPausedSampling() { if (!NS_IsMainThread()) { return; } ProfilerParentTracker::Enumerate([](ProfilerParent* profilerParent) { Unused << profilerParent->SendPauseSampling(); }); } /* static */ void ProfilerParent::ProfilerResumedSampling() { if (!NS_IsMainThread()) { return; } ProfilerParentTracker::Enumerate([](ProfilerParent* profilerParent) { Unused << profilerParent->SendResumeSampling(); }); } /* static */ void ProfilerParent::ClearAllPages() { if (!NS_IsMainThread()) { return; } ProfilerParentTracker::Enumerate([](ProfilerParent* profilerParent) { Unused << profilerParent->SendClearAllPages(); }); } void ProfilerParent::ActorDestroy(ActorDestroyReason aActorDestroyReason) { MOZ_RELEASE_ASSERT(NS_IsMainThread()); mDestroyed = true; } void ProfilerParent::ActorDealloc() { mSelfRef = nullptr; } } // namespace mozilla