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#include <atomic>
19#include <chrono>
20#include <thread>
21#include <vector>
22
23#include <gtest/gtest.h>
24
25#include <phosphor/chunk_lock.h>
26
27class ChunkLockTest : public testing::Test {
28protected:
29    phosphor::ChunkLock lock{phosphor::non_trivial_constructor};
30};
31
32TEST_F(ChunkLockTest, basic) {
33	lock.slave().lock();
34	lock.slave().unlock();
35
36	lock.master().lock();
37	EXPECT_FALSE(lock.slave().try_lock());
38	lock.master().unlock();
39	EXPECT_TRUE(lock.slave().try_lock());
40	lock.slave().unlock();
41}
42
43class ThreadedChunkLockTest : public ChunkLockTest {
44protected:
45    ThreadedChunkLockTest() : step(0) {}
46
47    virtual ~ThreadedChunkLockTest() {
48        for (auto& thread : threads) {
49            thread.join();
50        }
51    }
52
53    std::vector<std::thread> threads;
54    std::atomic<int> step;
55};
56
57// These tests are more or less imported from the sentinel test.
58// They're not great but do a slight sanity check that TSan
59// might pick up on.
60
61TEST_F(ThreadedChunkLockTest, SlaveSlave) {
62    threads.emplace_back([this]() {
63        lock.slave().lock();
64        ++step;
65        // Need to sleep to ensure the other thread has
66        // started spinning to acquire the lock
67        std::this_thread::sleep_for(std::chrono::milliseconds(50));
68        lock.slave().unlock();
69    });
70
71    threads.emplace_back([this]() {
72        while (step.load() != 1) {
73        }
74        lock.slave().lock();
75        lock.slave().unlock();
76    });
77}
78
79TEST_F(ThreadedChunkLockTest, SlaveMaster) {
80    threads.emplace_back([this]() {
81        lock.slave().lock();
82        ++step;
83        // Need to sleep to ensure the other thread has
84        // started spinning to acquire the lock
85        std::this_thread::sleep_for(std::chrono::milliseconds(50));
86        lock.slave().unlock();
87    });
88
89    threads.emplace_back([this]() {
90        while (step.load() != 1) {
91        }
92        lock.master().lock();
93        lock.master().unlock();
94    });
95}
96
97TEST_F(ThreadedChunkLockTest, MasterSlave) {
98    threads.emplace_back([this]() {
99        lock.master().lock();
100        ++step;
101        // Need to sleep to ensure the other thread has
102        // started spinning to acquire the lock
103        std::this_thread::sleep_for(std::chrono::milliseconds(50));
104        lock.master().unlock();
105    });
106
107    threads.emplace_back([this]() {
108        while (step.load() != 1) {
109        }
110        while(!lock.slave().try_lock()) {
111        }
112        lock.slave().unlock();
113    });
114}
115