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/profiler/scoped_tracker.h"
17 #include "base/strings/string_number_conversions.h"
18 #include "base/sys_info.h"
19 #include "base/trace_event/trace_event.h"
24 class DiscardableMemoryImpl
: public base::DiscardableMemory
{
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
),
32 ~DiscardableMemoryImpl() override
{
34 shared_memory_
->Unlock(0, 0);
36 deleted_callback_
.Run();
39 // Overridden from base::DiscardableMemory:
40 bool Lock() override
{
43 if (shared_memory_
->Lock(0, 0) != base::DiscardableSharedMemory::SUCCESS
)
49 void Unlock() override
{
52 shared_memory_
->Unlock(0, 0);
55 void* Memory() const override
{
57 return shared_memory_
->memory();
61 scoped_ptr
<base::DiscardableSharedMemory
> shared_memory_
;
62 const base::Closure deleted_callback_
;
65 DISALLOW_COPY_AND_ASSIGN(DiscardableMemoryImpl
);
68 base::LazyInstance
<HostDiscardableSharedMemoryManager
>
69 g_discardable_shared_memory_manager
= LAZY_INSTANCE_INITIALIZER
;
71 const int64_t kMaxDefaultMemoryLimit
= 512 * 1024 * 1024;
73 const int kEnforceMemoryPolicyDelayMs
= 1000;
75 // Global atomic to generate unique discardable shared memory IDs.
76 base::StaticAtomicSequenceNumber g_next_discardable_shared_memory_id
;
80 HostDiscardableSharedMemoryManager::MemorySegment::MemorySegment(
81 scoped_ptr
<base::DiscardableSharedMemory
> memory
)
82 : memory_(memory
.Pass()) {
85 HostDiscardableSharedMemoryManager::MemorySegment::~MemorySegment() {
88 HostDiscardableSharedMemoryManager::HostDiscardableSharedMemoryManager()
90 // Allow 25% of physical memory to be used for discardable memory.
91 std::min(base::SysInfo::AmountOfPhysicalMemory() / 4,
92 kMaxDefaultMemoryLimit
)),
94 memory_pressure_listener_(new base::MemoryPressureListener(
95 base::Bind(&HostDiscardableSharedMemoryManager::OnMemoryPressure
,
96 base::Unretained(this)))),
97 enforce_memory_policy_pending_(false),
98 weak_ptr_factory_(this) {
99 DCHECK_NE(memory_limit_
, 0u);
102 HostDiscardableSharedMemoryManager::~HostDiscardableSharedMemoryManager() {
105 HostDiscardableSharedMemoryManager
*
106 HostDiscardableSharedMemoryManager::current() {
107 return g_discardable_shared_memory_manager
.Pointer();
110 scoped_ptr
<base::DiscardableMemory
>
111 HostDiscardableSharedMemoryManager::AllocateLockedDiscardableMemory(
113 DiscardableSharedMemoryId new_id
=
114 g_next_discardable_shared_memory_id
.GetNext();
115 base::ProcessHandle current_process_handle
= base::GetCurrentProcessHandle();
117 // Note: Use DiscardableSharedMemoryHeap for in-process allocation
118 // of discardable memory if the cost of each allocation is too high.
119 base::SharedMemoryHandle handle
;
120 AllocateLockedDiscardableSharedMemory(current_process_handle
, size
, new_id
,
122 CHECK(base::SharedMemory::IsHandleValid(handle
));
123 scoped_ptr
<base::DiscardableSharedMemory
> memory(
124 new base::DiscardableSharedMemory(handle
));
125 CHECK(memory
->Map(size
));
126 return make_scoped_ptr(new DiscardableMemoryImpl(
129 &HostDiscardableSharedMemoryManager::DeletedDiscardableSharedMemory
,
130 base::Unretained(this), new_id
, current_process_handle
)));
133 void HostDiscardableSharedMemoryManager::
134 AllocateLockedDiscardableSharedMemoryForChild(
135 base::ProcessHandle process_handle
,
137 DiscardableSharedMemoryId id
,
138 base::SharedMemoryHandle
* shared_memory_handle
) {
139 AllocateLockedDiscardableSharedMemory(process_handle
, size
, id
,
140 shared_memory_handle
);
143 void HostDiscardableSharedMemoryManager::ChildDeletedDiscardableSharedMemory(
144 DiscardableSharedMemoryId id
,
145 base::ProcessHandle process_handle
) {
146 DeletedDiscardableSharedMemory(id
, process_handle
);
149 void HostDiscardableSharedMemoryManager::ProcessRemoved(
150 base::ProcessHandle process_handle
) {
151 base::AutoLock
lock(lock_
);
153 ProcessMap::iterator process_it
= processes_
.find(process_handle
);
154 if (process_it
== processes_
.end())
157 size_t bytes_allocated_before_releasing_memory
= bytes_allocated_
;
159 for (auto& segment_it
: process_it
->second
)
160 ReleaseMemory(segment_it
.second
->memory());
162 processes_
.erase(process_it
);
164 if (bytes_allocated_
!= bytes_allocated_before_releasing_memory
)
165 BytesAllocatedChanged(bytes_allocated_
);
168 void HostDiscardableSharedMemoryManager::SetMemoryLimit(size_t limit
) {
169 base::AutoLock
lock(lock_
);
171 memory_limit_
= limit
;
172 ReduceMemoryUsageUntilWithinMemoryLimit();
175 void HostDiscardableSharedMemoryManager::EnforceMemoryPolicy() {
176 base::AutoLock
lock(lock_
);
178 enforce_memory_policy_pending_
= false;
179 ReduceMemoryUsageUntilWithinMemoryLimit();
182 size_t HostDiscardableSharedMemoryManager::GetBytesAllocated() {
183 base::AutoLock
lock(lock_
);
185 return bytes_allocated_
;
188 void HostDiscardableSharedMemoryManager::AllocateLockedDiscardableSharedMemory(
189 base::ProcessHandle process_handle
,
191 DiscardableSharedMemoryId id
,
192 base::SharedMemoryHandle
* shared_memory_handle
) {
193 // TODO(erikchen): Remove ScopedTracker below once http://crbug.com/466405
195 tracked_objects::ScopedTracker
tracking_profile1(
196 FROM_HERE_WITH_EXPLICIT_FUNCTION(
197 "466405 AllocateLockedDiscardableSharedMemory::Start"));
198 base::AutoLock
lock(lock_
);
200 // Make sure |id| is not already in use.
201 MemorySegmentMap
& process_segments
= processes_
[process_handle
];
202 if (process_segments
.find(id
) != process_segments
.end()) {
203 LOG(ERROR
) << "Invalid discardable shared memory ID";
204 *shared_memory_handle
= base::SharedMemory::NULLHandle();
208 // Memory usage must be reduced to prevent the addition of |size| from
209 // taking usage above the limit. Usage should be reduced to 0 in cases
210 // where |size| is greater than the limit.
212 // Note: the actual mapped size can be larger than requested and cause
213 // |bytes_allocated_| to temporarily be larger than |memory_limit_|. The
214 // error is minimized by incrementing |bytes_allocated_| with the actual
215 // mapped size rather than |size| below.
216 if (size
< memory_limit_
)
217 limit
= memory_limit_
- size
;
219 // TODO(erikchen): Remove ScopedTracker below once http://crbug.com/466405
221 tracked_objects::ScopedTracker
tracking_profile2(
222 FROM_HERE_WITH_EXPLICIT_FUNCTION(
223 "466405 AllocateLockedDiscardableSharedMemory::ReduceMemoryUsage"));
224 if (bytes_allocated_
> limit
)
225 ReduceMemoryUsageUntilWithinLimit(limit
);
227 // TODO(erikchen): Remove ScopedTracker below once http://crbug.com/466405
229 tracked_objects::ScopedTracker
tracking_profile3(
230 FROM_HERE_WITH_EXPLICIT_FUNCTION(
231 "466405 AllocateLockedDiscardableSharedMemory::NewMemory"));
232 scoped_ptr
<base::DiscardableSharedMemory
> memory(
233 new base::DiscardableSharedMemory
);
234 if (!memory
->CreateAndMap(size
)) {
235 *shared_memory_handle
= base::SharedMemory::NULLHandle();
239 // TODO(erikchen): Remove ScopedTracker below once http://crbug.com/466405
241 tracked_objects::ScopedTracker
tracking_profile4(
242 FROM_HERE_WITH_EXPLICIT_FUNCTION(
243 "466405 AllocateLockedDiscardableSharedMemory::ShareToProcess"));
244 if (!memory
->ShareToProcess(process_handle
, shared_memory_handle
)) {
245 LOG(ERROR
) << "Cannot share discardable memory segment";
246 *shared_memory_handle
= base::SharedMemory::NULLHandle();
250 base::CheckedNumeric
<size_t> checked_bytes_allocated
= bytes_allocated_
;
251 checked_bytes_allocated
+= memory
->mapped_size();
252 if (!checked_bytes_allocated
.IsValid()) {
253 *shared_memory_handle
= base::SharedMemory::NULLHandle();
257 // TODO(erikchen): Remove ScopedTracker below once http://crbug.com/466405
259 tracked_objects::ScopedTracker
tracking_profile5(
260 FROM_HERE_WITH_EXPLICIT_FUNCTION(
262 "AllocateLockedDiscardableSharedMemory::BytesAllocatedChanged"));
263 bytes_allocated_
= checked_bytes_allocated
.ValueOrDie();
264 BytesAllocatedChanged(bytes_allocated_
);
266 scoped_refptr
<MemorySegment
> segment(new MemorySegment(memory
.Pass()));
267 process_segments
[id
] = segment
.get();
268 segments_
.push_back(segment
.get());
269 std::push_heap(segments_
.begin(), segments_
.end(), CompareMemoryUsageTime
);
271 // TODO(erikchen): Remove ScopedTracker below once http://crbug.com/466405
273 tracked_objects::ScopedTracker
tracking_profile6(
274 FROM_HERE_WITH_EXPLICIT_FUNCTION(
276 "AllocateLockedDiscardableSharedMemory::"
277 "ScheduleEnforceMemoryPolicy"));
278 if (bytes_allocated_
> memory_limit_
)
279 ScheduleEnforceMemoryPolicy();
282 void HostDiscardableSharedMemoryManager::DeletedDiscardableSharedMemory(
283 DiscardableSharedMemoryId id
,
284 base::ProcessHandle process_handle
) {
285 base::AutoLock
lock(lock_
);
287 MemorySegmentMap
& process_segments
= processes_
[process_handle
];
289 MemorySegmentMap::iterator segment_it
= process_segments
.find(id
);
290 if (segment_it
== process_segments
.end()) {
291 LOG(ERROR
) << "Invalid discardable shared memory ID";
295 size_t bytes_allocated_before_releasing_memory
= bytes_allocated_
;
297 ReleaseMemory(segment_it
->second
->memory());
299 process_segments
.erase(segment_it
);
301 if (bytes_allocated_
!= bytes_allocated_before_releasing_memory
)
302 BytesAllocatedChanged(bytes_allocated_
);
305 void HostDiscardableSharedMemoryManager::OnMemoryPressure(
306 base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level
) {
307 base::AutoLock
lock(lock_
);
309 switch (memory_pressure_level
) {
310 case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE
:
312 case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE
:
313 // Purge memory until usage is within half of |memory_limit_|.
314 ReduceMemoryUsageUntilWithinLimit(memory_limit_
/ 2);
316 case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL
:
317 // Purge everything possible when pressure is critical.
318 ReduceMemoryUsageUntilWithinLimit(0);
324 HostDiscardableSharedMemoryManager::ReduceMemoryUsageUntilWithinMemoryLimit() {
325 lock_
.AssertAcquired();
327 if (bytes_allocated_
<= memory_limit_
)
330 ReduceMemoryUsageUntilWithinLimit(memory_limit_
);
331 if (bytes_allocated_
> memory_limit_
)
332 ScheduleEnforceMemoryPolicy();
335 void HostDiscardableSharedMemoryManager::ReduceMemoryUsageUntilWithinLimit(
337 TRACE_EVENT1("renderer_host",
338 "HostDiscardableSharedMemoryManager::"
339 "ReduceMemoryUsageUntilWithinLimit",
343 // Usage time of currently locked segments are updated to this time and
344 // we stop eviction attempts as soon as we come across a segment that we've
345 // previously tried to evict but was locked.
346 base::Time current_time
= Now();
348 lock_
.AssertAcquired();
349 size_t bytes_allocated_before_purging
= bytes_allocated_
;
350 while (!segments_
.empty()) {
351 if (bytes_allocated_
<= limit
)
354 // Stop eviction attempts when the LRU segment is currently in use.
355 if (segments_
.front()->memory()->last_known_usage() >= current_time
)
358 std::pop_heap(segments_
.begin(), segments_
.end(), CompareMemoryUsageTime
);
359 scoped_refptr
<MemorySegment
> segment
= segments_
.back();
360 segments_
.pop_back();
362 // Attempt to purge LRU segment. When successful, released the memory.
363 if (segment
->memory()->Purge(current_time
)) {
364 ReleaseMemory(segment
->memory());
368 // Add memory segment (with updated usage timestamp) back on heap after
369 // failed attempt to purge it.
370 segments_
.push_back(segment
.get());
371 std::push_heap(segments_
.begin(), segments_
.end(), CompareMemoryUsageTime
);
374 if (bytes_allocated_
!= bytes_allocated_before_purging
)
375 BytesAllocatedChanged(bytes_allocated_
);
378 void HostDiscardableSharedMemoryManager::ReleaseMemory(
379 base::DiscardableSharedMemory
* memory
) {
380 lock_
.AssertAcquired();
382 size_t size
= memory
->mapped_size();
383 DCHECK_GE(bytes_allocated_
, size
);
384 bytes_allocated_
-= size
;
386 #if defined(DISCARDABLE_SHARED_MEMORY_SHRINKING)
387 // Shrink memory segment. This will immediately release the memory to the OS.
391 // This will unmap the memory segment and drop our reference. The result
392 // is that the memory will be released to the OS if the child process is
393 // no longer referencing it.
394 // Note: We intentionally leave the segment in the |segments| vector to
395 // avoid reconstructing the heap. The element will be removed from the heap
396 // when its last usage time is older than all other segments.
400 void HostDiscardableSharedMemoryManager::BytesAllocatedChanged(
401 size_t new_bytes_allocated
) const {
402 TRACE_COUNTER1("renderer_host", "TotalDiscardableMemoryUsage",
403 new_bytes_allocated
);
405 static const char kTotalDiscardableMemoryAllocatedKey
[] =
406 "total-discardable-memory-allocated";
407 base::debug::SetCrashKeyValue(kTotalDiscardableMemoryAllocatedKey
,
408 base::Uint64ToString(new_bytes_allocated
));
411 base::Time
HostDiscardableSharedMemoryManager::Now() const {
412 return base::Time::Now();
415 void HostDiscardableSharedMemoryManager::ScheduleEnforceMemoryPolicy() {
416 lock_
.AssertAcquired();
418 if (enforce_memory_policy_pending_
)
421 enforce_memory_policy_pending_
= true;
422 base::MessageLoop::current()->PostDelayedTask(
424 base::Bind(&HostDiscardableSharedMemoryManager::EnforceMemoryPolicy
,
425 weak_ptr_factory_
.GetWeakPtr()),
426 base::TimeDelta::FromMilliseconds(kEnforceMemoryPolicyDelayMs
));
429 } // namespace content