Record MTU discovery packets in net-internals log.
[chromium-blink-merge.git] / content / common / host_discardable_shared_memory_manager.cc
blob19197f51078c17809b1016dc0e1ba3eebf3c6c19
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/strings/stringprintf.h"
18 #include "base/sys_info.h"
19 #include "base/thread_task_runner_handle.h"
20 #include "base/trace_event/memory_allocator_dump.h"
21 #include "base/trace_event/memory_dump_manager.h"
22 #include "base/trace_event/process_memory_dump.h"
23 #include "base/trace_event/trace_event.h"
24 #include "content/common/discardable_shared_memory_heap.h"
25 #include "content/public/common/child_process_host.h"
27 namespace content {
28 namespace {
30 class DiscardableMemoryImpl : public base::DiscardableMemory {
31 public:
32 DiscardableMemoryImpl(scoped_ptr<base::DiscardableSharedMemory> shared_memory,
33 const base::Closure& deleted_callback)
34 : shared_memory_(shared_memory.Pass()),
35 deleted_callback_(deleted_callback),
36 is_locked_(true) {}
38 ~DiscardableMemoryImpl() override {
39 if (is_locked_)
40 shared_memory_->Unlock(0, 0);
42 deleted_callback_.Run();
45 // Overridden from base::DiscardableMemory:
46 bool Lock() override {
47 DCHECK(!is_locked_);
49 if (shared_memory_->Lock(0, 0) != base::DiscardableSharedMemory::SUCCESS)
50 return false;
52 is_locked_ = true;
53 return true;
55 void Unlock() override {
56 DCHECK(is_locked_);
58 shared_memory_->Unlock(0, 0);
59 is_locked_ = false;
61 void* data() const override {
62 DCHECK(is_locked_);
63 return shared_memory_->memory();
66 private:
67 scoped_ptr<base::DiscardableSharedMemory> shared_memory_;
68 const base::Closure deleted_callback_;
69 bool is_locked_;
71 DISALLOW_COPY_AND_ASSIGN(DiscardableMemoryImpl);
74 base::LazyInstance<HostDiscardableSharedMemoryManager>
75 g_discardable_shared_memory_manager = LAZY_INSTANCE_INITIALIZER;
77 #if defined(OS_ANDROID)
78 // Limits the number of FDs used to 32, assuming a 4MB allocation size.
79 const int64_t kMaxDefaultMemoryLimit = 128 * 1024 * 1024;
80 #else
81 const int64_t kMaxDefaultMemoryLimit = 512 * 1024 * 1024;
82 #endif
84 const int kEnforceMemoryPolicyDelayMs = 1000;
86 // Global atomic to generate unique discardable shared memory IDs.
87 base::StaticAtomicSequenceNumber g_next_discardable_shared_memory_id;
89 } // namespace
91 HostDiscardableSharedMemoryManager::MemorySegment::MemorySegment(
92 scoped_ptr<base::DiscardableSharedMemory> memory)
93 : memory_(memory.Pass()) {
96 HostDiscardableSharedMemoryManager::MemorySegment::~MemorySegment() {
99 HostDiscardableSharedMemoryManager::HostDiscardableSharedMemoryManager()
100 : memory_limit_(
101 // Allow 25% of physical memory to be used for discardable memory.
102 std::min(base::SysInfo::AmountOfPhysicalMemory() / 4,
103 base::SysInfo::IsLowEndDevice()
105 // Use 1/8th of discardable memory on low-end devices.
106 kMaxDefaultMemoryLimit / 8
107 : kMaxDefaultMemoryLimit)),
108 bytes_allocated_(0),
109 memory_pressure_listener_(new base::MemoryPressureListener(
110 base::Bind(&HostDiscardableSharedMemoryManager::OnMemoryPressure,
111 base::Unretained(this)))),
112 enforce_memory_policy_pending_(false),
113 weak_ptr_factory_(this) {
114 DCHECK_NE(memory_limit_, 0u);
115 base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider(
116 this);
119 HostDiscardableSharedMemoryManager::~HostDiscardableSharedMemoryManager() {
120 base::trace_event::MemoryDumpManager::GetInstance()->UnregisterDumpProvider(
121 this);
124 HostDiscardableSharedMemoryManager*
125 HostDiscardableSharedMemoryManager::current() {
126 return g_discardable_shared_memory_manager.Pointer();
129 scoped_ptr<base::DiscardableMemory>
130 HostDiscardableSharedMemoryManager::AllocateLockedDiscardableMemory(
131 size_t size) {
132 DiscardableSharedMemoryId new_id =
133 g_next_discardable_shared_memory_id.GetNext();
134 base::ProcessHandle current_process_handle = base::GetCurrentProcessHandle();
136 // Note: Use DiscardableSharedMemoryHeap for in-process allocation
137 // of discardable memory if the cost of each allocation is too high.
138 base::SharedMemoryHandle handle;
139 AllocateLockedDiscardableSharedMemory(current_process_handle,
140 ChildProcessHost::kInvalidUniqueID,
141 size, new_id, &handle);
142 CHECK(base::SharedMemory::IsHandleValid(handle));
143 scoped_ptr<base::DiscardableSharedMemory> memory(
144 new base::DiscardableSharedMemory(handle));
145 CHECK(memory->Map(size));
146 // Close file descriptor to avoid running out.
147 memory->Close();
148 return make_scoped_ptr(new DiscardableMemoryImpl(
149 memory.Pass(),
150 base::Bind(
151 &HostDiscardableSharedMemoryManager::DeletedDiscardableSharedMemory,
152 base::Unretained(this), new_id, ChildProcessHost::kInvalidUniqueID)));
155 bool HostDiscardableSharedMemoryManager::OnMemoryDump(
156 base::trace_event::ProcessMemoryDump* pmd) {
157 base::AutoLock lock(lock_);
158 for (const auto& process_entry : processes_) {
159 const int child_process_id = process_entry.first;
160 const MemorySegmentMap& process_segments = process_entry.second;
161 for (const auto& segment_entry : process_segments) {
162 const int segment_id = segment_entry.first;
163 const MemorySegment* segment = segment_entry.second.get();
164 std::string dump_name = base::StringPrintf(
165 "discardable/process_%x/segment_%d", child_process_id, segment_id);
166 base::trace_event::MemoryAllocatorDump* dump =
167 pmd->CreateAllocatorDump(dump_name);
168 dump->AddScalar(base::trace_event::MemoryAllocatorDump::kNameSize,
169 base::trace_event::MemoryAllocatorDump::kUnitsBytes,
170 segment->memory()->mapped_size());
172 // Create the cross-process ownership edge. If the child creates a
173 // corresponding dump for the same segment, this will avoid to
174 // double-count them in tracing. If, instead, no other process will emit a
175 // dump with the same guid, the segment will be accounted to the browser.
176 const uint64 child_tracing_process_id = base::trace_event::
177 MemoryDumpManager::ChildProcessIdToTracingProcessId(child_process_id);
178 base::trace_event::MemoryAllocatorDumpGuid shared_segment_guid =
179 DiscardableSharedMemoryHeap::GetSegmentGUIDForTracing(
180 child_tracing_process_id, segment_id);
181 pmd->CreateSharedGlobalAllocatorDump(shared_segment_guid);
182 pmd->AddOwnershipEdge(dump->guid(), shared_segment_guid);
185 return true;
188 void HostDiscardableSharedMemoryManager::
189 AllocateLockedDiscardableSharedMemoryForChild(
190 base::ProcessHandle process_handle,
191 int child_process_id,
192 size_t size,
193 DiscardableSharedMemoryId id,
194 base::SharedMemoryHandle* shared_memory_handle) {
195 AllocateLockedDiscardableSharedMemory(process_handle, child_process_id, size,
196 id, shared_memory_handle);
199 void HostDiscardableSharedMemoryManager::ChildDeletedDiscardableSharedMemory(
200 DiscardableSharedMemoryId id,
201 int child_process_id) {
202 DeletedDiscardableSharedMemory(id, child_process_id);
205 void HostDiscardableSharedMemoryManager::ProcessRemoved(int child_process_id) {
206 base::AutoLock lock(lock_);
208 ProcessMap::iterator process_it = processes_.find(child_process_id);
209 if (process_it == processes_.end())
210 return;
212 size_t bytes_allocated_before_releasing_memory = bytes_allocated_;
214 for (auto& segment_it : process_it->second)
215 ReleaseMemory(segment_it.second->memory());
217 processes_.erase(process_it);
219 if (bytes_allocated_ != bytes_allocated_before_releasing_memory)
220 BytesAllocatedChanged(bytes_allocated_);
223 void HostDiscardableSharedMemoryManager::SetMemoryLimit(size_t limit) {
224 base::AutoLock lock(lock_);
226 memory_limit_ = limit;
227 ReduceMemoryUsageUntilWithinMemoryLimit();
230 void HostDiscardableSharedMemoryManager::EnforceMemoryPolicy() {
231 base::AutoLock lock(lock_);
233 enforce_memory_policy_pending_ = false;
234 ReduceMemoryUsageUntilWithinMemoryLimit();
237 size_t HostDiscardableSharedMemoryManager::GetBytesAllocated() {
238 base::AutoLock lock(lock_);
240 return bytes_allocated_;
243 void HostDiscardableSharedMemoryManager::AllocateLockedDiscardableSharedMemory(
244 base::ProcessHandle process_handle,
245 int client_process_id,
246 size_t size,
247 DiscardableSharedMemoryId id,
248 base::SharedMemoryHandle* shared_memory_handle) {
249 base::AutoLock lock(lock_);
251 // Make sure |id| is not already in use.
252 MemorySegmentMap& process_segments = processes_[client_process_id];
253 if (process_segments.find(id) != process_segments.end()) {
254 LOG(ERROR) << "Invalid discardable shared memory ID";
255 *shared_memory_handle = base::SharedMemory::NULLHandle();
256 return;
259 // Memory usage must be reduced to prevent the addition of |size| from
260 // taking usage above the limit. Usage should be reduced to 0 in cases
261 // where |size| is greater than the limit.
262 size_t limit = 0;
263 // Note: the actual mapped size can be larger than requested and cause
264 // |bytes_allocated_| to temporarily be larger than |memory_limit_|. The
265 // error is minimized by incrementing |bytes_allocated_| with the actual
266 // mapped size rather than |size| below.
267 if (size < memory_limit_)
268 limit = memory_limit_ - size;
270 if (bytes_allocated_ > limit)
271 ReduceMemoryUsageUntilWithinLimit(limit);
273 scoped_ptr<base::DiscardableSharedMemory> memory(
274 new base::DiscardableSharedMemory);
275 if (!memory->CreateAndMap(size)) {
276 *shared_memory_handle = base::SharedMemory::NULLHandle();
277 return;
280 if (!memory->ShareToProcess(process_handle, shared_memory_handle)) {
281 LOG(ERROR) << "Cannot share discardable memory segment";
282 *shared_memory_handle = base::SharedMemory::NULLHandle();
283 return;
286 base::CheckedNumeric<size_t> checked_bytes_allocated = bytes_allocated_;
287 checked_bytes_allocated += memory->mapped_size();
288 if (!checked_bytes_allocated.IsValid()) {
289 *shared_memory_handle = base::SharedMemory::NULLHandle();
290 return;
293 bytes_allocated_ = checked_bytes_allocated.ValueOrDie();
294 BytesAllocatedChanged(bytes_allocated_);
296 #if !defined(DISCARDABLE_SHARED_MEMORY_SHRINKING)
297 // Close file descriptor to avoid running out.
298 memory->Close();
299 #endif
301 scoped_refptr<MemorySegment> segment(new MemorySegment(memory.Pass()));
302 process_segments[id] = segment.get();
303 segments_.push_back(segment.get());
304 std::push_heap(segments_.begin(), segments_.end(), CompareMemoryUsageTime);
306 if (bytes_allocated_ > memory_limit_)
307 ScheduleEnforceMemoryPolicy();
310 void HostDiscardableSharedMemoryManager::DeletedDiscardableSharedMemory(
311 DiscardableSharedMemoryId id,
312 int client_process_id) {
313 base::AutoLock lock(lock_);
315 MemorySegmentMap& process_segments = processes_[client_process_id];
317 MemorySegmentMap::iterator segment_it = process_segments.find(id);
318 if (segment_it == process_segments.end()) {
319 LOG(ERROR) << "Invalid discardable shared memory ID";
320 return;
323 size_t bytes_allocated_before_releasing_memory = bytes_allocated_;
325 ReleaseMemory(segment_it->second->memory());
327 process_segments.erase(segment_it);
329 if (bytes_allocated_ != bytes_allocated_before_releasing_memory)
330 BytesAllocatedChanged(bytes_allocated_);
333 void HostDiscardableSharedMemoryManager::OnMemoryPressure(
334 base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level) {
335 base::AutoLock lock(lock_);
337 switch (memory_pressure_level) {
338 case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE:
339 break;
340 case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE:
341 // Purge memory until usage is within half of |memory_limit_|.
342 ReduceMemoryUsageUntilWithinLimit(memory_limit_ / 2);
343 break;
344 case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL:
345 // Purge everything possible when pressure is critical.
346 ReduceMemoryUsageUntilWithinLimit(0);
347 break;
351 void
352 HostDiscardableSharedMemoryManager::ReduceMemoryUsageUntilWithinMemoryLimit() {
353 lock_.AssertAcquired();
355 if (bytes_allocated_ <= memory_limit_)
356 return;
358 ReduceMemoryUsageUntilWithinLimit(memory_limit_);
359 if (bytes_allocated_ > memory_limit_)
360 ScheduleEnforceMemoryPolicy();
363 void HostDiscardableSharedMemoryManager::ReduceMemoryUsageUntilWithinLimit(
364 size_t limit) {
365 TRACE_EVENT1("renderer_host",
366 "HostDiscardableSharedMemoryManager::"
367 "ReduceMemoryUsageUntilWithinLimit",
368 "bytes_allocated",
369 bytes_allocated_);
371 // Usage time of currently locked segments are updated to this time and
372 // we stop eviction attempts as soon as we come across a segment that we've
373 // previously tried to evict but was locked.
374 base::Time current_time = Now();
376 lock_.AssertAcquired();
377 size_t bytes_allocated_before_purging = bytes_allocated_;
378 while (!segments_.empty()) {
379 if (bytes_allocated_ <= limit)
380 break;
382 // Stop eviction attempts when the LRU segment is currently in use.
383 if (segments_.front()->memory()->last_known_usage() >= current_time)
384 break;
386 std::pop_heap(segments_.begin(), segments_.end(), CompareMemoryUsageTime);
387 scoped_refptr<MemorySegment> segment = segments_.back();
388 segments_.pop_back();
390 // Attempt to purge LRU segment. When successful, released the memory.
391 if (segment->memory()->Purge(current_time)) {
392 #if defined(DISCARDABLE_SHARED_MEMORY_SHRINKING)
393 size_t size = segment->memory()->mapped_size();
394 DCHECK_GE(bytes_allocated_, size);
395 bytes_allocated_ -= size;
396 // Shrink memory segment. This will immediately release the memory to
397 // the OS.
398 segment->memory()->Shrink();
399 DCHECK_EQ(segment->memory()->mapped_size(), 0u);
400 #endif
401 ReleaseMemory(segment->memory());
402 continue;
405 // Add memory segment (with updated usage timestamp) back on heap after
406 // failed attempt to purge it.
407 segments_.push_back(segment.get());
408 std::push_heap(segments_.begin(), segments_.end(), CompareMemoryUsageTime);
411 if (bytes_allocated_ != bytes_allocated_before_purging)
412 BytesAllocatedChanged(bytes_allocated_);
415 void HostDiscardableSharedMemoryManager::ReleaseMemory(
416 base::DiscardableSharedMemory* memory) {
417 lock_.AssertAcquired();
419 size_t size = memory->mapped_size();
420 DCHECK_GE(bytes_allocated_, size);
421 bytes_allocated_ -= size;
423 // This will unmap the memory segment and drop our reference. The result
424 // is that the memory will be released to the OS if the child process is
425 // no longer referencing it.
426 // Note: We intentionally leave the segment in the |segments| vector to
427 // avoid reconstructing the heap. The element will be removed from the heap
428 // when its last usage time is older than all other segments.
429 memory->Unmap();
430 memory->Close();
433 void HostDiscardableSharedMemoryManager::BytesAllocatedChanged(
434 size_t new_bytes_allocated) const {
435 TRACE_COUNTER1("renderer_host", "TotalDiscardableMemoryUsage",
436 new_bytes_allocated);
438 static const char kTotalDiscardableMemoryAllocatedKey[] =
439 "total-discardable-memory-allocated";
440 base::debug::SetCrashKeyValue(kTotalDiscardableMemoryAllocatedKey,
441 base::Uint64ToString(new_bytes_allocated));
444 base::Time HostDiscardableSharedMemoryManager::Now() const {
445 return base::Time::Now();
448 void HostDiscardableSharedMemoryManager::ScheduleEnforceMemoryPolicy() {
449 lock_.AssertAcquired();
451 if (enforce_memory_policy_pending_)
452 return;
454 enforce_memory_policy_pending_ = true;
455 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
456 FROM_HERE,
457 base::Bind(&HostDiscardableSharedMemoryManager::EnforceMemoryPolicy,
458 weak_ptr_factory_.GetWeakPtr()),
459 base::TimeDelta::FromMilliseconds(kEnforceMemoryPolicyDelayMs));
462 } // namespace content