]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/lock.hh
Merge pull request #14020 from omoerbeek/rec-compiling-rust-dcos
[thirdparty/pdns.git] / pdns / lock.hh
1 /*
2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
4 *
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.
8 *
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.
12 *
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.
17 *
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.
21 */
22 #pragma once
23 #include <mutex>
24 #include <shared_mutex>
25 #include <stdexcept>
26
27 /*
28 This file provides several features around locks:
29
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
33 holding the lock.
34
35 For example, to protect a set of integers with a simple mutex:
36
37 LockGuarded<std::set<int>> d_data;
38
39 or with a shared mutex instead:
40
41 SharedLockGuarded<std::set<int>> d_data;
42
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
45 shared one.
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:
50
51 return d_data.lock()->size();
52
53 Or when the lock needs to be kept for a bit longer:
54
55 {
56 auto data = d_data.lock();
57 data->clear();
58 data->insert(42);
59 }
60
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
64 reasons.
65
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
70 since C++17.
71
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.
79 */
80
81 class ReadWriteLock
82 {
83 public:
84 ReadWriteLock() = default;
85
86 ReadWriteLock(const ReadWriteLock& rhs) = delete;
87 ReadWriteLock(ReadWriteLock&& rhs) = delete;
88 ReadWriteLock& operator=(const ReadWriteLock& rhs) = delete;
89
90 std::shared_mutex& getLock()
91 {
92 return d_lock;
93 }
94
95 private:
96 std::shared_mutex d_lock;
97 };
98
99 class ReadLock
100 {
101 public:
102 ReadLock(ReadWriteLock& lock): ReadLock(lock.getLock())
103 {
104 }
105
106 ReadLock(ReadWriteLock* lock): ReadLock(lock->getLock())
107 {
108 }
109
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))
114 {
115 }
116
117 private:
118 ReadLock(std::shared_mutex& lock) : d_lock(lock)
119 {
120 }
121
122 std::shared_lock<std::shared_mutex> d_lock;
123 };
124
125 class WriteLock
126 {
127 public:
128 WriteLock(ReadWriteLock& lock): WriteLock(lock.getLock())
129 {
130 }
131
132 WriteLock(ReadWriteLock* lock): WriteLock(lock->getLock())
133 {
134 }
135
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))
140 {
141 }
142
143 private:
144 WriteLock(std::shared_mutex& lock) : d_lock(lock)
145 {
146 }
147
148 std::unique_lock<std::shared_mutex> d_lock;
149 };
150
151 class TryReadLock
152 {
153 public:
154 TryReadLock(ReadWriteLock& lock): TryReadLock(lock.getLock())
155 {
156 }
157
158 TryReadLock(ReadWriteLock* lock): TryReadLock(lock->getLock())
159 {
160 }
161
162 TryReadLock(const TryReadLock& rhs) = delete;
163 TryReadLock& operator=(const TryReadLock& rhs) = delete;
164
165 bool gotIt() const
166 {
167 return d_lock.owns_lock();
168 }
169
170 private:
171 TryReadLock(std::shared_mutex& lock) : d_lock(lock, std::try_to_lock)
172 {
173 }
174
175 std::shared_lock<std::shared_mutex> d_lock;
176 };
177
178 class TryWriteLock
179 {
180 public:
181 TryWriteLock(ReadWriteLock& lock): TryWriteLock(lock.getLock())
182 {
183 }
184
185 TryWriteLock(ReadWriteLock* lock): TryWriteLock(lock->getLock())
186 {
187 }
188
189 TryWriteLock(const TryWriteLock& rhs) = delete;
190 TryWriteLock& operator=(const TryWriteLock& rhs) = delete;
191
192 bool gotIt() const
193 {
194 return d_lock.owns_lock();
195 }
196
197 private:
198 TryWriteLock(std::shared_mutex& lock) : d_lock(lock, std::try_to_lock)
199 {
200 }
201
202 std::unique_lock<std::shared_mutex> d_lock;
203 };
204
205 template <typename T>
206 class LockGuardedHolder
207 {
208 public:
209 explicit LockGuardedHolder(T& value, std::mutex& mutex): d_lock(mutex), d_value(value)
210 {
211 }
212
213 T& operator*() const noexcept {
214 return d_value;
215 }
216
217 T* operator->() const noexcept {
218 return &d_value;
219 }
220
221 private:
222 std::lock_guard<std::mutex> d_lock;
223 T& d_value;
224 };
225
226 template <typename T>
227 class LockGuardedTryHolder
228 {
229 public:
230 explicit LockGuardedTryHolder(T& value, std::mutex& mutex): d_lock(mutex, std::try_to_lock), d_value(value)
231 {
232 }
233
234 T& operator*() const {
235 if (!owns_lock()) {
236 throw std::runtime_error("Trying to access data protected by a mutex while the lock has not been acquired");
237 }
238 return d_value;
239 }
240
241 T* operator->() const {
242 if (!owns_lock()) {
243 throw std::runtime_error("Trying to access data protected by a mutex while the lock has not been acquired");
244 }
245 return &d_value;
246 }
247
248 operator bool() const noexcept {
249 return d_lock.owns_lock();
250 }
251
252 bool owns_lock() const noexcept {
253 return d_lock.owns_lock();
254 }
255
256 void lock()
257 {
258 d_lock.lock();
259 }
260
261 private:
262 std::unique_lock<std::mutex> d_lock;
263 T& d_value;
264 };
265
266 template <typename T>
267 class LockGuarded
268 {
269 public:
270 explicit LockGuarded(const T& value): d_value(value)
271 {
272 }
273
274 explicit LockGuarded(T&& value): d_value(std::move(value))
275 {
276 }
277
278 explicit LockGuarded() = default;
279
280 LockGuardedTryHolder<T> try_lock()
281 {
282 return LockGuardedTryHolder<T>(d_value, d_mutex);
283 }
284
285 LockGuardedHolder<T> lock()
286 {
287 return LockGuardedHolder<T>(d_value, d_mutex);
288 }
289
290 LockGuardedHolder<const T> read_only_lock()
291 {
292 return LockGuardedHolder<const T>(d_value, d_mutex);
293 }
294
295 private:
296 std::mutex d_mutex;
297 T d_value;
298 };
299
300 template <typename T>
301 class SharedLockGuardedHolder
302 {
303 public:
304 explicit SharedLockGuardedHolder(T& value, std::shared_mutex& mutex): d_lock(mutex), d_value(value)
305 {
306 }
307
308 T& operator*() const noexcept {
309 return d_value;
310 }
311
312 T* operator->() const noexcept {
313 return &d_value;
314 }
315
316 private:
317 std::lock_guard<std::shared_mutex> d_lock;
318 T& d_value;
319 };
320
321 template <typename T>
322 class SharedLockGuardedTryHolder
323 {
324 public:
325 explicit SharedLockGuardedTryHolder(T& value, std::shared_mutex& mutex): d_lock(mutex, std::try_to_lock), d_value(value)
326 {
327 }
328
329 T& operator*() const {
330 if (!owns_lock()) {
331 throw std::runtime_error("Trying to access data protected by a mutex while the lock has not been acquired");
332 }
333 return d_value;
334 }
335
336 T* operator->() const {
337 if (!owns_lock()) {
338 throw std::runtime_error("Trying to access data protected by a mutex while the lock has not been acquired");
339 }
340 return &d_value;
341 }
342
343 operator bool() const noexcept {
344 return d_lock.owns_lock();
345 }
346
347 bool owns_lock() const noexcept {
348 return d_lock.owns_lock();
349 }
350
351 private:
352 std::unique_lock<std::shared_mutex> d_lock;
353 T& d_value;
354 };
355
356 template <typename T>
357 class SharedLockGuardedNonExclusiveHolder
358 {
359 public:
360 explicit SharedLockGuardedNonExclusiveHolder(const T& value, std::shared_mutex& mutex): d_lock(mutex), d_value(value)
361 {
362 }
363
364 const T& operator*() const noexcept {
365 return d_value;
366 }
367
368 const T* operator->() const noexcept {
369 return &d_value;
370 }
371
372 private:
373 std::shared_lock<std::shared_mutex> d_lock;
374 const T& d_value;
375 };
376
377 template <typename T>
378 class SharedLockGuardedNonExclusiveTryHolder
379 {
380 public:
381 explicit SharedLockGuardedNonExclusiveTryHolder(const T& value, std::shared_mutex& mutex): d_lock(mutex, std::try_to_lock), d_value(value)
382 {
383 }
384
385 const T& operator*() const {
386 if (!owns_lock()) {
387 throw std::runtime_error("Trying to access data protected by a mutex while the lock has not been acquired");
388 }
389 return d_value;
390 }
391
392 const T* operator->() const {
393 if (!owns_lock()) {
394 throw std::runtime_error("Trying to access data protected by a mutex while the lock has not been acquired");
395 }
396 return &d_value;
397 }
398
399 operator bool() const noexcept {
400 return d_lock.owns_lock();
401 }
402
403 bool owns_lock() const noexcept {
404 return d_lock.owns_lock();
405 }
406
407 private:
408 std::shared_lock<std::shared_mutex> d_lock;
409 const T& d_value;
410 };
411
412 template <typename T>
413 class SharedLockGuarded
414 {
415 public:
416 explicit SharedLockGuarded(const T& value): d_value(value)
417 {
418 }
419
420 explicit SharedLockGuarded(T&& value): d_value(std::move(value))
421 {
422 }
423
424 explicit SharedLockGuarded() = default;
425
426 SharedLockGuardedTryHolder<T> try_write_lock()
427 {
428 return SharedLockGuardedTryHolder<T>(d_value, d_mutex);
429 }
430
431 SharedLockGuardedHolder<T> write_lock()
432 {
433 return SharedLockGuardedHolder<T>(d_value, d_mutex);
434 }
435
436 SharedLockGuardedNonExclusiveTryHolder<T> try_read_lock()
437 {
438 return SharedLockGuardedNonExclusiveTryHolder<T>(d_value, d_mutex);
439 }
440
441 SharedLockGuardedNonExclusiveHolder<T> read_lock()
442 {
443 return SharedLockGuardedNonExclusiveHolder<T>(d_value, d_mutex);
444 }
445
446 private:
447 std::shared_mutex d_mutex;
448 T d_value;
449 };