fune/dom/media/ipc/RDDProcessManager.cpp
Gabriele Svelto aa43fa218e Bug 1831092 - Use the new pull-based API for all crash annotations and remove the global annotations table r=jgilbert,necko-reviewers,media-playback-reviewers,profiler-reviewers,win-reviewers,padenot,handyman,afranchuk,valentin,alwu,sotaro
This changes comes with several different refactorings all rolled into one,
unfotunately I couldn't find a way to pull them apart:
- First of all annotations now can either recorded (that is, we copy the value
  and have the crash reporting code own the copy) or registered. Several
  annotations are changed to use this functionality so that we don't need to
  update them as their value change.
- The code in the exception handler is modified to read the annotations from
  the mozannotation_client crate. This has the unfortunate side-effect that
  we need three different bits of code to serialize them: one for annotations
  read from a child process, one for reading annotations from the main process
  outside of the exception handler and one for reading annotations from the
  main process within the exception handler. As we move to fully
  out-of-process crash reporting the last two methods will go away.
- The mozannotation_client crate now doesn't record annotation types anymore.
  I realized as I was working on this that storing types at runtime has two
  issues: the first one is that buggy code might change the type of an
  annotation (that is record it under two different types at two different
  moments), the second issue is that types might become corrupt during a
  crash, so better enforce them at annotation-writing time. The end result is
  that the mozannotation_* crates now only store byte buffers, track the
  format the data is stored in (null-terminated string, fixed size buffer,
  etc...) but not the type of data each annotation is supposed to contain.
- Which brings us to the next change: concrete types for annotations are now
  enforced when they're written out. If an annotation doesn't match the
  expected type it's skipped. Storing an annotation with the wrong type will
  also trigger an assertion in debug builds.

Differential Revision: https://phabricator.services.mozilla.com/D195248
2024-03-04 10:24:43 +00:00

413 lines
13 KiB
C++

/* -*- 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 "RDDProcessManager.h"
#include "RDDChild.h"
#include "RDDProcessHost.h"
#include "mozilla/MemoryReportingProcess.h"
#include "mozilla/Preferences.h"
#include "mozilla/RemoteDecoderManagerChild.h"
#include "mozilla/RemoteDecoderManagerParent.h"
#include "mozilla/StaticPrefs_media.h"
#include "mozilla/SyncRunnable.h" // for LaunchRDDProcess
#include "mozilla/dom/ContentParent.h"
#include "mozilla/gfx/GPUProcessManager.h"
#include "mozilla/ipc/Endpoint.h"
#include "mozilla/ipc/ProcessChild.h"
#include "mozilla/layers/CompositorThread.h"
#include "mozilla/layers/VideoBridgeParent.h"
#include "nsAppRunner.h"
#include "nsContentUtils.h"
namespace mozilla {
using namespace gfx;
using namespace layers;
static StaticAutoPtr<RDDProcessManager> sRDDSingleton;
static bool sRDDProcessShutdown = false;
bool RDDProcessManager::IsShutdown() const {
MOZ_ASSERT(NS_IsMainThread());
return sRDDProcessShutdown || !sRDDSingleton;
}
RDDProcessManager* RDDProcessManager::Get() { return sRDDSingleton; }
void RDDProcessManager::Initialize() {
MOZ_ASSERT(XRE_IsParentProcess());
sRDDSingleton = new RDDProcessManager();
}
void RDDProcessManager::Shutdown() { sRDDSingleton = nullptr; }
void RDDProcessManager::RDDProcessShutdown() {
MOZ_ASSERT(NS_IsMainThread());
sRDDProcessShutdown = true;
if (sRDDSingleton) {
sRDDSingleton->DestroyProcess();
}
}
RDDProcessManager::RDDProcessManager()
: mObserver(new Observer(this)), mTaskFactory(this) {
MOZ_COUNT_CTOR(RDDProcessManager);
// Start listening for pref changes so we can
// forward them to the process once it is running.
nsContentUtils::RegisterShutdownObserver(mObserver);
Preferences::AddStrongObserver(mObserver, "");
}
RDDProcessManager::~RDDProcessManager() {
MOZ_COUNT_DTOR(RDDProcessManager);
MOZ_ASSERT(NS_IsMainThread());
// The RDD process should have already been shut down.
MOZ_ASSERT(!mProcess && !mRDDChild);
}
NS_IMPL_ISUPPORTS(RDDProcessManager::Observer, nsIObserver);
RDDProcessManager::Observer::Observer(RDDProcessManager* aManager)
: mManager(aManager) {}
NS_IMETHODIMP
RDDProcessManager::Observer::Observe(nsISupports* aSubject, const char* aTopic,
const char16_t* aData) {
if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
mManager->OnXPCOMShutdown();
} else if (!strcmp(aTopic, "nsPref:changed")) {
mManager->OnPreferenceChange(aData);
}
return NS_OK;
}
void RDDProcessManager::OnXPCOMShutdown() {
MOZ_ASSERT(NS_IsMainThread());
nsContentUtils::UnregisterShutdownObserver(mObserver);
Preferences::RemoveObserver(mObserver, "");
}
void RDDProcessManager::OnPreferenceChange(const char16_t* aData) {
MOZ_ASSERT(NS_IsMainThread());
if (!mProcess) {
// Process hasn't been launched yet
return;
}
// We know prefs are ASCII here.
NS_LossyConvertUTF16toASCII strData(aData);
mozilla::dom::Pref pref(strData, /* isLocked */ false,
/* isSanitized */ false, Nothing(), Nothing());
Preferences::GetPreference(&pref, GeckoProcessType_RDD,
/* remoteType */ ""_ns);
if (!!mRDDChild) {
MOZ_ASSERT(mQueuedPrefs.IsEmpty());
mRDDChild->SendPreferenceUpdate(pref);
} else if (IsRDDProcessLaunching()) {
mQueuedPrefs.AppendElement(pref);
}
}
RefPtr<GenericNonExclusivePromise> RDDProcessManager::LaunchRDDProcess() {
MOZ_ASSERT(NS_IsMainThread());
if (IsShutdown()) {
return GenericNonExclusivePromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE,
__func__);
}
if (mNumProcessAttempts && !StaticPrefs::media_rdd_retryonfailure_enabled()) {
// We failed to start the RDD process earlier, abort now.
return GenericNonExclusivePromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE,
__func__);
}
if (mLaunchRDDPromise && mProcess) {
return mLaunchRDDPromise;
}
std::vector<std::string> extraArgs;
ipc::ProcessChild::AddPlatformBuildID(extraArgs);
// The subprocess is launched asynchronously, so we
// wait for the promise to be resolved to acquire the IPDL actor.
mProcess = new RDDProcessHost(this);
if (!mProcess->Launch(extraArgs)) {
mNumProcessAttempts++;
DestroyProcess();
return GenericNonExclusivePromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE,
__func__);
}
mLaunchRDDPromise = mProcess->LaunchPromise()->Then(
GetMainThreadSerialEventTarget(), __func__,
[this](bool) {
if (IsShutdown()) {
return GenericNonExclusivePromise::CreateAndReject(
NS_ERROR_NOT_AVAILABLE, __func__);
}
if (IsRDDProcessDestroyed()) {
return GenericNonExclusivePromise::CreateAndReject(
NS_ERROR_NOT_AVAILABLE, __func__);
}
mRDDChild = mProcess->GetActor();
mProcessToken = mProcess->GetProcessToken();
// Flush any pref updates that happened during
// launch and weren't included in the blobs set
// up in LaunchRDDProcess.
for (const mozilla::dom::Pref& pref : mQueuedPrefs) {
Unused << NS_WARN_IF(!mRDDChild->SendPreferenceUpdate(pref));
}
mQueuedPrefs.Clear();
CrashReporter::RecordAnnotationCString(
CrashReporter::Annotation::RDDProcessStatus, "Running");
if (!CreateVideoBridge()) {
mNumProcessAttempts++;
DestroyProcess();
return GenericNonExclusivePromise::CreateAndReject(
NS_ERROR_NOT_AVAILABLE, __func__);
}
return GenericNonExclusivePromise::CreateAndResolve(true, __func__);
},
[this](nsresult aError) {
if (Get()) {
mNumProcessAttempts++;
DestroyProcess();
}
return GenericNonExclusivePromise::CreateAndReject(aError, __func__);
});
return mLaunchRDDPromise;
}
auto RDDProcessManager::EnsureRDDProcessAndCreateBridge(
base::ProcessId aOtherProcess, dom::ContentParentId aParentId)
-> RefPtr<EnsureRDDPromise> {
return InvokeAsync(
GetMainThreadSerialEventTarget(), __func__,
[aOtherProcess, aParentId, this]() -> RefPtr<EnsureRDDPromise> {
return LaunchRDDProcess()->Then(
GetMainThreadSerialEventTarget(), __func__,
[aOtherProcess, aParentId, this]() {
if (IsShutdown()) {
return EnsureRDDPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE,
__func__);
}
ipc::Endpoint<PRemoteDecoderManagerChild> endpoint;
if (!CreateContentBridge(aOtherProcess, aParentId, &endpoint)) {
return EnsureRDDPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE,
__func__);
}
mNumProcessAttempts = 0;
return EnsureRDDPromise::CreateAndResolve(std::move(endpoint),
__func__);
},
[](nsresult aError) {
return EnsureRDDPromise::CreateAndReject(aError, __func__);
});
});
}
bool RDDProcessManager::IsRDDProcessLaunching() {
MOZ_ASSERT(NS_IsMainThread());
return !!mProcess && !mRDDChild;
}
bool RDDProcessManager::IsRDDProcessDestroyed() const {
MOZ_ASSERT(NS_IsMainThread());
return !mRDDChild && !mProcess;
}
void RDDProcessManager::OnProcessUnexpectedShutdown(RDDProcessHost* aHost) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mProcess && mProcess == aHost);
mNumUnexpectedCrashes++;
DestroyProcess();
}
void RDDProcessManager::NotifyRemoteActorDestroyed(
const uint64_t& aProcessToken) {
if (!NS_IsMainThread()) {
RefPtr<Runnable> task = mTaskFactory.NewRunnableMethod(
&RDDProcessManager::NotifyRemoteActorDestroyed, aProcessToken);
NS_DispatchToMainThread(task.forget());
return;
}
if (mProcessToken != aProcessToken) {
// This token is for an older process; we can safely ignore it.
return;
}
// One of the bridged top-level actors for the RDD process has been
// prematurely terminated, and we're receiving a notification. This
// can happen if the ActorDestroy for a bridged protocol fires
// before the ActorDestroy for PRDDChild.
OnProcessUnexpectedShutdown(mProcess);
}
void RDDProcessManager::DestroyProcess() {
MOZ_ASSERT(NS_IsMainThread());
if (!mProcess) {
return;
}
mProcess->Shutdown();
mProcessToken = 0;
mProcess = nullptr;
mRDDChild = nullptr;
mQueuedPrefs.Clear();
CrashReporter::RecordAnnotationCString(
CrashReporter::Annotation::RDDProcessStatus, "Destroyed");
}
bool RDDProcessManager::CreateContentBridge(
base::ProcessId aOtherProcess, dom::ContentParentId aParentId,
ipc::Endpoint<PRemoteDecoderManagerChild>* aOutRemoteDecoderManager) {
MOZ_ASSERT(NS_IsMainThread());
if (IsRDDProcessDestroyed()) {
MOZ_LOG(sPDMLog, LogLevel::Debug,
("RDD shutdown before creating content bridge"));
return false;
}
ipc::Endpoint<PRemoteDecoderManagerParent> parentPipe;
ipc::Endpoint<PRemoteDecoderManagerChild> childPipe;
nsresult rv = PRemoteDecoderManager::CreateEndpoints(
mRDDChild->OtherPid(), aOtherProcess, &parentPipe, &childPipe);
if (NS_FAILED(rv)) {
MOZ_LOG(sPDMLog, LogLevel::Debug,
("Could not create content remote decoder: %d", int(rv)));
return false;
}
mRDDChild->SendNewContentRemoteDecoderManager(std::move(parentPipe),
aParentId);
*aOutRemoteDecoderManager = std::move(childPipe);
return true;
}
bool RDDProcessManager::CreateVideoBridge() {
MOZ_ASSERT(NS_IsMainThread());
ipc::Endpoint<PVideoBridgeParent> parentPipe;
ipc::Endpoint<PVideoBridgeChild> childPipe;
GPUProcessManager* gpuManager = GPUProcessManager::Get();
base::ProcessId gpuProcessPid =
gpuManager ? gpuManager->GPUProcessPid() : base::kInvalidProcessId;
// Build content device data first; this ensure that the GPU process is fully
// ready.
ContentDeviceData contentDeviceData;
gfxPlatform::GetPlatform()->BuildContentDeviceData(&contentDeviceData);
// The child end is the producer of video frames; the parent end is the
// consumer.
base::ProcessId childPid = RDDProcessPid();
base::ProcessId parentPid = gpuProcessPid != base::kInvalidProcessId
? gpuProcessPid
: base::GetCurrentProcId();
nsresult rv = PVideoBridge::CreateEndpoints(parentPid, childPid, &parentPipe,
&childPipe);
if (NS_FAILED(rv)) {
MOZ_LOG(sPDMLog, LogLevel::Debug,
("Could not create video bridge: %d", int(rv)));
return false;
}
mRDDChild->SendInitVideoBridge(std::move(childPipe),
mNumUnexpectedCrashes == 0, contentDeviceData);
if (gpuProcessPid != base::kInvalidProcessId) {
gpuManager->InitVideoBridge(std::move(parentPipe),
VideoBridgeSource::RddProcess);
} else {
VideoBridgeParent::Open(std::move(parentPipe),
VideoBridgeSource::RddProcess);
}
return true;
}
base::ProcessId RDDProcessManager::RDDProcessPid() {
MOZ_ASSERT(NS_IsMainThread());
base::ProcessId rddPid =
mRDDChild ? mRDDChild->OtherPid() : base::kInvalidProcessId;
return rddPid;
}
class RDDMemoryReporter : public MemoryReportingProcess {
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RDDMemoryReporter, override)
bool IsAlive() const override { return !!GetChild(); }
bool SendRequestMemoryReport(
const uint32_t& aGeneration, const bool& aAnonymize,
const bool& aMinimizeMemoryUsage,
const Maybe<ipc::FileDescriptor>& aDMDFile) override {
RDDChild* child = GetChild();
if (!child) {
return false;
}
return child->SendRequestMemoryReport(aGeneration, aAnonymize,
aMinimizeMemoryUsage, aDMDFile);
}
int32_t Pid() const override {
if (RDDChild* child = GetChild()) {
return (int32_t)child->OtherPid();
}
return 0;
}
private:
RDDChild* GetChild() const {
if (RDDProcessManager* rddpm = RDDProcessManager::Get()) {
if (RDDChild* child = rddpm->GetRDDChild()) {
return child;
}
}
return nullptr;
}
protected:
~RDDMemoryReporter() = default;
};
RefPtr<MemoryReportingProcess> RDDProcessManager::GetProcessMemoryReporter() {
if (!mProcess || !mProcess->IsConnected()) {
return nullptr;
}
return new RDDMemoryReporter();
}
RefPtr<PRDDChild::TestTriggerMetricsPromise>
RDDProcessManager::TestTriggerMetrics() {
if (!NS_WARN_IF(!mRDDChild)) {
return mRDDChild->SendTestTriggerMetrics();
}
return PRDDChild::TestTriggerMetricsPromise::CreateAndReject(
ipc::ResponseRejectReason::SendError, __func__);
}
} // namespace mozilla