File: CacheTest_RaceCondition.cpp

package info (click to toggle)
cryfs 1.0.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 28,412 kB
  • sloc: cpp: 150,187; asm: 10,493; python: 1,455; javascript: 65; sh: 50; makefile: 17; xml: 7
file content (109 lines) | stat: -rw-r--r-- 3,703 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
#include "testutils/CacheTest.h"
#include <chrono>
#include <thread>
#include <memory>
#include <future>
#include <cpp-utils/lock/ConditionBarrier.h>

using namespace blockstore::caching;
using std::chrono::seconds;
using std::string;
using cpputils::ConditionBarrier;
using std::unique_ptr;
using std::make_unique;
using std::future;

// Regression tests for a race condition.
// An element could be in the process of being thrown out of the cache and while the destructor is running, another
// thread calls pop() for the element and gets none returned. But since the destructor isn't finished yet, the data from
// the cache element also isn't completely written back yet and an application loading it runs into a race condition.

class ObjectWithLongDestructor {
public:
    ObjectWithLongDestructor(ConditionBarrier *onDestructorStarted, std::atomic<bool> *destructorFinished)
            : _onDestructorStarted(onDestructorStarted), _destructorFinished(destructorFinished) {}
    ~ObjectWithLongDestructor() {
        _onDestructorStarted->release();
        std::this_thread::sleep_for(seconds(1));
        *_destructorFinished = true;
    }
private:
    ConditionBarrier *_onDestructorStarted;
    std::atomic<bool> *_destructorFinished;

    DISALLOW_COPY_AND_ASSIGN(ObjectWithLongDestructor);
};

class CacheTest_RaceCondition: public ::testing::Test {
public:
    CacheTest_RaceCondition(): cache("test"), destructorStarted(), destructorFinished(false) {}

    static constexpr unsigned int MAX_ENTRIES = 100;

    Cache<int, unique_ptr<ObjectWithLongDestructor>, MAX_ENTRIES> cache;
    ConditionBarrier destructorStarted;
    std::atomic<bool> destructorFinished;

    int pushObjectWithLongDestructor() {
        cache.push(2, make_unique<ObjectWithLongDestructor>(&destructorStarted, &destructorFinished));
        return 2;
    }

    int pushDummyObject() {
        cache.push(3, nullptr);
        return 3;
    }

    future<void> causeCacheOverflowInOtherThread() {
        //Add maximum+1 element in another thread (this causes the cache to flush the first element in another thread)
        return std::async(std::launch::async, [this] {
            for(unsigned int i = 0; i < MAX_ENTRIES+1; ++i) {
                cache.push(MAX_ENTRIES+i, nullptr);
            }
        });
    }

    void EXPECT_POP_BLOCKS_UNTIL_DESTRUCTOR_FINISHED(int key) {
        EXPECT_FALSE(destructorFinished);
        cache.pop(key);
        EXPECT_TRUE(destructorFinished);
    }

    void EXPECT_POP_DOESNT_BLOCK_UNTIL_DESTRUCTOR_FINISHED(int key) {
        EXPECT_FALSE(destructorFinished);
        cache.pop(key);
        EXPECT_FALSE(destructorFinished);
    }
};

TEST_F(CacheTest_RaceCondition, PopBlocksWhileRequestedElementIsThrownOut_ByAge) {
    auto id = pushObjectWithLongDestructor();

    destructorStarted.wait();
    EXPECT_POP_BLOCKS_UNTIL_DESTRUCTOR_FINISHED(id);
}

TEST_F(CacheTest_RaceCondition, PopDoesntBlockWhileOtherElementIsThrownOut_ByAge) {
    pushObjectWithLongDestructor();
    auto id = pushDummyObject();

    destructorStarted.wait();
    EXPECT_POP_DOESNT_BLOCK_UNTIL_DESTRUCTOR_FINISHED(id);
}

TEST_F(CacheTest_RaceCondition, PopBlocksWhileRequestedElementIsThrownOut_ByPush) {
    auto id = pushObjectWithLongDestructor();

    auto future = causeCacheOverflowInOtherThread();
    destructorStarted.wait();
    EXPECT_POP_BLOCKS_UNTIL_DESTRUCTOR_FINISHED(id);
}

TEST_F(CacheTest_RaceCondition, PopDoesntBlockWhileOtherElementIsThrownOut_ByPush) {
    pushObjectWithLongDestructor();
    auto id = pushDummyObject();

    auto future = causeCacheOverflowInOtherThread();
    destructorStarted.wait();
    EXPECT_POP_DOESNT_BLOCK_UNTIL_DESTRUCTOR_FINISHED(id);
}