Chromium Code Reviews| 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 |