| 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() {}
|
|
|