1 // Copyright (C) 2020 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 <util/readwrite_mutex.h>
10 
11 #include <gtest/gtest.h>
12 
13 #include <boost/shared_ptr.hpp>
14 #include <boost/make_shared.hpp>
15 
16 #include <chrono>
17 #include <iostream>
18 #include <thread>
19 #include <unistd.h>
20 
21 using namespace isc::util;
22 using namespace std;
23 
24 namespace {
25 
26 /// @brief Test Fixture for testing read-write mutexes.
27 ///
28 /// Each not basic test follows the same schema:
29 /// @code
30 /// main thread    work thread
31 /// <-             started
32 /// work           ->
33 ///                enter guard
34 /// <-             done
35 /// terminate      ->
36 ///                return
37 /// join
38 /// @endcode
39 class ReadWriteMutexTest : public ::testing::Test {
40 public:
41 
42     /// @brief Read-write mutex.
43     ReadWriteMutex rw_mutex_;
44 
45     /// @brief Synchronization objects for work threads.
46     struct Sync {
47         bool started = false;
48         mutex started_mtx;
49         condition_variable started_cv;
50         bool work = false;
51         mutex work_mtx;
52         condition_variable work_cv;
53         bool done = false;
54         mutex done_mtx;
55         condition_variable done_cv;
56         bool terminate = false;
57         mutex terminate_mtx;
58         condition_variable terminate_cv;
59     } syncr_, syncw_;
60 
61     /// @brief Body of the reader.
62     ///
63     /// @param rw_mutex The read-write mutex.
64     /// @param syncr The reader synchronization object.
reader(ReadWriteMutex & rw_mutex,Sync & syncr)65     void reader(ReadWriteMutex& rw_mutex, Sync& syncr) {
66         // Take mutex to wait for main thread signals.
67         unique_lock<mutex> terminate_lock(syncr.terminate_mtx);
68 
69         // Signal the thread started.
70         {
71             lock_guard<mutex> lock(syncr.started_mtx);
72             syncr.started = true;
73         }
74 
75         // Wait for work.
76         {
77             unique_lock<mutex> work_lock(syncr.work_mtx);
78             // When this thread starts waiting, the main thread is resumed.
79             syncr.started_cv.notify_one();
80             syncr.work_cv.wait(work_lock, [&](){ return syncr.work; });
81         }
82 
83         {
84             // Enter a read lock guard.
85             ReadLockGuard rwlock(rw_mutex);
86 
87             // Signal the thread holds the guard.
88             {
89                 lock_guard<mutex> done_lock(syncr.done_mtx);
90                 syncr.done = true;
91             }
92             syncr.done_cv.notify_one();
93         }
94 
95         // Wait to terminate.
96         syncr.terminate_cv.wait(terminate_lock, [&](){ return syncr.terminate; });
97     }
98 
99     /// @brief Body of the writer.
100     ///
101     /// @param rw_mutex The read-write mutex.
102     /// @param syncw The writer synchronization object.
writer(ReadWriteMutex & rw_mutex,Sync & syncw)103     void writer(ReadWriteMutex& rw_mutex, Sync& syncw) {
104         // Take mutex to wait for main thread signals.
105         unique_lock<mutex> terminate_lock(syncw.terminate_mtx);
106 
107         // Signal the thread started.
108         {
109             lock_guard<mutex> lock(syncw.started_mtx);
110             syncw.started = true;
111         }
112 
113         // Wait for work.
114         {
115             unique_lock<mutex> work_lock(syncw.work_mtx);
116             // When this thread starts waiting, the main thread is resumed.
117             syncw.started_cv.notify_one();
118             syncw.work_cv.wait(work_lock, [&](){ return syncw.work; });
119         }
120 
121         {
122             // Enter a write lock guard.
123             WriteLockGuard rwlock(rw_mutex);
124 
125             // Signal the thread holds the guard.
126             {
127                 lock_guard<mutex> done_lock(syncw.done_mtx);
128                 syncw.done = true;
129             }
130             syncw.done_cv.notify_one();
131         }
132 
133         // Wait to terminate.
134         syncw.terminate_cv.wait(terminate_lock, [&](){ return syncw.terminate; });
135     }
136 };
137 
138 // Verify basic read lock guard.
TEST_F(ReadWriteMutexTest,basicRead)139 TEST_F(ReadWriteMutexTest, basicRead) {
140     ReadLockGuard lock(rw_mutex_);
141 }
142 
143 // Verify basic write lock guard.
TEST_F(ReadWriteMutexTest,basicWrite)144 TEST_F(ReadWriteMutexTest, basicWrite) {
145     WriteLockGuard lock(rw_mutex_);
146 }
147 
148 // Verify read lock guard using a thread.
TEST_F(ReadWriteMutexTest,read)149 TEST_F(ReadWriteMutexTest, read) {
150     // Take mutex to wait for work thread signals.
151     boost::shared_ptr<std::thread> thread;
152     {
153         unique_lock<mutex> started_lock(syncr_.started_mtx);
154 
155         // Create a work thread.
156         thread = boost::make_shared<std::thread>([this](){ reader(rw_mutex_, syncr_); });
157 
158         // Wait work thread to start.
159         syncr_.started_cv.wait(started_lock, [this](){ return syncr_.started; });
160 
161         unique_lock<mutex> done_lock(syncr_.done_mtx);
162 
163         // Signal the thread to work.
164         {
165             lock_guard<mutex> work_lock(syncr_.work_mtx);
166             syncr_.work = true;
167         }
168         syncr_.work_cv.notify_one();
169 
170         // Wait thread to hold the read lock.
171         syncr_.done_cv.wait(done_lock, [this](){ return syncr_.done; });
172     }
173 
174     // Signal the thread to terminate.
175     {
176         lock_guard<mutex> terminate_lock(syncr_.terminate_mtx);
177         syncr_.terminate = true;
178     }
179     syncr_.terminate_cv.notify_one();
180 
181     // Join the thread.
182     thread->join();
183 }
184 
185 // Verify write lock guard using a thread.
TEST_F(ReadWriteMutexTest,write)186 TEST_F(ReadWriteMutexTest, write) {
187     // Take mutex to wait for work thread signals.
188     boost::shared_ptr<std::thread> thread;
189     {
190         unique_lock<mutex> started_lock(syncw_.started_mtx);
191 
192         // Create a work thread.
193         thread = boost::make_shared<std::thread>([this](){ writer(rw_mutex_, syncw_); });
194 
195         // Wait work thread to start.
196         syncw_.started_cv.wait(started_lock, [this](){ return syncw_.started; });
197 
198         unique_lock<mutex> done_lock(syncw_.done_mtx);
199 
200         // Signal the thread to work.
201         {
202             lock_guard<mutex> work_lock(syncw_.work_mtx);
203             syncw_.work = true;
204         }
205         syncw_.work_cv.notify_one();
206 
207         // Wait thread to hold the write lock.
208         syncw_.done_cv.wait(done_lock, [this](){ return syncw_.done; });
209     }
210 
211     // Signal the thread to terminate.
212     {
213         lock_guard<mutex> terminate_lock(syncw_.terminate_mtx);
214         syncw_.terminate = true;
215     }
216     syncw_.terminate_cv.notify_one();
217 
218     // Join the thread.
219     thread->join();
220 }
221 
222 // Verify read lock guard can be acquired by multiple threads.
TEST_F(ReadWriteMutexTest,readRead)223 TEST_F(ReadWriteMutexTest, readRead) {
224     // Take mutex to wait for work thread signals.
225     boost::shared_ptr<std::thread> thread;
226     {
227         unique_lock<mutex> started_lock(syncr_.started_mtx);
228 
229         // Create a work thread.
230         thread = boost::make_shared<std::thread>([this](){ reader(rw_mutex_, syncr_); });
231 
232         // Enter a read lock guard.
233         ReadLockGuard rwlock(rw_mutex_);
234 
235         // Wait work thread to start.
236         syncr_.started_cv.wait(started_lock, [this](){ return syncr_.started; });
237 
238         unique_lock<mutex> done_lock(syncr_.done_mtx);
239 
240         // Signal the thread to work.
241         {
242             lock_guard<mutex> work_lock(syncr_.work_mtx);
243             syncr_.work = true;
244         }
245         syncr_.work_cv.notify_one();
246 
247         // Wait thread to hold the read lock.
248         syncr_.done_cv.wait(done_lock, [this](){ return syncr_.done; });
249     }
250 
251     // Signal the thread to terminate.
252     {
253         lock_guard<mutex> terminate_lock(syncr_.terminate_mtx);
254         syncr_.terminate = true;
255     }
256     syncr_.terminate_cv.notify_one();
257 
258     // Join the thread.
259     thread->join();
260 }
261 
262 // Verify write lock guard is exclusive of a reader.
TEST_F(ReadWriteMutexTest,readWrite)263 TEST_F(ReadWriteMutexTest, readWrite) {
264     // Take mutex to wait for work thread signals.
265     boost::shared_ptr<std::thread> thread;
266     {
267         unique_lock<mutex> started_lock(syncw_.started_mtx);
268 
269         // Create a work thread.
270         thread = boost::make_shared<std::thread>([this](){ writer(rw_mutex_, syncw_); });
271 
272         // Wait work thread to start.
273         syncw_.started_cv.wait(started_lock, [this](){ return syncw_.started; });
274 
275         unique_lock<mutex> done_lock(syncw_.done_mtx);
276 
277         {
278             // Enter a read lock guard.
279             ReadLockGuard rwlock(rw_mutex_);
280 
281             // Signal the thread to work.
282             {
283                 lock_guard<mutex> work_lock(syncw_.work_mtx);
284                 syncw_.work = true;
285             }
286             syncw_.work_cv.notify_one();
287 
288             // Verify the work thread is waiting for the write lock.
289             cout << "pausing for one second" << std::endl;
290             bool ret = syncw_.done_cv.wait_for(done_lock, chrono::seconds(1), [this](){ return syncw_.done; });
291 
292             EXPECT_FALSE(syncw_.done);
293             EXPECT_FALSE(ret);
294 
295             // Exiting the read lock guard.
296         }
297 
298         // Wait thread to hold the write lock.
299         syncw_.done_cv.wait(done_lock, [this](){ return syncw_.done; });
300     }
301 
302     // Signal the thread to terminate.
303     {
304         lock_guard<mutex> terminate_lock(syncw_.terminate_mtx);
305         syncw_.terminate = true;
306     }
307     syncw_.terminate_cv.notify_one();
308 
309     // Join the thread.
310     thread->join();
311 }
312 
313 // Verify write lock guard is exclusive of a writer.
TEST_F(ReadWriteMutexTest,writeWrite)314 TEST_F(ReadWriteMutexTest, writeWrite) {
315     // Take mutex to wait for work thread signals.
316     boost::shared_ptr<std::thread> thread;
317     {
318         unique_lock<mutex> started_lock(syncw_.started_mtx);
319 
320         // Create a work thread.
321         thread = boost::make_shared<std::thread>([this](){ writer(rw_mutex_, syncw_); });
322 
323         // Wait work thread to start.
324         syncw_.started_cv.wait(started_lock, [this](){ return syncw_.started; });
325 
326         unique_lock<mutex> done_lock(syncw_.done_mtx);
327 
328         {
329             // Enter a write lock guard.
330             WriteLockGuard rwlock(rw_mutex_);
331 
332             // Signal the thread to work.
333             {
334                 lock_guard<mutex> work_lock(syncw_.work_mtx);
335                 syncw_.work = true;
336             }
337             syncw_.work_cv.notify_one();
338 
339             // Verify the work thread is waiting for the write lock.
340             cout << "pausing for one second" << std::endl;
341             bool ret = syncw_.done_cv.wait_for(done_lock, chrono::seconds(1), [this](){ return syncw_.done; });
342 
343             EXPECT_FALSE(syncw_.done);
344             EXPECT_FALSE(ret);
345 
346             // Exiting the write lock guard.
347         }
348 
349         // Wait thread to hold the write lock.
350         syncw_.done_cv.wait(done_lock, [this](){ return syncw_.done; });
351     }
352 
353     // Signal the thread to terminate.
354     {
355         lock_guard<mutex> terminate_lock(syncw_.terminate_mtx);
356         syncw_.terminate = true;
357     }
358     syncw_.terminate_cv.notify_one();
359 
360     // Join the thread.
361     thread->join();
362 }
363 
364 // Verify that a writer has the preference.
TEST_F(ReadWriteMutexTest,readWriteRead)365 TEST_F(ReadWriteMutexTest, readWriteRead) {
366     // Take mutex to wait for work thread signals.
367     boost::shared_ptr<std::thread> threadw;
368     {
369         unique_lock<mutex> startedw_lock(syncw_.started_mtx);
370 
371         // First thread is a writer.
372         threadw = boost::make_shared<std::thread>([this](){ writer(rw_mutex_, syncw_); });
373 
374         // Wait work thread to start.
375         syncw_.started_cv.wait(startedw_lock, [this](){ return syncw_.started; });
376     }
377 
378     boost::shared_ptr<std::thread> threadr;
379     {
380         unique_lock<mutex> startedr_lock(syncr_.started_mtx);
381 
382         // Second thread is a reader.
383         threadr = boost::make_shared<std::thread>([this](){ reader(rw_mutex_, syncr_); });
384 
385         // Wait work thread to start.
386         syncr_.started_cv.wait(startedr_lock, [this](){ return syncr_.started; });
387     }
388 
389     {
390         unique_lock<mutex> donew_lock(syncw_.done_mtx);
391         {
392             // Enter a read lock guard.
393             ReadLockGuard rwlock(rw_mutex_);
394 
395             // Signal the writer thread to work.
396             {
397                 lock_guard<mutex> work_lock(syncw_.work_mtx);
398                 syncw_.work = true;
399             }
400             syncw_.work_cv.notify_one();
401 
402             // Verify the writer thread is waiting for the write lock.
403             cout << "pausing for one second" << std::endl;
404             bool ret = syncw_.done_cv.wait_for(donew_lock, chrono::seconds(1), [this](){ return syncw_.done; });
405 
406             EXPECT_FALSE(syncw_.done);
407             EXPECT_FALSE(ret);
408 
409             {
410                 unique_lock<mutex> doner_lock(syncr_.done_mtx);
411 
412                 // Signal the reader thread to work.
413                 {
414                     lock_guard<mutex> work_lock(syncr_.work_mtx);
415                     syncr_.work = true;
416                 }
417                 syncr_.work_cv.notify_one();
418 
419                 // Verify the reader thread is waiting for the read lock.
420                 cout << "pausing for one second" << std::endl;
421                 bool ret = syncr_.done_cv.wait_for(doner_lock, chrono::seconds(1), [this](){ return syncr_.done; });
422 
423                 EXPECT_FALSE(syncr_.done);
424                 EXPECT_FALSE(ret);
425             }
426             // Exiting the read lock guard.
427         }
428 
429         {
430             unique_lock<mutex> doner_lock(syncr_.done_mtx);
431             // Verify the reader thread is still waiting for the read lock.
432             cout << "pausing for one second" << std::endl;
433             bool ret = syncr_.done_cv.wait_for(doner_lock, chrono::seconds(1), [this](){ return syncr_.done; });
434 
435             EXPECT_FALSE(syncr_.done);
436             EXPECT_FALSE(ret);
437         }
438 
439         // Wait writer thread to hold the write lock.
440         syncw_.done_cv.wait(donew_lock, [this](){ return syncw_.done; });
441     }
442 
443     {
444         unique_lock<mutex> doner_lock(syncr_.done_mtx);
445         // Wait reader thread to hold the read lock.
446         syncr_.done_cv.wait(doner_lock, [this](){ return syncr_.done; });
447     }
448 
449     // Signal the writer thread to terminate.
450     {
451         lock_guard<mutex> terminate_lock(syncw_.terminate_mtx);
452         syncw_.terminate = true;
453     }
454     syncw_.terminate_cv.notify_one();
455 
456     // Join the writer thread.
457     threadw->join();
458 
459     // Signal the reader thread to terminate.
460     {
461         lock_guard<mutex> terminate_lock(syncr_.terminate_mtx);
462         syncr_.terminate = true;
463     }
464     syncr_.terminate_cv.notify_one();
465 
466     // Join the reader thread.
467     threadr->join();
468 }
469 
470 }
471