summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorvollick@chromium.org <vollick@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-10-30 16:33:30 +0000
committervollick@chromium.org <vollick@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-10-30 16:33:30 +0000
commitcef6c76ff40bf74edbcf8df0a19dc0c08fb7daca (patch)
tree56d1e3257dc3bea4d04dbaa36b84b4fd655c7c50
parent1bc20daed0a965dadbe4a140ee53057a1dac280a (diff)
downloadchromium_src-cef6c76ff40bf74edbcf8df0a19dc0c08fb7daca.zip
chromium_src-cef6c76ff40bf74edbcf8df0a19dc0c08fb7daca.tar.gz
chromium_src-cef6c76ff40bf74edbcf8df0a19dc0c08fb7daca.tar.bz2
Add discardable memory emulation for non-android/mac platforms
Adds support for emulated discardable memory. The memory is managed by a provider which listens for memory pressure notifications from the platform. Currently, only android pushes these notifications, but in future patches, we will apply pressure on other platforms in certain situations (e.g., when a tab gets backgrounded). BUG=237681 Review URL: https://codereview.chromium.org/17106004 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@231845 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--base/base.gyp6
-rw-r--r--base/base.gypi12
-rw-r--r--base/containers/mru_cache.h9
-rw-r--r--base/memory/discardable_memory.cc39
-rw-r--r--base/memory/discardable_memory.h8
-rw-r--r--base/memory/discardable_memory_android.cc2
-rw-r--r--base/memory/discardable_memory_emulated.cc85
-rw-r--r--base/memory/discardable_memory_mac.cc2
-rw-r--r--base/memory/discardable_memory_provider.cc240
-rw-r--r--base/memory/discardable_memory_provider.h151
-rw-r--r--base/memory/discardable_memory_provider_unittest.cc347
-rw-r--r--base/memory/discardable_memory_unittest.cc24
-rw-r--r--build/common.gypi17
-rw-r--r--skia/ext/SkDiscardableMemory_chrome.cc2
-rw-r--r--webkit/child/webkitplatformsupport_child_impl.cc2
15 files changed, 880 insertions, 66 deletions
diff --git a/base/base.gyp b/base/base.gyp
index 5dfe08d..baf8cd9 100644
--- a/base/base.gyp
+++ b/base/base.gyp
@@ -530,6 +530,7 @@
'md5_unittest.cc',
'memory/aligned_memory_unittest.cc',
'memory/discardable_memory_unittest.cc',
+ 'memory/discardable_memory_provider_unittest.cc',
'memory/linked_ptr_unittest.cc',
'memory/ref_counted_memory_unittest.cc',
'memory/ref_counted_unittest.cc',
@@ -830,6 +831,11 @@
'third_party/nspr/nspr.gyp:nspr',
],
}],
+ ['<(native_discardable_memory)==1', {
+ 'sources!': [
+ 'memory/discardable_memory_provider_unittest.cc',
+ ],
+ }],
], # conditions
'target_conditions': [
['OS == "ios" and _toolset != "host"', {
diff --git a/base/base.gypi b/base/base.gypi
index b60ffa6..eeda3e1 100644
--- a/base/base.gypi
+++ b/base/base.gypi
@@ -283,10 +283,12 @@
'mac/sdk_forward_declarations.h',
'memory/aligned_memory.cc',
'memory/aligned_memory.h',
- 'memory/discardable_memory.cc',
'memory/discardable_memory.h',
'memory/discardable_memory_android.cc',
+ 'memory/discardable_memory_emulated.cc',
'memory/discardable_memory_mac.cc',
+ 'memory/discardable_memory_provider.cc',
+ 'memory/discardable_memory_provider.h',
'memory/linked_ptr.h',
'memory/manual_constructor.h',
'memory/memory_pressure_listener.cc',
@@ -847,6 +849,14 @@
'sources/': [ ['exclude', '^win/'] ],
},
],
+ ['<(native_discardable_memory)==1', {
+ 'sources!': [
+ 'memory/discardable_memory_emulated.cc',
+ 'memory/discardable_memory_provider.cc',
+ 'memory/discardable_memory_provider.h',
+ ],
+ },
+ ],
['OS != "android" or >(nacl_untrusted_build)==1', {
'sources/': [ ['exclude', '^android/'] ],
},
diff --git a/base/containers/mru_cache.h b/base/containers/mru_cache.h
index e59e909..15ea2fc 100644
--- a/base/containers/mru_cache.h
+++ b/base/containers/mru_cache.h
@@ -123,8 +123,6 @@ class MRUCacheBase {
// Retrieves the payload associated with a given key and returns it via
// result without affecting the ordering (unlike Get).
- //
- // TODO(brettw) We may want a const version of this function in the future.
iterator Peek(const KeyType& key) {
typename KeyIndex::const_iterator index_iter = index_.find(key);
if (index_iter == index_.end())
@@ -132,6 +130,13 @@ class MRUCacheBase {
return index_iter->second;
}
+ const_iterator Peek(const KeyType& key) const {
+ typename KeyIndex::const_iterator index_iter = index_.find(key);
+ if (index_iter == index_.end())
+ return end();
+ return index_iter->second;
+ }
+
// Erases the item referenced by the given iterator. An iterator to the item
// following it will be returned. The iterator must be valid.
iterator Erase(iterator pos) {
diff --git a/base/memory/discardable_memory.cc b/base/memory/discardable_memory.cc
deleted file mode 100644
index 5dcfc1c..0000000
--- a/base/memory/discardable_memory.cc
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright (c) 2013 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.h"
-
-#include "base/compiler_specific.h"
-#include "base/logging.h"
-
-namespace base {
-
-// Stub implementations for platforms that don't support discardable memory.
-
-#if !defined(OS_ANDROID) && !defined(OS_MACOSX)
-
-// static
-bool DiscardableMemory::Supported() {
- return false;
-}
-
-// static
-scoped_ptr<DiscardableMemory> DiscardableMemory::CreateLockedMemory(
- size_t size) {
- return scoped_ptr<DiscardableMemory>();
-}
-
-// static
-bool DiscardableMemory::PurgeForTestingSupported() {
- return false;
-}
-
-// static
-void DiscardableMemory::PurgeForTesting() {
- NOTIMPLEMENTED();
-}
-
-#endif // OS_*
-
-} // namespace base
diff --git a/base/memory/discardable_memory.h b/base/memory/discardable_memory.h
index c6a8d82..d8fd4f6 100644
--- a/base/memory/discardable_memory.h
+++ b/base/memory/discardable_memory.h
@@ -38,6 +38,9 @@ enum LockDiscardableMemoryStatus {
// - Because of memory alignment, the amount of memory allocated can be
// larger than the requested memory size. It is not very efficient for
// small allocations.
+// - A discardable memory instance is not thread safe. It is the
+// responsibility of users of discardable memory to ensure there are no
+// races.
//
// References:
// - Linux: http://lwn.net/Articles/452035/
@@ -48,8 +51,9 @@ class BASE_EXPORT DiscardableMemory {
public:
virtual ~DiscardableMemory() {}
- // Returns whether the system supports discardable memory.
- static bool Supported();
+ // Check whether the system supports discardable memory natively. Returns
+ // false if the support is emulated.
+ static bool SupportedNatively();
static scoped_ptr<DiscardableMemory> CreateLockedMemory(size_t size);
diff --git a/base/memory/discardable_memory_android.cc b/base/memory/discardable_memory_android.cc
index b88a136..3850439 100644
--- a/base/memory/discardable_memory_android.cc
+++ b/base/memory/discardable_memory_android.cc
@@ -135,7 +135,7 @@ class DiscardableMemoryAndroid : public DiscardableMemory {
} // namespace
// static
-bool DiscardableMemory::Supported() {
+bool DiscardableMemory::SupportedNatively() {
return true;
}
diff --git a/base/memory/discardable_memory_emulated.cc b/base/memory/discardable_memory_emulated.cc
new file mode 100644
index 0000000..2462462
--- /dev/null
+++ b/base/memory/discardable_memory_emulated.cc
@@ -0,0 +1,85 @@
+// Copyright 2013 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.h"
+
+#include "base/memory/discardable_memory_provider.h"
+
+using base::internal::DiscardableMemoryProvider;
+
+namespace base {
+namespace {
+
+class DiscardableMemoryEmulated : public DiscardableMemory {
+ public:
+ explicit DiscardableMemoryEmulated(size_t size) : is_locked_(false) {
+ DiscardableMemoryProvider::GetInstance()->Register(this, size);
+ }
+
+ virtual ~DiscardableMemoryEmulated() {
+ if (is_locked_)
+ Unlock();
+ DiscardableMemoryProvider::GetInstance()->Unregister(this);
+ }
+
+ // DiscardableMemory:
+ virtual LockDiscardableMemoryStatus Lock() OVERRIDE {
+ DCHECK(!is_locked_);
+
+ bool purged = false;
+ memory_ = DiscardableMemoryProvider::GetInstance()->Acquire(this, &purged);
+ if (!memory_)
+ return DISCARDABLE_MEMORY_FAILED;
+
+ is_locked_ = true;
+ return purged ? DISCARDABLE_MEMORY_PURGED : DISCARDABLE_MEMORY_SUCCESS;
+ }
+
+ virtual void Unlock() OVERRIDE {
+ DCHECK(is_locked_);
+ DiscardableMemoryProvider::GetInstance()->Release(this, memory_.Pass());
+ is_locked_ = false;
+ }
+
+ virtual void* Memory() const OVERRIDE {
+ DCHECK(memory_);
+ return memory_.get();
+ }
+
+ private:
+ scoped_ptr<uint8, FreeDeleter> memory_;
+ bool is_locked_;
+
+ DISALLOW_COPY_AND_ASSIGN(DiscardableMemoryEmulated);
+};
+
+} // namespace
+
+// static
+bool DiscardableMemory::SupportedNatively() {
+ return false;
+}
+
+// static
+scoped_ptr<DiscardableMemory> DiscardableMemory::CreateLockedMemory(
+ size_t size) {
+ scoped_ptr<DiscardableMemory> memory(new DiscardableMemoryEmulated(size));
+ if (!memory)
+ return scoped_ptr<DiscardableMemory>();
+ if (memory->Lock() != DISCARDABLE_MEMORY_PURGED)
+ return scoped_ptr<DiscardableMemory>();
+ return memory.Pass();
+}
+
+// static
+bool DiscardableMemory::PurgeForTestingSupported() {
+ return true;
+}
+
+// static
+void DiscardableMemory::PurgeForTesting() {
+ DiscardableMemoryProvider::GetInstance()->PurgeAll();
+}
+
+} // namespace base
diff --git a/base/memory/discardable_memory_mac.cc b/base/memory/discardable_memory_mac.cc
index a7f47bb..aa68235 100644
--- a/base/memory/discardable_memory_mac.cc
+++ b/base/memory/discardable_memory_mac.cc
@@ -75,7 +75,7 @@ class DiscardableMemoryMac : public DiscardableMemory {
} // namespace
// static
-bool DiscardableMemory::Supported() {
+bool DiscardableMemory::SupportedNatively() {
return true;
}
diff --git a/base/memory/discardable_memory_provider.cc b/base/memory/discardable_memory_provider.cc
new file mode 100644
index 0000000..a0ba0ad
--- /dev/null
+++ b/base/memory/discardable_memory_provider.cc
@@ -0,0 +1,240 @@
+// Copyright 2013 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_provider.h"
+
+#include "base/bind.h"
+#include "base/containers/hash_tables.h"
+#include "base/containers/mru_cache.h"
+#include "base/debug/trace_event.h"
+#include "base/lazy_instance.h"
+#include "base/memory/discardable_memory.h"
+#include "base/synchronization/lock.h"
+#include "base/sys_info.h"
+
+namespace base {
+namespace internal {
+
+namespace {
+
+static base::LazyInstance<DiscardableMemoryProvider>::Leaky g_provider =
+ LAZY_INSTANCE_INITIALIZER;
+
+// If this is given a valid value via SetInstanceForTest, this pointer will be
+// returned by GetInstance rather than |g_provider|.
+static DiscardableMemoryProvider* g_provider_for_test = NULL;
+
+// This is admittedly pretty magical. It's approximately enough memory for two
+// 2560x1600 images.
+static const size_t kDefaultDiscardableMemoryLimit = 32 * 1024 * 1024;
+static const size_t kDefaultBytesToReclaimUnderModeratePressure =
+ kDefaultDiscardableMemoryLimit / 2;
+
+} // namespace
+
+DiscardableMemoryProvider::DiscardableMemoryProvider()
+ : allocations_(AllocationMap::NO_AUTO_EVICT),
+ bytes_allocated_(0),
+ discardable_memory_limit_(kDefaultDiscardableMemoryLimit),
+ bytes_to_reclaim_under_moderate_pressure_(
+ kDefaultBytesToReclaimUnderModeratePressure),
+ memory_pressure_listener_(
+ base::Bind(&DiscardableMemoryProvider::NotifyMemoryPressure)) {
+}
+
+DiscardableMemoryProvider::~DiscardableMemoryProvider() {
+ DCHECK(allocations_.empty());
+ DCHECK_EQ(0u, bytes_allocated_);
+}
+
+// static
+DiscardableMemoryProvider* DiscardableMemoryProvider::GetInstance() {
+ if (g_provider_for_test)
+ return g_provider_for_test;
+ return g_provider.Pointer();
+}
+
+// static
+void DiscardableMemoryProvider::SetInstanceForTest(
+ DiscardableMemoryProvider* provider) {
+ g_provider_for_test = provider;
+}
+
+// static
+void DiscardableMemoryProvider::NotifyMemoryPressure(
+ MemoryPressureListener::MemoryPressureLevel pressure_level) {
+ switch (pressure_level) {
+ case MemoryPressureListener::MEMORY_PRESSURE_MODERATE:
+ DiscardableMemoryProvider::GetInstance()->Purge();
+ return;
+ case MemoryPressureListener::MEMORY_PRESSURE_CRITICAL:
+ DiscardableMemoryProvider::GetInstance()->PurgeAll();
+ return;
+ }
+
+ NOTREACHED();
+}
+
+void DiscardableMemoryProvider::SetDiscardableMemoryLimit(size_t bytes) {
+ AutoLock lock(lock_);
+ discardable_memory_limit_ = bytes;
+ EnforcePolicyWithLockAcquired();
+}
+
+void DiscardableMemoryProvider::SetBytesToReclaimUnderModeratePressure(
+ size_t bytes) {
+ AutoLock lock(lock_);
+ bytes_to_reclaim_under_moderate_pressure_ = bytes;
+ EnforcePolicyWithLockAcquired();
+}
+
+void DiscardableMemoryProvider::Register(
+ const DiscardableMemory* discardable, size_t bytes) {
+ AutoLock lock(lock_);
+ DCHECK(allocations_.Peek(discardable) == allocations_.end());
+ allocations_.Put(discardable, Allocation(bytes));
+}
+
+void DiscardableMemoryProvider::Unregister(
+ const DiscardableMemory* discardable) {
+ AutoLock lock(lock_);
+ AllocationMap::iterator it = allocations_.Peek(discardable);
+ if (it == allocations_.end())
+ return;
+
+ if (it->second.memory) {
+ size_t bytes = it->second.bytes;
+ DCHECK_LE(bytes, bytes_allocated_);
+ bytes_allocated_ -= bytes;
+ free(it->second.memory);
+ }
+ allocations_.Erase(it);
+}
+
+scoped_ptr<uint8, FreeDeleter> DiscardableMemoryProvider::Acquire(
+ const DiscardableMemory* discardable,
+ bool* purged) {
+ AutoLock lock(lock_);
+ // NB: |allocations_| is an MRU cache, and use of |Get| here updates that
+ // cache.
+ AllocationMap::iterator it = allocations_.Get(discardable);
+ CHECK(it != allocations_.end());
+
+ if (it->second.memory) {
+ scoped_ptr<uint8, FreeDeleter> memory(it->second.memory);
+ it->second.memory = NULL;
+ *purged = false;
+ return memory.Pass();
+ }
+
+ size_t bytes = it->second.bytes;
+ if (!bytes)
+ return scoped_ptr<uint8, FreeDeleter>();
+
+ if (discardable_memory_limit_) {
+ size_t limit = 0;
+ if (bytes < discardable_memory_limit_)
+ limit = discardable_memory_limit_ - bytes;
+
+ PurgeLRUWithLockAcquiredUntilUsageIsWithin(limit);
+ }
+
+ bytes_allocated_ += bytes;
+ *purged = true;
+ return scoped_ptr<uint8, FreeDeleter>(static_cast<uint8*>(malloc(bytes)));
+}
+
+void DiscardableMemoryProvider::Release(
+ const DiscardableMemory* discardable,
+ scoped_ptr<uint8, FreeDeleter> memory) {
+ AutoLock lock(lock_);
+ // NB: |allocations_| is an MRU cache, and use of |Get| here updates that
+ // cache.
+ AllocationMap::iterator it = allocations_.Get(discardable);
+ CHECK(it != allocations_.end());
+
+ DCHECK(!it->second.memory);
+ it->second.memory = memory.release();
+
+ EnforcePolicyWithLockAcquired();
+}
+
+void DiscardableMemoryProvider::PurgeAll() {
+ AutoLock lock(lock_);
+ PurgeLRUWithLockAcquiredUntilUsageIsWithin(0);
+}
+
+bool DiscardableMemoryProvider::IsRegisteredForTest(
+ const DiscardableMemory* discardable) const {
+ AutoLock lock(lock_);
+ AllocationMap::const_iterator it = allocations_.Peek(discardable);
+ return it != allocations_.end();
+}
+
+bool DiscardableMemoryProvider::CanBePurgedForTest(
+ const DiscardableMemory* discardable) const {
+ AutoLock lock(lock_);
+ AllocationMap::const_iterator it = allocations_.Peek(discardable);
+ return it != allocations_.end() && it->second.memory;
+}
+
+size_t DiscardableMemoryProvider::GetBytesAllocatedForTest() const {
+ AutoLock lock(lock_);
+ return bytes_allocated_;
+}
+
+void DiscardableMemoryProvider::Purge() {
+ AutoLock lock(lock_);
+
+ if (bytes_to_reclaim_under_moderate_pressure_ == 0)
+ return;
+
+ size_t limit = 0;
+ if (bytes_to_reclaim_under_moderate_pressure_ < discardable_memory_limit_)
+ limit = bytes_allocated_ - bytes_to_reclaim_under_moderate_pressure_;
+
+ PurgeLRUWithLockAcquiredUntilUsageIsWithin(limit);
+}
+
+void DiscardableMemoryProvider::PurgeLRUWithLockAcquiredUntilUsageIsWithin(
+ size_t limit) {
+ TRACE_EVENT1(
+ "base",
+ "DiscardableMemoryProvider::PurgeLRUWithLockAcquiredUntilUsageIsWithin",
+ "limit", limit);
+
+ lock_.AssertAcquired();
+
+ for (AllocationMap::reverse_iterator it = allocations_.rbegin();
+ it != allocations_.rend();
+ ++it) {
+ if (bytes_allocated_ <= limit)
+ break;
+ if (!it->second.memory)
+ continue;
+
+ size_t bytes = it->second.bytes;
+ DCHECK_LE(bytes, bytes_allocated_);
+ bytes_allocated_ -= bytes;
+ free(it->second.memory);
+ it->second.memory = NULL;
+ }
+}
+
+void DiscardableMemoryProvider::EnforcePolicyWithLockAcquired() {
+ lock_.AssertAcquired();
+
+ bool exceeded_bound = bytes_allocated_ > discardable_memory_limit_;
+ if (!exceeded_bound || !bytes_to_reclaim_under_moderate_pressure_)
+ return;
+
+ size_t limit = 0;
+ if (bytes_to_reclaim_under_moderate_pressure_ < discardable_memory_limit_)
+ limit = bytes_allocated_ - bytes_to_reclaim_under_moderate_pressure_;
+
+ PurgeLRUWithLockAcquiredUntilUsageIsWithin(limit);
+}
+
+} // namespace internal
+} // namespace base
diff --git a/base/memory/discardable_memory_provider.h b/base/memory/discardable_memory_provider.h
new file mode 100644
index 0000000..005cc1d
--- /dev/null
+++ b/base/memory/discardable_memory_provider.h
@@ -0,0 +1,151 @@
+// Copyright 2013 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_PROVIDER_H_
+#define BASE_MEMORY_DISCARDABLE_MEMORY_PROVIDER_H_
+
+#include "base/base_export.h"
+#include "base/containers/hash_tables.h"
+#include "base/containers/mru_cache.h"
+#include "base/memory/memory_pressure_listener.h"
+#include "base/synchronization/lock.h"
+
+namespace base {
+class DiscardableMemory;
+} // namespace base
+
+#if defined(COMPILER_GCC)
+namespace BASE_HASH_NAMESPACE {
+template <>
+struct hash<const base::DiscardableMemory*> {
+ size_t operator()(const base::DiscardableMemory* ptr) const {
+ return hash<size_t>()(reinterpret_cast<size_t>(ptr));
+ }
+};
+} // namespace BASE_HASH_NAMESPACE
+#endif // COMPILER
+
+namespace base {
+namespace internal {
+
+// The DiscardableMemoryProvider manages a collection of emulated
+// DiscardableMemory instances. It is used on platforms that do not support
+// discardable memory natively. It keeps track of all DiscardableMemory
+// instances (in case they need to be purged), and the total amount of
+// allocated memory (in case this forces a purge).
+//
+// When notified of memory pressure, the provider either purges the LRU
+// memory -- if the pressure is moderate -- or all discardable memory
+// if the pressure is critical.
+//
+// NB - this class is an implementation detail. It has been exposed for testing
+// purposes. You should not need to use this class directly.
+class BASE_EXPORT_PRIVATE DiscardableMemoryProvider {
+ public:
+ DiscardableMemoryProvider();
+ ~DiscardableMemoryProvider();
+
+ static DiscardableMemoryProvider* GetInstance();
+
+ // Sets the instance of DiscardableMemoryProvider to be returned by
+ // GetInstance. This should only be used by tests and must be called
+ // prior to GetInstance(). The ownership of the given provider is
+ // retained by the caller.
+ static void SetInstanceForTest(DiscardableMemoryProvider* provider);
+
+ // The maximum number of bytes of discardable memory that may be allocated
+ // before we assume moderate memory pressure. If this amount is zero, it is
+ // interpreted as having no limit at all.
+ void SetDiscardableMemoryLimit(size_t bytes);
+
+ // Sets the amount of memory to reclaim when we're under moderate pressure.
+ void SetBytesToReclaimUnderModeratePressure(size_t bytes);
+
+ // Adds the given discardable memory to the provider's collection.
+ void Register(const DiscardableMemory* discardable, size_t bytes);
+
+ // Removes the given discardable memory from the provider's collection.
+ void Unregister(const DiscardableMemory* discardable);
+
+ // Returns NULL if an error occurred. Otherwise, returns the backing buffer
+ // and sets |purged| to indicate whether or not the backing buffer has been
+ // purged since last use.
+ scoped_ptr<uint8, FreeDeleter> Acquire(
+ const DiscardableMemory* discardable, bool* purged);
+
+ // Release a previously acquired backing buffer. This gives the buffer back
+ // to the provider where it can be purged if necessary.
+ void Release(const DiscardableMemory* discardable,
+ scoped_ptr<uint8, FreeDeleter> memory);
+
+ // Purges all discardable memory.
+ void PurgeAll();
+
+ // Returns true if discardable memory has been added to the provider's
+ // collection. This should only be used by tests.
+ bool IsRegisteredForTest(const DiscardableMemory* discardable) const;
+
+ // Returns true if discardable memory can be purged. This should only
+ // be used by tests.
+ bool CanBePurgedForTest(const DiscardableMemory* discardable) const;
+
+ // Returns total amount of allocated discardable memory. This should only
+ // be used by tests.
+ size_t GetBytesAllocatedForTest() const;
+
+ private:
+ struct Allocation {
+ explicit Allocation(size_t bytes)
+ : bytes(bytes),
+ memory(NULL) {
+ }
+
+ size_t bytes;
+ uint8* memory;
+ };
+ typedef HashingMRUCache<const DiscardableMemory*, Allocation> AllocationMap;
+
+ // This can be called as a hint that the system is under memory pressure.
+ static void NotifyMemoryPressure(
+ MemoryPressureListener::MemoryPressureLevel pressure_level);
+
+ // Purges least recently used memory based on the value of
+ // |bytes_to_reclaim_under_moderate_pressure_|.
+ void Purge();
+
+ // Purges least recently used memory until usage is less or equal to |limit|.
+ // Caller must acquire |lock_| prior to calling this function.
+ void PurgeLRUWithLockAcquiredUntilUsageIsWithin(size_t limit);
+
+ // Ensures that we don't allocate beyond our memory limit.
+ // Caller must acquire |lock_| prior to calling this function.
+ void EnforcePolicyWithLockAcquired();
+
+ // Needs to be held when accessing members.
+ mutable Lock lock_;
+
+ // A MRU cache of all allocated bits of discardable memory. Used for purging.
+ AllocationMap allocations_;
+
+ // The total amount of allocated discardable memory.
+ size_t bytes_allocated_;
+
+ // The maximum number of bytes of discardable memory that may be allocated
+ // before we assume moderate memory pressure.
+ size_t discardable_memory_limit_;
+
+ // Under moderate memory pressure, we will purge this amount of memory.
+ size_t bytes_to_reclaim_under_moderate_pressure_;
+
+ // Allows us to be respond when the system reports that it is under memory
+ // pressure.
+ MemoryPressureListener memory_pressure_listener_;
+
+ DISALLOW_COPY_AND_ASSIGN(DiscardableMemoryProvider);
+};
+
+} // namespace internal
+} // namespace base
+
+#endif // BASE_MEMORY_DISCARDABLE_MEMORY_PROVIDER_H_
diff --git a/base/memory/discardable_memory_provider_unittest.cc b/base/memory/discardable_memory_provider_unittest.cc
new file mode 100644
index 0000000..8c1aee8
--- /dev/null
+++ b/base/memory/discardable_memory_provider_unittest.cc
@@ -0,0 +1,347 @@
+// Copyright 2013 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_provider.h"
+
+#include "base/bind.h"
+#include "base/memory/discardable_memory.h"
+#include "base/run_loop.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/thread.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::internal::DiscardableMemoryProvider;
+
+namespace base {
+
+class DiscardableMemoryProviderTestBase {
+ public:
+ DiscardableMemoryProviderTestBase()
+ : message_loop_(MessageLoop::TYPE_IO),
+ provider_(new DiscardableMemoryProvider) {
+ // We set a provider here for two reasons:
+ // 1. It ensures that one test cannot affect the next, and
+ // 2. Since the provider listens for pressure notifications on the thread
+ // it was created on, if we create it on the test thread, we can run
+ // the test thread's message loop until idle when we want to process
+ // one of these notifications.
+ DiscardableMemoryProvider::SetInstanceForTest(provider_.get());
+ }
+
+ virtual ~DiscardableMemoryProviderTestBase() {
+ DiscardableMemoryProvider::SetInstanceForTest(NULL);
+ }
+
+ protected:
+ bool IsRegistered(const DiscardableMemory* discardable) {
+ return DiscardableMemoryProvider::GetInstance()->IsRegisteredForTest(
+ discardable);
+ }
+
+ bool CanBePurged(const DiscardableMemory* discardable) {
+ return DiscardableMemoryProvider::GetInstance()->CanBePurgedForTest(
+ discardable);
+ }
+
+ size_t BytesAllocated() const {
+ return DiscardableMemoryProvider::GetInstance()->
+ GetBytesAllocatedForTest();
+ }
+
+ void* Memory(const DiscardableMemory* discardable) const {
+ return discardable->Memory();
+ }
+
+ void SetDiscardableMemoryLimit(size_t bytes) {
+ DiscardableMemoryProvider::GetInstance()->
+ SetDiscardableMemoryLimit(bytes);
+ }
+
+ void SetBytesToReclaimUnderModeratePressure(size_t bytes) {
+ DiscardableMemoryProvider::GetInstance()->
+ SetBytesToReclaimUnderModeratePressure(bytes);
+ }
+
+ private:
+ MessageLoop message_loop_;
+ scoped_ptr<DiscardableMemoryProvider> provider_;
+};
+
+class DiscardableMemoryProviderTest
+ : public DiscardableMemoryProviderTestBase,
+ public testing::Test {
+ public:
+ DiscardableMemoryProviderTest() {}
+};
+
+TEST_F(DiscardableMemoryProviderTest, CreateLockedMemory) {
+ size_t size = 1024;
+ const scoped_ptr<DiscardableMemory> discardable(
+ DiscardableMemory::CreateLockedMemory(size));
+ EXPECT_TRUE(IsRegistered(discardable.get()));
+ EXPECT_NE(static_cast<void*>(NULL), Memory(discardable.get()));
+ EXPECT_EQ(1024u, BytesAllocated());
+ EXPECT_FALSE(CanBePurged(discardable.get()));
+}
+
+TEST_F(DiscardableMemoryProviderTest, CreateLockedMemoryZeroSize) {
+ size_t size = 0;
+ const scoped_ptr<DiscardableMemory> discardable(
+ DiscardableMemory::CreateLockedMemory(size));
+ EXPECT_FALSE(discardable);
+ EXPECT_FALSE(IsRegistered(discardable.get()));
+ EXPECT_EQ(0u, BytesAllocated());
+}
+
+TEST_F(DiscardableMemoryProviderTest, LockAfterUnlock) {
+ size_t size = 1024;
+ const scoped_ptr<DiscardableMemory> discardable(
+ DiscardableMemory::CreateLockedMemory(size));
+ EXPECT_TRUE(IsRegistered(discardable.get()));
+ EXPECT_NE(static_cast<void*>(NULL), Memory(discardable.get()));
+ EXPECT_EQ(1024u, BytesAllocated());
+ EXPECT_FALSE(CanBePurged(discardable.get()));
+
+ // Now unlock so we can lock later.
+ discardable->Unlock();
+ EXPECT_TRUE(CanBePurged(discardable.get()));
+
+ EXPECT_EQ(DISCARDABLE_MEMORY_SUCCESS, discardable->Lock());
+ EXPECT_FALSE(CanBePurged(discardable.get()));
+}
+
+TEST_F(DiscardableMemoryProviderTest, LockAfterPurge) {
+ size_t size = 1024;
+ const scoped_ptr<DiscardableMemory> discardable(
+ DiscardableMemory::CreateLockedMemory(size));
+ EXPECT_TRUE(IsRegistered(discardable.get()));
+ EXPECT_NE(static_cast<void*>(NULL), Memory(discardable.get()));
+ EXPECT_EQ(1024u, BytesAllocated());
+ EXPECT_FALSE(CanBePurged(discardable.get()));
+
+ // Now unlock so we can lock later.
+ discardable->Unlock();
+ EXPECT_TRUE(CanBePurged(discardable.get()));
+
+ // Force the system to purge.
+ MemoryPressureListener::NotifyMemoryPressure(
+ MemoryPressureListener::MEMORY_PRESSURE_CRITICAL);
+
+ // Required because ObserverListThreadSafe notifies via PostTask.
+ RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(DISCARDABLE_MEMORY_PURGED, discardable->Lock());
+ EXPECT_FALSE(CanBePurged(discardable.get()));
+}
+
+TEST_F(DiscardableMemoryProviderTest, LockAfterPurgeAndCannotReallocate) {
+ size_t size = 1024;
+ const scoped_ptr<DiscardableMemory> discardable(
+ DiscardableMemory::CreateLockedMemory(size));
+ EXPECT_TRUE(IsRegistered(discardable.get()));
+ EXPECT_NE(static_cast<void*>(NULL), Memory(discardable.get()));
+ EXPECT_EQ(1024u, BytesAllocated());
+ EXPECT_FALSE(CanBePurged(discardable.get()));
+
+ // Now unlock so we can lock later.
+ discardable->Unlock();
+ EXPECT_TRUE(CanBePurged(discardable.get()));
+
+ // Set max allowed allocation to 1 byte. This will make cause the memory
+ // to be purged.
+ SetDiscardableMemoryLimit(1);
+
+ EXPECT_EQ(DISCARDABLE_MEMORY_PURGED, discardable->Lock());
+ EXPECT_FALSE(CanBePurged(discardable.get()));
+}
+
+class PermutationTestData {
+ public:
+ PermutationTestData(unsigned d0, unsigned d1, unsigned d2) {
+ ordering_[0] = d0;
+ ordering_[1] = d1;
+ ordering_[2] = d2;
+ }
+
+ const unsigned* ordering() const { return ordering_; }
+
+ private:
+ unsigned ordering_[3];
+};
+
+class DiscardableMemoryProviderPermutationTest
+ : public DiscardableMemoryProviderTestBase,
+ public testing::TestWithParam<PermutationTestData> {
+ public:
+ DiscardableMemoryProviderPermutationTest() {}
+
+ protected:
+ // Use discardable memory in order specified by ordering parameter.
+ void CreateAndUseDiscardableMemory() {
+ for (int i = 0; i < 3; ++i) {
+ discardables_[i] = DiscardableMemory::CreateLockedMemory(1024);
+ EXPECT_TRUE(discardables_[i]);
+ EXPECT_NE(static_cast<void*>(NULL), Memory(discardables_[i].get()));
+ discardables_[i]->Unlock();
+ }
+ for (int i = 0; i < 3; ++i) {
+ int index = GetParam().ordering()[i];
+ EXPECT_NE(DISCARDABLE_MEMORY_FAILED, discardables_[index]->Lock());
+ // Leave i == 0 locked.
+ if (i > 0)
+ discardables_[index]->Unlock();
+ }
+ }
+
+ DiscardableMemory* discardable(unsigned position) {
+ return discardables_[GetParam().ordering()[position]].get();
+ }
+
+ private:
+ scoped_ptr<DiscardableMemory> discardables_[3];
+};
+
+// Verify that memory was discarded in the correct order after applying
+// memory pressure.
+TEST_P(DiscardableMemoryProviderPermutationTest, LRUDiscardedModeratePressure) {
+ CreateAndUseDiscardableMemory();
+
+ SetBytesToReclaimUnderModeratePressure(1024);
+ MemoryPressureListener::NotifyMemoryPressure(
+ MemoryPressureListener::MEMORY_PRESSURE_MODERATE);
+ RunLoop().RunUntilIdle();
+
+ EXPECT_NE(DISCARDABLE_MEMORY_FAILED, discardable(2)->Lock());
+ EXPECT_NE(DISCARDABLE_MEMORY_SUCCESS, discardable(1)->Lock());
+ // 0 should still be locked.
+ EXPECT_NE(static_cast<void*>(NULL), Memory(discardable(0)));
+}
+
+// Verify that memory was discarded in the correct order after changing
+// memory limit.
+TEST_P(DiscardableMemoryProviderPermutationTest, LRUDiscardedExceedLimit) {
+ CreateAndUseDiscardableMemory();
+
+ SetBytesToReclaimUnderModeratePressure(1024);
+ SetDiscardableMemoryLimit(2048);
+
+ EXPECT_NE(DISCARDABLE_MEMORY_FAILED, discardable(2)->Lock());
+ EXPECT_NE(DISCARDABLE_MEMORY_SUCCESS, discardable(1)->Lock());
+ // 0 should still be locked.
+ EXPECT_NE(static_cast<void*>(NULL), Memory(discardable(0)));
+}
+
+TEST_P(DiscardableMemoryProviderPermutationTest,
+ CriticalPressureFreesAllUnlocked) {
+ CreateAndUseDiscardableMemory();
+
+ MemoryPressureListener::NotifyMemoryPressure(
+ MemoryPressureListener::MEMORY_PRESSURE_CRITICAL);
+ RunLoop().RunUntilIdle();
+
+ for (int i = 0; i < 3; ++i) {
+ if (i == 0)
+ EXPECT_NE(static_cast<void*>(NULL), Memory(discardable(i)));
+ else
+ EXPECT_EQ(DISCARDABLE_MEMORY_PURGED, discardable(i)->Lock());
+ }
+}
+
+INSTANTIATE_TEST_CASE_P(DiscardableMemoryProviderPermutationTests,
+ DiscardableMemoryProviderPermutationTest,
+ ::testing::Values(PermutationTestData(0, 1, 2),
+ PermutationTestData(0, 2, 1),
+ PermutationTestData(1, 0, 2),
+ PermutationTestData(1, 2, 0),
+ PermutationTestData(2, 0, 1),
+ PermutationTestData(2, 1, 0)));
+
+TEST_F(DiscardableMemoryProviderTest, NormalDestruction) {
+ {
+ size_t size = 1024;
+ const scoped_ptr<DiscardableMemory> discardable(
+ DiscardableMemory::CreateLockedMemory(size));
+ EXPECT_TRUE(IsRegistered(discardable.get()));
+ EXPECT_EQ(1024u, BytesAllocated());
+ }
+ EXPECT_EQ(0u, BytesAllocated());
+}
+
+TEST_F(DiscardableMemoryProviderTest, DestructionWhileLocked) {
+ {
+ size_t size = 1024;
+ const scoped_ptr<DiscardableMemory> discardable(
+ DiscardableMemory::CreateLockedMemory(size));
+ EXPECT_TRUE(IsRegistered(discardable.get()));
+ EXPECT_NE(static_cast<void*>(NULL), Memory(discardable.get()));
+ EXPECT_EQ(1024u, BytesAllocated());
+ EXPECT_FALSE(CanBePurged(discardable.get()));
+ }
+ // Should have ignored the "locked" status and freed the discardable memory.
+ EXPECT_EQ(0u, BytesAllocated());
+}
+
+#if !defined(NDEBUG) && !defined(OS_ANDROID)
+// Death tests are not supported with Android APKs.
+TEST_F(DiscardableMemoryProviderTest, UnlockedMemoryAccessCrashesInDebugMode) {
+ size_t size = 1024;
+ const scoped_ptr<DiscardableMemory> discardable(
+ DiscardableMemory::CreateLockedMemory(size));
+ EXPECT_TRUE(IsRegistered(discardable.get()));
+ EXPECT_NE(static_cast<void*>(NULL), Memory(discardable.get()));
+ EXPECT_EQ(1024u, BytesAllocated());
+ EXPECT_FALSE(CanBePurged(discardable.get()));
+ discardable->Unlock();
+ EXPECT_TRUE(CanBePurged(discardable.get()));
+ // We *must* die if we are asked to vend a pointer to unlocked memory.
+ EXPECT_DEATH(discardable->Memory(), ".*Check failed.*");
+}
+#endif
+
+class ThreadedDiscardableMemoryProviderTest
+ : public DiscardableMemoryProviderTest {
+ public:
+ ThreadedDiscardableMemoryProviderTest()
+ : memory_usage_thread_("memory_usage_thread"),
+ thread_sync_(true, false) {
+ }
+
+ virtual void SetUp() OVERRIDE {
+ memory_usage_thread_.Start();
+ }
+
+ virtual void TearDown() OVERRIDE {
+ memory_usage_thread_.Stop();
+ }
+
+ void UseMemoryHelper() {
+ size_t size = 1024;
+ const scoped_ptr<DiscardableMemory> discardable(
+ DiscardableMemory::CreateLockedMemory(size));
+ EXPECT_TRUE(IsRegistered(discardable.get()));
+ EXPECT_NE(static_cast<void*>(NULL), Memory(discardable.get()));
+ discardable->Unlock();
+ }
+
+ void SignalHelper() {
+ thread_sync_.Signal();
+ }
+
+ Thread memory_usage_thread_;
+ WaitableEvent thread_sync_;
+};
+
+TEST_F(ThreadedDiscardableMemoryProviderTest, UseMemoryOnThread) {
+ memory_usage_thread_.message_loop()->PostTask(
+ FROM_HERE,
+ Bind(&ThreadedDiscardableMemoryProviderTest::UseMemoryHelper,
+ Unretained(this)));
+ memory_usage_thread_.message_loop()->PostTask(
+ FROM_HERE,
+ Bind(&ThreadedDiscardableMemoryProviderTest::SignalHelper,
+ Unretained(this)));
+ thread_sync_.Wait();
+}
+
+} // namespace base
diff --git a/base/memory/discardable_memory_unittest.cc b/base/memory/discardable_memory_unittest.cc
index d3c9c45..eb730f1 100644
--- a/base/memory/discardable_memory_unittest.cc
+++ b/base/memory/discardable_memory_unittest.cc
@@ -7,13 +7,22 @@
namespace base {
-#if defined(OS_ANDROID) || defined(OS_MACOSX)
const size_t kSize = 1024;
+TEST(DiscardableMemoryTest, SupportedNatively) {
+#if defined(DISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY)
+ ASSERT_TRUE(DiscardableMemory::SupportedNatively());
+#else
+ // If we ever have a platform that decides at runtime if it can support
+ // discardable memory natively, then we'll have to add a 'never supported
+ // natively' define for this case. At present, if it's not always supported
+ // natively, it's never supported.
+ ASSERT_FALSE(DiscardableMemory::SupportedNatively());
+#endif
+}
+
// Test Lock() and Unlock() functionalities.
TEST(DiscardableMemoryTest, LockAndUnLock) {
- ASSERT_TRUE(DiscardableMemory::Supported());
-
const scoped_ptr<DiscardableMemory> memory(
DiscardableMemory::CreateLockedMemory(kSize));
ASSERT_TRUE(memory);
@@ -32,17 +41,14 @@ TEST(DiscardableMemoryTest, LockAndUnLock) {
// Test delete a discardable memory while it is locked.
TEST(DiscardableMemoryTest, DeleteWhileLocked) {
- ASSERT_TRUE(DiscardableMemory::Supported());
-
const scoped_ptr<DiscardableMemory> memory(
DiscardableMemory::CreateLockedMemory(kSize));
ASSERT_TRUE(memory);
}
-#if defined(OS_MACOSX)
+#if !defined(OS_ANDROID)
// Test forced purging.
TEST(DiscardableMemoryTest, Purge) {
- ASSERT_TRUE(DiscardableMemory::Supported());
ASSERT_TRUE(DiscardableMemory::PurgeForTestingSupported());
const scoped_ptr<DiscardableMemory> memory(
@@ -53,7 +59,7 @@ TEST(DiscardableMemoryTest, Purge) {
DiscardableMemory::PurgeForTesting();
EXPECT_EQ(DISCARDABLE_MEMORY_PURGED, memory->Lock());
}
-#endif // OS_MACOSX
+#endif // !OS_ANDROID
#if !defined(NDEBUG) && !defined(OS_ANDROID)
// Death tests are not supported with Android APKs.
@@ -67,6 +73,4 @@ TEST(DiscardableMemoryTest, UnlockedMemoryAccessCrashesInDebugMode) {
}
#endif
-#endif // OS_*
-
}
diff --git a/build/common.gypi b/build/common.gypi
index a4b1820..54e5b47 100644
--- a/build/common.gypi
+++ b/build/common.gypi
@@ -557,6 +557,11 @@
'proprietary_codecs%': 0,
}],
+ ['OS=="mac"', {
+ 'native_discardable_memory%': 1,
+ 'native_memory_pressure_signals%': 1,
+ }],
+
# Enable autofill dialog for Android, Mac and Views-enabled platforms.
['toolkit_views==1 or (OS=="android" and android_webview_build==0) or OS=="mac"', {
'enable_autofill_dialog%': 1
@@ -2057,6 +2062,12 @@
['enable_hidpi==1', {
'defines': ['ENABLE_HIDPI=1'],
}],
+ ['native_discardable_memory==1', {
+ 'defines': ['DISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY'],
+ }],
+ ['native_memory_pressure_signals==1', {
+ 'defines': ['SYSTEM_NATIVELY_SIGNALS_MEMORY_PRESSURE'],
+ }],
['fastbuild!=0', {
'xcode_settings': {
'GCC_GENERATE_DEBUGGING_SYMBOLS': 'NO',
@@ -3467,12 +3478,6 @@
'-B<(PRODUCT_DIR)/../../third_party/gold',
],
}],
- ['native_discardable_memory', {
- 'defines': ['DISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY'],
- }],
- ['native_memory_pressure_signals', {
- 'defines': ['SYSTEM_NATIVELY_SIGNALS_MEMORY_PRESSURE'],
- }],
],
},
}],
diff --git a/skia/ext/SkDiscardableMemory_chrome.cc b/skia/ext/SkDiscardableMemory_chrome.cc
index 6639a5a..886c995 100644
--- a/skia/ext/SkDiscardableMemory_chrome.cc
+++ b/skia/ext/SkDiscardableMemory_chrome.cc
@@ -34,8 +34,6 @@ SkDiscardableMemoryChrome::SkDiscardableMemoryChrome(
}
SkDiscardableMemory* SkDiscardableMemory::Create(size_t bytes) {
- if (!base::DiscardableMemory::Supported())
- return NULL;
scoped_ptr<base::DiscardableMemory> discardable(
base::DiscardableMemory::CreateLockedMemory(bytes));
if (!discardable)
diff --git a/webkit/child/webkitplatformsupport_child_impl.cc b/webkit/child/webkitplatformsupport_child_impl.cc
index 849dcb7..c23c404 100644
--- a/webkit/child/webkitplatformsupport_child_impl.cc
+++ b/webkit/child/webkitplatformsupport_child_impl.cc
@@ -93,8 +93,6 @@ void WebKitPlatformSupportChildImpl::didStopWorkerRunLoop(
WebKit::WebDiscardableMemory*
WebKitPlatformSupportChildImpl::allocateAndLockDiscardableMemory(size_t bytes) {
- if (!base::DiscardableMemory::Supported())
- return NULL;
return WebDiscardableMemoryImpl::CreateLockedMemory(bytes).release();
}