| 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 5a24676c9386baf3b7a518f6f7c9240bfa6082e6..92a519f40df9f4f80a47a05a9ac16f523af4806b 100644
|
| --- a/content/browser/memory/memory_coordinator_impl.cc
|
| +++ b/content/browser/memory/memory_coordinator_impl.cc
|
| @@ -4,18 +4,22 @@
|
|
|
| #include "content/browser/memory/memory_coordinator_impl.h"
|
|
|
| +#include "base/memory/memory_coordinator_client_registry.h"
|
| #include "base/metrics/histogram_macros.h"
|
| +#include "base/process/process_handle.h"
|
| #include "base/process/process_metrics.h"
|
| #include "base/strings/string_number_conversions.h"
|
| #include "base/threading/thread_task_runner_handle.h"
|
| #include "base/trace_event/trace_event.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"
|
| #include "content/public/browser/render_process_host.h"
|
| #include "content/public/browser/render_widget_host.h"
|
| #include "content/public/common/content_features.h"
|
| +#include "mojo/public/cpp/bindings/binding.h"
|
|
|
| namespace content {
|
|
|
| @@ -95,28 +99,72 @@ void RecordMetricsOnStateChange(base::MemoryState prev_state,
|
|
|
| } // namespace
|
|
|
| +// The implementation of MemoryCoordinatorHandle. See memory_coordinator.mojom
|
| +// for the role of this class.
|
| +class MemoryCoordinatorHandleImpl : public mojom::MemoryCoordinatorHandle {
|
| + public:
|
| + MemoryCoordinatorHandleImpl(mojom::MemoryCoordinatorHandleRequest request,
|
| + MemoryCoordinatorImpl* coordinator,
|
| + int render_process_id);
|
| + ~MemoryCoordinatorHandleImpl() override;
|
| +
|
| + // mojom::MemoryCoordinatorHandle:
|
| + void AddChild(mojom::ChildMemoryCoordinatorPtr child) override;
|
| +
|
| + mojom::ChildMemoryCoordinatorPtr& child() { return child_; }
|
| + mojo::Binding<mojom::MemoryCoordinatorHandle>& binding() { return binding_; }
|
| +
|
| + private:
|
| + MemoryCoordinatorImpl* coordinator_;
|
| + int render_process_id_;
|
| + mojom::ChildMemoryCoordinatorPtr child_;
|
| + mojo::Binding<mojom::MemoryCoordinatorHandle> binding_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(MemoryCoordinatorHandleImpl);
|
| +};
|
| +
|
| +MemoryCoordinatorHandleImpl::MemoryCoordinatorHandleImpl(
|
| + mojom::MemoryCoordinatorHandleRequest request,
|
| + MemoryCoordinatorImpl* coordinator,
|
| + int render_process_id)
|
| + : coordinator_(coordinator),
|
| + render_process_id_(render_process_id),
|
| + binding_(this, std::move(request)) {
|
| + DCHECK(coordinator_);
|
| +}
|
| +
|
| +MemoryCoordinatorHandleImpl::~MemoryCoordinatorHandleImpl() {}
|
| +
|
| +void MemoryCoordinatorHandleImpl::AddChild(
|
| + mojom::ChildMemoryCoordinatorPtr child) {
|
| + DCHECK(!child_.is_bound());
|
| + child_ = std::move(child);
|
| + coordinator_->OnChildAdded(render_process_id_);
|
| +}
|
| +
|
| // SingletonTraits for MemoryCoordinator. Returns MemoryCoordinatorImpl
|
| // as an actual instance.
|
| -struct MemoryCoordinatorSingletonTraits
|
| - : public base::LeakySingletonTraits<MemoryCoordinator> {
|
| - static MemoryCoordinator* New() {
|
| +struct MemoryCoordinatorImplSingletonTraits
|
| + : public base::LeakySingletonTraits<MemoryCoordinatorImpl> {
|
| + static MemoryCoordinatorImpl* New() {
|
| return new MemoryCoordinatorImpl(base::ThreadTaskRunnerHandle::Get(),
|
| CreateMemoryMonitor());
|
| }
|
| };
|
|
|
| // static
|
| -MemoryCoordinator* MemoryCoordinator::GetInstance() {
|
| +MemoryCoordinatorImpl* MemoryCoordinatorImpl::GetInstance() {
|
| if (!base::FeatureList::IsEnabled(features::kMemoryCoordinator))
|
| return nullptr;
|
| - return base::Singleton<MemoryCoordinator,
|
| - MemoryCoordinatorSingletonTraits>::get();
|
| + return base::Singleton<MemoryCoordinatorImpl,
|
| + MemoryCoordinatorImplSingletonTraits>::get();
|
| }
|
|
|
| MemoryCoordinatorImpl::MemoryCoordinatorImpl(
|
| scoped_refptr<base::SingleThreadTaskRunner> task_runner,
|
| std::unique_ptr<MemoryMonitor> memory_monitor)
|
| - : memory_monitor_(std::move(memory_monitor)),
|
| + : delegate_(GetContentClient()->browser()->GetMemoryCoordinatorDelegate()),
|
| + memory_monitor_(std::move(memory_monitor)),
|
| state_updater_(base::MakeUnique<MemoryStateUpdater>(this, task_runner)) {
|
| DCHECK(memory_monitor_.get());
|
| }
|
| @@ -134,10 +182,76 @@ void MemoryCoordinatorImpl::Start() {
|
| state_updater_->ScheduleUpdateState(base::TimeDelta());
|
| }
|
|
|
| -void MemoryCoordinatorImpl::OnChildAdded(int render_process_id) {
|
| - // Populate the global state as an initial state of a newly created process.
|
| - auto new_state = ToMojomMemoryState(GetGlobalMemoryState());
|
| - SetChildMemoryState(render_process_id, new_state);
|
| +void MemoryCoordinatorImpl::CreateHandle(
|
| + int render_process_id,
|
| + mojom::MemoryCoordinatorHandleRequest request) {
|
| + std::unique_ptr<MemoryCoordinatorHandleImpl> handle(
|
| + new MemoryCoordinatorHandleImpl(std::move(request), this,
|
| + render_process_id));
|
| + handle->binding().set_connection_error_handler(
|
| + base::Bind(&MemoryCoordinatorImpl::OnConnectionError,
|
| + base::Unretained(this), render_process_id));
|
| + CreateChildInfoMapEntry(render_process_id, std::move(handle));
|
| +}
|
| +
|
| +bool MemoryCoordinatorImpl::SetChildMemoryState(int render_process_id,
|
| + mojom::MemoryState memory_state) {
|
| + // Can't set an invalid memory state.
|
| + if (memory_state == mojom::MemoryState::UNKNOWN)
|
| + return false;
|
| +
|
| + // Can't send a message to a child that doesn't exist.
|
| + auto iter = children_.find(render_process_id);
|
| + if (iter == children_.end())
|
| + return false;
|
| +
|
| + // Can't send a message to a child that isn't bound.
|
| + if (!iter->second.handle->child().is_bound())
|
| + return false;
|
| +
|
| + memory_state = OverrideGlobalState(memory_state, iter->second);
|
| +
|
| + // A nop doesn't need to be sent, but is considered successful.
|
| + if (iter->second.memory_state == memory_state)
|
| + return true;
|
| +
|
| + // Can't suspend the given renderer.
|
| + if (memory_state == mojom::MemoryState::SUSPENDED &&
|
| + !CanSuspendRenderer(render_process_id))
|
| + return false;
|
| +
|
| + // Update the internal state and send the message.
|
| + iter->second.memory_state = memory_state;
|
| + iter->second.handle->child()->OnStateChange(memory_state);
|
| + return true;
|
| +}
|
| +
|
| +mojom::MemoryState MemoryCoordinatorImpl::GetChildMemoryState(
|
| + int render_process_id) const {
|
| + auto iter = children_.find(render_process_id);
|
| + if (iter == children_.end())
|
| + return mojom::MemoryState::UNKNOWN;
|
| + return iter->second.memory_state;
|
| +}
|
| +
|
| +void MemoryCoordinatorImpl::RecordMemoryPressure(
|
| + base::MemoryPressureMonitor::MemoryPressureLevel level) {
|
| + DCHECK(GetGlobalMemoryState() != base::MemoryState::UNKNOWN);
|
| + int state = static_cast<int>(GetGlobalMemoryState());
|
| + switch (level) {
|
| + case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE:
|
| + UMA_HISTOGRAM_ENUMERATION(
|
| + "Memory.Coordinator.StateOnModerateNotificationReceived",
|
| + state, base::kMemoryStateMax);
|
| + break;
|
| + case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL:
|
| + UMA_HISTOGRAM_ENUMERATION(
|
| + "Memory.Coordinator.StateOnCriticalNotificationReceived",
|
| + state, base::kMemoryStateMax);
|
| + break;
|
| + case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE:
|
| + NOTREACHED();
|
| + }
|
| }
|
|
|
| base::MemoryState MemoryCoordinatorImpl::GetGlobalMemoryState() const {
|
| @@ -202,6 +316,75 @@ bool MemoryCoordinatorImpl::ChangeStateIfNeeded(base::MemoryState prev_state,
|
| return true;
|
| }
|
|
|
| +void MemoryCoordinatorImpl::AddChildForTesting(
|
| + int dummy_render_process_id, mojom::ChildMemoryCoordinatorPtr child) {
|
| + mojom::MemoryCoordinatorHandlePtr mch;
|
| + auto request = mojo::GetProxy(&mch);
|
| + std::unique_ptr<MemoryCoordinatorHandleImpl> handle(
|
| + new MemoryCoordinatorHandleImpl(std::move(request), this,
|
| + dummy_render_process_id));
|
| + handle->AddChild(std::move(child));
|
| + CreateChildInfoMapEntry(dummy_render_process_id, std::move(handle));
|
| +}
|
| +
|
| +void MemoryCoordinatorImpl::OnConnectionError(int render_process_id) {
|
| + children_.erase(render_process_id);
|
| +}
|
| +
|
| +bool MemoryCoordinatorImpl::CanSuspendRenderer(int render_process_id) {
|
| + // If there is no delegate (i.e. unittests), renderers are always suspendable.
|
| + if (!delegate_)
|
| + return true;
|
| + auto* render_process_host = RenderProcessHost::FromID(render_process_id);
|
| + if (!render_process_host || !render_process_host->IsProcessBackgrounded())
|
| + return false;
|
| + if (render_process_host->GetWorkerRefCount() > 0)
|
| + return false;
|
| + return delegate_->CanSuspendBackgroundedRenderer(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 = ToMojomMemoryState(GetGlobalMemoryState());
|
| + SetChildMemoryState(render_process_id, new_state);
|
| +}
|
| +
|
| +mojom::MemoryState MemoryCoordinatorImpl::OverrideGlobalState(
|
| + mojom::MemoryState memory_state,
|
| + const ChildInfo& child) {
|
| + // We don't suspend foreground renderers. Throttle them instead.
|
| + if (child.is_visible && memory_state == mojom::MemoryState::SUSPENDED)
|
| + return mojom::MemoryState::THROTTLED;
|
| +#if defined(OS_ANDROID)
|
| + // On Android, we throttle background renderers immediately.
|
| + // TODO(bashi): Create a specialized class of MemoryCoordinator for Android
|
| + // and move this ifdef to the class.
|
| + if (!child.is_visible && memory_state == mojom::MemoryState::NORMAL)
|
| + return mojom::MemoryState::THROTTLED;
|
| + // TODO(bashi): Suspend background renderers after a certain period of time.
|
| +#endif // defined(OS_ANDROID)
|
| + return memory_state;
|
| +}
|
| +
|
| +void MemoryCoordinatorImpl::SetDelegateForTesting(
|
| + std::unique_ptr<MemoryCoordinatorDelegate> delegate) {
|
| + CHECK(!delegate_);
|
| + delegate_ = std::move(delegate);
|
| +}
|
| +
|
| +void MemoryCoordinatorImpl::CreateChildInfoMapEntry(
|
| + int render_process_id,
|
| + std::unique_ptr<MemoryCoordinatorHandleImpl> handle) {
|
| + auto& child_info = children_[render_process_id];
|
| + // Process always start with normal memory state.
|
| + // We'll set renderer's memory state to the current global state when the
|
| + // corresponding renderer process is ready to communicate. Renderer processes
|
| + // call AddChild() when they are ready.
|
| + child_info.memory_state = mojom::MemoryState::NORMAL;
|
| + child_info.is_visible = true;
|
| + child_info.handle = std::move(handle);
|
| +}
|
| +
|
| void MemoryCoordinatorImpl::NotifyStateToClients() {
|
| auto state = GetCurrentMemoryState();
|
| base::MemoryCoordinatorClientRegistry::GetInstance()->Notify(state);
|
| @@ -244,4 +427,12 @@ void MemoryCoordinatorImpl::RecordStateChange(MemoryState prev_state,
|
| total_private_kb / 1024);
|
| }
|
|
|
| +MemoryCoordinatorImpl::ChildInfo::ChildInfo() {}
|
| +
|
| +MemoryCoordinatorImpl::ChildInfo::ChildInfo(const ChildInfo& rhs) {
|
| + // This is a nop, but exists for compatibility with STL containers.
|
| +}
|
| +
|
| +MemoryCoordinatorImpl::ChildInfo::~ChildInfo() {}
|
| +
|
| } // namespace content
|
|
|