Index: chrome/browser/memory/tab_manager.cc |
diff --git a/chrome/browser/memory/tab_manager.cc b/chrome/browser/memory/tab_manager.cc |
index ebcbe6400fda1cba478954593af32cfbf1ce6742..5e46e790613c3fd98fb6de6aea6c224c11a4276c 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_controller.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,26 @@ 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::MemoryPressureController::SendPressureNotification( |
+ render_process_host, level); |
+} |
+ |
} // namespace |
//////////////////////////////////////////////////////////////////////////////// |
@@ -117,12 +139,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,10 +232,20 @@ 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. |
- AddTabStats(&stats_list); |
+ 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. |
+ AddTabStats(&stats_list); |
+ } |
// Sort the collected data so that least desirable to be killed is first, most |
// desirable is last. |
@@ -214,6 +253,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(); |
} |
@@ -407,35 +470,42 @@ void TabManager::AddTabStats(TabStatsList* stats_list) { |
browser_iterator != browser_list->end_last_active(); |
++browser_iterator) { |
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(browser->tab_strip_model(), browser->is_app(), browser_active, |
+ 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; |
} |
} |
@@ -574,13 +644,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 { |
@@ -654,4 +737,64 @@ TimeTicks TabManager::NowTicks() const { |
return test_tick_clock_->NowTicks(); |
} |
+void TabManager::DoChildProcessDispatch() { |
+ // If Chrome is shutting down, do not do anything. |
+ if (g_browser_process->IsShuttingDown()) |
+ return; |
+ |
+ 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(); |
+ |
+ // The following code requires at least one renderer to be present or it will |
+ // busyloop. It's possible (however unlikely) for no renderers to exist, so |
+ // bail early if that's the case. |
+ if (renderers.empty()) |
+ return; |
+ |
+ // 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 |