Convert events_unittests to run exclusively on Swarming
[chromium-blink-merge.git] / content / common / host_discardable_shared_memory_manager.cc
blob7085ed0c322f4ce966d6873aeeaeef961b2f7962
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"
20 #include "content/public/common/child_process_host.h"
22 namespace content {
23 namespace {
25 class DiscardableMemoryImpl : public base::DiscardableMemory {
26 public:
27 DiscardableMemoryImpl(scoped_ptr<base::DiscardableSharedMemory> shared_memory,
28 const base::Closure& deleted_callback)
29 : shared_memory_(shared_memory.Pass()),
30 deleted_callback_(deleted_callback),
31 is_locked_(true) {}
33 ~DiscardableMemoryImpl() override {
34 if (is_locked_)
35 shared_memory_->Unlock(0, 0);
37 deleted_callback_.Run();
40 // Overridden from base::DiscardableMemory:
41 bool Lock() override {
42 DCHECK(!is_locked_);
44 if (shared_memory_->Lock(0, 0) != base::DiscardableSharedMemory::SUCCESS)
45 return false;
47 is_locked_ = true;
48 return true;
50 void Unlock() override {
51 DCHECK(is_locked_);
53 shared_memory_->Unlock(0, 0);
54 is_locked_ = false;
56 void* data() const override {
57 DCHECK(is_locked_);
58 return shared_memory_->memory();
61 private:
62 scoped_ptr<base::DiscardableSharedMemory> shared_memory_;
63 const base::Closure deleted_callback_;
64 bool is_locked_;
66 DISALLOW_COPY_AND_ASSIGN(DiscardableMemoryImpl);
69 base::LazyInstance<HostDiscardableSharedMemoryManager>
70 g_discardable_shared_memory_manager = LAZY_INSTANCE_INITIALIZER;
72 #if defined(OS_ANDROID)
73 // Limits the number of FDs used to 32, assuming a 4MB allocation size.
74 const int64_t kMaxDefaultMemoryLimit = 128 * 1024 * 1024;
75 #else
76 const int64_t kMaxDefaultMemoryLimit = 512 * 1024 * 1024;
77 #endif
79 const int kEnforceMemoryPolicyDelayMs = 1000;
81 // Global atomic to generate unique discardable shared memory IDs.
82 base::StaticAtomicSequenceNumber g_next_discardable_shared_memory_id;
84 } // namespace
86 HostDiscardableSharedMemoryManager::MemorySegment::MemorySegment(
87 scoped_ptr<base::DiscardableSharedMemory> memory)
88 : memory_(memory.Pass()) {
91 HostDiscardableSharedMemoryManager::MemorySegment::~MemorySegment() {
94 HostDiscardableSharedMemoryManager::HostDiscardableSharedMemoryManager()
95 : memory_limit_(
96 // Allow 25% of physical memory to be used for discardable memory.
97 std::min(base::SysInfo::AmountOfPhysicalMemory() / 4,
98 base::SysInfo::IsLowEndDevice()
100 // Use 1/8th of discardable memory on low-end devices.
101 kMaxDefaultMemoryLimit / 8
102 : kMaxDefaultMemoryLimit)),
103 bytes_allocated_(0),
104 memory_pressure_listener_(new base::MemoryPressureListener(
105 base::Bind(&HostDiscardableSharedMemoryManager::OnMemoryPressure,
106 base::Unretained(this)))),
107 enforce_memory_policy_pending_(false),
108 weak_ptr_factory_(this) {
109 DCHECK_NE(memory_limit_, 0u);
112 HostDiscardableSharedMemoryManager::~HostDiscardableSharedMemoryManager() {
115 HostDiscardableSharedMemoryManager*
116 HostDiscardableSharedMemoryManager::current() {
117 return g_discardable_shared_memory_manager.Pointer();
120 scoped_ptr<base::DiscardableMemory>
121 HostDiscardableSharedMemoryManager::AllocateLockedDiscardableMemory(
122 size_t size) {
123 DiscardableSharedMemoryId new_id =
124 g_next_discardable_shared_memory_id.GetNext();
125 base::ProcessHandle current_process_handle = base::GetCurrentProcessHandle();
127 // Note: Use DiscardableSharedMemoryHeap for in-process allocation
128 // of discardable memory if the cost of each allocation is too high.
129 base::SharedMemoryHandle handle;
130 AllocateLockedDiscardableSharedMemory(current_process_handle,
131 ChildProcessHost::kInvalidUniqueID,
132 size, new_id, &handle);
133 CHECK(base::SharedMemory::IsHandleValid(handle));
134 scoped_ptr<base::DiscardableSharedMemory> memory(
135 new base::DiscardableSharedMemory(handle));
136 CHECK(memory->Map(size));
137 // Close file descriptor to avoid running out.
138 memory->Close();
139 return make_scoped_ptr(new DiscardableMemoryImpl(
140 memory.Pass(),
141 base::Bind(
142 &HostDiscardableSharedMemoryManager::DeletedDiscardableSharedMemory,
143 base::Unretained(this), new_id, ChildProcessHost::kInvalidUniqueID)));
146 void HostDiscardableSharedMemoryManager::
147 AllocateLockedDiscardableSharedMemoryForChild(
148 base::ProcessHandle process_handle,
149 int child_process_id,
150 size_t size,
151 DiscardableSharedMemoryId id,
152 base::SharedMemoryHandle* shared_memory_handle) {
153 AllocateLockedDiscardableSharedMemory(process_handle, child_process_id, size,
154 id, shared_memory_handle);
157 void HostDiscardableSharedMemoryManager::ChildDeletedDiscardableSharedMemory(
158 DiscardableSharedMemoryId id,
159 int child_process_id) {
160 DeletedDiscardableSharedMemory(id, child_process_id);
163 void HostDiscardableSharedMemoryManager::ProcessRemoved(int child_process_id) {
164 base::AutoLock lock(lock_);
166 ProcessMap::iterator process_it = processes_.find(child_process_id);
167 if (process_it == processes_.end())
168 return;
170 size_t bytes_allocated_before_releasing_memory = bytes_allocated_;
172 for (auto& segment_it : process_it->second)
173 ReleaseMemory(segment_it.second->memory());
175 processes_.erase(process_it);
177 if (bytes_allocated_ != bytes_allocated_before_releasing_memory)
178 BytesAllocatedChanged(bytes_allocated_);
181 void HostDiscardableSharedMemoryManager::SetMemoryLimit(size_t limit) {
182 base::AutoLock lock(lock_);
184 memory_limit_ = limit;
185 ReduceMemoryUsageUntilWithinMemoryLimit();
188 void HostDiscardableSharedMemoryManager::EnforceMemoryPolicy() {
189 base::AutoLock lock(lock_);
191 enforce_memory_policy_pending_ = false;
192 ReduceMemoryUsageUntilWithinMemoryLimit();
195 size_t HostDiscardableSharedMemoryManager::GetBytesAllocated() {
196 base::AutoLock lock(lock_);
198 return bytes_allocated_;
201 void HostDiscardableSharedMemoryManager::AllocateLockedDiscardableSharedMemory(
202 base::ProcessHandle process_handle,
203 int client_process_id,
204 size_t size,
205 DiscardableSharedMemoryId id,
206 base::SharedMemoryHandle* shared_memory_handle) {
207 base::AutoLock lock(lock_);
209 // Make sure |id| is not already in use.
210 MemorySegmentMap& process_segments = processes_[client_process_id];
211 if (process_segments.find(id) != process_segments.end()) {
212 LOG(ERROR) << "Invalid discardable shared memory ID";
213 *shared_memory_handle = base::SharedMemory::NULLHandle();
214 return;
217 // Memory usage must be reduced to prevent the addition of |size| from
218 // taking usage above the limit. Usage should be reduced to 0 in cases
219 // where |size| is greater than the limit.
220 size_t limit = 0;
221 // Note: the actual mapped size can be larger than requested and cause
222 // |bytes_allocated_| to temporarily be larger than |memory_limit_|. The
223 // error is minimized by incrementing |bytes_allocated_| with the actual
224 // mapped size rather than |size| below.
225 if (size < memory_limit_)
226 limit = memory_limit_ - size;
228 if (bytes_allocated_ > limit)
229 ReduceMemoryUsageUntilWithinLimit(limit);
231 scoped_ptr<base::DiscardableSharedMemory> memory(
232 new base::DiscardableSharedMemory);
233 if (!memory->CreateAndMap(size)) {
234 *shared_memory_handle = base::SharedMemory::NULLHandle();
235 return;
238 if (!memory->ShareToProcess(process_handle, shared_memory_handle)) {
239 LOG(ERROR) << "Cannot share discardable memory segment";
240 *shared_memory_handle = base::SharedMemory::NULLHandle();
241 return;
244 base::CheckedNumeric<size_t> checked_bytes_allocated = bytes_allocated_;
245 checked_bytes_allocated += memory->mapped_size();
246 if (!checked_bytes_allocated.IsValid()) {
247 *shared_memory_handle = base::SharedMemory::NULLHandle();
248 return;
251 bytes_allocated_ = checked_bytes_allocated.ValueOrDie();
252 BytesAllocatedChanged(bytes_allocated_);
254 #if !defined(DISCARDABLE_SHARED_MEMORY_SHRINKING)
255 // Close file descriptor to avoid running out.
256 memory->Close();
257 #endif
259 scoped_refptr<MemorySegment> segment(new MemorySegment(memory.Pass()));
260 process_segments[id] = segment.get();
261 segments_.push_back(segment.get());
262 std::push_heap(segments_.begin(), segments_.end(), CompareMemoryUsageTime);
264 if (bytes_allocated_ > memory_limit_)
265 ScheduleEnforceMemoryPolicy();
268 void HostDiscardableSharedMemoryManager::DeletedDiscardableSharedMemory(
269 DiscardableSharedMemoryId id,
270 int client_process_id) {
271 base::AutoLock lock(lock_);
273 MemorySegmentMap& process_segments = processes_[client_process_id];
275 MemorySegmentMap::iterator segment_it = process_segments.find(id);
276 if (segment_it == process_segments.end()) {
277 LOG(ERROR) << "Invalid discardable shared memory ID";
278 return;
281 size_t bytes_allocated_before_releasing_memory = bytes_allocated_;
283 ReleaseMemory(segment_it->second->memory());
285 process_segments.erase(segment_it);
287 if (bytes_allocated_ != bytes_allocated_before_releasing_memory)
288 BytesAllocatedChanged(bytes_allocated_);
291 void HostDiscardableSharedMemoryManager::OnMemoryPressure(
292 base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level) {
293 base::AutoLock lock(lock_);
295 switch (memory_pressure_level) {
296 case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE:
297 break;
298 case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE:
299 // Purge memory until usage is within half of |memory_limit_|.
300 ReduceMemoryUsageUntilWithinLimit(memory_limit_ / 2);
301 break;
302 case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL:
303 // Purge everything possible when pressure is critical.
304 ReduceMemoryUsageUntilWithinLimit(0);
305 break;
309 void
310 HostDiscardableSharedMemoryManager::ReduceMemoryUsageUntilWithinMemoryLimit() {
311 lock_.AssertAcquired();
313 if (bytes_allocated_ <= memory_limit_)
314 return;
316 ReduceMemoryUsageUntilWithinLimit(memory_limit_);
317 if (bytes_allocated_ > memory_limit_)
318 ScheduleEnforceMemoryPolicy();
321 void HostDiscardableSharedMemoryManager::ReduceMemoryUsageUntilWithinLimit(
322 size_t limit) {
323 TRACE_EVENT1("renderer_host",
324 "HostDiscardableSharedMemoryManager::"
325 "ReduceMemoryUsageUntilWithinLimit",
326 "bytes_allocated",
327 bytes_allocated_);
329 // Usage time of currently locked segments are updated to this time and
330 // we stop eviction attempts as soon as we come across a segment that we've
331 // previously tried to evict but was locked.
332 base::Time current_time = Now();
334 lock_.AssertAcquired();
335 size_t bytes_allocated_before_purging = bytes_allocated_;
336 while (!segments_.empty()) {
337 if (bytes_allocated_ <= limit)
338 break;
340 // Stop eviction attempts when the LRU segment is currently in use.
341 if (segments_.front()->memory()->last_known_usage() >= current_time)
342 break;
344 std::pop_heap(segments_.begin(), segments_.end(), CompareMemoryUsageTime);
345 scoped_refptr<MemorySegment> segment = segments_.back();
346 segments_.pop_back();
348 // Attempt to purge LRU segment. When successful, released the memory.
349 if (segment->memory()->Purge(current_time)) {
350 #if defined(DISCARDABLE_SHARED_MEMORY_SHRINKING)
351 size_t size = segment->memory()->mapped_size();
352 DCHECK_GE(bytes_allocated_, size);
353 bytes_allocated_ -= size;
354 // Shrink memory segment. This will immediately release the memory to
355 // the OS.
356 segment->memory()->Shrink();
357 DCHECK_EQ(segment->memory()->mapped_size(), 0u);
358 #endif
359 ReleaseMemory(segment->memory());
360 continue;
363 // Add memory segment (with updated usage timestamp) back on heap after
364 // failed attempt to purge it.
365 segments_.push_back(segment.get());
366 std::push_heap(segments_.begin(), segments_.end(), CompareMemoryUsageTime);
369 if (bytes_allocated_ != bytes_allocated_before_purging)
370 BytesAllocatedChanged(bytes_allocated_);
373 void HostDiscardableSharedMemoryManager::ReleaseMemory(
374 base::DiscardableSharedMemory* memory) {
375 lock_.AssertAcquired();
377 size_t size = memory->mapped_size();
378 DCHECK_GE(bytes_allocated_, size);
379 bytes_allocated_ -= size;
381 // This will unmap the memory segment and drop our reference. The result
382 // is that the memory will be released to the OS if the child process is
383 // no longer referencing it.
384 // Note: We intentionally leave the segment in the |segments| vector to
385 // avoid reconstructing the heap. The element will be removed from the heap
386 // when its last usage time is older than all other segments.
387 memory->Unmap();
388 memory->Close();
391 void HostDiscardableSharedMemoryManager::BytesAllocatedChanged(
392 size_t new_bytes_allocated) const {
393 TRACE_COUNTER1("renderer_host", "TotalDiscardableMemoryUsage",
394 new_bytes_allocated);
396 static const char kTotalDiscardableMemoryAllocatedKey[] =
397 "total-discardable-memory-allocated";
398 base::debug::SetCrashKeyValue(kTotalDiscardableMemoryAllocatedKey,
399 base::Uint64ToString(new_bytes_allocated));
402 base::Time HostDiscardableSharedMemoryManager::Now() const {
403 return base::Time::Now();
406 void HostDiscardableSharedMemoryManager::ScheduleEnforceMemoryPolicy() {
407 lock_.AssertAcquired();
409 if (enforce_memory_policy_pending_)
410 return;
412 enforce_memory_policy_pending_ = true;
413 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
414 FROM_HERE,
415 base::Bind(&HostDiscardableSharedMemoryManager::EnforceMemoryPolicy,
416 weak_ptr_factory_.GetWeakPtr()),
417 base::TimeDelta::FromMilliseconds(kEnforceMemoryPolicyDelayMs));
420 } // namespace content