// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/controller/user_level_memory_pressure_signal_generator.h"
#include <limits>
#include "base/memory/memory_pressure_listener.h"
#include "base/metrics/field_trial_params.h"
#include "base/metrics/histogram_macros.h"
#include "base/system/sys_info.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/default_tick_clock.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/public/web/web_user_level_memory_pressure_signal_generator.h"
#include "third_party/blink/renderer/platform/bindings/v8_per_isolate_data.h"
#include "third_party/blink/renderer/platform/scheduler/public/main_thread_scheduler.h"
#include "third_party/blink/renderer/platform/wtf/allocator/partitions.h"
#if BUILDFLAG(IS_ANDROID)
namespace blink {
namespace {
UserLevelMemoryPressureSignalGenerator* g_instance = nullptr;
} // namespace
// static
UserLevelMemoryPressureSignalGenerator*
UserLevelMemoryPressureSignalGenerator::Instance() {
DCHECK(g_instance);
return g_instance;
}
// static
void UserLevelMemoryPressureSignalGenerator::Initialize(
Platform* platform,
scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
DEFINE_STATIC_LOCAL(
UserLevelMemoryPressureSignalGenerator, generator,
(std::move(task_runner),
platform->InertAndMinimumIntervalOfUserLevelMemoryPressureSignal()));
(void)generator;
}
UserLevelMemoryPressureSignalGenerator::UserLevelMemoryPressureSignalGenerator(
scoped_refptr<base::SingleThreadTaskRunner> task_runner,
std::pair<base::TimeDelta, base::TimeDelta> inert_and_minimum_interval)
: UserLevelMemoryPressureSignalGenerator(
std::move(task_runner),
inert_and_minimum_interval.first,
inert_and_minimum_interval.second,
base::DefaultTickClock::GetInstance(),
ThreadScheduler::Current()->ToMainThreadScheduler()) {}
UserLevelMemoryPressureSignalGenerator::UserLevelMemoryPressureSignalGenerator(
scoped_refptr<base::SingleThreadTaskRunner> task_runner,
base::TimeDelta inert_interval,
base::TimeDelta minimum_interval,
const base::TickClock* clock,
MainThreadScheduler* main_thread_scheduler)
: task_runner_(std::move(task_runner)),
inert_interval_(inert_interval),
minimum_interval_(minimum_interval),
clock_(clock),
main_thread_scheduler_(main_thread_scheduler) {
main_thread_scheduler->AddRAILModeObserver(this);
DCHECK(!g_instance);
g_instance = this;
}
UserLevelMemoryPressureSignalGenerator::
~UserLevelMemoryPressureSignalGenerator() {
main_thread_scheduler_->RemoveRAILModeObserver(this);
DCHECK_EQ(g_instance, this);
g_instance = nullptr;
}
void UserLevelMemoryPressureSignalGenerator::OnRAILModeChanged(
RAILMode rail_mode) {
bool was_loading = is_loading_;
is_loading_ = rail_mode == RAILMode::kLoad;
if (!is_loading_) {
if (!was_loading) {
return;
}
// Loading is finished because rail_mode changes another mode from kLoad.
last_loaded_ = clock_->NowTicks();
if (has_pending_request_) {
task_runner_->PostDelayedTask(
FROM_HERE,
WTF::BindOnce(&UserLevelMemoryPressureSignalGenerator::OnTimerFired,
WTF::UnretainedWrapper(this)),
inert_interval_);
}
}
}
void UserLevelMemoryPressureSignalGenerator::RequestMemoryPressureSignal() {
base::TimeTicks now = clock_->NowTicks();
last_requested_ = now;
// If |inert_interval_| >= 0, wait |inert_interval_| after loading is
// finished.
if (!inert_interval_.is_negative()) {
// If still loading, make |has_pending_request_| true and do not dispatch
// any pressure signals now.
if (is_loading_) {
has_pending_request_ = true;
return;
}
// Since loading is finished, we will see if |inert_interval_| has passed.
base::TimeDelta elapsed = !last_loaded_.has_value()
? inert_interval_
: (now - last_loaded_.value());
// If |inert_interval_| has not passed yet, do not dispatch any memory
// pressure signals now.
if (elapsed < inert_interval_) {
// If |has_pending_request_| = true, we will dispatch memory pressure
// signal when |inert_interval_ - elapsed| passes.
// Since we may have already started the timer, i.e.
// - start at OnRAILModeChanged(),
// - RequestMemoryPressureSignal() was invoked but still waiting
// |inert_interval_|. in the case, |has_pending_request_| is true.
if (!has_pending_request_) {
task_runner_->PostDelayedTask(
FROM_HERE,
WTF::BindOnce(&UserLevelMemoryPressureSignalGenerator::OnTimerFired,
WTF::UnretainedWrapper(this)),
inert_interval_ - elapsed);
}
has_pending_request_ = true;
return;
}
}
// - if inert_interval_ < 0, dispatch memory pressure signal now.
// - if loading is finished and >= |inert_interval_| passes after loading,
// dispatch memory pressure signal now.
Generate(now);
}
void UserLevelMemoryPressureSignalGenerator::Generate(base::TimeTicks now) {
// If |minimum_interval_| has not passed yet since the last generated time,
// does not generate any signals to avoid too many signals.
if (!last_generated_.has_value() ||
(now - last_generated_.value()) >= minimum_interval_) {
base::MemoryPressureListener::NotifyMemoryPressure(
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL);
last_generated_ = now;
}
has_pending_request_ = false;
}
void UserLevelMemoryPressureSignalGenerator::OnTimerFired() {
base::TimeTicks now = clock_->NowTicks();
DCHECK(has_pending_request_);
// If still loading, skip generating memory pressure signals. After loading
// is finished, start |signal_dispatch_timer_|.
if (is_loading_) {
// |has_pending_request_| must be kept true to know that memory pressure
// signal was requested when loading is finished.
return;
}
// If the inert interval has not passed yet, skip generating memory pressure
// signals. A new delayed task is posted and it will be executed at the end
// of inert interval.
if ((now - last_loaded_.value()) < inert_interval_) {
return;
}
// UserLevelMemoryPressureSignalGenerator will start monitoring if
// |minimum_interval_| passes after requesting memory pressure signals.
// So if we cannot dispatch pressure signals for kMinimumInterval (because
// of loading), we will wait for another request. If TotalPMF is still
// large, UserLevelMemoryPressureSignalGenerator will request pressure
// signals soon.
if ((now - last_requested_) > minimum_interval_) {
has_pending_request_ = false;
return;
}
Generate(now);
}
void RequestUserLevelMemoryPressureSignal() {
// TODO(crbug.com/1473814): AndroidWebView creates renderer processes
// without appending extra commandline switches,
// c.f. ChromeContentBrowserClient::AppendExtraCommandLineSwitches(),
// So renderer processes do not initialize user-level memory pressure
// siginal generators but the browser code expects they have already been
// initialized. So when requesting memory pressure signals, g_instance is
// nullptr and g_instance->clock_ will crash.
if (UserLevelMemoryPressureSignalGenerator* generator =
UserLevelMemoryPressureSignalGenerator::Instance()) {
generator->RequestMemoryPressureSignal();
}
}
} // namespace blink
#endif // BUILDFLAG(IS_ANDROID)