diff --git a/base/BUILD.gn b/base/BUILD.gn index 69dfcc5b3bbe28..24caaa5065a502 100644 --- a/base/BUILD.gn +++ b/base/BUILD.gn @@ -1961,6 +1961,7 @@ test("base_unittests") { "memory/aligned_memory_unittest.cc", "memory/discardable_shared_memory_unittest.cc", "memory/linked_ptr_unittest.cc", + "memory/memory_coordinator_client_registry_unittest.cc", "memory/memory_pressure_listener_unittest.cc", "memory/memory_pressure_monitor_chromeos_unittest.cc", "memory/memory_pressure_monitor_mac_unittest.cc", diff --git a/base/memory/memory_coordinator_client.h b/base/memory/memory_coordinator_client.h index 148f4c175345cd..d24d3e7cd102cc 100644 --- a/base/memory/memory_coordinator_client.h +++ b/base/memory/memory_coordinator_client.h @@ -12,16 +12,22 @@ namespace base { // OVERVIEW: // // MemoryCoordinatorClient is an interface which a component can implement to -// respond to memory state changes. Unlike MemoryPressureListener, this is a -// stateful mechanism and clients receive notifications only when memory states -// are changed. State transitions are throttled to avoid thrashing; the exact -// throttling period is platform dependent, but will be at least 5-10 seconds. -// Clients are expected to make changes in memory usage that persist for the -// duration of the memory state. +// adjust "future allocation" and "existing allocation". For "future allocation" +// it provides a callback to observe memory state changes, and for "existing +// allocation" it provides a callback to purge memory. +// +// Unlike MemoryPressureListener, memory state changes are stateful. State +// transitions are throttled to avoid thrashing; the exact throttling period is +// platform dependent, but will be at least 5-10 seconds. When a state change +// notification is dispatched, clients are expected to update their allocation +// policies (e.g. setting cache limit) that persist for the duration of the +// memory state. Note that clients aren't expected to free up memory on memory +// state changes. Clients should wait for a separate purge request to free up +// memory. Purging requests will be throttled as well. // MemoryState is an indicator that processes can use to guide their memory -// allocation policies. For example, a process that receives the suspended -// state can use that as as signal to drop memory caches. +// allocation policies. For example, a process that receives the throttled +// state can use that as as signal to decrease memory cache limits. // NOTE: This enum is used to back an UMA histogram, and therefore should be // treated as append-only. enum class MemoryState : int { @@ -29,14 +35,13 @@ enum class MemoryState : int { UNKNOWN = -1, // No memory constraints. NORMAL = 0, - // Running and interactive but allocation should be throttled. - // Clients should free up any memory that is used as an optimization but - // that is not necessary for the process to run (e.g. caches). + // Running and interactive but memory allocation should be throttled. + // Clients should set lower budget for any memory that is used as an + // optimization but that is not necessary for the process to run. + // (e.g. caches) THROTTLED = 1, // Still resident in memory but core processing logic has been suspended. - // Clients should free up any memory that is used as an optimization, or - // any memory whose contents can be reproduced when transitioning out of - // the suspended state (e.g. parsed resource that can be reloaded from disk). + // In most cases, OnPurgeMemory() will be called before entering this state. SUSPENDED = 2, }; @@ -54,11 +59,18 @@ class BASE_EXPORT MemoryCoordinatorClient { // UNKNOWN. General guidelines are: // * NORMAL: Restore the default settings for memory allocation/usage if // it has changed. - // * THROTTLED: Use smaller limits for memory allocations and caches. - // * SUSPENDED: Purge memory. - virtual void OnMemoryStateChange(MemoryState state) = 0; + // * THROTTLED: Use smaller limits for future memory allocations. You don't + // need to take any action on existing allocations. + // * SUSPENDED: Use much smaller limits for future memory allocations. You + // don't need to take any action on existing allocations. + virtual void OnMemoryStateChange(MemoryState state) {} + + // Called to purge memory. + // This callback should free up any memory that is used as an optimization, or + // any memory whose contents can be reproduced. + virtual void OnPurgeMemory() {} -protected: + protected: virtual ~MemoryCoordinatorClient() {} }; diff --git a/base/memory/memory_coordinator_client_registry.cc b/base/memory/memory_coordinator_client_registry.cc index 57eedc683c38cc..67064581ab001e 100644 --- a/base/memory/memory_coordinator_client_registry.cc +++ b/base/memory/memory_coordinator_client_registry.cc @@ -34,4 +34,8 @@ void MemoryCoordinatorClientRegistry::Notify(MemoryState state) { &base::MemoryCoordinatorClient::OnMemoryStateChange, state); } +void MemoryCoordinatorClientRegistry::PurgeMemory() { + clients_->Notify(FROM_HERE, &base::MemoryCoordinatorClient::OnPurgeMemory); +} + } // namespace base diff --git a/base/memory/memory_coordinator_client_registry.h b/base/memory/memory_coordinator_client_registry.h index 8b5e7b56134311..e2c81b71876fa4 100644 --- a/base/memory/memory_coordinator_client_registry.h +++ b/base/memory/memory_coordinator_client_registry.h @@ -39,6 +39,9 @@ class BASE_EXPORT MemoryCoordinatorClientRegistry { // Notify clients of a memory state change. void Notify(MemoryState state); + // Requests purging memory. + void PurgeMemory(); + private: friend struct DefaultSingletonTraits; diff --git a/base/memory/memory_coordinator_client_registry_unittest.cc b/base/memory/memory_coordinator_client_registry_unittest.cc new file mode 100644 index 00000000000000..37ed7673d9aab6 --- /dev/null +++ b/base/memory/memory_coordinator_client_registry_unittest.cc @@ -0,0 +1,58 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/memory/memory_coordinator_client_registry.h" + +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +namespace { + +class TestMemoryCoordinatorClient : public MemoryCoordinatorClient { + public: + void OnMemoryStateChange(MemoryState state) override { state_ = state; } + + void OnPurgeMemory() override { ++purge_count_; } + + MemoryState state() const { return state_; } + size_t purge_count() const { return purge_count_; } + + private: + MemoryState state_ = MemoryState::UNKNOWN; + size_t purge_count_ = 0; +}; + +void RunUntilIdle() { + base::RunLoop loop; + loop.RunUntilIdle(); +} + +TEST(MemoryCoordinatorClientRegistryTest, NotifyStateChange) { + MessageLoop loop; + auto* registry = MemoryCoordinatorClientRegistry::GetInstance(); + TestMemoryCoordinatorClient client; + registry->Register(&client); + registry->Notify(MemoryState::THROTTLED); + RunUntilIdle(); + ASSERT_EQ(MemoryState::THROTTLED, client.state()); + registry->Unregister(&client); +} + +TEST(MemoryCoordinatorClientRegistryTest, PurgeMemory) { + MessageLoop loop; + auto* registry = MemoryCoordinatorClientRegistry::GetInstance(); + TestMemoryCoordinatorClient client; + registry->Register(&client); + registry->PurgeMemory(); + RunUntilIdle(); + ASSERT_EQ(1u, client.purge_count()); + registry->Unregister(&client); +} + +} // namespace + +} // namespace base