// 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/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 kDefaultBytesToReclaimUnderModeratePressure = (3 * kDefaultDiscardableMemoryLimit) / 4; } // namespace DiscardableMemoryProvider::DiscardableMemoryProvider() : allocations_(AllocationMap::NO_AUTO_EVICT), bytes_allocated_(0), discardable_memory_limit_(kDefaultDiscardableMemoryLimit), bytes_to_reclaim_under_moderate_pressure_( kDefaultBytesToReclaimUnderModeratePressure) { } DiscardableMemoryProvider::~DiscardableMemoryProvider() { DCHECK(allocations_.empty()); DCHECK_EQ(0u, bytes_allocated_); } void DiscardableMemoryProvider::RegisterMemoryPressureListener() { AutoLock lock(lock_); DCHECK(base::MessageLoop::current()); DCHECK(!memory_pressure_listener_); memory_pressure_listener_.reset( new MemoryPressureListener( base::Bind(&DiscardableMemoryProvider::OnMemoryPressure, Unretained(this)))); } void DiscardableMemoryProvider::UnregisterMemoryPressureListener() { AutoLock lock(lock_); DCHECK(memory_pressure_listener_); memory_pressure_listener_.reset(); } void DiscardableMemoryProvider::SetDiscardableMemoryLimit(size_t bytes) { AutoLock lock(lock_); discardable_memory_limit_ = bytes; EnforcePolicyWithLockAcquired(); } void DiscardableMemoryProvider::SetBytesToReclaimUnderModeratePressure( size_t bytes) { AutoLock lock(lock_); bytes_to_reclaim_under_moderate_pressure_ = bytes; } void DiscardableMemoryProvider::Register( const DiscardableMemory* discardable, 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)); } void DiscardableMemoryProvider::Unregister( const DiscardableMemory* discardable) { 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; free(it->second.memory); } allocations_.Erase(it); } scoped_ptr DiscardableMemoryProvider::Acquire( const DiscardableMemory* discardable, bool* purged) { AutoLock lock(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 memory(it->second.memory); it->second.memory = NULL; *purged = false; return memory.Pass(); } size_t bytes = it->second.bytes; if (!bytes) return scoped_ptr(); if (discardable_memory_limit_) { size_t limit = 0; if (bytes < discardable_memory_limit_) limit = discardable_memory_limit_ - bytes; PurgeLRUWithLockAcquiredUntilUsageIsWithin(limit); } // Check for overflow. if (std::numeric_limits::max() - bytes < bytes_allocated_) return scoped_ptr(); scoped_ptr memory(static_cast(malloc(bytes))); if (!memory) return scoped_ptr(); bytes_allocated_ += bytes; *purged = true; return memory.Pass(); } void DiscardableMemoryProvider::Release( const DiscardableMemory* discardable, scoped_ptr memory) { AutoLock lock(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(); EnforcePolicyWithLockAcquired(); } void DiscardableMemoryProvider::PurgeAll() { AutoLock lock(lock_); PurgeLRUWithLockAcquiredUntilUsageIsWithin(0); } bool DiscardableMemoryProvider::IsRegisteredForTest( const DiscardableMemory* discardable) const { AutoLock lock(lock_); AllocationMap::const_iterator it = allocations_.Peek(discardable); return it != allocations_.end(); } bool DiscardableMemoryProvider::CanBePurgedForTest( const DiscardableMemory* discardable) const { AutoLock lock(lock_); AllocationMap::const_iterator it = allocations_.Peek(discardable); return it != allocations_.end() && it->second.memory; } size_t DiscardableMemoryProvider::GetBytesAllocatedForTest() const { AutoLock lock(lock_); return bytes_allocated_; } void DiscardableMemoryProvider::OnMemoryPressure( MemoryPressureListener::MemoryPressureLevel pressure_level) { switch (pressure_level) { case MemoryPressureListener::MEMORY_PRESSURE_MODERATE: Purge(); return; case MemoryPressureListener::MEMORY_PRESSURE_CRITICAL: PurgeAll(); return; } NOTREACHED(); } void DiscardableMemoryProvider::Purge() { AutoLock lock(lock_); if (bytes_to_reclaim_under_moderate_pressure_ == 0) return; size_t limit = 0; if (bytes_to_reclaim_under_moderate_pressure_ < bytes_allocated_) limit = bytes_allocated_ - bytes_to_reclaim_under_moderate_pressure_; PurgeLRUWithLockAcquiredUntilUsageIsWithin(limit); } void DiscardableMemoryProvider::PurgeLRUWithLockAcquiredUntilUsageIsWithin( size_t limit) { TRACE_EVENT1( "base", "DiscardableMemoryProvider::PurgeLRUWithLockAcquiredUntilUsageIsWithin", "limit", limit); 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::EnforcePolicyWithLockAcquired() { PurgeLRUWithLockAcquiredUntilUsageIsWithin(discardable_memory_limit_); } } // namespace internal } // namespace base