blob: 97fe08f0fbf4742bf621694d3226f59e2b3223cf [file] [log] [blame]
// Copyright 2017 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 "chrome/browser/android/oom_intervention/oom_intervention_decider.h"
#include "base/bind.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/profiles/profile.h"
#include "components/metrics/metrics_service.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/scoped_user_pref_update.h"
namespace {
const char kOomInterventionDecider[] = "oom_intervention.decider";
// Pref path for blacklist. If a hostname is in the blacklist we never trigger
// intervention on the host.
const char kBlacklist[] = "oom_intervention.blacklist";
// Pref path for declined host list. If a hostname is in the declined host list
// we don't trigger intervention until a OOM crash happends on the host.
const char kDeclinedHostList[] = "oom_intervention.declined_host_list";
// Pref path for OOM detected host list. When an OOM crash is observed on
// a host the hostname is added to the list.
const char kOomDetectedHostList[] = "oom_intervention.oom_detected_host_list";
class DelegateImpl : public OomInterventionDecider::Delegate {
public:
bool WasLastShutdownClean() override {
if (!g_browser_process || !g_browser_process->metrics_service())
return true;
return g_browser_process->metrics_service()->WasLastShutdownClean();
}
};
} // namespace
const size_t OomInterventionDecider::kMaxListSize = 10;
const size_t OomInterventionDecider::kMaxBlacklistSize = 6;
// static
void OomInterventionDecider::RegisterProfilePrefs(
user_prefs::PrefRegistrySyncable* registry) {
registry->RegisterListPref(kBlacklist);
registry->RegisterListPref(kDeclinedHostList);
registry->RegisterListPref(kOomDetectedHostList);
}
// static
OomInterventionDecider* OomInterventionDecider::GetForBrowserContext(
content::BrowserContext* context) {
// The OomIntervetnionDecider is disabled in incognito mode because it is
// written in such a way that hostnames would be persisted in preferences on
// disk which is not acceptable for incognito mode.
if (context->IsOffTheRecord())
return nullptr;
if (!context->GetUserData(kOomInterventionDecider)) {
PrefService* prefs = Profile::FromBrowserContext(context)->GetPrefs();
context->SetUserData(kOomInterventionDecider,
base::WrapUnique(new OomInterventionDecider(
std::make_unique<DelegateImpl>(), prefs)));
}
return static_cast<OomInterventionDecider*>(
context->GetUserData(kOomInterventionDecider));
}
OomInterventionDecider::OomInterventionDecider(
std::unique_ptr<Delegate> delegate,
PrefService* prefs)
: delegate_(std::move(delegate)), prefs_(prefs) {
DCHECK(delegate_);
PrefService::PrefInitializationStatus pref_status =
prefs_->GetInitializationStatus();
if (pref_status == PrefService::INITIALIZATION_STATUS_WAITING) {
prefs_->AddPrefInitObserver(base::BindOnce(
&OomInterventionDecider::OnPrefInitialized, base::Unretained(this)));
} else {
OnPrefInitialized(pref_status ==
PrefService::INITIALIZATION_STATUS_SUCCESS);
}
}
OomInterventionDecider::~OomInterventionDecider() = default;
bool OomInterventionDecider::CanTriggerIntervention(
const std::string& host) const {
if (IsOptedOut(host))
return false;
// Check whether OOM was observed before checking declined host list in favor
// of triggering intervention after OOM.
if (IsInList(kOomDetectedHostList, host))
return true;
if (IsInList(kDeclinedHostList, host))
return false;
return true;
}
void OomInterventionDecider::OnInterventionDeclined(const std::string& host) {
if (IsOptedOut(host))
return;
if (IsInList(kDeclinedHostList, host)) {
AddToList(kBlacklist, host);
} else {
AddToList(kDeclinedHostList, host);
}
}
void OomInterventionDecider::OnOomDetected(const std::string& host) {
if (IsOptedOut(host))
return;
AddToList(kOomDetectedHostList, host);
}
void OomInterventionDecider::ClearData() {
prefs_->ClearPref(kBlacklist);
prefs_->ClearPref(kDeclinedHostList);
prefs_->ClearPref(kOomDetectedHostList);
}
void OomInterventionDecider::OnPrefInitialized(bool success) {
if (!success)
return;
if (delegate_->WasLastShutdownClean())
return;
const base::Value::ListStorage& declined_list =
prefs_->GetList(kDeclinedHostList)->GetList();
if (declined_list.size() > 0) {
const std::string& last_declined =
declined_list[declined_list.size() - 1].GetString();
if (!IsInList(kBlacklist, last_declined))
AddToList(kOomDetectedHostList, last_declined);
}
}
bool OomInterventionDecider::IsOptedOut(const std::string& host) const {
const base::Value::ListStorage& blacklist =
prefs_->GetList(kBlacklist)->GetList();
if (blacklist.size() >= kMaxBlacklistSize)
return true;
return IsInList(kBlacklist, host);
}
bool OomInterventionDecider::IsInList(const char* list_name,
const std::string& host) const {
for (const auto& value : prefs_->GetList(list_name)->GetList()) {
if (value.GetString() == host)
return true;
}
return false;
}
void OomInterventionDecider::AddToList(const char* list_name,
const std::string& host) {
if (IsInList(list_name, host))
return;
ListPrefUpdate update(prefs_, list_name);
base::Value::ListStorage& list = update.Get()->GetList();
list.push_back(base::Value(host));
if (list.size() > kMaxListSize)
list.erase(list.begin());
// Save the list immediately because we typically modify lists under high
// memory pressure, in which the browser process can be killed by the OS
// soon.
prefs_->CommitPendingWrite();
}