// Copyright 2024 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef BASE_ANDROID_PRE_FREEZE_BACKGROUND_MEMORY_TRIMMER_H_ #define BASE_ANDROID_PRE_FREEZE_BACKGROUND_MEMORY_TRIMMER_H_ #include #include "base/compiler_specific.h" #include "base/debug/proc_maps_linux.h" #include "base/feature_list.h" #include "base/functional/callback.h" #include "base/memory/post_delayed_memory_reduction_task.h" #include "base/no_destructor.h" #include "base/task/delayed_task_handle.h" #include "base/task/sequenced_task_runner.h" #include "base/timer/timer.h" namespace base::android { class MemoryPurgeManagerAndroid; BASE_EXPORT BASE_DECLARE_FEATURE(kOnPreFreezeMemoryTrim); BASE_EXPORT BASE_DECLARE_FEATURE(kShouldFreezeSelf); // Starting from Android U, apps are frozen shortly after being backgrounded // (with some exceptions). This causes some background tasks for reclaiming // resources in Chrome to not be run until Chrome is foregrounded again (which // completely defeats their purpose). // // To try to avoid this problem, we use the |PostDelayedBackgroundTask| found // below. Prior to Android U, this will simply post the task in the background // with the given delay. From Android U onwards, this will post the task in the // background with the given delay, but will run it sooner if we are about to // be frozen. class BASE_EXPORT PreFreezeBackgroundMemoryTrimmer { public: static PreFreezeBackgroundMemoryTrimmer& Instance(); ~PreFreezeBackgroundMemoryTrimmer() = delete; // Posts a delayed task. On versions of Android starting from U, may run the // task sooner if we are backgrounded. In this case, we run the task on the // correct task runner, ignoring the given delay. static void PostDelayedBackgroundTask( scoped_refptr task_runner, const base::Location& from_here, OnceCallback task, base::TimeDelta delay) LOCKS_EXCLUDED(lock_) { PostDelayedBackgroundTask( task_runner, from_here, BindOnce( [](OnceClosure task, MemoryReductionTaskContext called_from_prefreeze) { std::move(task).Run(); }, std::move(task)), delay); } static void PostDelayedBackgroundTask( scoped_refptr task_runner, const base::Location& from_here, OnceCallback task, base::TimeDelta delay) LOCKS_EXCLUDED(lock_); class PreFreezeMetric { public: virtual ~PreFreezeMetric(); // |Measure| should return an amount of memory in bytes, or nullopt if // unable to record the metric for any reason. It is called underneath a // lock, so it should be fast enough to avoid delays (the same lock is held // when unregistering metrics). virtual std::optional Measure() const = 0; const std::string& name() const LIFETIME_BOUND { return name_; } protected: friend class PreFreezeBackgroundMemoryTrimmer; explicit PreFreezeMetric(const std::string& name); private: const std::string name_; }; // Registers a new metric to record before and after PreFreeze. Callers are // responsible for making sure that the same metric is not registered // multiple times. // // See |PreFreezeMetric| for details on the metric itself. // // Each time |OnPreFreeze| is run, |metric->Measure()| will be called twice: // - Once directly before any tasks are run; and // - Once two seconds after the first time it was called. // // As an example, calling RegisterMemoryMetric(PrivateMemoryFootprintMetric) // in the Browser process would cause the following metrics to be recorded 2 // seconds after the next time |OnPreFreeze| is run: // - "Memory.PreFreeze2.Browser.PrivateMemoryFootprint.Before" // - "Memory.PreFreeze2.Browser.PrivateMemoryFootprint.After" // - "Memory.PreFreeze2.Browser.PrivateMemoryFootprint.Diff" // // See "Memory.PreFreeze2.{process_type}.{name}.{suffix}" for details on the // exact metrics. static void RegisterMemoryMetric(const PreFreezeMetric* metric) LOCKS_EXCLUDED(Instance().lock_); static void UnregisterMemoryMetric(const PreFreezeMetric* metric) LOCKS_EXCLUDED(Instance().lock_); static bool SelfCompactionIsSupported(); // Compacts the memory for the process, and returns the number of bytes // processed on success. static std::optional CompactSelf(); static void SetSupportsModernTrimForTesting(bool is_supported); static void ClearMetricsForTesting() LOCKS_EXCLUDED(lock_); size_t GetNumberOfPendingBackgroundTasksForTesting() const LOCKS_EXCLUDED(lock_); size_t GetNumberOfKnownMetricsForTesting() const LOCKS_EXCLUDED(lock_); size_t GetNumberOfValuesBeforeForTesting() const LOCKS_EXCLUDED(lock_); bool DidRegisterTasksForTesting() const; static void OnPreFreezeForTesting() LOCKS_EXCLUDED(lock_) { OnPreFreeze(); } static std::optional CompactRegion(debug::MappedMemoryRegion region); // Called when Chrome is about to be frozen. Runs as many delayed tasks as // possible immediately, before we are frozen. static void OnPreFreeze() LOCKS_EXCLUDED(lock_); static void OnSelfFreeze() LOCKS_EXCLUDED(lock_); static bool SupportsModernTrim(); static bool ShouldUseModernTrim(); static bool IsTrimMemoryBackgroundCritical(); private: friend class base::NoDestructor; friend jboolean JNI_MemoryPurgeManager_IsOnPreFreezeMemoryTrimEnabled( JNIEnv* env); friend class base::android::MemoryPurgeManagerAndroid; friend class base::OneShotDelayedBackgroundTimer; friend class PreFreezeBackgroundMemoryTrimmerTest; friend class PreFreezeSelfCompactionTest; // We use our own implementation here, based on |PostCancelableDelayedTask|, // rather than relying on something like |base::OneShotTimer|, since // |base::OneShotTimer| doesn't support things like immediately running our // task from a different sequence, and some |base::OneShotTimer| // functionality (e.g. |FireNow|) only works with the default task runner. class BackgroundTask final { public: static std::unique_ptr Create( scoped_refptr task_runner, const base::Location& from_here, OnceCallback task, base::TimeDelta delay); explicit BackgroundTask( scoped_refptr task_runner); ~BackgroundTask(); static void RunNow(std::unique_ptr background_task); void Run(MemoryReductionTaskContext from_pre_freeze); void CancelTask(); private: friend class PreFreezeBackgroundMemoryTrimmer; void Start(const Location& from_here, TimeDelta delay, OnceCallback task); void StartInternal(const Location& from_here, TimeDelta delay, OnceClosure task); scoped_refptr task_runner_; base::DelayedTaskHandle task_handle_; OnceCallback task_; }; PreFreezeBackgroundMemoryTrimmer(); static std::optional CompactMemory( std::vector regions); void RegisterMemoryMetricInternal(const PreFreezeMetric* metric) EXCLUSIVE_LOCKS_REQUIRED(lock_); void UnregisterMemoryMetricInternal(const PreFreezeMetric* metric) EXCLUSIVE_LOCKS_REQUIRED(lock_); static void UnregisterBackgroundTask(BackgroundTask*) LOCKS_EXCLUDED(lock_); void UnregisterBackgroundTaskInternal(BackgroundTask*) LOCKS_EXCLUDED(lock_); static void RegisterPrivateMemoryFootprintMetric() LOCKS_EXCLUDED(lock_); void RegisterPrivateMemoryFootprintMetricInternal() LOCKS_EXCLUDED(lock_); void PostDelayedBackgroundTaskInternal( scoped_refptr task_runner, const base::Location& from_here, OnceCallback task, base::TimeDelta delay) LOCKS_EXCLUDED(lock_); void PostDelayedBackgroundTaskModern( scoped_refptr task_runner, const base::Location& from_here, OnceCallback task, base::TimeDelta delay) LOCKS_EXCLUDED(lock_); BackgroundTask* PostDelayedBackgroundTaskModernHelper( scoped_refptr task_runner, const base::Location& from_here, OnceCallback task, base::TimeDelta delay) EXCLUSIVE_LOCKS_REQUIRED(lock_); void OnPreFreezeInternal() LOCKS_EXCLUDED(lock_); void PostMetricsTasksIfModern() EXCLUSIVE_LOCKS_REQUIRED(lock_); void PostMetricsTask() EXCLUSIVE_LOCKS_REQUIRED(lock_); void RecordMetrics() LOCKS_EXCLUDED(lock_); mutable base::Lock lock_; std::deque> background_tasks_ GUARDED_BY(lock_); std::vector metrics_ GUARDED_BY(lock_); // When a metrics task is posted (see |RecordMetrics|), the values of each // metric before any tasks are run are saved here. The "i"th entry corresponds // to the "i"th entry in |metrics_|. When there is no pending metrics task, // |values_before_| should be empty. std::vector> values_before_ GUARDED_BY(lock_); bool supports_modern_trim_; }; } // namespace base::android #endif // BASE_ANDROID_PRE_FREEZE_BACKGROUND_MEMORY_TRIMMER_H_