1 // Copyright 2013 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_provider.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/trace_event.h"
11 #include "base/synchronization/lock.h"
12 #include "base/sys_info.h"
13
14 namespace base {
15 namespace internal {
16
17 namespace {
18
19 // This is admittedly pretty magical. It's approximately enough memory for two
20 // 2560x1600 images.
21 static const size_t kDefaultDiscardableMemoryLimit = 32 * 1024 * 1024;
22 static const size_t kDefaultBytesToReclaimUnderModeratePressure =
23 kDefaultDiscardableMemoryLimit / 2;
24
25 } // namespace
26
DiscardableMemoryProvider()27 DiscardableMemoryProvider::DiscardableMemoryProvider()
28 : allocations_(AllocationMap::NO_AUTO_EVICT),
29 bytes_allocated_(0),
30 discardable_memory_limit_(kDefaultDiscardableMemoryLimit),
31 bytes_to_reclaim_under_moderate_pressure_(
32 kDefaultBytesToReclaimUnderModeratePressure),
33 memory_pressure_listener_(
34 base::Bind(&DiscardableMemoryProvider::NotifyMemoryPressure,
35 Unretained(this))) {
36 }
37
~DiscardableMemoryProvider()38 DiscardableMemoryProvider::~DiscardableMemoryProvider() {
39 DCHECK(allocations_.empty());
40 DCHECK_EQ(0u, bytes_allocated_);
41 }
42
NotifyMemoryPressure(MemoryPressureListener::MemoryPressureLevel pressure_level)43 void DiscardableMemoryProvider::NotifyMemoryPressure(
44 MemoryPressureListener::MemoryPressureLevel pressure_level) {
45 switch (pressure_level) {
46 case MemoryPressureListener::MEMORY_PRESSURE_MODERATE:
47 Purge();
48 return;
49 case MemoryPressureListener::MEMORY_PRESSURE_CRITICAL:
50 PurgeAll();
51 return;
52 }
53
54 NOTREACHED();
55 }
56
SetDiscardableMemoryLimit(size_t bytes)57 void DiscardableMemoryProvider::SetDiscardableMemoryLimit(size_t bytes) {
58 AutoLock lock(lock_);
59 discardable_memory_limit_ = bytes;
60 EnforcePolicyWithLockAcquired();
61 }
62
SetBytesToReclaimUnderModeratePressure(size_t bytes)63 void DiscardableMemoryProvider::SetBytesToReclaimUnderModeratePressure(
64 size_t bytes) {
65 AutoLock lock(lock_);
66 bytes_to_reclaim_under_moderate_pressure_ = bytes;
67 }
68
Register(const DiscardableMemory * discardable,size_t bytes)69 void DiscardableMemoryProvider::Register(
70 const DiscardableMemory* discardable, size_t bytes) {
71 AutoLock lock(lock_);
72 DCHECK(allocations_.Peek(discardable) == allocations_.end());
73 allocations_.Put(discardable, Allocation(bytes));
74 }
75
Unregister(const DiscardableMemory * discardable)76 void DiscardableMemoryProvider::Unregister(
77 const DiscardableMemory* discardable) {
78 AutoLock lock(lock_);
79 AllocationMap::iterator it = allocations_.Peek(discardable);
80 if (it == allocations_.end())
81 return;
82
83 if (it->second.memory) {
84 size_t bytes = it->second.bytes;
85 DCHECK_LE(bytes, bytes_allocated_);
86 bytes_allocated_ -= bytes;
87 free(it->second.memory);
88 }
89 allocations_.Erase(it);
90 }
91
Acquire(const DiscardableMemory * discardable,bool * purged)92 scoped_ptr<uint8, FreeDeleter> DiscardableMemoryProvider::Acquire(
93 const DiscardableMemory* discardable,
94 bool* purged) {
95 AutoLock lock(lock_);
96 // NB: |allocations_| is an MRU cache, and use of |Get| here updates that
97 // cache.
98 AllocationMap::iterator it = allocations_.Get(discardable);
99 CHECK(it != allocations_.end());
100
101 if (it->second.memory) {
102 scoped_ptr<uint8, FreeDeleter> memory(it->second.memory);
103 it->second.memory = NULL;
104 *purged = false;
105 return memory.Pass();
106 }
107
108 size_t bytes = it->second.bytes;
109 if (!bytes)
110 return scoped_ptr<uint8, FreeDeleter>();
111
112 if (discardable_memory_limit_) {
113 size_t limit = 0;
114 if (bytes < discardable_memory_limit_)
115 limit = discardable_memory_limit_ - bytes;
116
117 PurgeLRUWithLockAcquiredUntilUsageIsWithin(limit);
118 }
119
120 // Check for overflow.
121 if (std::numeric_limits<size_t>::max() - bytes < bytes_allocated_)
122 return scoped_ptr<uint8, FreeDeleter>();
123
124 scoped_ptr<uint8, FreeDeleter> memory(static_cast<uint8*>(malloc(bytes)));
125 if (!memory)
126 return scoped_ptr<uint8, FreeDeleter>();
127
128 bytes_allocated_ += bytes;
129 *purged = true;
130 return memory.Pass();
131 }
132
Release(const DiscardableMemory * discardable,scoped_ptr<uint8,FreeDeleter> memory)133 void DiscardableMemoryProvider::Release(
134 const DiscardableMemory* discardable,
135 scoped_ptr<uint8, FreeDeleter> memory) {
136 AutoLock lock(lock_);
137 // NB: |allocations_| is an MRU cache, and use of |Get| here updates that
138 // cache.
139 AllocationMap::iterator it = allocations_.Get(discardable);
140 CHECK(it != allocations_.end());
141
142 DCHECK(!it->second.memory);
143 it->second.memory = memory.release();
144
145 EnforcePolicyWithLockAcquired();
146 }
147
PurgeAll()148 void DiscardableMemoryProvider::PurgeAll() {
149 AutoLock lock(lock_);
150 PurgeLRUWithLockAcquiredUntilUsageIsWithin(0);
151 }
152
IsRegisteredForTest(const DiscardableMemory * discardable) const153 bool DiscardableMemoryProvider::IsRegisteredForTest(
154 const DiscardableMemory* discardable) const {
155 AutoLock lock(lock_);
156 AllocationMap::const_iterator it = allocations_.Peek(discardable);
157 return it != allocations_.end();
158 }
159
CanBePurgedForTest(const DiscardableMemory * discardable) const160 bool DiscardableMemoryProvider::CanBePurgedForTest(
161 const DiscardableMemory* discardable) const {
162 AutoLock lock(lock_);
163 AllocationMap::const_iterator it = allocations_.Peek(discardable);
164 return it != allocations_.end() && it->second.memory;
165 }
166
GetBytesAllocatedForTest() const167 size_t DiscardableMemoryProvider::GetBytesAllocatedForTest() const {
168 AutoLock lock(lock_);
169 return bytes_allocated_;
170 }
171
Purge()172 void DiscardableMemoryProvider::Purge() {
173 AutoLock lock(lock_);
174
175 if (bytes_to_reclaim_under_moderate_pressure_ == 0)
176 return;
177
178 size_t limit = 0;
179 if (bytes_to_reclaim_under_moderate_pressure_ < bytes_allocated_)
180 limit = bytes_allocated_ - bytes_to_reclaim_under_moderate_pressure_;
181
182 PurgeLRUWithLockAcquiredUntilUsageIsWithin(limit);
183 }
184
PurgeLRUWithLockAcquiredUntilUsageIsWithin(size_t limit)185 void DiscardableMemoryProvider::PurgeLRUWithLockAcquiredUntilUsageIsWithin(
186 size_t limit) {
187 TRACE_EVENT1(
188 "base",
189 "DiscardableMemoryProvider::PurgeLRUWithLockAcquiredUntilUsageIsWithin",
190 "limit", limit);
191
192 lock_.AssertAcquired();
193
194 for (AllocationMap::reverse_iterator it = allocations_.rbegin();
195 it != allocations_.rend();
196 ++it) {
197 if (bytes_allocated_ <= limit)
198 break;
199 if (!it->second.memory)
200 continue;
201
202 size_t bytes = it->second.bytes;
203 DCHECK_LE(bytes, bytes_allocated_);
204 bytes_allocated_ -= bytes;
205 free(it->second.memory);
206 it->second.memory = NULL;
207 }
208 }
209
EnforcePolicyWithLockAcquired()210 void DiscardableMemoryProvider::EnforcePolicyWithLockAcquired() {
211 PurgeLRUWithLockAcquiredUntilUsageIsWithin(discardable_memory_limit_);
212 }
213
214 } // namespace internal
215 } // namespace base
216