diff options
author | reveman@chromium.org <reveman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-04-25 09:26:31 +0000 |
---|---|---|
committer | reveman@chromium.org <reveman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-04-25 09:26:31 +0000 |
commit | f71953259a72e9370685afb9e4b8469390d463c9 (patch) | |
tree | df75a4cbf0642ea3910c9ca004410bff93fbff3a /base/memory | |
parent | 00ee2f5e7cfde91efe3c62ad17f48f2f605644ed (diff) | |
download | chromium_src-f71953259a72e9370685afb9e4b8469390d463c9.zip chromium_src-f71953259a72e9370685afb9e4b8469390d463c9.tar.gz chromium_src-f71953259a72e9370685afb9e4b8469390d463c9.tar.bz2 |
base: Refactor DiscdarableMemoryManager for use with different type of allocations.
This replaces the code in DiscardableMemoryManager that was
hard-coded to use heap allocations with code that handle
instances that implement the
DiscardableMemoryManagerAllocation interface instead.
This allows us to use the manager class not only for
emulated discardable memory but any other type that
might need some level of userspace control. This also
removes the circular dependency between
DiscardableMemoryManager and DiscardableMemory classes.
BUG=327516
TEST=base_unittests --gtest_filter=DiscardableMemory*
Review URL: https://codereview.chromium.org/204733003
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@266174 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'base/memory')
-rw-r--r-- | base/memory/discardable_memory_emulated.cc | 19 | ||||
-rw-r--r-- | base/memory/discardable_memory_emulated.h | 13 | ||||
-rw-r--r-- | base/memory/discardable_memory_manager.cc | 148 | ||||
-rw-r--r-- | base/memory/discardable_memory_manager.h | 137 | ||||
-rw-r--r-- | base/memory/discardable_memory_manager_unittest.cc | 378 |
5 files changed, 367 insertions, 328 deletions
diff --git a/base/memory/discardable_memory_emulated.cc b/base/memory/discardable_memory_emulated.cc index 82f887f..c9effe6 100644 --- a/base/memory/discardable_memory_emulated.cc +++ b/base/memory/discardable_memory_emulated.cc @@ -8,7 +8,6 @@ #include "base/memory/discardable_memory_manager.h" namespace base { - namespace { base::LazyInstance<internal::DiscardableMemoryManager>::Leaky g_manager = @@ -52,8 +51,7 @@ DiscardableMemoryLockStatus DiscardableMemoryEmulated::Lock() { DCHECK(!is_locked_); bool purged = false; - memory_ = g_manager.Pointer()->Acquire(this, &purged); - if (!memory_) + if (!g_manager.Pointer()->AcquireLock(this, &purged)) return DISCARDABLE_MEMORY_LOCK_STATUS_FAILED; is_locked_ = true; @@ -63,14 +61,27 @@ DiscardableMemoryLockStatus DiscardableMemoryEmulated::Lock() { void DiscardableMemoryEmulated::Unlock() { DCHECK(is_locked_); - g_manager.Pointer()->Release(this, memory_.Pass()); + g_manager.Pointer()->ReleaseLock(this); is_locked_ = false; } void* DiscardableMemoryEmulated::Memory() const { + DCHECK(is_locked_); DCHECK(memory_); return memory_.get(); } +bool DiscardableMemoryEmulated::AllocateAndAcquireLock(size_t bytes) { + if (memory_) + return true; + + memory_.reset(new uint8[bytes]); + return false; +} + +void DiscardableMemoryEmulated::Purge() { + memory_.reset(); +} + } // namespace internal } // namespace base diff --git a/base/memory/discardable_memory_emulated.h b/base/memory/discardable_memory_emulated.h index 20144ff..35ce08e 100644 --- a/base/memory/discardable_memory_emulated.h +++ b/base/memory/discardable_memory_emulated.h @@ -7,10 +7,14 @@ #include "base/memory/discardable_memory.h" +#include "base/memory/discardable_memory_manager.h" + namespace base { namespace internal { -class DiscardableMemoryEmulated : public DiscardableMemory { +class DiscardableMemoryEmulated + : public DiscardableMemory, + public internal::DiscardableMemoryManagerAllocation { public: explicit DiscardableMemoryEmulated(size_t size); virtual ~DiscardableMemoryEmulated(); @@ -27,8 +31,13 @@ class DiscardableMemoryEmulated : public DiscardableMemory { virtual void Unlock() OVERRIDE; virtual void* Memory() const OVERRIDE; + // Overridden from internal::DiscardableMemoryManagerAllocation: + virtual bool AllocateAndAcquireLock(size_t bytes) OVERRIDE; + virtual void ReleaseLock() OVERRIDE {} + virtual void Purge() OVERRIDE; + private: - scoped_ptr<uint8, FreeDeleter> memory_; + scoped_ptr<uint8[]> memory_; bool is_locked_; DISALLOW_COPY_AND_ASSIGN(DiscardableMemoryEmulated); diff --git a/base/memory/discardable_memory_manager.cc b/base/memory/discardable_memory_manager.cc index 87d3270..1dff3e4d 100644 --- a/base/memory/discardable_memory_manager.cc +++ b/base/memory/discardable_memory_manager.cc @@ -9,25 +9,23 @@ #include "base/containers/mru_cache.h" #include "base/debug/trace_event.h" #include "base/synchronization/lock.h" -#include "base/sys_info.h" namespace base { namespace internal { - namespace { // This is admittedly pretty magical. It's approximately enough memory for four // 2560x1600 images. -static const size_t kDefaultDiscardableMemoryLimit = 64 * 1024 * 1024; +static const size_t kDefaultMemoryLimit = 64 * 1024 * 1024; static const size_t kDefaultBytesToKeepUnderModeratePressure = - kDefaultDiscardableMemoryLimit / 4; + kDefaultMemoryLimit / 4; } // namespace DiscardableMemoryManager::DiscardableMemoryManager() : allocations_(AllocationMap::NO_AUTO_EVICT), bytes_allocated_(0), - discardable_memory_limit_(kDefaultDiscardableMemoryLimit), + memory_limit_(kDefaultMemoryLimit), bytes_to_keep_under_moderate_pressure_( kDefaultBytesToKeepUnderModeratePressure) { BytesAllocatedChanged(); @@ -42,10 +40,8 @@ void DiscardableMemoryManager::RegisterMemoryPressureListener() { AutoLock lock(lock_); DCHECK(base::MessageLoop::current()); DCHECK(!memory_pressure_listener_); - memory_pressure_listener_.reset( - new MemoryPressureListener( - base::Bind(&DiscardableMemoryManager::OnMemoryPressure, - Unretained(this)))); + memory_pressure_listener_.reset(new MemoryPressureListener(base::Bind( + &DiscardableMemoryManager::OnMemoryPressure, Unretained(this)))); } void DiscardableMemoryManager::UnregisterMemoryPressureListener() { @@ -54,9 +50,9 @@ void DiscardableMemoryManager::UnregisterMemoryPressureListener() { memory_pressure_listener_.reset(); } -void DiscardableMemoryManager::SetDiscardableMemoryLimit(size_t bytes) { +void DiscardableMemoryManager::SetMemoryLimit(size_t bytes) { AutoLock lock(lock_); - discardable_memory_limit_ = bytes; + memory_limit_ = bytes; EnforcePolicyWithLockAcquired(); } @@ -66,90 +62,77 @@ void DiscardableMemoryManager::SetBytesToKeepUnderModeratePressure( bytes_to_keep_under_moderate_pressure_ = bytes; } -void DiscardableMemoryManager::Register( - const DiscardableMemory* discardable, size_t bytes) { +void DiscardableMemoryManager::Register(Allocation* allocation, size_t bytes) { AutoLock lock(lock_); // A registered memory listener is currently required. This DCHECK can be // moved or removed if we decide that it's useful to relax this condition. // TODO(reveman): Enable this DCHECK when skia and blink are able to // register memory pressure listeners. crbug.com/333907 // DCHECK(memory_pressure_listener_); - DCHECK(allocations_.Peek(discardable) == allocations_.end()); - allocations_.Put(discardable, Allocation(bytes)); + DCHECK(allocations_.Peek(allocation) == allocations_.end()); + allocations_.Put(allocation, AllocationInfo(bytes)); } -void DiscardableMemoryManager::Unregister( - const DiscardableMemory* discardable) { +void DiscardableMemoryManager::Unregister(Allocation* allocation) { AutoLock lock(lock_); - AllocationMap::iterator it = allocations_.Peek(discardable); - if (it == allocations_.end()) - return; - - if (it->second.memory) { - size_t bytes = it->second.bytes; - DCHECK_LE(bytes, bytes_allocated_); - bytes_allocated_ -= bytes; + AllocationMap::iterator it = allocations_.Peek(allocation); + DCHECK(it != allocations_.end()); + const AllocationInfo& info = it->second; + + if (info.purgable) { + size_t bytes_purgable = info.bytes; + DCHECK_LE(bytes_purgable, bytes_allocated_); + bytes_allocated_ -= bytes_purgable; BytesAllocatedChanged(); - free(it->second.memory); } allocations_.Erase(it); } -scoped_ptr<uint8, FreeDeleter> DiscardableMemoryManager::Acquire( - const DiscardableMemory* discardable, - bool* purged) { +bool DiscardableMemoryManager::AcquireLock(Allocation* allocation, + bool* purged) { AutoLock lock(lock_); - // NB: |allocations_| is an MRU cache, and use of |Get| here updates that + // Note: |allocations_| is an MRU cache, and use of |Get| here updates that // cache. - AllocationMap::iterator it = allocations_.Get(discardable); - CHECK(it != allocations_.end()); - - if (it->second.memory) { - scoped_ptr<uint8, FreeDeleter> memory(it->second.memory); - it->second.memory = NULL; - *purged = false; - return memory.Pass(); - } + AllocationMap::iterator it = allocations_.Get(allocation); + DCHECK(it != allocations_.end()); + AllocationInfo* info = &it->second; + + if (!info->bytes) + return false; - size_t bytes = it->second.bytes; - if (!bytes) - return scoped_ptr<uint8, FreeDeleter>(); + size_t bytes_required = info->purgable ? 0u : info->bytes; - if (discardable_memory_limit_) { + if (memory_limit_) { size_t limit = 0; - if (bytes < discardable_memory_limit_) - limit = discardable_memory_limit_ - bytes; + if (bytes_required < memory_limit_) + limit = memory_limit_ - bytes_required; PurgeLRUWithLockAcquiredUntilUsageIsWithin(limit); } // Check for overflow. - if (std::numeric_limits<size_t>::max() - bytes < bytes_allocated_) - return scoped_ptr<uint8, FreeDeleter>(); - - scoped_ptr<uint8, FreeDeleter> memory(static_cast<uint8*>(malloc(bytes))); - if (!memory) - return scoped_ptr<uint8, FreeDeleter>(); - - bytes_allocated_ += bytes; - BytesAllocatedChanged(); + if (std::numeric_limits<size_t>::max() - bytes_required < bytes_allocated_) + return false; - *purged = true; - return memory.Pass(); + *purged = !allocation->AllocateAndAcquireLock(info->bytes); + info->purgable = false; + if (bytes_required) { + bytes_allocated_ += bytes_required; + BytesAllocatedChanged(); + } + return true; } -void DiscardableMemoryManager::Release( - const DiscardableMemory* discardable, - scoped_ptr<uint8, FreeDeleter> memory) { +void DiscardableMemoryManager::ReleaseLock(Allocation* allocation) { AutoLock lock(lock_); - // NB: |allocations_| is an MRU cache, and use of |Get| here updates that + // Note: |allocations_| is an MRU cache, and use of |Get| here updates that // cache. - AllocationMap::iterator it = allocations_.Get(discardable); - CHECK(it != allocations_.end()); - - DCHECK(!it->second.memory); - it->second.memory = memory.release(); + AllocationMap::iterator it = allocations_.Get(allocation); + DCHECK(it != allocations_.end()); + AllocationInfo* info = &it->second; + allocation->ReleaseLock(); + info->purgable = true; EnforcePolicyWithLockAcquired(); } @@ -159,17 +142,17 @@ void DiscardableMemoryManager::PurgeAll() { } bool DiscardableMemoryManager::IsRegisteredForTest( - const DiscardableMemory* discardable) const { + Allocation* allocation) const { AutoLock lock(lock_); - AllocationMap::const_iterator it = allocations_.Peek(discardable); + AllocationMap::const_iterator it = allocations_.Peek(allocation); return it != allocations_.end(); } bool DiscardableMemoryManager::CanBePurgedForTest( - const DiscardableMemory* discardable) const { + Allocation* allocation) const { AutoLock lock(lock_); - AllocationMap::const_iterator it = allocations_.Peek(discardable); - return it != allocations_.end() && it->second.memory; + AllocationMap::const_iterator it = allocations_.Peek(allocation); + return it != allocations_.end() && it->second.purgable; } size_t DiscardableMemoryManager::GetBytesAllocatedForTest() const { @@ -203,7 +186,8 @@ void DiscardableMemoryManager::PurgeLRUWithLockAcquiredUntilUsageIsWithin( TRACE_EVENT1( "base", "DiscardableMemoryManager::PurgeLRUWithLockAcquiredUntilUsageIsWithin", - "limit", limit); + "limit", + limit); lock_.AssertAcquired(); @@ -211,16 +195,19 @@ void DiscardableMemoryManager::PurgeLRUWithLockAcquiredUntilUsageIsWithin( for (AllocationMap::reverse_iterator it = allocations_.rbegin(); it != allocations_.rend(); ++it) { + Allocation* allocation = it->first; + AllocationInfo* info = &it->second; + if (bytes_allocated_ <= limit) break; - if (!it->second.memory) + if (!info->purgable) continue; - size_t bytes = it->second.bytes; - DCHECK_LE(bytes, bytes_allocated_); - bytes_allocated_ -= bytes; - free(it->second.memory); - it->second.memory = NULL; + size_t bytes_purgable = info->bytes; + DCHECK_LE(bytes_purgable, bytes_allocated_); + bytes_allocated_ -= bytes_purgable; + info->purgable = false; + allocation->Purge(); } if (bytes_allocated_ != bytes_allocated_before_purging) @@ -228,14 +215,11 @@ void DiscardableMemoryManager::PurgeLRUWithLockAcquiredUntilUsageIsWithin( } void DiscardableMemoryManager::EnforcePolicyWithLockAcquired() { - PurgeLRUWithLockAcquiredUntilUsageIsWithin(discardable_memory_limit_); + PurgeLRUWithLockAcquiredUntilUsageIsWithin(memory_limit_); } void DiscardableMemoryManager::BytesAllocatedChanged() const { - TRACE_COUNTER_ID1("base", - "DiscardableMemoryUsage", - this, - bytes_allocated_); + TRACE_COUNTER_ID1("base", "DiscardableMemoryUsage", this, bytes_allocated_); } } // namespace internal diff --git a/base/memory/discardable_memory_manager.h b/base/memory/discardable_memory_manager.h index 7c0bd5c..8447521 100644 --- a/base/memory/discardable_memory_manager.h +++ b/base/memory/discardable_memory_manager.h @@ -12,14 +12,38 @@ #include "base/synchronization/lock.h" namespace base { -class DiscardableMemory; +namespace internal { + +// This interface is used by the DiscardableMemoryManager class to provide some +// level of userspace control over discardable memory allocations. +class DiscardableMemoryManagerAllocation { + public: + // Allocate and acquire a lock that prevents the allocation from being purged + // by the system. Returns true if memory was previously allocated and is still + // resident. + virtual bool AllocateAndAcquireLock(size_t bytes) = 0; + + // Release a previously acquired lock on the allocation so that it can be + // purged by the system. + virtual void ReleaseLock() = 0; + + // Explicitly purge this allocation. It is illegal to call this while a lock + // is acquired on the allocation. + virtual void Purge() = 0; + + protected: + virtual ~DiscardableMemoryManagerAllocation() {} +}; + +} // namespace internal } // namespace base #if defined(COMPILER_GCC) namespace BASE_HASH_NAMESPACE { template <> -struct hash<const base::DiscardableMemory*> { - size_t operator()(const base::DiscardableMemory* ptr) const { +struct hash<base::internal::DiscardableMemoryManagerAllocation*> { + size_t operator()( + base::internal::DiscardableMemoryManagerAllocation* ptr) const { return hash<size_t>()(reinterpret_cast<size_t>(ptr)); } }; @@ -29,87 +53,82 @@ struct hash<const base::DiscardableMemory*> { namespace base { namespace internal { -// The DiscardableMemoryManager manages a collection of emulated -// DiscardableMemory instances. It is used on platforms that do not support -// discardable memory natively. It keeps track of all DiscardableMemory -// instances (in case they need to be purged), and the total amount of -// allocated memory (in case this forces a purge). -// -// When notified of memory pressure, the manager either purges the LRU -// memory -- if the pressure is moderate -- or all discardable memory -// if the pressure is critical. +// The DiscardableMemoryManager manages a collection of +// DiscardableMemoryManagerAllocation instances. It is used on platforms that +// need some level of userspace control over discardable memory. It keeps track +// of all allocation instances (in case they need to be purged), and the total +// amount of allocated memory (in case this forces a purge). When memory usage +// reaches the limit, the manager purges the LRU memory. // -// NB - this class is an implementation detail. It has been exposed for testing -// purposes. You should not need to use this class directly. +// When notified of memory pressure, the manager either purges the LRU memory -- +// if the pressure is moderate -- or all discardable memory if the pressure is +// critical. class BASE_EXPORT_PRIVATE DiscardableMemoryManager { public: + typedef DiscardableMemoryManagerAllocation Allocation; + DiscardableMemoryManager(); ~DiscardableMemoryManager(); - // Call this to register memory pressure listener. Must be called on a - // thread with a MessageLoop current. + // Call this to register memory pressure listener. Must be called on a thread + // with a MessageLoop current. void RegisterMemoryPressureListener(); // Call this to unregister memory pressure listener. void UnregisterMemoryPressureListener(); - // The maximum number of bytes of discardable memory that may be allocated - // before we force a purge. If this amount is zero, it is interpreted as - // having no limit at all. - void SetDiscardableMemoryLimit(size_t bytes); + // The maximum number of bytes of memory that may be allocated before we force + // a purge. If this amount is zero, it is interpreted as having no limit at + // all. + void SetMemoryLimit(size_t bytes); // Sets the amount of memory to keep when we're under moderate pressure. void SetBytesToKeepUnderModeratePressure(size_t bytes); - // Adds the given discardable memory to the manager's collection. - void Register(const DiscardableMemory* discardable, size_t bytes); + // Adds the given allocation to the manager's collection. + void Register(Allocation* allocation, size_t bytes); - // Removes the given discardable memory from the manager's collection. - void Unregister(const DiscardableMemory* discardable); + // Removes the given allocation from the manager's collection. + void Unregister(Allocation* allocation); - // Returns NULL if an error occurred. Otherwise, returns the backing buffer - // and sets |purged| to indicate whether or not the backing buffer has been - // purged since last use. - scoped_ptr<uint8, FreeDeleter> Acquire( - const DiscardableMemory* discardable, bool* purged); + // Returns false if an error occurred. Otherwise, returns true and sets + // |purged| to indicate whether or not allocation has been purged since last + // use. + bool AcquireLock(Allocation* allocation, bool* purged); - // Release a previously acquired backing buffer. This gives the buffer back - // to the manager where it can be purged if necessary. - void Release(const DiscardableMemory* discardable, - scoped_ptr<uint8, FreeDeleter> memory); + // Release a previously acquired lock on allocation. This allows the manager + // to purge it if necessary. + void ReleaseLock(Allocation* allocation); // Purges all discardable memory. void PurgeAll(); - // Returns true if discardable memory has been added to the manager's - // collection. This should only be used by tests. - bool IsRegisteredForTest(const DiscardableMemory* discardable) const; + // Returns true if allocation has been added to the manager's collection. This + // should only be used by tests. + bool IsRegisteredForTest(Allocation* allocation) const; - // Returns true if discardable memory can be purged. This should only - // be used by tests. - bool CanBePurgedForTest(const DiscardableMemory* discardable) const; + // Returns true if allocation can be purged. This should only be used by + // tests. + bool CanBePurgedForTest(Allocation* allocation) const; - // Returns total amount of allocated discardable memory. This should only - // be used by tests. + // Returns total amount of allocated discardable memory. This should only be + // used by tests. size_t GetBytesAllocatedForTest() const; private: - struct Allocation { - explicit Allocation(size_t bytes) - : bytes(bytes), - memory(NULL) { - } - - size_t bytes; - uint8* memory; + struct AllocationInfo { + explicit AllocationInfo(size_t bytes) : bytes(bytes), purgable(false) {} + + const size_t bytes; + bool purgable; }; - typedef HashingMRUCache<const DiscardableMemory*, Allocation> AllocationMap; + typedef HashingMRUCache<Allocation*, AllocationInfo> AllocationMap; // This can be called as a hint that the system is under memory pressure. void OnMemoryPressure( MemoryPressureListener::MemoryPressureLevel pressure_level); - // Purges until discardable memory usage is within + // Purges memory until usage is within // |bytes_to_keep_under_moderate_pressure_|. void Purge(); @@ -117,8 +136,8 @@ class BASE_EXPORT_PRIVATE DiscardableMemoryManager { // Caller must acquire |lock_| prior to calling this function. void PurgeLRUWithLockAcquiredUntilUsageIsWithin(size_t limit); - // Ensures that we don't allocate beyond our memory limit. - // Caller must acquire |lock_| prior to calling this function. + // Ensures that we don't allocate beyond our memory limit. Caller must acquire + // |lock_| prior to calling this function. void EnforcePolicyWithLockAcquired(); // Called when a change to |bytes_allocated_| has been made. @@ -127,17 +146,17 @@ class BASE_EXPORT_PRIVATE DiscardableMemoryManager { // Needs to be held when accessing members. mutable Lock lock_; - // A MRU cache of all allocated bits of discardable memory. Used for purging. + // A MRU cache of all allocated bits of memory. Used for purging. AllocationMap allocations_; - // The total amount of allocated discardable memory. + // The total amount of allocated memory. size_t bytes_allocated_; - // The maximum number of bytes of discardable memory that may be allocated. - size_t discardable_memory_limit_; + // The maximum number of bytes of memory that may be allocated. + size_t memory_limit_; - // Under moderate memory pressure, we will purge until usage is within this - // limit. + // Under moderate memory pressure, we will purge memory until usage is within + // this limit. size_t bytes_to_keep_under_moderate_pressure_; // Allows us to be respond when the system reports that it is under memory diff --git a/base/memory/discardable_memory_manager_unittest.cc b/base/memory/discardable_memory_manager_unittest.cc index 95e7c13..58a9603 100644 --- a/base/memory/discardable_memory_manager_unittest.cc +++ b/base/memory/discardable_memory_manager_unittest.cc @@ -5,156 +5,157 @@ #include "base/memory/discardable_memory_manager.h" #include "base/bind.h" -#include "base/memory/discardable_memory.h" #include "base/run_loop.h" #include "base/synchronization/waitable_event.h" #include "base/threading/thread.h" #include "testing/gtest/include/gtest/gtest.h" namespace base { +namespace { -class DiscardableMemoryManagerTestBase { +class TestAllocationImpl : public internal::DiscardableMemoryManagerAllocation { public: - class TestDiscardableMemory : public DiscardableMemory { - public: - TestDiscardableMemory( - internal::DiscardableMemoryManager* manager, size_t size) - : manager_(manager), - is_locked_(false) { - manager_->Register(this, size); - } + TestAllocationImpl() : is_allocated_(false), is_locked_(false) {} + virtual ~TestAllocationImpl() { DCHECK(!is_locked_); } + + // Overridden from internal::DiscardableMemoryManagerAllocation: + virtual bool AllocateAndAcquireLock(size_t bytes) OVERRIDE { + bool was_allocated = is_allocated_; + is_allocated_ = true; + DCHECK(!is_locked_); + is_locked_ = true; + return was_allocated; + } + virtual void ReleaseLock() OVERRIDE { + DCHECK(is_locked_); + is_locked_ = false; + } + virtual void Purge() OVERRIDE { + DCHECK(is_allocated_); + is_allocated_ = false; + } - virtual ~TestDiscardableMemory() { - if (is_locked_) - Unlock(); - manager_->Unregister(this); - } + bool is_locked() const { return is_locked_; } - // Overridden from DiscardableMemory: - virtual DiscardableMemoryLockStatus Lock() OVERRIDE { - DCHECK(!is_locked_); + private: + bool is_allocated_; + bool is_locked_; +}; - bool purged = false; - memory_ = manager_->Acquire(this, &purged); - if (!memory_) - return DISCARDABLE_MEMORY_LOCK_STATUS_FAILED; +class DiscardableMemoryManagerTestBase { + public: + DiscardableMemoryManagerTestBase() { + manager_.RegisterMemoryPressureListener(); + } - is_locked_ = true; - return purged ? DISCARDABLE_MEMORY_LOCK_STATUS_PURGED - : DISCARDABLE_MEMORY_LOCK_STATUS_SUCCESS; - } - virtual void Unlock() OVERRIDE { - DCHECK(is_locked_); - manager_->Release(this, memory_.Pass()); - is_locked_ = false; - } - virtual void* Memory() const OVERRIDE { - DCHECK(memory_); - return memory_.get(); - } + protected: + enum LockStatus { + LOCK_STATUS_FAILED, + LOCK_STATUS_PURGED, + LOCK_STATUS_SUCCESS + }; - private: - internal::DiscardableMemoryManager* manager_; - scoped_ptr<uint8, FreeDeleter> memory_; - bool is_locked_; + size_t BytesAllocated() const { return manager_.GetBytesAllocatedForTest(); } - DISALLOW_COPY_AND_ASSIGN(TestDiscardableMemory); - }; + void SetMemoryLimit(size_t bytes) { manager_.SetMemoryLimit(bytes); } - DiscardableMemoryManagerTestBase() - : manager_(new internal::DiscardableMemoryManager) { - manager_->RegisterMemoryPressureListener(); + void SetBytesToKeepUnderModeratePressure(size_t bytes) { + manager_.SetBytesToKeepUnderModeratePressure(bytes); } - protected: - bool IsRegistered(const DiscardableMemory* discardable) { - return manager_->IsRegisteredForTest(discardable); + void Register(TestAllocationImpl* allocation, size_t bytes) { + manager_.Register(allocation, bytes); } - bool CanBePurged(const DiscardableMemory* discardable) { - return manager_->CanBePurgedForTest(discardable); + void Unregister(TestAllocationImpl* allocation) { + manager_.Unregister(allocation); } - size_t BytesAllocated() const { - return manager_->GetBytesAllocatedForTest(); + bool IsRegistered(TestAllocationImpl* allocation) const { + return manager_.IsRegisteredForTest(allocation); } - void* Memory(const DiscardableMemory* discardable) const { - return discardable->Memory(); + LockStatus Lock(TestAllocationImpl* allocation) { + bool purged; + if (!manager_.AcquireLock(allocation, &purged)) + return LOCK_STATUS_FAILED; + return purged ? LOCK_STATUS_PURGED : LOCK_STATUS_SUCCESS; } - void SetDiscardableMemoryLimit(size_t bytes) { - manager_->SetDiscardableMemoryLimit(bytes); + void Unlock(TestAllocationImpl* allocation) { + manager_.ReleaseLock(allocation); } - void SetBytesToKeepUnderModeratePressure(size_t bytes) { - manager_->SetBytesToKeepUnderModeratePressure(bytes); + LockStatus RegisterAndLock(TestAllocationImpl* allocation, size_t bytes) { + manager_.Register(allocation, bytes); + return Lock(allocation); } - scoped_ptr<DiscardableMemory> CreateLockedMemory(size_t size) { - scoped_ptr<TestDiscardableMemory> memory( - new TestDiscardableMemory(manager_.get(), size)); - if (memory->Lock() != DISCARDABLE_MEMORY_LOCK_STATUS_PURGED) - return scoped_ptr<DiscardableMemory>(); - return memory.PassAs<DiscardableMemory>(); + bool CanBePurged(TestAllocationImpl* allocation) const { + return manager_.CanBePurgedForTest(allocation); } private: MessageLoopForIO message_loop_; - scoped_ptr<internal::DiscardableMemoryManager> manager_; + internal::DiscardableMemoryManager manager_; }; -class DiscardableMemoryManagerTest - : public DiscardableMemoryManagerTestBase, - public testing::Test { +class DiscardableMemoryManagerTest : public DiscardableMemoryManagerTestBase, + public testing::Test { public: DiscardableMemoryManagerTest() {} }; -TEST_F(DiscardableMemoryManagerTest, CreateLockedMemory) { +TEST_F(DiscardableMemoryManagerTest, CreateAndLock) { size_t size = 1024; - const scoped_ptr<DiscardableMemory> discardable(CreateLockedMemory(size)); - EXPECT_TRUE(IsRegistered(discardable.get())); - EXPECT_NE(static_cast<void*>(NULL), Memory(discardable.get())); + TestAllocationImpl allocation; + Register(&allocation, size); + EXPECT_TRUE(IsRegistered(&allocation)); + EXPECT_EQ(LOCK_STATUS_PURGED, Lock(&allocation)); + EXPECT_TRUE(allocation.is_locked()); EXPECT_EQ(1024u, BytesAllocated()); - EXPECT_FALSE(CanBePurged(discardable.get())); + EXPECT_FALSE(CanBePurged(&allocation)); + Unlock(&allocation); + Unregister(&allocation); } -TEST_F(DiscardableMemoryManagerTest, CreateLockedMemoryZeroSize) { +TEST_F(DiscardableMemoryManagerTest, CreateZeroSize) { size_t size = 0; - const scoped_ptr<DiscardableMemory> discardable(CreateLockedMemory(size)); - EXPECT_FALSE(discardable); - EXPECT_FALSE(IsRegistered(discardable.get())); + TestAllocationImpl allocation; + Register(&allocation, size); + EXPECT_TRUE(IsRegistered(&allocation)); + EXPECT_EQ(LOCK_STATUS_FAILED, Lock(&allocation)); EXPECT_EQ(0u, BytesAllocated()); + Unregister(&allocation); } TEST_F(DiscardableMemoryManagerTest, LockAfterUnlock) { size_t size = 1024; - const scoped_ptr<DiscardableMemory> discardable(CreateLockedMemory(size)); - EXPECT_TRUE(IsRegistered(discardable.get())); - EXPECT_NE(static_cast<void*>(NULL), Memory(discardable.get())); + TestAllocationImpl allocation; + RegisterAndLock(&allocation, size); EXPECT_EQ(1024u, BytesAllocated()); - EXPECT_FALSE(CanBePurged(discardable.get())); + EXPECT_FALSE(CanBePurged(&allocation)); // Now unlock so we can lock later. - discardable->Unlock(); - EXPECT_TRUE(CanBePurged(discardable.get())); + Unlock(&allocation); + EXPECT_TRUE(CanBePurged(&allocation)); - EXPECT_EQ(DISCARDABLE_MEMORY_LOCK_STATUS_SUCCESS, discardable->Lock()); - EXPECT_FALSE(CanBePurged(discardable.get())); + EXPECT_EQ(LOCK_STATUS_SUCCESS, Lock(&allocation)); + EXPECT_FALSE(CanBePurged(&allocation)); + Unlock(&allocation); + Unregister(&allocation); } TEST_F(DiscardableMemoryManagerTest, LockAfterPurge) { size_t size = 1024; - const scoped_ptr<DiscardableMemory> discardable(CreateLockedMemory(size)); - EXPECT_TRUE(IsRegistered(discardable.get())); - EXPECT_NE(static_cast<void*>(NULL), Memory(discardable.get())); + TestAllocationImpl allocation; + RegisterAndLock(&allocation, size); EXPECT_EQ(1024u, BytesAllocated()); - EXPECT_FALSE(CanBePurged(discardable.get())); + EXPECT_FALSE(CanBePurged(&allocation)); // Now unlock so we can lock later. - discardable->Unlock(); - EXPECT_TRUE(CanBePurged(discardable.get())); + Unlock(&allocation); + EXPECT_TRUE(CanBePurged(&allocation)); // Force the system to purge. MemoryPressureListener::NotifyMemoryPressure( @@ -163,43 +164,53 @@ TEST_F(DiscardableMemoryManagerTest, LockAfterPurge) { // Required because ObserverListThreadSafe notifies via PostTask. RunLoop().RunUntilIdle(); - EXPECT_EQ(DISCARDABLE_MEMORY_LOCK_STATUS_PURGED, discardable->Lock()); - EXPECT_FALSE(CanBePurged(discardable.get())); + EXPECT_EQ(LOCK_STATUS_PURGED, Lock(&allocation)); + EXPECT_FALSE(CanBePurged(&allocation)); + + Unlock(&allocation); + Unregister(&allocation); } TEST_F(DiscardableMemoryManagerTest, LockAfterPurgeAndCannotReallocate) { size_t size = 1024; - const scoped_ptr<DiscardableMemory> discardable(CreateLockedMemory(size)); - EXPECT_TRUE(IsRegistered(discardable.get())); - EXPECT_NE(static_cast<void*>(NULL), Memory(discardable.get())); + TestAllocationImpl allocation; + RegisterAndLock(&allocation, size); EXPECT_EQ(1024u, BytesAllocated()); - EXPECT_FALSE(CanBePurged(discardable.get())); + EXPECT_FALSE(CanBePurged(&allocation)); // Now unlock so we can lock later. - discardable->Unlock(); - EXPECT_TRUE(CanBePurged(discardable.get())); + Unlock(&allocation); + EXPECT_TRUE(CanBePurged(&allocation)); + + // Set max allowed allocation to 1 byte. This will cause the memory to be + // purged. + SetMemoryLimit(1); - // Set max allowed allocation to 1 byte. This will make cause the memory - // to be purged. - SetDiscardableMemoryLimit(1); + EXPECT_EQ(LOCK_STATUS_PURGED, Lock(&allocation)); + EXPECT_FALSE(CanBePurged(&allocation)); - EXPECT_EQ(DISCARDABLE_MEMORY_LOCK_STATUS_PURGED, discardable->Lock()); - EXPECT_FALSE(CanBePurged(discardable.get())); + Unlock(&allocation); + Unregister(&allocation); } TEST_F(DiscardableMemoryManagerTest, Overflow) { + size_t size = 1024; { - size_t size = 1024; - const scoped_ptr<DiscardableMemory> discardable(CreateLockedMemory(size)); - EXPECT_TRUE(IsRegistered(discardable.get())); - EXPECT_NE(static_cast<void*>(NULL), Memory(discardable.get())); + TestAllocationImpl allocation; + RegisterAndLock(&allocation, size); EXPECT_EQ(1024u, BytesAllocated()); size_t massive_size = std::numeric_limits<size_t>::max(); - const scoped_ptr<DiscardableMemory> massive_discardable( - CreateLockedMemory(massive_size)); - EXPECT_FALSE(massive_discardable); + TestAllocationImpl massive_allocation; + Register(&massive_allocation, massive_size); + EXPECT_EQ(LOCK_STATUS_FAILED, Lock(&massive_allocation)); EXPECT_EQ(1024u, BytesAllocated()); + + Unlock(&allocation); + EXPECT_EQ(LOCK_STATUS_PURGED, Lock(&massive_allocation)); + Unlock(&massive_allocation); + Unregister(&massive_allocation); + Unregister(&allocation); } EXPECT_EQ(0u, BytesAllocated()); } @@ -225,83 +236,93 @@ class DiscardableMemoryManagerPermutationTest DiscardableMemoryManagerPermutationTest() {} protected: - // Use discardable memory in order specified by ordering parameter. - void CreateAndUseDiscardableMemory() { + // Use memory in order specified by ordering parameter. + void RegisterAndUseAllocations() { for (int i = 0; i < 3; ++i) { - discardables_[i] = CreateLockedMemory(1024); - EXPECT_TRUE(discardables_[i]); - EXPECT_NE(static_cast<void*>(NULL), Memory(discardables_[i].get())); - discardables_[i]->Unlock(); + RegisterAndLock(&allocation_[i], 1024); + Unlock(&allocation_[i]); } for (int i = 0; i < 3; ++i) { int index = GetParam().ordering()[i]; - EXPECT_NE(DISCARDABLE_MEMORY_LOCK_STATUS_FAILED, - discardables_[index]->Lock()); + EXPECT_NE(LOCK_STATUS_FAILED, Lock(&allocation_[index])); // Leave i == 0 locked. if (i > 0) - discardables_[index]->Unlock(); + Unlock(&allocation_[index]); } } - DiscardableMemory* discardable(unsigned position) { - return discardables_[GetParam().ordering()[position]].get(); + TestAllocationImpl* allocation(unsigned position) { + return &allocation_[GetParam().ordering()[position]]; + } + + void UnlockAndUnregisterAllocations() { + for (int i = 0; i < 3; ++i) { + if (allocation_[i].is_locked()) + Unlock(&allocation_[i]); + Unregister(&allocation_[i]); + } } private: - scoped_ptr<DiscardableMemory> discardables_[3]; + TestAllocationImpl allocation_[3]; }; // Verify that memory was discarded in the correct order after applying // memory pressure. TEST_P(DiscardableMemoryManagerPermutationTest, LRUDiscardedModeratePressure) { - CreateAndUseDiscardableMemory(); + RegisterAndUseAllocations(); SetBytesToKeepUnderModeratePressure(1024); - SetDiscardableMemoryLimit(2048); + SetMemoryLimit(2048); MemoryPressureListener::NotifyMemoryPressure( MemoryPressureListener::MEMORY_PRESSURE_MODERATE); RunLoop().RunUntilIdle(); - EXPECT_NE(DISCARDABLE_MEMORY_LOCK_STATUS_FAILED, discardable(2)->Lock()); - EXPECT_NE(DISCARDABLE_MEMORY_LOCK_STATUS_SUCCESS, discardable(1)->Lock()); + EXPECT_NE(LOCK_STATUS_FAILED, Lock(allocation(2))); + EXPECT_EQ(LOCK_STATUS_PURGED, Lock(allocation(1))); // 0 should still be locked. - EXPECT_NE(static_cast<void*>(NULL), Memory(discardable(0))); + EXPECT_TRUE(allocation(0)->is_locked()); + + UnlockAndUnregisterAllocations(); } // Verify that memory was discarded in the correct order after changing // memory limit. TEST_P(DiscardableMemoryManagerPermutationTest, LRUDiscardedExceedLimit) { - CreateAndUseDiscardableMemory(); + RegisterAndUseAllocations(); SetBytesToKeepUnderModeratePressure(1024); - SetDiscardableMemoryLimit(2048); + SetMemoryLimit(2048); - EXPECT_NE(DISCARDABLE_MEMORY_LOCK_STATUS_FAILED, discardable(2)->Lock()); - EXPECT_NE(DISCARDABLE_MEMORY_LOCK_STATUS_SUCCESS, discardable(1)->Lock()); + EXPECT_NE(LOCK_STATUS_FAILED, Lock(allocation(2))); + EXPECT_EQ(LOCK_STATUS_PURGED, Lock(allocation(1))); // 0 should still be locked. - EXPECT_NE(static_cast<void*>(NULL), Memory(discardable(0))); + EXPECT_TRUE(allocation(0)->is_locked()); + + UnlockAndUnregisterAllocations(); } // Verify that no more memory than necessary was discarded after changing // memory limit. TEST_P(DiscardableMemoryManagerPermutationTest, LRUDiscardedAmount) { SetBytesToKeepUnderModeratePressure(2048); - SetDiscardableMemoryLimit(4096); + SetMemoryLimit(4096); - CreateAndUseDiscardableMemory(); + RegisterAndUseAllocations(); - SetDiscardableMemoryLimit(2048); + SetMemoryLimit(2048); - EXPECT_EQ(DISCARDABLE_MEMORY_LOCK_STATUS_SUCCESS, discardable(2)->Lock()); - EXPECT_EQ(DISCARDABLE_MEMORY_LOCK_STATUS_PURGED, discardable(1)->Lock()); + EXPECT_EQ(LOCK_STATUS_SUCCESS, Lock(allocation(2))); + EXPECT_EQ(LOCK_STATUS_PURGED, Lock(allocation(1))); // 0 should still be locked. - EXPECT_NE(static_cast<void*>(NULL), Memory(discardable(0))); + EXPECT_TRUE(allocation(0)->is_locked()); + + UnlockAndUnregisterAllocations(); } -TEST_P(DiscardableMemoryManagerPermutationTest, - CriticalPressureFreesAllUnlocked) { - CreateAndUseDiscardableMemory(); +TEST_P(DiscardableMemoryManagerPermutationTest, PurgeFreesAllUnlocked) { + RegisterAndUseAllocations(); MemoryPressureListener::NotifyMemoryPressure( MemoryPressureListener::MEMORY_PRESSURE_CRITICAL); @@ -309,10 +330,12 @@ TEST_P(DiscardableMemoryManagerPermutationTest, for (int i = 0; i < 3; ++i) { if (i == 0) - EXPECT_NE(static_cast<void*>(NULL), Memory(discardable(i))); + EXPECT_TRUE(allocation(i)->is_locked()); else - EXPECT_EQ(DISCARDABLE_MEMORY_LOCK_STATUS_PURGED, discardable(i)->Lock()); + EXPECT_EQ(LOCK_STATUS_PURGED, Lock(allocation(i))); } + + UnlockAndUnregisterAllocations(); } INSTANTIATE_TEST_CASE_P(DiscardableMemoryManagerPermutationTests, @@ -327,69 +350,61 @@ INSTANTIATE_TEST_CASE_P(DiscardableMemoryManagerPermutationTests, TEST_F(DiscardableMemoryManagerTest, NormalDestruction) { { size_t size = 1024; - const scoped_ptr<DiscardableMemory> discardable(CreateLockedMemory(size)); - EXPECT_TRUE(IsRegistered(discardable.get())); - EXPECT_EQ(1024u, BytesAllocated()); + TestAllocationImpl allocation; + Register(&allocation, size); + Unregister(&allocation); } EXPECT_EQ(0u, BytesAllocated()); } -TEST_F(DiscardableMemoryManagerTest, DestructionWhileLocked) { +TEST_F(DiscardableMemoryManagerTest, DestructionAfterLocked) { { size_t size = 1024; - const scoped_ptr<DiscardableMemory> discardable(CreateLockedMemory(size)); - EXPECT_TRUE(IsRegistered(discardable.get())); - EXPECT_NE(static_cast<void*>(NULL), Memory(discardable.get())); + TestAllocationImpl allocation; + RegisterAndLock(&allocation, size); EXPECT_EQ(1024u, BytesAllocated()); - EXPECT_FALSE(CanBePurged(discardable.get())); + EXPECT_FALSE(CanBePurged(&allocation)); + Unlock(&allocation); + Unregister(&allocation); } - // Should have ignored the "locked" status and freed the discardable memory. EXPECT_EQ(0u, BytesAllocated()); } -#if !defined(NDEBUG) && !defined(OS_ANDROID) && !defined(OS_IOS) -// Death tests are not supported with Android APKs. -TEST_F(DiscardableMemoryManagerTest, UnlockedMemoryAccessCrashesInDebugMode) { - size_t size = 1024; - const scoped_ptr<DiscardableMemory> discardable(CreateLockedMemory(size)); - EXPECT_TRUE(IsRegistered(discardable.get())); - EXPECT_NE(static_cast<void*>(NULL), Memory(discardable.get())); - EXPECT_EQ(1024u, BytesAllocated()); - EXPECT_FALSE(CanBePurged(discardable.get())); - discardable->Unlock(); - EXPECT_TRUE(CanBePurged(discardable.get())); - // We *must* die if we are asked to vend a pointer to unlocked memory. - EXPECT_DEATH(discardable->Memory(), ".*Check failed.*"); +TEST_F(DiscardableMemoryManagerTest, DestructionAfterPurged) { + { + size_t size = 1024; + TestAllocationImpl allocation; + RegisterAndLock(&allocation, size); + EXPECT_EQ(1024u, BytesAllocated()); + Unlock(&allocation); + EXPECT_TRUE(CanBePurged(&allocation)); + SetMemoryLimit(0); + EXPECT_EQ(0u, BytesAllocated()); + Unregister(&allocation); + } + EXPECT_EQ(0u, BytesAllocated()); } -#endif class ThreadedDiscardableMemoryManagerTest : public DiscardableMemoryManagerTest { public: ThreadedDiscardableMemoryManagerTest() : memory_usage_thread_("memory_usage_thread"), - thread_sync_(true, false) { - } + thread_sync_(true, false) {} - virtual void SetUp() OVERRIDE { - memory_usage_thread_.Start(); - } + virtual void SetUp() OVERRIDE { memory_usage_thread_.Start(); } - virtual void TearDown() OVERRIDE { - memory_usage_thread_.Stop(); - } + virtual void TearDown() OVERRIDE { memory_usage_thread_.Stop(); } void UseMemoryHelper() { size_t size = 1024; - const scoped_ptr<DiscardableMemory> discardable(CreateLockedMemory(size)); - EXPECT_TRUE(IsRegistered(discardable.get())); - EXPECT_NE(static_cast<void*>(NULL), Memory(discardable.get())); - discardable->Unlock(); + TestAllocationImpl allocation; + RegisterAndLock(&allocation, size); + Unlock(&allocation); + Unregister(&allocation); } - void SignalHelper() { - thread_sync_.Signal(); - } + void SignalHelper() { thread_sync_.Signal(); } Thread memory_usage_thread_; WaitableEvent thread_sync_; @@ -407,4 +422,5 @@ TEST_F(ThreadedDiscardableMemoryManagerTest, UseMemoryOnThread) { thread_sync_.Wait(); } +} // namespace } // namespace base |