| // Copyright 2017 The Chromium Authors |
| // 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/functional/bind.h" |
| #include "base/memory/ptr_util.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"; |
| |
| // Deprecated: Replaced with `kBlocklist`. |
| // TODO(crbug.com/40744119): Remove this after M92 once existing |
| // clients have migrated to the new pref. |
| const char kBlacklist[] = "oom_intervention.blacklist"; |
| // Pref path for blocklist. If a hostname is in the blocklist we never trigger |
| // intervention on the host. |
| const char kBlocklist[] = "oom_intervention.blocklist"; |
| // Pref path for declined host list. If a hostname is in the declined host list |
| // we don't trigger intervention until a OOM crash happens 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::kMaxBlocklistSize = 6; |
| |
| // static |
| void OomInterventionDecider::RegisterProfilePrefs( |
| user_prefs::PrefRegistrySyncable* registry) { |
| registry->RegisterListPref(kBlocklist); |
| registry->RegisterListPref(kDeclinedHostList); |
| registry->RegisterListPref(kOomDetectedHostList); |
| |
| // Continue to register the old preference to migrate its value. |
| registry->RegisterListPref(kBlacklist); |
| } |
| |
| // 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(kBlocklist, host); |
| } else { |
| AddToList(kDeclinedHostList, host); |
| } |
| } |
| |
| void OomInterventionDecider::OnOomDetected(const std::string& host) { |
| if (IsOptedOut(host)) |
| return; |
| AddToList(kOomDetectedHostList, host); |
| } |
| |
| void OomInterventionDecider::ClearData() { |
| prefs_->ClearPref(kBlocklist); |
| prefs_->ClearPref(kDeclinedHostList); |
| prefs_->ClearPref(kOomDetectedHostList); |
| } |
| |
| void OomInterventionDecider::OnPrefInitialized(bool success) { |
| if (!success) |
| return; |
| |
| // Migrate `kBlacklist` to `kBlocklist`. |
| const base::Value::List& old_pref_value = prefs_->GetList(kBlacklist); |
| if (!old_pref_value.empty()) { |
| prefs_->SetList(kBlocklist, old_pref_value.Clone()); |
| prefs_->SetList(kBlacklist, base::Value::List()); |
| } |
| |
| if (delegate_->WasLastShutdownClean()) |
| return; |
| |
| const base::Value::List& declined_list = prefs_->GetList(kDeclinedHostList); |
| if (!declined_list.empty()) { |
| const std::string& last_declined = declined_list.back().GetString(); |
| if (!IsInList(kBlocklist, last_declined)) |
| AddToList(kOomDetectedHostList, last_declined); |
| } |
| } |
| |
| bool OomInterventionDecider::IsOptedOut(const std::string& host) const { |
| if (prefs_->GetList(kBlocklist).size() >= kMaxBlocklistSize) |
| return true; |
| |
| return IsInList(kBlocklist, host); |
| } |
| |
| bool OomInterventionDecider::IsInList(const char* list_name, |
| const std::string& host) const { |
| for (const auto& value : prefs_->GetList(list_name)) { |
| 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; |
| ScopedListPrefUpdate update(prefs_, list_name); |
| base::Value::List& update_list = update.Get(); |
| update_list.Append(host); |
| if (update_list.size() > kMaxListSize) |
| update_list.erase(update_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(); |
| } |