Index: base/memory/discardable_shared_memory.cc |
diff --git a/base/memory/discardable_shared_memory.cc b/base/memory/discardable_shared_memory.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..653304806ec98d1a49a4300640720332fc00fba3 |
--- /dev/null |
+++ b/base/memory/discardable_shared_memory.cc |
@@ -0,0 +1,234 @@ |
+// Copyright 2014 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "base/memory/discardable_shared_memory.h" |
+ |
+#if defined(OS_POSIX) |
+#include <unistd.h> |
+#endif |
+ |
+#include <algorithm> |
+ |
+#include "base/atomicops.h" |
+#include "base/logging.h" |
+#include "base/numerics/safe_math.h" |
+ |
+namespace base { |
+namespace { |
+ |
+// Use a machine-sized pointer as atomic type. It will use the Atomic32 or |
+// Atomic64 routines, depending on the architecture. |
+typedef intptr_t AtomicType; |
+typedef uintptr_t UAtomicType; |
+ |
+// Template specialization for timestamp serialization/deserialization. This |
+// is used to serialize timestamps using Unix time on systems where AtomicType |
+// does not have enough precision to contain a timestamp in the standard |
+// serialized format. |
+template <int> |
+Time TimeFromWireFormat(int64 value); |
+template <int> |
+int64 TimeToWireFormat(Time time); |
+ |
+// Serialize to Unix time when using 4-byte wire format. |
+// Note: 19 January 2038, this will cease to work. |
+template <> |
+Time ALLOW_UNUSED_TYPE TimeFromWireFormat<4>(int64 value) { |
+ return value ? Time::UnixEpoch() + TimeDelta::FromSeconds(value) : Time(); |
+} |
+template <> |
+int64 ALLOW_UNUSED_TYPE TimeToWireFormat<4>(Time time) { |
+ return time > Time::UnixEpoch() ? (time - Time::UnixEpoch()).InSeconds() : 0; |
+} |
+ |
+// Standard serialization format when using 8-byte wire format. |
+template <> |
+Time ALLOW_UNUSED_TYPE TimeFromWireFormat<8>(int64 value) { |
+ return Time::FromInternalValue(value); |
+} |
+template <> |
+int64 ALLOW_UNUSED_TYPE TimeToWireFormat<8>(Time time) { |
+ return time.ToInternalValue(); |
+} |
+ |
+struct SharedState { |
+ enum LockState { UNLOCKED = 0, LOCKED = 1 }; |
+ |
+ explicit SharedState(AtomicType ivalue) { value.i = ivalue; } |
+ SharedState(LockState lock_state, Time timestamp) { |
+ int64 wire_timestamp = TimeToWireFormat<sizeof(AtomicType)>(timestamp); |
+ DCHECK_GE(wire_timestamp, 0); |
+ DCHECK((lock_state & ~1) == 0); |
+ value.u = (static_cast<UAtomicType>(wire_timestamp) << 1) | lock_state; |
+ } |
+ |
+ LockState GetLockState() const { return static_cast<LockState>(value.u & 1); } |
+ |
+ Time GetTimestamp() const { |
+ return TimeFromWireFormat<sizeof(AtomicType)>(value.u >> 1); |
+ } |
+ |
+ // Bit 1: Lock state. Bit is set when locked. |
+ // Bit 2..sizeof(AtomicType)*8: Usage timestamp. NULL time when locked or |
+ // purged. |
+ union { |
+ AtomicType i; |
+ UAtomicType u; |
+ } value; |
+}; |
+ |
+// Shared state is stored at offset 0 in shared memory segments. |
+SharedState* SharedStateFromSharedMemory(const SharedMemory& shared_memory) { |
+ DCHECK(shared_memory.memory()); |
+ return static_cast<SharedState*>(shared_memory.memory()); |
+} |
+ |
+} // namespace |
+ |
+DiscardableSharedMemory::DiscardableSharedMemory() { |
+} |
+ |
+DiscardableSharedMemory::DiscardableSharedMemory( |
+ SharedMemoryHandle shared_memory_handle) |
+ : shared_memory_(shared_memory_handle, false) { |
+} |
+ |
+DiscardableSharedMemory::~DiscardableSharedMemory() { |
+} |
+ |
+bool DiscardableSharedMemory::CreateAndMap(size_t size) { |
+ CheckedNumeric<size_t> checked_size = size; |
+ checked_size += sizeof(SharedState); |
+ if (!checked_size.IsValid()) |
+ return false; |
+ |
+ if (!shared_memory_.CreateAndMapAnonymous(checked_size.ValueOrDie())) |
+ return false; |
+ |
+ DCHECK(last_known_usage_.is_null()); |
+ SharedState new_state(SharedState::LOCKED, Time()); |
+ subtle::Release_Store(&SharedStateFromSharedMemory(shared_memory_)->value.i, |
+ new_state.value.i); |
+ return true; |
+} |
+ |
+bool DiscardableSharedMemory::Map(size_t size) { |
+ return shared_memory_.Map(sizeof(SharedState) + size); |
+} |
+ |
+bool DiscardableSharedMemory::Lock() { |
+ DCHECK(shared_memory_.memory()); |
+ |
+ // Return false when instance has been purged or not initialized properly by |
+ // checking if |last_known_usage_| is NULL. |
+ if (last_known_usage_.is_null()) |
+ return false; |
+ |
+ SharedState old_state(SharedState::UNLOCKED, last_known_usage_); |
+ SharedState new_state(SharedState::LOCKED, Time()); |
+ SharedState result(subtle::Acquire_CompareAndSwap( |
+ &SharedStateFromSharedMemory(shared_memory_)->value.i, |
+ old_state.value.i, |
+ new_state.value.i)); |
+ if (result.value.u == old_state.value.u) |
+ return true; |
+ |
+ // Update |last_known_usage_| in case the above CAS failed because of |
+ // an incorrect timestamp. |
+ last_known_usage_ = result.GetTimestamp(); |
+ return false; |
+} |
+ |
+void DiscardableSharedMemory::Unlock() { |
+ DCHECK(shared_memory_.memory()); |
+ |
+ Time current_time = Now(); |
+ DCHECK(!current_time.is_null()); |
+ |
+ SharedState old_state(SharedState::LOCKED, Time()); |
+ SharedState new_state(SharedState::UNLOCKED, current_time); |
+ // Note: timestamp cannot be NULL as that is a unique value used when |
+ // locked or purged. |
+ DCHECK(!new_state.GetTimestamp().is_null()); |
+ // Timestamps precision should at least be accurate to the second. |
+ DCHECK_EQ((new_state.GetTimestamp() - Time::UnixEpoch()).InSeconds(), |
+ (current_time - Time::UnixEpoch()).InSeconds()); |
+ SharedState result(subtle::Release_CompareAndSwap( |
+ &SharedStateFromSharedMemory(shared_memory_)->value.i, |
+ old_state.value.i, |
+ new_state.value.i)); |
+ |
+ DCHECK_EQ(old_state.value.u, result.value.u); |
+ |
+ last_known_usage_ = current_time; |
+} |
+ |
+void* DiscardableSharedMemory::memory() const { |
+ return SharedStateFromSharedMemory(shared_memory_) + 1; |
+} |
+ |
+bool DiscardableSharedMemory::Purge(Time current_time) { |
+ // Early out if not mapped. This can happen if the segment was previously |
+ // unmapped using a call to Close(). |
+ if (!shared_memory_.memory()) |
+ return true; |
+ |
+ SharedState old_state(SharedState::UNLOCKED, last_known_usage_); |
+ SharedState new_state(SharedState::UNLOCKED, Time()); |
+ SharedState result(subtle::Acquire_CompareAndSwap( |
+ &SharedStateFromSharedMemory(shared_memory_)->value.i, |
+ old_state.value.i, |
+ new_state.value.i)); |
+ |
+ // Update |last_known_usage_| to |current_time| if the memory is locked. This |
+ // allows the caller to determine if purging failed because last known usage |
+ // was incorrect or memory was locked. In the second case, the caller should |
+ // most likely wait for some amount of time before attempting to purge the |
+ // the memory again. |
+ if (result.value.u != old_state.value.u) { |
+ last_known_usage_ = result.GetLockState() == SharedState::LOCKED |
+ ? current_time |
+ : result.GetTimestamp(); |
+ return false; |
+ } |
+ |
+ last_known_usage_ = Time(); |
+ return true; |
+} |
+ |
+bool DiscardableSharedMemory::PurgeAndTruncate(Time current_time) { |
+ if (!Purge(current_time)) |
+ return false; |
+ |
+#if defined(OS_POSIX) |
+ // Truncate shared memory to size of SharedState. |
+ SharedMemoryHandle handle = shared_memory_.handle(); |
+ if (SharedMemory::IsHandleValid(handle)) { |
+ if (HANDLE_EINTR(ftruncate(handle.fd, sizeof(SharedState))) != 0) |
+ DPLOG(ERROR) << "ftruncate() failed"; |
+ } |
+#endif |
+ |
+ return true; |
+} |
+ |
+bool DiscardableSharedMemory::IsMemoryResident() const { |
+ DCHECK(shared_memory_.memory()); |
+ |
+ SharedState result(subtle::NoBarrier_Load( |
+ &SharedStateFromSharedMemory(shared_memory_)->value.i)); |
+ |
+ return result.GetLockState() == SharedState::LOCKED || |
+ !result.GetTimestamp().is_null(); |
+} |
+ |
+void DiscardableSharedMemory::Close() { |
+ shared_memory_.Close(); |
+} |
+ |
+Time DiscardableSharedMemory::Now() const { |
+ return Time::Now(); |
+} |
+ |
+} // namespace base |