|  | // Copyright 2014 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_manager.h" | 
|  |  | 
|  | #include "base/bind.h" | 
|  | #include "base/containers/adapters.h" | 
|  | #include "base/containers/hash_tables.h" | 
|  | #include "base/containers/mru_cache.h" | 
|  | #include "base/debug/crash_logging.h" | 
|  | #include "base/strings/string_number_conversions.h" | 
|  | #include "base/synchronization/lock.h" | 
|  | #include "base/trace_event/trace_event.h" | 
|  |  | 
|  | namespace base { | 
|  | namespace internal { | 
|  |  | 
|  | DiscardableMemoryManager::DiscardableMemoryManager( | 
|  | size_t memory_limit, | 
|  | size_t soft_memory_limit, | 
|  | TimeDelta hard_memory_limit_expiration_time) | 
|  | : allocations_(AllocationMap::NO_AUTO_EVICT), | 
|  | bytes_allocated_(0u), | 
|  | memory_limit_(memory_limit), | 
|  | soft_memory_limit_(soft_memory_limit), | 
|  | hard_memory_limit_expiration_time_(hard_memory_limit_expiration_time) { | 
|  | BytesAllocatedChanged(bytes_allocated_); | 
|  | } | 
|  |  | 
|  | DiscardableMemoryManager::~DiscardableMemoryManager() { | 
|  | DCHECK(allocations_.empty()); | 
|  | DCHECK_EQ(0u, bytes_allocated_); | 
|  | } | 
|  |  | 
|  | void DiscardableMemoryManager::SetMemoryLimit(size_t bytes) { | 
|  | AutoLock lock(lock_); | 
|  | memory_limit_ = bytes; | 
|  | PurgeIfNotUsedSinceTimestampUntilUsageIsWithinLimitWithLockAcquired( | 
|  | Now(), memory_limit_); | 
|  | } | 
|  |  | 
|  | void DiscardableMemoryManager::SetSoftMemoryLimit(size_t bytes) { | 
|  | AutoLock lock(lock_); | 
|  | soft_memory_limit_ = bytes; | 
|  | } | 
|  |  | 
|  | void DiscardableMemoryManager::SetHardMemoryLimitExpirationTime( | 
|  | TimeDelta hard_memory_limit_expiration_time) { | 
|  | AutoLock lock(lock_); | 
|  | hard_memory_limit_expiration_time_ = hard_memory_limit_expiration_time; | 
|  | } | 
|  |  | 
|  | bool DiscardableMemoryManager::ReduceMemoryUsage() { | 
|  | return PurgeIfNotUsedSinceHardLimitCutoffUntilWithinSoftMemoryLimit(); | 
|  | } | 
|  |  | 
|  | void DiscardableMemoryManager::ReduceMemoryUsageUntilWithinLimit(size_t bytes) { | 
|  | AutoLock lock(lock_); | 
|  | PurgeIfNotUsedSinceTimestampUntilUsageIsWithinLimitWithLockAcquired(Now(), | 
|  | bytes); | 
|  | } | 
|  |  | 
|  | void DiscardableMemoryManager::Register(Allocation* allocation, size_t bytes) { | 
|  | AutoLock lock(lock_); | 
|  | DCHECK(allocations_.Peek(allocation) == allocations_.end()); | 
|  | allocations_.Put(allocation, AllocationInfo(bytes)); | 
|  | } | 
|  |  | 
|  | void DiscardableMemoryManager::Unregister(Allocation* allocation) { | 
|  | AutoLock lock(lock_); | 
|  | 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(bytes_allocated_); | 
|  | } | 
|  | allocations_.Erase(it); | 
|  | } | 
|  |  | 
|  | bool DiscardableMemoryManager::AcquireLock(Allocation* allocation, | 
|  | bool* purged) { | 
|  | AutoLock lock(lock_); | 
|  | // Note: |allocations_| is an MRU cache, and use of |Get| here updates that | 
|  | // cache. | 
|  | AllocationMap::iterator it = allocations_.Get(allocation); | 
|  | DCHECK(it != allocations_.end()); | 
|  | AllocationInfo* info = &it->second; | 
|  |  | 
|  | if (!info->bytes) | 
|  | return false; | 
|  |  | 
|  | TimeTicks now = Now(); | 
|  | size_t bytes_required = info->purgable ? 0u : info->bytes; | 
|  |  | 
|  | if (memory_limit_) { | 
|  | size_t limit = 0; | 
|  | if (bytes_required < memory_limit_) | 
|  | limit = memory_limit_ - bytes_required; | 
|  |  | 
|  | PurgeIfNotUsedSinceTimestampUntilUsageIsWithinLimitWithLockAcquired(now, | 
|  | limit); | 
|  | } | 
|  |  | 
|  | // Check for overflow. | 
|  | if (std::numeric_limits<size_t>::max() - bytes_required < bytes_allocated_) | 
|  | return false; | 
|  |  | 
|  | *purged = !allocation->AllocateAndAcquireLock(); | 
|  | info->purgable = false; | 
|  | info->last_usage = now; | 
|  | if (bytes_required) { | 
|  | bytes_allocated_ += bytes_required; | 
|  | BytesAllocatedChanged(bytes_allocated_); | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void DiscardableMemoryManager::ReleaseLock(Allocation* allocation) { | 
|  | AutoLock lock(lock_); | 
|  | // Note: |allocations_| is an MRU cache, and use of |Get| here updates that | 
|  | // cache. | 
|  | AllocationMap::iterator it = allocations_.Get(allocation); | 
|  | DCHECK(it != allocations_.end()); | 
|  | AllocationInfo* info = &it->second; | 
|  |  | 
|  | TimeTicks now = Now(); | 
|  | allocation->ReleaseLock(); | 
|  | info->purgable = true; | 
|  | info->last_usage = now; | 
|  |  | 
|  | PurgeIfNotUsedSinceTimestampUntilUsageIsWithinLimitWithLockAcquired( | 
|  | now, memory_limit_); | 
|  | } | 
|  |  | 
|  | void DiscardableMemoryManager::PurgeAll() { | 
|  | AutoLock lock(lock_); | 
|  | PurgeIfNotUsedSinceTimestampUntilUsageIsWithinLimitWithLockAcquired(Now(), 0); | 
|  | } | 
|  |  | 
|  | bool DiscardableMemoryManager::IsRegisteredForTest( | 
|  | Allocation* allocation) const { | 
|  | AutoLock lock(lock_); | 
|  | AllocationMap::const_iterator it = allocations_.Peek(allocation); | 
|  | return it != allocations_.end(); | 
|  | } | 
|  |  | 
|  | bool DiscardableMemoryManager::CanBePurgedForTest( | 
|  | Allocation* allocation) const { | 
|  | AutoLock lock(lock_); | 
|  | AllocationMap::const_iterator it = allocations_.Peek(allocation); | 
|  | return it != allocations_.end() && it->second.purgable; | 
|  | } | 
|  |  | 
|  | size_t DiscardableMemoryManager::GetBytesAllocatedForTest() const { | 
|  | AutoLock lock(lock_); | 
|  | return bytes_allocated_; | 
|  | } | 
|  |  | 
|  | bool DiscardableMemoryManager:: | 
|  | PurgeIfNotUsedSinceHardLimitCutoffUntilWithinSoftMemoryLimit() { | 
|  | AutoLock lock(lock_); | 
|  |  | 
|  | PurgeIfNotUsedSinceTimestampUntilUsageIsWithinLimitWithLockAcquired( | 
|  | Now() - hard_memory_limit_expiration_time_, soft_memory_limit_); | 
|  |  | 
|  | return bytes_allocated_ <= soft_memory_limit_; | 
|  | } | 
|  |  | 
|  | void DiscardableMemoryManager:: | 
|  | PurgeIfNotUsedSinceTimestampUntilUsageIsWithinLimitWithLockAcquired( | 
|  | TimeTicks timestamp, | 
|  | size_t limit) { | 
|  | lock_.AssertAcquired(); | 
|  |  | 
|  | size_t bytes_allocated_before_purging = bytes_allocated_; | 
|  | for (auto& entry : base::Reversed(allocations_)) { | 
|  | Allocation* allocation = entry.first; | 
|  | AllocationInfo* info = &entry.second; | 
|  |  | 
|  | if (bytes_allocated_ <= limit) | 
|  | break; | 
|  |  | 
|  | bool purgable = info->purgable && info->last_usage <= timestamp; | 
|  | if (!purgable) | 
|  | continue; | 
|  |  | 
|  | 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) | 
|  | BytesAllocatedChanged(bytes_allocated_); | 
|  | } | 
|  |  | 
|  | void DiscardableMemoryManager::BytesAllocatedChanged( | 
|  | size_t new_bytes_allocated) const { | 
|  | TRACE_COUNTER_ID1( | 
|  | "base", "DiscardableMemoryUsage", this, new_bytes_allocated); | 
|  |  | 
|  | static const char kDiscardableMemoryUsageKey[] = "dm-usage"; | 
|  | base::debug::SetCrashKeyValue(kDiscardableMemoryUsageKey, | 
|  | Uint64ToString(new_bytes_allocated)); | 
|  | } | 
|  |  | 
|  | TimeTicks DiscardableMemoryManager::Now() const { | 
|  | return TimeTicks::Now(); | 
|  | } | 
|  |  | 
|  | }  // namespace internal | 
|  | }  // namespace base |