| // 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 <deque> |
| |
| #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); |
| |
| // 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<base::SequencedTaskRunner> task_runner, |
| const base::Location& from_here, |
| OnceCallback<void(void)> 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<base::SequencedTaskRunner> task_runner, |
| const base::Location& from_here, |
| OnceCallback<void(MemoryReductionTaskContext)> 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<uint64_t> Measure() const = 0; |
| |
| const std::string& name() const { 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 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(); } |
| |
| // 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 bool SupportsModernTrim(); |
| static bool ShouldUseModernTrim(); |
| static bool IsTrimMemoryBackgroundCritical(); |
| |
| private: |
| friend class base::NoDestructor<PreFreezeBackgroundMemoryTrimmer>; |
| friend jboolean JNI_MemoryPurgeManager_IsOnPreFreezeMemoryTrimEnabled( |
| JNIEnv* env); |
| friend class base::android::MemoryPurgeManagerAndroid; |
| friend class base::OneShotDelayedBackgroundTimer; |
| |
| // 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<BackgroundTask> Create( |
| scoped_refptr<base::SequencedTaskRunner> task_runner, |
| const base::Location& from_here, |
| OnceCallback<void(MemoryReductionTaskContext)> task, |
| base::TimeDelta delay); |
| |
| explicit BackgroundTask( |
| scoped_refptr<base::SequencedTaskRunner> task_runner); |
| ~BackgroundTask(); |
| |
| static void RunNow(std::unique_ptr<BackgroundTask> background_task); |
| |
| void Run(MemoryReductionTaskContext from_pre_freeze); |
| |
| void CancelTask(); |
| |
| private: |
| friend class PreFreezeBackgroundMemoryTrimmer; |
| void Start(const Location& from_here, |
| TimeDelta delay, |
| OnceCallback<void(MemoryReductionTaskContext)> task); |
| void StartInternal(const Location& from_here, |
| TimeDelta delay, |
| OnceClosure task); |
| scoped_refptr<base::SequencedTaskRunner> task_runner_; |
| base::DelayedTaskHandle task_handle_; |
| |
| OnceCallback<void(MemoryReductionTaskContext)> task_; |
| }; |
| |
| PreFreezeBackgroundMemoryTrimmer(); |
| |
| 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<base::SequencedTaskRunner> task_runner, |
| const base::Location& from_here, |
| OnceCallback<void(MemoryReductionTaskContext)> task, |
| base::TimeDelta delay) LOCKS_EXCLUDED(lock_); |
| void PostDelayedBackgroundTaskModern( |
| scoped_refptr<base::SequencedTaskRunner> task_runner, |
| const base::Location& from_here, |
| OnceCallback<void(MemoryReductionTaskContext)> task, |
| base::TimeDelta delay) LOCKS_EXCLUDED(lock_); |
| BackgroundTask* PostDelayedBackgroundTaskModernHelper( |
| scoped_refptr<base::SequencedTaskRunner> task_runner, |
| const base::Location& from_here, |
| OnceCallback<void(MemoryReductionTaskContext)> 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<std::unique_ptr<BackgroundTask>> background_tasks_ |
| GUARDED_BY(lock_); |
| std::vector<const PreFreezeMetric*> 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<std::optional<uint64_t>> values_before_ GUARDED_BY(lock_); |
| bool supports_modern_trim_; |
| }; |
| |
| } // namespace base::android |
| |
| #endif // BASE_ANDROID_PRE_FREEZE_BACKGROUND_MEMORY_TRIMMER_H_ |