| // 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 <stddef.h> |
| #include <utility> |
| |
| #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; |
| |
| std::unique_ptr<base::DiscardableSharedMemory> memory( |
| new base::DiscardableSharedMemory); |
| ASSERT_TRUE(memory->CreateAndMap(memory_size)); |
| |
| // Create new span for memory. |
| std::unique_ptr<DiscardableSharedMemoryHeap::Span> 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. |
| std::unique_ptr<DiscardableSharedMemoryHeap::Span> 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; |
| |
| std::unique_ptr<base::DiscardableSharedMemory> memory( |
| new base::DiscardableSharedMemory); |
| ASSERT_TRUE(memory->CreateAndMap(memory_size)); |
| std::unique_ptr<DiscardableSharedMemoryHeap::Span> new_span( |
| heap.Grow(std::move(memory), memory_size, |
| next_discardable_shared_memory_id++, base::Bind(NullTask))); |
| |
| // Split span into two. |
| std::unique_ptr<DiscardableSharedMemoryHeap::Span> 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. |
| std::unique_ptr<DiscardableSharedMemoryHeap::Span> span1 = |
| heap.SearchFreeLists(2, kBlocks); |
| ASSERT_TRUE(span1); |
| |
| // Remove another 2 page span from free lists. |
| std::unique_ptr<DiscardableSharedMemoryHeap::Span> 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. |
| std::unique_ptr<DiscardableSharedMemoryHeap::Span> 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; |
| |
| std::unique_ptr<base::DiscardableSharedMemory> memory( |
| new base::DiscardableSharedMemory); |
| ASSERT_TRUE(memory->CreateAndMap(memory_size)); |
| std::unique_ptr<DiscardableSharedMemoryHeap::Span> new_span( |
| heap.Grow(std::move(memory), memory_size, |
| next_discardable_shared_memory_id++, base::Bind(NullTask))); |
| |
| // Split span into two. |
| std::unique_ptr<DiscardableSharedMemoryHeap::Span> 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; |
| |
| std::unique_ptr<base::DiscardableSharedMemory> 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. |
| std::unique_ptr<DiscardableSharedMemoryHeap::Span> span1 = |
| heap.SearchFreeLists(1, 0); |
| EXPECT_TRUE(span1); |
| |
| // No more memory available. |
| EXPECT_FALSE(heap.SearchFreeLists(1, 0)); |
| |
| // Grow free lists using new memory. |
| std::unique_ptr<base::DiscardableSharedMemory> 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. |
| std::unique_ptr<DiscardableSharedMemoryHeap::Span> 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; |
| |
| std::unique_ptr<base::DiscardableSharedMemory> memory( |
| new base::DiscardableSharedMemory); |
| ASSERT_TRUE(memory->CreateAndMap(block_size)); |
| std::unique_ptr<DiscardableSharedMemoryHeap::Span> 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; |
| |
| std::unique_ptr<base::DiscardableSharedMemory> memory( |
| new base::DiscardableSharedMemory); |
| ASSERT_TRUE(memory->CreateAndMap(block_size)); |
| std::unique_ptr<DiscardableSharedMemoryHeap::Span> 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; |
| |
| std::unique_ptr<base::DiscardableSharedMemory> 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)); |
| |
| std::unique_ptr<DiscardableSharedMemoryHeap::Span> 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; |
| |
| std::unique_ptr<base::DiscardableSharedMemory> memory( |
| new base::DiscardableSharedMemory); |
| ASSERT_TRUE(memory->CreateAndMap(block_size)); |
| bool deleted = false; |
| std::unique_ptr<DiscardableSharedMemoryHeap::Span> 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; |
| |
| std::unique_ptr<base::DiscardableSharedMemory> memory( |
| new base::DiscardableSharedMemory); |
| ASSERT_TRUE(memory->CreateAndMap(block_size)); |
| std::unique_ptr<DiscardableSharedMemoryHeap::Span> 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. |
| std::unique_ptr<base::trace_event::ProcessMemoryDump> 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 |