summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorreveman <reveman@chromium.org>2014-10-24 17:34:39 -0700
committerCommit bot <commit-bot@chromium.org>2014-10-25 00:35:28 +0000
commitcb5a66afdf410c55c7a743978038cedccca1638c (patch)
tree0e5271d79da2d97053563e4537850439efacf8b3
parentde59287924cb5cffe784ab40ff7793cae39b1ef4 (diff)
downloadchromium_src-cb5a66afdf410c55c7a743978038cedccca1638c.zip
chromium_src-cb5a66afdf410c55c7a743978038cedccca1638c.tar.gz
chromium_src-cb5a66afdf410c55c7a743978038cedccca1638c.tar.bz2
Re-land: Add browser-wide discardable memory implementation.
This provides a platform agnostic implementation of discardable memory based on shared memory. Managment of discardable memory is moved to the browser process instead of being local to each renderer. This provides much better control over chromium's total discardable memory usage. A round-trip to the browser IO thread is necessary for a renderer to allocate a new discardable memory segment but the cost of this can be reduced by maintaining free lists on the renderer side and have discardable memory instances share the same discardable memory segment. The implementation of this optimization is left as a follow up. Note: while the general implementation is platform agnostic, it takes advantage of the ftruncate syscall available on Posix platforms. This syscall makes it possible to release discardable memory segments immediately to the OS from the browser process. BUG=381178,400423,422953 TEST=base_unittests --gtest_filter=DiscardableMemoryTests*, base_unittests --gtest_filter=DiscardableSharedMemory.*, content_unittests --gtest_filter=HostDiscardableSharedMemoryManagerTest.* Review URL: https://codereview.chromium.org/531343002 Cr-Commit-Position: refs/heads/master@{#301247}
-rw-r--r--base/BUILD.gn7
-rw-r--r--base/base.gyp1
-rw-r--r--base/base.gypi6
-rw-r--r--base/memory/discardable_memory.cc3
-rw-r--r--base/memory/discardable_memory.h7
-rw-r--r--base/memory/discardable_memory_android.cc18
-rw-r--r--base/memory/discardable_memory_ashmem.cc4
-rw-r--r--base/memory/discardable_memory_ashmem.h1
-rw-r--r--base/memory/discardable_memory_emulated.cc4
-rw-r--r--base/memory/discardable_memory_emulated.h1
-rw-r--r--base/memory/discardable_memory_linux.cc18
-rw-r--r--base/memory/discardable_memory_mac.cc18
-rw-r--r--base/memory/discardable_memory_mach.cc4
-rw-r--r--base/memory/discardable_memory_mach.h1
-rw-r--r--base/memory/discardable_memory_manager.cc28
-rw-r--r--base/memory/discardable_memory_manager.h7
-rw-r--r--base/memory/discardable_memory_manager_unittest.cc4
-rw-r--r--base/memory/discardable_memory_shmem.cc121
-rw-r--r--base/memory/discardable_memory_shmem.h52
-rw-r--r--base/memory/discardable_memory_shmem_allocator.cc58
-rw-r--r--base/memory/discardable_memory_shmem_allocator.h32
-rw-r--r--base/memory/discardable_memory_win.cc18
-rw-r--r--base/memory/discardable_shared_memory.cc234
-rw-r--r--base/memory/discardable_shared_memory.h109
-rw-r--r--base/memory/discardable_shared_memory_unittest.cc251
-rw-r--r--chrome/common/crash_keys.cc1
-rw-r--r--content/browser/renderer_host/render_message_filter.cc13
-rw-r--r--content/browser/renderer_host/render_message_filter.h5
-rw-r--r--content/child/child_discardable_shared_memory_manager.cc40
-rw-r--r--content/child/child_discardable_shared_memory_manager.h34
-rw-r--r--content/child/child_thread.cc4
-rw-r--r--content/child/child_thread.h9
-rw-r--r--content/common/child_process_messages.h7
-rw-r--r--content/common/host_discardable_shared_memory_manager.cc255
-rw-r--r--content/common/host_discardable_shared_memory_manager.h100
-rw-r--r--content/common/host_discardable_shared_memory_manager_unittest.cc188
-rw-r--r--content/content_child.gypi6
-rw-r--r--content/content_common.gypi2
-rw-r--r--content/content_tests.gypi1
-rw-r--r--content/renderer/render_thread_impl.cc6
40 files changed, 1670 insertions, 8 deletions
diff --git a/base/BUILD.gn b/base/BUILD.gn
index 556a09a..d94b440 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -329,7 +329,13 @@ component("base") {
"memory/discardable_memory_mac.cc",
"memory/discardable_memory_manager.cc",
"memory/discardable_memory_manager.h",
+ "memory/discardable_memory_shmem.cc",
+ "memory/discardable_memory_shmem.h",
+ "memory/discardable_memory_shmem_allocator.cc",
+ "memory/discardable_memory_shmem_allocator.h",
"memory/discardable_memory_win.cc",
+ "memory/discardable_shared_memory.cc",
+ "memory/discardable_shared_memory.h",
"memory/linked_ptr.h",
"memory/manual_constructor.h",
"memory/memory_pressure_listener.cc",
@@ -1185,6 +1191,7 @@ test("base_unittests") {
"memory/aligned_memory_unittest.cc",
"memory/discardable_memory_manager_unittest.cc",
"memory/discardable_memory_unittest.cc",
+ "memory/discardable_shared_memory_unittest.cc",
"memory/linked_ptr_unittest.cc",
"memory/ref_counted_memory_unittest.cc",
"memory/ref_counted_unittest.cc",
diff --git a/base/base.gyp b/base/base.gyp
index 1f801c8..beb8995 100644
--- a/base/base.gyp
+++ b/base/base.gyp
@@ -522,6 +522,7 @@
'memory/aligned_memory_unittest.cc',
'memory/discardable_memory_manager_unittest.cc',
'memory/discardable_memory_unittest.cc',
+ 'memory/discardable_shared_memory_unittest.cc',
'memory/linked_ptr_unittest.cc',
'memory/ref_counted_memory_unittest.cc',
'memory/ref_counted_unittest.cc',
diff --git a/base/base.gypi b/base/base.gypi
index 2dfde09..9542529 100644
--- a/base/base.gypi
+++ b/base/base.gypi
@@ -332,7 +332,13 @@
'memory/discardable_memory_mac.cc',
'memory/discardable_memory_manager.cc',
'memory/discardable_memory_manager.h',
+ 'memory/discardable_memory_shmem.cc',
+ 'memory/discardable_memory_shmem.h',
+ 'memory/discardable_memory_shmem_allocator.cc',
+ 'memory/discardable_memory_shmem_allocator.h',
'memory/discardable_memory_win.cc',
+ 'memory/discardable_shared_memory.cc',
+ 'memory/discardable_shared_memory.h',
'memory/linked_ptr.h',
'memory/manual_constructor.h',
'memory/memory_pressure_listener.cc',
diff --git a/base/memory/discardable_memory.cc b/base/memory/discardable_memory.cc
index 3ecc5f7..3d82f91 100644
--- a/base/memory/discardable_memory.cc
+++ b/base/memory/discardable_memory.cc
@@ -16,7 +16,8 @@ const struct TypeNamePair {
} kTypeNamePairs[] = {
{ DISCARDABLE_MEMORY_TYPE_ASHMEM, "ashmem" },
{ DISCARDABLE_MEMORY_TYPE_MACH, "mach" },
- { DISCARDABLE_MEMORY_TYPE_EMULATED, "emulated" }
+ { DISCARDABLE_MEMORY_TYPE_EMULATED, "emulated" },
+ { DISCARDABLE_MEMORY_TYPE_SHMEM, "shmem" }
};
DiscardableMemoryType g_preferred_type = DISCARDABLE_MEMORY_TYPE_NONE;
diff --git a/base/memory/discardable_memory.h b/base/memory/discardable_memory.h
index 5f83e33..d8b7a58 100644
--- a/base/memory/discardable_memory.h
+++ b/base/memory/discardable_memory.h
@@ -19,7 +19,8 @@ enum DiscardableMemoryType {
DISCARDABLE_MEMORY_TYPE_NONE,
DISCARDABLE_MEMORY_TYPE_ASHMEM,
DISCARDABLE_MEMORY_TYPE_MACH,
- DISCARDABLE_MEMORY_TYPE_EMULATED
+ DISCARDABLE_MEMORY_TYPE_EMULATED,
+ DISCARDABLE_MEMORY_TYPE_SHMEM
};
enum DiscardableMemoryLockStatus {
@@ -88,6 +89,10 @@ class BASE_EXPORT DiscardableMemory {
// Create a DiscardableMemory instance with preferred type and |size|.
static scoped_ptr<DiscardableMemory> CreateLockedMemory(size_t size);
+ // Discardable memory implementations might use this to release memory
+ // or resources assigned to instances that have been purged.
+ static void ReleaseFreeMemory();
+
// Discardable memory implementations might allow an elevated usage level
// while in frequent use. Call this to have the usage reduced to the base
// level. Returns true if there's no need to call this again until
diff --git a/base/memory/discardable_memory_android.cc b/base/memory/discardable_memory_android.cc
index 0c9f3fc..de71124 100644
--- a/base/memory/discardable_memory_android.cc
+++ b/base/memory/discardable_memory_android.cc
@@ -11,6 +11,7 @@
#include "base/memory/discardable_memory_ashmem.h"
#include "base/memory/discardable_memory_ashmem_allocator.h"
#include "base/memory/discardable_memory_emulated.h"
+#include "base/memory/discardable_memory_shmem.h"
#include "base/sys_info.h"
namespace base {
@@ -43,6 +44,11 @@ LazyInstance<SharedState>::Leaky g_shared_state = LAZY_INSTANCE_INITIALIZER;
} // namespace
// static
+void DiscardableMemory::ReleaseFreeMemory() {
+ internal::DiscardableMemoryShmem::ReleaseFreeMemory();
+}
+
+// static
bool DiscardableMemory::ReduceMemoryUsage() {
return internal::DiscardableMemoryEmulated::ReduceMemoryUsage();
}
@@ -52,7 +58,8 @@ void DiscardableMemory::GetSupportedTypes(
std::vector<DiscardableMemoryType>* types) {
const DiscardableMemoryType supported_types[] = {
DISCARDABLE_MEMORY_TYPE_ASHMEM,
- DISCARDABLE_MEMORY_TYPE_EMULATED
+ DISCARDABLE_MEMORY_TYPE_EMULATED,
+ DISCARDABLE_MEMORY_TYPE_SHMEM
};
types->assign(supported_types, supported_types + arraysize(supported_types));
}
@@ -79,6 +86,14 @@ scoped_ptr<DiscardableMemory> DiscardableMemory::CreateLockedMemoryWithType(
return memory.Pass();
}
+ case DISCARDABLE_MEMORY_TYPE_SHMEM: {
+ scoped_ptr<internal::DiscardableMemoryShmem> memory(
+ new internal::DiscardableMemoryShmem(size));
+ if (!memory->Initialize())
+ return nullptr;
+
+ return memory.Pass();
+ }
case DISCARDABLE_MEMORY_TYPE_NONE:
case DISCARDABLE_MEMORY_TYPE_MACH:
NOTREACHED();
@@ -93,6 +108,7 @@ scoped_ptr<DiscardableMemory> DiscardableMemory::CreateLockedMemoryWithType(
void DiscardableMemory::PurgeForTesting() {
g_shared_state.Pointer()->manager.PurgeAll();
internal::DiscardableMemoryEmulated::PurgeForTesting();
+ internal::DiscardableMemoryShmem::PurgeForTesting();
}
} // namespace base
diff --git a/base/memory/discardable_memory_ashmem.cc b/base/memory/discardable_memory_ashmem.cc
index a590e53..df0697c 100644
--- a/base/memory/discardable_memory_ashmem.cc
+++ b/base/memory/discardable_memory_ashmem.cc
@@ -71,5 +71,9 @@ void DiscardableMemoryAshmem::Purge() {
ashmem_chunk_.reset();
}
+bool DiscardableMemoryAshmem::IsMemoryResident() const {
+ return true;
+}
+
} // namespace internal
} // namespace base
diff --git a/base/memory/discardable_memory_ashmem.h b/base/memory/discardable_memory_ashmem.h
index 392f290..c1cc077 100644
--- a/base/memory/discardable_memory_ashmem.h
+++ b/base/memory/discardable_memory_ashmem.h
@@ -38,6 +38,7 @@ class DiscardableMemoryAshmem
virtual bool AllocateAndAcquireLock() override;
virtual void ReleaseLock() override;
virtual void Purge() override;
+ virtual bool IsMemoryResident() const override;
private:
const size_t bytes_;
diff --git a/base/memory/discardable_memory_emulated.cc b/base/memory/discardable_memory_emulated.cc
index 9820e2e..4303400 100644
--- a/base/memory/discardable_memory_emulated.cc
+++ b/base/memory/discardable_memory_emulated.cc
@@ -113,5 +113,9 @@ void DiscardableMemoryEmulated::Purge() {
memory_.reset();
}
+bool DiscardableMemoryEmulated::IsMemoryResident() const {
+ return true;
+}
+
} // namespace internal
} // namespace base
diff --git a/base/memory/discardable_memory_emulated.h b/base/memory/discardable_memory_emulated.h
index 0dc15e3..150c1ab 100644
--- a/base/memory/discardable_memory_emulated.h
+++ b/base/memory/discardable_memory_emulated.h
@@ -39,6 +39,7 @@ class DiscardableMemoryEmulated
bool AllocateAndAcquireLock() override;
void ReleaseLock() override {}
void Purge() override;
+ bool IsMemoryResident() const override;
private:
const size_t bytes_;
diff --git a/base/memory/discardable_memory_linux.cc b/base/memory/discardable_memory_linux.cc
index 6a9a28df..9b4e940 100644
--- a/base/memory/discardable_memory_linux.cc
+++ b/base/memory/discardable_memory_linux.cc
@@ -6,10 +6,16 @@
#include "base/logging.h"
#include "base/memory/discardable_memory_emulated.h"
+#include "base/memory/discardable_memory_shmem.h"
namespace base {
// static
+void DiscardableMemory::ReleaseFreeMemory() {
+ internal::DiscardableMemoryShmem::ReleaseFreeMemory();
+}
+
+// static
bool DiscardableMemory::ReduceMemoryUsage() {
return internal::DiscardableMemoryEmulated::ReduceMemoryUsage();
}
@@ -18,7 +24,8 @@ bool DiscardableMemory::ReduceMemoryUsage() {
void DiscardableMemory::GetSupportedTypes(
std::vector<DiscardableMemoryType>* types) {
const DiscardableMemoryType supported_types[] = {
- DISCARDABLE_MEMORY_TYPE_EMULATED
+ DISCARDABLE_MEMORY_TYPE_EMULATED,
+ DISCARDABLE_MEMORY_TYPE_SHMEM
};
types->assign(supported_types, supported_types + arraysize(supported_types));
}
@@ -35,6 +42,14 @@ scoped_ptr<DiscardableMemory> DiscardableMemory::CreateLockedMemoryWithType(
return memory.Pass();
}
+ case DISCARDABLE_MEMORY_TYPE_SHMEM: {
+ scoped_ptr<internal::DiscardableMemoryShmem> memory(
+ new internal::DiscardableMemoryShmem(size));
+ if (!memory->Initialize())
+ return nullptr;
+
+ return memory.Pass();
+ }
case DISCARDABLE_MEMORY_TYPE_NONE:
case DISCARDABLE_MEMORY_TYPE_ASHMEM:
case DISCARDABLE_MEMORY_TYPE_MACH:
@@ -49,6 +64,7 @@ scoped_ptr<DiscardableMemory> DiscardableMemory::CreateLockedMemoryWithType(
// static
void DiscardableMemory::PurgeForTesting() {
internal::DiscardableMemoryEmulated::PurgeForTesting();
+ internal::DiscardableMemoryShmem::PurgeForTesting();
}
} // namespace base
diff --git a/base/memory/discardable_memory_mac.cc b/base/memory/discardable_memory_mac.cc
index 6896e5a7..18cf80a 100644
--- a/base/memory/discardable_memory_mac.cc
+++ b/base/memory/discardable_memory_mac.cc
@@ -8,11 +8,17 @@
#include "base/memory/discardable_memory_emulated.h"
#include "base/memory/discardable_memory_mach.h"
#include "base/memory/discardable_memory_manager.h"
+#include "base/memory/discardable_memory_shmem.h"
#include "base/memory/scoped_ptr.h"
namespace base {
// static
+void DiscardableMemory::ReleaseFreeMemory() {
+ internal::DiscardableMemoryShmem::ReleaseFreeMemory();
+}
+
+// static
bool DiscardableMemory::ReduceMemoryUsage() {
return internal::DiscardableMemoryEmulated::ReduceMemoryUsage();
}
@@ -22,7 +28,8 @@ void DiscardableMemory::GetSupportedTypes(
std::vector<DiscardableMemoryType>* types) {
const DiscardableMemoryType supported_types[] = {
DISCARDABLE_MEMORY_TYPE_MACH,
- DISCARDABLE_MEMORY_TYPE_EMULATED
+ DISCARDABLE_MEMORY_TYPE_EMULATED,
+ DISCARDABLE_MEMORY_TYPE_SHMEM
};
types->assign(supported_types, supported_types + arraysize(supported_types));
}
@@ -47,6 +54,14 @@ scoped_ptr<DiscardableMemory> DiscardableMemory::CreateLockedMemoryWithType(
return memory.Pass();
}
+ case DISCARDABLE_MEMORY_TYPE_SHMEM: {
+ scoped_ptr<internal::DiscardableMemoryShmem> memory(
+ new internal::DiscardableMemoryShmem(size));
+ if (!memory->Initialize())
+ return nullptr;
+
+ return memory.Pass();
+ }
case DISCARDABLE_MEMORY_TYPE_NONE:
case DISCARDABLE_MEMORY_TYPE_ASHMEM:
NOTREACHED();
@@ -61,6 +76,7 @@ scoped_ptr<DiscardableMemory> DiscardableMemory::CreateLockedMemoryWithType(
void DiscardableMemory::PurgeForTesting() {
internal::DiscardableMemoryMach::PurgeForTesting();
internal::DiscardableMemoryEmulated::PurgeForTesting();
+ internal::DiscardableMemoryShmem::PurgeForTesting();
}
} // namespace base
diff --git a/base/memory/discardable_memory_mach.cc b/base/memory/discardable_memory_mach.cc
index c6681b1..5fc43f2 100644
--- a/base/memory/discardable_memory_mach.cc
+++ b/base/memory/discardable_memory_mach.cc
@@ -154,5 +154,9 @@ void DiscardableMemoryMach::Purge() {
memory_.reset();
}
+bool DiscardableMemoryMach::IsMemoryResident() const {
+ return true;
+}
+
} // namespace internal
} // namespace base
diff --git a/base/memory/discardable_memory_mach.h b/base/memory/discardable_memory_mach.h
index 4da13d3f..af2191f 100644
--- a/base/memory/discardable_memory_mach.h
+++ b/base/memory/discardable_memory_mach.h
@@ -33,6 +33,7 @@ class DiscardableMemoryMach
bool AllocateAndAcquireLock() override;
void ReleaseLock() override;
void Purge() override;
+ bool IsMemoryResident() const override;
private:
mac::ScopedMachVM memory_;
diff --git a/base/memory/discardable_memory_manager.cc b/base/memory/discardable_memory_manager.cc
index 5f5e604..faf583b 100644
--- a/base/memory/discardable_memory_manager.cc
+++ b/base/memory/discardable_memory_manager.cc
@@ -51,6 +51,34 @@ void DiscardableMemoryManager::SetHardMemoryLimitExpirationTime(
hard_memory_limit_expiration_time_ = hard_memory_limit_expiration_time;
}
+void DiscardableMemoryManager::ReleaseFreeMemory() {
+ TRACE_EVENT0("base", "DiscardableMemoryManager::ReleaseFreeMemory");
+
+ AutoLock lock(lock_);
+ size_t bytes_allocated_before_releasing_memory = bytes_allocated_;
+ for (auto& entry : allocations_) {
+ Allocation* allocation = entry.first;
+ AllocationInfo* info = &entry.second;
+
+ if (!info->purgable)
+ continue;
+
+ // Skip if memory is still resident, otherwise purge and adjust
+ // |bytes_allocated_|.
+ if (allocation->IsMemoryResident())
+ continue;
+
+ size_t bytes_purgable = info->bytes;
+ DCHECK_LE(bytes_purgable, bytes_allocated_);
+ bytes_allocated_ -= bytes_purgable;
+ info->purgable = false;
+ allocation->Purge();
+ }
+
+ if (bytes_allocated_ != bytes_allocated_before_releasing_memory)
+ BytesAllocatedChanged(bytes_allocated_);
+}
+
bool DiscardableMemoryManager::ReduceMemoryUsage() {
return PurgeIfNotUsedSinceHardLimitCutoffUntilWithinSoftMemoryLimit();
}
diff --git a/base/memory/discardable_memory_manager.h b/base/memory/discardable_memory_manager.h
index 43737f8..8bf9289 100644
--- a/base/memory/discardable_memory_manager.h
+++ b/base/memory/discardable_memory_manager.h
@@ -31,6 +31,10 @@ class DiscardableMemoryManagerAllocation {
// is acquired on the allocation.
virtual void Purge() = 0;
+ // Check if allocated memory is still resident. It is illegal to call this
+ // while a lock is acquired on the allocation.
+ virtual bool IsMemoryResident() const = 0;
+
protected:
virtual ~DiscardableMemoryManagerAllocation() {}
};
@@ -68,6 +72,9 @@ class BASE_EXPORT_PRIVATE DiscardableMemoryManager {
void SetHardMemoryLimitExpirationTime(
TimeDelta hard_memory_limit_expiration_time);
+ // This will make sure that all purged memory is released to the OS.
+ void ReleaseFreeMemory();
+
// This will attempt to reduce memory footprint until within soft memory
// limit. Returns true if there's no need to call this again until allocations
// have been used.
diff --git a/base/memory/discardable_memory_manager_unittest.cc b/base/memory/discardable_memory_manager_unittest.cc
index fce7593..12f7f08 100644
--- a/base/memory/discardable_memory_manager_unittest.cc
+++ b/base/memory/discardable_memory_manager_unittest.cc
@@ -33,6 +33,10 @@ class TestAllocationImpl : public internal::DiscardableMemoryManagerAllocation {
DCHECK(is_allocated_);
is_allocated_ = false;
}
+ virtual bool IsMemoryResident() const override {
+ DCHECK(is_allocated_);
+ return true;
+ }
bool is_locked() const { return is_locked_; }
diff --git a/base/memory/discardable_memory_shmem.cc b/base/memory/discardable_memory_shmem.cc
new file mode 100644
index 0000000..77699f0
--- /dev/null
+++ b/base/memory/discardable_memory_shmem.cc
@@ -0,0 +1,121 @@
+// 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_memory_shmem.h"
+
+#include "base/lazy_instance.h"
+#include "base/memory/discardable_memory_shmem_allocator.h"
+#include "base/memory/discardable_shared_memory.h"
+
+namespace base {
+namespace {
+
+// Have the DiscardableMemoryManager trigger in-process eviction
+// when address space usage gets too high (e.g. 512 MBytes).
+const size_t kMemoryLimit = 512 * 1024 * 1024;
+
+// internal::DiscardableMemoryManager has an explicit constructor that takes
+// a number of memory limit parameters. The LeakyLazyInstanceTraits doesn't
+// handle the case. Thus, we need our own class here.
+struct DiscardableMemoryManagerLazyInstanceTraits {
+ // Leaky as discardable memory clients can use this after the exit handler
+ // has been called.
+ static const bool kRegisterOnExit = false;
+#ifndef NDEBUG
+ static const bool kAllowedToAccessOnNonjoinableThread = true;
+#endif
+
+ static internal::DiscardableMemoryManager* New(void* instance) {
+ return new (instance) internal::DiscardableMemoryManager(
+ kMemoryLimit, kMemoryLimit, TimeDelta::Max());
+ }
+ static void Delete(internal::DiscardableMemoryManager* instance) {
+ instance->~DiscardableMemoryManager();
+ }
+};
+
+LazyInstance<internal::DiscardableMemoryManager,
+ DiscardableMemoryManagerLazyInstanceTraits> g_manager =
+ LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+namespace internal {
+
+DiscardableMemoryShmem::DiscardableMemoryShmem(size_t bytes)
+ : bytes_(bytes), is_locked_(false) {
+ g_manager.Pointer()->Register(this, bytes);
+}
+
+DiscardableMemoryShmem::~DiscardableMemoryShmem() {
+ if (is_locked_)
+ Unlock();
+ g_manager.Pointer()->Unregister(this);
+}
+
+// static
+void DiscardableMemoryShmem::ReleaseFreeMemory() {
+ g_manager.Pointer()->ReleaseFreeMemory();
+}
+
+// static
+void DiscardableMemoryShmem::PurgeForTesting() {
+ g_manager.Pointer()->PurgeAll();
+}
+
+bool DiscardableMemoryShmem::Initialize() {
+ return Lock() != DISCARDABLE_MEMORY_LOCK_STATUS_FAILED;
+}
+
+DiscardableMemoryLockStatus DiscardableMemoryShmem::Lock() {
+ DCHECK(!is_locked_);
+
+ bool purged = false;
+ if (!g_manager.Pointer()->AcquireLock(this, &purged))
+ return DISCARDABLE_MEMORY_LOCK_STATUS_FAILED;
+
+ is_locked_ = true;
+ return purged ? DISCARDABLE_MEMORY_LOCK_STATUS_PURGED
+ : DISCARDABLE_MEMORY_LOCK_STATUS_SUCCESS;
+}
+
+void DiscardableMemoryShmem::Unlock() {
+ DCHECK(is_locked_);
+ g_manager.Pointer()->ReleaseLock(this);
+ is_locked_ = false;
+}
+
+void* DiscardableMemoryShmem::Memory() const {
+ DCHECK(is_locked_);
+ DCHECK(shared_memory_);
+ return shared_memory_->memory();
+}
+
+bool DiscardableMemoryShmem::AllocateAndAcquireLock() {
+ if (shared_memory_ && shared_memory_->Lock())
+ return true;
+
+ // TODO(reveman): Allocate fixed size memory segments and use a free list to
+ // improve performance and limit the number of file descriptors used.
+ shared_memory_ = DiscardableMemoryShmemAllocator::GetInstance()
+ ->AllocateLockedDiscardableSharedMemory(bytes_);
+ DCHECK(shared_memory_);
+ return false;
+}
+
+void DiscardableMemoryShmem::ReleaseLock() {
+ shared_memory_->Unlock();
+}
+
+void DiscardableMemoryShmem::Purge() {
+ shared_memory_->Purge(Time());
+ shared_memory_.reset();
+}
+
+bool DiscardableMemoryShmem::IsMemoryResident() const {
+ return shared_memory_->IsMemoryResident();
+}
+
+} // namespace internal
+} // namespace base
diff --git a/base/memory/discardable_memory_shmem.h b/base/memory/discardable_memory_shmem.h
new file mode 100644
index 0000000..d25f84c
--- /dev/null
+++ b/base/memory/discardable_memory_shmem.h
@@ -0,0 +1,52 @@
+// 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.
+
+#ifndef BASE_MEMORY_DISCARDABLE_MEMORY_SHMEM_H_
+#define BASE_MEMORY_DISCARDABLE_MEMORY_SHMEM_H_
+
+#include "base/memory/discardable_memory.h"
+
+#include "base/memory/discardable_memory_manager.h"
+
+namespace base {
+class DiscardableSharedMemory;
+
+namespace internal {
+
+class DiscardableMemoryShmem
+ : public DiscardableMemory,
+ public internal::DiscardableMemoryManagerAllocation {
+ public:
+ explicit DiscardableMemoryShmem(size_t bytes);
+ virtual ~DiscardableMemoryShmem();
+
+ static void ReleaseFreeMemory();
+
+ static void PurgeForTesting();
+
+ bool Initialize();
+
+ // Overridden from DiscardableMemory:
+ virtual DiscardableMemoryLockStatus Lock() override;
+ virtual void Unlock() override;
+ virtual void* Memory() const override;
+
+ // Overridden from internal::DiscardableMemoryManagerAllocation:
+ virtual bool AllocateAndAcquireLock() override;
+ virtual void ReleaseLock() override;
+ virtual void Purge() override;
+ virtual bool IsMemoryResident() const override;
+
+ private:
+ const size_t bytes_;
+ scoped_ptr<DiscardableSharedMemory> shared_memory_;
+ bool is_locked_;
+
+ DISALLOW_COPY_AND_ASSIGN(DiscardableMemoryShmem);
+};
+
+} // namespace internal
+} // namespace base
+
+#endif // BASE_MEMORY_DISCARDABLE_MEMORY_SHMEM_H_
diff --git a/base/memory/discardable_memory_shmem_allocator.cc b/base/memory/discardable_memory_shmem_allocator.cc
new file mode 100644
index 0000000..78d15c1
--- /dev/null
+++ b/base/memory/discardable_memory_shmem_allocator.cc
@@ -0,0 +1,58 @@
+// 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_memory_shmem_allocator.h"
+
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/memory/discardable_shared_memory.h"
+
+namespace base {
+namespace {
+
+// Default allocator implementation that allocates in-process
+// DiscardableSharedMemory instances.
+class DiscardableMemoryShmemAllocatorImpl
+ : public DiscardableMemoryShmemAllocator {
+ public:
+ // Overridden from DiscardableMemoryShmemAllocator:
+ virtual scoped_ptr<DiscardableSharedMemory>
+ AllocateLockedDiscardableSharedMemory(size_t size) override {
+ scoped_ptr<DiscardableSharedMemory> memory(new DiscardableSharedMemory);
+ if (!memory->CreateAndMap(size))
+ return scoped_ptr<DiscardableSharedMemory>();
+
+ return memory.Pass();
+ }
+};
+
+LazyInstance<DiscardableMemoryShmemAllocatorImpl>::Leaky g_default_allocator =
+ LAZY_INSTANCE_INITIALIZER;
+
+DiscardableMemoryShmemAllocator* g_allocator = nullptr;
+
+} // namespace
+
+// static
+void DiscardableMemoryShmemAllocator::SetInstance(
+ DiscardableMemoryShmemAllocator* allocator) {
+ DCHECK(allocator);
+
+ // Make sure this function is only called once before the first call
+ // to GetInstance().
+ DCHECK(!g_allocator);
+
+ g_allocator = allocator;
+}
+
+// static
+DiscardableMemoryShmemAllocator*
+DiscardableMemoryShmemAllocator::GetInstance() {
+ if (!g_allocator)
+ g_allocator = g_default_allocator.Pointer();
+
+ return g_allocator;
+}
+
+} // namespace base
diff --git a/base/memory/discardable_memory_shmem_allocator.h b/base/memory/discardable_memory_shmem_allocator.h
new file mode 100644
index 0000000..68624b3
--- /dev/null
+++ b/base/memory/discardable_memory_shmem_allocator.h
@@ -0,0 +1,32 @@
+// 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.
+
+#ifndef BASE_MEMORY_DISCARDABLE_MEMORY_SHMEM_ALLOCATOR_H_
+#define BASE_MEMORY_DISCARDABLE_MEMORY_SHMEM_ALLOCATOR_H_
+
+#include "base/base_export.h"
+#include "base/memory/scoped_ptr.h"
+
+namespace base {
+class DiscardableSharedMemory;
+
+class BASE_EXPORT DiscardableMemoryShmemAllocator {
+ public:
+ // Returns the allocator instance.
+ static DiscardableMemoryShmemAllocator* GetInstance();
+
+ // Sets the allocator instance. Can only be called once, e.g. on startup.
+ // Ownership of |instance| remains with the caller.
+ static void SetInstance(DiscardableMemoryShmemAllocator* allocator);
+
+ virtual scoped_ptr<DiscardableSharedMemory>
+ AllocateLockedDiscardableSharedMemory(size_t size) = 0;
+
+ protected:
+ virtual ~DiscardableMemoryShmemAllocator() {}
+};
+
+} // namespace base
+
+#endif // BASE_MEMORY_DISCARDABLE_MEMORY_SHMEM_ALLOCATOR_H_
diff --git a/base/memory/discardable_memory_win.cc b/base/memory/discardable_memory_win.cc
index 6a9a28df..9b4e940 100644
--- a/base/memory/discardable_memory_win.cc
+++ b/base/memory/discardable_memory_win.cc
@@ -6,10 +6,16 @@
#include "base/logging.h"
#include "base/memory/discardable_memory_emulated.h"
+#include "base/memory/discardable_memory_shmem.h"
namespace base {
// static
+void DiscardableMemory::ReleaseFreeMemory() {
+ internal::DiscardableMemoryShmem::ReleaseFreeMemory();
+}
+
+// static
bool DiscardableMemory::ReduceMemoryUsage() {
return internal::DiscardableMemoryEmulated::ReduceMemoryUsage();
}
@@ -18,7 +24,8 @@ bool DiscardableMemory::ReduceMemoryUsage() {
void DiscardableMemory::GetSupportedTypes(
std::vector<DiscardableMemoryType>* types) {
const DiscardableMemoryType supported_types[] = {
- DISCARDABLE_MEMORY_TYPE_EMULATED
+ DISCARDABLE_MEMORY_TYPE_EMULATED,
+ DISCARDABLE_MEMORY_TYPE_SHMEM
};
types->assign(supported_types, supported_types + arraysize(supported_types));
}
@@ -35,6 +42,14 @@ scoped_ptr<DiscardableMemory> DiscardableMemory::CreateLockedMemoryWithType(
return memory.Pass();
}
+ case DISCARDABLE_MEMORY_TYPE_SHMEM: {
+ scoped_ptr<internal::DiscardableMemoryShmem> memory(
+ new internal::DiscardableMemoryShmem(size));
+ if (!memory->Initialize())
+ return nullptr;
+
+ return memory.Pass();
+ }
case DISCARDABLE_MEMORY_TYPE_NONE:
case DISCARDABLE_MEMORY_TYPE_ASHMEM:
case DISCARDABLE_MEMORY_TYPE_MACH:
@@ -49,6 +64,7 @@ scoped_ptr<DiscardableMemory> DiscardableMemory::CreateLockedMemoryWithType(
// static
void DiscardableMemory::PurgeForTesting() {
internal::DiscardableMemoryEmulated::PurgeForTesting();
+ internal::DiscardableMemoryShmem::PurgeForTesting();
}
} // namespace base
diff --git a/base/memory/discardable_shared_memory.cc b/base/memory/discardable_shared_memory.cc
new file mode 100644
index 0000000..6533048
--- /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
diff --git a/base/memory/discardable_shared_memory.h b/base/memory/discardable_shared_memory.h
new file mode 100644
index 0000000..ca2accf
--- /dev/null
+++ b/base/memory/discardable_shared_memory.h
@@ -0,0 +1,109 @@
+// 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.
+
+#ifndef BASE_MEMORY_DISCARDABLE_SHARED_MEMORY_H_
+#define BASE_MEMORY_DISCARDABLE_SHARED_MEMORY_H_
+
+#include "base/base_export.h"
+#include "base/memory/shared_memory.h"
+#include "base/time/time.h"
+
+namespace base {
+
+// Platform abstraction for discardable shared memory.
+class BASE_EXPORT DiscardableSharedMemory {
+ public:
+ DiscardableSharedMemory();
+
+ // Create a new DiscardableSharedMemory object from an existing, open shared
+ // memory file.
+ explicit DiscardableSharedMemory(SharedMemoryHandle handle);
+
+ // Closes any open files.
+ virtual ~DiscardableSharedMemory();
+
+ // Creates and maps a locked DiscardableSharedMemory object with |size|.
+ // Returns true on success and false on failure.
+ bool CreateAndMap(size_t size);
+
+ // Maps the discardable memory into the caller's address space.
+ // Returns true on success, false otherwise.
+ bool Map(size_t size);
+
+ // The actual size of the mapped memory (may be larger than requested).
+ size_t mapped_size() const { return shared_memory_.mapped_size(); }
+
+ // Returns a shared memory handle for this DiscardableSharedMemory object.
+ SharedMemoryHandle handle() const { return shared_memory_.handle(); }
+
+ // Locks the memory so that it will not be purged by the system. Returns
+ // true if successful and the memory is still resident. Locking can fail
+ // for three reasons; object might have been purged, our last known usage
+ // timestamp might be out of date or memory might already be locked. Last
+ // know usage time is updated to the actual last usage timestamp if memory
+ // is still resident or 0 if not.
+ bool Lock();
+
+ // Unlock previously successfully locked memory.
+ void Unlock();
+
+ // Gets a pointer to the opened discardable memory space. Discardable memory
+ // must have been mapped via Map().
+ void* memory() const;
+
+ // Returns the last know usage time for DiscardableSharedMemory object. This
+ // may be earlier than the "true" usage time when memory has been used by a
+ // different process. Returns NULL time if purged.
+ Time last_known_usage() const { return last_known_usage_; }
+
+ // This returns true and sets |last_known_usage_| to 0 if
+ // DiscardableSharedMemory object was successfully purged. Purging can fail
+ // for two reasons; object might be locked or our last known usage timestamp
+ // might be out of date. Last known usage time is updated to |current_time|
+ // if locked or the actual last usage timestamp if unlocked. It is often
+ // neccesary to call this function twice for the object to successfully be
+ // purged. First call, updates |last_known_usage_|. Second call, successfully
+ // purges the object using the updated |last_known_usage_|.
+ // Note: there is no guarantee that multiple calls to this function will
+ // successfully purge object. DiscardableSharedMemory object might be locked
+ // or another thread/process might be able to lock and unlock it in between
+ // each call.
+ bool Purge(Time current_time);
+
+ // Purge and release as much memory as possible to the OS.
+ // Note: The amount of memory that can be released to the OS is platform
+ // specific. Best case, all but one page is released. Worst case, nothing
+ // is released.
+ bool PurgeAndTruncate(Time current_time);
+
+ // Returns true if memory is still resident.
+ bool IsMemoryResident() const;
+
+ // Closes the open discardable memory segment.
+ // It is safe to call Close repeatedly.
+ void Close();
+
+ // Shares the discardable memory segment to another process. Attempts to
+ // create a platform-specific |new_handle| which can be used in a remote
+ // process to access the discardable memory segment. |new_handle| is an
+ // output parameter to receive the handle for use in the remote process.
+ // Returns true on success, false otherwise.
+ bool ShareToProcess(ProcessHandle process_handle,
+ SharedMemoryHandle* new_handle) {
+ return shared_memory_.ShareToProcess(process_handle, new_handle);
+ }
+
+ private:
+ // Virtual for tests.
+ virtual Time Now() const;
+
+ SharedMemory shared_memory_;
+ Time last_known_usage_;
+
+ DISALLOW_COPY_AND_ASSIGN(DiscardableSharedMemory);
+};
+
+} // namespace base
+
+#endif // BASE_MEMORY_DISCARDABLE_SHARED_MEMORY_H_
diff --git a/base/memory/discardable_shared_memory_unittest.cc b/base/memory/discardable_shared_memory_unittest.cc
new file mode 100644
index 0000000..e517429
--- /dev/null
+++ b/base/memory/discardable_shared_memory_unittest.cc
@@ -0,0 +1,251 @@
+// 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/basictypes.h"
+#include "base/memory/discardable_shared_memory.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace {
+
+class TestDiscardableSharedMemory : public DiscardableSharedMemory {
+ public:
+ TestDiscardableSharedMemory() {}
+
+ explicit TestDiscardableSharedMemory(SharedMemoryHandle handle)
+ : DiscardableSharedMemory(handle) {}
+
+ void SetNow(Time now) { now_ = now; }
+
+ private:
+ // Overriden from DiscardableSharedMemory:
+ virtual Time Now() const override { return now_; }
+
+ Time now_;
+};
+
+TEST(DiscardableSharedMemoryTest, CreateAndMap) {
+ const uint32 kDataSize = 1024;
+
+ TestDiscardableSharedMemory memory;
+ bool rv = memory.CreateAndMap(kDataSize);
+ ASSERT_TRUE(rv);
+ EXPECT_GE(memory.mapped_size(), kDataSize);
+}
+
+TEST(DiscardableSharedMemoryTest, CreateFromHandle) {
+ const uint32 kDataSize = 1024;
+
+ TestDiscardableSharedMemory memory1;
+ bool rv = memory1.CreateAndMap(kDataSize);
+ ASSERT_TRUE(rv);
+
+ SharedMemoryHandle shared_handle;
+ ASSERT_TRUE(
+ memory1.ShareToProcess(GetCurrentProcessHandle(), &shared_handle));
+ ASSERT_TRUE(SharedMemory::IsHandleValid(shared_handle));
+
+ TestDiscardableSharedMemory memory2(shared_handle);
+ rv = memory2.Map(kDataSize);
+ ASSERT_TRUE(rv);
+}
+
+TEST(DiscardableSharedMemoryTest, LockAndUnlock) {
+ const uint32 kDataSize = 1024;
+
+ TestDiscardableSharedMemory memory1;
+ bool rv = memory1.CreateAndMap(kDataSize);
+ ASSERT_TRUE(rv);
+
+ // Memory is initially locked. Unlock it.
+ memory1.SetNow(Time::FromDoubleT(1));
+ memory1.Unlock();
+
+ // Lock and unlock memory.
+ rv = memory1.Lock();
+ EXPECT_TRUE(rv);
+ memory1.SetNow(Time::FromDoubleT(2));
+ memory1.Unlock();
+
+ SharedMemoryHandle shared_handle;
+ ASSERT_TRUE(
+ memory1.ShareToProcess(GetCurrentProcessHandle(), &shared_handle));
+ ASSERT_TRUE(SharedMemory::IsHandleValid(shared_handle));
+
+ TestDiscardableSharedMemory memory2(shared_handle);
+ rv = memory2.Map(kDataSize);
+ ASSERT_TRUE(rv);
+
+ // Lock first instance again.
+ rv = memory1.Lock();
+ EXPECT_TRUE(rv);
+
+ // Unlock second instance.
+ memory2.SetNow(Time::FromDoubleT(3));
+ memory2.Unlock();
+
+ // Lock and unlock second instance.
+ rv = memory2.Lock();
+ EXPECT_TRUE(rv);
+ memory2.SetNow(Time::FromDoubleT(4));
+ memory2.Unlock();
+
+ // Try to lock first instance again. Should fail as first instance has an
+ // incorrect last know usage time.
+ rv = memory1.Lock();
+ EXPECT_FALSE(rv);
+
+ // Memory should still be resident.
+ rv = memory1.IsMemoryResident();
+ EXPECT_TRUE(rv);
+
+ // Second attempt to lock first instance should succeed as last known usage
+ // time is now correct.
+ rv = memory1.Lock();
+ EXPECT_TRUE(rv);
+ memory1.SetNow(Time::FromDoubleT(5));
+ memory1.Unlock();
+}
+
+TEST(DiscardableSharedMemoryTest, Purge) {
+ const uint32 kDataSize = 1024;
+
+ TestDiscardableSharedMemory memory1;
+ bool rv = memory1.CreateAndMap(kDataSize);
+ ASSERT_TRUE(rv);
+
+ SharedMemoryHandle shared_handle;
+ ASSERT_TRUE(
+ memory1.ShareToProcess(GetCurrentProcessHandle(), &shared_handle));
+ ASSERT_TRUE(SharedMemory::IsHandleValid(shared_handle));
+
+ TestDiscardableSharedMemory memory2(shared_handle);
+ rv = memory2.Map(kDataSize);
+ ASSERT_TRUE(rv);
+
+ // This should fail as memory is locked.
+ rv = memory1.Purge(Time::FromDoubleT(1));
+ EXPECT_FALSE(rv);
+
+ memory2.SetNow(Time::FromDoubleT(2));
+ memory2.Unlock();
+
+ ASSERT_TRUE(memory2.IsMemoryResident());
+
+ // Memory is unlocked, but our usage timestamp is incorrect.
+ rv = memory1.Purge(Time::FromDoubleT(3));
+ EXPECT_FALSE(rv);
+
+ ASSERT_TRUE(memory2.IsMemoryResident());
+
+ // Memory is unlocked and our usage timestamp should be correct.
+ rv = memory1.Purge(Time::FromDoubleT(4));
+ EXPECT_TRUE(rv);
+
+ // Lock should fail as memory has been purged.
+ rv = memory2.Lock();
+ EXPECT_FALSE(rv);
+
+ ASSERT_FALSE(memory2.IsMemoryResident());
+}
+
+TEST(DiscardableSharedMemoryTest, LastUsed) {
+ const uint32 kDataSize = 1024;
+
+ TestDiscardableSharedMemory memory1;
+ bool rv = memory1.CreateAndMap(kDataSize);
+ ASSERT_TRUE(rv);
+
+ SharedMemoryHandle shared_handle;
+ ASSERT_TRUE(
+ memory1.ShareToProcess(GetCurrentProcessHandle(), &shared_handle));
+ ASSERT_TRUE(SharedMemory::IsHandleValid(shared_handle));
+
+ TestDiscardableSharedMemory memory2(shared_handle);
+ rv = memory2.Map(kDataSize);
+ ASSERT_TRUE(rv);
+
+ memory2.SetNow(Time::FromDoubleT(1));
+ memory2.Unlock();
+
+ EXPECT_EQ(memory2.last_known_usage(), Time::FromDoubleT(1));
+
+ rv = memory2.Lock();
+ EXPECT_TRUE(rv);
+
+ // This should fail as memory is locked.
+ rv = memory1.Purge(Time::FromDoubleT(2));
+ ASSERT_FALSE(rv);
+
+ // Last usage should have been updated to timestamp passed to Purge above.
+ EXPECT_EQ(memory1.last_known_usage(), Time::FromDoubleT(2));
+
+ memory2.SetNow(Time::FromDoubleT(3));
+ memory2.Unlock();
+
+ // Usage time should be correct for |memory2| instance.
+ EXPECT_EQ(memory2.last_known_usage(), Time::FromDoubleT(3));
+
+ // However, usage time has not changed as far as |memory1| instance knows.
+ EXPECT_EQ(memory1.last_known_usage(), Time::FromDoubleT(2));
+
+ // Memory is unlocked, but our usage timestamp is incorrect.
+ rv = memory1.Purge(Time::FromDoubleT(4));
+ EXPECT_FALSE(rv);
+
+ // The failed purge attempt should have updated usage time to the correct
+ // value.
+ EXPECT_EQ(memory1.last_known_usage(), Time::FromDoubleT(3));
+
+ // Purge memory through |memory2| instance. The last usage time should be
+ // set to 0 as a result of this.
+ rv = memory2.Purge(Time::FromDoubleT(5));
+ EXPECT_TRUE(rv);
+ EXPECT_TRUE(memory2.last_known_usage().is_null());
+
+ // This should fail as memory has already been purged and |memory1|'s usage
+ // time is incorrect as a result.
+ rv = memory1.Purge(Time::FromDoubleT(6));
+ EXPECT_FALSE(rv);
+
+ // The failed purge attempt should have updated usage time to the correct
+ // value.
+ EXPECT_TRUE(memory1.last_known_usage().is_null());
+
+ // Purge should succeed now that usage time is correct.
+ rv = memory1.Purge(Time::FromDoubleT(7));
+ EXPECT_TRUE(rv);
+}
+
+TEST(DiscardableSharedMemoryTest, LockShouldAlwaysFailAfterSuccessfulPurge) {
+ const uint32 kDataSize = 1024;
+
+ TestDiscardableSharedMemory memory1;
+ bool rv = memory1.CreateAndMap(kDataSize);
+ ASSERT_TRUE(rv);
+
+ SharedMemoryHandle shared_handle;
+ ASSERT_TRUE(
+ memory1.ShareToProcess(GetCurrentProcessHandle(), &shared_handle));
+ ASSERT_TRUE(SharedMemory::IsHandleValid(shared_handle));
+
+ TestDiscardableSharedMemory memory2(shared_handle);
+ rv = memory2.Map(kDataSize);
+ ASSERT_TRUE(rv);
+
+ memory2.SetNow(Time::FromDoubleT(1));
+ memory2.Unlock();
+
+ rv = memory2.Purge(Time::FromDoubleT(2));
+ EXPECT_TRUE(rv);
+
+ // Lock should fail as memory has been purged.
+ rv = memory2.Lock();
+ EXPECT_FALSE(rv);
+ rv = memory1.Lock();
+ EXPECT_FALSE(rv);
+}
+
+} // namespace
+} // namespace base
diff --git a/chrome/common/crash_keys.cc b/chrome/common/crash_keys.cc
index 9a03923..b96a3ac 100644
--- a/chrome/common/crash_keys.cc
+++ b/chrome/common/crash_keys.cc
@@ -145,6 +145,7 @@ size_t RegisterChromeCrashKeys() {
// base/:
{ "dm-usage", kSmallSize },
+ { "total-dm-usage", kSmallSize },
// content/:
{ kFontKeyName, kSmallSize},
{ "ppapi_path", kMediumSize },
diff --git a/content/browser/renderer_host/render_message_filter.cc b/content/browser/renderer_host/render_message_filter.cc
index 4d221b6..1c982f0 100644
--- a/content/browser/renderer_host/render_message_filter.cc
+++ b/content/browser/renderer_host/render_message_filter.cc
@@ -38,6 +38,7 @@
#include "content/common/desktop_notification_messages.h"
#include "content/common/frame_messages.h"
#include "content/common/gpu/client/gpu_memory_buffer_impl.h"
+#include "content/common/host_discardable_shared_memory_manager.h"
#include "content/common/host_shared_bitmap_manager.h"
#include "content/common/media/media_param_traits.h"
#include "content/common/view_messages.h"
@@ -326,6 +327,7 @@ RenderMessageFilter::~RenderMessageFilter() {
DCHECK(plugin_host_clients_.empty());
HostSharedBitmapManager::current()->ProcessRemoved(PeerHandle());
BrowserGpuMemoryBufferManager::current()->ProcessRemoved(PeerHandle());
+ HostDiscardableSharedMemoryManager::current()->ProcessRemoved(PeerHandle());
}
void RenderMessageFilter::OnChannelClosing() {
@@ -410,6 +412,9 @@ bool RenderMessageFilter::OnMessageReceived(const IPC::Message& message) {
OnAllocatedSharedBitmap)
IPC_MESSAGE_HANDLER(ChildProcessHostMsg_DeletedSharedBitmap,
OnDeletedSharedBitmap)
+ IPC_MESSAGE_HANDLER(
+ ChildProcessHostMsg_SyncAllocateLockedDiscardableSharedMemory,
+ OnAllocateLockedDiscardableSharedMemory)
#if defined(OS_POSIX) && !defined(OS_ANDROID)
IPC_MESSAGE_HANDLER(ViewHostMsg_AllocTransportDIB, OnAllocTransportDIB)
IPC_MESSAGE_HANDLER(ViewHostMsg_FreeTransportDIB, OnFreeTransportDIB)
@@ -945,6 +950,14 @@ void RenderMessageFilter::OnDeletedSharedBitmap(const cc::SharedBitmapId& id) {
HostSharedBitmapManager::current()->ChildDeletedSharedBitmap(id);
}
+void RenderMessageFilter::OnAllocateLockedDiscardableSharedMemory(
+ uint32 size,
+ base::SharedMemoryHandle* handle) {
+ HostDiscardableSharedMemoryManager::current()
+ ->AllocateLockedDiscardableSharedMemoryForChild(
+ PeerHandle(), size, handle);
+}
+
net::CookieStore* RenderMessageFilter::GetCookieStoreForURL(
const GURL& url) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
diff --git a/content/browser/renderer_host/render_message_filter.h b/content/browser/renderer_host/render_message_filter.h
index e9e68d0..fba815b 100644
--- a/content/browser/renderer_host/render_message_filter.h
+++ b/content/browser/renderer_host/render_message_filter.h
@@ -240,6 +240,11 @@ class CONTENT_EXPORT RenderMessageFilter : public BrowserMessageFilter {
void OnDeletedSharedBitmap(const cc::SharedBitmapId& id);
void OnResolveProxy(const GURL& url, IPC::Message* reply_msg);
+ // Browser side discardable shared memory allocation.
+ void OnAllocateLockedDiscardableSharedMemory(
+ uint32 size,
+ base::SharedMemoryHandle* handle);
+
// Browser side transport DIB allocation
void OnAllocTransportDIB(uint32 size,
bool cache_in_browser,
diff --git a/content/child/child_discardable_shared_memory_manager.cc b/content/child/child_discardable_shared_memory_manager.cc
new file mode 100644
index 0000000..606ca6c
--- /dev/null
+++ b/content/child/child_discardable_shared_memory_manager.cc
@@ -0,0 +1,40 @@
+// 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 "content/child/child_discardable_shared_memory_manager.h"
+
+#include "base/memory/discardable_shared_memory.h"
+#include "content/child/child_thread.h"
+#include "content/common/child_process_messages.h"
+
+namespace content {
+
+ChildDiscardableSharedMemoryManager::ChildDiscardableSharedMemoryManager(
+ ThreadSafeSender* sender)
+ : sender_(sender) {
+}
+
+ChildDiscardableSharedMemoryManager::~ChildDiscardableSharedMemoryManager() {
+}
+
+scoped_ptr<base::DiscardableSharedMemory>
+ChildDiscardableSharedMemoryManager::AllocateLockedDiscardableSharedMemory(
+ size_t size) {
+ TRACE_EVENT1("renderer",
+ "ChildDiscardableSharedMemoryManager::"
+ "AllocateLockedDiscardableSharedMemory",
+ "size",
+ size);
+
+ base::SharedMemoryHandle handle = base::SharedMemory::NULLHandle();
+ sender_->Send(
+ new ChildProcessHostMsg_SyncAllocateLockedDiscardableSharedMemory(
+ size, &handle));
+ scoped_ptr<base::DiscardableSharedMemory> memory(
+ new base::DiscardableSharedMemory(handle));
+ CHECK(memory->Map(size));
+ return memory.Pass();
+}
+
+} // namespace content
diff --git a/content/child/child_discardable_shared_memory_manager.h b/content/child/child_discardable_shared_memory_manager.h
new file mode 100644
index 0000000..e35c57b
--- /dev/null
+++ b/content/child/child_discardable_shared_memory_manager.h
@@ -0,0 +1,34 @@
+// 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.
+
+#ifndef CONTENT_CHILD_CHILD_DISCARDABLE_SHARED_MEMORY_MANAGER_H_
+#define CONTENT_CHILD_CHILD_DISCARDABLE_SHARED_MEMORY_MANAGER_H_
+
+#include "base/memory/discardable_memory_shmem_allocator.h"
+#include "base/memory/ref_counted.h"
+#include "content/child/thread_safe_sender.h"
+
+namespace content {
+
+// Implementation of DiscardableMemoryShmemAllocator that allocates
+// discardable memory segments through the browser process.
+class ChildDiscardableSharedMemoryManager
+ : public base::DiscardableMemoryShmemAllocator {
+ public:
+ explicit ChildDiscardableSharedMemoryManager(ThreadSafeSender* sender);
+ ~ChildDiscardableSharedMemoryManager() override;
+
+ // Overridden from base::DiscardableMemoryShmemAllocator:
+ scoped_ptr<base::DiscardableSharedMemory>
+ AllocateLockedDiscardableSharedMemory(size_t size) override;
+
+ private:
+ scoped_refptr<ThreadSafeSender> sender_;
+
+ DISALLOW_COPY_AND_ASSIGN(ChildDiscardableSharedMemoryManager);
+};
+
+} // namespace content
+
+#endif // CONTENT_CHILD_CHILD_DISCARDABLE_SHARED_MEMORY_MANAGER_H_
diff --git a/content/child/child_thread.cc b/content/child/child_thread.cc
index de85d4f..dba6800 100644
--- a/content/child/child_thread.cc
+++ b/content/child/child_thread.cc
@@ -26,6 +26,7 @@
#include "base/threading/thread_local.h"
#include "base/tracked_objects.h"
#include "components/tracing/child_trace_message_filter.h"
+#include "content/child/child_discardable_shared_memory_manager.h"
#include "content/child/child_gpu_memory_buffer_manager.h"
#include "content/child/child_histogram_message_filter.h"
#include "content/child/child_process.h"
@@ -362,6 +363,9 @@ void ChildThread::Init(const Options& options) {
gpu_memory_buffer_manager_.reset(
new ChildGpuMemoryBufferManager(thread_safe_sender()));
+
+ discardable_shared_memory_manager_.reset(
+ new ChildDiscardableSharedMemoryManager(thread_safe_sender()));
}
ChildThread::~ChildThread() {
diff --git a/content/child/child_thread.h b/content/child/child_thread.h
index 6c71c0f..e1a5b85 100644
--- a/content/child/child_thread.h
+++ b/content/child/child_thread.h
@@ -36,6 +36,7 @@ class WebFrame;
} // namespace blink
namespace content {
+class ChildDiscardableSharedMemoryManager;
class ChildGpuMemoryBufferManager;
class ChildHistogramMessageFilter;
class ChildResourceMessageFilter;
@@ -101,6 +102,11 @@ class CONTENT_EXPORT ChildThread : public IPC::Listener, public IPC::Sender {
return gpu_memory_buffer_manager_.get();
}
+ ChildDiscardableSharedMemoryManager* discardable_shared_memory_manager()
+ const {
+ return discardable_shared_memory_manager_.get();
+ }
+
ResourceDispatcher* resource_dispatcher() const {
return resource_dispatcher_.get();
}
@@ -241,6 +247,9 @@ class CONTENT_EXPORT ChildThread : public IPC::Listener, public IPC::Sender {
scoped_ptr<ChildGpuMemoryBufferManager> gpu_memory_buffer_manager_;
+ scoped_ptr<ChildDiscardableSharedMemoryManager>
+ discardable_shared_memory_manager_;
+
// Observes the trace event system. When tracing is enabled, optionally
// starts profiling the tcmalloc heap.
scoped_ptr<base::debug::TraceMemoryController> trace_memory_controller_;
diff --git a/content/common/child_process_messages.h b/content/common/child_process_messages.h
index 0d4a3f6..44932dc 100644
--- a/content/common/child_process_messages.h
+++ b/content/common/child_process_messages.h
@@ -201,3 +201,10 @@ IPC_SYNC_MESSAGE_CONTROL4_1(ChildProcessHostMsg_SyncAllocateGpuMemoryBuffer,
IPC_MESSAGE_CONTROL2(ChildProcessHostMsg_DeletedGpuMemoryBuffer,
gfx::GpuMemoryBufferType,
gfx::GpuMemoryBufferId)
+
+// Asks the browser to create a block of discardable shared memory for the
+// child process.
+IPC_SYNC_MESSAGE_CONTROL1_1(
+ ChildProcessHostMsg_SyncAllocateLockedDiscardableSharedMemory,
+ uint32 /* size */,
+ base::SharedMemoryHandle)
diff --git a/content/common/host_discardable_shared_memory_manager.cc b/content/common/host_discardable_shared_memory_manager.cc
new file mode 100644
index 0000000..c56fd11
--- /dev/null
+++ b/content/common/host_discardable_shared_memory_manager.cc
@@ -0,0 +1,255 @@
+// 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 "content/common/host_discardable_shared_memory_manager.h"
+
+#include <algorithm>
+
+#include "base/callback.h"
+#include "base/debug/crash_logging.h"
+#include "base/debug/trace_event.h"
+#include "base/lazy_instance.h"
+#include "base/numerics/safe_math.h"
+#include "base/strings/string_number_conversions.h"
+
+namespace content {
+namespace {
+
+base::LazyInstance<HostDiscardableSharedMemoryManager>
+ g_discardable_shared_memory_manager = LAZY_INSTANCE_INITIALIZER;
+
+const size_t kDefaultMemoryLimit = 512 * 1024 * 1024;
+
+const int kEnforceMemoryPolicyDelayMs = 1000;
+
+} // namespace
+
+HostDiscardableSharedMemoryManager::MemorySegment::MemorySegment(
+ linked_ptr<base::DiscardableSharedMemory> memory,
+ base::ProcessHandle process_handle)
+ : memory(memory), process_handle(process_handle) {
+}
+
+HostDiscardableSharedMemoryManager::MemorySegment::~MemorySegment() {
+}
+
+HostDiscardableSharedMemoryManager::HostDiscardableSharedMemoryManager()
+ : memory_limit_(kDefaultMemoryLimit),
+ bytes_allocated_(0),
+ memory_pressure_listener_(new base::MemoryPressureListener(
+ base::Bind(&HostDiscardableSharedMemoryManager::OnMemoryPressure,
+ base::Unretained(this)))),
+ enforce_memory_policy_pending_(false),
+ weak_ptr_factory_(this) {
+}
+
+HostDiscardableSharedMemoryManager::~HostDiscardableSharedMemoryManager() {
+}
+
+HostDiscardableSharedMemoryManager*
+HostDiscardableSharedMemoryManager::current() {
+ return g_discardable_shared_memory_manager.Pointer();
+}
+
+scoped_ptr<base::DiscardableSharedMemory>
+HostDiscardableSharedMemoryManager::AllocateLockedDiscardableSharedMemory(
+ size_t size) {
+ // TODO(reveman): Need to implement this for discardable memory support in
+ // the browser process.
+ NOTIMPLEMENTED();
+ return scoped_ptr<base::DiscardableSharedMemory>();
+}
+
+void HostDiscardableSharedMemoryManager::
+ AllocateLockedDiscardableSharedMemoryForChild(
+ base::ProcessHandle process_handle,
+ size_t size,
+ base::SharedMemoryHandle* shared_memory_handle) {
+ base::AutoLock lock(lock_);
+
+ // Memory usage must be reduced to prevent the addition of |size| from
+ // taking usage above the limit. Usage should be reduced to 0 in cases
+ // where |size| is greater than the limit.
+ size_t limit = 0;
+ // Note: the actual mapped size can be larger than requested and cause
+ // |bytes_allocated_| to temporarily be larger than |memory_limit_|. The
+ // error is minimized by incrementing |bytes_allocated_| with the actual
+ // mapped size rather than |size| below.
+ if (size < memory_limit_)
+ limit = memory_limit_ - size;
+
+ if (bytes_allocated_ > limit)
+ ReduceMemoryUsageUntilWithinLimit(limit);
+
+ linked_ptr<base::DiscardableSharedMemory> memory(
+ new base::DiscardableSharedMemory);
+ if (!memory->CreateAndMap(size)) {
+ *shared_memory_handle = base::SharedMemory::NULLHandle();
+ return;
+ }
+
+ if (!memory->ShareToProcess(process_handle, shared_memory_handle)) {
+ LOG(ERROR) << "Cannot share discardable memory segment";
+ *shared_memory_handle = base::SharedMemory::NULLHandle();
+ return;
+ }
+
+ base::CheckedNumeric<size_t> checked_bytes_allocated = bytes_allocated_;
+ checked_bytes_allocated += memory->mapped_size();
+ if (!checked_bytes_allocated.IsValid()) {
+ *shared_memory_handle = base::SharedMemory::NULLHandle();
+ return;
+ }
+
+ bytes_allocated_ = checked_bytes_allocated.ValueOrDie();
+ BytesAllocatedChanged(bytes_allocated_);
+
+ segments_.push_back(MemorySegment(memory, process_handle));
+ std::push_heap(segments_.begin(), segments_.end(), CompareMemoryUsageTime);
+
+ if (bytes_allocated_ > memory_limit_)
+ ScheduleEnforceMemoryPolicy();
+}
+
+void HostDiscardableSharedMemoryManager::ProcessRemoved(
+ base::ProcessHandle process_handle) {
+ base::AutoLock lock(lock_);
+
+ size_t bytes_allocated_before_purging = bytes_allocated_;
+ for (auto& segment : segments_) {
+ // Skip segments that belong to a different process.
+ if (segment.process_handle != process_handle)
+ continue;
+
+ size_t size = segment.memory->mapped_size();
+ DCHECK_GE(bytes_allocated_, size);
+
+ // This will unmap the memory segment and drop our reference. The result
+ // is that the memory will be released to the OS if the child process is
+ // no longer referencing it.
+ // Note: We intentionally leave the segment in the vector to avoid
+ // reconstructing the heap. The element will be removed from the heap
+ // when its last usage time is older than all other segments.
+ segment.memory->Close();
+ bytes_allocated_ -= size;
+ }
+
+ if (bytes_allocated_ != bytes_allocated_before_purging)
+ BytesAllocatedChanged(bytes_allocated_);
+}
+
+void HostDiscardableSharedMemoryManager::SetMemoryLimit(size_t limit) {
+ base::AutoLock lock(lock_);
+
+ memory_limit_ = limit;
+ ReduceMemoryUsageUntilWithinMemoryLimit();
+}
+
+void HostDiscardableSharedMemoryManager::EnforceMemoryPolicy() {
+ base::AutoLock lock(lock_);
+
+ enforce_memory_policy_pending_ = false;
+ ReduceMemoryUsageUntilWithinMemoryLimit();
+}
+
+void HostDiscardableSharedMemoryManager::OnMemoryPressure(
+ base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level) {
+ base::AutoLock lock(lock_);
+
+ switch (memory_pressure_level) {
+ case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE:
+ break;
+ case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL:
+ // Purge everything possible when pressure is critical.
+ ReduceMemoryUsageUntilWithinLimit(0);
+ break;
+ }
+}
+
+void
+HostDiscardableSharedMemoryManager::ReduceMemoryUsageUntilWithinMemoryLimit() {
+ if (bytes_allocated_ <= memory_limit_)
+ return;
+
+ ReduceMemoryUsageUntilWithinLimit(memory_limit_);
+ if (bytes_allocated_ > memory_limit_)
+ ScheduleEnforceMemoryPolicy();
+}
+
+void HostDiscardableSharedMemoryManager::ReduceMemoryUsageUntilWithinLimit(
+ size_t limit) {
+ TRACE_EVENT1("renderer_host",
+ "HostDiscardableSharedMemoryManager::"
+ "ReduceMemoryUsageUntilWithinLimit",
+ "bytes_allocated",
+ bytes_allocated_);
+
+ // Usage time of currently locked segments are updated to this time and
+ // we stop eviction attempts as soon as we come across a segment that we've
+ // previously tried to evict but was locked.
+ base::Time current_time = Now();
+
+ lock_.AssertAcquired();
+ size_t bytes_allocated_before_purging = bytes_allocated_;
+ while (!segments_.empty()) {
+ if (bytes_allocated_ <= limit)
+ break;
+
+ // Stop eviction attempts when the LRU segment is currently in use.
+ if (segments_.front().memory->last_known_usage() >= current_time)
+ break;
+
+ std::pop_heap(segments_.begin(), segments_.end(), CompareMemoryUsageTime);
+ MemorySegment segment = segments_.back();
+ segments_.pop_back();
+
+ // Attempt to purge and truncate LRU segment. When successful, as much
+ // memory as possible will be released to the OS. How much memory is
+ // released depends on the platform. The child process should perform
+ // periodic cleanup to ensure that all memory is release within a
+ // reasonable amount of time.
+ if (segment.memory->PurgeAndTruncate(current_time)) {
+ size_t size = segment.memory->mapped_size();
+ DCHECK_GE(bytes_allocated_, size);
+ bytes_allocated_ -= size;
+ continue;
+ }
+
+ // Add memory segment (with updated usage timestamp) back on heap after
+ // failed attempt to purge it.
+ segments_.push_back(segment);
+ std::push_heap(segments_.begin(), segments_.end(), CompareMemoryUsageTime);
+ }
+
+ if (bytes_allocated_ != bytes_allocated_before_purging)
+ BytesAllocatedChanged(bytes_allocated_);
+}
+
+void HostDiscardableSharedMemoryManager::BytesAllocatedChanged(
+ size_t new_bytes_allocated) const {
+ TRACE_COUNTER_ID1(
+ "base", "TotalDiscardableMemoryUsage", this, new_bytes_allocated);
+
+ static const char kTotalDiscardableMemoryUsageKey[] = "total-dm-usage";
+ base::debug::SetCrashKeyValue(kTotalDiscardableMemoryUsageKey,
+ base::Uint64ToString(new_bytes_allocated));
+}
+
+base::Time HostDiscardableSharedMemoryManager::Now() const {
+ return base::Time::Now();
+}
+
+void HostDiscardableSharedMemoryManager::ScheduleEnforceMemoryPolicy() {
+ if (enforce_memory_policy_pending_)
+ return;
+
+ enforce_memory_policy_pending_ = true;
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&HostDiscardableSharedMemoryManager::EnforceMemoryPolicy,
+ weak_ptr_factory_.GetWeakPtr()),
+ base::TimeDelta::FromMilliseconds(kEnforceMemoryPolicyDelayMs));
+}
+
+} // namespace content
diff --git a/content/common/host_discardable_shared_memory_manager.h b/content/common/host_discardable_shared_memory_manager.h
new file mode 100644
index 0000000..148c4fc
--- /dev/null
+++ b/content/common/host_discardable_shared_memory_manager.h
@@ -0,0 +1,100 @@
+// 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.
+
+#ifndef CONTENT_COMMON_HOST_DISCARDABLE_SHARED_MEMORY_MANAGER_H_
+#define CONTENT_COMMON_HOST_DISCARDABLE_SHARED_MEMORY_MANAGER_H_
+
+#include <vector>
+
+#include "base/memory/discardable_memory_shmem_allocator.h"
+#include "base/memory/discardable_shared_memory.h"
+#include "base/memory/linked_ptr.h"
+#include "base/memory/memory_pressure_listener.h"
+#include "base/memory/shared_memory.h"
+#include "base/memory/weak_ptr.h"
+#include "base/process/process_handle.h"
+#include "base/synchronization/lock.h"
+#include "content/common/content_export.h"
+
+namespace content {
+
+// Implementation of DiscardableMemoryShmemAllocator that allocates and
+// manages discardable memory segments for the browser process and child
+// processes. This class is thread-safe and instances can safely be used
+// on any thread.
+class CONTENT_EXPORT HostDiscardableSharedMemoryManager
+ : public base::DiscardableMemoryShmemAllocator {
+ public:
+ HostDiscardableSharedMemoryManager();
+ ~HostDiscardableSharedMemoryManager() override;
+
+ // Returns a singleton instance.
+ static HostDiscardableSharedMemoryManager* current();
+
+ // Overridden from base::DiscardableMemoryShmemAllocator:
+ scoped_ptr<base::DiscardableSharedMemory>
+ AllocateLockedDiscardableSharedMemory(size_t size) override;
+
+ // This allocates a discardable memory segment for |process_handle|.
+ // A valid shared memory handle is returned on success.
+ void AllocateLockedDiscardableSharedMemoryForChild(
+ base::ProcessHandle process_handle,
+ size_t size,
+ base::SharedMemoryHandle* shared_memory_handle);
+
+ // Call this to notify the manager that child process associated with
+ // |process_handle| has been removed. The manager will use this to release
+ // memory segments allocated for child process to the OS.
+ void ProcessRemoved(base::ProcessHandle process_handle);
+
+ // The maximum number of bytes of memory that may be allocated. This will
+ // cause memory usage to be reduced if currently above |limit|.
+ void SetMemoryLimit(size_t limit);
+
+ // Reduce memory usage if above current memory limit.
+ void EnforceMemoryPolicy();
+
+ private:
+ struct MemorySegment {
+ MemorySegment(linked_ptr<base::DiscardableSharedMemory> memory,
+ base::ProcessHandle process_handle);
+ ~MemorySegment();
+
+ linked_ptr<base::DiscardableSharedMemory> memory;
+ base::ProcessHandle process_handle;
+ };
+
+ static bool CompareMemoryUsageTime(const MemorySegment& a,
+ const MemorySegment& b) {
+ // In this system, LRU memory segment is evicted first.
+ return a.memory->last_known_usage() > b.memory->last_known_usage();
+ }
+
+ void OnMemoryPressure(
+ base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level);
+ void ReduceMemoryUsageUntilWithinMemoryLimit();
+ void ReduceMemoryUsageUntilWithinLimit(size_t limit);
+ void BytesAllocatedChanged(size_t new_bytes_allocated) const;
+
+ // Virtual for tests.
+ virtual base::Time Now() const;
+ virtual void ScheduleEnforceMemoryPolicy();
+
+ base::Lock lock_;
+ // Note: The elements in |segments_| are arranged in such a way that they form
+ // a heap. The LRU memory segment always first.
+ typedef std::vector<MemorySegment> MemorySegmentVector;
+ MemorySegmentVector segments_;
+ size_t memory_limit_;
+ size_t bytes_allocated_;
+ scoped_ptr<base::MemoryPressureListener> memory_pressure_listener_;
+ bool enforce_memory_policy_pending_;
+ base::WeakPtrFactory<HostDiscardableSharedMemoryManager> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(HostDiscardableSharedMemoryManager);
+};
+
+} // namespace content
+
+#endif // CONTENT_COMMON_HOST_DISCARDABLE_SHARED_MEMORY_MANAGER_H_
diff --git a/content/common/host_discardable_shared_memory_manager_unittest.cc b/content/common/host_discardable_shared_memory_manager_unittest.cc
new file mode 100644
index 0000000..d4c5e00
--- /dev/null
+++ b/content/common/host_discardable_shared_memory_manager_unittest.cc
@@ -0,0 +1,188 @@
+// 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 "content/common/host_discardable_shared_memory_manager.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+namespace {
+
+class TestDiscardableSharedMemory : public base::DiscardableSharedMemory {
+ public:
+ TestDiscardableSharedMemory() {}
+
+ explicit TestDiscardableSharedMemory(base::SharedMemoryHandle handle)
+ : DiscardableSharedMemory(handle) {}
+
+ void SetNow(base::Time now) { now_ = now; }
+
+ private:
+ // Overriden from base::DiscardableSharedMemory:
+ base::Time Now() const override { return now_; }
+
+ base::Time now_;
+};
+
+class TestHostDiscardableSharedMemoryManager
+ : public HostDiscardableSharedMemoryManager {
+ public:
+ TestHostDiscardableSharedMemoryManager()
+ : enforce_memory_policy_pending_(false) {}
+
+ void SetNow(base::Time now) { now_ = now; }
+
+ void set_enforce_memory_policy_pending(bool enforce_memory_policy_pending) {
+ enforce_memory_policy_pending_ = enforce_memory_policy_pending;
+ }
+ bool enforce_memory_policy_pending() const {
+ return enforce_memory_policy_pending_;
+ }
+
+ private:
+ // Overriden from HostDiscardableSharedMemoryManager:
+ base::Time Now() const override { return now_; }
+ void ScheduleEnforceMemoryPolicy() override {
+ enforce_memory_policy_pending_ = true;
+ }
+
+ base::Time now_;
+ bool enforce_memory_policy_pending_;
+};
+
+class HostDiscardableSharedMemoryManagerTest : public testing::Test {
+ protected:
+ // Overridden from testing::Test:
+ void SetUp() override {
+ manager_.reset(new TestHostDiscardableSharedMemoryManager);
+ }
+
+ scoped_ptr<TestHostDiscardableSharedMemoryManager> manager_;
+};
+
+TEST_F(HostDiscardableSharedMemoryManagerTest, AllocateForChild) {
+ const int kDataSize = 1024;
+ uint8 data[kDataSize];
+ memset(data, 0x80, kDataSize);
+
+ base::SharedMemoryHandle shared_handle;
+ manager_->AllocateLockedDiscardableSharedMemoryForChild(
+ base::GetCurrentProcessHandle(), kDataSize, &shared_handle);
+ ASSERT_TRUE(base::SharedMemory::IsHandleValid(shared_handle));
+
+ TestDiscardableSharedMemory memory(shared_handle);
+ bool rv = memory.Map(kDataSize);
+ ASSERT_TRUE(rv);
+
+ memcpy(memory.memory(), data, kDataSize);
+ memory.SetNow(base::Time::FromDoubleT(1));
+ memory.Unlock();
+
+ ASSERT_TRUE(memory.Lock());
+ EXPECT_EQ(memcmp(data, memory.memory(), kDataSize), 0);
+ memory.Unlock();
+}
+
+TEST_F(HostDiscardableSharedMemoryManagerTest, Purge) {
+ const int kDataSize = 1024;
+
+ base::SharedMemoryHandle shared_handle1;
+ manager_->AllocateLockedDiscardableSharedMemoryForChild(
+ base::GetCurrentProcessHandle(), kDataSize, &shared_handle1);
+ ASSERT_TRUE(base::SharedMemory::IsHandleValid(shared_handle1));
+
+ TestDiscardableSharedMemory memory1(shared_handle1);
+ bool rv = memory1.Map(kDataSize);
+ ASSERT_TRUE(rv);
+
+ base::SharedMemoryHandle shared_handle2;
+ manager_->AllocateLockedDiscardableSharedMemoryForChild(
+ base::GetCurrentProcessHandle(), kDataSize, &shared_handle2);
+ ASSERT_TRUE(base::SharedMemory::IsHandleValid(shared_handle2));
+
+ TestDiscardableSharedMemory memory2(shared_handle2);
+ rv = memory2.Map(kDataSize);
+ ASSERT_TRUE(rv);
+
+ // Enough memory for both allocations.
+ manager_->SetNow(base::Time::FromDoubleT(1));
+ manager_->SetMemoryLimit(memory1.mapped_size() + memory2.mapped_size());
+
+ memory1.SetNow(base::Time::FromDoubleT(2));
+ memory1.Unlock();
+ memory2.SetNow(base::Time::FromDoubleT(2));
+ memory2.Unlock();
+
+ // Manager should not have to schedule another call to EnforceMemoryPolicy().
+ manager_->SetNow(base::Time::FromDoubleT(3));
+ manager_->EnforceMemoryPolicy();
+ EXPECT_FALSE(manager_->enforce_memory_policy_pending());
+
+ // Memory should still be resident.
+ EXPECT_TRUE(memory1.IsMemoryResident());
+ EXPECT_TRUE(memory2.IsMemoryResident());
+
+ rv = memory1.Lock();
+ EXPECT_TRUE(rv);
+ rv = memory2.Lock();
+ EXPECT_TRUE(rv);
+
+ memory1.SetNow(base::Time::FromDoubleT(4));
+ memory1.Unlock();
+ memory2.SetNow(base::Time::FromDoubleT(5));
+ memory2.Unlock();
+
+ // Just enough memory for one allocation.
+ manager_->SetNow(base::Time::FromDoubleT(6));
+ manager_->SetMemoryLimit(memory2.mapped_size());
+ EXPECT_FALSE(manager_->enforce_memory_policy_pending());
+
+ // LRU allocation should still be resident.
+ EXPECT_FALSE(memory1.IsMemoryResident());
+ EXPECT_TRUE(memory2.IsMemoryResident());
+
+ rv = memory1.Lock();
+ EXPECT_FALSE(rv);
+ rv = memory2.Lock();
+ EXPECT_TRUE(rv);
+}
+
+TEST_F(HostDiscardableSharedMemoryManagerTest, EnforceMemoryPolicy) {
+ const int kDataSize = 1024;
+
+ base::SharedMemoryHandle shared_handle;
+ manager_->AllocateLockedDiscardableSharedMemoryForChild(
+ base::GetCurrentProcessHandle(), kDataSize, &shared_handle);
+ ASSERT_TRUE(base::SharedMemory::IsHandleValid(shared_handle));
+
+ TestDiscardableSharedMemory memory(shared_handle);
+ bool rv = memory.Map(kDataSize);
+ ASSERT_TRUE(rv);
+
+ // Not enough memory for one allocation.
+ manager_->SetNow(base::Time::FromDoubleT(1));
+ manager_->SetMemoryLimit(memory.mapped_size() - 1);
+ // We need to enforce memory policy as our memory usage is currently above
+ // the limit.
+ EXPECT_TRUE(manager_->enforce_memory_policy_pending());
+
+ manager_->set_enforce_memory_policy_pending(false);
+ manager_->SetNow(base::Time::FromDoubleT(2));
+ manager_->EnforceMemoryPolicy();
+ // Still need to enforce memory policy as nothing can be purged.
+ EXPECT_TRUE(manager_->enforce_memory_policy_pending());
+
+ memory.SetNow(base::Time::FromDoubleT(3));
+ memory.Unlock();
+
+ manager_->set_enforce_memory_policy_pending(false);
+ manager_->SetNow(base::Time::FromDoubleT(4));
+ manager_->EnforceMemoryPolicy();
+ // Memory policy should have successfully been enforced.
+ EXPECT_FALSE(manager_->enforce_memory_policy_pending());
+
+ EXPECT_FALSE(memory.Lock());
+}
+
+} // namespace
+} // namespace content
diff --git a/content/content_child.gypi b/content/content_child.gypi
index 7e2b109..8dbc7a2 100644
--- a/content/content_child.gypi
+++ b/content/content_child.gypi
@@ -40,6 +40,10 @@
'child/blink_platform_impl.h',
'child/browser_font_resource_trusted.cc',
'child/browser_font_resource_trusted.h',
+ 'child/child_discardable_shared_memory_manager.cc',
+ 'child/child_discardable_shared_memory_manager.h',
+ 'child/child_gpu_memory_buffer_manager.cc',
+ 'child/child_gpu_memory_buffer_manager.h',
'child/child_histogram_message_filter.cc',
'child/child_histogram_message_filter.h',
'child/child_message_filter.cc',
@@ -48,8 +52,6 @@
'child/child_process.h',
'child/child_resource_message_filter.cc',
'child/child_resource_message_filter.h',
- 'child/child_gpu_memory_buffer_manager.cc',
- 'child/child_gpu_memory_buffer_manager.h',
'child/child_shared_bitmap_manager.cc',
'child/child_shared_bitmap_manager.h',
'child/child_thread.cc',
diff --git a/content/content_common.gypi b/content/content_common.gypi
index 4d625ee..ab93e54 100644
--- a/content/content_common.gypi
+++ b/content/content_common.gypi
@@ -330,6 +330,8 @@
'common/gpu/sync_point_manager.h',
'common/handle_enumerator_win.cc',
'common/handle_enumerator_win.h',
+ 'common/host_discardable_shared_memory_manager.cc',
+ 'common/host_discardable_shared_memory_manager.h',
'common/host_shared_bitmap_manager.cc',
'common/host_shared_bitmap_manager.h',
'common/image_messages.h',
diff --git a/content/content_tests.gypi b/content/content_tests.gypi
index 3fa41e3..ab604b4 100644
--- a/content/content_tests.gypi
+++ b/content/content_tests.gypi
@@ -670,6 +670,7 @@
'common/dom_storage/dom_storage_map_unittest.cc',
'common/fileapi/file_system_util_unittest.cc',
'common/gpu/gpu_memory_manager_unittest.cc',
+ 'common/host_discardable_shared_memory_manager_unittest.cc',
'common/host_shared_bitmap_manager_unittest.cc',
'common/indexed_db/indexed_db_key_unittest.cc',
'common/input/gesture_event_stream_validator_unittest.cc',
diff --git a/content/renderer/render_thread_impl.cc b/content/renderer/render_thread_impl.cc
index 1e31fa8..7677ac5 100644
--- a/content/renderer/render_thread_impl.cc
+++ b/content/renderer/render_thread_impl.cc
@@ -16,6 +16,7 @@
#include "base/logging.h"
#include "base/memory/discardable_memory.h"
#include "base/memory/discardable_memory_emulated.h"
+#include "base/memory/discardable_memory_shmem_allocator.h"
#include "base/memory/shared_memory.h"
#include "base/metrics/field_trial.h"
#include "base/metrics/histogram.h"
@@ -35,6 +36,7 @@
#include "cc/resources/raster_worker_pool.h"
#include "content/child/appcache/appcache_dispatcher.h"
#include "content/child/appcache/appcache_frontend_impl.h"
+#include "content/child/child_discardable_shared_memory_manager.h"
#include "content/child/child_gpu_memory_buffer_manager.h"
#include "content/child/child_histogram_message_filter.h"
#include "content/child/content_child_helpers.h"
@@ -565,6 +567,9 @@ void RenderThreadImpl::Init() {
cc::RasterWorkerPool::SetNumRasterThreads(num_raster_threads);
}
+ base::DiscardableMemoryShmemAllocator::SetInstance(
+ ChildThread::discardable_shared_memory_manager());
+
service_registry()->AddService<RenderFrameSetup>(
base::Bind(CreateRenderFrameSetup));
@@ -1011,6 +1016,7 @@ void RenderThreadImpl::IdleHandler() {
}
base::allocator::ReleaseFreeMemory();
+ base::DiscardableMemory::ReleaseFreeMemory();
// Continue the idle timer if the webkit shared timer is not suspended or
// something is left to do.