blob: 65376e8a7bd2d431774436d38d29360d28ecdb99 [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/allocator_shim_default_dispatch_to_partition_alloc.h"
#include "base/allocator/buildflags.h"
#include "base/allocator/partition_alloc_features.h"
#include "base/allocator/partition_alloc_support.h"
#include "base/allocator/partition_allocator/partition_alloc_config.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/location.h"
#include "base/memory/nonscannable_memory.h"
#include "base/no_destructor.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "content/public/common/content_switches.h"
#if BUILDFLAG(IS_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});
base::allocator::RegisterPCScanStatsReporter();
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});
base::allocator::RegisterPCScanStatsReporter();
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});
base::allocator::RegisterPCScanStatsReporter();
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;
[[maybe_unused]] bool split_main_partition = false;
[[maybe_unused]] bool use_dedicated_aligned_partition = false;
#if BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
#if BUILDFLAG(USE_BACKUP_REF_PTR)
bool process_affected_by_brp_flag = false;
if (base::FeatureList::IsEnabled(
base::features::kPartitionAllocBackupRefPtr)) {
// No specified process type means this is the Browser process.
switch (base::features::kBackupRefPtrEnabledProcessesParam.Get()) {
case base::features::BackupRefPtrEnabledProcesses::kBrowserOnly:
process_affected_by_brp_flag = process_type.empty();
break;
case base::features::BackupRefPtrEnabledProcesses::kBrowserAndRenderer:
process_affected_by_brp_flag =
process_type.empty() ||
(process_type == switches::kRendererProcess);
break;
case base::features::BackupRefPtrEnabledProcesses::kNonRenderer:
process_affected_by_brp_flag =
(process_type != switches::kRendererProcess);
break;
case base::features::BackupRefPtrEnabledProcesses::kAllProcesses:
process_affected_by_brp_flag = true;
break;
}
}
if (process_affected_by_brp_flag) {
switch (base::features::kBackupRefPtrModeParam.Get()) {
case base::features::BackupRefPtrMode::kDisabled:
// Do nothing. Equivalent to !IsEnabled(kPartitionAllocBackupRefPtr).
break;
case base::features::BackupRefPtrMode::kEnabled:
enable_brp = true;
split_main_partition = true;
#if !BUILDFLAG(PUT_REF_COUNT_IN_PREVIOUS_SLOT)
// AlignedAlloc relies on natural alignment offered by the allocator
// (see the comment inside PartitionRoot::AlignedAllocFlags). Any extras
// in front of the allocation will mess up that alignment. Such extras
// are used when BackupRefPtr is on, in which case, we need a separate
// partition, dedicated to handle only aligned allocations, where those
// extras are disabled. However, if the "previous slot" variant is used,
// no dedicated partition is needed, as the extras won't interfere with
// the alignment requirements.
use_dedicated_aligned_partition = true;
#endif
break;
case base::features::BackupRefPtrMode::kDisabledButSplitPartitions2Way:
split_main_partition = true;
break;
case base::features::BackupRefPtrMode::kDisabledButSplitPartitions3Way:
split_main_partition = true;
use_dedicated_aligned_partition = true;
break;
}
}
#endif // BUILDFLAG(USE_BACKUP_REF_PTR)
#endif // BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
#if BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
base::allocator::ConfigurePartitions(
base::allocator::EnableBrp(enable_brp),
base::allocator::SplitMainPartition(split_main_partition),
base::allocator::UseDedicatedAlignedPartition(
use_dedicated_aligned_partition),
base::allocator::AlternateBucketDistribution(base::FeatureList::IsEnabled(
base::features::kPartitionAllocUseAlternateDistribution)));
#endif // BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
// If BRP is not enabled, check if any of PCScan flags is enabled.
bool scan_enabled = false;
if (!enable_brp) {
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);
}
}
#if BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
// Non-quarantinable partition is dealing with hot V8's zone allocations.
// In case PCScan is enabled in Renderer, enable thread cache on this
// partition. At the same time, thread cache on the main(malloc) partition
// must be disabled, because only one partition can have it on.
if (scan_enabled && process_type == switches::kRendererProcess) {
base::internal::NonQuarantinableAllocator::Instance()
.root()
->EnableThreadCacheIfSupported();
} else {
base::internal::PartitionAllocMalloc::Allocator()
->EnableThreadCacheIfSupported();
}
if (base::FeatureList::IsEnabled(
base::features::kPartitionAllocLargeEmptySlotSpanRing)) {
base::internal::PartitionAllocMalloc::Allocator()
->EnableLargeEmptySlotSpanRing();
base::internal::PartitionAllocMalloc::AlignedAllocator()
->EnableLargeEmptySlotSpanRing();
}
#endif // BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
}
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);
base::allocator::StartThreadCachePeriodicPurge();
#if BUILDFLAG(IS_ANDROID)
// Lower thread cache limits to avoid stranding too much memory in the caches.
if (base::SysInfo::IsLowEndDevice()) {
::partition_alloc::ThreadCacheRegistry::Instance().SetThreadCacheMultiplier(
::partition_alloc::ThreadCache::kDefaultMultiplier / 2.);
}
#endif // BUILDFLAG(IS_ANDROID)
// Renderer processes are more performance-sensitive, increase thread cache
// limits.
if (process_type == switches::kRendererProcess &&
base::FeatureList::IsEnabled(
base::features::kPartitionAllocLargeThreadCacheSize)) {
largest_cached_size_ =
::partition_alloc::ThreadCacheLimits::kLargeSizeThreshold;
#if BUILDFLAG(IS_ANDROID) && defined(ARCH_CPU_32_BITS)
// Devices almost always report less physical memory than what they actually
// have, so anything above 3GiB will catch 4GiB and above.
if (base::SysInfo::AmountOfPhysicalMemory() <= int64_t{3500} * 1024 * 1024)
largest_cached_size_ =
::partition_alloc::ThreadCacheLimits::kDefaultSizeThreshold;
#endif // BUILDFLAG(IS_ANDROID) && !defined(ARCH_CPU_64_BITS)
::partition_alloc::ThreadCache::SetLargestCachedSize(largest_cached_size_);
}
#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::internal::PCScan::PerformDelayedScan};
base::internal::PCScan::scheduler().SetNewSchedulingBackend(
*mu_aware_task_based_backend.get());
}
#if BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
base::allocator::StartMemoryReclaimer(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;
}
::partition_alloc::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.
::partition_alloc::ThreadCache::SetLargestCachedSize(
::partition_alloc::ThreadCacheLimits::kDefaultSizeThreshold);
// In renderers, memory reclaim uses the "idle time" task runner to run
// periodic reclaim. This does not always run when the renderer is idle, and
// in particular after the renderer gets backgrounded. As a result, empty slot
// spans are potentially never decommitted. To mitigate that, run a one-off
// reclaim a few seconds later. Even if the renderer comes back to foreground
// in the meantime, the worst case is a few more system calls.
//
// TODO(lizeb): Remove once/if the behavior of idle tasks changes.
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, base::BindOnce([]() {
::partition_alloc::MemoryReclaimer::Instance()->ReclaimAll();
}),
base::Seconds(10));
#endif // defined(PA_THREAD_CACHE_SUPPORTED) &&
// BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
}
} // namespace internal
} // namespace content