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/callback.h"
10 #include "base/debug/crash_logging.h"
11 #include "base/debug/trace_event.h"
12 #include "base/lazy_instance.h"
13 #include "base/numerics/safe_math.h"
14 #include "base/strings/string_number_conversions.h"
19 base::LazyInstance
<HostDiscardableSharedMemoryManager
>
20 g_discardable_shared_memory_manager
= LAZY_INSTANCE_INITIALIZER
;
22 const size_t kDefaultMemoryLimit
= 512 * 1024 * 1024;
24 const int kEnforceMemoryPolicyDelayMs
= 1000;
28 HostDiscardableSharedMemoryManager::MemorySegment::MemorySegment(
29 linked_ptr
<base::DiscardableSharedMemory
> memory
,
30 base::ProcessHandle process_handle
)
31 : memory(memory
), process_handle(process_handle
) {
34 HostDiscardableSharedMemoryManager::MemorySegment::~MemorySegment() {
37 HostDiscardableSharedMemoryManager::HostDiscardableSharedMemoryManager()
38 : memory_limit_(kDefaultMemoryLimit
),
40 memory_pressure_listener_(new base::MemoryPressureListener(
41 base::Bind(&HostDiscardableSharedMemoryManager::OnMemoryPressure
,
42 base::Unretained(this)))),
43 enforce_memory_policy_pending_(false),
44 weak_ptr_factory_(this) {
47 HostDiscardableSharedMemoryManager::~HostDiscardableSharedMemoryManager() {
50 HostDiscardableSharedMemoryManager
*
51 HostDiscardableSharedMemoryManager::current() {
52 return g_discardable_shared_memory_manager
.Pointer();
55 scoped_ptr
<base::DiscardableMemoryShmemChunk
>
56 HostDiscardableSharedMemoryManager::AllocateLockedDiscardableMemory(
58 // TODO(reveman): Need to implement this for discardable memory support in
59 // the browser process.
64 void HostDiscardableSharedMemoryManager::
65 AllocateLockedDiscardableSharedMemoryForChild(
66 base::ProcessHandle process_handle
,
68 base::SharedMemoryHandle
* shared_memory_handle
) {
69 base::AutoLock
lock(lock_
);
71 // Memory usage must be reduced to prevent the addition of |size| from
72 // taking usage above the limit. Usage should be reduced to 0 in cases
73 // where |size| is greater than the limit.
75 // Note: the actual mapped size can be larger than requested and cause
76 // |bytes_allocated_| to temporarily be larger than |memory_limit_|. The
77 // error is minimized by incrementing |bytes_allocated_| with the actual
78 // mapped size rather than |size| below.
79 if (size
< memory_limit_
)
80 limit
= memory_limit_
- size
;
82 if (bytes_allocated_
> limit
)
83 ReduceMemoryUsageUntilWithinLimit(limit
);
85 linked_ptr
<base::DiscardableSharedMemory
> memory(
86 new base::DiscardableSharedMemory
);
87 if (!memory
->CreateAndMap(size
)) {
88 *shared_memory_handle
= base::SharedMemory::NULLHandle();
92 if (!memory
->ShareToProcess(process_handle
, shared_memory_handle
)) {
93 LOG(ERROR
) << "Cannot share discardable memory segment";
94 *shared_memory_handle
= base::SharedMemory::NULLHandle();
98 base::CheckedNumeric
<size_t> checked_bytes_allocated
= bytes_allocated_
;
99 checked_bytes_allocated
+= memory
->mapped_size();
100 if (!checked_bytes_allocated
.IsValid()) {
101 *shared_memory_handle
= base::SharedMemory::NULLHandle();
105 bytes_allocated_
= checked_bytes_allocated
.ValueOrDie();
106 BytesAllocatedChanged(bytes_allocated_
);
108 segments_
.push_back(MemorySegment(memory
, process_handle
));
109 std::push_heap(segments_
.begin(), segments_
.end(), CompareMemoryUsageTime
);
111 if (bytes_allocated_
> memory_limit_
)
112 ScheduleEnforceMemoryPolicy();
115 void HostDiscardableSharedMemoryManager::ProcessRemoved(
116 base::ProcessHandle process_handle
) {
117 base::AutoLock
lock(lock_
);
119 size_t bytes_allocated_before_purging
= bytes_allocated_
;
120 for (auto& segment
: segments_
) {
121 // Skip segments that belong to a different process.
122 if (segment
.process_handle
!= process_handle
)
125 size_t size
= segment
.memory
->mapped_size();
126 DCHECK_GE(bytes_allocated_
, size
);
128 // This will unmap the memory segment and drop our reference. The result
129 // is that the memory will be released to the OS if the child process is
130 // no longer referencing it.
131 // Note: We intentionally leave the segment in the vector to avoid
132 // reconstructing the heap. The element will be removed from the heap
133 // when its last usage time is older than all other segments.
134 segment
.memory
->Close();
135 bytes_allocated_
-= size
;
138 if (bytes_allocated_
!= bytes_allocated_before_purging
)
139 BytesAllocatedChanged(bytes_allocated_
);
142 void HostDiscardableSharedMemoryManager::SetMemoryLimit(size_t limit
) {
143 base::AutoLock
lock(lock_
);
145 memory_limit_
= limit
;
146 ReduceMemoryUsageUntilWithinMemoryLimit();
149 void HostDiscardableSharedMemoryManager::EnforceMemoryPolicy() {
150 base::AutoLock
lock(lock_
);
152 enforce_memory_policy_pending_
= false;
153 ReduceMemoryUsageUntilWithinMemoryLimit();
156 void HostDiscardableSharedMemoryManager::OnMemoryPressure(
157 base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level
) {
158 base::AutoLock
lock(lock_
);
160 switch (memory_pressure_level
) {
161 case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE
:
162 case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE
:
164 case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL
:
165 // Purge everything possible when pressure is critical.
166 ReduceMemoryUsageUntilWithinLimit(0);
172 HostDiscardableSharedMemoryManager::ReduceMemoryUsageUntilWithinMemoryLimit() {
173 if (bytes_allocated_
<= memory_limit_
)
176 ReduceMemoryUsageUntilWithinLimit(memory_limit_
);
177 if (bytes_allocated_
> memory_limit_
)
178 ScheduleEnforceMemoryPolicy();
181 void HostDiscardableSharedMemoryManager::ReduceMemoryUsageUntilWithinLimit(
183 TRACE_EVENT1("renderer_host",
184 "HostDiscardableSharedMemoryManager::"
185 "ReduceMemoryUsageUntilWithinLimit",
189 // Usage time of currently locked segments are updated to this time and
190 // we stop eviction attempts as soon as we come across a segment that we've
191 // previously tried to evict but was locked.
192 base::Time current_time
= Now();
194 lock_
.AssertAcquired();
195 size_t bytes_allocated_before_purging
= bytes_allocated_
;
196 while (!segments_
.empty()) {
197 if (bytes_allocated_
<= limit
)
200 // Stop eviction attempts when the LRU segment is currently in use.
201 if (segments_
.front().memory
->last_known_usage() >= current_time
)
204 std::pop_heap(segments_
.begin(), segments_
.end(), CompareMemoryUsageTime
);
205 MemorySegment segment
= segments_
.back();
206 segments_
.pop_back();
208 // Attempt to purge and truncate LRU segment. When successful, as much
209 // memory as possible will be released to the OS. How much memory is
210 // released depends on the platform. The child process should perform
211 // periodic cleanup to ensure that all memory is release within a
212 // reasonable amount of time.
213 if (segment
.memory
->PurgeAndTruncate(current_time
)) {
214 size_t size
= segment
.memory
->mapped_size();
215 DCHECK_GE(bytes_allocated_
, size
);
216 bytes_allocated_
-= size
;
220 // Add memory segment (with updated usage timestamp) back on heap after
221 // failed attempt to purge it.
222 segments_
.push_back(segment
);
223 std::push_heap(segments_
.begin(), segments_
.end(), CompareMemoryUsageTime
);
226 if (bytes_allocated_
!= bytes_allocated_before_purging
)
227 BytesAllocatedChanged(bytes_allocated_
);
230 void HostDiscardableSharedMemoryManager::BytesAllocatedChanged(
231 size_t new_bytes_allocated
) const {
233 "base", "TotalDiscardableMemoryUsage", this, new_bytes_allocated
);
235 static const char kTotalDiscardableMemoryUsageKey
[] = "total-dm-usage";
236 base::debug::SetCrashKeyValue(kTotalDiscardableMemoryUsageKey
,
237 base::Uint64ToString(new_bytes_allocated
));
240 base::Time
HostDiscardableSharedMemoryManager::Now() const {
241 return base::Time::Now();
244 void HostDiscardableSharedMemoryManager::ScheduleEnforceMemoryPolicy() {
245 if (enforce_memory_policy_pending_
)
248 enforce_memory_policy_pending_
= true;
249 base::MessageLoop::current()->PostDelayedTask(
251 base::Bind(&HostDiscardableSharedMemoryManager::EnforceMemoryPolicy
,
252 weak_ptr_factory_
.GetWeakPtr()),
253 base::TimeDelta::FromMilliseconds(kEnforceMemoryPolicyDelayMs
));
256 } // namespace content