client_discardable_shared_memory_manager_unittest.cc 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537
  1. // Copyright 2020 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "components/discardable_memory/client/client_discardable_shared_memory_manager.h"
  5. #include "base/memory/discardable_memory.h"
  6. #include "base/memory/discardable_shared_memory.h"
  7. #include "base/memory/page_size.h"
  8. #include "base/synchronization/lock.h"
  9. #include "base/test/metrics/histogram_tester.h"
  10. #include "base/test/scoped_feature_list.h"
  11. #include "base/test/task_environment.h"
  12. #include "build/build_config.h"
  13. #include "testing/gtest/include/gtest/gtest.h"
  14. namespace discardable_memory {
  15. namespace {
  16. using base::Location;
  17. using base::OnceClosure;
  18. class TestSingleThreadTaskRunner : public base::SingleThreadTaskRunner {
  19. ~TestSingleThreadTaskRunner() override = default;
  20. bool PostTask(const Location& from_here, OnceClosure task) { return true; }
  21. template <class T>
  22. bool DeleteSoon(const Location& from_here, const T* object) {
  23. return true;
  24. }
  25. bool PostDelayedTask(const Location& from_here,
  26. OnceClosure task,
  27. base::TimeDelta delay) override {
  28. return true;
  29. }
  30. bool PostNonNestableDelayedTask(const Location& from_here,
  31. OnceClosure task,
  32. base::TimeDelta delay) override {
  33. return true;
  34. }
  35. bool RunsTasksInCurrentSequence() const override { return true; }
  36. };
  37. class TestClientDiscardableSharedMemoryManager
  38. : public ClientDiscardableSharedMemoryManager {
  39. public:
  40. TestClientDiscardableSharedMemoryManager()
  41. : ClientDiscardableSharedMemoryManager(
  42. base::MakeRefCounted<TestSingleThreadTaskRunner>()) {}
  43. std::unique_ptr<base::DiscardableSharedMemory>
  44. AllocateLockedDiscardableSharedMemory(size_t size, int32_t id) override {
  45. auto shared_memory = std::make_unique<base::DiscardableSharedMemory>();
  46. shared_memory->CreateAndMap(size);
  47. return shared_memory;
  48. }
  49. void DeletedDiscardableSharedMemory(int32_t id) override {}
  50. size_t GetSize() const {
  51. base::AutoLock lock(lock_);
  52. return heap_->GetSize();
  53. }
  54. size_t GetFreelistSize() const {
  55. base::AutoLock lock(lock_);
  56. return heap_->GetFreelistSize();
  57. }
  58. size_t GetDirtyFreedMemoryPageCount() const {
  59. base::AutoLock lock(lock_);
  60. return heap_->dirty_freed_memory_page_count_;
  61. }
  62. bool IsPurgeScheduled() const {
  63. base::AutoLock lock(lock_);
  64. return is_purge_scheduled_;
  65. }
  66. private:
  67. ~TestClientDiscardableSharedMemoryManager() override = default;
  68. };
  69. class ClientDiscardableSharedMemoryManagerTest : public testing::Test {
  70. public:
  71. ClientDiscardableSharedMemoryManagerTest()
  72. : task_env_(base::test::TaskEnvironment::MainThreadType::UI,
  73. base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
  74. const size_t page_size_ = base::GetPageSize();
  75. base::test::TaskEnvironment task_env_;
  76. };
  77. // This test allocates a single piece of memory, then verifies that calling
  78. // |BackgroundPurge| only affects the memory when it is unlocked.
  79. TEST_F(ClientDiscardableSharedMemoryManagerTest, Simple) {
  80. auto client =
  81. base::MakeRefCounted<TestClientDiscardableSharedMemoryManager>();
  82. // Initially, we should have no memory allocated
  83. ASSERT_EQ(client->GetBytesAllocated(), 0u);
  84. ASSERT_EQ(client->GetFreelistSize(), 0u);
  85. auto mem = client->AllocateLockedDiscardableMemory(page_size_);
  86. // After allocation, we should have allocated a single piece of memory.
  87. EXPECT_EQ(client->GetBytesAllocated(), page_size_);
  88. client->BackgroundPurge();
  89. // All our memory is locked, so calling |BackgroundPurge| should have no
  90. // effect.
  91. EXPECT_EQ(client->GetBytesAllocated(), base::GetPageSize());
  92. mem->Unlock();
  93. // Unlocking has no effect on the amount of memory we have allocated.
  94. EXPECT_EQ(client->GetBytesAllocated(), base::GetPageSize());
  95. client->BackgroundPurge();
  96. // Now that |mem| is unlocked, the call to |BackgroundPurge| will
  97. // remove it.
  98. EXPECT_EQ(client->GetBytesAllocated(), 0u);
  99. }
  100. // This test allocates multiple pieces of memory, then unlocks them one by one,
  101. // verifying that |BackgroundPurge| only affects the unlocked pieces of
  102. // memory.
  103. TEST_F(ClientDiscardableSharedMemoryManagerTest, MultipleOneByOne) {
  104. auto client =
  105. base::MakeRefCounted<TestClientDiscardableSharedMemoryManager>();
  106. ASSERT_EQ(client->GetBytesAllocated(), 0u);
  107. ASSERT_EQ(client->GetFreelistSize(), 0u);
  108. auto mem1 = client->AllocateLockedDiscardableMemory(page_size_ * 2.2);
  109. auto mem2 = client->AllocateLockedDiscardableMemory(page_size_ * 1.1);
  110. auto mem3 = client->AllocateLockedDiscardableMemory(page_size_ * 3.5);
  111. auto mem4 = client->AllocateLockedDiscardableMemory(page_size_ * 0.2);
  112. EXPECT_EQ(client->GetBytesAllocated(), 10 * page_size_);
  113. // Does nothing because everything is locked.
  114. client->BackgroundPurge();
  115. EXPECT_EQ(client->GetBytesAllocated(), 10 * page_size_);
  116. mem1->Unlock();
  117. // Does nothing, since we don't have any free memory, just unlocked memory.
  118. client->ReleaseFreeMemory();
  119. EXPECT_EQ(client->GetBytesAllocated(), 10 * page_size_);
  120. // This gets rid of |mem1| (which is unlocked), but not the rest of the
  121. // memory.
  122. client->BackgroundPurge();
  123. EXPECT_EQ(client->GetBytesAllocated(), 7 * page_size_);
  124. // We do similar checks to above for the rest of the memory.
  125. mem2->Unlock();
  126. client->BackgroundPurge();
  127. EXPECT_EQ(client->GetBytesAllocated(), 5 * page_size_);
  128. mem3->Unlock();
  129. client->BackgroundPurge();
  130. EXPECT_EQ(client->GetBytesAllocated(), 1 * page_size_);
  131. mem4->Unlock();
  132. client->BackgroundPurge();
  133. EXPECT_EQ(client->GetBytesAllocated(), 0 * page_size_);
  134. }
  135. // This test allocates multiple pieces of memory, then unlocks them all,
  136. // verifying that |BackgroundPurge| only affects the unlocked pieces of
  137. // memory.
  138. TEST_F(ClientDiscardableSharedMemoryManagerTest, MultipleAtOnce) {
  139. auto client =
  140. base::MakeRefCounted<TestClientDiscardableSharedMemoryManager>();
  141. ASSERT_EQ(client->GetBytesAllocated(), 0u);
  142. ASSERT_EQ(client->GetFreelistSize(), 0u);
  143. auto mem1 = client->AllocateLockedDiscardableMemory(page_size_ * 2.2);
  144. auto mem2 = client->AllocateLockedDiscardableMemory(page_size_ * 1.1);
  145. auto mem3 = client->AllocateLockedDiscardableMemory(page_size_ * 3.5);
  146. auto mem4 = client->AllocateLockedDiscardableMemory(page_size_ * 0.2);
  147. EXPECT_EQ(client->GetBytesAllocated(), 10 * page_size_);
  148. // Does nothing because everything is locked.
  149. client->BackgroundPurge();
  150. EXPECT_EQ(client->GetBytesAllocated(), 10 * page_size_);
  151. // Unlock all pieces of memory at once.
  152. mem1->Unlock();
  153. mem2->Unlock();
  154. mem3->Unlock();
  155. mem4->Unlock();
  156. client->BackgroundPurge();
  157. EXPECT_EQ(client->GetBytesAllocated(), 0 * page_size_);
  158. }
  159. // Tests that FreeLists are only released once all memory has been released.
  160. TEST_F(ClientDiscardableSharedMemoryManagerTest, Release) {
  161. auto client =
  162. base::MakeRefCounted<TestClientDiscardableSharedMemoryManager>();
  163. ASSERT_EQ(client->GetBytesAllocated(), 0u);
  164. ASSERT_EQ(client->GetFreelistSize(), 0u);
  165. auto mem1 = client->AllocateLockedDiscardableMemory(page_size_ * 3);
  166. auto mem2 = client->AllocateLockedDiscardableMemory(page_size_ * 2);
  167. size_t freelist_size = client->GetFreelistSize();
  168. EXPECT_EQ(client->GetBytesAllocated(), 5 * page_size_);
  169. mem1 = nullptr;
  170. // Less memory is now allocated, but freelists are grown.
  171. EXPECT_EQ(client->GetBytesAllocated(), page_size_ * 2);
  172. EXPECT_EQ(client->GetFreelistSize(), freelist_size + page_size_ * 3);
  173. client->BackgroundPurge();
  174. // Purging doesn't remove any memory since none is unlocked, also doesn't
  175. // remove freelists since we still have some.
  176. EXPECT_EQ(client->GetBytesAllocated(), page_size_ * 2);
  177. EXPECT_EQ(client->GetFreelistSize(), freelist_size + page_size_ * 3);
  178. mem2 = nullptr;
  179. // No memory is allocated, but freelists are grown.
  180. EXPECT_EQ(client->GetBytesAllocated(), 0u);
  181. EXPECT_EQ(client->GetFreelistSize(), freelist_size + page_size_ * 5);
  182. client->BackgroundPurge();
  183. // Purging now shrinks freelists as well.
  184. EXPECT_EQ(client->GetBytesAllocated(), 0u);
  185. EXPECT_EQ(client->GetFreelistSize(), 0u);
  186. }
  187. // Similar to previous test, but makes sure that freelist still shrinks when
  188. // last piece of memory was just unlocked instead of released.
  189. TEST_F(ClientDiscardableSharedMemoryManagerTest, ReleaseUnlocked) {
  190. auto client =
  191. base::MakeRefCounted<TestClientDiscardableSharedMemoryManager>();
  192. ASSERT_EQ(client->GetBytesAllocated(), 0u);
  193. ASSERT_EQ(client->GetFreelistSize(), 0u);
  194. auto mem1 = client->AllocateLockedDiscardableMemory(page_size_ * 3);
  195. auto mem2 = client->AllocateLockedDiscardableMemory(page_size_ * 2);
  196. size_t freelist_size = client->GetFreelistSize();
  197. EXPECT_EQ(client->GetBytesAllocated(), 5 * page_size_);
  198. mem1 = nullptr;
  199. // Less memory is now allocated, but freelists are grown.
  200. EXPECT_EQ(client->GetBytesAllocated(), page_size_ * 2);
  201. EXPECT_EQ(client->GetFreelistSize(), freelist_size + page_size_ * 3);
  202. client->BackgroundPurge();
  203. // Purging doesn't remove any memory since none is unlocked, also doesn't
  204. // remove freelists since we still have some.
  205. EXPECT_EQ(client->GetBytesAllocated(), page_size_ * 2);
  206. EXPECT_EQ(client->GetFreelistSize(), freelist_size + page_size_ * 3);
  207. mem2->Unlock();
  208. // No change in memory usage, since memory was only unlocked not released.
  209. EXPECT_EQ(client->GetBytesAllocated(), page_size_ * 2);
  210. EXPECT_EQ(client->GetFreelistSize(), freelist_size + page_size_ * 3);
  211. client->BackgroundPurge();
  212. // Purging now shrinks freelists as well.
  213. EXPECT_EQ(client->GetBytesAllocated(), 0u);
  214. EXPECT_EQ(client->GetFreelistSize(), 0u);
  215. }
  216. // This tests that memory is actually removed by the periodic purging. We mock a
  217. // task runner for this test and fast forward to make sure that the memory is
  218. // purged at the right time.
  219. TEST_F(ClientDiscardableSharedMemoryManagerTest, ScheduledReleaseUnlocked) {
  220. auto client =
  221. base::MakeRefCounted<TestClientDiscardableSharedMemoryManager>();
  222. ASSERT_EQ(client->GetBytesAllocated(), 0u);
  223. ASSERT_EQ(client->GetFreelistSize(), 0u);
  224. auto mem1 = client->AllocateLockedDiscardableMemory(page_size_ * 3);
  225. task_env_.FastForwardBy(
  226. ClientDiscardableSharedMemoryManager::kMinAgeForScheduledPurge * 2);
  227. EXPECT_EQ(client->GetBytesAllocated(), 3 * page_size_);
  228. mem1->Unlock();
  229. task_env_.FastForwardBy(
  230. ClientDiscardableSharedMemoryManager::kMinAgeForScheduledPurge * 2);
  231. EXPECT_EQ(client->GetBytesAllocated(), 0u);
  232. }
  233. // Same as the above test, but tests that multiple pieces of memory will be
  234. // handled properly.
  235. TEST_F(ClientDiscardableSharedMemoryManagerTest,
  236. ScheduledReleaseUnlockedMultiple) {
  237. auto client =
  238. base::MakeRefCounted<TestClientDiscardableSharedMemoryManager>();
  239. ASSERT_EQ(client->GetBytesAllocated(), 0u);
  240. ASSERT_EQ(client->GetFreelistSize(), 0u);
  241. auto mem1 = client->AllocateLockedDiscardableMemory(page_size_ * 3);
  242. auto mem2 = client->AllocateLockedDiscardableMemory(page_size_ * 2);
  243. task_env_.FastForwardBy(
  244. ClientDiscardableSharedMemoryManager::kMinAgeForScheduledPurge * 2);
  245. EXPECT_EQ(client->GetBytesAllocated(), 5 * page_size_);
  246. mem1->Unlock();
  247. EXPECT_EQ(client->GetBytesAllocated(), 5 * page_size_);
  248. // The purge only removes things that have been unlocked for at least
  249. // |kMinAgeForScheduledPurge|
  250. // minutes so this shouldn't remove anything.
  251. task_env_.FastForwardBy(
  252. ClientDiscardableSharedMemoryManager::kMinAgeForScheduledPurge / 2);
  253. EXPECT_EQ(client->GetBytesAllocated(), 5 * page_size_);
  254. // The periodic purge should remove anything that's been locked for over
  255. // |kMinAgeForScheduledPurge|
  256. // minutes, so fast forward slightly more so that it happens.
  257. task_env_.FastForwardBy(
  258. ClientDiscardableSharedMemoryManager::kMinAgeForScheduledPurge / 2);
  259. EXPECT_EQ(client->GetBytesAllocated(), 2 * page_size_);
  260. mem2->Unlock();
  261. task_env_.FastForwardBy(
  262. ClientDiscardableSharedMemoryManager::kMinAgeForScheduledPurge * 2);
  263. EXPECT_EQ(client->GetBytesAllocated(), 0u);
  264. EXPECT_EQ(client->GetFreelistSize(), 0u);
  265. }
  266. // Tests that the UMA for Lock()-ing successes
  267. // ("Memory.Discardable.LockingSuccess") is recorded properly.
  268. TEST_F(ClientDiscardableSharedMemoryManagerTest, LockingSuccessUma) {
  269. auto client =
  270. base::MakeRefCounted<TestClientDiscardableSharedMemoryManager>();
  271. base::HistogramTester histograms;
  272. // number is arbitrary, we are only focused on whether the histogram is
  273. // properly recorded in this test.
  274. auto mem = client->AllocateLockedDiscardableMemory(200);
  275. histograms.ExpectBucketCount("Memory.Discardable.LockingSuccess", false, 0);
  276. histograms.ExpectBucketCount("Memory.Discardable.LockingSuccess", true, 0);
  277. // Unlock then lock. This should add one sample to the success bucket.
  278. mem->Unlock();
  279. bool result = mem->Lock();
  280. ASSERT_EQ(result, true);
  281. histograms.ExpectBucketCount("Memory.Discardable.LockingSuccess", false, 0);
  282. histograms.ExpectBucketCount("Memory.Discardable.LockingSuccess", true, 1);
  283. // Repeat the above to verify a second sample is added.
  284. mem->Unlock();
  285. result = mem->Lock();
  286. ASSERT_EQ(result, true);
  287. histograms.ExpectBucketCount("Memory.Discardable.LockingSuccess", false, 0);
  288. histograms.ExpectBucketCount("Memory.Discardable.LockingSuccess", true, 2);
  289. // This should now fail because the unlocked memory was purged. This should
  290. // add a sample to the failure bucket.
  291. mem->Unlock();
  292. client->BackgroundPurge();
  293. result = mem->Lock();
  294. ASSERT_EQ(result, false);
  295. histograms.ExpectBucketCount("Memory.Discardable.LockingSuccess", false, 1);
  296. histograms.ExpectBucketCount("Memory.Discardable.LockingSuccess", true, 2);
  297. }
  298. // Test that a repeating timer for background purging is created when we
  299. // allocate memory and discarded when we run out of allocated memory.
  300. TEST_F(ClientDiscardableSharedMemoryManagerTest, SchedulingProactivePurging) {
  301. auto client =
  302. base::MakeRefCounted<TestClientDiscardableSharedMemoryManager>();
  303. ASSERT_FALSE(client->IsPurgeScheduled());
  304. // the amount of memory allocated here is arbitrary, we're only trying to get
  305. // the timer started.
  306. auto mem = client->AllocateLockedDiscardableMemory(200);
  307. EXPECT_TRUE(client->IsPurgeScheduled());
  308. // This does not stop periodic purge because there is still memory allocated.
  309. client->ReleaseFreeMemory();
  310. EXPECT_TRUE(client->IsPurgeScheduled());
  311. // Even though no memory is allocated, periodic purge will stop running after
  312. // the next scheduled task.
  313. mem = nullptr;
  314. EXPECT_TRUE(client->IsPurgeScheduled());
  315. client->ReleaseFreeMemory();
  316. EXPECT_TRUE(client->IsPurgeScheduled());
  317. task_env_.FastForwardBy(
  318. ClientDiscardableSharedMemoryManager::kScheduledPurgeInterval);
  319. EXPECT_FALSE(client->IsPurgeScheduled());
  320. }
  321. // This test is similar to the one above, but tests that creating and deleting
  322. // the timer still works with multiple pieces of allocated memory.
  323. TEST_F(ClientDiscardableSharedMemoryManagerTest,
  324. SchedulingProactivePurgingMultipleAllocations) {
  325. auto client =
  326. base::MakeRefCounted<TestClientDiscardableSharedMemoryManager>();
  327. ASSERT_FALSE(client->IsPurgeScheduled());
  328. // the amount of memory allocated here is arbitrary, we're only trying to get
  329. // the timer started.
  330. auto mem = client->AllocateLockedDiscardableMemory(200);
  331. auto mem2 = client->AllocateLockedDiscardableMemory(100);
  332. task_env_.FastForwardBy(base::Seconds(0));
  333. EXPECT_TRUE(client->IsPurgeScheduled());
  334. client->ReleaseFreeMemory();
  335. EXPECT_TRUE(client->IsPurgeScheduled());
  336. mem = nullptr;
  337. task_env_.FastForwardBy(
  338. ClientDiscardableSharedMemoryManager::kScheduledPurgeInterval);
  339. EXPECT_TRUE(client->IsPurgeScheduled());
  340. client->ReleaseFreeMemory();
  341. EXPECT_TRUE(client->IsPurgeScheduled());
  342. mem2 = nullptr;
  343. task_env_.FastForwardBy(
  344. ClientDiscardableSharedMemoryManager::kScheduledPurgeInterval);
  345. EXPECT_FALSE(client->IsPurgeScheduled());
  346. }
  347. TEST_F(ClientDiscardableSharedMemoryManagerTest, MarkDirtyFreelistPages) {
  348. base::test::ScopedFeatureList fl;
  349. fl.InitAndDisableFeature(
  350. discardable_memory::kReleaseDiscardableFreeListPages);
  351. auto client =
  352. base::MakeRefCounted<TestClientDiscardableSharedMemoryManager>();
  353. ASSERT_EQ(0u, client->GetDirtyFreedMemoryPageCount());
  354. auto mem1 = client->AllocateLockedDiscardableMemory(base::GetPageSize() / 2u);
  355. ASSERT_EQ(0u, client->GetDirtyFreedMemoryPageCount());
  356. auto mem2 =
  357. client->AllocateLockedDiscardableMemory(base::GetPageSize() * 1.2);
  358. ASSERT_EQ(0u, client->GetDirtyFreedMemoryPageCount());
  359. // Allocate 5 MiB. This is to test large allocations, which are special-cased
  360. // when allocating.
  361. auto mem3 = client->AllocateLockedDiscardableMemory(5 * 1024 * 1024);
  362. ASSERT_EQ(0u, client->GetDirtyFreedMemoryPageCount());
  363. mem1 = nullptr;
  364. ASSERT_EQ(1u, client->GetDirtyFreedMemoryPageCount());
  365. mem2 = nullptr;
  366. // Allocations on done in multiples of the page size, so we have 3 pages
  367. // dirtied, even though we only actually touched 1.7 pages (since the 0.5 page
  368. // allocation used 1 page, and the 1.2 page allocation used 2).
  369. ASSERT_EQ(3u, client->GetDirtyFreedMemoryPageCount());
  370. mem3 = nullptr;
  371. ASSERT_EQ(3u + 5 * 1024 * 1024 / base::GetPageSize(),
  372. client->GetDirtyFreedMemoryPageCount());
  373. client->ReleaseFreeMemory();
  374. // All pages should be freed now, so there are no dirty pages in the freelist.
  375. ASSERT_EQ(0u, client->GetDirtyFreedMemoryPageCount());
  376. }
  377. TEST_F(ClientDiscardableSharedMemoryManagerTest,
  378. MarkDirtyFreelistPagesReleaseFreeListPages) {
  379. base::test::ScopedFeatureList fl;
  380. fl.InitAndEnableFeature(discardable_memory::kReleaseDiscardableFreeListPages);
  381. auto client =
  382. base::MakeRefCounted<TestClientDiscardableSharedMemoryManager>();
  383. ASSERT_EQ(0u, client->GetDirtyFreedMemoryPageCount());
  384. auto mem1 = client->AllocateLockedDiscardableMemory(base::GetPageSize() / 2u);
  385. ASSERT_EQ(0u, client->GetDirtyFreedMemoryPageCount());
  386. auto mem2 =
  387. client->AllocateLockedDiscardableMemory(base::GetPageSize() * 1.2);
  388. ASSERT_EQ(0u, client->GetDirtyFreedMemoryPageCount());
  389. mem1 = nullptr;
  390. ASSERT_EQ(0u, client->GetDirtyFreedMemoryPageCount());
  391. mem2 = nullptr;
  392. // Freelist memory is released immediately, so there's no dirty memory.
  393. ASSERT_EQ(0u, client->GetDirtyFreedMemoryPageCount());
  394. client->ReleaseFreeMemory();
  395. ASSERT_EQ(0u, client->GetDirtyFreedMemoryPageCount());
  396. }
  397. } // namespace
  398. } // namespace discardable_memory