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