1 // Copyright (C) 2020-2021 Internet Systems Consortium, Inc. ("ISC")
2 //
3 // This Source Code Form is subject to the terms of the Mozilla Public
4 // License, v. 2.0. If a copy of the MPL was not distributed with this
5 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7 #include <config.h>
8
9 #include <gtest/gtest.h>
10
11 #include <util/unlock_guard.h>
12 #include <exceptions/exceptions.h>
13
14 #include <mutex>
15 #include <thread>
16
17 using namespace isc::util;
18 using namespace std;
19
20 namespace {
21
22 /// @brief test mutex class used to check the internal state of a 'fictional'
23 /// mutex so that the functionality of the UnlockGuard can be tested
24 /// @note the test mutex can be recursive which means that a lock can be called
25 /// on the same thread and not resulting in a dead lock
26 class TestMutex {
27 public:
28 /// @brief Constructor
29 ///
30 /// @param recursive sets the mutex as recursive mutex
TestMutex(bool recursive=false)31 TestMutex(bool recursive = false) : lock_(0), dead_lock_(false),
32 lock_count_(0), unlock_count_(0), recursive_(recursive) {
33 }
34
35 /// @brief lock the mutex
lock()36 void lock() {
37 lock_guard<mutex> lk(mutex_);
38 if (lock_ >= 1) {
39 // mutex is already locked
40 if (!recursive_) {
41 // lock on a non-recursive mutex resulting in a dead lock
42 dead_lock_ = true;
43 isc_throw(isc::InvalidOperation,
44 "recursive lock on already locked mutex resulting in "
45 "dead lock");
46 } else {
47 // lock on a recursive mutex
48 if (this_thread::get_id() != id_) {
49 // lock on a recursive mutex on a different thread resulting
50 // in a dead lock
51 dead_lock_ = true;
52 isc_throw(isc::InvalidOperation,
53 "recursive lock on a different thread on already "
54 "locked mutex resulting in dead lock");
55 }
56 }
57 }
58 // increment the total number of locks
59 lock_count_++;
60 // increment the lock state
61 lock_++;
62 // save the thread id
63 id_ = this_thread::get_id();
64 }
65
66 /// @brief unlock the mutex
unlock()67 void unlock() {
68 lock_guard<mutex> lk(mutex_);
69 if (lock_ <= 0) {
70 // unlock an unlocked mutex
71 isc_throw(isc::InvalidOperation, "unlock on non locked mutex "
72 "resulting in undefined behavior");
73 }
74 if (lock_ == 1) {
75 // only one thread has the lock
76 // self healing mutex resetting the dead lock flag
77 dead_lock_ = false;
78 // reset the thread id
79 id_ = std::thread::id();
80 }
81 // increment the total number of unlocks
82 unlock_count_++;
83 // decrement the lock state
84 lock_--;
85 }
86
87 /// @brief get the mutex lock state
88 ///
89 /// @return the mutex lock state
getLock()90 int32_t getLock() {
91 lock_guard<mutex> lk(mutex_);
92 return lock_;
93 }
94
95 /// @brief get the mutex dead lock state
96 ///
97 /// @return the mutex dead lock state
getDeadLock()98 bool getDeadLock() {
99 lock_guard<mutex> lk(mutex_);
100 return dead_lock_;
101 }
102
103 /// @brief get the number of locks performed on mutex
104 ///
105 /// @return the mutex number of locks
getLockCount()106 uint32_t getLockCount() {
107 lock_guard<mutex> lk(mutex_);
108 return lock_count_;
109 }
110
111 /// @brief get the number of unlocks performed on mutex
112 ///
113 /// @return the mutex number of unlocks
getUnlockCount()114 uint32_t getUnlockCount() {
115 lock_guard<mutex> lk(mutex_);
116 return unlock_count_;
117 }
118
119 /// @brief test the internal state of the mutex
120 ///
121 /// @param expected_lock check equality of this value with lock state
122 /// @param expected_lock_count check equality of this value with lock count
123 /// @param expected_unlock_count check equality of this value with unlock count
124 /// @param expected_dead_lock check equality of this value with dead lock state
testMutexState(int32_t expected_lock,uint32_t expected_lock_count,uint32_t expected_unlock_count,bool expected_dead_lock)125 void testMutexState(int32_t expected_lock,
126 uint32_t expected_lock_count,
127 uint32_t expected_unlock_count,
128 bool expected_dead_lock) {
129 ASSERT_EQ(getLock(), expected_lock);
130 ASSERT_EQ(getLockCount(), expected_lock_count);
131 ASSERT_EQ(getUnlockCount(), expected_unlock_count);
132 ASSERT_EQ(getDeadLock(), expected_dead_lock);
133 }
134
135 private:
136 /// @brief internal lock state of the mutex
137 int32_t lock_;
138
139 /// @brief state which indicates that the mutex is in dead lock
140 bool dead_lock_;
141
142 /// @brief total number of locks performed on the mutex
143 uint32_t lock_count_;
144
145 /// @brief total number of unlocks performed on the mutex
146 uint32_t unlock_count_;
147
148 /// @brief flag to indicate if the mutex is recursive or not
149 bool recursive_;
150
151 /// @brief mutex used to keep the internal state consistent
152 mutex mutex_;
153
154 /// @brief the id of the thread holding the mutex
155 std::thread::id id_;
156 };
157
158 /// @brief Test Fixture for testing isc::util::UnlockGuard
159 class UnlockGuardTest : public ::testing::Test {
160 };
161
162 /// @brief test TestMutex functionality with non-recursive mutex, and recursive
163 /// mutex
TEST_F(UnlockGuardTest,testMutex)164 TEST_F(UnlockGuardTest, testMutex) {
165 shared_ptr<TestMutex> test_mutex;
166 // test non-recursive lock
167 test_mutex = make_shared<TestMutex>();
168 test_mutex->testMutexState(0, 0, 0, false);
169 {
170 // call lock_guard constructor which locks mutex
171 lock_guard<TestMutex> lock(*test_mutex.get());
172 // expect lock 1 lock_count 1 unlock_count 0 dead_lock false
173 test_mutex->testMutexState(1, 1, 0, false);
174 {
175 // call lock_guard constructor which locks mutex resulting in an
176 // exception as the mutex is already locked (dead lock)
177 EXPECT_THROW(lock_guard<TestMutex> lock(*test_mutex.get()),
178 isc::InvalidOperation);
179 // expect lock 1 lock_count 1 unlock_count 0 dead_lock true
180 // you should not be able to get here...using a real mutex
181 test_mutex->testMutexState(1, 1, 0, true);
182 }
183 // expect lock 1 lock_count 1 unlock_count 0 dead_lock true
184 // you should not be able to get here...using a real mutex
185 test_mutex->testMutexState(1, 1, 0, true);
186 }
187 // expect lock 0 lock_count 1 unlock_count 1 dead_lock false
188 // the implementation is self healing when completely unlocking the mutex
189 test_mutex->testMutexState(0, 1, 1, false);
190 // test recursive lock
191 test_mutex = make_shared<TestMutex>(true);
192 test_mutex->testMutexState(0, 0, 0, false);
193 {
194 // call lock_guard constructor which locks mutex
195 lock_guard<TestMutex> lock(*test_mutex.get());
196 // expect lock 1 lock_count 1 unlock_count 0 dead_lock false
197 test_mutex->testMutexState(1, 1, 0, false);
198 {
199 // call lock_guard constructor which locks mutex but does not block
200 // as this is done on the same thread and the mutex is recursive
201 EXPECT_NO_THROW(lock_guard<TestMutex> lock(*test_mutex.get()));
202 // expect lock 1 lock_count 2 unlock_count 1 dead_lock false
203 // the destructor was already called in EXPECT_NO_THROW scope
204 test_mutex->testMutexState(1, 2, 1, false);
205 }
206 // expect lock 1 lock_count 2 unlock_count 1 dead_lock false
207 test_mutex->testMutexState(1, 2, 1, false);
208 }
209 // expect lock 0 lock_count 2 unlock_count 2 dead_lock false
210 test_mutex->testMutexState(0, 2, 2, false);
211 }
212
213 /// @brief test UnlockGuard functionality with non-recursive mutex
TEST_F(UnlockGuardTest,testUnlockGuard)214 TEST_F(UnlockGuardTest, testUnlockGuard) {
215 shared_ptr<TestMutex> test_mutex;
216 // test non-recursive lock
217 test_mutex = make_shared<TestMutex>();
218 test_mutex->testMutexState(0, 0, 0, false);
219 {
220 // call lock_guard constructor which locks mutex
221 lock_guard<TestMutex> lock(*test_mutex.get());
222 // expect lock 1 lock_count 1 unlock_count 0 dead_lock false
223 test_mutex->testMutexState(1, 1, 0, false);
224 {
225 UnlockGuard<TestMutex> unlock_guard(*test_mutex.get());
226 // expect lock 0 lock_count 1 unlock_count 1 dead_lock false
227 test_mutex->testMutexState(0, 1, 1, false);
228 }
229 // expect lock 1 lock_count 2 unlock_count 1 dead_lock false
230 test_mutex->testMutexState(1, 2, 1, false);
231 }
232 // expect lock 0 lock_count 2 unlock_count 2 dead_lock false
233 test_mutex->testMutexState(0, 2, 2, false);
234 }
235
236 } // namespace
237