blob: 6b09929ab4e92d9a041e09e89ca361fcb418a38a [file] [log] [blame]
// Copyright 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/child_process_launcher.h"
#include <utility>
#include "base/bind.h"
#include "base/check_op.h"
#include "base/clang_profiling_buildflags.h"
#include "base/command_line.h"
#include "base/files/file_util.h"
#include "base/i18n/icu_util.h"
#include "base/metrics/histogram_macros.h"
#include "base/process/launch.h"
#include "base/process/process_metrics.h"
#include "base/time/time.h"
#include "base/tracing/protos/chrome_track_event.pbzero.h"
#include "build/build_config.h"
#include "content/public/browser/child_process_launcher_utils.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/sandboxed_process_launcher_delegate.h"
#if BUILDFLAG(IS_MAC)
#include "content/browser/child_process_task_port_provider_mac.h"
#endif
namespace content {
using internal::ChildProcessLauncherHelper;
void ChildProcessLauncherPriority::WriteIntoTrace(
perfetto::TracedProto<TraceProto> proto) const {
proto->set_is_backgrounded(is_background());
proto->set_has_pending_views(boost_for_pending_views);
#if BUILDFLAG(IS_ANDROID)
using PriorityProto = perfetto::protos::pbzero::ChildProcessLauncherPriority;
switch (importance) {
case ChildProcessImportance::IMPORTANT:
proto->set_importance(PriorityProto::IMPORTANCE_IMPORTANT);
break;
case ChildProcessImportance::NORMAL:
proto->set_importance(PriorityProto::IMPORTANCE_NORMAL);
break;
case ChildProcessImportance::MODERATE:
proto->set_importance(PriorityProto::IMPORTANCE_MODERATE);
break;
}
#endif
}
ChildProcessLauncherFileData::ChildProcessLauncherFileData() = default;
ChildProcessLauncherFileData::~ChildProcessLauncherFileData() = default;
#if BUILDFLAG(IS_ANDROID)
bool ChildProcessLauncher::Client::CanUseWarmUpConnection() {
return true;
}
#endif
ChildProcessLauncher::ChildProcessLauncher(
std::unique_ptr<SandboxedProcessLauncherDelegate> delegate,
std::unique_ptr<base::CommandLine> command_line,
int child_process_id,
Client* client,
mojo::OutgoingInvitation mojo_invitation,
const mojo::ProcessErrorCallback& process_error_callback,
std::unique_ptr<ChildProcessLauncherFileData> file_data,
bool terminate_on_shutdown)
: client_(client),
starting_(true),
#if defined(ADDRESS_SANITIZER) || defined(LEAK_SANITIZER) || \
defined(MEMORY_SANITIZER) || defined(THREAD_SANITIZER) || \
defined(UNDEFINED_SANITIZER) || BUILDFLAG(CLANG_PROFILING)
terminate_child_on_shutdown_(false)
#else
terminate_child_on_shutdown_(terminate_on_shutdown)
#endif
{
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
helper_ = base::MakeRefCounted<ChildProcessLauncherHelper>(
child_process_id, std::move(command_line), std::move(delegate),
weak_factory_.GetWeakPtr(), terminate_on_shutdown,
#if BUILDFLAG(IS_ANDROID)
client_->CanUseWarmUpConnection(),
#endif
std::move(mojo_invitation), process_error_callback, std::move(file_data));
helper_->StartLaunchOnClientThread();
}
ChildProcessLauncher::~ChildProcessLauncher() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (process_.process.IsValid() && terminate_child_on_shutdown_) {
// Client has gone away, so just kill the process.
ChildProcessLauncherHelper::ForceNormalProcessTerminationAsync(
std::move(process_));
}
}
void ChildProcessLauncher::SetProcessPriority(
const ChildProcessLauncherPriority& priority) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
base::Process to_pass = process_.process.Duplicate();
GetProcessLauncherTaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(
&ChildProcessLauncherHelper::SetProcessPriorityOnLauncherThread,
helper_, std::move(to_pass), priority));
}
void ChildProcessLauncher::Notify(ChildProcessLauncherHelper::Process process,
#if BUILDFLAG(IS_WIN)
DWORD last_error,
#endif
int error_code) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
starting_ = false;
process_ = std::move(process);
if (process_.process.IsValid()) {
process_start_time_ = base::TimeTicks::Now();
client_->OnProcessLaunched();
} else {
termination_info_.status = base::TERMINATION_STATUS_LAUNCH_FAILED;
termination_info_.exit_code = error_code;
#if BUILDFLAG(IS_WIN)
termination_info_.last_error = last_error;
#endif
// NOTE: May delete |this|.
client_->OnProcessLaunchFailed(error_code);
}
}
bool ChildProcessLauncher::IsStarting() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return starting_;
}
const base::Process& ChildProcessLauncher::GetProcess() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!starting_);
return process_.process;
}
ChildProcessTerminationInfo ChildProcessLauncher::GetChildTerminationInfo(
bool known_dead) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!process_.process.IsValid()) {
// Make sure to avoid using the default termination status if the process
// hasn't even started yet.
if (IsStarting())
termination_info_.status = base::TERMINATION_STATUS_STILL_RUNNING;
// Process doesn't exist, so return the cached termination info.
return termination_info_;
}
#if !BUILDFLAG(IS_ANDROID)
// GetTerminationInfo() invokes base::GetTerminationStatus() which reaps the
// zombie process info, after which it's not longer readable. This is why
// RecordProcessLifetimeMetrics() needs to called before that happens.
//
// Not done on Android since there sandboxed child processes run under a
// different user/uid, and the browser doesn't have permission to access
// the /proc dirs for the child processes. For the Android solution look at
// content/common/android/cpu_time_metrics.h.
RecordProcessLifetimeMetrics();
#endif
termination_info_ = helper_->GetTerminationInfo(process_, known_dead);
// POSIX: If the process crashed, then the kernel closed the socket for it and
// so the child has already died by the time we get here. Since
// GetTerminationInfo called waitpid with WNOHANG, it'll reap the process.
// However, if GetTerminationInfo didn't reap the child (because it was
// still running), we'll need to Terminate via ProcessWatcher. So we can't
// close the handle here.
if (termination_info_.status != base::TERMINATION_STATUS_STILL_RUNNING) {
process_.process.Exited(termination_info_.exit_code);
process_.process.Close();
}
return termination_info_;
}
bool ChildProcessLauncher::Terminate(int exit_code) {
return IsStarting() ? false
: ChildProcessLauncherHelper::TerminateProcess(
GetProcess(), exit_code);
}
void ChildProcessLauncher::RecordProcessLifetimeMetrics() {
// TODO(https://crbug.com/1224378): Record the lifetime of all child
// processes.
if (helper_->GetProcessType() != switches::kRendererProcess)
return;
#if BUILDFLAG(IS_MAC)
std::unique_ptr<base::ProcessMetrics> process_metrics =
base::ProcessMetrics::CreateProcessMetrics(
process_.process.Handle(),
ChildProcessTaskPortProvider::GetInstance());
#else
std::unique_ptr<base::ProcessMetrics> process_metrics =
base::ProcessMetrics::CreateProcessMetrics(process_.process.Handle());
#endif
const base::TimeDelta process_lifetime =
base::TimeTicks::Now() - process_start_time_;
const base::TimeDelta process_total_cpu_use =
process_metrics->GetCumulativeCPUUsage();
constexpr base::TimeDelta kShortLifetime = base::Minutes(1);
if (process_lifetime <= kShortLifetime) {
// Bucketing chosen by looking at AverageCPU2.RendererProcess in UMA. Only
// a renderer at the 99.9th percentile of this metric would overflow.
UMA_HISTOGRAM_CUSTOM_TIMES("Renderer.TotalCPUUse2.ShortLived",
process_total_cpu_use, base::Milliseconds(1),
base::Seconds(30), 100);
} else {
// Bucketing chosen by looking at AverageCPU2.RendererProcess and
// Renderer.ProcessLifetime values in UMA. Only a renderer at the 99th
// percentile of both of those values combined will overflow.
UMA_HISTOGRAM_CUSTOM_TIMES("Renderer.TotalCPUUse2.LongLived",
process_total_cpu_use, base::Milliseconds(1),
base::Hours(3), 100);
}
// Global measurement. Bucketing identical to LongLivedRenders.
UMA_HISTOGRAM_CUSTOM_TIMES("Renderer.TotalCPUUse2", process_total_cpu_use,
base::Milliseconds(1), base::Hours(3), 100);
}
// static
bool ChildProcessLauncher::TerminateProcess(const base::Process& process,
int exit_code) {
return ChildProcessLauncherHelper::TerminateProcess(process, exit_code);
}
#if BUILDFLAG(IS_ANDROID)
void ChildProcessLauncher::DumpProcessStack() {
base::Process to_pass = process_.process.Duplicate();
GetProcessLauncherTaskRunner()->PostTask(
FROM_HERE, base::BindOnce(&ChildProcessLauncherHelper::DumpProcessStack,
helper_, std::move(to_pass)));
}
#endif
ChildProcessLauncher::Client* ChildProcessLauncher::ReplaceClientForTest(
Client* client) {
Client* ret = client_;
client_ = client;
return ret;
}
bool ChildProcessLauncherPriority::is_background() const {
return !visible && !has_media_stream && !boost_for_pending_views &&
!has_foreground_service_worker;
}
bool ChildProcessLauncherPriority::operator==(
const ChildProcessLauncherPriority& other) const {
return visible == other.visible &&
has_media_stream == other.has_media_stream &&
has_foreground_service_worker == other.has_foreground_service_worker &&
frame_depth == other.frame_depth &&
intersects_viewport == other.intersects_viewport &&
boost_for_pending_views == other.boost_for_pending_views
#if BUILDFLAG(IS_ANDROID)
&& importance == other.importance
#endif
;
}
} // namespace content