Ensure low-memory renderers retry failed loads correctly.
[chromium-blink-merge.git] / base / memory / discardable_shared_memory.cc
blob0f85c1fe903d612ecf055043c1a0d84d362ddbf7
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 "base/memory/discardable_shared_memory.h"
7 #if defined(OS_POSIX)
8 #include <unistd.h>
9 #endif
11 #include <algorithm>
13 #include "base/atomicops.h"
14 #include "base/bits.h"
15 #include "base/logging.h"
16 #include "base/numerics/safe_math.h"
17 #include "base/process/process_metrics.h"
19 #if defined(OS_ANDROID)
20 #include "third_party/ashmem/ashmem.h"
21 #endif
23 namespace base {
24 namespace {
26 // Use a machine-sized pointer as atomic type. It will use the Atomic32 or
27 // Atomic64 routines, depending on the architecture.
28 typedef intptr_t AtomicType;
29 typedef uintptr_t UAtomicType;
31 // Template specialization for timestamp serialization/deserialization. This
32 // is used to serialize timestamps using Unix time on systems where AtomicType
33 // does not have enough precision to contain a timestamp in the standard
34 // serialized format.
35 template <int>
36 Time TimeFromWireFormat(int64 value);
37 template <int>
38 int64 TimeToWireFormat(Time time);
40 // Serialize to Unix time when using 4-byte wire format.
41 // Note: 19 January 2038, this will cease to work.
42 template <>
43 Time ALLOW_UNUSED_TYPE TimeFromWireFormat<4>(int64 value) {
44 return value ? Time::UnixEpoch() + TimeDelta::FromSeconds(value) : Time();
46 template <>
47 int64 ALLOW_UNUSED_TYPE TimeToWireFormat<4>(Time time) {
48 return time > Time::UnixEpoch() ? (time - Time::UnixEpoch()).InSeconds() : 0;
51 // Standard serialization format when using 8-byte wire format.
52 template <>
53 Time ALLOW_UNUSED_TYPE TimeFromWireFormat<8>(int64 value) {
54 return Time::FromInternalValue(value);
56 template <>
57 int64 ALLOW_UNUSED_TYPE TimeToWireFormat<8>(Time time) {
58 return time.ToInternalValue();
61 struct SharedState {
62 enum LockState { UNLOCKED = 0, LOCKED = 1 };
64 explicit SharedState(AtomicType ivalue) { value.i = ivalue; }
65 SharedState(LockState lock_state, Time timestamp) {
66 int64 wire_timestamp = TimeToWireFormat<sizeof(AtomicType)>(timestamp);
67 DCHECK_GE(wire_timestamp, 0);
68 DCHECK_EQ(lock_state & ~1, 0);
69 value.u = (static_cast<UAtomicType>(wire_timestamp) << 1) | lock_state;
72 LockState GetLockState() const { return static_cast<LockState>(value.u & 1); }
74 Time GetTimestamp() const {
75 return TimeFromWireFormat<sizeof(AtomicType)>(value.u >> 1);
78 // Bit 1: Lock state. Bit is set when locked.
79 // Bit 2..sizeof(AtomicType)*8: Usage timestamp. NULL time when locked or
80 // purged.
81 union {
82 AtomicType i;
83 UAtomicType u;
84 } value;
87 // Shared state is stored at offset 0 in shared memory segments.
88 SharedState* SharedStateFromSharedMemory(const SharedMemory& shared_memory) {
89 DCHECK(shared_memory.memory());
90 return static_cast<SharedState*>(shared_memory.memory());
93 // Round up |size| to a multiple of page size.
94 size_t AlignToPageSize(size_t size) {
95 return bits::Align(size, base::GetPageSize());
98 } // namespace
100 DiscardableSharedMemory::DiscardableSharedMemory()
101 : mapped_size_(0), locked_page_count_(0) {
104 DiscardableSharedMemory::DiscardableSharedMemory(
105 SharedMemoryHandle shared_memory_handle)
106 : shared_memory_(shared_memory_handle, false),
107 mapped_size_(0),
108 locked_page_count_(0) {
111 DiscardableSharedMemory::~DiscardableSharedMemory() {
114 bool DiscardableSharedMemory::CreateAndMap(size_t size) {
115 CheckedNumeric<size_t> checked_size = size;
116 checked_size += AlignToPageSize(sizeof(SharedState));
117 if (!checked_size.IsValid())
118 return false;
120 if (!shared_memory_.CreateAndMapAnonymous(checked_size.ValueOrDie()))
121 return false;
123 mapped_size_ =
124 shared_memory_.mapped_size() - AlignToPageSize(sizeof(SharedState));
126 locked_page_count_ = AlignToPageSize(mapped_size_) / base::GetPageSize();
127 #if DCHECK_IS_ON()
128 for (size_t page = 0; page < locked_page_count_; ++page)
129 locked_pages_.insert(page);
130 #endif
132 DCHECK(last_known_usage_.is_null());
133 SharedState new_state(SharedState::LOCKED, Time());
134 subtle::Release_Store(&SharedStateFromSharedMemory(shared_memory_)->value.i,
135 new_state.value.i);
136 return true;
139 bool DiscardableSharedMemory::Map(size_t size) {
140 if (!shared_memory_.Map(AlignToPageSize(sizeof(SharedState)) + size))
141 return false;
143 mapped_size_ =
144 shared_memory_.mapped_size() - AlignToPageSize(sizeof(SharedState));
146 locked_page_count_ = AlignToPageSize(mapped_size_) / base::GetPageSize();
147 #if DCHECK_IS_ON()
148 for (size_t page = 0; page < locked_page_count_; ++page)
149 locked_pages_.insert(page);
150 #endif
152 return true;
155 bool DiscardableSharedMemory::Unmap() {
156 if (!shared_memory_.Unmap())
157 return false;
159 mapped_size_ = 0;
160 return true;
163 DiscardableSharedMemory::LockResult DiscardableSharedMemory::Lock(
164 size_t offset, size_t length) {
165 DCHECK_EQ(AlignToPageSize(offset), offset);
166 DCHECK_EQ(AlignToPageSize(length), length);
168 // Calls to this function must be synchronized properly.
169 DFAKE_SCOPED_LOCK(thread_collision_warner_);
171 DCHECK(shared_memory_.memory());
173 // We need to successfully acquire the platform independent lock before
174 // individual pages can be locked.
175 if (!locked_page_count_) {
176 // Return false when instance has been purged or not initialized properly
177 // by checking if |last_known_usage_| is NULL.
178 if (last_known_usage_.is_null())
179 return FAILED;
181 SharedState old_state(SharedState::UNLOCKED, last_known_usage_);
182 SharedState new_state(SharedState::LOCKED, Time());
183 SharedState result(subtle::Acquire_CompareAndSwap(
184 &SharedStateFromSharedMemory(shared_memory_)->value.i,
185 old_state.value.i,
186 new_state.value.i));
187 if (result.value.u != old_state.value.u) {
188 // Update |last_known_usage_| in case the above CAS failed because of
189 // an incorrect timestamp.
190 last_known_usage_ = result.GetTimestamp();
191 return FAILED;
195 // Zero for length means "everything onward".
196 if (!length)
197 length = AlignToPageSize(mapped_size_) - offset;
199 size_t start = offset / base::GetPageSize();
200 size_t end = start + length / base::GetPageSize();
201 DCHECK_LT(start, end);
202 DCHECK_LE(end, AlignToPageSize(mapped_size_) / base::GetPageSize());
204 // Add pages to |locked_page_count_|.
205 // Note: Locking a page that is already locked is an error.
206 locked_page_count_ += end - start;
207 #if DCHECK_IS_ON()
208 // Detect incorrect usage by keeping track of exactly what pages are locked.
209 for (auto page = start; page < end; ++page) {
210 auto result = locked_pages_.insert(page);
211 DCHECK(result.second);
213 DCHECK_EQ(locked_pages_.size(), locked_page_count_);
214 #endif
216 #if defined(OS_ANDROID)
217 SharedMemoryHandle handle = shared_memory_.handle();
218 if (SharedMemory::IsHandleValid(handle)) {
219 if (ashmem_pin_region(
220 handle.fd, AlignToPageSize(sizeof(SharedState)) + offset, length)) {
221 return PURGED;
224 #endif
226 return SUCCESS;
229 void DiscardableSharedMemory::Unlock(size_t offset, size_t length) {
230 DCHECK_EQ(AlignToPageSize(offset), offset);
231 DCHECK_EQ(AlignToPageSize(length), length);
233 // Calls to this function must be synchronized properly.
234 DFAKE_SCOPED_LOCK(thread_collision_warner_);
236 // Zero for length means "everything onward".
237 if (!length)
238 length = AlignToPageSize(mapped_size_) - offset;
240 DCHECK(shared_memory_.memory());
242 #if defined(OS_ANDROID)
243 SharedMemoryHandle handle = shared_memory_.handle();
244 if (SharedMemory::IsHandleValid(handle)) {
245 if (ashmem_unpin_region(
246 handle.fd, AlignToPageSize(sizeof(SharedState)) + offset, length)) {
247 DPLOG(ERROR) << "ashmem_unpin_region() failed";
250 #endif
252 size_t start = offset / base::GetPageSize();
253 size_t end = start + length / base::GetPageSize();
254 DCHECK_LT(start, end);
255 DCHECK_LE(end, AlignToPageSize(mapped_size_) / base::GetPageSize());
257 // Remove pages from |locked_page_count_|.
258 // Note: Unlocking a page that is not locked is an error.
259 DCHECK_GE(locked_page_count_, end - start);
260 locked_page_count_ -= end - start;
261 #if DCHECK_IS_ON()
262 // Detect incorrect usage by keeping track of exactly what pages are locked.
263 for (auto page = start; page < end; ++page) {
264 auto erased_count = locked_pages_.erase(page);
265 DCHECK_EQ(1u, erased_count);
267 DCHECK_EQ(locked_pages_.size(), locked_page_count_);
268 #endif
270 // Early out and avoid releasing the platform independent lock if some pages
271 // are still locked.
272 if (locked_page_count_)
273 return;
275 Time current_time = Now();
276 DCHECK(!current_time.is_null());
278 SharedState old_state(SharedState::LOCKED, Time());
279 SharedState new_state(SharedState::UNLOCKED, current_time);
280 // Note: timestamp cannot be NULL as that is a unique value used when
281 // locked or purged.
282 DCHECK(!new_state.GetTimestamp().is_null());
283 // Timestamp precision should at least be accurate to the second.
284 DCHECK_EQ((new_state.GetTimestamp() - Time::UnixEpoch()).InSeconds(),
285 (current_time - Time::UnixEpoch()).InSeconds());
286 SharedState result(subtle::Release_CompareAndSwap(
287 &SharedStateFromSharedMemory(shared_memory_)->value.i,
288 old_state.value.i,
289 new_state.value.i));
291 DCHECK_EQ(old_state.value.u, result.value.u);
293 last_known_usage_ = current_time;
296 void* DiscardableSharedMemory::memory() const {
297 return reinterpret_cast<uint8*>(shared_memory_.memory()) +
298 AlignToPageSize(sizeof(SharedState));
301 bool DiscardableSharedMemory::Purge(Time current_time) {
302 // Calls to this function must be synchronized properly.
303 DFAKE_SCOPED_LOCK(thread_collision_warner_);
305 // Early out if not mapped. This can happen if the segment was previously
306 // unmapped using a call to Close().
307 if (!shared_memory_.memory())
308 return true;
310 SharedState old_state(SharedState::UNLOCKED, last_known_usage_);
311 SharedState new_state(SharedState::UNLOCKED, Time());
312 SharedState result(subtle::Acquire_CompareAndSwap(
313 &SharedStateFromSharedMemory(shared_memory_)->value.i,
314 old_state.value.i,
315 new_state.value.i));
317 // Update |last_known_usage_| to |current_time| if the memory is locked. This
318 // allows the caller to determine if purging failed because last known usage
319 // was incorrect or memory was locked. In the second case, the caller should
320 // most likely wait for some amount of time before attempting to purge the
321 // the memory again.
322 if (result.value.u != old_state.value.u) {
323 last_known_usage_ = result.GetLockState() == SharedState::LOCKED
324 ? current_time
325 : result.GetTimestamp();
326 return false;
329 last_known_usage_ = Time();
330 return true;
333 bool DiscardableSharedMemory::IsMemoryResident() const {
334 DCHECK(shared_memory_.memory());
336 SharedState result(subtle::NoBarrier_Load(
337 &SharedStateFromSharedMemory(shared_memory_)->value.i));
339 return result.GetLockState() == SharedState::LOCKED ||
340 !result.GetTimestamp().is_null();
343 void DiscardableSharedMemory::Close() {
344 shared_memory_.Close();
347 #if defined(DISCARDABLE_SHARED_MEMORY_SHRINKING)
348 void DiscardableSharedMemory::Shrink() {
349 #if defined(OS_POSIX)
350 SharedMemoryHandle handle = shared_memory_.handle();
351 if (!SharedMemory::IsHandleValid(handle))
352 return;
354 // Truncate shared memory to size of SharedState.
355 if (HANDLE_EINTR(ftruncate(SharedMemory::GetFdFromSharedMemoryHandle(handle),
356 AlignToPageSize(sizeof(SharedState)))) != 0) {
357 DPLOG(ERROR) << "ftruncate() failed";
358 return;
360 mapped_size_ = 0;
361 #else
362 NOTIMPLEMENTED();
363 #endif
365 #endif
367 Time DiscardableSharedMemory::Now() const {
368 return Time::Now();
371 } // namespace base