Chromium Code Reviews| Index: base/memory/discardable_memory_provider.cc |
| diff --git a/base/memory/discardable_memory_provider.cc b/base/memory/discardable_memory_provider.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..a4ab6967dbdf0ed3c3dcc7008b09f0e10ac65c59 |
| --- /dev/null |
| +++ b/base/memory/discardable_memory_provider.cc |
| @@ -0,0 +1,260 @@ |
| +// Copyright 2013 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/discardable_memory_provider.h" |
| + |
| +#include "base/bind.h" |
| +#include "base/containers/hash_tables.h" |
| +#include "base/containers/mru_cache.h" |
| +#include "base/debug/trace_event.h" |
| +#include "base/memory/discardable_memory.h" |
| +#include "base/synchronization/lock.h" |
| +#include "base/sys_info.h" |
| + |
| +namespace base { |
| +namespace internal { |
| + |
| +namespace { |
| + |
| +// If this is given a valid value via SetInstanceForTest, this pointer will be |
| +// returned by GetInstance rather than the usual singleton. |
| +static DiscardableMemoryProvider* s_provider_for_test = NULL; |
| + |
| +// This is admittedly pretty magical. It's approximately enough memory for two |
| +// 2560x1600 images. |
| +static const size_t kDefaultDiscardableMemoryLimit = 32 * 1024 * 1024; |
| +static const size_t kDefaultBytesToReclaimUnderModeratePressure = |
| + kDefaultDiscardableMemoryLimit / 2; |
| + |
| +} // namespace |
| + |
| +DiscardableMemoryProvider::DiscardableMemoryProvider() |
| + : allocations_(AllocationMap::NO_AUTO_EVICT), |
| + bytes_allocated_(0), |
| + discardable_memory_limit_(kDefaultDiscardableMemoryLimit), |
| + bytes_to_reclaim_under_moderate_pressure_( |
| + kDefaultBytesToReclaimUnderModeratePressure), |
| + enforcing_policy_(false), |
| + memory_pressure_listener_( |
| + base::Bind(&DiscardableMemoryProvider::NotifyMemoryPressure)) { |
| +} |
| + |
| +DiscardableMemoryProvider::~DiscardableMemoryProvider() { |
| + DCHECK_EQ(0u, allocations_.size()); |
| + DCHECK_EQ(0u, bytes_allocated_); |
| +} |
| + |
| +// static |
| +DiscardableMemoryProvider* DiscardableMemoryProvider::GetInstance() { |
| + if (s_provider_for_test) |
| + return s_provider_for_test; |
| + return Singleton<DiscardableMemoryProvider>::get(); |
|
willchan no longer on Chromium
2013/10/17 02:06:13
WDYT about using a LeakyLazyInstance instead? Is t
reveman
2013/10/19 21:17:04
Not really. I guess we need to use a LeakyLazyInst
willchan no longer on Chromium
2013/10/21 18:30:27
Not deleting on shutdown is preferable. It avoids
reveman
2013/10/22 00:11:41
Thanks for the explanation. That makes sense. It's
|
| +} |
| + |
| +// static |
| +void DiscardableMemoryProvider::SetInstanceForTest( |
| + DiscardableMemoryProvider* provider) { |
| + s_provider_for_test = provider; |
| +} |
| + |
| +// static |
| +void DiscardableMemoryProvider::NotifyMemoryPressure( |
| + MemoryPressureListener::MemoryPressureLevel pressure_level) { |
| + switch (pressure_level) { |
| + case MemoryPressureListener::MEMORY_PRESSURE_MODERATE: |
| + DiscardableMemoryProvider::GetInstance()->Purge(); |
| + return; |
| + case MemoryPressureListener::MEMORY_PRESSURE_CRITICAL: |
| + DiscardableMemoryProvider::GetInstance()->PurgeAll(); |
| + return; |
| + } |
| + |
| + NOTREACHED(); |
| +} |
| + |
| +void DiscardableMemoryProvider::SetDiscardableMemoryLimit(size_t bytes) { |
| + { |
| + AutoLock lock(bytes_allocated_lock_); |
| + discardable_memory_limit_ = bytes; |
| + } |
| + EnforcePolicy(); |
| +} |
| + |
| +void DiscardableMemoryProvider::SetBytesToReclaimUnderModeratePressure( |
| + size_t bytes) { |
| + { |
| + AutoLock lock(bytes_allocated_lock_); |
| + bytes_to_reclaim_under_moderate_pressure_ = bytes; |
| + } |
| + EnforcePolicy(); |
| +} |
| + |
| +void DiscardableMemoryProvider::Register( |
| + const DiscardableMemory* discardable, size_t bytes) { |
| + AutoLock lock(allocations_lock_); |
| + DCHECK(allocations_.Peek(discardable) == allocations_.end()); |
| + allocations_.Put(discardable, Allocation(bytes)); |
| +} |
| + |
| +void DiscardableMemoryProvider::Unregister( |
| + const DiscardableMemory* discardable) { |
| + AutoLock lock(allocations_lock_); |
|
willchan no longer on Chromium
2013/10/17 02:06:13
You acquire this lock here first. Later on, you ma
reveman
2013/10/19 21:17:04
Yes, the use of multiple locks here is broken. Red
|
| + AllocationMap::iterator it = allocations_.Peek(discardable); |
| + if (it == allocations_.end()) |
| + return; |
| + |
| + if (it->second.memory) { |
| + AutoLock bytes_lock(bytes_allocated_lock_); |
| + size_t bytes = it->second.bytes; |
| + DCHECK_LE(bytes, bytes_allocated_); |
| + bytes_allocated_ -= bytes; |
| + free(it->second.memory); |
| + } |
| + allocations_.Erase(it); |
| +} |
| + |
| +scoped_ptr<uint8, FreeDeleter> DiscardableMemoryProvider::Acquire( |
| + const DiscardableMemory* discardable, |
| + bool* purged) { |
| + AutoLock lock(allocations_lock_); |
| + // NB: |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(); |
| + } |
| + |
| + size_t bytes = it->second.bytes; |
| + if (!bytes) |
| + return scoped_ptr<uint8, FreeDeleter>(); |
| + |
| + AutoLock bytes_lock(bytes_allocated_lock_); |
| + |
| + if (discardable_memory_limit_) { |
| + if (bytes > discardable_memory_limit_) |
| + return scoped_ptr<uint8, FreeDeleter>(); |
| + |
| + size_t limit = discardable_memory_limit_ - bytes; |
| + PurgeLRU(limit); |
| + |
| + if (bytes_allocated_ > limit) |
| + return scoped_ptr<uint8, FreeDeleter>(); |
| + } |
| + |
| + bytes_allocated_ += bytes; |
| + *purged = true; |
| + return scoped_ptr<uint8, FreeDeleter>(static_cast<uint8*>(malloc(bytes))); |
| +} |
| + |
| +void DiscardableMemoryProvider::Release( |
| + const DiscardableMemory* discardable, |
| + scoped_ptr<uint8, FreeDeleter> memory) { |
| + { |
| + AutoLock lock(allocations_lock_); |
| + // NB: |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(); |
| + } |
| + |
| + EnforcePolicy(); |
| +} |
| + |
| +bool DiscardableMemoryProvider::IsRegisteredForTest( |
| + const DiscardableMemory* discardable) { |
| + AutoLock lock(allocations_lock_); |
| + AllocationMap::iterator it = allocations_.Peek(discardable); |
| + return it != allocations_.end(); |
| +} |
| + |
| +bool DiscardableMemoryProvider::CanBePurgedForTest( |
| + const DiscardableMemory* discardable) { |
| + AutoLock lock(allocations_lock_); |
| + AllocationMap::iterator it = allocations_.Peek(discardable); |
| + return it != allocations_.end() && it->second.memory; |
| +} |
| + |
| +size_t DiscardableMemoryProvider::GetBytesAllocatedForTest() { |
| + AutoLock lock(bytes_allocated_lock_); |
| + return bytes_allocated_; |
| +} |
| + |
| +void DiscardableMemoryProvider::PurgeLRU(size_t limit) { |
| + TRACE_EVENT1("base", "DiscardableMemoryProvider::PurgeLRU", "limit", limit); |
| + |
| + allocations_lock_.AssertAcquired(); |
| + bytes_allocated_lock_.AssertAcquired(); |
| + |
| + for (AllocationMap::reverse_iterator it = allocations_.rbegin(); |
| + it != allocations_.rend(); |
| + ++it) { |
| + if (bytes_allocated_ <= limit) |
| + break; |
| + if (!it->second.memory) |
| + continue; |
| + |
| + size_t bytes = it->second.bytes; |
| + DCHECK_LE(bytes, bytes_allocated_); |
| + bytes_allocated_ -= bytes; |
| + free(it->second.memory); |
| + it->second.memory = NULL; |
| + } |
| +} |
| + |
| +void DiscardableMemoryProvider::Purge() { |
| + size_t limit = 0; |
| + AutoLock bytes_lock(bytes_allocated_lock_); |
| + if (bytes_to_reclaim_under_moderate_pressure_ == 0) |
| + return; |
| + |
| + if (bytes_to_reclaim_under_moderate_pressure_ < discardable_memory_limit_) |
| + limit = bytes_allocated_ - bytes_to_reclaim_under_moderate_pressure_; |
| + |
| + AutoLock lock(allocations_lock_); |
| + PurgeLRU(limit); |
| +} |
| + |
| +void DiscardableMemoryProvider::PurgeAll() { |
| + AutoLock bytes_lock(bytes_allocated_lock_); |
| + AutoLock lock(allocations_lock_); |
| + PurgeLRU(0); |
| +} |
| + |
| +void DiscardableMemoryProvider::EnforcePolicy() { |
| + { |
| + AutoLock lock(bytes_allocated_lock_); |
| + if (enforcing_policy_) |
| + return; |
| + } |
| + |
| + bool exceeded_bound = false; |
| + { |
| + AutoLock lock(bytes_allocated_lock_); |
| + enforcing_policy_ = true; |
| + if (discardable_memory_limit_ == 0) { |
| + enforcing_policy_ = false; |
| + return; |
| + } |
| + exceeded_bound = bytes_allocated_ > discardable_memory_limit_; |
| + } |
| + |
| + if (exceeded_bound) |
| + Purge(); |
| + |
| + { |
| + AutoLock lock(bytes_allocated_lock_); |
| + enforcing_policy_ = false; |
| + } |
| +} |
| + |
| +} // namespace internal |
| +} // namespace base |