blob: c0bd53eb7b890392f55068993047058b92575b7f [file] [log] [blame]
// Copyright (c) 2021 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/common/partition_alloc_support.h"
#include <string>
#include "base/allocator/allocator_shim.h"
#include "base/allocator/buildflags.h"
#include "base/allocator/partition_allocator/partition_alloc_config.h"
#include "base/allocator/partition_allocator/partition_alloc_features.h"
#include "base/allocator/partition_allocator/starscan/pcscan.h"
#include "base/allocator/partition_allocator/starscan/pcscan_scheduling.h"
#include "base/allocator/partition_allocator/starscan/stack/stack.h"
#include "base/allocator/partition_allocator/thread_cache.h"
#include "base/bind.h"
#include "base/callback.h"
#include "base/feature_list.h"
#include "base/no_destructor.h"
#include "build/build_config.h"
#include "content/public/common/content_switches.h"
#if defined(OS_ANDROID)
#include "base/system/sys_info.h"
#endif
#if BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
#include "base/allocator/partition_allocator/memory_reclaimer.h"
#include "base/threading/thread_task_runner_handle.h"
#endif
namespace content {
namespace internal {
namespace {
void SetProcessNameForPCScan(const std::string& process_type) {
const char* name = [&process_type] {
if (process_type.empty()) {
// Empty means browser process.
return "Browser";
}
if (process_type == switches::kRendererProcess)
return "Renderer";
if (process_type == switches::kGpuProcess)
return "Gpu";
if (process_type == switches::kUtilityProcess)
return "Utility";
return static_cast<const char*>(nullptr);
}();
if (name) {
base::internal::PCScan::SetProcessName(name);
}
}
bool EnablePCScanForMallocPartitionsIfNeeded() {
#if BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) && defined(PA_ALLOW_PCSCAN)
using Config = base::internal::PCScan::InitConfig;
DCHECK(base::FeatureList::GetInstance());
if (base::FeatureList::IsEnabled(base::features::kPartitionAllocPCScan)) {
base::allocator::EnablePCScan({Config::WantedWriteProtectionMode::kEnabled,
Config::SafepointMode::kEnabled});
return true;
}
#endif // BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) && defined(PA_ALLOW_PCSCAN)
return false;
}
bool EnablePCScanForMallocPartitionsInBrowserProcessIfNeeded() {
#if BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) && defined(PA_ALLOW_PCSCAN)
using Config = base::internal::PCScan::InitConfig;
DCHECK(base::FeatureList::GetInstance());
if (base::FeatureList::IsEnabled(
base::features::kPartitionAllocPCScanBrowserOnly)) {
const Config::WantedWriteProtectionMode wp_mode =
base::FeatureList::IsEnabled(base::features::kPartitionAllocDCScan)
? Config::WantedWriteProtectionMode::kEnabled
: Config::WantedWriteProtectionMode::kDisabled;
#if !defined(PA_STARSCAN_UFFD_WRITE_PROTECTOR_SUPPORTED)
CHECK_EQ(Config::WantedWriteProtectionMode::kDisabled, wp_mode)
<< "DCScan is currently only supported on Linux based systems";
#endif
base::allocator::EnablePCScan({wp_mode, Config::SafepointMode::kEnabled});
return true;
}
#endif // BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) && defined(PA_ALLOW_PCSCAN)
return false;
}
bool EnablePCScanForMallocPartitionsInRendererProcessIfNeeded() {
#if BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) && defined(PA_ALLOW_PCSCAN)
using Config = base::internal::PCScan::InitConfig;
DCHECK(base::FeatureList::GetInstance());
if (base::FeatureList::IsEnabled(
base::features::kPartitionAllocPCScanRendererOnly)) {
const Config::WantedWriteProtectionMode wp_mode =
base::FeatureList::IsEnabled(base::features::kPartitionAllocDCScan)
? Config::WantedWriteProtectionMode::kEnabled
: Config::WantedWriteProtectionMode::kDisabled;
#if !defined(PA_STARSCAN_UFFD_WRITE_PROTECTOR_SUPPORTED)
CHECK_EQ(Config::WantedWriteProtectionMode::kDisabled, wp_mode)
<< "DCScan is currently only supported on Linux based systems";
#endif
base::allocator::EnablePCScan({wp_mode, Config::SafepointMode::kDisabled});
return true;
}
#endif // BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) && defined(PA_ALLOW_PCSCAN)
return false;
}
} // namespace
void ReconfigurePartitionForKnownProcess(const std::string& process_type) {
DCHECK_NE(process_type, switches::kZygoteProcess);
// TODO(keishi): Move the code to enable BRP back here after Finch
// experiments.
}
PartitionAllocSupport::PartitionAllocSupport() = default;
void PartitionAllocSupport::ReconfigureEarlyish(
const std::string& process_type) {
{
base::AutoLock scoped_lock(lock_);
// TODO(bartekn): Switch to DCHECK once confirmed there are no issues.
CHECK(!called_earlyish_)
<< "ReconfigureEarlyish was already called for process '"
<< established_process_type_ << "'; current process: '" << process_type
<< "'";
called_earlyish_ = true;
established_process_type_ = process_type;
}
if (process_type != switches::kZygoteProcess) {
ReconfigurePartitionForKnownProcess(process_type);
}
// These initializations are only relevant for PartitionAlloc-Everywhere
// builds.
#if BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
base::allocator::EnablePartitionAllocMemoryReclaimer();
#endif // BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
}
void PartitionAllocSupport::ReconfigureAfterZygoteFork(
const std::string& process_type) {
{
base::AutoLock scoped_lock(lock_);
// TODO(bartekn): Switch to DCHECK once confirmed there are no issues.
CHECK(!called_after_zygote_fork_)
<< "ReconfigureAfterZygoteFork was already called for process '"
<< established_process_type_ << "'; current process: '" << process_type
<< "'";
DCHECK(called_earlyish_)
<< "Attempt to call ReconfigureAfterZygoteFork without calling "
"ReconfigureEarlyish; current process: '"
<< process_type << "'";
DCHECK_EQ(established_process_type_, switches::kZygoteProcess)
<< "Attempt to call ReconfigureAfterZygoteFork while "
"ReconfigureEarlyish was called on non-zygote process '"
<< established_process_type_ << "'; current process: '" << process_type
<< "'";
called_after_zygote_fork_ = true;
established_process_type_ = process_type;
}
if (process_type != switches::kZygoteProcess) {
ReconfigurePartitionForKnownProcess(process_type);
}
}
void PartitionAllocSupport::ReconfigureAfterFeatureListInit(
const std::string& process_type) {
{
base::AutoLock scoped_lock(lock_);
// Avoid initializing more than once.
// TODO(bartekn): See if can be converted to (D)CHECK.
if (called_after_feature_list_init_) {
DCHECK_EQ(established_process_type_, process_type)
<< "ReconfigureAfterFeatureListInit was already called for process '"
<< established_process_type_ << "'; current process: '"
<< process_type << "'";
return;
}
DCHECK(called_earlyish_)
<< "Attempt to call ReconfigureAfterFeatureListInit without calling "
"ReconfigureEarlyish; current process: '"
<< process_type << "'";
DCHECK_NE(established_process_type_, switches::kZygoteProcess)
<< "Attempt to call ReconfigureAfterFeatureListInit without calling "
"ReconfigureAfterZygoteFork; current process: '"
<< process_type << "'";
DCHECK_EQ(established_process_type_, process_type)
<< "ReconfigureAfterFeatureListInit wasn't called for an already "
"established process '"
<< established_process_type_ << "'; current process: '" << process_type
<< "'";
called_after_feature_list_init_ = true;
}
DCHECK_NE(process_type, switches::kZygoteProcess);
// TODO(bartekn): Switch to DCHECK once confirmed there are no issues.
CHECK(base::FeatureList::GetInstance());
bool enable_brp = false;
#if BUILDFLAG(USE_BACKUP_REF_PTR)
if (base::FeatureList::IsEnabled(
base::features::kPartitionAllocBackupRefPtr)) {
// No specified process type means this is the Browser process.
enable_brp = process_type.empty();
if (base::features::kBackupRefPtrEnabledProcessesParam.Get() ==
base::features::BackupRefPtrEnabledProcesses::kBrowserAndRenderer) {
enable_brp |= process_type == switches::kRendererProcess;
}
base::allocator::ConfigurePartitionBackupRefPtrSupport(enable_brp);
}
#endif
#if BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
base::allocator::ReconfigurePartitionAllocLazyCommit();
#endif // BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
// Don't enable PCScan if BRP is enabled.
if (enable_brp)
return;
bool scan_enabled = EnablePCScanForMallocPartitionsIfNeeded();
// No specified process type means this is the Browser process.
if (process_type.empty()) {
scan_enabled = scan_enabled ||
EnablePCScanForMallocPartitionsInBrowserProcessIfNeeded();
}
if (process_type == switches::kRendererProcess) {
scan_enabled = scan_enabled ||
EnablePCScanForMallocPartitionsInRendererProcessIfNeeded();
}
if (scan_enabled) {
if (base::FeatureList::IsEnabled(
base::features::kPartitionAllocPCScanStackScanning)) {
#if BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
base::internal::PCScan::EnableStackScanning();
// Notify PCScan about the main thread.
base::internal::PCScan::NotifyThreadCreated(
base::internal::GetStackTop());
#endif
}
if (base::FeatureList::IsEnabled(
base::features::kPartitionAllocPCScanImmediateFreeing)) {
base::internal::PCScan::EnableImmediateFreeing();
}
if (base::FeatureList::IsEnabled(
base::features::kPartitionAllocPCScanEagerClearing)) {
base::internal::PCScan::SetClearType(
base::internal::PCScan::ClearType::kEager);
}
SetProcessNameForPCScan(process_type);
}
}
void PartitionAllocSupport::ReconfigureAfterTaskRunnerInit(
const std::string& process_type) {
{
base::AutoLock scoped_lock(lock_);
// Init only once.
if (called_after_thread_pool_init_)
return;
DCHECK_EQ(established_process_type_, process_type);
// Enforce ordering.
DCHECK(called_earlyish_);
DCHECK(called_after_feature_list_init_);
called_after_thread_pool_init_ = true;
}
#if defined(PA_THREAD_CACHE_SUPPORTED) && \
BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
// This should be called in specific processes, as the main thread is
// initialized later.
DCHECK(process_type != switches::kZygoteProcess);
auto& registry = base::internal::ThreadCacheRegistry::Instance();
registry.StartPeriodicPurge();
#if defined(OS_ANDROID)
// Lower thread cache limits to avoid stranding too much memory in the caches.
if (base::SysInfo::IsLowEndDevice()) {
registry.SetThreadCacheMultiplier(
base::internal::ThreadCache::kDefaultMultiplier / 2.);
}
#endif // defined(OS_ANDROID)
// Renderer processes are more performance-sensitive, increase thread cache
// limits.
if (process_type == switches::kRendererProcess &&
base::FeatureList::IsEnabled(
base::features::kPartitionAllocLargeThreadCacheSize)) {
#if defined(OS_ANDROID) && !defined(ARCH_CPU_64_BITS)
// Don't use a higher threshold on Android 32 bits, as long as memory usage
// is not carefully tuned. Only control the threshold here to avoid changing
// the rest of the code below.
// As of 2021, 64 bits Android devices are not memory constrained.
largest_cached_size_ =
base::internal::ThreadCacheLimits::kDefaultSizeThreshold;
#else
largest_cached_size_ =
base::internal::ThreadCacheLimits::kLargeSizeThreshold;
base::internal::ThreadCache::SetLargestCachedSize(
base::internal::ThreadCacheLimits::kLargeSizeThreshold);
#endif // defined(OS_ANDROID) && !defined(ARCH_CPU_64_BITS)
}
#endif // defined(PA_THREAD_CACHE_SUPPORTED) &&
// BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
if (base::FeatureList::IsEnabled(
base::features::kPartitionAllocPCScanMUAwareScheduler)) {
// Assign PCScan a task-based scheduling backend.
static base::NoDestructor<base::internal::MUAwareTaskBasedBackend>
mu_aware_task_based_backend{
base::internal::PCScan::scheduler(),
base::BindRepeating([](base::TimeDelta delay) {
base::internal::PCScan::PerformDelayedScan(delay);
})};
base::internal::PCScan::scheduler().SetNewSchedulingBackend(
*mu_aware_task_based_backend.get());
}
#if BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
base::PartitionAllocMemoryReclaimer::Instance()->Start(
base::ThreadTaskRunnerHandle::Get());
#endif
}
void PartitionAllocSupport::OnForegrounded() {
#if defined(PA_THREAD_CACHE_SUPPORTED) && \
BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
{
base::AutoLock scoped_lock(lock_);
if (established_process_type_ != switches::kRendererProcess)
return;
}
base::internal::ThreadCache::SetLargestCachedSize(largest_cached_size_);
#endif // defined(PA_THREAD_CACHE_SUPPORTED) &&
// BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
}
void PartitionAllocSupport::OnBackgrounded() {
#if defined(PA_THREAD_CACHE_SUPPORTED) && \
BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
{
base::AutoLock scoped_lock(lock_);
if (established_process_type_ != switches::kRendererProcess)
return;
}
// Performance matters less for background renderers, don't pay the memory
// cost.
//
// TODO(lizeb): Consider forcing a one-off thread cache purge.
base::internal::ThreadCache::SetLargestCachedSize(
base::internal::ThreadCacheLimits::kDefaultSizeThreshold);
#endif // defined(PA_THREAD_CACHE_SUPPORTED) &&
// BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
}
} // namespace internal
} // namespace content