Process Alt-Svc headers.
[chromium-blink-merge.git] / content / common / host_discardable_shared_memory_manager.cc
blobd2afe38f1497de74b5d54d440600a75057005c71
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/child_process_host_impl.h"
25 #include "content/common/discardable_shared_memory_heap.h"
26 #include "content/public/common/child_process_host.h"
28 namespace content {
29 namespace {
31 class DiscardableMemoryImpl : public base::DiscardableMemory {
32 public:
33 DiscardableMemoryImpl(scoped_ptr<base::DiscardableSharedMemory> shared_memory,
34 const base::Closure& deleted_callback)
35 : shared_memory_(shared_memory.Pass()),
36 deleted_callback_(deleted_callback),
37 is_locked_(true) {}
39 ~DiscardableMemoryImpl() override {
40 if (is_locked_)
41 shared_memory_->Unlock(0, 0);
43 deleted_callback_.Run();
46 // Overridden from base::DiscardableMemory:
47 bool Lock() override {
48 DCHECK(!is_locked_);
50 if (shared_memory_->Lock(0, 0) != base::DiscardableSharedMemory::SUCCESS)
51 return false;
53 is_locked_ = true;
54 return true;
56 void Unlock() override {
57 DCHECK(is_locked_);
59 shared_memory_->Unlock(0, 0);
60 is_locked_ = false;
62 void* data() const override {
63 DCHECK(is_locked_);
64 return shared_memory_->memory();
67 private:
68 scoped_ptr<base::DiscardableSharedMemory> shared_memory_;
69 const base::Closure deleted_callback_;
70 bool is_locked_;
72 DISALLOW_COPY_AND_ASSIGN(DiscardableMemoryImpl);
75 base::LazyInstance<HostDiscardableSharedMemoryManager>
76 g_discardable_shared_memory_manager = LAZY_INSTANCE_INITIALIZER;
78 #if defined(OS_ANDROID)
79 // Limits the number of FDs used to 32, assuming a 4MB allocation size.
80 const int64_t kMaxDefaultMemoryLimit = 128 * 1024 * 1024;
81 #else
82 const int64_t kMaxDefaultMemoryLimit = 512 * 1024 * 1024;
83 #endif
85 const int kEnforceMemoryPolicyDelayMs = 1000;
87 // Global atomic to generate unique discardable shared memory IDs.
88 base::StaticAtomicSequenceNumber g_next_discardable_shared_memory_id;
90 } // namespace
92 HostDiscardableSharedMemoryManager::MemorySegment::MemorySegment(
93 scoped_ptr<base::DiscardableSharedMemory> memory)
94 : memory_(memory.Pass()) {
97 HostDiscardableSharedMemoryManager::MemorySegment::~MemorySegment() {
100 HostDiscardableSharedMemoryManager::HostDiscardableSharedMemoryManager()
101 : memory_limit_(
102 // Allow 25% of physical memory to be used for discardable memory.
103 std::min(base::SysInfo::AmountOfPhysicalMemory() / 4,
104 base::SysInfo::IsLowEndDevice()
106 // Use 1/8th of discardable memory on low-end devices.
107 kMaxDefaultMemoryLimit / 8
108 : kMaxDefaultMemoryLimit)),
109 bytes_allocated_(0),
110 memory_pressure_listener_(new base::MemoryPressureListener(
111 base::Bind(&HostDiscardableSharedMemoryManager::OnMemoryPressure,
112 base::Unretained(this)))),
113 enforce_memory_policy_pending_(false),
114 weak_ptr_factory_(this) {
115 DCHECK_NE(memory_limit_, 0u);
116 base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider(
117 this);
120 HostDiscardableSharedMemoryManager::~HostDiscardableSharedMemoryManager() {
121 base::trace_event::MemoryDumpManager::GetInstance()->UnregisterDumpProvider(
122 this);
125 HostDiscardableSharedMemoryManager*
126 HostDiscardableSharedMemoryManager::current() {
127 return g_discardable_shared_memory_manager.Pointer();
130 scoped_ptr<base::DiscardableMemory>
131 HostDiscardableSharedMemoryManager::AllocateLockedDiscardableMemory(
132 size_t size) {
133 DiscardableSharedMemoryId new_id =
134 g_next_discardable_shared_memory_id.GetNext();
135 base::ProcessHandle current_process_handle = base::GetCurrentProcessHandle();
137 // Note: Use DiscardableSharedMemoryHeap for in-process allocation
138 // of discardable memory if the cost of each allocation is too high.
139 base::SharedMemoryHandle handle;
140 AllocateLockedDiscardableSharedMemory(current_process_handle,
141 ChildProcessHost::kInvalidUniqueID,
142 size, new_id, &handle);
143 CHECK(base::SharedMemory::IsHandleValid(handle));
144 scoped_ptr<base::DiscardableSharedMemory> memory(
145 new base::DiscardableSharedMemory(handle));
146 CHECK(memory->Map(size));
147 // Close file descriptor to avoid running out.
148 memory->Close();
149 return make_scoped_ptr(new DiscardableMemoryImpl(
150 memory.Pass(),
151 base::Bind(
152 &HostDiscardableSharedMemoryManager::DeletedDiscardableSharedMemory,
153 base::Unretained(this), new_id, ChildProcessHost::kInvalidUniqueID)));
156 bool HostDiscardableSharedMemoryManager::OnMemoryDump(
157 base::trace_event::ProcessMemoryDump* pmd) {
158 base::AutoLock lock(lock_);
159 for (const auto& process_entry : processes_) {
160 const int child_process_id = process_entry.first;
161 const MemorySegmentMap& process_segments = process_entry.second;
162 for (const auto& segment_entry : process_segments) {
163 const int segment_id = segment_entry.first;
164 const MemorySegment* segment = segment_entry.second.get();
165 std::string dump_name = base::StringPrintf(
166 "discardable/process_%x/segment_%d", child_process_id, segment_id);
167 base::trace_event::MemoryAllocatorDump* dump =
168 pmd->CreateAllocatorDump(dump_name);
169 dump->AddScalar(base::trace_event::MemoryAllocatorDump::kNameSize,
170 base::trace_event::MemoryAllocatorDump::kUnitsBytes,
171 segment->memory()->mapped_size());
173 // Create the cross-process ownership edge. If the child creates a
174 // corresponding dump for the same segment, this will avoid to
175 // double-count them in tracing. If, instead, no other process will emit a
176 // dump with the same guid, the segment will be accounted to the browser.
177 const uint64 child_tracing_process_id =
178 ChildProcessHostImpl::ChildProcessUniqueIdToTracingProcessId(
179 child_process_id);
180 base::trace_event::MemoryAllocatorDumpGuid shared_segment_guid =
181 DiscardableSharedMemoryHeap::GetSegmentGUIDForTracing(
182 child_tracing_process_id, segment_id);
183 pmd->CreateSharedGlobalAllocatorDump(shared_segment_guid);
184 pmd->AddOwnershipEdge(dump->guid(), shared_segment_guid);
187 return true;
190 void HostDiscardableSharedMemoryManager::
191 AllocateLockedDiscardableSharedMemoryForChild(
192 base::ProcessHandle process_handle,
193 int child_process_id,
194 size_t size,
195 DiscardableSharedMemoryId id,
196 base::SharedMemoryHandle* shared_memory_handle) {
197 AllocateLockedDiscardableSharedMemory(process_handle, child_process_id, size,
198 id, shared_memory_handle);
201 void HostDiscardableSharedMemoryManager::ChildDeletedDiscardableSharedMemory(
202 DiscardableSharedMemoryId id,
203 int child_process_id) {
204 DeletedDiscardableSharedMemory(id, child_process_id);
207 void HostDiscardableSharedMemoryManager::ProcessRemoved(int child_process_id) {
208 base::AutoLock lock(lock_);
210 ProcessMap::iterator process_it = processes_.find(child_process_id);
211 if (process_it == processes_.end())
212 return;
214 size_t bytes_allocated_before_releasing_memory = bytes_allocated_;
216 for (auto& segment_it : process_it->second)
217 ReleaseMemory(segment_it.second->memory());
219 processes_.erase(process_it);
221 if (bytes_allocated_ != bytes_allocated_before_releasing_memory)
222 BytesAllocatedChanged(bytes_allocated_);
225 void HostDiscardableSharedMemoryManager::SetMemoryLimit(size_t limit) {
226 base::AutoLock lock(lock_);
228 memory_limit_ = limit;
229 ReduceMemoryUsageUntilWithinMemoryLimit();
232 void HostDiscardableSharedMemoryManager::EnforceMemoryPolicy() {
233 base::AutoLock lock(lock_);
235 enforce_memory_policy_pending_ = false;
236 ReduceMemoryUsageUntilWithinMemoryLimit();
239 size_t HostDiscardableSharedMemoryManager::GetBytesAllocated() {
240 base::AutoLock lock(lock_);
242 return bytes_allocated_;
245 void HostDiscardableSharedMemoryManager::AllocateLockedDiscardableSharedMemory(
246 base::ProcessHandle process_handle,
247 int client_process_id,
248 size_t size,
249 DiscardableSharedMemoryId id,
250 base::SharedMemoryHandle* shared_memory_handle) {
251 base::AutoLock lock(lock_);
253 // Make sure |id| is not already in use.
254 MemorySegmentMap& process_segments = processes_[client_process_id];
255 if (process_segments.find(id) != process_segments.end()) {
256 LOG(ERROR) << "Invalid discardable shared memory ID";
257 *shared_memory_handle = base::SharedMemory::NULLHandle();
258 return;
261 // Memory usage must be reduced to prevent the addition of |size| from
262 // taking usage above the limit. Usage should be reduced to 0 in cases
263 // where |size| is greater than the limit.
264 size_t limit = 0;
265 // Note: the actual mapped size can be larger than requested and cause
266 // |bytes_allocated_| to temporarily be larger than |memory_limit_|. The
267 // error is minimized by incrementing |bytes_allocated_| with the actual
268 // mapped size rather than |size| below.
269 if (size < memory_limit_)
270 limit = memory_limit_ - size;
272 if (bytes_allocated_ > limit)
273 ReduceMemoryUsageUntilWithinLimit(limit);
275 scoped_ptr<base::DiscardableSharedMemory> memory(
276 new base::DiscardableSharedMemory);
277 if (!memory->CreateAndMap(size)) {
278 *shared_memory_handle = base::SharedMemory::NULLHandle();
279 return;
282 if (!memory->ShareToProcess(process_handle, shared_memory_handle)) {
283 LOG(ERROR) << "Cannot share discardable memory segment";
284 *shared_memory_handle = base::SharedMemory::NULLHandle();
285 return;
288 base::CheckedNumeric<size_t> checked_bytes_allocated = bytes_allocated_;
289 checked_bytes_allocated += memory->mapped_size();
290 if (!checked_bytes_allocated.IsValid()) {
291 *shared_memory_handle = base::SharedMemory::NULLHandle();
292 return;
295 bytes_allocated_ = checked_bytes_allocated.ValueOrDie();
296 BytesAllocatedChanged(bytes_allocated_);
298 #if !defined(DISCARDABLE_SHARED_MEMORY_SHRINKING)
299 // Close file descriptor to avoid running out.
300 memory->Close();
301 #endif
303 scoped_refptr<MemorySegment> segment(new MemorySegment(memory.Pass()));
304 process_segments[id] = segment.get();
305 segments_.push_back(segment.get());
306 std::push_heap(segments_.begin(), segments_.end(), CompareMemoryUsageTime);
308 if (bytes_allocated_ > memory_limit_)
309 ScheduleEnforceMemoryPolicy();
312 void HostDiscardableSharedMemoryManager::DeletedDiscardableSharedMemory(
313 DiscardableSharedMemoryId id,
314 int client_process_id) {
315 base::AutoLock lock(lock_);
317 MemorySegmentMap& process_segments = processes_[client_process_id];
319 MemorySegmentMap::iterator segment_it = process_segments.find(id);
320 if (segment_it == process_segments.end()) {
321 LOG(ERROR) << "Invalid discardable shared memory ID";
322 return;
325 size_t bytes_allocated_before_releasing_memory = bytes_allocated_;
327 ReleaseMemory(segment_it->second->memory());
329 process_segments.erase(segment_it);
331 if (bytes_allocated_ != bytes_allocated_before_releasing_memory)
332 BytesAllocatedChanged(bytes_allocated_);
335 void HostDiscardableSharedMemoryManager::OnMemoryPressure(
336 base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level) {
337 base::AutoLock lock(lock_);
339 switch (memory_pressure_level) {
340 case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE:
341 break;
342 case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE:
343 // Purge memory until usage is within half of |memory_limit_|.
344 ReduceMemoryUsageUntilWithinLimit(memory_limit_ / 2);
345 break;
346 case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL:
347 // Purge everything possible when pressure is critical.
348 ReduceMemoryUsageUntilWithinLimit(0);
349 break;
353 void
354 HostDiscardableSharedMemoryManager::ReduceMemoryUsageUntilWithinMemoryLimit() {
355 lock_.AssertAcquired();
357 if (bytes_allocated_ <= memory_limit_)
358 return;
360 ReduceMemoryUsageUntilWithinLimit(memory_limit_);
361 if (bytes_allocated_ > memory_limit_)
362 ScheduleEnforceMemoryPolicy();
365 void HostDiscardableSharedMemoryManager::ReduceMemoryUsageUntilWithinLimit(
366 size_t limit) {
367 TRACE_EVENT1("renderer_host",
368 "HostDiscardableSharedMemoryManager::"
369 "ReduceMemoryUsageUntilWithinLimit",
370 "bytes_allocated",
371 bytes_allocated_);
373 // Usage time of currently locked segments are updated to this time and
374 // we stop eviction attempts as soon as we come across a segment that we've
375 // previously tried to evict but was locked.
376 base::Time current_time = Now();
378 lock_.AssertAcquired();
379 size_t bytes_allocated_before_purging = bytes_allocated_;
380 while (!segments_.empty()) {
381 if (bytes_allocated_ <= limit)
382 break;
384 // Stop eviction attempts when the LRU segment is currently in use.
385 if (segments_.front()->memory()->last_known_usage() >= current_time)
386 break;
388 std::pop_heap(segments_.begin(), segments_.end(), CompareMemoryUsageTime);
389 scoped_refptr<MemorySegment> segment = segments_.back();
390 segments_.pop_back();
392 // Attempt to purge LRU segment. When successful, released the memory.
393 if (segment->memory()->Purge(current_time)) {
394 #if defined(DISCARDABLE_SHARED_MEMORY_SHRINKING)
395 size_t size = segment->memory()->mapped_size();
396 DCHECK_GE(bytes_allocated_, size);
397 bytes_allocated_ -= size;
398 // Shrink memory segment. This will immediately release the memory to
399 // the OS.
400 segment->memory()->Shrink();
401 DCHECK_EQ(segment->memory()->mapped_size(), 0u);
402 #endif
403 ReleaseMemory(segment->memory());
404 continue;
407 // Add memory segment (with updated usage timestamp) back on heap after
408 // failed attempt to purge it.
409 segments_.push_back(segment.get());
410 std::push_heap(segments_.begin(), segments_.end(), CompareMemoryUsageTime);
413 if (bytes_allocated_ != bytes_allocated_before_purging)
414 BytesAllocatedChanged(bytes_allocated_);
417 void HostDiscardableSharedMemoryManager::ReleaseMemory(
418 base::DiscardableSharedMemory* memory) {
419 lock_.AssertAcquired();
421 size_t size = memory->mapped_size();
422 DCHECK_GE(bytes_allocated_, size);
423 bytes_allocated_ -= size;
425 // This will unmap the memory segment and drop our reference. The result
426 // is that the memory will be released to the OS if the child process is
427 // no longer referencing it.
428 // Note: We intentionally leave the segment in the |segments| vector to
429 // avoid reconstructing the heap. The element will be removed from the heap
430 // when its last usage time is older than all other segments.
431 memory->Unmap();
432 memory->Close();
435 void HostDiscardableSharedMemoryManager::BytesAllocatedChanged(
436 size_t new_bytes_allocated) const {
437 TRACE_COUNTER1("renderer_host", "TotalDiscardableMemoryUsage",
438 new_bytes_allocated);
440 static const char kTotalDiscardableMemoryAllocatedKey[] =
441 "total-discardable-memory-allocated";
442 base::debug::SetCrashKeyValue(kTotalDiscardableMemoryAllocatedKey,
443 base::Uint64ToString(new_bytes_allocated));
446 base::Time HostDiscardableSharedMemoryManager::Now() const {
447 return base::Time::Now();
450 void HostDiscardableSharedMemoryManager::ScheduleEnforceMemoryPolicy() {
451 lock_.AssertAcquired();
453 if (enforce_memory_policy_pending_)
454 return;
456 enforce_memory_policy_pending_ = true;
457 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
458 FROM_HERE,
459 base::Bind(&HostDiscardableSharedMemoryManager::EnforceMemoryPolicy,
460 weak_ptr_factory_.GetWeakPtr()),
461 base::TimeDelta::FromMilliseconds(kEnforceMemoryPolicyDelayMs));
464 } // namespace content