1// Copyright 2014 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
5#include "base/memory/discardable_memory_manager.h"
6
7#include "base/bind.h"
8#include "base/synchronization/waitable_event.h"
9#include "base/threading/thread.h"
10#include "testing/gtest/include/gtest/gtest.h"
11
12namespace base {
13namespace {
14
15class TestAllocationImpl : public internal::DiscardableMemoryManagerAllocation {
16 public:
17  TestAllocationImpl() : is_allocated_(false), is_locked_(false) {}
18  virtual ~TestAllocationImpl() { DCHECK(!is_locked_); }
19
20  // Overridden from internal::DiscardableMemoryManagerAllocation:
21  virtual bool AllocateAndAcquireLock() OVERRIDE {
22    bool was_allocated = is_allocated_;
23    is_allocated_ = true;
24    DCHECK(!is_locked_);
25    is_locked_ = true;
26    return was_allocated;
27  }
28  virtual void ReleaseLock() OVERRIDE {
29    DCHECK(is_locked_);
30    is_locked_ = false;
31  }
32  virtual void Purge() OVERRIDE {
33    DCHECK(is_allocated_);
34    is_allocated_ = false;
35  }
36
37  bool is_locked() const { return is_locked_; }
38
39 private:
40  bool is_allocated_;
41  bool is_locked_;
42};
43
44// Tests can assume that the default limit is at least 1024. Tests that rely on
45// something else needs to explicit set the limit.
46const size_t kDefaultMemoryLimit = 1024;
47const size_t kDefaultSoftMemoryLimit = kDefaultMemoryLimit;
48
49class TestDiscardableMemoryManagerImpl
50    : public internal::DiscardableMemoryManager {
51 public:
52  TestDiscardableMemoryManagerImpl()
53      : DiscardableMemoryManager(kDefaultMemoryLimit,
54                                 kDefaultSoftMemoryLimit,
55                                 TimeDelta::Max()) {}
56
57  void SetNow(TimeTicks now) { now_ = now; }
58
59 private:
60  // Overriden from internal::DiscardableMemoryManager:
61  virtual TimeTicks Now() const OVERRIDE { return now_; }
62
63  TimeTicks now_;
64};
65
66class DiscardableMemoryManagerTestBase {
67 public:
68  DiscardableMemoryManagerTestBase() {}
69
70 protected:
71  enum LockStatus {
72    LOCK_STATUS_FAILED,
73    LOCK_STATUS_PURGED,
74    LOCK_STATUS_SUCCESS
75  };
76
77  size_t BytesAllocated() const { return manager_.GetBytesAllocatedForTest(); }
78
79  void SetMemoryLimit(size_t bytes) { manager_.SetMemoryLimit(bytes); }
80
81  void SetSoftMemoryLimit(size_t bytes) { manager_.SetSoftMemoryLimit(bytes); }
82
83  void SetHardMemoryLimitExpirationTime(TimeDelta time) {
84    manager_.SetHardMemoryLimitExpirationTime(time);
85  }
86
87  void Register(TestAllocationImpl* allocation, size_t bytes) {
88    manager_.Register(allocation, bytes);
89  }
90
91  void Unregister(TestAllocationImpl* allocation) {
92    manager_.Unregister(allocation);
93  }
94
95  bool IsRegistered(TestAllocationImpl* allocation) const {
96    return manager_.IsRegisteredForTest(allocation);
97  }
98
99  LockStatus Lock(TestAllocationImpl* allocation) {
100    bool purged;
101    if (!manager_.AcquireLock(allocation, &purged))
102      return LOCK_STATUS_FAILED;
103    return purged ? LOCK_STATUS_PURGED : LOCK_STATUS_SUCCESS;
104  }
105
106  void Unlock(TestAllocationImpl* allocation) {
107    manager_.ReleaseLock(allocation);
108  }
109
110  LockStatus RegisterAndLock(TestAllocationImpl* allocation, size_t bytes) {
111    manager_.Register(allocation, bytes);
112    return Lock(allocation);
113  }
114
115  bool CanBePurged(TestAllocationImpl* allocation) const {
116    return manager_.CanBePurgedForTest(allocation);
117  }
118
119  void SetNow(TimeTicks now) { manager_.SetNow(now); }
120
121  void PurgeAll() { return manager_.PurgeAll(); }
122
123  bool ReduceMemoryUsage() { return manager_.ReduceMemoryUsage(); }
124
125  void ReduceMemoryUsageUntilWithinLimit(size_t bytes) {
126    manager_.ReduceMemoryUsageUntilWithinLimit(bytes);
127  }
128
129 private:
130  TestDiscardableMemoryManagerImpl manager_;
131};
132
133class DiscardableMemoryManagerTest : public DiscardableMemoryManagerTestBase,
134                                     public testing::Test {
135 public:
136  DiscardableMemoryManagerTest() {}
137};
138
139TEST_F(DiscardableMemoryManagerTest, CreateAndLock) {
140  size_t size = 1024;
141  TestAllocationImpl allocation;
142  Register(&allocation, size);
143  EXPECT_TRUE(IsRegistered(&allocation));
144  EXPECT_EQ(LOCK_STATUS_PURGED, Lock(&allocation));
145  EXPECT_TRUE(allocation.is_locked());
146  EXPECT_EQ(1024u, BytesAllocated());
147  EXPECT_FALSE(CanBePurged(&allocation));
148  Unlock(&allocation);
149  Unregister(&allocation);
150}
151
152TEST_F(DiscardableMemoryManagerTest, CreateZeroSize) {
153  size_t size = 0;
154  TestAllocationImpl allocation;
155  Register(&allocation, size);
156  EXPECT_TRUE(IsRegistered(&allocation));
157  EXPECT_EQ(LOCK_STATUS_FAILED, Lock(&allocation));
158  EXPECT_EQ(0u, BytesAllocated());
159  Unregister(&allocation);
160}
161
162TEST_F(DiscardableMemoryManagerTest, LockAfterUnlock) {
163  size_t size = 1024;
164  TestAllocationImpl allocation;
165  RegisterAndLock(&allocation, size);
166  EXPECT_EQ(1024u, BytesAllocated());
167  EXPECT_FALSE(CanBePurged(&allocation));
168
169  // Now unlock so we can lock later.
170  Unlock(&allocation);
171  EXPECT_TRUE(CanBePurged(&allocation));
172
173  EXPECT_EQ(LOCK_STATUS_SUCCESS, Lock(&allocation));
174  EXPECT_FALSE(CanBePurged(&allocation));
175  Unlock(&allocation);
176  Unregister(&allocation);
177}
178
179TEST_F(DiscardableMemoryManagerTest, LockAfterPurge) {
180  size_t size = 1024;
181  TestAllocationImpl allocation;
182  RegisterAndLock(&allocation, size);
183  EXPECT_EQ(1024u, BytesAllocated());
184  EXPECT_FALSE(CanBePurged(&allocation));
185
186  // Now unlock so we can lock later.
187  Unlock(&allocation);
188  EXPECT_TRUE(CanBePurged(&allocation));
189
190  // Force the system to purge.
191  PurgeAll();
192
193  EXPECT_EQ(LOCK_STATUS_PURGED, Lock(&allocation));
194  EXPECT_FALSE(CanBePurged(&allocation));
195
196  Unlock(&allocation);
197  Unregister(&allocation);
198}
199
200TEST_F(DiscardableMemoryManagerTest, LockAfterPurgeAndCannotReallocate) {
201  size_t size = 1024;
202  TestAllocationImpl allocation;
203  RegisterAndLock(&allocation, size);
204  EXPECT_EQ(1024u, BytesAllocated());
205  EXPECT_FALSE(CanBePurged(&allocation));
206
207  // Now unlock so we can lock later.
208  Unlock(&allocation);
209  EXPECT_TRUE(CanBePurged(&allocation));
210
211  // Set max allowed allocation to 1 byte. This will cause the memory to be
212  // purged.
213  SetMemoryLimit(1);
214
215  EXPECT_EQ(LOCK_STATUS_PURGED, Lock(&allocation));
216  EXPECT_FALSE(CanBePurged(&allocation));
217
218  Unlock(&allocation);
219  Unregister(&allocation);
220}
221
222TEST_F(DiscardableMemoryManagerTest, Overflow) {
223  size_t size = 1024;
224  {
225    TestAllocationImpl allocation;
226    RegisterAndLock(&allocation, size);
227    EXPECT_EQ(1024u, BytesAllocated());
228
229    size_t massive_size = std::numeric_limits<size_t>::max();
230    TestAllocationImpl massive_allocation;
231    Register(&massive_allocation, massive_size);
232    EXPECT_EQ(LOCK_STATUS_FAILED, Lock(&massive_allocation));
233    EXPECT_EQ(1024u, BytesAllocated());
234
235    Unlock(&allocation);
236    EXPECT_EQ(LOCK_STATUS_PURGED, Lock(&massive_allocation));
237    Unlock(&massive_allocation);
238    Unregister(&massive_allocation);
239    Unregister(&allocation);
240  }
241  EXPECT_EQ(0u, BytesAllocated());
242}
243
244class PermutationTestData {
245 public:
246  PermutationTestData(unsigned d0, unsigned d1, unsigned d2) {
247    ordering_[0] = d0;
248    ordering_[1] = d1;
249    ordering_[2] = d2;
250  }
251
252  const unsigned* ordering() const { return ordering_; }
253
254 private:
255  unsigned ordering_[3];
256};
257
258class DiscardableMemoryManagerPermutationTest
259    : public DiscardableMemoryManagerTestBase,
260      public testing::TestWithParam<PermutationTestData> {
261 public:
262  DiscardableMemoryManagerPermutationTest() {}
263
264 protected:
265  // Use memory in order specified by ordering parameter.
266  void RegisterAndUseAllocations() {
267    for (int i = 0; i < 3; ++i) {
268      RegisterAndLock(&allocation_[i], 1024);
269      Unlock(&allocation_[i]);
270    }
271    for (int i = 0; i < 3; ++i) {
272      int index = GetParam().ordering()[i];
273      EXPECT_NE(LOCK_STATUS_FAILED, Lock(&allocation_[index]));
274      // Leave i == 0 locked.
275      if (i > 0)
276        Unlock(&allocation_[index]);
277    }
278  }
279
280  TestAllocationImpl* allocation(unsigned position) {
281    return &allocation_[GetParam().ordering()[position]];
282  }
283
284  void UnlockAndUnregisterAllocations() {
285    for (int i = 0; i < 3; ++i) {
286      if (allocation_[i].is_locked())
287        Unlock(&allocation_[i]);
288      Unregister(&allocation_[i]);
289    }
290  }
291
292 private:
293  TestAllocationImpl allocation_[3];
294};
295
296// Verify that memory was discarded in the correct order after reducing usage to
297// limit.
298TEST_P(DiscardableMemoryManagerPermutationTest, LRUDiscarded) {
299  RegisterAndUseAllocations();
300
301  SetMemoryLimit(2048);
302
303  ReduceMemoryUsageUntilWithinLimit(1024);
304
305  EXPECT_NE(LOCK_STATUS_FAILED, Lock(allocation(2)));
306  EXPECT_EQ(LOCK_STATUS_PURGED, Lock(allocation(1)));
307  // 0 should still be locked.
308  EXPECT_TRUE(allocation(0)->is_locked());
309
310  UnlockAndUnregisterAllocations();
311}
312
313// Verify that memory was discarded in the correct order after changing
314// memory limit.
315TEST_P(DiscardableMemoryManagerPermutationTest, LRUDiscardedExceedLimit) {
316  RegisterAndUseAllocations();
317
318  SetMemoryLimit(2048);
319
320  EXPECT_NE(LOCK_STATUS_FAILED, Lock(allocation(2)));
321  EXPECT_EQ(LOCK_STATUS_PURGED, Lock(allocation(1)));
322  // 0 should still be locked.
323  EXPECT_TRUE(allocation(0)->is_locked());
324
325  UnlockAndUnregisterAllocations();
326}
327
328// Verify that no more memory than necessary was discarded after changing
329// memory limit.
330TEST_P(DiscardableMemoryManagerPermutationTest, LRUDiscardedAmount) {
331  SetMemoryLimit(4096);
332
333  RegisterAndUseAllocations();
334
335  SetMemoryLimit(2048);
336
337  EXPECT_EQ(LOCK_STATUS_SUCCESS, Lock(allocation(2)));
338  EXPECT_EQ(LOCK_STATUS_PURGED, Lock(allocation(1)));
339  // 0 should still be locked.
340  EXPECT_TRUE(allocation(0)->is_locked());
341
342  UnlockAndUnregisterAllocations();
343}
344
345TEST_P(DiscardableMemoryManagerPermutationTest, PurgeFreesAllUnlocked) {
346  RegisterAndUseAllocations();
347
348  PurgeAll();
349
350  for (int i = 0; i < 3; ++i) {
351    if (i == 0)
352      EXPECT_TRUE(allocation(i)->is_locked());
353    else
354      EXPECT_EQ(LOCK_STATUS_PURGED, Lock(allocation(i)));
355  }
356
357  UnlockAndUnregisterAllocations();
358}
359
360INSTANTIATE_TEST_CASE_P(DiscardableMemoryManagerPermutationTests,
361                        DiscardableMemoryManagerPermutationTest,
362                        ::testing::Values(PermutationTestData(0, 1, 2),
363                                          PermutationTestData(0, 2, 1),
364                                          PermutationTestData(1, 0, 2),
365                                          PermutationTestData(1, 2, 0),
366                                          PermutationTestData(2, 0, 1),
367                                          PermutationTestData(2, 1, 0)));
368
369TEST_F(DiscardableMemoryManagerTest, NormalDestruction) {
370  {
371    size_t size = 1024;
372    TestAllocationImpl allocation;
373    Register(&allocation, size);
374    Unregister(&allocation);
375  }
376  EXPECT_EQ(0u, BytesAllocated());
377}
378
379TEST_F(DiscardableMemoryManagerTest, DestructionAfterLocked) {
380  {
381    size_t size = 1024;
382    TestAllocationImpl allocation;
383    RegisterAndLock(&allocation, size);
384    EXPECT_EQ(1024u, BytesAllocated());
385    EXPECT_FALSE(CanBePurged(&allocation));
386    Unlock(&allocation);
387    Unregister(&allocation);
388  }
389  EXPECT_EQ(0u, BytesAllocated());
390}
391
392TEST_F(DiscardableMemoryManagerTest, DestructionAfterPurged) {
393  {
394    size_t size = 1024;
395    TestAllocationImpl allocation;
396    RegisterAndLock(&allocation, size);
397    EXPECT_EQ(1024u, BytesAllocated());
398    Unlock(&allocation);
399    EXPECT_TRUE(CanBePurged(&allocation));
400    SetMemoryLimit(0);
401    EXPECT_EQ(0u, BytesAllocated());
402    Unregister(&allocation);
403  }
404  EXPECT_EQ(0u, BytesAllocated());
405}
406
407TEST_F(DiscardableMemoryManagerTest, ReduceMemoryUsage) {
408  SetMemoryLimit(3072);
409  SetSoftMemoryLimit(1024);
410  SetHardMemoryLimitExpirationTime(TimeDelta::FromInternalValue(1));
411
412  size_t size = 1024;
413  TestAllocationImpl allocation[3];
414  RegisterAndLock(&allocation[0], size);
415  RegisterAndLock(&allocation[1], size);
416  RegisterAndLock(&allocation[2], size);
417  EXPECT_EQ(3072u, BytesAllocated());
418
419  // Above soft limit but nothing that can be purged.
420  EXPECT_FALSE(ReduceMemoryUsage());
421
422  SetNow(TimeTicks::FromInternalValue(0));
423  Unlock(&allocation[0]);
424
425  // Above soft limit but still nothing that can be purged as all unlocked
426  // allocations are within the hard limit cutoff time.
427  EXPECT_FALSE(ReduceMemoryUsage());
428
429  SetNow(TimeTicks::FromInternalValue(1));
430  Unlock(&allocation[1]);
431
432  // One unlocked allocation is no longer within the hard limit cutoff time. It
433  // should be purged and ReduceMemoryUsage() should return false as we're not
434  // yet within the soft memory limit.
435  EXPECT_FALSE(ReduceMemoryUsage());
436  EXPECT_EQ(2048u, BytesAllocated());
437
438  // One more unlocked allocation is no longer within the hard limit cutoff
439  // time. It should be purged and ReduceMemoryUsage() should return true as
440  // we're now within the soft memory limit.
441  SetNow(TimeTicks::FromInternalValue(2));
442  EXPECT_TRUE(ReduceMemoryUsage());
443  EXPECT_EQ(1024u, BytesAllocated());
444
445  Unlock(&allocation[2]);
446
447  Unregister(&allocation[0]);
448  Unregister(&allocation[1]);
449  Unregister(&allocation[2]);
450}
451
452class ThreadedDiscardableMemoryManagerTest
453    : public DiscardableMemoryManagerTest {
454 public:
455  ThreadedDiscardableMemoryManagerTest()
456      : memory_usage_thread_("memory_usage_thread"),
457        thread_sync_(true, false) {}
458
459  virtual void SetUp() OVERRIDE { memory_usage_thread_.Start(); }
460
461  virtual void TearDown() OVERRIDE { memory_usage_thread_.Stop(); }
462
463  void UseMemoryHelper() {
464    size_t size = 1024;
465    TestAllocationImpl allocation;
466    RegisterAndLock(&allocation, size);
467    Unlock(&allocation);
468    Unregister(&allocation);
469  }
470
471  void SignalHelper() { thread_sync_.Signal(); }
472
473  Thread memory_usage_thread_;
474  WaitableEvent thread_sync_;
475};
476
477TEST_F(ThreadedDiscardableMemoryManagerTest, UseMemoryOnThread) {
478  memory_usage_thread_.message_loop()->PostTask(
479      FROM_HERE,
480      Bind(&ThreadedDiscardableMemoryManagerTest::UseMemoryHelper,
481           Unretained(this)));
482  memory_usage_thread_.message_loop()->PostTask(
483      FROM_HERE,
484      Bind(&ThreadedDiscardableMemoryManagerTest::SignalHelper,
485           Unretained(this)));
486  thread_sync_.Wait();
487}
488
489}  // namespace
490}  // namespace base
491