// 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/discardable_shared_memory_heap.h" #include #include #include "base/bind.h" #include "base/memory/discardable_shared_memory.h" #include "base/process/process_metrics.h" #include "testing/gtest/include/gtest/gtest.h" namespace content { namespace { void NullTask() { } TEST(DiscardableSharedMemoryHeapTest, Basic) { size_t block_size = base::GetPageSize(); DiscardableSharedMemoryHeap heap(block_size); // Initial size should be 0. EXPECT_EQ(0u, heap.GetSize()); // Initial size of free lists should be 0. EXPECT_EQ(0u, heap.GetSizeOfFreeLists()); // Free lists are initially empty. EXPECT_FALSE(heap.SearchFreeLists(1, 0)); const size_t kBlocks = 10; size_t memory_size = block_size * kBlocks; int next_discardable_shared_memory_id = 0; scoped_ptr memory( new base::DiscardableSharedMemory); ASSERT_TRUE(memory->CreateAndMap(memory_size)); // Create new span for memory. scoped_ptr new_span( heap.Grow(std::move(memory), memory_size, next_discardable_shared_memory_id++, base::Bind(NullTask))); // Size should match |memory_size|. EXPECT_EQ(memory_size, heap.GetSize()); // Size of free lists should still be 0. EXPECT_EQ(0u, heap.GetSizeOfFreeLists()); // Free list should still be empty as |new_span| is currently in use. EXPECT_FALSE(heap.SearchFreeLists(1, 0)); // Done using |new_span|. Merge it into the free lists. heap.MergeIntoFreeLists(std::move(new_span)); // Size of free lists should now match |memory_size|. EXPECT_EQ(memory_size, heap.GetSizeOfFreeLists()); // Free lists should not contain a span that is larger than kBlocks. EXPECT_FALSE(heap.SearchFreeLists(kBlocks + 1, 0)); // Free lists should contain a span that satisfies the request for kBlocks. scoped_ptr span = heap.SearchFreeLists(kBlocks, 0); ASSERT_TRUE(span); // Free lists should be empty again. EXPECT_FALSE(heap.SearchFreeLists(1, 0)); // Merge it into the free lists again. heap.MergeIntoFreeLists(std::move(span)); } TEST(DiscardableSharedMemoryHeapTest, SplitAndMerge) { size_t block_size = base::GetPageSize(); DiscardableSharedMemoryHeap heap(block_size); const size_t kBlocks = 6; size_t memory_size = block_size * kBlocks; int next_discardable_shared_memory_id = 0; scoped_ptr memory( new base::DiscardableSharedMemory); ASSERT_TRUE(memory->CreateAndMap(memory_size)); scoped_ptr new_span( heap.Grow(std::move(memory), memory_size, next_discardable_shared_memory_id++, base::Bind(NullTask))); // Split span into two. scoped_ptr leftover = heap.Split(new_span.get(), 3); ASSERT_TRUE(leftover); // Merge |leftover| into free lists. heap.MergeIntoFreeLists(std::move(leftover)); // Some of the memory is still in use. EXPECT_FALSE(heap.SearchFreeLists(kBlocks, 0)); // Merge |span| into free lists. heap.MergeIntoFreeLists(std::move(new_span)); // Remove a 2 page span from free lists. scoped_ptr span1 = heap.SearchFreeLists(2, kBlocks); ASSERT_TRUE(span1); // Remove another 2 page span from free lists. scoped_ptr span2 = heap.SearchFreeLists(2, kBlocks); ASSERT_TRUE(span2); // Merge |span1| back into free lists. heap.MergeIntoFreeLists(std::move(span1)); // Some of the memory is still in use. EXPECT_FALSE(heap.SearchFreeLists(kBlocks, 0)); // Merge |span2| back into free lists. heap.MergeIntoFreeLists(std::move(span2)); // All memory has been returned to the free lists. scoped_ptr large_span = heap.SearchFreeLists(kBlocks, 0); ASSERT_TRUE(large_span); // Merge it into the free lists again. heap.MergeIntoFreeLists(std::move(large_span)); } TEST(DiscardableSharedMemoryHeapTest, MergeSingleBlockSpan) { size_t block_size = base::GetPageSize(); DiscardableSharedMemoryHeap heap(block_size); const size_t kBlocks = 6; size_t memory_size = block_size * kBlocks; int next_discardable_shared_memory_id = 0; scoped_ptr memory( new base::DiscardableSharedMemory); ASSERT_TRUE(memory->CreateAndMap(memory_size)); scoped_ptr new_span( heap.Grow(std::move(memory), memory_size, next_discardable_shared_memory_id++, base::Bind(NullTask))); // Split span into two. scoped_ptr leftover = heap.Split(new_span.get(), 5); ASSERT_TRUE(leftover); // Merge |new_span| into free lists. heap.MergeIntoFreeLists(std::move(new_span)); // Merge |leftover| into free lists. heap.MergeIntoFreeLists(std::move(leftover)); } TEST(DiscardableSharedMemoryHeapTest, Grow) { size_t block_size = base::GetPageSize(); DiscardableSharedMemoryHeap heap(block_size); int next_discardable_shared_memory_id = 0; scoped_ptr memory1( new base::DiscardableSharedMemory); ASSERT_TRUE(memory1->CreateAndMap(block_size)); heap.MergeIntoFreeLists(heap.Grow(std::move(memory1), block_size, next_discardable_shared_memory_id++, base::Bind(NullTask))); // Remove a span from free lists. scoped_ptr span1 = heap.SearchFreeLists(1, 0); EXPECT_TRUE(span1); // No more memory available. EXPECT_FALSE(heap.SearchFreeLists(1, 0)); // Grow free lists using new memory. scoped_ptr memory2( new base::DiscardableSharedMemory); ASSERT_TRUE(memory2->CreateAndMap(block_size)); heap.MergeIntoFreeLists(heap.Grow(std::move(memory2), block_size, next_discardable_shared_memory_id++, base::Bind(NullTask))); // Memory should now be available. scoped_ptr span2 = heap.SearchFreeLists(1, 0); EXPECT_TRUE(span2); // Merge spans into the free lists again. heap.MergeIntoFreeLists(std::move(span1)); heap.MergeIntoFreeLists(std::move(span2)); } TEST(DiscardableSharedMemoryHeapTest, ReleaseFreeMemory) { size_t block_size = base::GetPageSize(); DiscardableSharedMemoryHeap heap(block_size); int next_discardable_shared_memory_id = 0; scoped_ptr memory( new base::DiscardableSharedMemory); ASSERT_TRUE(memory->CreateAndMap(block_size)); scoped_ptr span = heap.Grow(std::move(memory), block_size, next_discardable_shared_memory_id++, base::Bind(NullTask)); // Free lists should be empty. EXPECT_EQ(0u, heap.GetSizeOfFreeLists()); heap.ReleaseFreeMemory(); // Size should still match |block_size|. EXPECT_EQ(block_size, heap.GetSize()); heap.MergeIntoFreeLists(std::move(span)); heap.ReleaseFreeMemory(); // Memory should have been released. EXPECT_EQ(0u, heap.GetSize()); EXPECT_EQ(0u, heap.GetSizeOfFreeLists()); } TEST(DiscardableSharedMemoryHeapTest, ReleasePurgedMemory) { size_t block_size = base::GetPageSize(); DiscardableSharedMemoryHeap heap(block_size); int next_discardable_shared_memory_id = 0; scoped_ptr memory( new base::DiscardableSharedMemory); ASSERT_TRUE(memory->CreateAndMap(block_size)); scoped_ptr span = heap.Grow(std::move(memory), block_size, next_discardable_shared_memory_id++, base::Bind(NullTask)); // Unlock memory so it can be purged. span->shared_memory()->Unlock(0, 0); // Purge and release shared memory. bool rv = span->shared_memory()->Purge(base::Time::Now()); EXPECT_TRUE(rv); heap.ReleasePurgedMemory(); // Shared memory backing for |span| should be gone. EXPECT_FALSE(span->shared_memory()); // Size should be 0. EXPECT_EQ(0u, heap.GetSize()); } TEST(DiscardableSharedMemoryHeapTest, Slack) { size_t block_size = base::GetPageSize(); DiscardableSharedMemoryHeap heap(block_size); const size_t kBlocks = 6; size_t memory_size = block_size * kBlocks; int next_discardable_shared_memory_id = 0; scoped_ptr memory( new base::DiscardableSharedMemory); ASSERT_TRUE(memory->CreateAndMap(memory_size)); heap.MergeIntoFreeLists(heap.Grow(std::move(memory), memory_size, next_discardable_shared_memory_id++, base::Bind(NullTask))); // No free span that is less or equal to 3 + 1. EXPECT_FALSE(heap.SearchFreeLists(3, 1)); // No free span that is less or equal to 3 + 2. EXPECT_FALSE(heap.SearchFreeLists(3, 2)); // No free span that is less or equal to 1 + 4. EXPECT_FALSE(heap.SearchFreeLists(1, 4)); scoped_ptr span = heap.SearchFreeLists(1, 5); EXPECT_TRUE(span); heap.MergeIntoFreeLists(std::move(span)); } void OnDeleted(bool* deleted) { *deleted = true; } TEST(DiscardableSharedMemoryHeapTest, DeletedCallback) { size_t block_size = base::GetPageSize(); DiscardableSharedMemoryHeap heap(block_size); int next_discardable_shared_memory_id = 0; scoped_ptr memory( new base::DiscardableSharedMemory); ASSERT_TRUE(memory->CreateAndMap(block_size)); bool deleted = false; scoped_ptr span = heap.Grow( std::move(memory), block_size, next_discardable_shared_memory_id++, base::Bind(OnDeleted, base::Unretained(&deleted))); heap.MergeIntoFreeLists(std::move(span)); heap.ReleaseFreeMemory(); EXPECT_TRUE(deleted); } TEST(DiscardableSharedMemoryHeapTest, CreateMemoryAllocatorDumpTest) { size_t block_size = base::GetPageSize(); DiscardableSharedMemoryHeap heap(block_size); int next_discardable_shared_memory_id = 0; scoped_ptr memory( new base::DiscardableSharedMemory); ASSERT_TRUE(memory->CreateAndMap(block_size)); scoped_ptr span = heap.Grow(std::move(memory), block_size, next_discardable_shared_memory_id++, base::Bind(NullTask)); // Check if allocator dump is created when span exists. scoped_ptr pmd( new base::trace_event::ProcessMemoryDump(nullptr)); EXPECT_TRUE(heap.CreateMemoryAllocatorDump(span.get(), "discardable/test1", pmd.get())); // Unlock, Purge and release shared memory. span->shared_memory()->Unlock(0, 0); bool rv = span->shared_memory()->Purge(base::Time::Now()); EXPECT_TRUE(rv); heap.ReleasePurgedMemory(); // Check that allocator dump is created after memory is purged. EXPECT_TRUE(heap.CreateMemoryAllocatorDump(span.get(), "discardable/test2", pmd.get())); } } // namespace } // namespace content