blob: 9e39699ef2cb9c51816188cac0216afec9f96121 [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/renderer_host/spare_render_process_host_manager.h"
#include "base/check.h"
#include "base/debug/dump_without_crashing.h"
#include "base/memory/memory_pressure_monitor.h"
#include "base/no_destructor.h"
#include "content/browser/renderer_host/render_process_host_impl.h"
#include "content/browser/site_instance_impl.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/common/content_client.h"
namespace content {
SpareRenderProcessHostManager::SpareRenderProcessHostManager() = default;
SpareRenderProcessHostManager::~SpareRenderProcessHostManager() = default;
// static
SpareRenderProcessHostManager& SpareRenderProcessHostManager::GetInstance() {
static base::NoDestructor<SpareRenderProcessHostManager> s_instance;
return *s_instance;
}
void SpareRenderProcessHostManager::WarmupSpareRenderProcessHost(
BrowserContext* browser_context) {
if (delay_timer_) {
UMA_HISTOGRAM_TIMES("BrowserRenderProcessHost.SpareProcessDelayTime",
delay_timer_->Elapsed());
delay_timer_.reset();
}
if (spare_render_process_host_ &&
spare_render_process_host_->GetBrowserContext() == browser_context) {
DCHECK_EQ(browser_context->GetDefaultStoragePartition(),
spare_render_process_host_->GetStoragePartition());
return; // Nothing to warm up.
}
bool had_spare_renderer = !!spare_render_process_host_;
CleanupSpareRenderProcessHost();
UMA_HISTOGRAM_BOOLEAN(
"BrowserRenderProcessHost.SpareProcessEvictedOtherSpare",
had_spare_renderer);
// Don't create a spare renderer for a BrowserContext that is in the
// process of shutting down.
if (browser_context->ShutdownStarted()) {
// Create a crash dump to help us assess what scenarios trigger this
// path to be taken.
// TODO(acolwell): Remove this call once are confident we've eliminated
// any problematic callers.
base::debug::DumpWithoutCrashing();
return;
}
// Don't create a spare renderer if we're using --single-process or if we've
// got too many processes. See also ShouldTryToUseExistingProcessHost in
// this file.
if (RenderProcessHost::run_renderer_in_process() ||
RenderProcessHostImpl::GetProcessCountForLimit() >=
RenderProcessHostImpl::GetMaxRendererProcessCount()) {
return;
}
// Don't create a spare renderer when the system is under load. This is
// currently approximated by only looking at the memory pressure. See also
// https://crbug.com/852905.
auto* memory_monitor = base::MemoryPressureMonitor::Get();
if (memory_monitor &&
memory_monitor->GetCurrentPressureLevel() >=
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE) {
return;
}
process_startup_timer_ = std::make_unique<base::ElapsedTimer>();
spare_render_process_host_ = RenderProcessHostImpl::CreateRenderProcessHost(
browser_context, nullptr /* site_instance */);
spare_render_process_host_->AddObserver(this);
spare_render_process_host_->Init();
// The spare render process isn't ready, so wait and do the "spare render
// process changed" callback in RenderProcessReady().
}
void SpareRenderProcessHostManager::DeferredWarmupSpareRenderProcessHost(
BrowserContext* browser_context,
base::TimeDelta delay) {
if (delay == base::TimeDelta::Max()) {
return;
}
deferred_warmup_timer_.Start(
FROM_HERE, delay,
base::BindOnce(
[](SpareRenderProcessHostManager* self,
base::WeakPtr<BrowserContext> browser_context) {
// The browser context might have been destroyed when the timer
// fires.
if (browser_context) {
self->WarmupSpareRenderProcessHost(browser_context.get());
}
},
base::Unretained(this), browser_context->GetWeakPtr()));
}
RenderProcessHost*
SpareRenderProcessHostManager::MaybeTakeSpareRenderProcessHost(
BrowserContext* browser_context,
SiteInstanceImpl* site_instance) {
// Give embedder a chance to disable using a spare RenderProcessHost for
// certain SiteInstances. Some navigations, such as to NTP or extensions,
// require passing command-line flags to the renderer process at process
// launch time, but this cannot be done for spare RenderProcessHosts, which
// are started before it is known which navigation might use them. So, a
// spare RenderProcessHost should not be used in such cases.
//
// Note that exempting NTP and extensions from using the spare process might
// also happen via HasProcess check below (which returns true for
// process-per-site SiteInstances if the given process-per-site process
// already exists). Despite this potential overlap, it is important to do
// both kinds of checks (to account for other non-ntp/extension
// process-per-site scenarios + to work correctly even if
// ShouldUseSpareRenderProcessHost starts covering non-process-per-site
// scenarios).
bool embedder_allows_spare_usage =
GetContentClient()->browser()->ShouldUseSpareRenderProcessHost(
browser_context, site_instance->GetSiteInfo().site_url());
// The spare RenderProcessHost always launches with JIT enabled, so if JIT
// is disabled for the site then it's not possible to use this as the JIT
// policy will differ.
if (GetContentClient()->browser()->IsJitDisabledForSite(
browser_context, site_instance->GetSiteInfo().process_lock_url())) {
embedder_allows_spare_usage = false;
}
// We shouldn't use the spare if:
// 1. The SiteInstance has already got an associated process. This is
// important to avoid taking and then immediately discarding the spare
// for process-per-site scenarios (which the HasProcess call below
// accounts for). Note that HasProcess will return false and allow using
// the spare if the given process-per-site process hasn't been launched.
// 2. The SiteInstance has opted out of using the spare process.
bool site_instance_allows_spare_usage =
!site_instance->HasProcess() &&
site_instance->CanAssociateWithSpareProcess();
bool hosts_pdf_content = site_instance->GetSiteInfo().is_pdf();
// Get the StoragePartition for |site_instance|. Note that this might be
// different than the default StoragePartition for |browser_context|.
StoragePartition* site_storage =
browser_context->GetStoragePartition(site_instance);
// Log UMA metrics.
using SpareProcessMaybeTakeAction =
RenderProcessHostImpl::SpareProcessMaybeTakeAction;
SpareProcessMaybeTakeAction action =
SpareProcessMaybeTakeAction::kNoSparePresent;
if (!spare_render_process_host_) {
action = SpareProcessMaybeTakeAction::kNoSparePresent;
} else if (browser_context !=
spare_render_process_host_->GetBrowserContext()) {
action = SpareProcessMaybeTakeAction::kMismatchedBrowserContext;
} else if (!spare_render_process_host_->InSameStoragePartition(
site_storage)) {
action = SpareProcessMaybeTakeAction::kMismatchedStoragePartition;
} else if (!embedder_allows_spare_usage) {
action = SpareProcessMaybeTakeAction::kRefusedByEmbedder;
} else if (!site_instance_allows_spare_usage) {
action = SpareProcessMaybeTakeAction::kRefusedBySiteInstance;
} else if (hosts_pdf_content) {
action = SpareProcessMaybeTakeAction::kRefusedForPdfContent;
} else {
action = SpareProcessMaybeTakeAction::kSpareTaken;
}
UMA_HISTOGRAM_ENUMERATION(
"BrowserRenderProcessHost.SpareProcessMaybeTakeAction", action);
// Decide whether to take or drop the spare process.
RenderProcessHost* returned_process = nullptr;
if (spare_render_process_host_ &&
browser_context == spare_render_process_host_->GetBrowserContext() &&
spare_render_process_host_->InSameStoragePartition(site_storage) &&
!site_instance->IsGuest() && embedder_allows_spare_usage &&
site_instance_allows_spare_usage && !hosts_pdf_content) {
CHECK(spare_render_process_host_->HostHasNotBeenUsed());
// If the spare process ends up getting killed, the spare manager should
// discard the spare RPH, so if one exists, it should always be live here.
CHECK(spare_render_process_host_->IsInitializedAndNotDead());
DCHECK_EQ(SpareProcessMaybeTakeAction::kSpareTaken, action);
returned_process = spare_render_process_host_;
ReleaseSpareRenderProcessHost();
} else if (!RenderProcessHostImpl::IsSpareProcessKeptAtAllTimes()) {
// If the spare shouldn't be kept around, then discard it as soon as we
// find that the current spare was mismatched.
CleanupSpareRenderProcessHost();
} else if (RenderProcessHostImpl::GetProcessCountForLimit() >=
RenderProcessHostImpl::GetMaxRendererProcessCount()) {
// Drop the spare if we are at a process limit and the spare wasn't taken.
// This helps avoid process reuse.
CleanupSpareRenderProcessHost();
}
return returned_process;
}
void SpareRenderProcessHostManager::PrepareForFutureRequests(
BrowserContext* browser_context,
std::optional<base::TimeDelta> delay) {
if (RenderProcessHostImpl::IsSpareProcessKeptAtAllTimes()) {
// Always keep around a spare process for the most recently requested
// |browser_context|.
if (delay.has_value()) {
// The actual delay time is not necessarily `delay` because the
// process can be delayed until the page stops loading, in which case
// `delay` is TimeDelta::Max().
delay_timer_ = std::make_unique<base::ElapsedTimer>();
DeferredWarmupSpareRenderProcessHost(browser_context, *delay);
} else {
WarmupSpareRenderProcessHost(browser_context);
}
} else {
// Discard the ignored (probably non-matching) spare so as not to waste
// resources.
CleanupSpareRenderProcessHost();
}
}
void SpareRenderProcessHostManager::CleanupSpareRenderProcessHost() {
if (spare_render_process_host_) {
// Stop observing the process, to avoid getting notifications as a
// consequence of the Cleanup call below - such notification could call
// back into CleanupSpareRenderProcessHost leading to stack overflow.
spare_render_process_host_->RemoveObserver(this);
// Make sure the RenderProcessHost object gets destroyed.
if (!spare_render_process_host_->AreRefCountsDisabled()) {
spare_render_process_host_->Cleanup();
}
// Drop reference to the RenderProcessHost object.
spare_render_process_host_ = nullptr;
spare_render_process_host_changed_callback_list_.Notify(nullptr);
}
}
base::CallbackListSubscription
SpareRenderProcessHostManager::RegisterSpareRenderProcessHostChangedCallback(
const base::RepeatingCallback<void(RenderProcessHost*)>& cb) {
// Do an initial notification, as the subscriber will need to know what the
// current spare host is.
cb.Run(spare_render_process_host_.get());
return spare_render_process_host_changed_callback_list_.Add(cb);
}
void SpareRenderProcessHostManager::ReleaseSpareRenderProcessHost() {
CHECK(spare_render_process_host_);
spare_render_process_host_->RemoveObserver(this);
spare_render_process_host_ = nullptr;
spare_render_process_host_changed_callback_list_.Notify(nullptr);
}
void SpareRenderProcessHostManager::RenderProcessReady(
RenderProcessHost* host) {
CHECK_EQ(spare_render_process_host_, host);
CHECK(process_startup_timer_);
UMA_HISTOGRAM_TIMES("BrowserRenderProcessHost.SpareProcessStartupTime",
process_startup_timer_->Elapsed());
process_startup_timer_.reset();
spare_render_process_host_changed_callback_list_.Notify(
spare_render_process_host_);
}
void SpareRenderProcessHostManager::RenderProcessExited(
RenderProcessHost* host,
const ChildProcessTerminationInfo& info) {
CHECK_EQ(spare_render_process_host_, host);
CleanupSpareRenderProcessHost();
}
void SpareRenderProcessHostManager::RenderProcessHostDestroyed(
RenderProcessHost* host) {
CHECK_EQ(spare_render_process_host_, host);
ReleaseSpareRenderProcessHost();
}
} // namespace content