olp-cpp-sdk  1.22.0
shared_mutex.h
1 /*
2  * Copyright (C) 2020 HERE Europe B.V.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  * SPDX-License-Identifier: Apache-2.0
17  * License-Filename: LICENSE
18  */
19 
20 #pragma once
21 
22 #if ((__cplusplus >= 201703L) || (defined(_MSC_VER) && _MSC_VER >= 1900))
23 #include <shared_mutex>
24 #else
25 
26 // This implementation is taken from GCC 7.4 headers
27 
28 #include <cassert>
29 #include <condition_variable>
30 #include <system_error>
31 
32 #if (defined(__USE_UNIX98) || defined(__USE_XOPEN2K))
33 #define HAVE_PTHREAD_RWLOCK
34 #endif
35 
36 #ifndef THROW_OR_ABORT
37 # if __cpp_exceptions
38 # define THROW_OR_ABORT(exception) (throw (exception))
39 # else
40 # include <cstdlib>
41 # define THROW_OR_ABORT(exception) \
42  do { \
43  (void)exception; \
44  std::abort(); \
45  } while (0)
46 # endif
47 #endif
48 
49 namespace std {
50 namespace detail {
51 
52 // C++11 compatible std::exchange
53 template<class T, class U = T>
54 T
55 exchange(T& obj, U&& new_value)
56 {
57  T old_value = std::move(obj);
58  obj = std::forward<U>(new_value);
59  return old_value;
60 }
61 
62 #if defined(HAVE_PTHREAD_RWLOCK)
63 
65 class shared_mutex_pthread {
66 #if defined(PTHREAD_RWLOCK_INITIALIZER)
67  pthread_rwlock_t _M_rwlock = PTHREAD_RWLOCK_INITIALIZER;
68 
69  public:
70  shared_mutex_pthread() = default;
71  ~shared_mutex_pthread() = default;
72 #else
73  pthread_rwlock_t _M_rwlock;
74 
75  public:
76  shared_mutex_pthread() {
77  int __ret = pthread_rwlock_init(&_M_rwlock, NULL);
78  if (__ret == ENOMEM)
79  THROW_OR_ABORT(std::bad_alloc());
80  else if (__ret == EAGAIN)
81  THROW_OR_ABORT(std::system_error(
82  std::make_error_code(errc::resource_unavailable_try_again)));
83  else if (__ret == EPERM)
84  THROW_OR_ABORT(std::system_error(
85  std::make_error_code(errc::operation_not_permitted)));
86  // Errors not handled: EBUSY, EINVAL
87  assert(__ret == 0);
88  }
89 
90  ~shared_mutex_pthread() {
91  int __ret __attribute((__unused__)) = pthread_rwlock_destroy(&_M_rwlock);
92  // Errors not handled: EBUSY, EINVAL
93  assert(__ret == 0);
94  }
95 #endif
96 
97  shared_mutex_pthread(const shared_mutex_pthread&) = delete;
98  shared_mutex_pthread& operator=(const shared_mutex_pthread&) = delete;
99 
100  void lock() {
101  int __ret = pthread_rwlock_wrlock(&_M_rwlock);
102  if (__ret == EDEADLK)
103  THROW_OR_ABORT(std::system_error(
104  std::make_error_code(errc::resource_deadlock_would_occur)));
105  // Errors not handled: EINVAL
106  assert(__ret == 0);
107  }
108 
109  bool try_lock() {
110  int __ret = pthread_rwlock_trywrlock(&_M_rwlock);
111  if (__ret == EBUSY)
112  return false;
113  // Errors not handled: EINVAL
114  assert(__ret == 0);
115  return true;
116  }
117 
118  void unlock() {
119  int __ret __attribute((__unused__)) = pthread_rwlock_unlock(&_M_rwlock);
120  // Errors not handled: EPERM, EBUSY, EINVAL
121  assert(__ret == 0);
122  }
123 
124  // Shared ownership
125 
126  void lock_shared() {
127  int __ret;
128  // We retry if we exceeded the maximum number of read locks supported by
129  // the POSIX implementation; this can result in busy-waiting, but this
130  // is okay based on the current specification of forward progress
131  // guarantees by the standard.
132  do {
133  __ret = pthread_rwlock_rdlock(&_M_rwlock);
134  } while (__ret == EAGAIN);
135  if (__ret == EDEADLK)
136  THROW_OR_ABORT(std::system_error(
137  std::make_error_code(errc::resource_deadlock_would_occur)));
138  // Errors not handled: EINVAL
139  assert(__ret == 0);
140  }
141 
142  bool try_lock_shared() {
143  int __ret = pthread_rwlock_tryrdlock(&_M_rwlock);
144  // If the maximum number of read locks has been exceeded, we just fail
145  // to acquire the lock. Unlike for lock(), we are not allowed to throw
146  // an exception.
147  if (__ret == EBUSY || __ret == EAGAIN)
148  return false;
149  // Errors not handled: EINVAL
150  assert(__ret == 0);
151  return true;
152  }
153 
154  void unlock_shared() { unlock(); }
155 
156  void* native_handle() { return &_M_rwlock; }
157 };
158 
159 #else
160 
163  // Based on Howard Hinnant's reference implementation from N2406.
164 
165  // The high bit of _M_state is the write-entered flag which is set to
166  // indicate a writer has taken the lock or is queuing to take the lock.
167  // The remaining bits are the count of reader locks.
168  //
169  // To take a reader lock, block on gate1 while the write-entered flag is
170  // set or the maximum number of reader locks is held, then increment the
171  // reader lock count.
172  // To release, decrement the count, then if the write-entered flag is set
173  // and the count is zero then signal gate2 to wake a queued writer,
174  // otherwise if the maximum number of reader locks was held signal gate1
175  // to wake a reader.
176  //
177  // To take a writer lock, block on gate1 while the write-entered flag is
178  // set, then set the write-entered flag to start queueing, then block on
179  // gate2 while the number of reader locks is non-zero.
180  // To release, unset the write-entered flag and signal gate1 to wake all
181  // blocked readers and writers.
182  //
183  // This means that when no reader locks are held readers and writers get
184  // equal priority. When one or more reader locks is held a writer gets
185  // priority and no more reader locks can be taken while the writer is
186  // queued.
187 
188  // Only locked when accessing _M_state or waiting on condition variables.
189  mutex _M_mut;
190  // Used to block while write-entered is set or reader count at maximum.
191  condition_variable _M_gate1;
192  // Used to block queued writers while reader count is non-zero.
193  condition_variable _M_gate2;
194  // The write-entered flag and reader count.
195  unsigned _M_state;
196 
197  static constexpr unsigned _S_write_entered =
198  1U << (sizeof(unsigned) * __CHAR_BIT__ - 1);
199  static constexpr unsigned _S_max_readers = ~_S_write_entered;
200 
201  // Test whether the write-entered flag is set. _M_mut must be locked.
202  bool _M_write_entered() const { return _M_state & _S_write_entered; }
203 
204  // The number of reader locks currently held. _M_mut must be locked.
205  unsigned _M_readers() const { return _M_state & _S_max_readers; }
206 
207  public:
208  shared_mutex_cv() : _M_state(0) {}
209 
210  ~shared_mutex_cv() { assert(_M_state == 0); }
211 
212  shared_mutex_cv(const shared_mutex_cv&) = delete;
213  shared_mutex_cv& operator=(const shared_mutex_cv&) = delete;
214 
215  // Exclusive ownership
216 
218  void lock() {
219  unique_lock<mutex> __lk(_M_mut);
220  // Wait until we can set the write-entered flag.
221  _M_gate1.wait(__lk, [=] { return !_M_write_entered(); });
222  _M_state |= _S_write_entered;
223  // Then wait until there are no more readers.
224  _M_gate2.wait(__lk, [=] { return _M_readers() == 0; });
225  }
226 
228  bool try_lock() {
229  unique_lock<mutex> __lk(_M_mut, try_to_lock);
230  if (__lk.owns_lock() && _M_state == 0) {
231  _M_state = _S_write_entered;
232  return true;
233  }
234  return false;
235  }
236 
238  void unlock() {
239  lock_guard<mutex> __lk(_M_mut);
240  assert(_M_write_entered());
241  _M_state = 0;
242  // call notify_all() while mutex is held so that another thread can't
243  // lock and unlock the mutex then destroy *this before we make the call.
244  _M_gate1.notify_all();
245  }
246 
247  // Shared ownership
248 
250  void lock_shared() {
251  unique_lock<mutex> __lk(_M_mut);
252  _M_gate1.wait(__lk, [=] { return _M_state < _S_max_readers; });
253  ++_M_state;
254  }
255 
258  unique_lock<mutex> __lk(_M_mut, try_to_lock);
259  if (!__lk.owns_lock())
260  return false;
261  if (_M_state < _S_max_readers) {
262  ++_M_state;
263  return true;
264  }
265  return false;
266  }
267 
269  void unlock_shared() {
270  lock_guard<mutex> __lk(_M_mut);
271  assert(_M_readers() > 0);
272  auto __prev = _M_state--;
273  if (_M_write_entered()) {
274  // Wake the queued writer if there are no more readers.
275  if (_M_readers() == 0)
276  _M_gate2.notify_one();
277  // No need to notify gate1 because we give priority to the queued
278  // writer, and that writer will eventually notify gate1 after it
279  // clears the write-entered flag.
280  } else {
281  // Wake any thread that was blocked on reader overflow.
282  if (__prev == _S_max_readers)
283  _M_gate1.notify_one();
284  }
285  }
286 };
287 #endif
288 } // namespace detail
289 
295  public:
296  shared_mutex() = default;
297  ~shared_mutex() = default;
298 
299  shared_mutex(const shared_mutex&) = delete;
300  shared_mutex& operator=(const shared_mutex&) = delete;
301 
303  void lock() { _M_impl.lock(); }
304 
310  bool try_lock() { return _M_impl.try_lock(); }
311 
313  void unlock() { _M_impl.unlock(); }
314 
319  void lock_shared() { _M_impl.lock_shared(); }
320 
326  bool try_lock_shared() { return _M_impl.try_lock_shared(); }
327 
329  void unlock_shared() { _M_impl.unlock_shared(); }
330 
331 #if defined(HAVE_PTHREAD_RWLOCK)
332  typedef void* native_handle_type;
333  native_handle_type native_handle() { return _M_impl.native_handle(); }
334 
335  private:
336  detail::shared_mutex_pthread _M_impl;
337 #else
338 
339  private:
340  detail::shared_mutex_cv _M_impl;
341 #endif
342 };
343 
344 template <typename Mutex>
349 class shared_lock {
350  public:
352  typedef Mutex mutex_type;
353 
354  shared_lock() noexcept : _M_pm(nullptr), _M_owns(false) {}
355 
362  explicit shared_lock(mutex_type& __m)
363  : _M_pm(std::addressof(__m)), _M_owns(true) {
364  __m.lock_shared();
365  }
366 
373  shared_lock(mutex_type& __m, defer_lock_t) noexcept
374  : _M_pm(std::addressof(__m)), _M_owns(false) {}
375 
382  shared_lock(mutex_type& __m, try_to_lock_t)
383  : _M_pm(std::addressof(__m)), _M_owns(__m.try_lock_shared()) {}
384 
391  shared_lock(mutex_type& __m, adopt_lock_t)
392  : _M_pm(std::addressof(__m)), _M_owns(true) {}
393 
394  template <typename _Clock, typename _Duration>
395 
404  const chrono::time_point<_Clock, _Duration>& __abs_time)
405  : _M_pm(std::addressof(__m)),
406  _M_owns(__m.try_lock_shared_until(__abs_time)) {}
407 
408  template <typename _Rep, typename _Period>
409 
418  const chrono::duration<_Rep, _Period>& __rel_time)
419  : _M_pm(std::addressof(__m)),
420  _M_owns(__m.try_lock_shared_for(__rel_time)) {}
421 
422  ~shared_lock() {
423  if (_M_owns)
424  _M_pm->unlock_shared();
425  }
426 
427  shared_lock(shared_lock const&) = delete;
428  shared_lock& operator=(shared_lock const&) = delete;
429 
435  shared_lock(shared_lock&& __sl) noexcept : shared_lock() { swap(__sl); }
436 
442  shared_lock& operator=(shared_lock&& __sl) noexcept {
443  shared_lock(std::move(__sl)).swap(*this);
444  return *this;
445  }
446 
448  void lock() {
449  _M_lockable();
450  _M_pm->lock_shared();
451  _M_owns = true;
452  }
453 
455  bool try_lock() {
456  _M_lockable();
457  return _M_owns = _M_pm->try_lock_shared();
458  }
459 
460  template <typename _Rep, typename _Period>
461 
470  bool try_lock_for(const chrono::duration<_Rep, _Period>& __rel_time) {
471  _M_lockable();
472  return _M_owns = _M_pm->try_lock_shared_for(__rel_time);
473  }
474 
475  template <typename _Clock, typename _Duration>
476 
485  bool try_lock_until(const chrono::time_point<_Clock, _Duration>& __abs_time) {
486  _M_lockable();
487  return _M_owns = _M_pm->try_lock_shared_until(__abs_time);
488  }
489 
491  void unlock() {
492  if (!_M_owns)
493  THROW_OR_ABORT(std::system_error(
494  std::make_error_code(errc::resource_deadlock_would_occur)));
495  _M_pm->unlock_shared();
496  _M_owns = false;
497  }
498 
504  void swap(shared_lock& __u) noexcept {
505  std::swap(_M_pm, __u._M_pm);
506  std::swap(_M_owns, __u._M_owns);
507  }
508 
515  mutex_type* release() noexcept {
516  _M_owns = false;
517  return detail::exchange(_M_pm, nullptr);
518  }
519 
526  bool owns_lock() const noexcept { return _M_owns; }
527 
529  explicit operator bool() const noexcept { return _M_owns; }
530 
537  mutex_type* mutex() const noexcept { return _M_pm; }
538 
539  private:
540  void _M_lockable() const {
541  if (_M_pm == nullptr)
542  THROW_OR_ABORT(std::system_error(
543  std::make_error_code(errc::operation_not_permitted)));
544  if (_M_owns)
545  THROW_OR_ABORT(std::system_error(
546  std::make_error_code(errc::resource_deadlock_would_occur)));
547  }
548 
549  mutex_type* _M_pm;
550  bool _M_owns;
551 };
552 
553 template <typename _Mutex>
554 void swap(shared_lock<_Mutex>& __x, shared_lock<_Mutex>& __y) noexcept {
555  __x.swap(__y);
556 }
557 
558 } // namespace std
559 
560 #if defined(HAVE_PTHREAD_RWLOCK)
561 #undef HAVE_PTHREAD_RWLOCK
562 #endif
563 
564 #endif
A shared mutex type implemented using std::condition_variable.
Definition: shared_mutex.h:162
void lock()
Takes ownership of the associated mutex.
Definition: shared_mutex.h:218
bool try_lock()
Tries to take ownership of the mutex without blocking.
Definition: shared_mutex.h:228
void lock_shared()
Blocks the calling thread until the thread obtains shared ownership of the mutex.
Definition: shared_mutex.h:250
bool try_lock_shared()
Tries to take shared ownership of the mutex without blocking.
Definition: shared_mutex.h:257
void unlock()
Releases the ownership of the mutex from the calling thread.
Definition: shared_mutex.h:238
void unlock_shared()
Releases the shared ownership of the mutex from the calling thread.
Definition: shared_mutex.h:269
A shared mutex wrapper that supports timed lock operations and non-exclusive sharing by multiple thre...
Definition: shared_mutex.h:349
bool try_lock_for(const chrono::duration< _Rep, _Period > &__rel_time)
Tries to take shared ownership of the mutex and blocks it until the specified time elapses.
Definition: shared_mutex.h:470
bool try_lock()
Tries to take ownership of the mutex without blocking.
Definition: shared_mutex.h:455
void swap(shared_lock &__u) noexcept
Exchanges the data members of two shared_lock instances.
Definition: shared_mutex.h:504
shared_lock(shared_lock &&__sl) noexcept
Creates a shared_lock instance based on the other shared lock.
Definition: shared_mutex.h:435
shared_lock(mutex_type &__m, const chrono::time_point< _Clock, _Duration > &__abs_time)
Creates a shared_lock instance and tries to lock the associated mutex until the specified absolute ti...
Definition: shared_mutex.h:403
shared_lock(mutex_type &__m, adopt_lock_t)
Creates a shared_lock instance and assumes that the calling thread already owns the associated mutex.
Definition: shared_mutex.h:391
void unlock()
Releases the ownership of the mutex from the calling thread.
Definition: shared_mutex.h:491
mutex_type * release() noexcept
Disassociates the mutex without unlocking.
Definition: shared_mutex.h:515
shared_lock(mutex_type &__m, defer_lock_t) noexcept
Creates a shared_lock instance and does not lock the associated mutex.
Definition: shared_mutex.h:373
shared_lock(mutex_type &__m, try_to_lock_t)
Creates a shared_lock instance and tries to lock the associated mutex without blocking.
Definition: shared_mutex.h:382
void lock()
Takes ownership of the associated mutex.
Definition: shared_mutex.h:448
bool try_lock_until(const chrono::time_point< _Clock, _Duration > &__abs_time)
Tries to take shared ownership of the mutex and blocks it until the absolute time has passed.
Definition: shared_mutex.h:485
Mutex mutex_type
A typedef for the mutex type.
Definition: shared_mutex.h:352
bool owns_lock() const noexcept
Checks whether the lock owns its associated mutex.
Definition: shared_mutex.h:526
shared_lock(mutex_type &__m, const chrono::duration< _Rep, _Period > &__rel_time)
Creates a shared_lock instance and tries to lock the associated mutex until the specified duration ha...
Definition: shared_mutex.h:417
mutex_type * mutex() const noexcept
Gets a pointer to the associated mutex.
Definition: shared_mutex.h:537
shared_lock(mutex_type &__m)
Creates a shared_lock instance and locks the associated mutex.
Definition: shared_mutex.h:362
shared_lock & operator=(shared_lock &&__sl) noexcept
The default move assignment operator.
Definition: shared_mutex.h:442
A shared mutex type that can be locked exclusively by one thread or shared non-exclusively by multipl...
Definition: shared_mutex.h:294
bool try_lock()
Tries to take ownership of the mutex without blocking.
Definition: shared_mutex.h:310
bool try_lock_shared()
Tries to take shared ownership of the mutex without blocking.
Definition: shared_mutex.h:326
void lock_shared()
Blocks the calling thread until the thread obtains shared ownership of the mutex.
Definition: shared_mutex.h:319
void lock()
Takes ownership of the associated mutex.
Definition: shared_mutex.h:303
void unlock_shared()
Releases the shared ownership of the mutex from the calling thread.
Definition: shared_mutex.h:329
void unlock()
Releases the ownership of the mutex from the calling thread.
Definition: shared_mutex.h:313