[Memory Pressure] Add critical pressure signal for low disk space on Mac
When a system is critically low on disk space, browser performance can
degrade significantly as the OS may be unable to swap memory to disk
effectively.
This CL introduces a mechanism in SystemMemoryPressureEvaluator on macOS
to monitor for low disk space. It periodically checks the free space on
the volume containing the user's home directory, which serves as a
reliable proxy for the volume storing browser data.
If the available space falls below a configurable threshold (default
250MB), a MEMORY_PRESSURE_LEVEL_CRITICAL signal is dispatched. This
allows the browser to proactively reduce its memory footprint before
disk-related performance issues occur.
The feature is controlled by the MacCriticalDiskSpacePressure feature
flag, which is disabled by default.
Bug: 393346737
Change-Id: I2111c224fcd0734a0ad9084c2855fc945e4e8721
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6639007
Commit-Queue: Alex Attar <aattar@google.com>
Reviewed-by: Francois Pierre Doray <fdoray@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1476185}
diff --git a/components/memory_pressure/multi_source_memory_pressure_monitor_unittest.cc b/components/memory_pressure/multi_source_memory_pressure_monitor_unittest.cc
index 62b57655..cea7ade 100644
--- a/components/memory_pressure/multi_source_memory_pressure_monitor_unittest.cc
+++ b/components/memory_pressure/multi_source_memory_pressure_monitor_unittest.cc
@@ -20,8 +20,15 @@
}
TEST(MultiSourceMemoryPressureMonitorTest, RunDispatchCallback) {
+#if BUILDFLAG(IS_FUCHSIA)
+ // On Fuchsia, the previous SingleThreadTaskEnvironment was sufficient.
base::test::SingleThreadTaskEnvironment task_environment(
base::test::TaskEnvironment::MainThreadType::IO);
+#else
+ // On other platforms (like Mac), the full TaskEnvironment is needed for the
+ // ThreadPool.
+ base::test::TaskEnvironment task_environment;
+#endif
MultiSourceMemoryPressureMonitor monitor;
bool callback_called = false;
diff --git a/components/memory_pressure/system_memory_pressure_evaluator_mac.cc b/components/memory_pressure/system_memory_pressure_evaluator_mac.cc
index 45b4225..1da4316 100644
--- a/components/memory_pressure/system_memory_pressure_evaluator_mac.cc
+++ b/components/memory_pressure/system_memory_pressure_evaluator_mac.cc
@@ -9,6 +9,7 @@
#include <stddef.h>
#include <sys/sysctl.h>
+#include <algorithm>
#include <cmath>
#include "base/check_op.h"
@@ -16,7 +17,10 @@
#include "base/functional/bind.h"
#include "base/mac/mac_util.h"
#include "base/memory/memory_pressure_monitor.h"
+#include "base/path_service.h"
#include "base/task/sequenced_task_runner.h"
+#include "base/task/thread_pool.h"
+#include "base/time/time.h"
namespace memory_pressure::mac {
@@ -27,6 +31,28 @@
BASE_FEATURE(kSkipModerateMemoryPressureLevelMac,
"SkipModerateMemoryPressureLevelMac",
base::FEATURE_DISABLED_BY_DEFAULT);
+
+// This feature controls the critical memory pressure signal based on low disk
+// space. Disabling this feature turns off the disk space check entirely.
+BASE_FEATURE(kMacCriticalDiskSpacePressure,
+ "MacCriticalDiskSpacePressure",
+ base::FEATURE_DISABLED_BY_DEFAULT);
+
+// The default threshold in megabytes for the critical disk space pressure
+// signal.
+constexpr int kDefaultCriticalDiskSpaceMb = 250;
+const int64_t kBytesPerMb = 1024 * 1024;
+
+// Defines the threshold in megabytes for the critical disk space pressure
+// signal. This is a parameter for the kMacCriticalDiskSpacePressure feature.
+BASE_FEATURE_PARAM(int,
+ kMacCriticalDiskSpacePressureThresholdMB,
+ &kMacCriticalDiskSpacePressure,
+ "MacCriticalDiskSpacePressureThresholdMB",
+ kDefaultCriticalDiskSpaceMb);
+
+// How often to check for free disk space.
+constexpr base::TimeDelta kDiskSpaceCheckPeriod = base::Seconds(5);
} // namespace
base::MemoryPressureListener::MemoryPressureLevel
@@ -61,7 +87,18 @@
base::BindRepeating(&SystemMemoryPressureEvaluator::SendCurrentVote,
base::Unretained(this),
/*notify=*/true)),
+ disk_check_task_runner_(
+ base::ThreadPool::CreateSequencedTaskRunner({base::MayBlock()})),
weak_ptr_factory_(this) {
+ // A check for available disk space is necessary to generate a
+ // low-disk-space pressure signal.
+ //
+ // To ensure the correct disk volume is checked, this implementation uses
+ // the user's home directory path, retrieved via `base::PathService`. On
+ // macOS, the browser's data directory is a subdirectory of home, so this
+ // correctly targets the volume most relevant to browser performance.
+ base::PathService::Get(base::DIR_HOME, &user_data_dir_);
+
// WeakPtr needed because there is no guarantee that |this| is still be alive
// when the task posted to the TaskRunner or event handler runs.
base::WeakPtr<SystemMemoryPressureEvaluator> weak_this =
@@ -82,6 +119,15 @@
// Start monitoring the event source.
dispatch_resume(memory_level_event_source_.get());
}
+
+ if (base::FeatureList::IsEnabled(kMacCriticalDiskSpacePressure)) {
+ disk_space_check_timer_.Start(
+ FROM_HERE, kDiskSpaceCheckPeriod,
+ base::BindRepeating(&SystemMemoryPressureEvaluator::CheckDiskSpace,
+ weak_this));
+ // Perform an initial check on startup.
+ CheckDiskSpace();
+ }
}
SystemMemoryPressureEvaluator::~SystemMemoryPressureEvaluator() {
@@ -106,12 +152,55 @@
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Get the current macOS pressure level and convert to the corresponding
// Chrome pressure level.
- SetCurrentVote(MemoryPressureLevelForMacMemoryPressureLevel(
- GetMacMemoryPressureLevel()));
+ auto os_pressure_level =
+ MemoryPressureLevelForMacMemoryPressureLevel(GetMacMemoryPressureLevel());
+
+ // The effective pressure level is the most severe of the OS-reported level
+ // and our disk-space-derived level. If the disk pressure feature is disabled,
+ // `disk_pressure_vote_` will always be `NONE`.
+ auto effective_pressure_level =
+ std::max(os_pressure_level, disk_pressure_vote_);
+
+ SetCurrentVote(effective_pressure_level);
}
void SystemMemoryPressureEvaluator::OnMemoryPressureChanged() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ UpdatePressureAndManageNotifications();
+}
+
+void SystemMemoryPressureEvaluator::CheckDiskSpace() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ disk_check_task_runner_->PostTaskAndReplyWithResult(
+ FROM_HERE,
+ base::BindOnce(&base::SysInfo::AmountOfFreeDiskSpace, user_data_dir_),
+ base::BindOnce(&SystemMemoryPressureEvaluator::OnDiskSpaceCheckComplete,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+void SystemMemoryPressureEvaluator::OnDiskSpaceCheckComplete(
+ int64_t free_bytes) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ base::MemoryPressureListener::MemoryPressureLevel new_disk_vote =
+ base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE;
+
+ const int64_t threshold_mb = kMacCriticalDiskSpacePressureThresholdMB.Get();
+ // The minimum free disk space in MB before dispatching a critical memory
+ // pressure signal.
+ const int64_t critical_disk_space_bytes = threshold_mb * kBytesPerMb;
+
+ if (free_bytes != -1 && free_bytes < critical_disk_space_bytes) {
+ new_disk_vote =
+ base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL;
+ }
+
+ if (disk_pressure_vote_ != new_disk_vote) {
+ disk_pressure_vote_ = new_disk_vote;
+ UpdatePressureAndManageNotifications();
+ }
+}
+
+void SystemMemoryPressureEvaluator::UpdatePressureAndManageNotifications() {
// The OS has sent a notification that the memory pressure level has changed.
// Go through the normal memory pressure level checking mechanism so that
// |current_vote_| and UMA get updated to the current value.
diff --git a/components/memory_pressure/system_memory_pressure_evaluator_mac.h b/components/memory_pressure/system_memory_pressure_evaluator_mac.h
index c8cef53e..20949037 100644
--- a/components/memory_pressure/system_memory_pressure_evaluator_mac.h
+++ b/components/memory_pressure/system_memory_pressure_evaluator_mac.h
@@ -10,8 +10,10 @@
#include "base/apple/scoped_cftyperef.h"
#include "base/apple/scoped_dispatch_object.h"
+#include "base/files/file_path.h"
#include "base/message_loop/message_pump_apple.h"
#include "base/sequence_checker.h"
+#include "base/system/sys_info.h"
#include "base/timer/timer.h"
#include "components/memory_pressure/memory_pressure_voter.h"
#include "components/memory_pressure/system_memory_pressure_evaluator.h"
@@ -50,6 +52,16 @@
// Run |dispatch_callback| on memory pressure notifications from the OS.
void OnMemoryPressureChanged();
+ // Periodically checks the amount of free disk space.
+ void CheckDiskSpace();
+
+ // Callback for the disk space check. Updates the pressure level based on the
+ // amount of free space.
+ void OnDiskSpaceCheckComplete(int64_t free_bytes);
+
+ // Updates the pressure level and manages re-notification timers.
+ void UpdatePressureAndManageNotifications();
+
// The dispatch source that generates memory pressure change notifications.
base::apple::ScopedDispatchObject<dispatch_source_t>
memory_level_event_source_;
@@ -57,6 +69,19 @@
// Timer that will re-notify with the current vote at regular interval.
base::RepeatingTimer renotify_current_vote_timer_;
+ // A task runner that can be used for blocking tasks.
+ scoped_refptr<base::SequencedTaskRunner> disk_check_task_runner_;
+
+ // The timer that periodically triggers a disk space check.
+ base::RepeatingTimer disk_space_check_timer_;
+
+ // The pressure level calculated from the available disk space.
+ base::MemoryPressureListener::MemoryPressureLevel disk_pressure_vote_ =
+ base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE;
+
+ // The path to the user data directory, used for the disk space check.
+ base::FilePath user_data_dir_;
+
SEQUENCE_CHECKER(sequence_checker_);
base::WeakPtrFactory<SystemMemoryPressureEvaluator> weak_ptr_factory_;