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