Index: content/browser/memory/memory_coordinator_impl.cc |
diff --git a/content/browser/memory/memory_coordinator_impl.cc b/content/browser/memory/memory_coordinator_impl.cc |
index 49ea5500830287960f921f7398508c836039081b..75727c01c3f6dfe21e46e9200007156066e58ab7 100644 |
--- a/content/browser/memory/memory_coordinator_impl.cc |
+++ b/content/browser/memory/memory_coordinator_impl.cc |
@@ -10,8 +10,8 @@ |
#include "base/process/process_handle.h" |
#include "base/threading/thread_task_runner_handle.h" |
#include "base/trace_event/trace_event.h" |
+#include "content/browser/memory/memory_condition_observer.h" |
#include "content/browser/memory/memory_monitor.h" |
-#include "content/browser/memory/memory_state_updater.h" |
#include "content/public/browser/content_browser_client.h" |
#include "content/public/browser/notification_service.h" |
#include "content/public/browser/notification_types.h" |
@@ -22,17 +22,21 @@ |
namespace content { |
+using MemoryState = base::MemoryState; |
+ |
namespace { |
-mojom::MemoryState ToMojomMemoryState(base::MemoryState state) { |
+const int kDefaultMinimumTransitionPeriodSeconds = 30; |
+ |
+mojom::MemoryState ToMojomMemoryState(MemoryState state) { |
switch (state) { |
- case base::MemoryState::UNKNOWN: |
+ case MemoryState::UNKNOWN: |
return mojom::MemoryState::UNKNOWN; |
- case base::MemoryState::NORMAL: |
+ case MemoryState::NORMAL: |
return mojom::MemoryState::NORMAL; |
- case base::MemoryState::THROTTLED: |
+ case MemoryState::THROTTLED: |
return mojom::MemoryState::THROTTLED; |
- case base::MemoryState::SUSPENDED: |
+ case MemoryState::SUSPENDED: |
return mojom::MemoryState::SUSPENDED; |
default: |
NOTREACHED(); |
@@ -40,6 +44,19 @@ mojom::MemoryState ToMojomMemoryState(base::MemoryState state) { |
} |
} |
+const char* MemoryConditionToString(MemoryCondition condition) { |
+ switch (condition) { |
+ case MemoryCondition::NORMAL: |
+ return "normal"; |
+ case MemoryCondition::WARNING: |
+ return "warning"; |
+ case MemoryCondition::CRITICAL: |
+ return "critical"; |
+ } |
+ NOTREACHED(); |
+ return "N/A"; |
+} |
+ |
} // namespace |
// The implementation of MemoryCoordinatorHandle. See memory_coordinator.mojom |
@@ -113,7 +130,10 @@ MemoryCoordinatorImpl::MemoryCoordinatorImpl( |
std::unique_ptr<MemoryMonitor> memory_monitor) |
: delegate_(GetContentClient()->browser()->GetMemoryCoordinatorDelegate()), |
memory_monitor_(std::move(memory_monitor)), |
- state_updater_(base::MakeUnique<MemoryStateUpdater>(this, task_runner)) { |
+ condition_observer_( |
+ base::MakeUnique<MemoryConditionObserver>(this, task_runner)), |
+ minimum_state_transition_period_(base::TimeDelta::FromSeconds( |
+ kDefaultMinimumTransitionPeriodSeconds)) { |
DCHECK(memory_monitor_.get()); |
base::MemoryCoordinatorProxy::SetMemoryCoordinator(this); |
} |
@@ -127,8 +147,7 @@ void MemoryCoordinatorImpl::Start() { |
notification_registrar_.Add( |
this, NOTIFICATION_RENDER_WIDGET_VISIBILITY_CHANGED, |
NotificationService::AllBrowserContextsAndSources()); |
- last_state_change_ = base::TimeTicks::Now(); |
- state_updater_->ScheduleUpdateState(base::TimeDelta()); |
+ condition_observer_->ScheduleUpdateCondition(base::TimeDelta()); |
} |
void MemoryCoordinatorImpl::CreateHandle( |
@@ -158,7 +177,7 @@ bool MemoryCoordinatorImpl::SetChildMemoryState(int render_process_id, |
if (!iter->second.handle->child().is_bound()) |
return false; |
- memory_state = OverrideGlobalState(memory_state, iter->second); |
+ memory_state = OverrideState(memory_state, iter->second); |
// A nop doesn't need to be sent, but is considered successful. |
if (iter->second.memory_state == memory_state) |
@@ -175,11 +194,11 @@ bool MemoryCoordinatorImpl::SetChildMemoryState(int render_process_id, |
return true; |
} |
-base::MemoryState MemoryCoordinatorImpl::GetChildMemoryState( |
+MemoryState MemoryCoordinatorImpl::GetChildMemoryState( |
int render_process_id) const { |
auto iter = children_.find(render_process_id); |
if (iter == children_.end()) |
- return base::MemoryState::UNKNOWN; |
+ return MemoryState::UNKNOWN; |
return iter->second.memory_state; |
} |
@@ -188,30 +207,26 @@ void MemoryCoordinatorImpl::RecordMemoryPressure( |
// TODO(bashi): Record memory pressure level. |
} |
-base::MemoryState MemoryCoordinatorImpl::GetGlobalMemoryState() const { |
- return current_state_; |
+MemoryState MemoryCoordinatorImpl::GetBrowserMemoryState() const { |
+ return browser_memory_state_; |
} |
-base::MemoryState MemoryCoordinatorImpl::GetCurrentMemoryState() const { |
- // SUSPENDED state may not make sense to the browser process. Use THROTTLED |
- // instead when the global state is SUSPENDED. |
- // TODO(bashi): Maybe worth considering another state for the browser. |
- return current_state_ == MemoryState::SUSPENDED ? MemoryState::THROTTLED |
- : current_state_; |
+MemoryState MemoryCoordinatorImpl::GetCurrentMemoryState() const { |
+ return GetBrowserMemoryState(); |
} |
void MemoryCoordinatorImpl::SetCurrentMemoryStateForTesting( |
- base::MemoryState memory_state) { |
- // This changes the current state temporariy for testing. The state will be |
- // updated 1 minute later. |
- ForceSetGlobalState(memory_state, base::TimeDelta::FromMinutes(1)); |
+ MemoryState memory_state) { |
+ // Resets |last_state_change_| so that |
+ // UpdateBrowserStateAndNotifyStateToClients() to set memory state forcibly. |
+ last_state_change_ = base::TimeTicks(); |
+ UpdateBrowserStateAndNotifyStateToClients(memory_state); |
} |
-void MemoryCoordinatorImpl::ForceSetGlobalState(base::MemoryState new_state, |
- base::TimeDelta duration) { |
- DCHECK(new_state != MemoryState::UNKNOWN); |
- ChangeStateIfNeeded(current_state_, new_state); |
- state_updater_->ScheduleUpdateState(duration); |
+void MemoryCoordinatorImpl::ForceSetMemoryCondition(MemoryCondition condition, |
+ base::TimeDelta duration) { |
+ UpdateConditionIfNeeded(condition); |
+ condition_observer_->ScheduleUpdateCondition(duration); |
} |
void MemoryCoordinatorImpl::Observe(int type, |
@@ -225,18 +240,31 @@ void MemoryCoordinatorImpl::Observe(int type, |
auto iter = children().find(process->GetID()); |
if (iter == children().end()) |
return; |
+ |
iter->second.is_visible = *Details<bool>(details).ptr(); |
- auto new_state = GetGlobalMemoryState(); |
+ // The current heuristics for state calculation: |
+ // - Foregrounded renderers: THROTTLED when condition is CRITICAL, otherwise |
+ // NORMAL. |
+ // - Backgrounded renderers: THROTTLED when condition is WARNING/CRITICAL, |
+ // otherwise NORMAL. |
+ MemoryState new_state = MemoryState::NORMAL; |
+ MemoryCondition condition = GetMemoryCondition(); |
+ if (condition == MemoryCondition::WARNING) { |
+ new_state = |
+ iter->second.is_visible ? MemoryState::NORMAL : MemoryState::THROTTLED; |
+ } else if (condition == MemoryCondition::CRITICAL) { |
+ new_state = MemoryState::THROTTLED; |
+ } |
SetChildMemoryState(iter->first, new_state); |
} |
-base::MemoryState MemoryCoordinatorImpl::GetStateForProcess( |
+MemoryState MemoryCoordinatorImpl::GetStateForProcess( |
base::ProcessHandle handle) { |
DCHECK(CalledOnValidThread()); |
if (handle == base::kNullProcessHandle) |
return MemoryState::UNKNOWN; |
if (handle == base::GetCurrentProcessHandle()) |
- return GetCurrentMemoryState(); |
+ return browser_memory_state_; |
for (auto& iter : children()) { |
auto* render_process_host = GetRenderProcessHost(iter.first); |
@@ -246,22 +274,43 @@ base::MemoryState MemoryCoordinatorImpl::GetStateForProcess( |
return MemoryState::UNKNOWN; |
} |
-bool MemoryCoordinatorImpl::ChangeStateIfNeeded(base::MemoryState prev_state, |
- base::MemoryState next_state) { |
+void MemoryCoordinatorImpl::UpdateConditionIfNeeded( |
+ MemoryCondition next_condition) { |
DCHECK(CalledOnValidThread()); |
- if (prev_state == next_state) |
- return false; |
+ if (memory_condition_ == next_condition) |
+ return; |
- last_state_change_ = base::TimeTicks::Now(); |
- current_state_ = next_state; |
+ MemoryCondition prev_condition = memory_condition_; |
+ memory_condition_ = next_condition; |
TRACE_EVENT2(TRACE_DISABLED_BY_DEFAULT("memory-infra"), |
- "MemoryCoordinatorImpl::ChangeStateIfNeeded", "prev", |
- MemoryStateToString(prev_state), "next", |
- MemoryStateToString(next_state)); |
- NotifyStateToClients(); |
- NotifyStateToChildren(); |
- return true; |
+ "MemoryCoordinatorImpl::UpdateConditionIfNeeded", "prev", |
+ MemoryConditionToString(prev_condition), "next", |
+ MemoryConditionToString(next_condition)); |
+ |
+ // TODO(bashi): Following actions are tentative. We might want to prioritize |
+ // processes and handle them one-by-one. |
+ |
+ if (next_condition == MemoryCondition::NORMAL) { |
+ // Set NORMAL state to all clients/processes. |
+ UpdateBrowserStateAndNotifyStateToClients(MemoryState::NORMAL); |
+ NotifyStateToChildren(MemoryState::NORMAL); |
+ } else if (next_condition == MemoryCondition::WARNING) { |
+ // Set NORMAL state to foreground proceses and clients in the browser |
+ // process. Set THROTTLED state to background processes. |
+ UpdateBrowserStateAndNotifyStateToClients(MemoryState::NORMAL); |
+ for (auto& iter : children()) { |
+ auto state = |
+ iter.second.is_visible ? MemoryState::NORMAL : MemoryState::THROTTLED; |
+ SetChildMemoryState(iter.first, state); |
+ } |
+ // Idea: Purge memory from background processes. |
+ } else if (next_condition == MemoryCondition::CRITICAL) { |
+ // Set THROTTLED state to all clients/processes. |
+ UpdateBrowserStateAndNotifyStateToClients(MemoryState::THROTTLED); |
+ NotifyStateToChildren(MemoryState::THROTTLED); |
+ // Idea: Start discarding tabs. |
+ } |
} |
void MemoryCoordinatorImpl::DiscardTab() { |
@@ -308,14 +357,18 @@ bool MemoryCoordinatorImpl::CanSuspendRenderer(int render_process_id) { |
} |
void MemoryCoordinatorImpl::OnChildAdded(int render_process_id) { |
- // Populate the global state as an initial state of a newly created process. |
- auto new_state = GetGlobalMemoryState(); |
+ // Populate an initial state of a newly created process, assuming it's |
+ // foregrounded. |
+ // TODO(bashi): Don't assume the process is foregrounded. It can be added |
+ // as a background process. |
+ auto new_state = GetMemoryCondition() == MemoryCondition::CRITICAL |
+ ? MemoryState::THROTTLED |
+ : MemoryState::NORMAL; |
SetChildMemoryState(render_process_id, new_state); |
} |
-base::MemoryState MemoryCoordinatorImpl::OverrideGlobalState( |
- MemoryState memory_state, |
- const ChildInfo& child) { |
+MemoryState MemoryCoordinatorImpl::OverrideState(MemoryState memory_state, |
+ const ChildInfo& child) { |
// We don't suspend foreground renderers. Throttle them instead. |
if (child.is_visible && memory_state == MemoryState::SUSPENDED) |
return MemoryState::THROTTLED; |
@@ -343,16 +396,30 @@ void MemoryCoordinatorImpl::CreateChildInfoMapEntry( |
child_info.handle = std::move(handle); |
} |
-void MemoryCoordinatorImpl::NotifyStateToClients() { |
- auto state = GetCurrentMemoryState(); |
+void MemoryCoordinatorImpl::UpdateBrowserStateAndNotifyStateToClients( |
+ MemoryState memory_state) { |
+ if (memory_state == browser_memory_state_) |
+ return; |
+ |
+ base::TimeTicks now = base::TimeTicks::Now(); |
+ if (!last_state_change_.is_null() && |
+ (now - last_state_change_ < minimum_state_transition_period_)) |
+ return; |
+ |
+ last_state_change_ = now; |
+ browser_memory_state_ = memory_state; |
+ NotifyStateToClients(memory_state); |
+} |
+ |
+void MemoryCoordinatorImpl::NotifyStateToClients(MemoryState state) { |
base::MemoryCoordinatorClientRegistry::GetInstance()->Notify(state); |
} |
-void MemoryCoordinatorImpl::NotifyStateToChildren() { |
+void MemoryCoordinatorImpl::NotifyStateToChildren(MemoryState state) { |
// It's OK to call SetChildMemoryState() unconditionally because it checks |
// whether this state transition is valid. |
for (auto& iter : children()) |
- SetChildMemoryState(iter.first, current_state_); |
+ SetChildMemoryState(iter.first, state); |
} |
MemoryCoordinatorImpl::ChildInfo::ChildInfo() {} |