1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "base/memory/discardable_memory_manager.h"
6
7#include "base/bind.h"
8#include "base/containers/hash_tables.h"
9#include "base/containers/mru_cache.h"
10#include "base/debug/crash_logging.h"
11#include "base/debug/trace_event.h"
12#include "base/strings/string_number_conversions.h"
13#include "base/synchronization/lock.h"
14
15namespace base {
16namespace internal {
17
18DiscardableMemoryManager::DiscardableMemoryManager(
19    size_t memory_limit,
20    size_t soft_memory_limit,
21    TimeDelta hard_memory_limit_expiration_time)
22    : allocations_(AllocationMap::NO_AUTO_EVICT),
23      bytes_allocated_(0u),
24      memory_limit_(memory_limit),
25      soft_memory_limit_(soft_memory_limit),
26      hard_memory_limit_expiration_time_(hard_memory_limit_expiration_time) {
27  BytesAllocatedChanged(bytes_allocated_);
28}
29
30DiscardableMemoryManager::~DiscardableMemoryManager() {
31  DCHECK(allocations_.empty());
32  DCHECK_EQ(0u, bytes_allocated_);
33}
34
35void DiscardableMemoryManager::SetMemoryLimit(size_t bytes) {
36  AutoLock lock(lock_);
37  memory_limit_ = bytes;
38  PurgeIfNotUsedSinceTimestampUntilUsageIsWithinLimitWithLockAcquired(
39      Now(), memory_limit_);
40}
41
42void DiscardableMemoryManager::SetSoftMemoryLimit(size_t bytes) {
43  AutoLock lock(lock_);
44  soft_memory_limit_ = bytes;
45}
46
47void DiscardableMemoryManager::SetHardMemoryLimitExpirationTime(
48    TimeDelta hard_memory_limit_expiration_time) {
49  AutoLock lock(lock_);
50  hard_memory_limit_expiration_time_ = hard_memory_limit_expiration_time;
51}
52
53bool DiscardableMemoryManager::ReduceMemoryUsage() {
54  return PurgeIfNotUsedSinceHardLimitCutoffUntilWithinSoftMemoryLimit();
55}
56
57void DiscardableMemoryManager::ReduceMemoryUsageUntilWithinLimit(size_t bytes) {
58  AutoLock lock(lock_);
59  PurgeIfNotUsedSinceTimestampUntilUsageIsWithinLimitWithLockAcquired(Now(),
60                                                                      bytes);
61}
62
63void DiscardableMemoryManager::Register(Allocation* allocation, size_t bytes) {
64  AutoLock lock(lock_);
65  DCHECK(allocations_.Peek(allocation) == allocations_.end());
66  allocations_.Put(allocation, AllocationInfo(bytes));
67}
68
69void DiscardableMemoryManager::Unregister(Allocation* allocation) {
70  AutoLock lock(lock_);
71  AllocationMap::iterator it = allocations_.Peek(allocation);
72  DCHECK(it != allocations_.end());
73  const AllocationInfo& info = it->second;
74
75  if (info.purgable) {
76    size_t bytes_purgable = info.bytes;
77    DCHECK_LE(bytes_purgable, bytes_allocated_);
78    bytes_allocated_ -= bytes_purgable;
79    BytesAllocatedChanged(bytes_allocated_);
80  }
81  allocations_.Erase(it);
82}
83
84bool DiscardableMemoryManager::AcquireLock(Allocation* allocation,
85                                           bool* purged) {
86  AutoLock lock(lock_);
87  // Note: |allocations_| is an MRU cache, and use of |Get| here updates that
88  // cache.
89  AllocationMap::iterator it = allocations_.Get(allocation);
90  DCHECK(it != allocations_.end());
91  AllocationInfo* info = &it->second;
92
93  if (!info->bytes)
94    return false;
95
96  TimeTicks now = Now();
97  size_t bytes_required = info->purgable ? 0u : info->bytes;
98
99  if (memory_limit_) {
100    size_t limit = 0;
101    if (bytes_required < memory_limit_)
102      limit = memory_limit_ - bytes_required;
103
104    PurgeIfNotUsedSinceTimestampUntilUsageIsWithinLimitWithLockAcquired(now,
105                                                                        limit);
106  }
107
108  // Check for overflow.
109  if (std::numeric_limits<size_t>::max() - bytes_required < bytes_allocated_)
110    return false;
111
112  *purged = !allocation->AllocateAndAcquireLock();
113  info->purgable = false;
114  info->last_usage = now;
115  if (bytes_required) {
116    bytes_allocated_ += bytes_required;
117    BytesAllocatedChanged(bytes_allocated_);
118  }
119  return true;
120}
121
122void DiscardableMemoryManager::ReleaseLock(Allocation* allocation) {
123  AutoLock lock(lock_);
124  // Note: |allocations_| is an MRU cache, and use of |Get| here updates that
125  // cache.
126  AllocationMap::iterator it = allocations_.Get(allocation);
127  DCHECK(it != allocations_.end());
128  AllocationInfo* info = &it->second;
129
130  TimeTicks now = Now();
131  allocation->ReleaseLock();
132  info->purgable = true;
133  info->last_usage = now;
134
135  PurgeIfNotUsedSinceTimestampUntilUsageIsWithinLimitWithLockAcquired(
136      now, memory_limit_);
137}
138
139void DiscardableMemoryManager::PurgeAll() {
140  AutoLock lock(lock_);
141  PurgeIfNotUsedSinceTimestampUntilUsageIsWithinLimitWithLockAcquired(Now(), 0);
142}
143
144bool DiscardableMemoryManager::IsRegisteredForTest(
145    Allocation* allocation) const {
146  AutoLock lock(lock_);
147  AllocationMap::const_iterator it = allocations_.Peek(allocation);
148  return it != allocations_.end();
149}
150
151bool DiscardableMemoryManager::CanBePurgedForTest(
152    Allocation* allocation) const {
153  AutoLock lock(lock_);
154  AllocationMap::const_iterator it = allocations_.Peek(allocation);
155  return it != allocations_.end() && it->second.purgable;
156}
157
158size_t DiscardableMemoryManager::GetBytesAllocatedForTest() const {
159  AutoLock lock(lock_);
160  return bytes_allocated_;
161}
162
163bool DiscardableMemoryManager::
164    PurgeIfNotUsedSinceHardLimitCutoffUntilWithinSoftMemoryLimit() {
165  AutoLock lock(lock_);
166
167  PurgeIfNotUsedSinceTimestampUntilUsageIsWithinLimitWithLockAcquired(
168      Now() - hard_memory_limit_expiration_time_, soft_memory_limit_);
169
170  return bytes_allocated_ <= soft_memory_limit_;
171}
172
173void DiscardableMemoryManager::
174    PurgeIfNotUsedSinceTimestampUntilUsageIsWithinLimitWithLockAcquired(
175        TimeTicks timestamp,
176        size_t limit) {
177  lock_.AssertAcquired();
178
179  size_t bytes_allocated_before_purging = bytes_allocated_;
180  for (AllocationMap::reverse_iterator it = allocations_.rbegin();
181       it != allocations_.rend();
182       ++it) {
183    Allocation* allocation = it->first;
184    AllocationInfo* info = &it->second;
185
186    if (bytes_allocated_ <= limit)
187      break;
188
189    bool purgable = info->purgable && info->last_usage <= timestamp;
190    if (!purgable)
191      continue;
192
193    size_t bytes_purgable = info->bytes;
194    DCHECK_LE(bytes_purgable, bytes_allocated_);
195    bytes_allocated_ -= bytes_purgable;
196    info->purgable = false;
197    allocation->Purge();
198  }
199
200  if (bytes_allocated_ != bytes_allocated_before_purging)
201    BytesAllocatedChanged(bytes_allocated_);
202}
203
204void DiscardableMemoryManager::BytesAllocatedChanged(
205    size_t new_bytes_allocated) const {
206  TRACE_COUNTER_ID1(
207      "base", "DiscardableMemoryUsage", this, new_bytes_allocated);
208
209  static const char kDiscardableMemoryUsageKey[] = "dm-usage";
210  base::debug::SetCrashKeyValue(kDiscardableMemoryUsageKey,
211                                Uint64ToString(new_bytes_allocated));
212}
213
214TimeTicks DiscardableMemoryManager::Now() const {
215  return TimeTicks::Now();
216}
217
218}  // namespace internal
219}  // namespace base
220