1 /*
2 * Copyright (c) Facebook, Inc. and its affiliates.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <chrono>
18 #include <thread>
19 #include <vector>
20
21 #include <folly/Memory.h>
22 #include <folly/executors/ManualExecutor.h>
23 #include <folly/futures/Future.h>
24 #include <folly/portability/GMock.h>
25 #include <folly/portability/GTest.h>
26 #include <folly/synchronization/Baton.h>
27 #include <wangle/client/persistence/LRUPersistentCache.h>
28 #include <wangle/client/persistence/SharedMutexCacheLockGuard.h>
29
30 using namespace folly;
31 using namespace std;
32 using namespace testing;
33 using namespace wangle;
34
35 using TestPersistenceLayer = CachePersistence;
36
37 using MutexTypes = ::testing::Types<std::mutex, folly::SharedMutex>;
38 TYPED_TEST_CASE(LRUPersistentCacheTest, MutexTypes);
39
40 template <typename T>
createCache(size_t capacity,uint32_t syncMillis,std::unique_ptr<TestPersistenceLayer> persistence=nullptr,bool loadPersistenceInline=true)41 static shared_ptr<LRUPersistentCache<string, string, T>> createCache(
42 size_t capacity,
43 uint32_t syncMillis,
44 std::unique_ptr<TestPersistenceLayer> persistence = nullptr,
45 bool loadPersistenceInline = true) {
46 using TestCache = LRUPersistentCache<string, string, T>;
47 return std::make_shared<TestCache>(
48 PersistentCacheConfig::Builder()
49 .setCapacity(capacity)
50 .setSyncInterval(std::chrono::milliseconds(syncMillis))
51 .setSyncRetries(3)
52 .setInlinePersistenceLoading(loadPersistenceInline)
53 .build(),
54 std::move(persistence));
55 }
56
57 template <typename T>
58 static shared_ptr<LRUPersistentCache<string, string, T>>
createCacheWithExecutor(std::shared_ptr<folly::Executor> executor,std::unique_ptr<TestPersistenceLayer> persistence,std::chrono::milliseconds syncInterval,int retryLimit,bool loadPersistenceInline=true)59 createCacheWithExecutor(
60 std::shared_ptr<folly::Executor> executor,
61 std::unique_ptr<TestPersistenceLayer> persistence,
62 std::chrono::milliseconds syncInterval,
63 int retryLimit,
64 bool loadPersistenceInline = true) {
65 return std::make_shared<LRUPersistentCache<string, string, T>>(
66 PersistentCacheConfig::Builder()
67 .setCapacity(10)
68 .setExecutor(std::move(executor))
69 .setSyncInterval(syncInterval)
70 .setSyncRetries(retryLimit)
71 .setInlinePersistenceLoading(loadPersistenceInline)
72 .build(),
73 std::move(persistence));
74 }
75
76 class MockPersistenceLayer : public TestPersistenceLayer {
77 public:
~MockPersistenceLayer()78 ~MockPersistenceLayer() override {
79 LOG(ERROR) << "ok.";
80 }
persist(const dynamic & obj)81 bool persist(const dynamic& obj) noexcept override {
82 return persist_(obj);
83 }
load()84 folly::Optional<dynamic> load() noexcept override {
85 return load_();
86 }
getLastPersistedVersionConcrete() const87 CacheDataVersion getLastPersistedVersionConcrete() const {
88 return TestPersistenceLayer::getLastPersistedVersion();
89 }
setPersistedVersionConcrete(CacheDataVersion version)90 void setPersistedVersionConcrete(CacheDataVersion version) {
91 TestPersistenceLayer::setPersistedVersion(version);
92 }
93 MOCK_METHOD0(clear, void());
94 MOCK_METHOD1(persist_, bool(const dynamic&));
95 MOCK_METHOD0(load_, folly::Optional<dynamic>());
96 MOCK_CONST_METHOD0(getLastPersistedVersion, CacheDataVersion());
97 GMOCK_METHOD1_(, noexcept, , setPersistedVersion, void(CacheDataVersion));
98 };
99
100 template <typename MutexT>
101 class LRUPersistentCacheTest : public Test {
102 protected:
SetUp()103 void SetUp() override {
104 persistence = make_unique<MockPersistenceLayer>();
105 ON_CALL(*persistence, getLastPersistedVersion())
106 .WillByDefault(Invoke(
107 persistence.get(),
108 &MockPersistenceLayer::getLastPersistedVersionConcrete));
109 ON_CALL(*persistence, setPersistedVersion(_))
110 .WillByDefault(Invoke(
111 persistence.get(),
112 &MockPersistenceLayer::setPersistedVersionConcrete));
113 manualExecutor = std::make_shared<folly::ManualExecutor>();
114 inlineExecutor = std::make_shared<folly::InlineExecutor>();
115 }
116
117 unique_ptr<MockPersistenceLayer> persistence;
118 std::shared_ptr<folly::ManualExecutor> manualExecutor;
119 std::shared_ptr<folly::InlineExecutor> inlineExecutor;
120 };
121
TYPED_TEST(LRUPersistentCacheTest,NullPersistence)122 TYPED_TEST(LRUPersistentCacheTest, NullPersistence) {
123 // make sure things sync even without a persistence layer
124 auto cache = createCache<TypeParam>(10, 1, nullptr);
125 cache->init();
126 cache->put("k0", "v0");
127 makeFuture()
128 .delayed(std::chrono::milliseconds(20))
129 .thenValue([cache](auto&&) {
130 auto val = cache->get("k0");
131 EXPECT_TRUE(val);
132 EXPECT_EQ(*val, "v0");
133 EXPECT_FALSE(cache->hasPendingUpdates());
134 });
135 }
136
137 MATCHER_P(DynSize, n, "") {
138 return size_t(n) == arg.size();
139 }
140
TYPED_TEST(LRUPersistentCacheTest,SettingPersistenceFromCtor)141 TYPED_TEST(LRUPersistentCacheTest, SettingPersistenceFromCtor) {
142 InSequence seq;
143 folly::dynamic data = dynamic::array(dynamic::array("k1", "v1"));
144 auto rawPersistence = this->persistence.get();
145 EXPECT_CALL(*rawPersistence, load_()).Times(1).WillOnce(Return(data));
146 EXPECT_CALL(*rawPersistence, persist_(DynSize(2)))
147 .Times(1)
148 .WillOnce(Return(true));
149 auto cache = createCache<TypeParam>(10, 10, std::move(this->persistence));
150 cache->init();
151 cache->put("k0", "v0");
152 }
153
TYPED_TEST(LRUPersistentCacheTest,SyncOnDestroy)154 TYPED_TEST(LRUPersistentCacheTest, SyncOnDestroy) {
155 auto persistence = this->persistence.get();
156 auto cache = createCache<TypeParam>(10, 10, std::move(this->persistence));
157 cache->init();
158 cache->put("k0", "v0");
159 EXPECT_CALL(*persistence, persist_(_)).Times(1).WillOnce(Return(true));
160 cache.reset();
161 }
162
TYPED_TEST(LRUPersistentCacheTest,SyncOnDestroyWithExecutor)163 TYPED_TEST(LRUPersistentCacheTest, SyncOnDestroyWithExecutor) {
164 auto persistence = this->persistence.get();
165 auto cache = createCacheWithExecutor<TypeParam>(
166 this->manualExecutor,
167 std::move(this->persistence),
168 std::chrono::milliseconds::zero(),
169 1);
170 cache->init();
171 cache->put("k0", "v0");
172 EXPECT_CALL(*persistence, persist_(_))
173 .Times(AtLeast(1))
174 .WillRepeatedly(Return(true));
175 cache.reset();
176 }
177
TYPED_TEST(LRUPersistentCacheTest,PersistNotCalled)178 TYPED_TEST(LRUPersistentCacheTest, PersistNotCalled) {
179 folly::dynamic data = dynamic::array(dynamic::array("k1", "v1"));
180 EXPECT_CALL(*this->persistence, load_()).Times(1).WillOnce(Return(data));
181 EXPECT_CALL(*this->persistence, persist_(_)).Times(0).WillOnce(Return(false));
182 auto cache = createCache<TypeParam>(10, 10, std::move(this->persistence));
183 cache->init();
184 EXPECT_EQ(cache->size(), 1);
185 }
186
TYPED_TEST(LRUPersistentCacheTest,PersistentSetBeforeSyncer)187 TYPED_TEST(LRUPersistentCacheTest, PersistentSetBeforeSyncer) {
188 EXPECT_CALL(*this->persistence, getLastPersistedVersion())
189 .Times(AtLeast(1))
190 .WillRepeatedly(Invoke(
191 this->persistence.get(),
192 &MockPersistenceLayer::getLastPersistedVersionConcrete));
193 auto cache = createCache<TypeParam>(10, 10, std::move(this->persistence));
194 cache->init();
195 }
196
TYPED_TEST(LRUPersistentCacheTest,ClearKeepPersist)197 TYPED_TEST(LRUPersistentCacheTest, ClearKeepPersist) {
198 EXPECT_CALL(*this->persistence, clear()).Times(0);
199 auto cache = createCache<TypeParam>(10, 10, std::move(this->persistence));
200 cache->init();
201 cache->clear();
202 }
203
TYPED_TEST(LRUPersistentCacheTest,ClearDontKeepPersist)204 TYPED_TEST(LRUPersistentCacheTest, ClearDontKeepPersist) {
205 EXPECT_CALL(*this->persistence, clear()).Times(1);
206 auto cache = createCache<TypeParam>(10, 10, std::move(this->persistence));
207 cache->init();
208 cache->clear(true);
209 }
210
TYPED_TEST(LRUPersistentCacheTest,ExecutorCacheDeallocBeforeAdd)211 TYPED_TEST(LRUPersistentCacheTest, ExecutorCacheDeallocBeforeAdd) {
212 auto cache = createCacheWithExecutor<TypeParam>(
213 this->manualExecutor,
214 std::move(this->persistence),
215 std::chrono::milliseconds::zero(),
216 1);
217 cache.reset();
218 // Nothing should happen here
219 this->manualExecutor->drain();
220 }
221
TYPED_TEST(LRUPersistentCacheTest,ExecutorCacheRunTask)222 TYPED_TEST(LRUPersistentCacheTest, ExecutorCacheRunTask) {
223 folly::dynamic data = dynamic::array(dynamic::array("k1", "v1"));
224 EXPECT_CALL(*this->persistence, load_()).Times(1).WillOnce(Return(data));
225 auto rawPersistence = this->persistence.get();
226 auto cache = createCacheWithExecutor<TypeParam>(
227 this->manualExecutor,
228 std::move(this->persistence),
229 std::chrono::milliseconds::zero(),
230 1);
231 cache->init();
232 this->manualExecutor->run();
233 cache->put("k0", "v0");
234 EXPECT_CALL(*rawPersistence, getLastPersistedVersion())
235 .Times(1)
236 .WillOnce(Invoke(
237 rawPersistence,
238 &MockPersistenceLayer::getLastPersistedVersionConcrete));
239 EXPECT_CALL(*rawPersistence, persist_(DynSize(2)))
240 .Times(1)
241 .WillOnce(Return(true));
242 this->manualExecutor->run();
243
244 EXPECT_CALL(*rawPersistence, getLastPersistedVersion())
245 .Times(1)
246 .WillOnce(Invoke(
247 rawPersistence,
248 &MockPersistenceLayer::getLastPersistedVersionConcrete));
249 cache.reset();
250 }
251
TYPED_TEST(LRUPersistentCacheTest,ExecutorCacheRunTaskInline)252 TYPED_TEST(LRUPersistentCacheTest, ExecutorCacheRunTaskInline) {
253 folly::dynamic data = dynamic::array(dynamic::array("k1", "v1"));
254 EXPECT_CALL(*this->persistence, load_()).Times(1).WillOnce(Return(data));
255 auto rawPersistence = this->persistence.get();
256 auto cache = createCacheWithExecutor<TypeParam>(
257 this->inlineExecutor,
258 std::move(this->persistence),
259 std::chrono::milliseconds::zero(),
260 1);
261 cache->init();
262 EXPECT_CALL(*rawPersistence, getLastPersistedVersion())
263 .Times(1)
264 .WillOnce(Invoke(
265 rawPersistence,
266 &MockPersistenceLayer::getLastPersistedVersionConcrete));
267 EXPECT_CALL(*rawPersistence, persist_(DynSize(2)))
268 .Times(1)
269 .WillOnce(Return(true));
270 cache->put("k0", "v0");
271
272 EXPECT_CALL(*rawPersistence, getLastPersistedVersion())
273 .Times(1)
274 .WillOnce(Invoke(
275 rawPersistence,
276 &MockPersistenceLayer::getLastPersistedVersionConcrete));
277 EXPECT_CALL(*rawPersistence, persist_(DynSize(3)))
278 .Times(1)
279 .WillOnce(Return(true));
280 cache->put("k2", "v2");
281
282 EXPECT_CALL(*rawPersistence, getLastPersistedVersion())
283 .Times(1)
284 .WillOnce(Invoke(
285 rawPersistence,
286 &MockPersistenceLayer::getLastPersistedVersionConcrete));
287 cache.reset();
288 }
289
TYPED_TEST(LRUPersistentCacheTest,ExecutorCacheRetries)290 TYPED_TEST(LRUPersistentCacheTest, ExecutorCacheRetries) {
291 EXPECT_CALL(*this->persistence, load_())
292 .Times(1)
293 .WillOnce(Return(dynamic::array()));
294 auto rawPersistence = this->persistence.get();
295 auto cache = createCacheWithExecutor<TypeParam>(
296 this->manualExecutor,
297 std::move(this->persistence),
298 std::chrono::milliseconds::zero(),
299 2);
300 cache->init();
301 this->manualExecutor->run();
302 EXPECT_CALL(*rawPersistence, getLastPersistedVersion())
303 .WillRepeatedly(Invoke(
304 rawPersistence,
305 &MockPersistenceLayer::getLastPersistedVersionConcrete));
306
307 cache->put("k0", "v0");
308 EXPECT_CALL(*rawPersistence, persist_(DynSize(1)))
309 .Times(1)
310 .WillOnce(Return(false));
311 this->manualExecutor->run();
312
313 cache->put("k1", "v1");
314 EXPECT_CALL(*rawPersistence, persist_(DynSize(2)))
315 .Times(1)
316 .WillOnce(Return(false));
317 // reached retry limit, so we will set a version anyway
318 EXPECT_CALL(*rawPersistence, setPersistedVersion(_))
319 .Times(1)
320 .WillOnce(Invoke(
321 rawPersistence, &MockPersistenceLayer::setPersistedVersionConcrete));
322 this->manualExecutor->run();
323
324 cache.reset();
325 }
326
TYPED_TEST(LRUPersistentCacheTest,ExecutorCacheSchduledAndDealloc)327 TYPED_TEST(LRUPersistentCacheTest, ExecutorCacheSchduledAndDealloc) {
328 folly::dynamic data = dynamic::array(dynamic::array("k1", "v1"));
329 EXPECT_CALL(*this->persistence, load_()).Times(1).WillOnce(Return(data));
330 auto cache = createCacheWithExecutor<TypeParam>(
331 this->manualExecutor,
332 std::move(this->persistence),
333 std::chrono::milliseconds::zero(),
334 1);
335 cache->init();
336 this->manualExecutor->run();
337 cache->put("k0", "v0");
338 cache->put("k2", "v2");
339
340 // Kill cache first then try to run scheduled tasks. Nothing will run and no
341 // one should crash.
342 cache.reset();
343 this->manualExecutor->drain();
344 }
345
TYPED_TEST(LRUPersistentCacheTest,ExecutorCacheScheduleInterval)346 TYPED_TEST(LRUPersistentCacheTest, ExecutorCacheScheduleInterval) {
347 EXPECT_CALL(*this->persistence, load_())
348 .Times(1)
349 .WillOnce(Return(dynamic::array()));
350 auto rawPersistence = this->persistence.get();
351 auto cache = createCacheWithExecutor<TypeParam>(
352 this->manualExecutor,
353 std::move(this->persistence),
354 std::chrono::milliseconds(60 * 60 * 1000),
355 1);
356 cache->init();
357 this->manualExecutor->run();
358 EXPECT_CALL(*rawPersistence, getLastPersistedVersion())
359 .WillRepeatedly(Invoke(
360 rawPersistence,
361 &MockPersistenceLayer::getLastPersistedVersionConcrete));
362
363 cache->put("k0", "v0");
364 EXPECT_CALL(*rawPersistence, persist_(DynSize(1)))
365 .Times(1)
366 .WillOnce(Return(false));
367 this->manualExecutor->run();
368
369 // The following put won't trigger a run due to the interval
370 EXPECT_CALL(*rawPersistence, persist_(DynSize(2))).Times(0);
371 EXPECT_CALL(*rawPersistence, setPersistedVersion(_)).Times(0);
372 cache->put("k1", "v1");
373 this->manualExecutor->run();
374
375 // But we will sync again upon destroy
376 EXPECT_CALL(*rawPersistence, persist_(DynSize(2)))
377 .Times(1)
378 .WillOnce(Return(true));
379 cache.reset();
380 // Nothing more should happen after this
381 this->manualExecutor->drain();
382 }
383
TYPED_TEST(LRUPersistentCacheTest,InitCache)384 TYPED_TEST(LRUPersistentCacheTest, InitCache) {
385 folly::dynamic data = dynamic::array(dynamic::array("k1", "v1"));
386 EXPECT_CALL(*this->persistence, load_()).Times(1).WillOnce(Return(data));
387 auto cache = createCacheWithExecutor<TypeParam>(
388 this->inlineExecutor,
389 std::move(this->persistence),
390 std::chrono::milliseconds::zero(),
391 1,
392 false);
393 cache->init();
394 EXPECT_FALSE(cache->hasPendingUpdates());
395 }
396
TYPED_TEST(LRUPersistentCacheTest,BlockingAccessCanContinueWithExecutor)397 TYPED_TEST(LRUPersistentCacheTest, BlockingAccessCanContinueWithExecutor) {
398 folly::dynamic data = dynamic::array(dynamic::array("k1", "v1"));
399 EXPECT_CALL(*this->persistence, load_()).Times(1).WillOnce(Return(data));
400 auto cache = createCacheWithExecutor<TypeParam>(
401 this->manualExecutor,
402 std::move(this->persistence),
403 std::chrono::milliseconds::zero(),
404 1,
405 false);
406 cache->init();
407 std::string value1, value2;
408 std::thread willBeBlocked([&]() { value1 = cache->get("k1").value(); });
409 // Without running the executor to finish setPersistenceHelper in init,
410 // willbeBlocked will be blocked.
411 this->manualExecutor->run();
412 willBeBlocked.join();
413 EXPECT_EQ("v1", value1);
414
415 std::thread wontBeBlocked([&]() { value2 = cache->get("k1").value(); });
416 wontBeBlocked.join();
417 EXPECT_EQ("v1", value2);
418 }
419
TYPED_TEST(LRUPersistentCacheTest,BlockingAccessCanContinueWithThread)420 TYPED_TEST(LRUPersistentCacheTest, BlockingAccessCanContinueWithThread) {
421 folly::dynamic data = dynamic::array(dynamic::array("k1", "v1"));
422 EXPECT_CALL(*this->persistence, load_()).Times(1).WillOnce(Return(data));
423 auto cache =
424 createCache<TypeParam>(10, 10, std::move(this->persistence), false);
425 cache->init();
426 std::string value1;
427 std::thread appThread([&]() { value1 = cache->get("k1").value(); });
428 appThread.join();
429 EXPECT_EQ("v1", value1);
430 }
431
TYPED_TEST(LRUPersistentCacheTest,PersistenceOnlyLoadedOnceFromCtorWithExecutor)432 TYPED_TEST(
433 LRUPersistentCacheTest,
434 PersistenceOnlyLoadedOnceFromCtorWithExecutor) {
435 folly::dynamic data = dynamic::array(dynamic::array("k1", "v1"));
436 auto persistence = this->persistence.get();
437 EXPECT_CALL(*persistence, load_()).Times(1).WillOnce(Return(data));
438 auto cache = createCacheWithExecutor<TypeParam>(
439 this->manualExecutor,
440 std::move(this->persistence),
441 std::chrono::milliseconds::zero(),
442 1);
443 cache->init();
444 }
445
TYPED_TEST(LRUPersistentCacheTest,PersistenceOnlyLoadedOnceFromCtorWithSyncThread)446 TYPED_TEST(
447 LRUPersistentCacheTest,
448 PersistenceOnlyLoadedOnceFromCtorWithSyncThread) {
449 folly::dynamic data = dynamic::array(dynamic::array("k1", "v1"));
450 auto persistence = this->persistence.get();
451 EXPECT_CALL(*persistence, load_()).Times(1).WillOnce(Return(data));
452 auto cache = createCache<TypeParam>(10, 1, std::move(this->persistence));
453 cache->init();
454 }
455
TYPED_TEST(LRUPersistentCacheTest,DestroyWithoutInitThreadMode)456 TYPED_TEST(LRUPersistentCacheTest, DestroyWithoutInitThreadMode) {
457 auto cache = createCache<TypeParam>(10, 10, std::move(this->persistence));
458 cache.reset();
459 }
460
TYPED_TEST(LRUPersistentCacheTest,DestroyWithoutInitExecutorMode)461 TYPED_TEST(LRUPersistentCacheTest, DestroyWithoutInitExecutorMode) {
462 auto cache = createCacheWithExecutor<TypeParam>(
463 this->inlineExecutor,
464 std::move(this->persistence),
465 std::chrono::milliseconds::zero(),
466 1);
467 cache.reset();
468 }
469
TYPED_TEST(LRUPersistentCacheTest,EmptyPersistenceMatchesEmptyCache)470 TYPED_TEST(LRUPersistentCacheTest, EmptyPersistenceMatchesEmptyCache) {
471 auto persistence = this->persistence.get();
472 EXPECT_CALL(*persistence, load_()).Times(1).WillOnce(Return(folly::none));
473 auto cache = createCacheWithExecutor<TypeParam>(
474 this->manualExecutor,
475 std::move(this->persistence),
476 std::chrono::milliseconds::zero(),
477 1);
478 cache->init();
479 EXPECT_FALSE(cache->hasPendingUpdates());
480 EXPECT_EQ(
481 kDefaultInitCacheDataVersion, persistence->getLastPersistedVersion());
482 this->manualExecutor->drain();
483
484 cache->put("k0", "v0");
485 EXPECT_TRUE(cache->hasPendingUpdates());
486 }
487
TYPED_TEST(LRUPersistentCacheTest,ZeroSyncIntervalSyncsImmediately)488 TYPED_TEST(LRUPersistentCacheTest, ZeroSyncIntervalSyncsImmediately) {
489 EXPECT_CALL(*this->persistence, load_())
490 .Times(1)
491 .WillOnce(Return(dynamic::array()));
492 auto rawPersistence = this->persistence.get();
493 auto cache = createCacheWithExecutor<TypeParam>(
494 this->manualExecutor,
495 std::move(this->persistence),
496 std::chrono::milliseconds::zero(),
497 1,
498 true);
499 cache->init();
500 this->manualExecutor->run();
501 EXPECT_CALL(*rawPersistence, getLastPersistedVersion())
502 .WillRepeatedly(Invoke(
503 rawPersistence,
504 &MockPersistenceLayer::getLastPersistedVersionConcrete));
505
506 cache->put("k0", "v0");
507 EXPECT_CALL(*rawPersistence, persist_(DynSize(1)))
508 .Times(1)
509 .WillOnce(Return(true));
510 this->manualExecutor->run();
511
512 // The following put will trigger a sync because syncImmediatelyWithExecutor
513 // is set to true
514 EXPECT_CALL(*rawPersistence, getLastPersistedVersion())
515 .WillRepeatedly(Invoke(
516 rawPersistence,
517 &MockPersistenceLayer::getLastPersistedVersionConcrete));
518 EXPECT_CALL(*rawPersistence, persist_(DynSize(2)))
519 .Times(1)
520 .WillOnce(Return(true));
521 cache->put("k1", "v1");
522 this->manualExecutor->run();
523
524 EXPECT_CALL(*rawPersistence, persist_(_)).Times(0);
525 cache.reset();
526 // Nothing more should happen after this
527 this->manualExecutor->drain();
528 }
529