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