2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of version 2 of the GNU General Public License as
7 * published by the Free Software Foundation.
9 * In addition, for the avoidance of any doubt, permission is granted to
10 * link this program with OpenSSL and to (re)distribute the binaries
11 * produced as the result of such linking.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 #include <shared_mutex>
28 This file provides several features around locks:
30 - LockGuarded and SharedLockGuarded provide a way to wrap any data structure as
31 protected by a lock (mutex or shared mutex), while making it immediately clear
32 which data is protected by that lock, and preventing any access to the data without
35 For example, to protect a set of integers with a simple mutex:
37 LockGuarded<std::set<int>> d_data;
39 or with a shared mutex instead:
41 SharedLockGuarded<std::set<int>> d_data;
43 Then the only ways to access the data is to call the lock(), read_only_lock() or try_lock() methods
44 for the simple case, or the read_lock(), write_lock(), try_read_lock() or try_write_lock() for the
46 Doing so will return a "holder" object, which provides access to the protected data, checking that
47 the lock has really been acquired if needed (try_ cases). The data might be read-only if read_lock(),
48 try_read_lock() or read_only_lock() was called. Access is provided by dereferencing the holder object
49 via '*' or '->', allowing a quick-access syntax:
51 return d_data.lock()->size();
53 Or when the lock needs to be kept for a bit longer:
56 auto data = d_data.lock();
61 - ReadWriteLock is a very light wrapper around a std::shared_mutex.
62 It used to be useful as a RAII wrapper around pthread_rwlock, but since
63 C++17 we don't actually that, so it's mostly there for historical
66 - ReadLock, WriteLock, TryReadLock and TryWriteLock are there as RAII
67 objects allowing to take a lock and be sure that it will always be unlocked
68 when we exit the block, even with a unforeseen exception.
69 They are light wrappers around std::unique_lock and std::shared_lock
72 Note that while the use of a shared mutex might be very efficient when the data
73 is predominantly concurrently accessed for reading by multiple threads and not
74 often written to (although if it is almost never updated our StateHolder in
75 sholder.hh might be a better fit), it is significantly more expensive than
76 a regular mutex, so that one might be a better choice if the contention is
77 low. It is wise to start with a regular mutex and actually measure the contention
78 under load before switching to a shared mutex.
84 ReadWriteLock() = default;
86 ReadWriteLock(const ReadWriteLock& rhs) = delete;
87 ReadWriteLock(ReadWriteLock&& rhs) = delete;
88 ReadWriteLock& operator=(const ReadWriteLock& rhs) = delete;
90 std::shared_mutex& getLock()
96 std::shared_mutex d_lock;
102 ReadLock(ReadWriteLock& lock): ReadLock(lock.getLock())
106 ReadLock(ReadWriteLock* lock): ReadLock(lock->getLock())
110 ReadLock(const ReadLock& rhs) = delete;
111 ReadLock& operator=(const ReadLock& rhs) = delete;
112 ReadLock(ReadLock&& rhs) noexcept :
113 d_lock(std::move(rhs.d_lock))
118 ReadLock(std::shared_mutex& lock) : d_lock(lock)
122 std::shared_lock<std::shared_mutex> d_lock;
128 WriteLock(ReadWriteLock& lock): WriteLock(lock.getLock())
132 WriteLock(ReadWriteLock* lock): WriteLock(lock->getLock())
136 WriteLock(const WriteLock& rhs) = delete;
137 WriteLock& operator=(const WriteLock& rhs) = delete;
138 WriteLock(WriteLock&& rhs) noexcept :
139 d_lock(std::move(rhs.d_lock))
144 WriteLock(std::shared_mutex& lock) : d_lock(lock)
148 std::unique_lock<std::shared_mutex> d_lock;
154 TryReadLock(ReadWriteLock& lock): TryReadLock(lock.getLock())
158 TryReadLock(ReadWriteLock* lock): TryReadLock(lock->getLock())
162 TryReadLock(const TryReadLock& rhs) = delete;
163 TryReadLock& operator=(const TryReadLock& rhs) = delete;
167 return d_lock.owns_lock();
171 TryReadLock(std::shared_mutex& lock) : d_lock(lock, std::try_to_lock)
175 std::shared_lock<std::shared_mutex> d_lock;
181 TryWriteLock(ReadWriteLock& lock): TryWriteLock(lock.getLock())
185 TryWriteLock(ReadWriteLock* lock): TryWriteLock(lock->getLock())
189 TryWriteLock(const TryWriteLock& rhs) = delete;
190 TryWriteLock& operator=(const TryWriteLock& rhs) = delete;
194 return d_lock.owns_lock();
198 TryWriteLock(std::shared_mutex& lock) : d_lock(lock, std::try_to_lock)
202 std::unique_lock<std::shared_mutex> d_lock;
205 template <typename T>
206 class LockGuardedHolder
209 explicit LockGuardedHolder(T& value, std::mutex& mutex): d_lock(mutex), d_value(value)
213 T& operator*() const noexcept {
217 T* operator->() const noexcept {
222 std::lock_guard<std::mutex> d_lock;
226 template <typename T>
227 class LockGuardedTryHolder
230 explicit LockGuardedTryHolder(T& value, std::mutex& mutex): d_lock(mutex, std::try_to_lock), d_value(value)
234 T& operator*() const {
236 throw std::runtime_error("Trying to access data protected by a mutex while the lock has not been acquired");
241 T* operator->() const {
243 throw std::runtime_error("Trying to access data protected by a mutex while the lock has not been acquired");
248 operator bool() const noexcept {
249 return d_lock.owns_lock();
252 bool owns_lock() const noexcept {
253 return d_lock.owns_lock();
262 std::unique_lock<std::mutex> d_lock;
266 template <typename T>
270 explicit LockGuarded(const T& value): d_value(value)
274 explicit LockGuarded(T&& value): d_value(std::move(value))
278 explicit LockGuarded() = default;
280 LockGuardedTryHolder<T> try_lock()
282 return LockGuardedTryHolder<T>(d_value, d_mutex);
285 LockGuardedHolder<T> lock()
287 return LockGuardedHolder<T>(d_value, d_mutex);
290 LockGuardedHolder<const T> read_only_lock()
292 return LockGuardedHolder<const T>(d_value, d_mutex);
300 template <typename T>
301 class SharedLockGuardedHolder
304 explicit SharedLockGuardedHolder(T& value, std::shared_mutex& mutex): d_lock(mutex), d_value(value)
308 T& operator*() const noexcept {
312 T* operator->() const noexcept {
317 std::lock_guard<std::shared_mutex> d_lock;
321 template <typename T>
322 class SharedLockGuardedTryHolder
325 explicit SharedLockGuardedTryHolder(T& value, std::shared_mutex& mutex): d_lock(mutex, std::try_to_lock), d_value(value)
329 T& operator*() const {
331 throw std::runtime_error("Trying to access data protected by a mutex while the lock has not been acquired");
336 T* operator->() const {
338 throw std::runtime_error("Trying to access data protected by a mutex while the lock has not been acquired");
343 operator bool() const noexcept {
344 return d_lock.owns_lock();
347 bool owns_lock() const noexcept {
348 return d_lock.owns_lock();
352 std::unique_lock<std::shared_mutex> d_lock;
356 template <typename T>
357 class SharedLockGuardedNonExclusiveHolder
360 explicit SharedLockGuardedNonExclusiveHolder(const T& value, std::shared_mutex& mutex): d_lock(mutex), d_value(value)
364 const T& operator*() const noexcept {
368 const T* operator->() const noexcept {
373 std::shared_lock<std::shared_mutex> d_lock;
377 template <typename T>
378 class SharedLockGuardedNonExclusiveTryHolder
381 explicit SharedLockGuardedNonExclusiveTryHolder(const T& value, std::shared_mutex& mutex): d_lock(mutex, std::try_to_lock), d_value(value)
385 const T& operator*() const {
387 throw std::runtime_error("Trying to access data protected by a mutex while the lock has not been acquired");
392 const T* operator->() const {
394 throw std::runtime_error("Trying to access data protected by a mutex while the lock has not been acquired");
399 operator bool() const noexcept {
400 return d_lock.owns_lock();
403 bool owns_lock() const noexcept {
404 return d_lock.owns_lock();
408 std::shared_lock<std::shared_mutex> d_lock;
412 template <typename T>
413 class SharedLockGuarded
416 explicit SharedLockGuarded(const T& value): d_value(value)
420 explicit SharedLockGuarded(T&& value): d_value(std::move(value))
424 explicit SharedLockGuarded() = default;
426 SharedLockGuardedTryHolder<T> try_write_lock()
428 return SharedLockGuardedTryHolder<T>(d_value, d_mutex);
431 SharedLockGuardedHolder<T> write_lock()
433 return SharedLockGuardedHolder<T>(d_value, d_mutex);
436 SharedLockGuardedNonExclusiveTryHolder<T> try_read_lock()
438 return SharedLockGuardedNonExclusiveTryHolder<T>(d_value, d_mutex);
441 SharedLockGuardedNonExclusiveHolder<T> read_lock()
443 return SharedLockGuardedNonExclusiveHolder<T>(d_value, d_mutex);
447 std::shared_mutex d_mutex;