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 "partition_alloc/partition_alloc.h"
8 #include "partition_alloc/partition_alloc_base/no_destructor.h"
9 #include "partition_alloc/partition_alloc_buildflags.h"
10 #include "partition_alloc/partition_alloc_check.h"
11 #include "partition_alloc/partition_alloc_config.h"
12 
13 #if BUILDFLAG(USE_STARSCAN)
14 #include "partition_alloc/starscan/pcscan.h"
15 #endif
16 
17 namespace partition_alloc {
18 
19 // static
Instance()20 MemoryReclaimer* MemoryReclaimer::Instance() {
21   static internal::base::NoDestructor<MemoryReclaimer> instance;
22   return instance.get();
23 }
24 
RegisterPartition(PartitionRoot * partition)25 void MemoryReclaimer::RegisterPartition(PartitionRoot* partition) {
26   internal::ScopedGuard lock(lock_);
27   PA_DCHECK(partition);
28   auto it_and_whether_inserted = partitions_.insert(partition);
29   PA_DCHECK(it_and_whether_inserted.second);
30 }
31 
UnregisterPartition(PartitionRoot * partition)32 void MemoryReclaimer::UnregisterPartition(PartitionRoot* partition) {
33   internal::ScopedGuard lock(lock_);
34   PA_DCHECK(partition);
35   size_t erased_count = partitions_.erase(partition);
36   PA_DCHECK(erased_count == 1u);
37 }
38 
39 MemoryReclaimer::MemoryReclaimer() = default;
40 MemoryReclaimer::~MemoryReclaimer() = default;
41 
ReclaimAll()42 void MemoryReclaimer::ReclaimAll() {
43   constexpr int kFlags = PurgeFlags::kDecommitEmptySlotSpans |
44                          PurgeFlags::kDiscardUnusedSystemPages |
45                          PurgeFlags::kAggressiveReclaim;
46   Reclaim(kFlags);
47 }
48 
ReclaimNormal()49 void MemoryReclaimer::ReclaimNormal() {
50   constexpr int kFlags = PurgeFlags::kDecommitEmptySlotSpans |
51                          PurgeFlags::kDiscardUnusedSystemPages;
52   Reclaim(kFlags);
53 }
54 
Reclaim(int flags)55 void MemoryReclaimer::Reclaim(int flags) {
56   internal::ScopedGuard lock(
57       lock_);  // Has to protect from concurrent (Un)Register calls.
58 
59   // PCScan quarantines freed slots. Trigger the scan first to let it call
60   // FreeNoHooksImmediate on slots that pass the quarantine.
61   //
62   // In turn, FreeNoHooksImmediate may add slots to thread cache. Purge it next
63   // so that the slots are actually freed. (This is done synchronously only for
64   // the current thread.)
65   //
66   // Lastly decommit empty slot spans and lastly try to discard unused pages at
67   // the end of the remaining active slots.
68 #if PA_CONFIG(STARSCAN_ENABLE_STARSCAN_ON_RECLAIM) && BUILDFLAG(USE_STARSCAN)
69   {
70     using PCScan = internal::PCScan;
71     const auto invocation_mode = flags & PurgeFlags::kAggressiveReclaim
72                                      ? PCScan::InvocationMode::kForcedBlocking
73                                      : PCScan::InvocationMode::kBlocking;
74     PCScan::PerformScanIfNeeded(invocation_mode);
75   }
76 #endif  // PA_CONFIG(STARSCAN_ENABLE_STARSCAN_ON_RECLAIM) &&
77         // BUILDFLAG(USE_STARSCAN)
78 
79 #if PA_CONFIG(THREAD_CACHE_SUPPORTED)
80   // Don't completely empty the thread cache outside of low memory situations,
81   // as there is periodic purge which makes sure that it doesn't take too much
82   // space.
83   if (flags & PurgeFlags::kAggressiveReclaim) {
84     ThreadCacheRegistry::Instance().PurgeAll();
85   }
86 #endif  // PA_CONFIG(THREAD_CACHE_SUPPORTED)
87 
88   for (auto* partition : partitions_) {
89     partition->PurgeMemory(flags);
90   }
91 }
92 
ResetForTesting()93 void MemoryReclaimer::ResetForTesting() {
94   internal::ScopedGuard lock(lock_);
95   partitions_.clear();
96 }
97 
98 }  // namespace partition_alloc
99