1 /* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3  *     Copyright 2017 Couchbase, Inc
4  *
5  *   Licensed under the Apache License, Version 2.0 (the "License");
6  *   you may not use this file except in compliance with the License.
7  *   You may obtain a copy of the License at
8  *
9  *       http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *   Unless required by applicable law or agreed to in writing, software
12  *   distributed under the License is distributed on an "AS IS" BASIS,
13  *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *   See the License for the specific language governing permissions and
15  *   limitations under the License.
16  */
17 
18 /**
19  * Unit tests for Item Paging / Expiration.
20  */
21 
22 #include "../mock/mock_global_task.h"
23 #include "../mock/mock_paging_visitor.h"
24 #include "bgfetcher.h"
25 #include "checkpoint_manager.h"
26 #include "checkpoint_utils.h"
27 #include "ep_bucket.h"
28 #include "ep_time.h"
29 #include "evp_store_single_threaded_test.h"
30 #include "item.h"
31 #include "item_eviction.h"
32 #include "kv_bucket.h"
33 #include "memory_tracker.h"
34 #include "test_helpers.h"
35 #include "tests/mock/mock_synchronous_ep_engine.h"
36 
37 #include <folly/portability/GTest.h>
38 #include <programs/engine_testapp/mock_server.h>
39 #include <string_utilities.h>
40 #include <xattr/blob.h>
41 #include <xattr/utils.h>
42 
43 using namespace std::string_literals;
44 
45 using FlushResult = EPBucket::FlushResult;
46 using MoreAvailable = EPBucket::MoreAvailable;
47 using WakeCkptRemover = EPBucket::WakeCkptRemover;
48 
49 /**
50  * Test fixture for bucket quota tests. Sets quota (max_size) to 200KB and
51  * enables the MemoryTracker.
52  *
53  * NOTE: All the tests using this (including subclasses) require memory
54  * tracking to be enabled.
55  */
56 class STBucketQuotaTest : public STParameterizedBucketTest {
57 public:
SetUpTestCase()58     static void SetUpTestCase() {
59         // Setup the MemoryTracker.
60         MemoryTracker::getInstance(*get_mock_server_api()->alloc_hooks);
61     }
62 
TearDownTestCase()63     static void TearDownTestCase() {
64         MemoryTracker::destroyInstance();
65     }
66 
67 protected:
SetUp()68     void SetUp() override {
69         // Set specific ht_size given we need to control expected memory usage.
70         config_string += "ht_size=47;max_size=" + std::to_string(200 * 1024) +
71                          ";mem_low_wat=" + std::to_string(120 * 1024) +
72                          ";mem_high_wat=" + std::to_string(160 * 1024);
73         STParameterizedBucketTest::SetUp();
74 
75         // How many nonIO tasks we expect initially
76         // - 0 for persistent.
77         // - 1 for Ephemeral (EphTombstoneHTCleaner).
78         if (std::get<0>(GetParam()) == "ephemeral") {
79             ++initialNonIoTasks;
80         }
81 
82         // Sanity check - need memory tracker to be able to check our memory
83         // usage.
84         ASSERT_TRUE(MemoryTracker::trackingMemoryAllocations())
85             << "Memory tracker not enabled - cannot continue";
86 
87         store->setVBucketState(vbid, vbucket_state_active);
88 
89         // Sanity check - to ensure memory usage doesn't increase without us
90         // noticing.
91         ASSERT_EQ(47, store->getVBucket(vbid)->ht.getSize())
92                 << "Expected to have a HashTable of size 47 (mem calculations "
93                    "based on this).";
94         auto& stats = engine->getEpStats();
95         ASSERT_LE(stats.getEstimatedTotalMemoryUsed(), 20 * 1024)
96                 << "Expected to start with less than 20KB of memory used";
97         ASSERT_LT(stats.getEstimatedTotalMemoryUsed(),
98                   stats.getMaxDataSize() * 0.5)
99                 << "Expected to start below 50% of bucket quota";
100     }
101 
storeItem(Item & item)102     ENGINE_ERROR_CODE storeItem(Item& item) {
103         uint64_t cas = 0;
104         return engine->storeInner(cookie, item, cas, OPERATION_SET);
105     }
106 
107     /**
108      * Write documents to the bucket until they fail with TMP_FAIL.
109      * Note this stores via external API (epstore) so we trigger the
110      * memoryCondition() code in the event of ENGINE_ENOMEM.
111      *
112      * @param vbid vBucket to write items to.
113      * @param expiry value for items. 0 == no TTL.
114      * @return number of documents written.
115      */
populateUntilTmpFail(Vbid vbid,rel_time_t ttl=0)116     size_t populateUntilTmpFail(Vbid vbid, rel_time_t ttl = 0) {
117         size_t count = 0;
118         const std::string value(512, 'x'); // 512B value to use for documents.
119         ENGINE_ERROR_CODE result;
120         const auto expiry =
121                 (ttl != 0) ? ep_abs_time(ep_reltime(ttl)) : time_t(0);
122         for (result = ENGINE_SUCCESS; result == ENGINE_SUCCESS; count++) {
123             auto key = makeStoredDocKey("xxx_" + std::to_string(count));
124             auto item = make_item(vbid, key, value, expiry);
125             // Set NRU of item to maximum; so will be a candidate for paging out
126             // straight away.
127             item.setNRUValue(MAX_NRU_VALUE);
128             item.setFreqCounterValue(0);
129             result = storeItem(item);
130         }
131         EXPECT_EQ(ENGINE_TMPFAIL, result);
132         // Fixup count for last loop iteration.
133         --count;
134 
135         auto& stats = engine->getEpStats();
136         EXPECT_GT(stats.getEstimatedTotalMemoryUsed(),
137                   stats.getMaxDataSize() * 0.8)
138                 << "Expected to exceed 80% of bucket quota after hitting "
139                    "TMPFAIL";
140         EXPECT_GT(stats.getEstimatedTotalMemoryUsed(), stats.mem_low_wat.load())
141                 << "Expected to exceed low watermark after hitting TMPFAIL";
142 
143         // To ensure the Blobs can actually be removed from memory, they must have
144         // a ref-count of 1. This will not be the case if there's any open
145         // checkpoints hanging onto Items. Therefore force the creation of a new
146         // checkpoint.
147         store->getVBucket(vbid)->checkpointManager->createNewCheckpoint();
148 
149         // Ensure items are flushed to disk (so we can evict them).
150         flushDirectlyIfPersistent(vbid);
151 
152         return count;
153     }
154 
populateUntilAboveHighWaterMark(Vbid vbid)155     void populateUntilAboveHighWaterMark(Vbid vbid) {
156         bool populate = true;
157         int count = 0;
158         auto& stats = engine->getEpStats();
159         while (populate) {
160             auto key = makeStoredDocKey("key_" + std::to_string(count++));
161             auto item = make_item(vbid, key, {"x", 128}, 0 /*ttl*/);
162             // Set NRU of item to maximum; so will be a candidate for paging out
163             // straight away.
164             item.setNRUValue(MAX_NRU_VALUE);
165             EXPECT_EQ(ENGINE_SUCCESS, storeItem(item));
166             populate = stats.getEstimatedTotalMemoryUsed() <=
167                        stats.mem_high_wat.load();
168         }
169     }
170 
171     /**
172      * Directly flush the given vBucket (instead of calling a unit test
173      * function) because we don't know/care how many items are going to be
174      * flushed.
175      */
flushDirectlyIfPersistent(Vbid vb)176     void flushDirectlyIfPersistent(Vbid vb) {
177         if (std::get<0>(GetParam()) == "persistent") {
178             auto& bucket = dynamic_cast<EPBucket&>(*store);
179             bucket.flushVBucket(vb);
180         }
181     }
182 
183     /**
184      * Directly flush the given vBucket (instead of calling a unit test
185      * function) and test the output of the flush function.
186      */
flushDirectlyIfPersistent(Vbid vb,const FlushResult & expected)187     void flushDirectlyIfPersistent(Vbid vb, const FlushResult& expected) {
188         if (std::get<0>(GetParam()) == "persistent") {
189             const auto res = dynamic_cast<EPBucket&>(*store).flushVBucket(vb);
190             EXPECT_EQ(expected, res);
191         }
192     }
193 
194     /// Count of nonIO tasks we should initially have.
195     size_t initialNonIoTasks = 0;
196 };
197 
198 /**
199  * Test fixture for item pager tests - enables the Item Pager (in addition to
200  * what the parent class does).
201  */
202 class STItemPagerTest : public STBucketQuotaTest {
203 protected:
SetUp()204     void SetUp() override {
205         STBucketQuotaTest::SetUp();
206 
207         // For Ephemeral fail_new_data buckets we have no item pager, instead
208         // the Expiry pager is used.
209         if (std::get<1>(GetParam()) == "fail_new_data") {
210             initializeExpiryPager();
211             ++initialNonIoTasks;
212         } else {
213             // Everyone else uses the ItemPager.
214             scheduleItemPager();
215             ++initialNonIoTasks;
216             itemPagerScheduled = true;
217         }
218 
219         // Sanity check - should be no nonIO tasks ready to run,
220         // and expected number in futureQ.
221         auto& lpNonioQ = *task_executor->getLpTaskQ()[NONIO_TASK_IDX];
222         EXPECT_EQ(0, lpNonioQ.getReadyQueueSize());
223         EXPECT_EQ(initialNonIoTasks, lpNonioQ.getFutureQueueSize());
224 
225         // We shouldn't be able to schedule the Item Pager task yet as it's not
226         // ready.
227         try {
228             SCOPED_TRACE("");
229             runNextTask(lpNonioQ, "Paging out items.");
230             FAIL() << "Unexpectedly managed to run Item Pager";
231         } catch (std::logic_error&) {
232         }
233     }
234 
235     /**
236      * Run the pager which is scheduled when the high watermark is reached
237      * (memoryCondition). This is either the ItemPager (for buckets where
238      * items can be paged out - Persistent or Ephemeral-auto_delete), or
239      * the Expiry pager (Ephemeral-fail_new_data).
240      */
runHighMemoryPager()241     void runHighMemoryPager() {
242         auto& lpNonioQ = *task_executor->getLpTaskQ()[NONIO_TASK_IDX];
243         ASSERT_EQ(0, lpNonioQ.getReadyQueueSize());
244         ASSERT_EQ(initialNonIoTasks, lpNonioQ.getFutureQueueSize());
245 
246         if (itemPagerScheduled) {
247             // Item pager consists of two Tasks - the parent ItemPager task,
248             // and then a task (via VCVBAdapter) to process each vBucket (which
249             // there is just one of as we only have one vBucket online).
250             runNextTask(lpNonioQ, "Paging out items.");
251             ASSERT_EQ(0, lpNonioQ.getReadyQueueSize());
252             ASSERT_EQ(initialNonIoTasks + 1, lpNonioQ.getFutureQueueSize());
253             runNextTask(lpNonioQ, "Item pager on vb:0");
254         } else {
255             runNextTask(lpNonioQ, "Paging expired items.");
256             // Multiple vBuckets are processed in a single task run.
257             runNextTask(lpNonioQ, "Expired item remover on vb:0");
258         }
259         // Once complete, should have the same number of tasks we initially
260         // had.
261         ASSERT_EQ(0, lpNonioQ.getReadyQueueSize());
262         ASSERT_EQ(initialNonIoTasks, lpNonioQ.getFutureQueueSize());
263 
264         // Ensure any deletes are flushed to disk (so item counts are accurate).
265         flushDirectlyIfPersistent(vbid);
266     }
267 
268     /// Has the item pager been scheduled to run?
269     bool itemPagerScheduled = false;
270 };
271 
272 // Test that the ItemPager is scheduled when the Server Quota is reached, and
273 // that items are successfully paged out.
TEST_P(STItemPagerTest,ServerQuotaReached)274 TEST_P(STItemPagerTest, ServerQuotaReached) {
275 
276     size_t count = populateUntilTmpFail(vbid);
277     ASSERT_GE(count, 50) << "Too few documents stored";
278 
279     runHighMemoryPager();
280 
281     // For all configurations except ephemeral fail_new_data, memory usage
282     // should have dropped.
283     auto& stats = engine->getEpStats();
284     auto vb = engine->getVBucket(vbid);
285     if (std::get<1>(GetParam()) == "fail_new_data") {
286         EXPECT_GT(stats.getEstimatedTotalMemoryUsed(), stats.mem_low_wat.load())
287                 << "Expected still to exceed low watermark after hitting "
288                    "TMPFAIL with fail_new_data bucket";
289         EXPECT_EQ(count, vb->getNumItems());
290     } else {
291         EXPECT_LT(stats.getEstimatedTotalMemoryUsed(), stats.mem_low_wat.load())
292                 << "Expected to be below low watermark after running item "
293                    "pager";
294         const auto numResidentItems =
295                 vb->getNumItems() - vb->getNumNonResidentItems();
296         EXPECT_LT(numResidentItems, count);
297     }
298 }
299 
TEST_P(STItemPagerTest,HighWaterMarkTriggersPager)300 TEST_P(STItemPagerTest, HighWaterMarkTriggersPager) {
301     // Fill to just over HWM
302     populateUntilAboveHighWaterMark(vbid);
303     // Success if the pager is now ready
304     runHighMemoryPager();
305 }
306 
307 // Tests that for the hifi_mfu eviction algorithm we visit replica vbuckets
308 // first.
TEST_P(STItemPagerTest,ReplicaItemsVisitedFirst)309 TEST_P(STItemPagerTest, ReplicaItemsVisitedFirst) {
310     // For the Expiry Pager we do not enforce the visiting of replica buckets
311     // first.
312     if ((std::get<1>(GetParam()) == "fail_new_data")) {
313         return;
314     }
315     auto& lpNonioQ = *task_executor->getLpTaskQ()[NONIO_TASK_IDX];
316 
317     const Vbid activeVB = Vbid(0);
318     const Vbid pendingVB = Vbid(1);
319     const Vbid replicaVB = Vbid(2);
320 
321     // Set pendingVB online, initially as active (so we can populate it).
322     store->setVBucketState(pendingVB, vbucket_state_active);
323     // Set replicaVB online, initially as active (so we can populate it).
324     store->setVBucketState(replicaVB, vbucket_state_active);
325 
326     // Add a document to both the active and pending vbucket.
327     const std::string value(512, 'x'); // 512B value to use for documents.
328     for (int ii = 0; ii < 10; ii++) {
329         auto key = makeStoredDocKey("key_" + std::to_string(ii));
330         auto activeItem = make_item(activeVB, key, value);
331         auto pendingItem = make_item(pendingVB, key, value);
332         ASSERT_EQ(ENGINE_SUCCESS, storeItem(activeItem));
333         ASSERT_EQ(ENGINE_SUCCESS, storeItem(pendingItem));
334     }
335 
336     store->setVBucketState(pendingVB, vbucket_state_pending);
337 
338     auto count = populateUntilTmpFail(replicaVB);
339     store->setVBucketState(replicaVB, vbucket_state_replica);
340 
341     runNextTask(lpNonioQ, "Paging out items.");
342     runNextTask(lpNonioQ, "Item pager on vb:0");
343 
344     if (std::get<0>(GetParam()) == "ephemeral") {
345         // We should have not evicted from replica vbuckets
346         EXPECT_EQ(count, store->getVBucket(replicaVB)->getNumItems());
347         // We should have evicted from the active/pending vbuckets
348         auto activeAndPendingItems =
349                 store->getVBucket(activeVB)->getNumItems() +
350                 store->getVBucket(pendingVB)->getNumItems();
351         EXPECT_NE(20, activeAndPendingItems);
352 
353     } else {
354         // We should have evicted from replica vbuckets
355         EXPECT_NE(0, store->getVBucket(replicaVB)->getNumNonResidentItems());
356         auto evictedActiveAndPendingItems =
357                 store->getVBucket(activeVB)->getNumNonResidentItems() +
358                 store->getVBucket(pendingVB)->getNumNonResidentItems();
359         // We should not have evicted from active or pending vbuckets
360         EXPECT_EQ(0, evictedActiveAndPendingItems);
361     }
362     ASSERT_EQ(initialNonIoTasks, lpNonioQ.getFutureQueueSize());
363 }
364 
365 // Test that when the server quota is reached, we delete items which have
366 // expired before any other items.
TEST_P(STItemPagerTest,ExpiredItemsDeletedFirst)367 TEST_P(STItemPagerTest, ExpiredItemsDeletedFirst) {
368     // Test only works for the now removed 2-bit LRU eviction algorithm
369     // @todo Investigate converting the test to work with the new hifi_mfu
370     // eviction algorithm.
371     return;
372 
373     // Populate bucket with non-expiring items until we reach the low
374     // watermark.
375     size_t countA = 0;
376     const std::string value(512, 'x'); // 512B value to use for documents.
377     auto& stats = engine->getEpStats();
378     do {
379         auto key = makeStoredDocKey("key_" + std::to_string(countA));
380         auto item = make_item(vbid, key, value);
381         ASSERT_EQ(ENGINE_SUCCESS, storeItem(item));
382         countA++;
383     } while (stats.getEstimatedTotalMemoryUsed() < stats.mem_low_wat.load());
384 
385     ASSERT_GE(countA, 10)
386             << "Expected at least 10 items before hitting low watermark";
387 
388     // Fill bucket with items with a TTL of 1s until we hit ENOMEM. When
389     // we run the pager, we expect these items to be deleted first.
390     auto countB = populateUntilTmpFail(vbid, 1);
391 
392     ASSERT_GE(countB, 50)
393         << "Expected at least 50 documents total before hitting high watermark";
394 
395     // Advance time so when the pager runs it will find expired items.
396     TimeTraveller billSPrestonEsq(2);
397 
398     EXPECT_EQ(countA + countB, store->getVBucket(vbid)->getNumItems());
399 
400     runHighMemoryPager();
401 
402     // Ensure deletes are flushed to disk (so any temp items removed from
403     // HashTable).
404     flushVBucketToDiskIfPersistent(vbid);
405 
406     // Check which items remain. We should have deleted all of the items with
407     // a TTL, as they should have been considered first).
408 
409     // Initial documents should still exist. Note we need to use getMetaData
410     // here as get() would expire the item on access.
411     for (size_t ii = 0; ii < countA; ii++) {
412         auto key = makeStoredDocKey("key_" + std::to_string(ii));
413         auto result = store->get(key, vbid, cookie, get_options_t());
414         EXPECT_EQ(ENGINE_SUCCESS, result.getStatus()) << "For key:" << key;
415     }
416 
417     // Documents which had a TTL should be deleted. Note it's hard to check
418     // the specific keys without triggering an expire-on-access (and hence
419     // doing the item pager's job for it). Therefore just check the count of
420     // items still existing (should have decreased by the number of items
421     // with TTLs) and expiry statistics.
422     EXPECT_EQ(countA, store->getVBucket(vbid)->getNumItems());
423     EXPECT_EQ(countB, stats.expired_pager);
424     EXPECT_EQ(0, stats.expired_access);
425     EXPECT_EQ(0, stats.expired_compactor);
426 }
427 
428 // Test migrated and mutated from from ep_testsuite_basic so that it's less
429 // racey
TEST_P(STItemPagerTest,test_memory_limit)430 TEST_P(STItemPagerTest, test_memory_limit) {
431     // Test only works for the now removed 2-bit LRU eviction algorithm
432     // @todo Investigate converting the test to work with the new hifi_mfu
433     // eviction algorithm.
434     return;
435 
436     // Now set max_size to be 10MiB
437     std::string msg;
438     EXPECT_EQ(
439             cb::mcbp::Status::Success,
440             engine->setFlushParam(
441                     "max_size", std::to_string(10 * 1024 * 1204).c_str(), msg));
442 
443     // Store a large document 4MiB
444     std::string value(4 * 1024 * 1204, 'a');
445     {
446         auto item =
447                 make_item(vbid, {"key", DocKeyEncodesCollectionId::No}, value);
448         // ensure this is eligible for eviction on the first pass of the pager
449         item.setNRUValue(MAX_NRU_VALUE);
450         ASSERT_EQ(ENGINE_SUCCESS, storeItem(item));
451     }
452 
453     if (std::get<0>(GetParam()) == "persistent") {
454         // flush so the HT item becomes clean
455         flush_vbucket_to_disk(vbid);
456 
457         // Now do some steps which will remove the checkpoint, all of these
458         // steps are needed
459         auto vb = engine->getVBucket(vbid);
460 
461         // Force close the current checkpoint
462         vb->checkpointManager->createNewCheckpoint();
463         // Reflush
464         flush_vbucket_to_disk(vbid);
465         bool newCheckpointCreated = false;
466         auto removed = vb->checkpointManager->removeClosedUnrefCheckpoints(
467                 *vb, newCheckpointCreated);
468         EXPECT_EQ(1, removed);
469     }
470 
471     // Now set max_size to be mem_used + 10% (we need some headroom)
472     auto& stats = engine->getEpStats();
473     EXPECT_EQ(cb::mcbp::Status::Success,
474               engine->setFlushParam(
475                       "max_size",
476                       std::to_string(stats.getEstimatedTotalMemoryUsed() * 1.10)
477                               .c_str(),
478                       msg));
479 
480     // The next tests use itemAllocate (as per a real SET)
481     EXPECT_EQ(ENGINE_TMPFAIL,
482               engine->itemAllocate(nullptr,
483                                    {"key2", DocKeyEncodesCollectionId::No},
484                                    value.size(),
485                                    0,
486                                    0,
487                                    0,
488                                    0,
489                                    vbid));
490 
491     // item_pager should be notified and ready to run
492     runHighMemoryPager();
493 
494     if (std::get<0>(GetParam()) == "persistent") {
495         EXPECT_EQ(1, stats.numValueEjects);
496     }
497 
498     if (std::get<1>(GetParam()) != "fail_new_data") {
499         // Enough should of been freed so itemAllocate can succeed
500         item* itm = nullptr;
501         EXPECT_EQ(ENGINE_SUCCESS,
502                   engine->itemAllocate(&itm,
503                                        {"key2", DocKeyEncodesCollectionId::No},
504                                        value.size(),
505                                        0,
506                                        0,
507                                        0,
508                                        0,
509                                        vbid));
510         engine->itemRelease(itm);
511     }
512 }
513 
514 /**
515  * MB-29236: Test that if an item is eligible to be evicted but exceeding the
516  * eviction threshold we do not add the maximum value (255) to the
517  * ItemEviction histogram.
518  */
TEST_P(STItemPagerTest,isEligible)519 TEST_P(STItemPagerTest, isEligible) {
520     populateUntilTmpFail(vbid);
521 
522     EventuallyPersistentEngine* epe =
523             ObjectRegistry::onSwitchThread(NULL, true);
524     get_options_t options = static_cast<get_options_t>(
525             QUEUE_BG_FETCH | HONOR_STATES | TRACK_REFERENCE | DELETE_TEMP |
526             HIDE_LOCKED_CAS | TRACK_STATISTICS);
527 
528     for (int ii = 0; ii < 10; ii++) {
529         auto key = makeStoredDocKey("xxx_0");
530         store->get(key, vbid, cookie, options);
531         ObjectRegistry::onSwitchThread(epe);
532     }
533     std::shared_ptr<std::atomic<bool>> available;
534     std::atomic<item_pager_phase> phase;
535     Configuration& cfg = engine->getConfiguration();
536     bool isEphemeral = std::get<0>(GetParam()) == "ephemeral";
537     std::unique_ptr<MockPagingVisitor> pv = std::make_unique<MockPagingVisitor>(
538             *engine->getKVBucket(),
539             engine->getEpStats(),
540             1.0,
541             available,
542             ITEM_PAGER,
543             false,
544             0.5,
545             VBucketFilter(),
546             &phase,
547             isEphemeral,
548             cfg.getItemEvictionAgePercentage(),
549             cfg.getItemEvictionFreqCounterAgeThreshold());
550 
551     VBucketPtr vb = store->getVBucket(vbid);
552     pv->visitBucket(vb);
553     auto initialCount = Item::initialFreqCount;
554     EXPECT_NE(initialCount,
555               pv->getItemEviction().getThresholds(100.0, 0.0).first);
556     EXPECT_NE(255, pv->getItemEviction().getThresholds(100.0, 0.0).first);
557 }
558 
559 /**
560  * MB-29333:  Test that if a vbucket contains a single document with an
561  * execution frequency of Item::initialFreqCount, the document will
562  * be evicted if the paging visitor is run a sufficient number of times.
563  */
TEST_P(STItemPagerTest,decayByOne)564 TEST_P(STItemPagerTest, decayByOne) {
565     const std::string value(512, 'x'); // 512B value to use for documents.
566     auto key = makeStoredDocKey("xxx_0");
567     auto item = make_item(vbid, key, value, time_t(0));
568     storeItem(item);
569 
570     std::shared_ptr<std::atomic<bool>> available;
571     std::atomic<item_pager_phase> phase{ACTIVE_AND_PENDING_ONLY};
572     Configuration& cfg = engine->getConfiguration();
573     bool isEphemeral = std::get<0>(GetParam()) == "ephemeral";
574     std::unique_ptr<MockPagingVisitor> pv = std::make_unique<MockPagingVisitor>(
575             *engine->getKVBucket(),
576             engine->getEpStats(),
577             10.0,
578             available,
579             ITEM_PAGER,
580             false,
581             0.5,
582             VBucketFilter(),
583             &phase,
584             isEphemeral,
585             cfg.getItemEvictionAgePercentage(),
586             cfg.getItemEvictionFreqCounterAgeThreshold());
587 
588     pv->setCurrentBucket(engine->getKVBucket()->getVBucket(vbid));
589     flushVBucketToDiskIfPersistent(vbid);
590     int iterationCount = 0;
591     while ((pv->getEjected() == 0) &&
592            iterationCount <= Item::initialFreqCount) {
593         pv->setFreqCounterThreshold(0);
594         VBucketPtr vb = store->getVBucket(vbid);
595         vb->ht.visit(*pv);
596         iterationCount++;
597     }
598     EXPECT_EQ(1, pv->getEjected());
599 }
600 
601 /**
602  * MB-29333:  Test that if a vbucket contains a single document with an
603  * execution frequency of Item::initialFreqCount, but the document
604  * is not eligible for eviction (due to being replica in ephemeral case and
605  * not flushed in the persistent case) check that its frequency count is not
606  * decremented.
607  */
TEST_P(STItemPagerTest,doNotDecayIfCannotEvict)608 TEST_P(STItemPagerTest, doNotDecayIfCannotEvict) {
609     const std::string value(512, 'x'); // 512B value to use for documents.
610     auto key = makeStoredDocKey("xxx_0");
611     auto item = make_item(vbid, key, value, time_t(0));
612     storeItem(item);
613 
614     std::shared_ptr<std::atomic<bool>> available;
615     std::atomic<item_pager_phase> phase{ACTIVE_AND_PENDING_ONLY};
616     Configuration& cfg = engine->getConfiguration();
617     bool isEphemeral = std::get<0>(GetParam()) == "ephemeral";
618     std::unique_ptr<MockPagingVisitor> pv = std::make_unique<MockPagingVisitor>(
619             *engine->getKVBucket(),
620             engine->getEpStats(),
621             10.0,
622             available,
623             ITEM_PAGER,
624             false,
625             0.5,
626             VBucketFilter(),
627             &phase,
628             isEphemeral,
629             cfg.getItemEvictionAgePercentage(),
630             cfg.getItemEvictionFreqCounterAgeThreshold());
631 
632     pv->setCurrentBucket(engine->getKVBucket()->getVBucket(vbid));
633     store->setVBucketState(vbid, vbucket_state_replica);
634     for (int ii = 0; ii <= Item::initialFreqCount; ii++) {
635         pv->setFreqCounterThreshold(0);
636         pv->getItemEviction().reset();
637         VBucketPtr vb = store->getVBucket(vbid);
638         vb->ht.visit(*pv);
639     }
640 
641     // Now make the document eligible for eviction.
642     store->setVBucketState(vbid, vbucket_state_active);
643     flushVBucketToDiskIfPersistent(vbid);
644 
645     // Check still not be able to evict, because the frequency count is still
646     // at Item::initialFreqCount
647     pv->setFreqCounterThreshold(0);
648     pv->getItemEviction().reset();
649     VBucketPtr vb = store->getVBucket(vbid);
650     vb->ht.visit(*pv);
651     auto initialFreqCount = Item::initialFreqCount;
652     EXPECT_EQ(initialFreqCount,
653               pv->getItemEviction().getThresholds(100.0, 0.0).first);
654     EXPECT_EQ(0, pv->getEjected());
655 
656 }
657 
658 /**
659  * MB-38315 found that when we BG Fetch a deleted item it can end up "stuck" in
660  * the HashTable until the compactor removes the tombstone for it. This
661  * increases memory pressure and could cause the system to lock up if the pager
662  * finds no items eligible for eviction (e.g. BGFetched an entirely deleted data
663  * set). Test that the item pager can page out a clean deleted item.
664  */
TEST_P(STItemPagerTest,EvictBGFetchedDeletedItem)665 TEST_P(STItemPagerTest, EvictBGFetchedDeletedItem) {
666     // Only relevant to full eviction as Value Eviction will:
667     //   1: Delete deleted items from the HashTable when they are persisted
668     //      (also true for Full Eviction)
669     //   2: Not allow BGFetches
670     //
671     // This means that there is no way for us to get a deleted item into the
672     // HashTable that is not dirty. As such, this test can't work for Value
673     // eviction.
674     if (!fullEviction()) {
675         return;
676     }
677 
678     // 1) Write our document
679     auto key = makeStoredDocKey("key");
680     store_item(vbid, key, "value");
681     flushVBucketToDiskIfPersistent(vbid, 1);
682 
683     delete_item(vbid, key);
684     flushVBucketToDiskIfPersistent(vbid, 1);
685 
686     // 2) Evict it
687     auto vb = store->getVBucket(vbid);
688     const auto buffer = std::make_unique<const char[]>(128);
689     const char* msg = buffer.get();
690     vb->evictKey(&msg, vb->lockCollections(key));
691 
692     // 3) Get to schedule our BGFetch
693     auto options = static_cast<get_options_t>(
694             QUEUE_BG_FETCH | HONOR_STATES | TRACK_REFERENCE | DELETE_TEMP |
695             HIDE_LOCKED_CAS | TRACK_STATISTICS | GET_DELETED_VALUE);
696     auto res = store->get(key, vbid, cookie, options);
697     EXPECT_EQ(ENGINE_EWOULDBLOCK, res.getStatus());
698 
699     EXPECT_EQ(0, vb->getNumItems());
700     EXPECT_EQ(1, vb->getNumTempItems());
701 
702     // 4) Fetch it back into the HashTable
703     runBGFetcherTask();
704 
705     // Should have replaced the temp item, but the item in the HT will be
706     // deleted and not counted in NumItems
707     EXPECT_EQ(0, vb->getNumItems());
708     EXPECT_EQ(0, vb->getNumTempItems());
709 
710     auto checkItemStatePostFetch = [&](bool expected) {
711         auto val = vb->fetchValidValue(WantsDeleted::Yes,
712                                        TrackReference::No,
713                                        QueueExpired::No,
714                                        vb->lockCollections(key));
715         if (expected) {
716             EXPECT_TRUE(val.storedValue);
717             EXPECT_TRUE(val.storedValue->isDeleted());
718             EXPECT_FALSE(val.storedValue->isDirty());
719             EXPECT_FALSE(val.storedValue->isTempItem());
720         } else {
721             EXPECT_FALSE(val.storedValue);
722         }
723     };
724 
725     // Check that the item is actually in the HT
726     checkItemStatePostFetch(true /*expect item to exist*/);
727 
728     // 5) Re-run our get after BG Fetch (proving that it does not affect the
729     // item). Returns success as we requested deleted values.
730     res = store->get(key, vbid, cookie, options);
731     EXPECT_EQ(ENGINE_SUCCESS, res.getStatus());
732 
733     EXPECT_EQ(0, vb->getNumItems());
734     EXPECT_EQ(0, vb->getNumTempItems());
735 
736     // Check that the item is still in the HT
737     checkItemStatePostFetch(true /*expect item to exist*/);
738 
739     // 6) Fill to just over HWM then run the pager
740     populateUntilAboveHighWaterMark(vbid);
741     runHighMemoryPager();
742 
743     // Finally, check our item again. It should now have been evicted
744     checkItemStatePostFetch(false /*expect item to not exist*/);
745 }
746 
747 /**
748  * Test fixture for Ephemeral-only item pager tests.
749  */
750 class STEphemeralItemPagerTest : public STItemPagerTest {
751 };
752 
753 // For Ephemeral buckets, replica items should not be paged out (deleted) -
754 // as that would cause the replica to have a diverging history from the active.
TEST_P(STEphemeralItemPagerTest,ReplicaNotPaged)755 TEST_P(STEphemeralItemPagerTest, ReplicaNotPaged) {
756     const Vbid active_vb = Vbid(0);
757     const Vbid replica_vb = Vbid(1);
758     // Set vBucket 1 online, initially as active (so we can populate it).
759     store->setVBucketState(replica_vb, vbucket_state_active);
760 
761     auto& stats = engine->getEpStats();
762     ASSERT_LE(stats.getEstimatedTotalMemoryUsed(), 40 * 1024)
763             << "Expected to start with less than 40KB of memory used";
764     ASSERT_LT(stats.getEstimatedTotalMemoryUsed(), stats.mem_low_wat.load())
765             << "Expected to start below low watermark";
766 
767     // Populate vbid 0 (active) until we reach the low watermark.
768     size_t active_count = 0;
769     const std::string value(1024, 'x'); // 1KB value to use for documents.
770     do {
771         auto key = makeStoredDocKey("key_" + std::to_string(active_count));
772         auto item = make_item(active_vb, key, value);
773         // Set NRU of item to maximum; so will be a candidate for paging out
774         // straight away.
775         item.setNRUValue(MAX_NRU_VALUE);
776         item.setFreqCounterValue(0);
777         ASSERT_EQ(ENGINE_SUCCESS, storeItem(item));
778         active_count++;
779     } while (stats.getEstimatedTotalMemoryUsed() < stats.mem_low_wat.load());
780 
781     ASSERT_GE(active_count, 10)
782             << "Expected at least 10 active items before hitting low watermark";
783 
784     // Populate vbid 1 (replica) until we reach the high watermark.
785     size_t replica_count = populateUntilTmpFail(replica_vb);
786     ASSERT_GE(replica_count, 10)
787         << "Expected at least 10 replica items before hitting high watermark";
788 
789     // Flip vb 1 to be a replica (and hence should not be a candidate for
790     // any paging out.
791     store->setVBucketState(replica_vb, vbucket_state_replica);
792     runHighMemoryPager();
793 
794     EXPECT_EQ(replica_count, store->getVBucket(replica_vb)->getNumItems())
795         << "Replica count should be unchanged after Item Pager";
796 
797     // Expected active vb behaviour depends on the full policy:
798     if (std::get<1>(GetParam()) == "fail_new_data") {
799         EXPECT_GT(stats.getEstimatedTotalMemoryUsed(),
800                   stats.mem_high_wat.load())
801                 << "Expected to be above high watermark after running item "
802                    "pager (fail_new_data)";
803         EXPECT_EQ(store->getVBucket(active_vb)->getNumItems(), active_count)
804                 << "Active count should be the same after Item Pager "
805                    "(fail_new_data)";
806     } else {
807         EXPECT_LT(stats.getEstimatedTotalMemoryUsed(), stats.mem_low_wat.load())
808                 << "Expected to be below low watermark after running item "
809                    "pager";
810         EXPECT_LT(store->getVBucket(active_vb)->getNumItems(), active_count)
811                 << "Active count should have decreased after Item Pager";
812     }
813 }
814 
815 /**
816  * Test fixture for expiry pager tests - enables the Expiry Pager (in addition
817  * to what the parent class does).
818  */
819 class STExpiryPagerTest : public STBucketQuotaTest {
820 protected:
SetUp()821     void SetUp() override {
822         STBucketQuotaTest::SetUp();
823 
824         // Setup expiry pager - this adds one to the number of nonIO tasks
825         initializeExpiryPager();
826         ++initialNonIoTasks;
827 
828         // Sanity check - should be no nonIO tasks ready to run, and initial
829         // count in futureQ.
830         auto& lpNonioQ = *task_executor->getLpTaskQ()[NONIO_TASK_IDX];
831         EXPECT_EQ(0, lpNonioQ.getReadyQueueSize());
832         EXPECT_EQ(initialNonIoTasks, lpNonioQ.getFutureQueueSize());
833     }
834 
wakeUpExpiryPager()835     void wakeUpExpiryPager() {
836         store->wakeUpExpiryPager();
837         // Expiry pager consists of two Tasks - the parent ExpiryPager task,
838         // and then a per-vBucket task (via VCVBAdapter) - which there is
839         // just one of as we only have one vBucket online.
840         // Trigger expiry pager - note the main task just spawns individual
841         // tasks per vBucket - we also need to execute one of them.
842         auto& lpNonioQ = *task_executor->getLpTaskQ()[NONIO_TASK_IDX];
843         runNextTask(lpNonioQ, "Paging expired items.");
844         EXPECT_EQ(0, lpNonioQ.getReadyQueueSize());
845         EXPECT_EQ(initialNonIoTasks + 1, lpNonioQ.getFutureQueueSize());
846         runNextTask(lpNonioQ, "Expired item remover on vb:0");
847         EXPECT_EQ(0, lpNonioQ.getReadyQueueSize());
848         EXPECT_EQ(initialNonIoTasks, lpNonioQ.getFutureQueueSize());
849     }
850 
851     void expiredItemsDeleted();
852 };
853 
expiredItemsDeleted()854 void STExpiryPagerTest::expiredItemsDeleted() {
855     // Populate bucket with three documents - one with no expiry, one with an
856     // expiry in 10 seconds, and one with an expiry in 20 seconds.
857     std::string value = createXattrValue("body");
858     for (size_t ii = 0; ii < 3; ii++) {
859         auto key = makeStoredDocKey("key_" + std::to_string(ii));
860         const uint32_t expiry =
861                 ii > 0 ? ep_abs_time(ep_current_time() + ii * 10) : 0;
862         auto item = make_item(
863                 vbid,
864                 key,
865                 value,
866                 expiry,
867                 PROTOCOL_BINARY_DATATYPE_JSON | PROTOCOL_BINARY_DATATYPE_XATTR);
868         ASSERT_EQ(ENGINE_SUCCESS, storeItem(item));
869     }
870 
871     flushDirectlyIfPersistent(vbid,
872                               {MoreAvailable::No, 3, WakeCkptRemover::No});
873 
874     // Sanity check - should have not hit high watermark (otherwise the
875     // item pager will run automatically and aggressively delete items).
876     auto& stats = engine->getEpStats();
877     EXPECT_LE(stats.getEstimatedTotalMemoryUsed(), stats.getMaxDataSize() * 0.8)
878             << "Expected to not have exceeded 80% of bucket quota";
879 
880     // Move time forward by 11s, so key_1 should be expired.
881     TimeTraveller tedTheodoreLogan(11);
882 
883     // Sanity check - should still have all items present in VBucket.
884     ASSERT_EQ(3, engine->getVBucket(vbid)->getNumItems());
885 
886     wakeUpExpiryPager();
887     flushDirectlyIfPersistent(vbid,
888                               {MoreAvailable::No, 1, WakeCkptRemover::Yes});
889 
890     EXPECT_EQ(2, engine->getVBucket(vbid)->getNumItems())
891         << "Should only have 2 items after running expiry pager";
892 
893     // Check our items.
894     auto key_0 = makeStoredDocKey("key_0");
895     auto getKeyFn = [this](const StoredDocKey& key) {
896         return store->get(key, vbid, cookie, QUEUE_BG_FETCH).getStatus();
897     };
898     EXPECT_EQ(ENGINE_SUCCESS, getKeyFn(key_0))
899             << "Key without TTL should still exist.";
900 
901     auto key_1 = makeStoredDocKey("key_1");
902 
903     if (::testing::get<1>(GetParam()) == "full_eviction") {
904         // Need an extra get() to trigger EWOULDBLOCK / bgfetch.
905         EXPECT_EQ(ENGINE_EWOULDBLOCK, getKeyFn(key_1));
906         runBGFetcherTask();
907     }
908     EXPECT_EQ(ENGINE_KEY_ENOENT, getKeyFn(key_1))
909             << "Key with TTL:10 should be removed.";
910 
911     auto key_2 = makeStoredDocKey("key_2");
912     EXPECT_EQ(ENGINE_SUCCESS, getKeyFn(key_2))
913             << "Key with TTL:20 should still exist.";
914 
915     // Move time forward by +10s, so key_2 should also be expired.
916     TimeTraveller philConners(10);
917 
918     // Sanity check - should still have 2 items present in VBucket.
919     ASSERT_EQ(2, engine->getVBucket(vbid)->getNumItems())
920         << "Should still have 2 items after time-travelling";
921 
922     wakeUpExpiryPager();
923     flushDirectlyIfPersistent(vbid,
924                               {MoreAvailable::No, 1, WakeCkptRemover::Yes});
925 
926     // Should only be 1 item remaining.
927     EXPECT_EQ(1, engine->getVBucket(vbid)->getNumItems());
928 
929     // Check our items.
930     EXPECT_EQ(ENGINE_SUCCESS, getKeyFn(key_0))
931             << "Key without TTL should still exist.";
932 
933     EXPECT_EQ(ENGINE_KEY_ENOENT, getKeyFn(key_1))
934             << "Key with TTL:10 should be removed.";
935 
936     if (::testing::get<1>(GetParam()) == "full_eviction") {
937         // Need an extra get() to trigger EWOULDBLOCK / bgfetch.
938         EXPECT_EQ(ENGINE_EWOULDBLOCK, getKeyFn(key_2));
939         runBGFetcherTask();
940     }
941     EXPECT_EQ(ENGINE_KEY_ENOENT, getKeyFn(key_2))
942             << "Key with TTL:20 should be removed.";
943 }
944 
945 // Test that when the expiry pager runs, all expired items are deleted.
TEST_P(STExpiryPagerTest,ExpiredItemsDeleted)946 TEST_P(STExpiryPagerTest, ExpiredItemsDeleted) {
947     expiredItemsDeleted();
948 }
949 
950 // Test that when an expired system-xattr document is fetched with getMeta
951 // it can be successfully expired again
TEST_P(STExpiryPagerTest,MB_25650)952 TEST_P(STExpiryPagerTest, MB_25650) {
953     expiredItemsDeleted();
954 
955     auto vb = store->getVBucket(Vbid(0));
956 
957     auto key_1 = makeStoredDocKey("key_1");
958     ItemMetaData metadata;
959     uint32_t deleted;
960     uint8_t datatype;
961 
962     // Ephemeral doesn't bgfetch, persistent full-eviction already had to
963     // perform a bgfetch to check key_1 no longer exists in the above
964     // expiredItemsDeleted().
965     const ENGINE_ERROR_CODE err =
966             std::get<0>(GetParam()) == "persistent" &&
967                             std::get<1>(GetParam()) == "value_only"
968                     ? ENGINE_EWOULDBLOCK
969                     : ENGINE_SUCCESS;
970 
971     // Bring document meta back into memory and run expiry on it
972     EXPECT_EQ(err,
973               store->getMetaData(
974                       key_1, vbid, cookie, metadata, deleted, datatype));
975     if (std::get<0>(GetParam()) == "persistent") {
976         runBGFetcherTask();
977         EXPECT_EQ(ENGINE_SUCCESS,
978                   store->getMetaData(
979                           key_1, vbid, cookie, metadata, deleted, datatype));
980     }
981 
982     // Original bug is that we would segfault running the pager here
983     wakeUpExpiryPager();
984 
985     get_options_t options =
986             static_cast<get_options_t>(QUEUE_BG_FETCH | GET_DELETED_VALUE);
987     EXPECT_EQ(err, store->get(key_1, vbid, cookie, options).getStatus())
988             << "Key with TTL:10 should be removed.";
989 
990     // Verify that the xattr body still exists.
991     if (std::get<0>(GetParam()) == "persistent") {
992         runBGFetcherTask();
993     }
994     auto item = store->get(key_1, vbid, cookie, GET_DELETED_VALUE);
995 
996     ASSERT_EQ(ENGINE_SUCCESS, item.getStatus());
997     EXPECT_TRUE(mcbp::datatype::is_xattr(item.item->getDataType()));
998     ASSERT_NE(0, item.item->getNBytes());
999     cb::xattr::Blob blob(
1000             {const_cast<char*>(item.item->getData()), item.item->getNBytes()},
1001             false);
1002 
1003     EXPECT_EQ(0, blob.get("user").size());
1004     EXPECT_EQ(0, blob.get("meta").size());
1005     ASSERT_NE(0, blob.get("_sync").size());
1006     EXPECT_STREQ("{\"cas\":\"0xdeadbeefcafefeed\"}",
1007                  reinterpret_cast<char*>(blob.get("_sync").data()));
1008 }
1009 
1010 // Test that when an expired system-xattr document is fetched with getMeta
1011 // deleteWithMeta can be successfully invoked
TEST_P(STExpiryPagerTest,MB_25671)1012 TEST_P(STExpiryPagerTest, MB_25671) {
1013     expiredItemsDeleted();
1014     auto vb = store->getVBucket(vbid);
1015 
1016     // key_1 has been expired
1017     auto key_1 = makeStoredDocKey("key_1");
1018     ItemMetaData metadata;
1019     uint32_t deleted = 0;
1020     uint8_t datatype = 0;
1021 
1022     // Ephemeral doesn't bgfetch, persistent full-eviction already had to
1023     // perform a bgfetch to check key_1 no longer exists in the above
1024     // expiredItemsDeleted().
1025     const ENGINE_ERROR_CODE err =
1026             std::get<0>(GetParam()) == "persistent" &&
1027                             std::get<1>(GetParam()) == "value_only"
1028                     ? ENGINE_EWOULDBLOCK
1029                     : ENGINE_SUCCESS;
1030 
1031     // Bring the deleted key back with a getMeta call
1032     EXPECT_EQ(err,
1033               store->getMetaData(
1034                       key_1, vbid, cookie, metadata, deleted, datatype));
1035     if (std::get<0>(GetParam()) == "persistent") {
1036         runBGFetcherTask();
1037         EXPECT_EQ(ENGINE_SUCCESS,
1038                   store->getMetaData(
1039                           key_1, vbid, cookie, metadata, deleted, datatype));
1040     }
1041 
1042     uint64_t cas = -1;
1043     metadata.flags = 0xf00f0088;
1044     metadata.cas = 0xbeeff00dcafe1234ull;
1045     metadata.revSeqno = 0xdad;
1046     metadata.exptime = 0xfeedface;
1047     PermittedVBStates vbstates(vbucket_state_active);
1048     auto deleteWithMeta = std::bind(&KVBucketIface::deleteWithMeta,
1049                                     store,
1050                                     key_1,
1051                                     cas,
1052                                     nullptr,
1053                                     vbid,
1054                                     cookie,
1055                                     vbstates,
1056                                     CheckConflicts::No,
1057                                     metadata,
1058                                     GenerateBySeqno::No,
1059                                     GenerateCas::No,
1060                                     0,
1061                                     nullptr,
1062                                     DeleteSource::Explicit);
1063     // Prior to the MB fix - this would crash.
1064     EXPECT_EQ(err, deleteWithMeta());
1065 
1066     get_options_t options =
1067             static_cast<get_options_t>(QUEUE_BG_FETCH | GET_DELETED_VALUE);
1068     if (std::get<0>(GetParam()) == "persistent") {
1069         runBGFetcherTask();
1070         EXPECT_EQ(ENGINE_SUCCESS, deleteWithMeta());
1071     }
1072 
1073     auto item = store->get(key_1, vbid, cookie, options);
1074     ASSERT_EQ(ENGINE_SUCCESS, item.getStatus());
1075     EXPECT_TRUE(item.item->isDeleted()) << "Not deleted " << *item.item;
1076     ASSERT_NE(0, item.item->getNBytes()) << "No value " << *item.item;
1077 
1078     cb::xattr::Blob blob(
1079             {const_cast<char*>(item.item->getData()), item.item->getNBytes()},
1080             false);
1081 
1082     EXPECT_EQ(0, blob.get("user").size());
1083     EXPECT_EQ(0, blob.get("meta").size());
1084     ASSERT_NE(0, blob.get("_sync").size());
1085     EXPECT_STREQ("{\"cas\":\"0xdeadbeefcafefeed\"}",
1086                  reinterpret_cast<char*>(blob.get("_sync").data()));
1087     EXPECT_EQ(metadata.flags, item.item->getFlags());
1088     EXPECT_EQ(metadata.exptime, item.item->getExptime());
1089     EXPECT_EQ(metadata.cas, item.item->getCas());
1090     EXPECT_EQ(metadata.revSeqno, item.item->getRevSeqno());
1091 }
1092 
1093 /**
1094  * Subclass for expiry tests only applicable to Value eviction persistent
1095  * buckets.
1096  */
1097 class STValueEvictionExpiryPagerTest : public STExpiryPagerTest {
1098 public:
configValues()1099     static auto configValues() {
1100         return ::testing::Values(std::make_tuple("persistent"s, "value_only"s));
1101     }
1102 };
1103 
1104 // Test that when a xattr value is ejected, we can still expire it. Previous
1105 // to the fix we crash because the item has no value in memory.
1106 //
1107 // (Not applicable to full-eviction as relies on in-memory expiry pager
1108 //  expiring a non-resident item; with full-eviction the item is completely
1109 //  evicted and hence ExpiryPager won't find it.)
TEST_P(STValueEvictionExpiryPagerTest,MB_25931)1110 TEST_P(STValueEvictionExpiryPagerTest, MB_25931) {
1111     std::string value = createXattrValue("body");
1112     auto key = makeStoredDocKey("key_1");
1113     auto item = make_item(
1114             vbid,
1115             key,
1116             value,
1117             ep_abs_time(ep_current_time() + 10),
1118             PROTOCOL_BINARY_DATATYPE_JSON | PROTOCOL_BINARY_DATATYPE_XATTR);
1119     ASSERT_EQ(ENGINE_SUCCESS, storeItem(item));
1120 
1121     flushDirectlyIfPersistent(vbid,
1122                               {MoreAvailable::No, 1, WakeCkptRemover::No});
1123 
1124     const char* msg;
1125     EXPECT_EQ(cb::mcbp::Status::Success, store->evictKey(key, vbid, &msg));
1126     EXPECT_STREQ("Ejected.", msg);
1127 
1128     runBGFetcherTask();
1129 
1130     TimeTraveller docBrown(15);
1131 
1132     wakeUpExpiryPager();
1133     flushDirectlyIfPersistent(vbid,
1134                               {MoreAvailable::No, 1, WakeCkptRemover::Yes});
1135 }
1136 
1137 // Test that expiring a non-resident item works (and item counts are correct).
1138 //
1139 // (Not applicable to full-eviction as relies on in-memory expiry pager
1140 //  expiring a non-resident item; with full-eviction the item is completely
1141 //  evicted and hence ExpiryPager won't find it.)
TEST_P(STValueEvictionExpiryPagerTest,MB_25991_ExpiryNonResident)1142 TEST_P(STValueEvictionExpiryPagerTest, MB_25991_ExpiryNonResident) {
1143     // Populate bucket with a TTL'd document, and then evict that document.
1144     auto key = makeStoredDocKey("key");
1145     auto expiry = ep_abs_time(ep_current_time() + 5);
1146     auto item = make_item(vbid, key, "value", expiry);
1147     ASSERT_EQ(ENGINE_SUCCESS, storeItem(item));
1148 
1149     flushDirectlyIfPersistent(vbid,
1150                               {MoreAvailable::No, 1, WakeCkptRemover::No});
1151 
1152     // Sanity check - should have not hit high watermark (otherwise the
1153     // item pager will run automatically and aggressively delete items).
1154     auto& stats = engine->getEpStats();
1155     EXPECT_LE(stats.getEstimatedTotalMemoryUsed(), stats.getMaxDataSize() * 0.8)
1156             << "Expected to not have exceeded 80% of bucket quota";
1157 
1158     // Evict key so it is no longer resident.
1159     evict_key(vbid, key);
1160 
1161     // Move time forward by 11s, so key should be expired.
1162     TimeTraveller tedTheodoreLogan(11);
1163 
1164     // Sanity check - should still have item present (and non-resident)
1165     // in VBucket.
1166     ASSERT_EQ(1, engine->getVBucket(vbid)->getNumItems());
1167     ASSERT_EQ(1, engine->getVBucket(vbid)->getNumNonResidentItems());
1168 
1169     wakeUpExpiryPager();
1170     flushDirectlyIfPersistent(vbid,
1171                               {MoreAvailable::No, 1, WakeCkptRemover::Yes});
1172 
1173     EXPECT_EQ(0, engine->getVBucket(vbid)->getNumItems())
1174             << "Should have 0 items after running expiry pager";
1175     EXPECT_EQ(0, engine->getVBucket(vbid)->getNumNonResidentItems())
1176             << "Should have 0 non-resident items after running expiry pager";
1177 
1178     // Check our item - should not exist.
1179     auto result = store->get(key, vbid, cookie, get_options_t());
1180     EXPECT_EQ(ENGINE_KEY_ENOENT, result.getStatus());
1181 }
1182 
1183 class MB_32669 : public STValueEvictionExpiryPagerTest {
1184 public:
SetUp()1185     void SetUp() override {
1186         config_string += "compression_mode=active;";
1187         STValueEvictionExpiryPagerTest::SetUp();
1188         store->enableItemCompressor();
1189         initialNonIoTasks++;
1190     }
1191 
runItemCompressor()1192     void runItemCompressor() {
1193         auto& lpNonioQ = *task_executor->getLpTaskQ()[NONIO_TASK_IDX];
1194         runNextTask(lpNonioQ, "Item Compressor");
1195     }
1196 };
1197 
1198 // Test that an xattr value which is compressed, evicted and then expired
1199 // doesn't trigger an exception
TEST_P(MB_32669,expire_a_compressed_and_evicted_xattr_document)1200 TEST_P(MB_32669, expire_a_compressed_and_evicted_xattr_document) {
1201     // 1) Add bucket a TTL'd xattr document
1202     auto key = makeStoredDocKey("key");
1203     auto expiry = ep_abs_time(ep_current_time() + 5);
1204     auto value = createXattrValue(std::string(100, 'a'), true /*sys xattrs*/);
1205     auto item =
1206             make_item(vbid, key, value, expiry, PROTOCOL_BINARY_DATATYPE_XATTR);
1207     ASSERT_EQ(ENGINE_SUCCESS, storeItem(item));
1208 
1209     flushDirectlyIfPersistent(vbid,
1210                               {MoreAvailable::No, 1, WakeCkptRemover::No});
1211 
1212     // Sanity check - should have not hit high watermark (otherwise the
1213     // item pager will run automatically and aggressively delete items).
1214     auto& stats = engine->getEpStats();
1215     ASSERT_LE(stats.getEstimatedTotalMemoryUsed(), stats.getMaxDataSize() * 0.8)
1216             << "Expected to not have exceeded 80% of bucket quota";
1217 
1218     // 2) Run the compressor
1219     runItemCompressor();
1220 
1221     // 2.1) And validate the document is now snappy
1222     ItemMetaData metadata;
1223     uint32_t deleted;
1224     uint8_t datatype;
1225 
1226     EXPECT_EQ(
1227             ENGINE_SUCCESS,
1228             store->getMetaData(key, vbid, cookie, metadata, deleted, datatype));
1229     ASSERT_EQ(PROTOCOL_BINARY_DATATYPE_SNAPPY,
1230               datatype & PROTOCOL_BINARY_DATATYPE_SNAPPY);
1231 
1232     // 3) Evict key so it is no longer resident.
1233     evict_key(vbid, key);
1234 
1235     // 4) Move time forward by 11s, so key should be expired.
1236     TimeTraveller wyldStallyns(11);
1237 
1238     // Sanity check - should still have item present (and non-resident)
1239     // in VBucket.
1240     ASSERT_EQ(1, engine->getVBucket(vbid)->getNumItems());
1241     ASSERT_EQ(1, engine->getVBucket(vbid)->getNumNonResidentItems());
1242 
1243     wakeUpExpiryPager();
1244 
1245     flushDirectlyIfPersistent(vbid,
1246                               {MoreAvailable::No, 1, WakeCkptRemover::Yes});
1247 
1248     EXPECT_EQ(0, engine->getVBucket(vbid)->getNumItems())
1249             << "Should have 0 items after running expiry pager";
1250     EXPECT_EQ(0, engine->getVBucket(vbid)->getNumNonResidentItems())
1251             << "Should have 0 non-resident items after running expiry pager";
1252 
1253     // Check our item has been deleted and the xattrs pruned
1254     get_options_t options = static_cast<get_options_t>(
1255             QUEUE_BG_FETCH | HONOR_STATES | TRACK_REFERENCE | DELETE_TEMP |
1256             HIDE_LOCKED_CAS | TRACK_STATISTICS | GET_DELETED_VALUE);
1257     GetValue gv = store->get(key, vbid, cookie, options);
1258     EXPECT_EQ(ENGINE_EWOULDBLOCK, gv.getStatus());
1259 
1260     runBGFetcherTask();
1261     gv = store->get(key, vbid, cookie, options);
1262     ASSERT_EQ(ENGINE_SUCCESS, gv.getStatus());
1263 
1264     EXPECT_TRUE(gv.item->isDeleted());
1265     auto get_itm = gv.item.get();
1266     auto get_data = const_cast<char*>(get_itm->getData());
1267 
1268     cb::char_buffer value_buf{get_data, get_itm->getNBytes()};
1269     cb::xattr::Blob new_blob(value_buf, false);
1270 
1271     // expect sys attributes to remain
1272     const std::string& cas_str{"{\"cas\":\"0xdeadbeefcafefeed\"}"};
1273     const std::string& sync_str = to_string(new_blob.get("_sync"));
1274 
1275     EXPECT_EQ(cas_str, sync_str) << "Unexpected system xattrs";
1276     EXPECT_TRUE(new_blob.get("user").empty())
1277             << "The user attribute should be gone";
1278     EXPECT_TRUE(new_blob.get("meta").empty())
1279             << "The meta attribute should be gone";
1280 }
1281 
1282 class MB_36087 : public STParameterizedBucketTest {
1283 public:
1284 };
1285 
1286 // Test for MB-36087 - simply check that an evicted xattr item doesn't crash
1287 // when a winning del-with-meta arrives.
TEST_P(MB_36087,DelWithMeta_EvictedKey)1288 TEST_P(MB_36087, DelWithMeta_EvictedKey) {
1289     store->setVBucketState(vbid, vbucket_state_active);
1290     if (!persistent()) {
1291         return;
1292     }
1293     ASSERT_TRUE(persistent());
1294     std::string value = createXattrValue("body");
1295     auto key = makeStoredDocKey("k1");
1296     auto item = make_item(
1297             vbid,
1298             key,
1299             value,
1300             0,
1301             PROTOCOL_BINARY_DATATYPE_JSON | PROTOCOL_BINARY_DATATYPE_XATTR);
1302     uint64_t cas = 0;
1303     ASSERT_EQ(ENGINE_SUCCESS,
1304               engine->storeInner(cookie, item, cas, OPERATION_SET));
1305 
1306     auto& bucket = dynamic_cast<EPBucket&>(*store);
1307     EXPECT_EQ(1, bucket.flushVBucket(vbid).numFlushed);
1308 
1309     // 1) Store k1
1310     auto vb = store->getVBucket(vbid);
1311 
1312     // 2) Evict k1
1313     evict_key(vbid, key);
1314 
1315     // 3) A winning delWithMeta - system must bgFetch and not crash...
1316     ItemMetaData metadata;
1317 
1318     cas = -1;
1319     metadata.flags = 0xf00f0088;
1320     metadata.cas = 0xbeeff00dcafe1234ull;
1321     metadata.revSeqno = 0xdad;
1322     metadata.exptime = 0xfeedface;
1323     PermittedVBStates vbstates(vbucket_state_active);
1324 
1325     auto deleteWithMeta = std::bind(&KVBucketIface::deleteWithMeta,
1326                                     store,
1327                                     key,
1328                                     cas,
1329                                     nullptr,
1330                                     vbid,
1331                                     cookie,
1332                                     vbstates,
1333                                     CheckConflicts::Yes,
1334                                     metadata,
1335                                     GenerateBySeqno::Yes,
1336                                     GenerateCas::No,
1337                                     0,
1338                                     nullptr,
1339                                     DeleteSource::Explicit);
1340     // A bgfetch is required for full or value eviction because we need the
1341     // xattr value
1342     EXPECT_EQ(ENGINE_EWOULDBLOCK, deleteWithMeta());
1343     runBGFetcherTask();
1344 
1345     // Full eviction first did a meta-fetch, now has todo a full fetch
1346     auto err = std::get<1>(GetParam()) == "full_eviction" ? ENGINE_EWOULDBLOCK
1347                                                           : ENGINE_SUCCESS;
1348     EXPECT_EQ(err, deleteWithMeta());
1349 
1350     if (std::get<1>(GetParam()) == "full_eviction") {
1351         runBGFetcherTask();
1352         EXPECT_EQ(ENGINE_SUCCESS, deleteWithMeta());
1353     }
1354 
1355     get_options_t options =
1356             static_cast<get_options_t>(QUEUE_BG_FETCH | GET_DELETED_VALUE);
1357     auto gv = store->get(key, vbid, cookie, options);
1358     ASSERT_EQ(ENGINE_SUCCESS, gv.getStatus());
1359     EXPECT_TRUE(gv.item->isDeleted()) << "Not deleted " << *gv.item;
1360     ASSERT_NE(0, gv.item->getNBytes()) << "No value " << *gv.item;
1361 
1362     cb::xattr::Blob blob(
1363             {const_cast<char*>(gv.item->getData()), gv.item->getNBytes()},
1364             false);
1365 
1366     EXPECT_EQ(0, blob.get("user").size());
1367     EXPECT_EQ(0, blob.get("meta").size());
1368     ASSERT_NE(0, blob.get("_sync").size());
1369     EXPECT_STREQ("{\"cas\":\"0xdeadbeefcafefeed\"}",
1370                  reinterpret_cast<char*>(blob.get("_sync").data()));
1371     EXPECT_EQ(metadata.flags, gv.item->getFlags());
1372     EXPECT_EQ(metadata.exptime, gv.item->getExptime());
1373     EXPECT_EQ(metadata.cas, gv.item->getCas());
1374     EXPECT_EQ(metadata.revSeqno, gv.item->getRevSeqno());
1375 }
1376 
1377 // TODO: Ideally all of these tests should run with or without jemalloc,
1378 // however we currently rely on jemalloc for accurate memory tracking; and
1379 // hence it is required currently.
1380 #if defined(HAVE_JEMALLOC)
1381 
1382 INSTANTIATE_TEST_CASE_P(EphemeralOrPersistent,
1383                         STItemPagerTest,
1384                         STParameterizedBucketTest::allConfigValues(),
1385                         STParameterizedBucketTest::PrintToStringParamName);
1386 
1387 INSTANTIATE_TEST_CASE_P(EphemeralOrPersistent,
1388                         STExpiryPagerTest,
1389                         STParameterizedBucketTest::allConfigValues(),
1390                         STParameterizedBucketTest::PrintToStringParamName);
1391 
1392 INSTANTIATE_TEST_CASE_P(ValueOnly,
1393                         STValueEvictionExpiryPagerTest,
1394                         STValueEvictionExpiryPagerTest::configValues(),
1395                         STParameterizedBucketTest::PrintToStringParamName);
1396 
1397 INSTANTIATE_TEST_CASE_P(Persistent,
1398                         MB_32669,
1399                         STValueEvictionExpiryPagerTest::configValues(),
1400                         STParameterizedBucketTest::PrintToStringParamName);
1401 
1402 INSTANTIATE_TEST_CASE_P(Ephemeral,
1403                         STEphemeralItemPagerTest,
1404                         STParameterizedBucketTest::ephConfigValues(),
1405                         STParameterizedBucketTest::PrintToStringParamName);
1406 
1407 INSTANTIATE_TEST_CASE_P(PersistentFullValue,
1408                         MB_36087,
1409                         STParameterizedBucketTest::persistentConfigValues(),
1410                         STParameterizedBucketTest::PrintToStringParamName);
1411 
1412 #endif
1413