[Android WebView] Fix webview perf bot switchover to use org.chromium.webview_shell...
[chromium-blink-merge.git] / content / common / host_discardable_shared_memory_manager.cc
bloba209cc0cc56f14e6f16ee96729e1649ec237179d
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.
5 #include "content/common/host_discardable_shared_memory_manager.h"
7 #include <algorithm>
9 #include "base/atomic_sequence_num.h"
10 #include "base/bind.h"
11 #include "base/callback.h"
12 #include "base/debug/crash_logging.h"
13 #include "base/lazy_instance.h"
14 #include "base/memory/discardable_memory.h"
15 #include "base/numerics/safe_math.h"
16 #include "base/strings/string_number_conversions.h"
17 #include "base/sys_info.h"
18 #include "base/thread_task_runner_handle.h"
19 #include "base/trace_event/trace_event.h"
21 namespace content {
22 namespace {
24 class DiscardableMemoryImpl : public base::DiscardableMemory {
25 public:
26 DiscardableMemoryImpl(scoped_ptr<base::DiscardableSharedMemory> shared_memory,
27 const base::Closure& deleted_callback)
28 : shared_memory_(shared_memory.Pass()),
29 deleted_callback_(deleted_callback),
30 is_locked_(true) {}
32 ~DiscardableMemoryImpl() override {
33 if (is_locked_)
34 shared_memory_->Unlock(0, 0);
36 deleted_callback_.Run();
39 // Overridden from base::DiscardableMemory:
40 bool Lock() override {
41 DCHECK(!is_locked_);
43 if (shared_memory_->Lock(0, 0) != base::DiscardableSharedMemory::SUCCESS)
44 return false;
46 is_locked_ = true;
47 return true;
49 void Unlock() override {
50 DCHECK(is_locked_);
52 shared_memory_->Unlock(0, 0);
53 is_locked_ = false;
55 void* data() const override {
56 DCHECK(is_locked_);
57 return shared_memory_->memory();
60 private:
61 scoped_ptr<base::DiscardableSharedMemory> shared_memory_;
62 const base::Closure deleted_callback_;
63 bool is_locked_;
65 DISALLOW_COPY_AND_ASSIGN(DiscardableMemoryImpl);
68 base::LazyInstance<HostDiscardableSharedMemoryManager>
69 g_discardable_shared_memory_manager = LAZY_INSTANCE_INITIALIZER;
71 #if defined(OS_ANDROID)
72 // Limits the number of FDs used to 32, assuming a 4MB allocation size.
73 const int64_t kMaxDefaultMemoryLimit = 128 * 1024 * 1024;
74 #else
75 const int64_t kMaxDefaultMemoryLimit = 512 * 1024 * 1024;
76 #endif
78 const int kEnforceMemoryPolicyDelayMs = 1000;
80 // Global atomic to generate unique discardable shared memory IDs.
81 base::StaticAtomicSequenceNumber g_next_discardable_shared_memory_id;
83 } // namespace
85 HostDiscardableSharedMemoryManager::MemorySegment::MemorySegment(
86 scoped_ptr<base::DiscardableSharedMemory> memory)
87 : memory_(memory.Pass()) {
90 HostDiscardableSharedMemoryManager::MemorySegment::~MemorySegment() {
93 HostDiscardableSharedMemoryManager::HostDiscardableSharedMemoryManager()
94 : memory_limit_(
95 // Allow 25% of physical memory to be used for discardable memory.
96 std::min(base::SysInfo::AmountOfPhysicalMemory() / 4,
97 base::SysInfo::IsLowEndDevice()
99 // Use 1/8th of discardable memory on low-end devices.
100 kMaxDefaultMemoryLimit / 8
101 : kMaxDefaultMemoryLimit)),
102 bytes_allocated_(0),
103 memory_pressure_listener_(new base::MemoryPressureListener(
104 base::Bind(&HostDiscardableSharedMemoryManager::OnMemoryPressure,
105 base::Unretained(this)))),
106 enforce_memory_policy_pending_(false),
107 weak_ptr_factory_(this) {
108 DCHECK_NE(memory_limit_, 0u);
111 HostDiscardableSharedMemoryManager::~HostDiscardableSharedMemoryManager() {
114 HostDiscardableSharedMemoryManager*
115 HostDiscardableSharedMemoryManager::current() {
116 return g_discardable_shared_memory_manager.Pointer();
119 scoped_ptr<base::DiscardableMemory>
120 HostDiscardableSharedMemoryManager::AllocateLockedDiscardableMemory(
121 size_t size) {
122 DiscardableSharedMemoryId new_id =
123 g_next_discardable_shared_memory_id.GetNext();
124 base::ProcessHandle current_process_handle = base::GetCurrentProcessHandle();
126 // Note: Use DiscardableSharedMemoryHeap for in-process allocation
127 // of discardable memory if the cost of each allocation is too high.
128 base::SharedMemoryHandle handle;
129 AllocateLockedDiscardableSharedMemory(current_process_handle, size, new_id,
130 &handle);
131 CHECK(base::SharedMemory::IsHandleValid(handle));
132 scoped_ptr<base::DiscardableSharedMemory> memory(
133 new base::DiscardableSharedMemory(handle));
134 CHECK(memory->Map(size));
135 // Close file descriptor to avoid running out.
136 memory->Close();
137 return make_scoped_ptr(new DiscardableMemoryImpl(
138 memory.Pass(),
139 base::Bind(
140 &HostDiscardableSharedMemoryManager::DeletedDiscardableSharedMemory,
141 base::Unretained(this), new_id, current_process_handle)));
144 void HostDiscardableSharedMemoryManager::
145 AllocateLockedDiscardableSharedMemoryForChild(
146 base::ProcessHandle process_handle,
147 size_t size,
148 DiscardableSharedMemoryId id,
149 base::SharedMemoryHandle* shared_memory_handle) {
150 AllocateLockedDiscardableSharedMemory(process_handle, size, id,
151 shared_memory_handle);
154 void HostDiscardableSharedMemoryManager::ChildDeletedDiscardableSharedMemory(
155 DiscardableSharedMemoryId id,
156 base::ProcessHandle process_handle) {
157 DeletedDiscardableSharedMemory(id, process_handle);
160 void HostDiscardableSharedMemoryManager::ProcessRemoved(
161 base::ProcessHandle process_handle) {
162 base::AutoLock lock(lock_);
164 ProcessMap::iterator process_it = processes_.find(process_handle);
165 if (process_it == processes_.end())
166 return;
168 size_t bytes_allocated_before_releasing_memory = bytes_allocated_;
170 for (auto& segment_it : process_it->second)
171 ReleaseMemory(segment_it.second->memory());
173 processes_.erase(process_it);
175 if (bytes_allocated_ != bytes_allocated_before_releasing_memory)
176 BytesAllocatedChanged(bytes_allocated_);
179 void HostDiscardableSharedMemoryManager::SetMemoryLimit(size_t limit) {
180 base::AutoLock lock(lock_);
182 memory_limit_ = limit;
183 ReduceMemoryUsageUntilWithinMemoryLimit();
186 void HostDiscardableSharedMemoryManager::EnforceMemoryPolicy() {
187 base::AutoLock lock(lock_);
189 enforce_memory_policy_pending_ = false;
190 ReduceMemoryUsageUntilWithinMemoryLimit();
193 size_t HostDiscardableSharedMemoryManager::GetBytesAllocated() {
194 base::AutoLock lock(lock_);
196 return bytes_allocated_;
199 void HostDiscardableSharedMemoryManager::AllocateLockedDiscardableSharedMemory(
200 base::ProcessHandle process_handle,
201 size_t size,
202 DiscardableSharedMemoryId id,
203 base::SharedMemoryHandle* shared_memory_handle) {
204 base::AutoLock lock(lock_);
206 // Make sure |id| is not already in use.
207 MemorySegmentMap& process_segments = processes_[process_handle];
208 if (process_segments.find(id) != process_segments.end()) {
209 LOG(ERROR) << "Invalid discardable shared memory ID";
210 *shared_memory_handle = base::SharedMemory::NULLHandle();
211 return;
214 // Memory usage must be reduced to prevent the addition of |size| from
215 // taking usage above the limit. Usage should be reduced to 0 in cases
216 // where |size| is greater than the limit.
217 size_t limit = 0;
218 // Note: the actual mapped size can be larger than requested and cause
219 // |bytes_allocated_| to temporarily be larger than |memory_limit_|. The
220 // error is minimized by incrementing |bytes_allocated_| with the actual
221 // mapped size rather than |size| below.
222 if (size < memory_limit_)
223 limit = memory_limit_ - size;
225 if (bytes_allocated_ > limit)
226 ReduceMemoryUsageUntilWithinLimit(limit);
228 scoped_ptr<base::DiscardableSharedMemory> memory(
229 new base::DiscardableSharedMemory);
230 if (!memory->CreateAndMap(size)) {
231 *shared_memory_handle = base::SharedMemory::NULLHandle();
232 return;
235 if (!memory->ShareToProcess(process_handle, shared_memory_handle)) {
236 LOG(ERROR) << "Cannot share discardable memory segment";
237 *shared_memory_handle = base::SharedMemory::NULLHandle();
238 return;
241 base::CheckedNumeric<size_t> checked_bytes_allocated = bytes_allocated_;
242 checked_bytes_allocated += memory->mapped_size();
243 if (!checked_bytes_allocated.IsValid()) {
244 *shared_memory_handle = base::SharedMemory::NULLHandle();
245 return;
248 bytes_allocated_ = checked_bytes_allocated.ValueOrDie();
249 BytesAllocatedChanged(bytes_allocated_);
251 #if !defined(DISCARDABLE_SHARED_MEMORY_SHRINKING)
252 // Close file descriptor to avoid running out.
253 memory->Close();
254 #endif
256 scoped_refptr<MemorySegment> segment(new MemorySegment(memory.Pass()));
257 process_segments[id] = segment.get();
258 segments_.push_back(segment.get());
259 std::push_heap(segments_.begin(), segments_.end(), CompareMemoryUsageTime);
261 if (bytes_allocated_ > memory_limit_)
262 ScheduleEnforceMemoryPolicy();
265 void HostDiscardableSharedMemoryManager::DeletedDiscardableSharedMemory(
266 DiscardableSharedMemoryId id,
267 base::ProcessHandle process_handle) {
268 base::AutoLock lock(lock_);
270 MemorySegmentMap& process_segments = processes_[process_handle];
272 MemorySegmentMap::iterator segment_it = process_segments.find(id);
273 if (segment_it == process_segments.end()) {
274 LOG(ERROR) << "Invalid discardable shared memory ID";
275 return;
278 size_t bytes_allocated_before_releasing_memory = bytes_allocated_;
280 ReleaseMemory(segment_it->second->memory());
282 process_segments.erase(segment_it);
284 if (bytes_allocated_ != bytes_allocated_before_releasing_memory)
285 BytesAllocatedChanged(bytes_allocated_);
288 void HostDiscardableSharedMemoryManager::OnMemoryPressure(
289 base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level) {
290 base::AutoLock lock(lock_);
292 switch (memory_pressure_level) {
293 case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE:
294 break;
295 case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE:
296 // Purge memory until usage is within half of |memory_limit_|.
297 ReduceMemoryUsageUntilWithinLimit(memory_limit_ / 2);
298 break;
299 case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL:
300 // Purge everything possible when pressure is critical.
301 ReduceMemoryUsageUntilWithinLimit(0);
302 break;
306 void
307 HostDiscardableSharedMemoryManager::ReduceMemoryUsageUntilWithinMemoryLimit() {
308 lock_.AssertAcquired();
310 if (bytes_allocated_ <= memory_limit_)
311 return;
313 ReduceMemoryUsageUntilWithinLimit(memory_limit_);
314 if (bytes_allocated_ > memory_limit_)
315 ScheduleEnforceMemoryPolicy();
318 void HostDiscardableSharedMemoryManager::ReduceMemoryUsageUntilWithinLimit(
319 size_t limit) {
320 TRACE_EVENT1("renderer_host",
321 "HostDiscardableSharedMemoryManager::"
322 "ReduceMemoryUsageUntilWithinLimit",
323 "bytes_allocated",
324 bytes_allocated_);
326 // Usage time of currently locked segments are updated to this time and
327 // we stop eviction attempts as soon as we come across a segment that we've
328 // previously tried to evict but was locked.
329 base::Time current_time = Now();
331 lock_.AssertAcquired();
332 size_t bytes_allocated_before_purging = bytes_allocated_;
333 while (!segments_.empty()) {
334 if (bytes_allocated_ <= limit)
335 break;
337 // Stop eviction attempts when the LRU segment is currently in use.
338 if (segments_.front()->memory()->last_known_usage() >= current_time)
339 break;
341 std::pop_heap(segments_.begin(), segments_.end(), CompareMemoryUsageTime);
342 scoped_refptr<MemorySegment> segment = segments_.back();
343 segments_.pop_back();
345 // Attempt to purge LRU segment. When successful, released the memory.
346 if (segment->memory()->Purge(current_time)) {
347 #if defined(DISCARDABLE_SHARED_MEMORY_SHRINKING)
348 size_t size = segment->memory()->mapped_size();
349 DCHECK_GE(bytes_allocated_, size);
350 bytes_allocated_ -= size;
351 // Shrink memory segment. This will immediately release the memory to
352 // the OS.
353 segment->memory()->Shrink();
354 DCHECK_EQ(segment->memory()->mapped_size(), 0u);
355 #endif
356 ReleaseMemory(segment->memory());
357 continue;
360 // Add memory segment (with updated usage timestamp) back on heap after
361 // failed attempt to purge it.
362 segments_.push_back(segment.get());
363 std::push_heap(segments_.begin(), segments_.end(), CompareMemoryUsageTime);
366 if (bytes_allocated_ != bytes_allocated_before_purging)
367 BytesAllocatedChanged(bytes_allocated_);
370 void HostDiscardableSharedMemoryManager::ReleaseMemory(
371 base::DiscardableSharedMemory* memory) {
372 lock_.AssertAcquired();
374 size_t size = memory->mapped_size();
375 DCHECK_GE(bytes_allocated_, size);
376 bytes_allocated_ -= size;
378 // This will unmap the memory segment and drop our reference. The result
379 // is that the memory will be released to the OS if the child process is
380 // no longer referencing it.
381 // Note: We intentionally leave the segment in the |segments| vector to
382 // avoid reconstructing the heap. The element will be removed from the heap
383 // when its last usage time is older than all other segments.
384 memory->Unmap();
385 memory->Close();
388 void HostDiscardableSharedMemoryManager::BytesAllocatedChanged(
389 size_t new_bytes_allocated) const {
390 TRACE_COUNTER1("renderer_host", "TotalDiscardableMemoryUsage",
391 new_bytes_allocated);
393 static const char kTotalDiscardableMemoryAllocatedKey[] =
394 "total-discardable-memory-allocated";
395 base::debug::SetCrashKeyValue(kTotalDiscardableMemoryAllocatedKey,
396 base::Uint64ToString(new_bytes_allocated));
399 base::Time HostDiscardableSharedMemoryManager::Now() const {
400 return base::Time::Now();
403 void HostDiscardableSharedMemoryManager::ScheduleEnforceMemoryPolicy() {
404 lock_.AssertAcquired();
406 if (enforce_memory_policy_pending_)
407 return;
409 enforce_memory_policy_pending_ = true;
410 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
411 FROM_HERE,
412 base::Bind(&HostDiscardableSharedMemoryManager::EnforceMemoryPolicy,
413 weak_ptr_factory_.GetWeakPtr()),
414 base::TimeDelta::FromMilliseconds(kEnforceMemoryPolicyDelayMs));
417 } // namespace content