// 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_manager.h" #include "base/bind.h" #include "base/synchronization/waitable_event.h" #include "base/threading/thread.h" #include "testing/gtest/include/gtest/gtest.h" namespace base { namespace { class TestAllocationImpl : public internal::DiscardableMemoryManagerAllocation { public: TestAllocationImpl() : is_allocated_(false), is_locked_(false) {} ~TestAllocationImpl() override { DCHECK(!is_locked_); } // Overridden from internal::DiscardableMemoryManagerAllocation: bool AllocateAndAcquireLock() override { bool was_allocated = is_allocated_; is_allocated_ = true; DCHECK(!is_locked_); is_locked_ = true; return was_allocated; } void ReleaseLock() override { DCHECK(is_locked_); is_locked_ = false; } void Purge() override { DCHECK(is_allocated_); is_allocated_ = false; } virtual bool IsMemoryResident() const override { DCHECK(is_allocated_); return true; } bool is_locked() const { return is_locked_; } private: bool is_allocated_; bool is_locked_; }; // Tests can assume that the default limit is at least 1024. Tests that rely on // something else needs to explicit set the limit. const size_t kDefaultMemoryLimit = 1024; const size_t kDefaultSoftMemoryLimit = kDefaultMemoryLimit; class TestDiscardableMemoryManagerImpl : public internal::DiscardableMemoryManager { public: TestDiscardableMemoryManagerImpl() : DiscardableMemoryManager(kDefaultMemoryLimit, kDefaultSoftMemoryLimit, TimeDelta::Max()) {} void SetNow(TimeTicks now) { now_ = now; } private: // Overriden from internal::DiscardableMemoryManager: TimeTicks Now() const override { return now_; } TimeTicks now_; }; class DiscardableMemoryManagerTestBase { public: DiscardableMemoryManagerTestBase() {} protected: enum LockStatus { LOCK_STATUS_FAILED, LOCK_STATUS_PURGED, LOCK_STATUS_SUCCESS }; size_t BytesAllocated() const { return manager_.GetBytesAllocatedForTest(); } void SetMemoryLimit(size_t bytes) { manager_.SetMemoryLimit(bytes); } void SetSoftMemoryLimit(size_t bytes) { manager_.SetSoftMemoryLimit(bytes); } void SetHardMemoryLimitExpirationTime(TimeDelta time) { manager_.SetHardMemoryLimitExpirationTime(time); } void Register(TestAllocationImpl* allocation, size_t bytes) { manager_.Register(allocation, bytes); } void Unregister(TestAllocationImpl* allocation) { manager_.Unregister(allocation); } bool IsRegistered(TestAllocationImpl* allocation) const { return manager_.IsRegisteredForTest(allocation); } LockStatus Lock(TestAllocationImpl* allocation) { bool purged; if (!manager_.AcquireLock(allocation, &purged)) return LOCK_STATUS_FAILED; return purged ? LOCK_STATUS_PURGED : LOCK_STATUS_SUCCESS; } void Unlock(TestAllocationImpl* allocation) { manager_.ReleaseLock(allocation); } LockStatus RegisterAndLock(TestAllocationImpl* allocation, size_t bytes) { manager_.Register(allocation, bytes); return Lock(allocation); } bool CanBePurged(TestAllocationImpl* allocation) const { return manager_.CanBePurgedForTest(allocation); } void SetNow(TimeTicks now) { manager_.SetNow(now); } void PurgeAll() { return manager_.PurgeAll(); } bool ReduceMemoryUsage() { return manager_.ReduceMemoryUsage(); } void ReduceMemoryUsageUntilWithinLimit(size_t bytes) { manager_.ReduceMemoryUsageUntilWithinLimit(bytes); } private: TestDiscardableMemoryManagerImpl manager_; }; class DiscardableMemoryManagerTest : public DiscardableMemoryManagerTestBase, public testing::Test { public: DiscardableMemoryManagerTest() {} }; TEST_F(DiscardableMemoryManagerTest, CreateAndLock) { size_t size = 1024; TestAllocationImpl allocation; Register(&allocation, size); EXPECT_TRUE(IsRegistered(&allocation)); EXPECT_EQ(LOCK_STATUS_PURGED, Lock(&allocation)); EXPECT_TRUE(allocation.is_locked()); EXPECT_EQ(1024u, BytesAllocated()); EXPECT_FALSE(CanBePurged(&allocation)); Unlock(&allocation); Unregister(&allocation); } TEST_F(DiscardableMemoryManagerTest, CreateZeroSize) { size_t size = 0; TestAllocationImpl allocation; Register(&allocation, size); EXPECT_TRUE(IsRegistered(&allocation)); EXPECT_EQ(LOCK_STATUS_FAILED, Lock(&allocation)); EXPECT_EQ(0u, BytesAllocated()); Unregister(&allocation); } TEST_F(DiscardableMemoryManagerTest, LockAfterUnlock) { size_t size = 1024; TestAllocationImpl allocation; RegisterAndLock(&allocation, size); EXPECT_EQ(1024u, BytesAllocated()); EXPECT_FALSE(CanBePurged(&allocation)); // Now unlock so we can lock later. Unlock(&allocation); EXPECT_TRUE(CanBePurged(&allocation)); EXPECT_EQ(LOCK_STATUS_SUCCESS, Lock(&allocation)); EXPECT_FALSE(CanBePurged(&allocation)); Unlock(&allocation); Unregister(&allocation); } TEST_F(DiscardableMemoryManagerTest, LockAfterPurge) { size_t size = 1024; TestAllocationImpl allocation; RegisterAndLock(&allocation, size); EXPECT_EQ(1024u, BytesAllocated()); EXPECT_FALSE(CanBePurged(&allocation)); // Now unlock so we can lock later. Unlock(&allocation); EXPECT_TRUE(CanBePurged(&allocation)); // Force the system to purge. PurgeAll(); EXPECT_EQ(LOCK_STATUS_PURGED, Lock(&allocation)); EXPECT_FALSE(CanBePurged(&allocation)); Unlock(&allocation); Unregister(&allocation); } TEST_F(DiscardableMemoryManagerTest, LockAfterPurgeAndCannotReallocate) { size_t size = 1024; TestAllocationImpl allocation; RegisterAndLock(&allocation, size); EXPECT_EQ(1024u, BytesAllocated()); EXPECT_FALSE(CanBePurged(&allocation)); // Now unlock so we can lock later. Unlock(&allocation); EXPECT_TRUE(CanBePurged(&allocation)); // Set max allowed allocation to 1 byte. This will cause the memory to be // purged. SetMemoryLimit(1); EXPECT_EQ(LOCK_STATUS_PURGED, Lock(&allocation)); EXPECT_FALSE(CanBePurged(&allocation)); Unlock(&allocation); Unregister(&allocation); } TEST_F(DiscardableMemoryManagerTest, Overflow) { size_t size = 1024; { TestAllocationImpl allocation; RegisterAndLock(&allocation, size); EXPECT_EQ(1024u, BytesAllocated()); size_t massive_size = std::numeric_limits::max(); TestAllocationImpl massive_allocation; Register(&massive_allocation, massive_size); EXPECT_EQ(LOCK_STATUS_FAILED, Lock(&massive_allocation)); EXPECT_EQ(1024u, BytesAllocated()); Unlock(&allocation); EXPECT_EQ(LOCK_STATUS_PURGED, Lock(&massive_allocation)); Unlock(&massive_allocation); Unregister(&massive_allocation); Unregister(&allocation); } EXPECT_EQ(0u, BytesAllocated()); } 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 DiscardableMemoryManagerPermutationTest : public DiscardableMemoryManagerTestBase, public testing::TestWithParam { public: DiscardableMemoryManagerPermutationTest() {} protected: // Use memory in order specified by ordering parameter. void RegisterAndUseAllocations() { for (int i = 0; i < 3; ++i) { RegisterAndLock(&allocation_[i], 1024); Unlock(&allocation_[i]); } for (int i = 0; i < 3; ++i) { int index = GetParam().ordering()[i]; EXPECT_NE(LOCK_STATUS_FAILED, Lock(&allocation_[index])); // Leave i == 0 locked. if (i > 0) Unlock(&allocation_[index]); } } TestAllocationImpl* allocation(unsigned position) { return &allocation_[GetParam().ordering()[position]]; } void UnlockAndUnregisterAllocations() { for (int i = 0; i < 3; ++i) { if (allocation_[i].is_locked()) Unlock(&allocation_[i]); Unregister(&allocation_[i]); } } private: TestAllocationImpl allocation_[3]; }; // Verify that memory was discarded in the correct order after reducing usage to // limit. TEST_P(DiscardableMemoryManagerPermutationTest, LRUDiscarded) { RegisterAndUseAllocations(); SetMemoryLimit(2048); ReduceMemoryUsageUntilWithinLimit(1024); EXPECT_NE(LOCK_STATUS_FAILED, Lock(allocation(2))); EXPECT_EQ(LOCK_STATUS_PURGED, Lock(allocation(1))); // 0 should still be locked. EXPECT_TRUE(allocation(0)->is_locked()); UnlockAndUnregisterAllocations(); } // Verify that memory was discarded in the correct order after changing // memory limit. TEST_P(DiscardableMemoryManagerPermutationTest, LRUDiscardedExceedLimit) { RegisterAndUseAllocations(); SetMemoryLimit(2048); EXPECT_NE(LOCK_STATUS_FAILED, Lock(allocation(2))); EXPECT_EQ(LOCK_STATUS_PURGED, Lock(allocation(1))); // 0 should still be locked. EXPECT_TRUE(allocation(0)->is_locked()); UnlockAndUnregisterAllocations(); } // Verify that no more memory than necessary was discarded after changing // memory limit. TEST_P(DiscardableMemoryManagerPermutationTest, LRUDiscardedAmount) { SetMemoryLimit(4096); RegisterAndUseAllocations(); SetMemoryLimit(2048); EXPECT_EQ(LOCK_STATUS_SUCCESS, Lock(allocation(2))); EXPECT_EQ(LOCK_STATUS_PURGED, Lock(allocation(1))); // 0 should still be locked. EXPECT_TRUE(allocation(0)->is_locked()); UnlockAndUnregisterAllocations(); } TEST_P(DiscardableMemoryManagerPermutationTest, PurgeFreesAllUnlocked) { RegisterAndUseAllocations(); PurgeAll(); for (int i = 0; i < 3; ++i) { if (i == 0) EXPECT_TRUE(allocation(i)->is_locked()); else EXPECT_EQ(LOCK_STATUS_PURGED, Lock(allocation(i))); } UnlockAndUnregisterAllocations(); } INSTANTIATE_TEST_CASE_P(DiscardableMemoryManagerPermutationTests, DiscardableMemoryManagerPermutationTest, ::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(DiscardableMemoryManagerTest, NormalDestruction) { { size_t size = 1024; TestAllocationImpl allocation; Register(&allocation, size); Unregister(&allocation); } EXPECT_EQ(0u, BytesAllocated()); } TEST_F(DiscardableMemoryManagerTest, DestructionAfterLocked) { { size_t size = 1024; TestAllocationImpl allocation; RegisterAndLock(&allocation, size); EXPECT_EQ(1024u, BytesAllocated()); EXPECT_FALSE(CanBePurged(&allocation)); Unlock(&allocation); Unregister(&allocation); } EXPECT_EQ(0u, BytesAllocated()); } TEST_F(DiscardableMemoryManagerTest, DestructionAfterPurged) { { size_t size = 1024; TestAllocationImpl allocation; RegisterAndLock(&allocation, size); EXPECT_EQ(1024u, BytesAllocated()); Unlock(&allocation); EXPECT_TRUE(CanBePurged(&allocation)); SetMemoryLimit(0); EXPECT_EQ(0u, BytesAllocated()); Unregister(&allocation); } EXPECT_EQ(0u, BytesAllocated()); } TEST_F(DiscardableMemoryManagerTest, ReduceMemoryUsage) { SetMemoryLimit(3072); SetSoftMemoryLimit(1024); SetHardMemoryLimitExpirationTime(TimeDelta::FromInternalValue(1)); size_t size = 1024; TestAllocationImpl allocation[3]; RegisterAndLock(&allocation[0], size); RegisterAndLock(&allocation[1], size); RegisterAndLock(&allocation[2], size); EXPECT_EQ(3072u, BytesAllocated()); // Above soft limit but nothing that can be purged. EXPECT_FALSE(ReduceMemoryUsage()); SetNow(TimeTicks::FromInternalValue(0)); Unlock(&allocation[0]); // Above soft limit but still nothing that can be purged as all unlocked // allocations are within the hard limit cutoff time. EXPECT_FALSE(ReduceMemoryUsage()); SetNow(TimeTicks::FromInternalValue(1)); Unlock(&allocation[1]); // One unlocked allocation is no longer within the hard limit cutoff time. It // should be purged and ReduceMemoryUsage() should return false as we're not // yet within the soft memory limit. EXPECT_FALSE(ReduceMemoryUsage()); EXPECT_EQ(2048u, BytesAllocated()); // One more unlocked allocation is no longer within the hard limit cutoff // time. It should be purged and ReduceMemoryUsage() should return true as // we're now within the soft memory limit. SetNow(TimeTicks::FromInternalValue(2)); EXPECT_TRUE(ReduceMemoryUsage()); EXPECT_EQ(1024u, BytesAllocated()); Unlock(&allocation[2]); Unregister(&allocation[0]); Unregister(&allocation[1]); Unregister(&allocation[2]); } class ThreadedDiscardableMemoryManagerTest : public DiscardableMemoryManagerTest { public: ThreadedDiscardableMemoryManagerTest() : 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; TestAllocationImpl allocation; RegisterAndLock(&allocation, size); Unlock(&allocation); Unregister(&allocation); } void SignalHelper() { thread_sync_.Signal(); } Thread memory_usage_thread_; WaitableEvent thread_sync_; }; TEST_F(ThreadedDiscardableMemoryManagerTest, UseMemoryOnThread) { memory_usage_thread_.message_loop()->PostTask( FROM_HERE, Bind(&ThreadedDiscardableMemoryManagerTest::UseMemoryHelper, Unretained(this))); memory_usage_thread_.message_loop()->PostTask( FROM_HERE, Bind(&ThreadedDiscardableMemoryManagerTest::SignalHelper, Unretained(this))); thread_sync_.Wait(); } } // namespace } // namespace base