Fast RTPS  Version 2.14.1
Fast RTPS
Loading...
Searching...
No Matches
shared_mutex.hpp
1/*
2 Copyright Howard Hinnant 2007-2010. Distributed under the Boost
3 Software License, Version 1.0. (see http://www.boost.org/LICENSE_1_0.txt)
4 The original implementation has been modified to support the POSIX priorities:
5
6 PTHREAD_RWLOCK_PREFER_READER_NP
7 This is the default. A thread may hold multiple read
8 locks; that is, read locks are recursive. According to
9 The Single Unix Specification, the behavior is unspecified
10 when a reader tries to place a lock, and there is no write
11 lock but writers are waiting. Giving preference to the
12 reader, as is set by PTHREAD_RWLOCK_PREFER_READER_NP,
13 implies that the reader will receive the requested lock,
14 even if a writer is waiting. As long as there are
15 readers, the writer will be starved.
16
17 PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP
18 Setting the lock kind to this avoids writer starvation as
19 long as any read locking is not done in a recursive
20 fashion.
21
22 The C++ Standard has not yet (C++20) imposed any requirements on shared_mutex implementation thus
23 each platform made its own choices:
24 Windows & Boost defaults to PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP.
25 Linux & Mac defaults to PTHREAD_RWLOCK_PREFER_READER_NP.
26 */
27
32#ifndef _UTILS_SHARED_MUTEX_HPP_
33#define _UTILS_SHARED_MUTEX_HPP_
34
35#include <climits>
36#include <condition_variable>
37#include <map>
38#include <mutex>
39#include <system_error>
40#include <thread>
41
42namespace eprosima {
43namespace detail {
44
45// mimic POSIX Read-Write lock syntax
50
52{
53
54protected:
55
56 typedef std::mutex mutex_t;
57 typedef std::condition_variable cond_t;
58 typedef unsigned count_t;
59
63
64 static const count_t write_entered_ = 1U << (sizeof(count_t) * CHAR_BIT - 1);
65 static const count_t n_readers_ = ~write_entered_;
66
67public:
68
70 : state_(0)
71 {
72 }
73
75 {
76 std::lock_guard<mutex_t> _(mut_);
77 }
78
80 const shared_mutex_base&) = delete;
82 const shared_mutex_base&) = delete;
83
84 // Exclusive ownership
85
86 bool try_lock()
87 {
88 std::lock_guard<mutex_t> _(mut_);
89 if (state_ == 0)
90 {
92 return true;
93 }
94 return false;
95 }
96
97 void unlock()
98 {
99 std::lock_guard<mutex_t> _(mut_);
100 state_ = 0;
101 gate1_.notify_all();
102 }
103
104 // Shared ownership
105
107 {
108 std::unique_lock<mutex_t> lk(mut_);
109 while ((state_ & write_entered_) || (state_ & n_readers_) == n_readers_)
110 {
111 gate1_.wait(lk);
112 }
113 count_t num_readers = (state_ & n_readers_) + 1;
114 state_ &= ~n_readers_;
115 state_ |= num_readers;
116 }
117
119 {
120 std::lock_guard<mutex_t> _(mut_);
121 count_t num_readers = state_ & n_readers_;
122 if (!(state_ & write_entered_) && num_readers != n_readers_)
123 {
124 ++num_readers;
125 state_ &= ~n_readers_;
126 state_ |= num_readers;
127 return true;
128 }
129 return false;
130 }
131
132};
133
134template<shared_mutex_type>
136
137// original Hinnant implementation prioritizing writers
138
139template<>
141 : public shared_mutex_base
142{
143 cond_t gate2_;
144
145public:
146
147 void lock()
148 {
149 std::unique_lock<mutex_t> lk(mut_);
150 while (state_ & write_entered_)
151 {
152 gate1_.wait(lk);
153 }
154 state_ |= write_entered_;
155 while (state_ & n_readers_)
156 {
157 gate2_.wait(lk);
158 }
159 }
160
162 {
163 std::lock_guard<mutex_t> _(mut_);
164 count_t num_readers = (state_ & n_readers_) - 1;
165 state_ &= ~n_readers_;
166 state_ |= num_readers;
167 if (state_ & write_entered_)
168 {
169 if (num_readers == 0)
170 {
171 gate2_.notify_one();
172 }
173 }
174 else if (num_readers == n_readers_ - 1)
175 {
176 gate1_.notify_one();
177 }
178 }
179
180};
181
182// implementation not locking readers on behalf of writers
183
184template<>
186 : public shared_mutex_base
187{
188 count_t writer_waiting_ = 0;
189
190public:
191
192 void lock()
193 {
194 std::unique_lock<mutex_t> lk(mut_);
195 ++writer_waiting_;
196 while (state_ & n_readers_ || state_ & write_entered_)
197 {
198 gate1_.wait(lk);
199 }
200 state_ |= write_entered_;
201 --writer_waiting_;
202 }
203
205 {
206 std::lock_guard<mutex_t> _(mut_);
207 count_t num_readers = (state_ & n_readers_) - 1;
208 state_ &= ~n_readers_;
209 state_ |= num_readers;
210
211 if ((writer_waiting_ && num_readers == 0)
212 || (num_readers == n_readers_ - 1))
213 {
214 gate1_.notify_one();
215 }
216 }
217
218};
219
220// Debugger wrapper class that provides insight
221template<class sm>
222class debug_wrapper : public sm
223{
224 std::mutex wm_;
225 // Identity of the exclusive owner if any
226 std::thread::id exclusive_owner_ = {};
227 // key_type thread_id, mapped_type number of locks
228 std::map<std::thread::id, unsigned int> shared_owners_;
229
230public:
231
233 {
234 std::lock_guard<std::mutex> _(wm_);
235 }
236
237 // Exclusive ownership
238
239 void lock()
240 {
241 sm::lock();
242 std::lock_guard<std::mutex> _(wm_);
243 exclusive_owner_ = std::this_thread::get_id();
244 }
245
246 bool try_lock()
247 {
248 bool res = sm::try_lock();
249 std::lock_guard<std::mutex> _(wm_);
250 if (res)
251 {
252 exclusive_owner_ = std::this_thread::get_id();
253 }
254 return res;
255 }
256
257 void unlock()
258 {
259 sm::unlock();
260 std::lock_guard<std::mutex> _(wm_);
261 exclusive_owner_ = std::thread::id();
262 }
263
264 // Shared ownership
265
267 {
268 sm::lock_shared();
269 std::lock_guard<std::mutex> _(wm_);
270 ++shared_owners_[std::this_thread::get_id()];
271 }
272
274 {
275 bool res = sm::try_lock_shared();
276 std::lock_guard<std::mutex> _(wm_);
277 if (res)
278 {
279 ++shared_owners_[std::this_thread::get_id()];
280 }
281 return res;
282 }
283
285 {
286 sm::unlock_shared();
287 std::lock_guard<std::mutex> _(wm_);
288 auto owner = shared_owners_.find(std::this_thread::get_id());
289 if ( owner != shared_owners_.end() && 0 == --owner->second )
290 {
291 shared_owners_.erase(owner);
292 }
293 }
294
295};
296
297} // namespace detail
298} // namespace eprosima
299
300#if defined(__has_include) && __has_include(<version>)
301# include <version>
302#endif // if defined(__has_include) && __has_include(<version>)
303
304// Detect if the shared_lock feature is available
305#if defined(__has_include) && __has_include(<version>) && !defined(__cpp_lib_shared_mutex) || \
306 /* deprecated procedure if the good one is not available*/ \
307 ( !(defined(__has_include) && __has_include(<version>)) && \
308 !(defined(HAVE_CXX17) && HAVE_CXX17) && __cplusplus < 201703 )
309
310namespace eprosima {
311
312template <class Mutex>
314{
315public:
316
317 typedef Mutex mutex_type;
318
319private:
320
321 mutex_type* m_;
322 bool owns_;
323
324 struct __nat
325 {
326 int _;
327 };
328
329public:
330
332 : m_(nullptr)
333 , owns_(false)
334 {
335 }
336
337 explicit shared_lock(
338 mutex_type& m)
339 : m_(&m)
340 , owns_(true)
341 {
342 m_->lock_shared();
343 }
344
346 mutex_type& m,
347 std::defer_lock_t)
348 : m_(&m)
349 , owns_(false)
350 {
351 }
352
354 mutex_type& m,
355 std::try_to_lock_t)
356 : m_(&m)
357 , owns_(m.try_lock_shared())
358 {
359 }
360
362 mutex_type& m,
363 std::adopt_lock_t)
364 : m_(&m)
365 , owns_(true)
366 {
367 }
368
369 template <class Clock, class Duration>
371 mutex_type& m,
372 const std::chrono::time_point<Clock, Duration>& abs_time)
373 : m_(&m)
374 , owns_(m.try_lock_shared_until(abs_time))
375 {
376 }
377
378 template <class Rep, class Period>
380 mutex_type& m,
381 const std::chrono::duration<Rep, Period>& rel_time)
382 : m_(&m)
383 , owns_(m.try_lock_shared_for(rel_time))
384 {
385 }
386
388 {
389 if (owns_)
390 {
391 m_->unlock_shared();
392 }
393 }
394
396 shared_lock const&) = delete;
398 shared_lock const&) = delete;
399
401 shared_lock&& sl)
402 : m_(sl.m_)
403 , owns_(sl.owns_)
404 {
405 sl.m_ = nullptr; sl.owns_ = false;
406 }
407
409 shared_lock&& sl)
410 {
411 if (owns_)
412 {
413 m_->unlock_shared();
414 }
415 m_ = sl.m_;
416 owns_ = sl.owns_;
417 sl.m_ = nullptr;
418 sl.owns_ = false;
419 return *this;
420 }
421
422 explicit shared_lock(
423 std::unique_lock<mutex_type>&& ul)
424 : m_(ul.mutex())
425 , owns_(ul.owns_lock())
426 {
427 if (owns_)
428 {
429 m_->unlock_and_lock_shared();
430 }
431 ul.release();
432 }
433
434 void lock();
435 bool try_lock();
436 template <class Rep, class Period>
438 const std::chrono::duration<Rep, Period>& rel_time)
439 {
440 return try_lock_until(std::chrono::steady_clock::now() + rel_time);
441 }
442
443 template <class Clock, class Duration>
444 bool
446 const std::chrono::time_point<Clock, Duration>& abs_time);
447 void unlock();
448
449 void swap(
450 shared_lock&& u)
451 {
452 std::swap(m_, u.m_);
453 std::swap(owns_, u.owns_);
454 }
455
457 {
458 mutex_type* r = m_;
459 m_ = nullptr;
460 owns_ = false;
461 return r;
462 }
463
464 bool owns_lock() const
465 {
466 return owns_;
467 }
468
469 operator int __nat::* () const {
470 return owns_ ? &__nat::_ : 0;
471 }
473 {
474 return m_;
475 }
476
477};
478
479template <class Mutex>
480void
482{
483 if (m_ == nullptr)
484 {
485 throw std::system_error(std::error_code(EPERM, std::system_category()),
486 "shared_lock::lock: references null mutex");
487 }
488 if (owns_)
489 {
490 throw std::system_error(std::error_code(EDEADLK, std::system_category()),
491 "shared_lock::lock: already locked");
492 }
493 m_->lock_shared();
494 owns_ = true;
495}
496
497template <class Mutex>
498bool
500{
501 if (m_ == nullptr)
502 {
503 throw std::system_error(std::error_code(EPERM, std::system_category()),
504 "shared_lock::try_lock: references null mutex");
505 }
506 if (owns_)
507 {
508 throw std::system_error(std::error_code(EDEADLK, std::system_category()),
509 "shared_lock::try_lock: already locked");
510 }
511 owns_ = m_->try_lock_shared();
512 return owns_;
513}
514
515template <class Mutex>
516template <class Clock, class Duration>
517bool
519 const std::chrono::time_point<Clock, Duration>& abs_time)
520{
521 if (m_ == nullptr)
522 {
523 throw std::system_error(std::error_code(EPERM, std::system_category()),
524 "shared_lock::try_lock_until: references null mutex");
525 }
526 if (owns_)
527 {
528 throw std::system_error(std::error_code(EDEADLK, std::system_category()),
529 "shared_lock::try_lock_until: already locked");
530 }
531 owns_ = m_->try_lock_shared_until(abs_time);
532 return owns_;
533}
534
535template <class Mutex>
536void
538{
539 if (!owns_)
540 {
541 throw std::system_error(std::error_code(EPERM, std::system_category()),
542 "shared_lock::unlock: not locked");
543 }
544 m_->unlock_shared();
545 owns_ = false;
546}
547
548template <class Mutex>
549inline
550void
554{
555 x.swap(y);
556}
557
558} //namespace eprosima
559
560#else // fallback to STL
561
562#include <shared_mutex>
563
564namespace eprosima {
565
566using std::shared_lock;
567using std::swap;
568
569} //namespace eprosima
570
571#endif // shared_lock selection
572
573#ifndef USE_THIRDPARTY_SHARED_MUTEX
574# if defined(_MSC_VER) && _MSVC_LANG < 202302L
575# pragma message("warning: USE_THIRDPARTY_SHARED_MUTEX not defined. By default use framework version.")
576# else
577# warning "USE_THIRDPARTY_SHARED_MUTEX not defined. By default use framework version."
578# endif // if defined(_MSC_VER) && _MSVC_LANG < 202302L
579# define USE_THIRDPARTY_SHARED_MUTEX 0
580#endif // ifndef USE_THIRDPARTY_SHARED_MUTEX
581
582// Detect if the share_mutex feature is available or if the user forces it
583#if defined(__has_include) && __has_include(<version>) && !defined(__cpp_lib_shared_mutex) || \
584 /* allow users to ignore shared_mutex framework implementation */ \
585 (~USE_THIRDPARTY_SHARED_MUTEX + 1) || \
586 /* deprecated procedure if the good one is not available*/ \
587 ( !(defined(__has_include) && __has_include(<version>)) && \
588 !(defined(HAVE_CXX17) && HAVE_CXX17) && __cplusplus < 201703 )
589
590/*
591 Fast-DDS defaults to PTHREAD_RWLOCK_PREFER_READER_NP for two main reasons:
592
593 - It allows reader side recursiveness. If we have two threads (T1, T2) and
594 called S a shared lock and E and exclusive one.
595
596 T1: S -> S
597 T2: E
598
599 PTHREAD_RWLOCK_PREFER_READER_NP will never deadlock. The S locks are not
600 influenced by the E locks.
601
602 PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP will deadlock. before T1
603 takes S twice. That happens because:
604 + T1's second S will wait for E (writer is prioritized)
605 + E will wait for T1's first S lock (writer needs atomic access)
606 + T1's first S cannot unlock because is blocked in the second S.
607
608 Thus, shared_mutex<PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP> is
609 non-recursive.
610
611 - It prevents ABBA deadlocks with other mutexes. If we have three threads
612 (Ti) and P is an ordinary mutex:
613
614 T1: P -> S
615 T2: S -> P
616 T3: E
617
618 PTHREAD_RWLOCK_PREFER_READER_NP will never deadlock. The S locks are not
619 influenced by the E locks. Starvation issues can be managed in the user
620 code.
621
622 PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP will deadlock if T3 takes E
623 before T1 takes S. That happens because:
624 + T1's S will wait for E (writer is prioritized)
625 + E will wait for T2's S lock (writer needs atomic access)
626 + T2's S cannot unlock because is blocked in P (owned by T1).
627
628 Thus, shared_mutex<PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP> must be
629 managed like an ordinary mutex in deadlock sense.
630 */
631
632namespace eprosima {
633
634#ifdef NDEBUG
635using shared_mutex = detail::shared_mutex<detail::shared_mutex_type::PTHREAD_RWLOCK_PREFER_READER_NP>;
636#else
639#endif // NDEBUG
640
641} //namespace eprosima
642
643#else // fallback to STL
644
645#include <shared_mutex>
646
647namespace eprosima {
648
649using std::shared_mutex;
650
651} //namespace eprosima
652
653#endif // shared_mutex selection
654
655#endif // _UTILS_SHARED_MUTEX_HPP_
Definition shared_mutex.hpp:223
bool try_lock_shared()
Definition shared_mutex.hpp:273
void unlock_shared()
Definition shared_mutex.hpp:284
void lock_shared()
Definition shared_mutex.hpp:266
~debug_wrapper()
Definition shared_mutex.hpp:232
void unlock()
Definition shared_mutex.hpp:257
bool try_lock()
Definition shared_mutex.hpp:246
void lock()
Definition shared_mutex.hpp:239
Definition shared_mutex.hpp:52
shared_mutex_base & operator=(const shared_mutex_base &)=delete
shared_mutex_base(const shared_mutex_base &)=delete
bool try_lock_shared()
Definition shared_mutex.hpp:118
void lock_shared()
Definition shared_mutex.hpp:106
~shared_mutex_base()
Definition shared_mutex.hpp:74
static const count_t n_readers_
Definition shared_mutex.hpp:65
void unlock()
Definition shared_mutex.hpp:97
count_t state_
Definition shared_mutex.hpp:62
unsigned count_t
Definition shared_mutex.hpp:58
bool try_lock()
Definition shared_mutex.hpp:86
std::condition_variable cond_t
Definition shared_mutex.hpp:57
static const count_t write_entered_
Definition shared_mutex.hpp:64
mutex_t mut_
Definition shared_mutex.hpp:60
std::mutex mutex_t
Definition shared_mutex.hpp:56
cond_t gate1_
Definition shared_mutex.hpp:61
shared_mutex_base()
Definition shared_mutex.hpp:69
Definition shared_mutex.hpp:135
Definition shared_mutex.hpp:314
mutex_type * release()
Definition shared_mutex.hpp:456
mutex_type * mutex() const
Definition shared_mutex.hpp:472
shared_lock(mutex_type &m, const std::chrono::duration< Rep, Period > &rel_time)
Definition shared_mutex.hpp:379
bool try_lock_until(const std::chrono::time_point< Clock, Duration > &abs_time)
Definition shared_mutex.hpp:518
shared_lock(shared_lock &&sl)
Definition shared_mutex.hpp:400
void swap(shared_lock &&u)
Definition shared_mutex.hpp:449
shared_lock & operator=(shared_lock const &)=delete
shared_lock(mutex_type &m)
Definition shared_mutex.hpp:337
Mutex mutex_type
Definition shared_mutex.hpp:317
shared_lock()
Definition shared_mutex.hpp:331
bool try_lock_for(const std::chrono::duration< Rep, Period > &rel_time)
Definition shared_mutex.hpp:437
shared_lock(std::unique_lock< mutex_type > &&ul)
Definition shared_mutex.hpp:422
shared_lock(mutex_type &m, std::adopt_lock_t)
Definition shared_mutex.hpp:361
void unlock()
Definition shared_mutex.hpp:537
shared_lock(shared_lock const &)=delete
bool try_lock()
Definition shared_mutex.hpp:499
void lock()
Definition shared_mutex.hpp:481
shared_lock(mutex_type &m, std::try_to_lock_t)
Definition shared_mutex.hpp:353
shared_lock(mutex_type &m, const std::chrono::time_point< Clock, Duration > &abs_time)
Definition shared_mutex.hpp:370
bool owns_lock() const
Definition shared_mutex.hpp:464
shared_lock(mutex_type &m, std::defer_lock_t)
Definition shared_mutex.hpp:345
~shared_lock()
Definition shared_mutex.hpp:387
shared_mutex_type
Definition shared_mutex.hpp:47
eProsima namespace.
Definition LibrarySettingsAttributes.h:23
detail::debug_wrapper< detail::shared_mutex< detail::shared_mutex_type::PTHREAD_RWLOCK_PREFER_READER_NP > > shared_mutex
Definition shared_mutex.hpp:638
void swap(shared_lock< Mutex > &x, shared_lock< Mutex > &y)
Definition shared_mutex.hpp:551