1 // Copyright 2019 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "partition_alloc/memory_reclaimer.h"
6
7 #include <memory>
8 #include <utility>
9
10 #include "build/build_config.h"
11 #include "partition_alloc/partition_alloc_base/compiler_specific.h"
12 #include "partition_alloc/partition_alloc_base/logging.h"
13 #include "partition_alloc/partition_alloc_buildflags.h"
14 #include "partition_alloc/partition_alloc_config.h"
15 #include "partition_alloc/partition_alloc_for_testing.h"
16 #include "partition_alloc/shim/allocator_shim_default_dispatch_to_partition_alloc.h"
17 #include "testing/gtest/include/gtest/gtest.h"
18
19 #if BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) && \
20 PA_CONFIG(THREAD_CACHE_SUPPORTED)
21 #include "partition_alloc/extended_api.h"
22 #include "partition_alloc/thread_cache.h"
23 #endif
24
25 // Otherwise, PartitionAlloc doesn't allocate any memory, and the tests are
26 // meaningless.
27 #if !defined(MEMORY_TOOL_REPLACES_ALLOCATOR)
28
29 namespace partition_alloc {
30
31 namespace {
32
HandleOOM(size_t unused_size)33 void HandleOOM(size_t unused_size) {
34 PA_LOG(FATAL) << "Out of memory";
35 }
36
37 } // namespace
38
39 class MemoryReclaimerTest : public ::testing::Test {
40 public:
MemoryReclaimerTest()41 MemoryReclaimerTest() {
42 // Since MemoryReclaimer::ResetForTesting() clears partitions_,
43 // we need to make PartitionAllocator after this ResetForTesting().
44 // Otherwise, we will see no PartitionAllocator is registered.
45 MemoryReclaimer::Instance()->ResetForTesting();
46
47 PartitionOptions opts;
48 opts.star_scan_quarantine = PartitionOptions::kAllowed;
49 allocator_ = std::make_unique<PartitionAllocatorForTesting>(opts);
50 allocator_->root()->UncapEmptySlotSpanMemoryForTesting();
51 PartitionAllocGlobalInit(HandleOOM);
52 }
53
~MemoryReclaimerTest()54 ~MemoryReclaimerTest() override {
55 // Since MemoryReclaimer::UnregisterPartition() checks whether
56 // the given partition is managed by MemoryReclaimer, need to
57 // destruct |allocator_| before ResetForTesting().
58 allocator_ = nullptr;
59 PartitionAllocGlobalUninitForTesting();
60 }
61
Reclaim()62 void Reclaim() { MemoryReclaimer::Instance()->ReclaimNormal(); }
63
AllocateAndFree()64 void AllocateAndFree() {
65 void* data = allocator_->root()->Alloc(1);
66 allocator_->root()->Free(data);
67 }
68
69 std::unique_ptr<PartitionAllocatorForTesting> allocator_;
70 };
71
TEST_F(MemoryReclaimerTest,FreesMemory)72 TEST_F(MemoryReclaimerTest, FreesMemory) {
73 PartitionRoot* root = allocator_->root();
74
75 size_t committed_initially = root->get_total_size_of_committed_pages();
76 AllocateAndFree();
77 size_t committed_before = root->get_total_size_of_committed_pages();
78
79 EXPECT_GT(committed_before, committed_initially);
80
81 Reclaim();
82 size_t committed_after = root->get_total_size_of_committed_pages();
83 EXPECT_LT(committed_after, committed_before);
84 EXPECT_LE(committed_initially, committed_after);
85 }
86
TEST_F(MemoryReclaimerTest,Reclaim)87 TEST_F(MemoryReclaimerTest, Reclaim) {
88 PartitionRoot* root = allocator_->root();
89 size_t committed_initially = root->get_total_size_of_committed_pages();
90
91 {
92 AllocateAndFree();
93
94 size_t committed_before = root->get_total_size_of_committed_pages();
95 EXPECT_GT(committed_before, committed_initially);
96 MemoryReclaimer::Instance()->ReclaimAll();
97 size_t committed_after = root->get_total_size_of_committed_pages();
98
99 EXPECT_LT(committed_after, committed_before);
100 EXPECT_LE(committed_initially, committed_after);
101 }
102 }
103
104 #if BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) && \
105 PA_CONFIG(THREAD_CACHE_SUPPORTED)
106
107 namespace {
108 // malloc() / free() pairs can be removed by the compiler, this is enough (for
109 // now) to prevent that.
FreeForTest(void * data)110 PA_NOINLINE void FreeForTest(void* data) {
111 free(data);
112 }
113 } // namespace
114
TEST_F(MemoryReclaimerTest,DoNotAlwaysPurgeThreadCache)115 TEST_F(MemoryReclaimerTest, DoNotAlwaysPurgeThreadCache) {
116 // Make sure the thread cache is enabled in the main partition.
117 internal::ThreadCacheProcessScopeForTesting scope(
118 allocator_shim::internal::PartitionAllocMalloc::Allocator());
119
120 for (size_t i = 0; i < ThreadCache::kDefaultSizeThreshold; i++) {
121 void* data = malloc(i);
122 FreeForTest(data);
123 }
124
125 auto* tcache = ThreadCache::Get();
126 ASSERT_TRUE(tcache);
127 size_t cached_size = tcache->CachedMemory();
128
129 Reclaim();
130
131 // No thread cache purging during periodic purge, but with ReclaimAll().
132 //
133 // Cannot assert on the exact size of the thread cache, since it can shrink
134 // when a buffer is overfull, and this may happen through other malloc()
135 // allocations in the test harness.
136 EXPECT_GT(tcache->CachedMemory(), cached_size / 2);
137
138 Reclaim();
139 EXPECT_GT(tcache->CachedMemory(), cached_size / 2);
140
141 MemoryReclaimer::Instance()->ReclaimAll();
142 EXPECT_LT(tcache->CachedMemory(), cached_size / 2);
143 }
144
145 #endif // BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) && \
146 // PA_CONFIG(THREAD_CACHE_SUPPORTED)
147
148 } // namespace partition_alloc
149
150 #endif // !defined(MEMORY_TOOL_REPLACES_ALLOCATOR)
151