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