зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1651102 - Safely delay handling of parent profile buffer updates - r=canaltinova
Profile buffer updates could be triggered from a number of locations, including scopes where profiler and/or system locks are held, making deadlocks possible if profiler and/or system function are called. So instead of dispatching updates to the main thread (which may use OS task queue functions), we fold updates into a static storage. When child updates arrive, we can safely handle parent updates as well. Child updates are assumed to arrive regularly enough to properly handle parent updates. But in the worst case, if no updates came from children, it should mean nothing is happening, and in any case we wouldn't know how the memory is used elsewhere. Note that the chunk manager still enforces local limits automatically, so memory usage would still get limited. Differential Revision: https://phabricator.services.mozilla.com/D83746
This commit is contained in:
Родитель
2a75e1761c
Коммит
7ad0136123
|
@ -9,6 +9,7 @@
|
|||
#include "nsProfiler.h"
|
||||
|
||||
#include "mozilla/ClearOnShutdown.h"
|
||||
#include "mozilla/DataMutex.h"
|
||||
#include "mozilla/IOInterposer.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/ProfileBufferControlledChunkManager.h"
|
||||
|
@ -17,6 +18,8 @@
|
|||
#include "nsTArray.h"
|
||||
#include "nsThreadUtils.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
using namespace ipc;
|
||||
|
@ -32,18 +35,28 @@ class ProfileBufferGlobalController final {
|
|||
|
||||
~ProfileBufferGlobalController();
|
||||
|
||||
void HandleChunkManagerUpdate(
|
||||
void HandleChildChunkManagerUpdate(
|
||||
base::ProcessId aProcessId,
|
||||
ProfileBufferControlledChunkManager::Update&& aUpdate);
|
||||
|
||||
private:
|
||||
void HandleChunkManagerNonFinalUpdate(
|
||||
base::ProcessId aProcessId,
|
||||
ProfileBufferControlledChunkManager::Update&& aUpdate,
|
||||
ProfileBufferControlledChunkManager& aParentChunkManager);
|
||||
|
||||
const size_t mMaximumBytes;
|
||||
|
||||
const base::ProcessId mParentProcessId = base::GetCurrentProcId();
|
||||
|
||||
// Set to null when we receive the final empty update.
|
||||
ProfileBufferControlledChunkManager* mParentChunkManager =
|
||||
profiler_get_controlled_chunk_manager();
|
||||
struct ParentChunkManagerAndPendingUpdate {
|
||||
ProfileBufferControlledChunkManager* mChunkManager = nullptr;
|
||||
ProfileBufferControlledChunkManager::Update mPendingUpdate;
|
||||
};
|
||||
|
||||
DataMutex<ParentChunkManagerAndPendingUpdate>
|
||||
mParentChunkManagerAndPendingUpdate{
|
||||
"ProfileBufferGlobalController::mParentChunkManagerAndPendingUpdate"};
|
||||
|
||||
size_t mUnreleasedTotalBytes = 0;
|
||||
|
||||
|
@ -112,7 +125,7 @@ class ProfilerParentTracker final {
|
|||
template <typename FuncType>
|
||||
static void ForChild(base::ProcessId aChildPid, FuncType&& aIterFunc);
|
||||
|
||||
static void ForwardChunkManagerUpdate(
|
||||
static void ForwardChildChunkManagerUpdate(
|
||||
base::ProcessId aProcessId,
|
||||
ProfileBufferControlledChunkManager::Update&& aUpdate);
|
||||
|
||||
|
@ -143,65 +156,94 @@ 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.
|
||||
if (NS_WARN_IF(!mParentChunkManager)) {
|
||||
ProfileBufferControlledChunkManager* parentChunkManager =
|
||||
profiler_get_controlled_chunk_manager();
|
||||
|
||||
if (NS_WARN_IF(!parentChunkManager)) {
|
||||
return;
|
||||
}
|
||||
mParentChunkManager->SetUpdateCallback(
|
||||
[parentProcessId = mParentProcessId](
|
||||
ProfileBufferControlledChunkManager::Update&& aUpdate) {
|
||||
|
||||
{
|
||||
auto lockedParentChunkManagerAndPendingUpdate =
|
||||
mParentChunkManagerAndPendingUpdate.Lock();
|
||||
lockedParentChunkManagerAndPendingUpdate->mChunkManager =
|
||||
parentChunkManager;
|
||||
}
|
||||
|
||||
parentChunkManager->SetUpdateCallback(
|
||||
[this](ProfileBufferControlledChunkManager::Update&& aUpdate) {
|
||||
MOZ_ASSERT(!aUpdate.IsNotUpdate(),
|
||||
"Update callback should never be given a non-update");
|
||||
// Always dispatch the update, to reduce the time spent in the callback,
|
||||
// and to avoid potential re-entrancy issues.
|
||||
// Handled by the ProfilerParentTracker singleton, because the
|
||||
// Controller could be destroyed in the meantime.
|
||||
Unused << NS_DispatchToMainThread(NS_NewRunnableFunction(
|
||||
"ChunkManagerUpdate parent callback",
|
||||
[parentProcessId, update = std::move(aUpdate)]() mutable {
|
||||
ProfilerParentTracker::ForwardChunkManagerUpdate(
|
||||
parentProcessId, std::move(update));
|
||||
}));
|
||||
auto lockedParentChunkManagerAndPendingUpdate =
|
||||
mParentChunkManagerAndPendingUpdate.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());
|
||||
if (mParentChunkManager) {
|
||||
// We haven't handled a final update, 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.
|
||||
mParentChunkManager->SetUpdateCallback({});
|
||||
// Extract the parent chunk manager (if still set).
|
||||
// This means any update after this will be ignored.
|
||||
ProfileBufferControlledChunkManager* parentChunkManager = [this]() {
|
||||
auto lockedParentChunkManagerAndPendingUpdate =
|
||||
mParentChunkManagerAndPendingUpdate.Lock();
|
||||
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::HandleChunkManagerUpdate(
|
||||
void ProfileBufferGlobalController::HandleChildChunkManagerUpdate(
|
||||
base::ProcessId aProcessId,
|
||||
ProfileBufferControlledChunkManager::Update&& aUpdate) {
|
||||
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (!mParentChunkManager) {
|
||||
MOZ_ASSERT(aProcessId != mParentProcessId);
|
||||
|
||||
MOZ_ASSERT(!aUpdate.IsNotUpdate(),
|
||||
"HandleChildChunkManagerUpdate should not be given a non-update");
|
||||
|
||||
auto lockedParentChunkManagerAndPendingUpdate =
|
||||
mParentChunkManagerAndPendingUpdate.Lock();
|
||||
if (!lockedParentChunkManagerAndPendingUpdate->mChunkManager) {
|
||||
// No chunk manager, ignore updates.
|
||||
return;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(!aUpdate.IsNotUpdate(),
|
||||
"HandleChunkManagerUpdate should not be given a non-update");
|
||||
|
||||
if (aUpdate.IsFinal()) {
|
||||
if (aProcessId == mParentProcessId) {
|
||||
// This was the final update in the parent process, 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.
|
||||
mParentChunkManager = nullptr;
|
||||
mUnreleasedTotalBytes = 0;
|
||||
mUnreleasedBytesByPid.Clear();
|
||||
mReleasedTotalBytes = 0;
|
||||
mReleasedChunksByTime.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
// Final update in a child process, remove all traces of it.
|
||||
// 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.
|
||||
|
@ -227,7 +269,31 @@ void ProfileBufferGlobalController::HandleChunkManagerUpdate(
|
|||
return;
|
||||
}
|
||||
|
||||
// Non-final update in parent or child process.
|
||||
// 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);
|
||||
}
|
||||
|
||||
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) {
|
||||
|
@ -291,7 +357,7 @@ void ProfileBufferGlobalController::HandleChunkManagerUpdate(
|
|||
const TimeStampAndBytesAndPid& oldest = mReleasedChunksByTime[0];
|
||||
mReleasedTotalBytes -= oldest.mBytes;
|
||||
if (oldest.mProcessId == mParentProcessId) {
|
||||
mParentChunkManager->DestroyChunksAtOrBefore(oldest.mTimeStamp);
|
||||
aParentChunkManager.DestroyChunksAtOrBefore(oldest.mTimeStamp);
|
||||
} else {
|
||||
ProfilerParentTracker::ForChild(
|
||||
oldest.mProcessId,
|
||||
|
@ -410,7 +476,7 @@ void ProfilerParentTracker::ForChild(base::ProcessId aChildPid,
|
|||
}
|
||||
|
||||
/* static */
|
||||
void ProfilerParentTracker::ForwardChunkManagerUpdate(
|
||||
void ProfilerParentTracker::ForwardChildChunkManagerUpdate(
|
||||
base::ProcessId aProcessId,
|
||||
ProfileBufferControlledChunkManager::Update&& aUpdate) {
|
||||
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
||||
|
@ -420,8 +486,8 @@ void ProfilerParentTracker::ForwardChunkManagerUpdate(
|
|||
|
||||
MOZ_ASSERT(!aUpdate.IsNotUpdate(),
|
||||
"No process should ever send a non-update");
|
||||
sInstance->mMaybeController->HandleChunkManagerUpdate(aProcessId,
|
||||
std::move(aUpdate));
|
||||
sInstance->mMaybeController->HandleChildChunkManagerUpdate(
|
||||
aProcessId, std::move(aUpdate));
|
||||
}
|
||||
|
||||
ProfilerParentTracker::ProfilerParentTracker() {
|
||||
|
@ -561,7 +627,7 @@ void ProfilerParent::RequestChunkManagerUpdate() {
|
|||
const ProfileBufferChunkManagerUpdate& aUpdate) {
|
||||
if (aUpdate.unreleasedBytes() == scUpdateUnreleasedBytesFINAL) {
|
||||
// Special value meaning it's the final update from that child.
|
||||
ProfilerParentTracker::ForwardChunkManagerUpdate(
|
||||
ProfilerParentTracker::ForwardChildChunkManagerUpdate(
|
||||
self->mChildPid,
|
||||
ProfileBufferControlledChunkManager::Update(nullptr));
|
||||
} else {
|
||||
|
@ -576,7 +642,7 @@ void ProfilerParent::RequestChunkManagerUpdate() {
|
|||
}
|
||||
}
|
||||
// Let the tracker handle it.
|
||||
ProfilerParentTracker::ForwardChunkManagerUpdate(
|
||||
ProfilerParentTracker::ForwardChildChunkManagerUpdate(
|
||||
self->mChildPid,
|
||||
ProfileBufferControlledChunkManager::Update(
|
||||
aUpdate.unreleasedBytes(), aUpdate.releasedBytes(),
|
||||
|
@ -589,7 +655,7 @@ void ProfilerParent::RequestChunkManagerUpdate() {
|
|||
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::ForwardChunkManagerUpdate(
|
||||
ProfilerParentTracker::ForwardChildChunkManagerUpdate(
|
||||
self->mChildPid,
|
||||
ProfileBufferControlledChunkManager::Update(nullptr));
|
||||
});
|
||||
|
|
Загрузка…
Ссылка в новой задаче