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"
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"
31 class DiscardableMemoryImpl
: public base::DiscardableMemory
{
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
),
39 ~DiscardableMemoryImpl() override
{
41 shared_memory_
->Unlock(0, 0);
43 deleted_callback_
.Run();
46 // Overridden from base::DiscardableMemory:
47 bool Lock() override
{
50 if (shared_memory_
->Lock(0, 0) != base::DiscardableSharedMemory::SUCCESS
)
56 void Unlock() override
{
59 shared_memory_
->Unlock(0, 0);
62 void* data() const override
{
64 return shared_memory_
->memory();
67 base::trace_event::MemoryAllocatorDump
* CreateMemoryAllocatorDump(
69 base::trace_event::ProcessMemoryDump
* pmd
) const override
{
70 // The memory could have been purged, but we still create a dump with
71 // mapped_size. So, the size can be inaccurate.
72 base::trace_event::MemoryAllocatorDump
* dump
=
73 pmd
->CreateAllocatorDump(name
);
74 dump
->AddScalar(base::trace_event::MemoryAllocatorDump::kNameSize
,
75 base::trace_event::MemoryAllocatorDump::kUnitsBytes
,
76 shared_memory_
->mapped_size());
81 scoped_ptr
<base::DiscardableSharedMemory
> shared_memory_
;
82 const base::Closure deleted_callback_
;
85 DISALLOW_COPY_AND_ASSIGN(DiscardableMemoryImpl
);
88 base::LazyInstance
<HostDiscardableSharedMemoryManager
>
89 g_discardable_shared_memory_manager
= LAZY_INSTANCE_INITIALIZER
;
91 #if defined(OS_ANDROID)
92 // Limits the number of FDs used to 32, assuming a 4MB allocation size.
93 const int64_t kMaxDefaultMemoryLimit
= 128 * 1024 * 1024;
95 const int64_t kMaxDefaultMemoryLimit
= 512 * 1024 * 1024;
98 const int kEnforceMemoryPolicyDelayMs
= 1000;
100 // Global atomic to generate unique discardable shared memory IDs.
101 base::StaticAtomicSequenceNumber g_next_discardable_shared_memory_id
;
105 HostDiscardableSharedMemoryManager::MemorySegment::MemorySegment(
106 scoped_ptr
<base::DiscardableSharedMemory
> memory
)
107 : memory_(memory
.Pass()) {
110 HostDiscardableSharedMemoryManager::MemorySegment::~MemorySegment() {
113 HostDiscardableSharedMemoryManager::HostDiscardableSharedMemoryManager()
115 // Allow 25% of physical memory to be used for discardable memory.
116 std::min(base::SysInfo::AmountOfPhysicalMemory() / 4,
117 base::SysInfo::IsLowEndDevice()
119 // Use 1/8th of discardable memory on low-end devices.
120 kMaxDefaultMemoryLimit
/ 8
121 : kMaxDefaultMemoryLimit
)),
123 memory_pressure_listener_(new base::MemoryPressureListener(
124 base::Bind(&HostDiscardableSharedMemoryManager::OnMemoryPressure
,
125 base::Unretained(this)))),
126 enforce_memory_policy_pending_(false),
127 weak_ptr_factory_(this) {
128 DCHECK_NE(memory_limit_
, 0u);
129 base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider(
133 HostDiscardableSharedMemoryManager::~HostDiscardableSharedMemoryManager() {
134 base::trace_event::MemoryDumpManager::GetInstance()->UnregisterDumpProvider(
138 HostDiscardableSharedMemoryManager
*
139 HostDiscardableSharedMemoryManager::current() {
140 return g_discardable_shared_memory_manager
.Pointer();
143 scoped_ptr
<base::DiscardableMemory
>
144 HostDiscardableSharedMemoryManager::AllocateLockedDiscardableMemory(
146 DiscardableSharedMemoryId new_id
=
147 g_next_discardable_shared_memory_id
.GetNext();
148 base::ProcessHandle current_process_handle
= base::GetCurrentProcessHandle();
150 // Note: Use DiscardableSharedMemoryHeap for in-process allocation
151 // of discardable memory if the cost of each allocation is too high.
152 base::SharedMemoryHandle handle
;
153 AllocateLockedDiscardableSharedMemory(current_process_handle
,
154 ChildProcessHost::kInvalidUniqueID
,
155 size
, new_id
, &handle
);
156 CHECK(base::SharedMemory::IsHandleValid(handle
));
157 scoped_ptr
<base::DiscardableSharedMemory
> memory(
158 new base::DiscardableSharedMemory(handle
));
159 CHECK(memory
->Map(size
));
160 // Close file descriptor to avoid running out.
162 return make_scoped_ptr(new DiscardableMemoryImpl(
165 &HostDiscardableSharedMemoryManager::DeletedDiscardableSharedMemory
,
166 base::Unretained(this), new_id
, ChildProcessHost::kInvalidUniqueID
)));
169 bool HostDiscardableSharedMemoryManager::OnMemoryDump(
170 const base::trace_event::MemoryDumpArgs
& args
,
171 base::trace_event::ProcessMemoryDump
* pmd
) {
172 base::AutoLock
lock(lock_
);
173 for (const auto& process_entry
: processes_
) {
174 const int child_process_id
= process_entry
.first
;
175 const MemorySegmentMap
& process_segments
= process_entry
.second
;
176 for (const auto& segment_entry
: process_segments
) {
177 const int segment_id
= segment_entry
.first
;
178 const MemorySegment
* segment
= segment_entry
.second
.get();
179 std::string dump_name
= base::StringPrintf(
180 "discardable/process_%x/segment_%d", child_process_id
, segment_id
);
181 base::trace_event::MemoryAllocatorDump
* dump
=
182 pmd
->CreateAllocatorDump(dump_name
);
183 dump
->AddScalar(base::trace_event::MemoryAllocatorDump::kNameSize
,
184 base::trace_event::MemoryAllocatorDump::kUnitsBytes
,
185 segment
->memory()->mapped_size());
187 // Create the cross-process ownership edge. If the child creates a
188 // corresponding dump for the same segment, this will avoid to
189 // double-count them in tracing. If, instead, no other process will emit a
190 // dump with the same guid, the segment will be accounted to the browser.
191 const uint64 child_tracing_process_id
=
192 ChildProcessHostImpl::ChildProcessUniqueIdToTracingProcessId(
194 base::trace_event::MemoryAllocatorDumpGuid shared_segment_guid
=
195 DiscardableSharedMemoryHeap::GetSegmentGUIDForTracing(
196 child_tracing_process_id
, segment_id
);
197 pmd
->CreateSharedGlobalAllocatorDump(shared_segment_guid
);
198 pmd
->AddOwnershipEdge(dump
->guid(), shared_segment_guid
);
204 void HostDiscardableSharedMemoryManager::
205 AllocateLockedDiscardableSharedMemoryForChild(
206 base::ProcessHandle process_handle
,
207 int child_process_id
,
209 DiscardableSharedMemoryId id
,
210 base::SharedMemoryHandle
* shared_memory_handle
) {
211 AllocateLockedDiscardableSharedMemory(process_handle
, child_process_id
, size
,
212 id
, shared_memory_handle
);
215 void HostDiscardableSharedMemoryManager::ChildDeletedDiscardableSharedMemory(
216 DiscardableSharedMemoryId id
,
217 int child_process_id
) {
218 DeletedDiscardableSharedMemory(id
, child_process_id
);
221 void HostDiscardableSharedMemoryManager::ProcessRemoved(int child_process_id
) {
222 base::AutoLock
lock(lock_
);
224 ProcessMap::iterator process_it
= processes_
.find(child_process_id
);
225 if (process_it
== processes_
.end())
228 size_t bytes_allocated_before_releasing_memory
= bytes_allocated_
;
230 for (auto& segment_it
: process_it
->second
)
231 ReleaseMemory(segment_it
.second
->memory());
233 processes_
.erase(process_it
);
235 if (bytes_allocated_
!= bytes_allocated_before_releasing_memory
)
236 BytesAllocatedChanged(bytes_allocated_
);
239 void HostDiscardableSharedMemoryManager::SetMemoryLimit(size_t limit
) {
240 base::AutoLock
lock(lock_
);
242 memory_limit_
= limit
;
243 ReduceMemoryUsageUntilWithinMemoryLimit();
246 void HostDiscardableSharedMemoryManager::EnforceMemoryPolicy() {
247 base::AutoLock
lock(lock_
);
249 enforce_memory_policy_pending_
= false;
250 ReduceMemoryUsageUntilWithinMemoryLimit();
253 size_t HostDiscardableSharedMemoryManager::GetBytesAllocated() {
254 base::AutoLock
lock(lock_
);
256 return bytes_allocated_
;
259 void HostDiscardableSharedMemoryManager::AllocateLockedDiscardableSharedMemory(
260 base::ProcessHandle process_handle
,
261 int client_process_id
,
263 DiscardableSharedMemoryId id
,
264 base::SharedMemoryHandle
* shared_memory_handle
) {
265 base::AutoLock
lock(lock_
);
267 // Make sure |id| is not already in use.
268 MemorySegmentMap
& process_segments
= processes_
[client_process_id
];
269 if (process_segments
.find(id
) != process_segments
.end()) {
270 LOG(ERROR
) << "Invalid discardable shared memory ID";
271 *shared_memory_handle
= base::SharedMemory::NULLHandle();
275 // Memory usage must be reduced to prevent the addition of |size| from
276 // taking usage above the limit. Usage should be reduced to 0 in cases
277 // where |size| is greater than the limit.
279 // Note: the actual mapped size can be larger than requested and cause
280 // |bytes_allocated_| to temporarily be larger than |memory_limit_|. The
281 // error is minimized by incrementing |bytes_allocated_| with the actual
282 // mapped size rather than |size| below.
283 if (size
< memory_limit_
)
284 limit
= memory_limit_
- size
;
286 if (bytes_allocated_
> limit
)
287 ReduceMemoryUsageUntilWithinLimit(limit
);
289 scoped_ptr
<base::DiscardableSharedMemory
> memory(
290 new base::DiscardableSharedMemory
);
291 if (!memory
->CreateAndMap(size
)) {
292 *shared_memory_handle
= base::SharedMemory::NULLHandle();
296 if (!memory
->ShareToProcess(process_handle
, shared_memory_handle
)) {
297 LOG(ERROR
) << "Cannot share discardable memory segment";
298 *shared_memory_handle
= base::SharedMemory::NULLHandle();
302 base::CheckedNumeric
<size_t> checked_bytes_allocated
= bytes_allocated_
;
303 checked_bytes_allocated
+= memory
->mapped_size();
304 if (!checked_bytes_allocated
.IsValid()) {
305 *shared_memory_handle
= base::SharedMemory::NULLHandle();
309 bytes_allocated_
= checked_bytes_allocated
.ValueOrDie();
310 BytesAllocatedChanged(bytes_allocated_
);
312 #if !defined(DISCARDABLE_SHARED_MEMORY_SHRINKING)
313 // Close file descriptor to avoid running out.
317 scoped_refptr
<MemorySegment
> segment(new MemorySegment(memory
.Pass()));
318 process_segments
[id
] = segment
.get();
319 segments_
.push_back(segment
.get());
320 std::push_heap(segments_
.begin(), segments_
.end(), CompareMemoryUsageTime
);
322 if (bytes_allocated_
> memory_limit_
)
323 ScheduleEnforceMemoryPolicy();
326 void HostDiscardableSharedMemoryManager::DeletedDiscardableSharedMemory(
327 DiscardableSharedMemoryId id
,
328 int client_process_id
) {
329 base::AutoLock
lock(lock_
);
331 MemorySegmentMap
& process_segments
= processes_
[client_process_id
];
333 MemorySegmentMap::iterator segment_it
= process_segments
.find(id
);
334 if (segment_it
== process_segments
.end()) {
335 LOG(ERROR
) << "Invalid discardable shared memory ID";
339 size_t bytes_allocated_before_releasing_memory
= bytes_allocated_
;
341 ReleaseMemory(segment_it
->second
->memory());
343 process_segments
.erase(segment_it
);
345 if (bytes_allocated_
!= bytes_allocated_before_releasing_memory
)
346 BytesAllocatedChanged(bytes_allocated_
);
349 void HostDiscardableSharedMemoryManager::OnMemoryPressure(
350 base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level
) {
351 base::AutoLock
lock(lock_
);
353 switch (memory_pressure_level
) {
354 case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE
:
356 case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE
:
357 // Purge memory until usage is within half of |memory_limit_|.
358 ReduceMemoryUsageUntilWithinLimit(memory_limit_
/ 2);
360 case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL
:
361 // Purge everything possible when pressure is critical.
362 ReduceMemoryUsageUntilWithinLimit(0);
368 HostDiscardableSharedMemoryManager::ReduceMemoryUsageUntilWithinMemoryLimit() {
369 lock_
.AssertAcquired();
371 if (bytes_allocated_
<= memory_limit_
)
374 ReduceMemoryUsageUntilWithinLimit(memory_limit_
);
375 if (bytes_allocated_
> memory_limit_
)
376 ScheduleEnforceMemoryPolicy();
379 void HostDiscardableSharedMemoryManager::ReduceMemoryUsageUntilWithinLimit(
381 TRACE_EVENT1("renderer_host",
382 "HostDiscardableSharedMemoryManager::"
383 "ReduceMemoryUsageUntilWithinLimit",
387 // Usage time of currently locked segments are updated to this time and
388 // we stop eviction attempts as soon as we come across a segment that we've
389 // previously tried to evict but was locked.
390 base::Time current_time
= Now();
392 lock_
.AssertAcquired();
393 size_t bytes_allocated_before_purging
= bytes_allocated_
;
394 while (!segments_
.empty()) {
395 if (bytes_allocated_
<= limit
)
398 // Stop eviction attempts when the LRU segment is currently in use.
399 if (segments_
.front()->memory()->last_known_usage() >= current_time
)
402 std::pop_heap(segments_
.begin(), segments_
.end(), CompareMemoryUsageTime
);
403 scoped_refptr
<MemorySegment
> segment
= segments_
.back();
404 segments_
.pop_back();
406 // Attempt to purge LRU segment. When successful, released the memory.
407 if (segment
->memory()->Purge(current_time
)) {
408 #if defined(DISCARDABLE_SHARED_MEMORY_SHRINKING)
409 size_t size
= segment
->memory()->mapped_size();
410 DCHECK_GE(bytes_allocated_
, size
);
411 bytes_allocated_
-= size
;
412 // Shrink memory segment. This will immediately release the memory to
414 segment
->memory()->Shrink();
415 DCHECK_EQ(segment
->memory()->mapped_size(), 0u);
417 ReleaseMemory(segment
->memory());
421 // Add memory segment (with updated usage timestamp) back on heap after
422 // failed attempt to purge it.
423 segments_
.push_back(segment
.get());
424 std::push_heap(segments_
.begin(), segments_
.end(), CompareMemoryUsageTime
);
427 if (bytes_allocated_
!= bytes_allocated_before_purging
)
428 BytesAllocatedChanged(bytes_allocated_
);
431 void HostDiscardableSharedMemoryManager::ReleaseMemory(
432 base::DiscardableSharedMemory
* memory
) {
433 lock_
.AssertAcquired();
435 size_t size
= memory
->mapped_size();
436 DCHECK_GE(bytes_allocated_
, size
);
437 bytes_allocated_
-= size
;
439 // This will unmap the memory segment and drop our reference. The result
440 // is that the memory will be released to the OS if the child process is
441 // no longer referencing it.
442 // Note: We intentionally leave the segment in the |segments| vector to
443 // avoid reconstructing the heap. The element will be removed from the heap
444 // when its last usage time is older than all other segments.
449 void HostDiscardableSharedMemoryManager::BytesAllocatedChanged(
450 size_t new_bytes_allocated
) const {
451 static const char kTotalDiscardableMemoryAllocatedKey
[] =
452 "total-discardable-memory-allocated";
453 base::debug::SetCrashKeyValue(kTotalDiscardableMemoryAllocatedKey
,
454 base::Uint64ToString(new_bytes_allocated
));
457 base::Time
HostDiscardableSharedMemoryManager::Now() const {
458 return base::Time::Now();
461 void HostDiscardableSharedMemoryManager::ScheduleEnforceMemoryPolicy() {
462 lock_
.AssertAcquired();
464 if (enforce_memory_policy_pending_
)
467 enforce_memory_policy_pending_
= true;
468 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
470 base::Bind(&HostDiscardableSharedMemoryManager::EnforceMemoryPolicy
,
471 weak_ptr_factory_
.GetWeakPtr()),
472 base::TimeDelta::FromMilliseconds(kEnforceMemoryPolicyDelayMs
));
475 } // namespace content