Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(14)

Unified Diff: chrome/browser/memory/tab_manager.cc

Issue 1641813002: Provide renderers with memory pressure signals. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@contentapi
Patch Set: Cleaned up. Created 4 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/browser/memory/tab_manager.cc
diff --git a/chrome/browser/memory/tab_manager.cc b/chrome/browser/memory/tab_manager.cc
index 7124a5c13dc1a74f8caeb87d4c915b63d2f4f2fd..d1161868886931b2c3b47d1e4f0859f7c985eb51 100644
--- a/chrome/browser/memory/tab_manager.cc
+++ b/chrome/browser/memory/tab_manager.cc
@@ -26,6 +26,7 @@
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
+#include "base/thread_task_runner_handle.h"
#include "base/threading/thread.h"
#include "base/time/tick_clock.h"
#include "build/build_config.h"
@@ -48,6 +49,7 @@
#include "components/metrics/system_memory_stats_recorder.h"
#include "components/variations/variations_associated_data.h"
#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/memory_pressure.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"
@@ -107,6 +109,25 @@ int FindTabStripModelById(int64_t target_web_contents_id,
return -1;
}
+// A wrapper around base::MemoryPressureMonitor::GetCurrentPressureLevel.
+// TODO(chrisha): Move this do the default implementation of a delegate.
+base::MemoryPressureListener::MemoryPressureLevel
+GetCurrentPressureLevel() {
+ auto monitor = base::MemoryPressureMonitor::Get();
+ if (monitor)
+ return monitor->GetCurrentPressureLevel();
+ return base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE;
+}
+
+// A wrapper to content::SendPressureNotification that doesn't have overloaded
+// type ambiguity. Makes use of Bind easier.
+// TODO(chrisha): Move this do the default implementation of a delegate.
+void NotifyRendererProcess(
+ const content::RenderProcessHost* render_process_host,
+ base::MemoryPressureListener::MemoryPressureLevel level) {
+ content::SendPressureNotification(render_process_host, level);
+}
+
} // namespace
////////////////////////////////////////////////////////////////////////////////
@@ -117,12 +138,19 @@ TabManager::TabManager()
recent_tab_discard_(false),
discard_once_(false),
browser_tab_strip_tracker_(this, nullptr, nullptr),
- test_tick_clock_(nullptr) {
+ test_tick_clock_(nullptr),
+ under_memory_pressure_(false),
+ weak_ptr_factory_(this) {
#if defined(OS_CHROMEOS)
delegate_.reset(new TabManagerDelegate);
#endif
browser_tab_strip_tracker_.Init(
BrowserTabStripTracker::InitWith::ALL_BROWERS);
+
+ // Set up default callbacks. These may be overridden post-construction as
+ // testing seams.
+ get_current_pressure_level_ = base::Bind(&GetCurrentPressureLevel);
+ notify_renderer_process_ = base::Bind(&NotifyRendererProcess);
}
TabManager::~TabManager() {
@@ -203,20 +231,32 @@ void TabManager::Stop() {
TabStatsList TabManager::GetTabStats() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
TabStatsList stats_list;
- stats_list.reserve(32); // 99% of users have < 30 tabs open
-
- // Go through each window to get all the tabs. Depending on the platform,
- // windows are either native or ash or both. The goal is to make sure to go
- // through them all, starting with the active window first (use
- // chrome::GetActiveDesktop to get the current used type).
- AddTabStats(BrowserList::GetInstance(chrome::GetActiveDesktop()), true,
- &stats_list);
- if (chrome::GetActiveDesktop() != chrome::HOST_DESKTOP_TYPE_NATIVE) {
- AddTabStats(BrowserList::GetInstance(chrome::HOST_DESKTOP_TYPE_NATIVE),
- false, &stats_list);
- } else if (chrome::GetActiveDesktop() != chrome::HOST_DESKTOP_TYPE_ASH) {
- AddTabStats(BrowserList::GetInstance(chrome::HOST_DESKTOP_TYPE_ASH), false,
+ stats_list.reserve(32); // 99% of users have < 30 tabs open.
+
+ // TODO(chrisha): Move this code to a TabStripModel enumeration delegate!
+ if (!test_tab_strip_models_.empty()) {
+ for (size_t i = 0; i < test_tab_strip_models_.size(); ++i) {
+ AddTabStats(test_tab_strip_models_[i].first, // tab_strip_model
+ test_tab_strip_models_[i].second, // is_app
+ i == 0, // is_active
+ &stats_list);
+ }
+ } else {
+ // The code here can only be tested under a full browser test.
+
+ // Go through each window to get all the tabs. Depending on the platform,
+ // windows are either native or ash or both. The goal is to make sure to go
+ // through them all, starting with the active window first (use
+ // chrome::GetActiveDesktop to get the current used type).
+ AddTabStats(BrowserList::GetInstance(chrome::GetActiveDesktop()), true,
&stats_list);
+ if (chrome::GetActiveDesktop() != chrome::HOST_DESKTOP_TYPE_NATIVE) {
+ AddTabStats(BrowserList::GetInstance(chrome::HOST_DESKTOP_TYPE_NATIVE),
+ false, &stats_list);
+ } else if (chrome::GetActiveDesktop() != chrome::HOST_DESKTOP_TYPE_ASH) {
+ AddTabStats(BrowserList::GetInstance(chrome::HOST_DESKTOP_TYPE_ASH),
+ false, &stats_list);
+ }
}
// Sort the collected data so that least desirable to be killed is first, most
@@ -225,6 +265,30 @@ TabStatsList TabManager::GetTabStats() {
return stats_list;
}
+std::vector<content::RenderProcessHost*> TabManager::GetOrderedRenderers() {
+ // Get the tab stats.
+ auto tab_stats = GetTabStats();
+
+ std::vector<content::RenderProcessHost*> sorted_renderers;
+ std::set<content::RenderProcessHost*> seen_renderers;
+ sorted_renderers.reserve(tab_stats.size());
+
+ // Convert the tab sort order to a process sort order. The process inherits
+ // the priority of its highest priority tab.
+ for (auto& tab : tab_stats) {
+ // Skip renderers that have already been encountered. This can occur when
+ // multiple tabs are folded into a single renderer process. In this case the
+ // process takes the priority of its highest priority contained tab.
+ auto renderer = tab.render_process_host;
+ if (!seen_renderers.insert(renderer).second)
+ continue;
+
+ sorted_renderers.push_back(renderer);
+ }
+
+ return sorted_renderers;
+}
+
bool TabManager::IsTabDiscarded(content::WebContents* contents) const {
return GetWebContentsData(contents)->IsDiscarded();
}
@@ -422,33 +486,41 @@ void TabManager::AddTabStats(BrowserList* browser_list,
Browser* browser = *browser_iterator;
bool is_browser_for_app = browser->is_app();
const TabStripModel* model = browser->tab_strip_model();
- for (int i = 0; i < model->count(); i++) {
- WebContents* contents = model->GetWebContentsAt(i);
- if (!contents->IsCrashed()) {
- TabStats stats;
- stats.is_app = is_browser_for_app;
- stats.is_internal_page =
- IsInternalPage(contents->GetLastCommittedURL());
- stats.is_media = IsMediaTab(contents);
- stats.is_pinned = model->IsTabPinned(i);
- stats.is_selected = browser_active && model->IsTabSelected(i);
- stats.is_discarded = GetWebContentsData(contents)->IsDiscarded();
- stats.has_form_entry =
- contents->GetPageImportanceSignals().had_form_interaction;
- stats.discard_count = GetWebContentsData(contents)->DiscardCount();
- stats.last_active = contents->GetLastActiveTime();
- stats.renderer_handle = contents->GetRenderProcessHost()->GetHandle();
- stats.child_process_host_id = contents->GetRenderProcessHost()->GetID();
+ AddTabStats(model, is_browser_for_app, active_desktop, stats_list);
+ // The active browser window is processed in the first iteration.
+ browser_active = false;
+ }
+}
+
+void TabManager::AddTabStats(const TabStripModel* model,
+ bool is_app,
+ bool active_model,
+ TabStatsList* stats_list) {
+for (int i = 0; i < model->count(); i++) {
+ WebContents* contents = model->GetWebContentsAt(i);
+ if (!contents->IsCrashed()) {
+ TabStats stats;
+ stats.is_app = is_app;
+ stats.is_internal_page =
+ IsInternalPage(contents->GetLastCommittedURL());
+ stats.is_media = IsMediaTab(contents);
+ stats.is_pinned = model->IsTabPinned(i);
+ stats.is_selected = active_model && model->IsTabSelected(i);
+ stats.is_discarded = GetWebContentsData(contents)->IsDiscarded();
+ stats.has_form_entry =
+ contents->GetPageImportanceSignals().had_form_interaction;
+ stats.discard_count = GetWebContentsData(contents)->DiscardCount();
+ stats.last_active = contents->GetLastActiveTime();
+ stats.render_process_host = contents->GetRenderProcessHost();
+ stats.renderer_handle = contents->GetRenderProcessHost()->GetHandle();
+ stats.child_process_host_id = contents->GetRenderProcessHost()->GetID();
#if defined(OS_CHROMEOS)
- stats.oom_score = delegate_->GetOomScore(stats.child_process_host_id);
+ stats.oom_score = delegate_->GetOomScore(stats.child_process_host_id);
#endif
- stats.title = contents->GetTitle();
- stats.tab_contents_id = IdFromWebContents(contents);
- stats_list->push_back(stats);
- }
+ stats.title = contents->GetTitle();
+ stats.tab_contents_id = IdFromWebContents(contents);
+ stats_list->push_back(stats);
}
- // The active browser window is processed in the first iteration.
- browser_active = false;
}
}
@@ -588,13 +660,26 @@ void TabManager::OnMemoryPressure(
if (g_browser_process->IsShuttingDown())
return;
- // For the moment only do something when critical state is reached.
+ // If no task runner has been set, then use the same one that the memory
+ // pressure subsystem uses.
+ if (!task_runner_.get())
+ task_runner_ = base::ThreadTaskRunnerHandle::Get();
+
+ // Under critical pressure try to discard a tab.
if (memory_pressure_level ==
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL) {
LogMemoryAndDiscardTab();
}
// TODO(skuhne): If more memory pressure levels are introduced, consider
// calling PurgeBrowserMemory() before CRITICAL is reached.
+
+ // If this is the beginning of a period of memory pressure then kick off
+ // notification of child processes.
+ // NOTE: This mechanism relies on having a MemoryPressureMonitor
+ // implementation that supports "CurrentPressureLevel". This is true on all
+ // platforms on which TabManager is used.
+ if (!under_memory_pressure_)
+ DoChildProcessDispatch();
}
bool TabManager::IsMediaTab(WebContents* contents) const {
@@ -668,4 +753,55 @@ TimeTicks TabManager::NowTicks() const {
return test_tick_clock_->NowTicks();
}
+void TabManager::DoChildProcessDispatch() {
+ if (!under_memory_pressure_)
+ under_memory_pressure_ = true;
+
+ // If the memory pressure condition has ended then stop dispatching messages.
+ auto level = get_current_pressure_level_.Run();
+ if (level == base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE) {
+ under_memory_pressure_ = false;
+ notified_renderers_.clear();
+ return;
+ }
+
+ // Get a vector of active renderers, from highest to lowest priority.
+ auto renderers = GetOrderedRenderers();
+ CHECK(!renderers.empty());
Georges Khalil 2016/01/28 15:41:02 Could this be triggered while shutting down and no
chrisha 2016/01/29 16:17:54 Very good point, as that's a possible race conditi
+
+ // Notify a single renderer of memory pressure.
+ bool notified = false;
+ while (!notified) {
+ // Notify the lowest priority renderer that hasn't been notified yet.
+ for (auto rit = renderers.rbegin(); rit != renderers.rend(); ++rit) {
+ // If this renderer has already been notified then look at the next one.
+ if (!notified_renderers_.insert(*rit).second)
+ continue;
+
+ // Notify the renderer.
+ notify_renderer_process_.Run(*rit, level);
+ notified = true;
+ break;
+ }
+
+ // If all renderers were processed and none were notified, then all
+ // renderers have already been notified. Clear the list and start again.
+ if (!notified)
+ notified_renderers_.clear();
+
+ // This loop can only run at most twice. If it doesn't exit the first time
+ // through, by the second time through |notified_renderers_| will be empty.
+ // Since |renderers| is always non-empty, the first renderer encountered
+ // during the second pass will be notified.
+ }
+
+ // Schedule another notification. Use a weak pointer so this doesn't explode
+ // during tear down.
+ task_runner_->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&TabManager::DoChildProcessDispatch,
+ weak_ptr_factory_.GetWeakPtr()),
+ base::TimeDelta::FromSeconds(kRendererNotificationDelayInSeconds));
+}
+
} // namespace memory

Powered by Google App Engine
This is Rietveld 408576698