Coverage Report

Created: 2026-06-07 07:03

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/pdns/pdns/lock.hh
Line
Count
Source
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 an 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
  ~ReadWriteLock() = default;
86
87
  ReadWriteLock(const ReadWriteLock& rhs) = delete;
88
  ReadWriteLock(ReadWriteLock&& rhs) = delete;
89
  ReadWriteLock& operator=(ReadWriteLock&&) = delete;
90
  ReadWriteLock& operator=(const ReadWriteLock& rhs) = delete;
91
92
  std::shared_mutex& getLock()
93
0
  {
94
0
    return d_lock;
95
0
  }
96
97
private:
98
  std::shared_mutex d_lock;
99
};
100
101
class ReadLock
102
{
103
public:
104
  ReadLock(ReadWriteLock& lock) :
105
    ReadLock(lock.getLock())
106
0
  {
107
0
  }
108
109
  ReadLock(ReadWriteLock* lock) :
110
    ReadLock(lock->getLock())
111
0
  {
112
0
  }
113
114
  ~ReadLock() = default;
115
  ReadLock(const ReadLock& rhs) = delete;
116
  ReadLock& operator=(const ReadLock& rhs) = delete;
117
  ReadLock& operator=(ReadLock&&) = delete;
118
119
  ReadLock(ReadLock&& rhs) noexcept :
120
    d_lock(std::move(rhs.d_lock))
121
0
  {
122
0
  }
123
124
private:
125
  ReadLock(std::shared_mutex& lock) :
126
    d_lock(lock)
127
0
  {
128
0
  }
129
130
  std::shared_lock<std::shared_mutex> d_lock;
131
};
132
133
class WriteLock
134
{
135
public:
136
  WriteLock(ReadWriteLock& lock) :
137
    WriteLock(lock.getLock())
138
0
  {
139
0
  }
140
141
  WriteLock(ReadWriteLock* lock) :
142
    WriteLock(lock->getLock())
143
0
  {
144
0
  }
145
146
  ~WriteLock() = default;
147
  WriteLock(const WriteLock& rhs) = delete;
148
  WriteLock& operator=(const WriteLock& rhs) = delete;
149
  WriteLock& operator=(WriteLock&&) = delete;
150
151
  WriteLock(WriteLock&& rhs) noexcept :
152
    d_lock(std::move(rhs.d_lock))
153
0
  {
154
0
  }
155
156
private:
157
  WriteLock(std::shared_mutex& lock) :
158
    d_lock(lock)
159
0
  {
160
0
  }
161
162
  std::unique_lock<std::shared_mutex> d_lock;
163
};
164
165
class TryReadLock
166
{
167
public:
168
  TryReadLock(ReadWriteLock& lock) :
169
    TryReadLock(lock.getLock())
170
0
  {
171
0
  }
172
173
  TryReadLock(ReadWriteLock* lock) :
174
    TryReadLock(lock->getLock())
175
0
  {
176
0
  }
177
178
  ~TryReadLock() = default;
179
  TryReadLock(const TryReadLock& rhs) = delete;
180
  TryReadLock(TryReadLock&&) = delete;
181
  TryReadLock& operator=(const TryReadLock& rhs) = delete;
182
  TryReadLock& operator=(TryReadLock&&) = delete;
183
184
  [[nodiscard]] bool gotIt() const
185
0
  {
186
0
    return d_lock.owns_lock();
187
0
  }
188
189
private:
190
  TryReadLock(std::shared_mutex& lock) :
191
    d_lock(lock, std::try_to_lock)
192
0
  {
193
0
  }
194
195
  std::shared_lock<std::shared_mutex> d_lock;
196
};
197
198
class TryWriteLock
199
{
200
public:
201
  TryWriteLock(ReadWriteLock& lock) :
202
    TryWriteLock(lock.getLock())
203
0
  {
204
0
  }
205
206
  TryWriteLock(ReadWriteLock* lock) :
207
    TryWriteLock(lock->getLock())
208
0
  {
209
0
  }
210
211
  ~TryWriteLock() = default;
212
  TryWriteLock(const TryWriteLock& rhs) = delete;
213
  TryWriteLock(TryWriteLock&&) = delete;
214
  TryWriteLock& operator=(const TryWriteLock& rhs) = delete;
215
  TryWriteLock& operator=(TryWriteLock&&) = delete;
216
217
  [[nodiscard]] bool gotIt() const
218
0
  {
219
0
    return d_lock.owns_lock();
220
0
  }
221
222
private:
223
  TryWriteLock(std::shared_mutex& lock) :
224
    d_lock(lock, std::try_to_lock)
225
0
  {
226
0
  }
227
228
  std::unique_lock<std::shared_mutex> d_lock;
229
};
230
231
template <typename T>
232
class LockGuardedHolder
233
{
234
public:
235
  explicit LockGuardedHolder(T& value, std::mutex& mutex) :
236
0
    d_lock(mutex), d_value(value)
237
0
  {
238
0
  }
Unexecuted instantiation: LockGuardedHolder<StatRing<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, CIStringCompare> >::LockGuardedHolder(StatRing<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, CIStringCompare>&, std::__1::mutex&)
Unexecuted instantiation: LockGuardedHolder<StatRing<SComboAddress, std::__1::less<SComboAddress> > >::LockGuardedHolder(StatRing<SComboAddress, std::__1::less<SComboAddress> >&, std::__1::mutex&)
Unexecuted instantiation: LockGuardedHolder<StatRing<std::__1::tuple<DNSName, QType>, std::__1::less<std::__1::tuple<DNSName, QType> > > >::LockGuardedHolder(StatRing<std::__1::tuple<DNSName, QType>, std::__1::less<std::__1::tuple<DNSName, QType> > >&, std::__1::mutex&)
239
240
  T& operator*() const noexcept
241
  {
242
    return d_value;
243
  }
244
245
  T* operator->() const noexcept
246
0
  {
247
0
    return &d_value;
248
0
  }
Unexecuted instantiation: LockGuardedHolder<StatRing<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, CIStringCompare> >::operator->() const
Unexecuted instantiation: LockGuardedHolder<StatRing<SComboAddress, std::__1::less<SComboAddress> > >::operator->() const
Unexecuted instantiation: LockGuardedHolder<StatRing<std::__1::tuple<DNSName, QType>, std::__1::less<std::__1::tuple<DNSName, QType> > > >::operator->() const
249
250
private:
251
  std::scoped_lock<std::mutex> d_lock;
252
  T& d_value;
253
};
254
255
template <typename T>
256
class LockGuardedTryHolder
257
{
258
public:
259
  explicit LockGuardedTryHolder(T& value, std::mutex& mutex) :
260
    d_lock(mutex, std::try_to_lock), d_value(value)
261
  {
262
  }
263
264
  T& operator*() const
265
  {
266
    if (!owns_lock()) {
267
      throw std::runtime_error("Trying to access data protected by a mutex while the lock has not been acquired");
268
    }
269
    return d_value;
270
  }
271
272
  T* operator->() const
273
  {
274
    if (!owns_lock()) {
275
      throw std::runtime_error("Trying to access data protected by a mutex while the lock has not been acquired");
276
    }
277
    return &d_value;
278
  }
279
280
  operator bool() const noexcept
281
  {
282
    return d_lock.owns_lock();
283
  }
284
285
  [[nodiscard]] bool owns_lock() const noexcept
286
  {
287
    return d_lock.owns_lock();
288
  }
289
290
  void lock()
291
  {
292
    d_lock.lock();
293
  }
294
295
private:
296
  std::unique_lock<std::mutex> d_lock;
297
  T& d_value;
298
};
299
300
template <typename T>
301
class LockGuarded
302
{
303
public:
304
  explicit LockGuarded(const T& value) :
305
    d_value(value)
306
  {
307
  }
308
309
  explicit LockGuarded(T&& value) :
310
0
    d_value(std::move(value))
311
0
  {
312
0
  }
Unexecuted instantiation: LockGuarded<StatRing<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, CIStringCompare> >::LockGuarded(StatRing<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, CIStringCompare>&&)
Unexecuted instantiation: LockGuarded<StatRing<SComboAddress, std::__1::less<SComboAddress> > >::LockGuarded(StatRing<SComboAddress, std::__1::less<SComboAddress> >&&)
Unexecuted instantiation: LockGuarded<StatRing<std::__1::tuple<DNSName, QType>, std::__1::less<std::__1::tuple<DNSName, QType> > > >::LockGuarded(StatRing<std::__1::tuple<DNSName, QType>, std::__1::less<std::__1::tuple<DNSName, QType> > >&&)
313
314
0
  explicit LockGuarded() = default;
Unexecuted instantiation: LockGuarded<StatRing<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, CIStringCompare> >::LockGuarded()
Unexecuted instantiation: LockGuarded<StatRing<SComboAddress, std::__1::less<SComboAddress> > >::LockGuarded()
Unexecuted instantiation: LockGuarded<StatRing<std::__1::tuple<DNSName, QType>, std::__1::less<std::__1::tuple<DNSName, QType> > > >::LockGuarded()
315
316
  LockGuardedTryHolder<T> try_lock()
317
  {
318
    return LockGuardedTryHolder<T>(d_value, d_mutex);
319
  }
320
321
  LockGuardedHolder<T> lock()
322
0
  {
323
0
    return LockGuardedHolder<T>(d_value, d_mutex);
324
0
  }
Unexecuted instantiation: LockGuarded<StatRing<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, CIStringCompare> >::lock()
Unexecuted instantiation: LockGuarded<StatRing<SComboAddress, std::__1::less<SComboAddress> > >::lock()
Unexecuted instantiation: LockGuarded<StatRing<std::__1::tuple<DNSName, QType>, std::__1::less<std::__1::tuple<DNSName, QType> > > >::lock()
325
326
  LockGuardedHolder<const T> read_only_lock()
327
  {
328
    return LockGuardedHolder<const T>(d_value, d_mutex);
329
  }
330
331
private:
332
  std::mutex d_mutex;
333
  T d_value;
334
};
335
336
template <typename T>
337
class RecursiveLockGuardedHolder
338
{
339
public:
340
  explicit RecursiveLockGuardedHolder(T& value, std::recursive_mutex& mutex) :
341
    d_lock(mutex), d_value(value)
342
  {
343
  }
344
345
  T& operator*() const noexcept
346
  {
347
    return d_value;
348
  }
349
350
  T* operator->() const noexcept
351
  {
352
    return &d_value;
353
  }
354
355
private:
356
  std::scoped_lock<std::recursive_mutex> d_lock;
357
  T& d_value;
358
};
359
360
template <typename T>
361
class RecursiveLockGuardedTryHolder
362
{
363
public:
364
  explicit RecursiveLockGuardedTryHolder(T& value, std::recursive_mutex& mutex) :
365
    d_lock(mutex, std::try_to_lock), d_value(value)
366
  {
367
  }
368
369
  T& operator*() const
370
  {
371
    if (!owns_lock()) {
372
      throw std::runtime_error("Trying to access data protected by a mutex while the lock has not been acquired");
373
    }
374
    return d_value;
375
  }
376
377
  T* operator->() const
378
  {
379
    if (!owns_lock()) {
380
      throw std::runtime_error("Trying to access data protected by a mutex while the lock has not been acquired");
381
    }
382
    return &d_value;
383
  }
384
385
  operator bool() const noexcept
386
  {
387
    return d_lock.owns_lock();
388
  }
389
390
  [[nodiscard]] bool owns_lock() const noexcept
391
  {
392
    return d_lock.owns_lock();
393
  }
394
395
  void lock()
396
  {
397
    d_lock.lock();
398
  }
399
400
private:
401
  std::unique_lock<std::recursive_mutex> d_lock;
402
  T& d_value;
403
};
404
405
template <typename T>
406
class RecursiveLockGuarded
407
{
408
public:
409
  explicit RecursiveLockGuarded(const T& value) :
410
    d_value(value)
411
  {
412
  }
413
414
  explicit RecursiveLockGuarded(T&& value) :
415
    d_value(std::move(value))
416
  {
417
  }
418
419
  explicit RecursiveLockGuarded() = default;
420
421
  RecursiveLockGuardedTryHolder<T> try_lock()
422
  {
423
    return RecursiveLockGuardedTryHolder<T>(d_value, d_mutex);
424
  }
425
426
  RecursiveLockGuardedHolder<T> lock()
427
  {
428
    return RecursiveLockGuardedHolder<T>(d_value, d_mutex);
429
  }
430
431
  RecursiveLockGuardedHolder<const T> read_only_lock()
432
  {
433
    return RecursiveLockGuardedHolder<const T>(d_value, d_mutex);
434
  }
435
436
private:
437
  std::recursive_mutex d_mutex;
438
  T d_value;
439
};
440
441
template <typename T>
442
class SharedLockGuardedHolder
443
{
444
public:
445
  explicit SharedLockGuardedHolder(T& value, std::shared_mutex& mutex) :
446
    d_lock(mutex), d_value(value)
447
  {
448
  }
449
450
  T& operator*() const noexcept
451
  {
452
    return d_value;
453
  }
454
455
  T* operator->() const noexcept
456
  {
457
    return &d_value;
458
  }
459
460
private:
461
  std::scoped_lock<std::shared_mutex> d_lock;
462
  T& d_value;
463
};
464
465
template <typename T>
466
class SharedLockGuardedTryHolder
467
{
468
public:
469
  explicit SharedLockGuardedTryHolder(T& value, std::shared_mutex& mutex) :
470
    d_lock(mutex, std::try_to_lock), d_value(value)
471
  {
472
  }
473
474
  T& operator*() const
475
  {
476
    if (!owns_lock()) {
477
      throw std::runtime_error("Trying to access data protected by a mutex while the lock has not been acquired");
478
    }
479
    return d_value;
480
  }
481
482
  T* operator->() const
483
  {
484
    if (!owns_lock()) {
485
      throw std::runtime_error("Trying to access data protected by a mutex while the lock has not been acquired");
486
    }
487
    return &d_value;
488
  }
489
490
  operator bool() const noexcept
491
  {
492
    return d_lock.owns_lock();
493
  }
494
495
  [[nodiscard]] bool owns_lock() const noexcept
496
  {
497
    return d_lock.owns_lock();
498
  }
499
500
private:
501
  std::unique_lock<std::shared_mutex> d_lock;
502
  T& d_value;
503
};
504
505
template <typename T>
506
class SharedLockGuardedNonExclusiveHolder
507
{
508
public:
509
  explicit SharedLockGuardedNonExclusiveHolder(const T& value, std::shared_mutex& mutex) :
510
    d_lock(mutex), d_value(value)
511
  {
512
  }
513
514
  const T& operator*() const noexcept
515
  {
516
    return d_value;
517
  }
518
519
  const T* operator->() const noexcept
520
  {
521
    return &d_value;
522
  }
523
524
private:
525
  std::shared_lock<std::shared_mutex> d_lock;
526
  const T& d_value;
527
};
528
529
template <typename T>
530
class SharedLockGuardedNonExclusiveTryHolder
531
{
532
public:
533
  explicit SharedLockGuardedNonExclusiveTryHolder(const T& value, std::shared_mutex& mutex) :
534
    d_lock(mutex, std::try_to_lock), d_value(value)
535
  {
536
  }
537
538
  const T& operator*() const
539
  {
540
    if (!owns_lock()) {
541
      throw std::runtime_error("Trying to access data protected by a mutex while the lock has not been acquired");
542
    }
543
    return d_value;
544
  }
545
546
  const T* operator->() const
547
  {
548
    if (!owns_lock()) {
549
      throw std::runtime_error("Trying to access data protected by a mutex while the lock has not been acquired");
550
    }
551
    return &d_value;
552
  }
553
554
  operator bool() const noexcept
555
  {
556
    return d_lock.owns_lock();
557
  }
558
559
  [[nodiscard]] bool owns_lock() const noexcept
560
  {
561
    return d_lock.owns_lock();
562
  }
563
564
private:
565
  std::shared_lock<std::shared_mutex> d_lock;
566
  const T& d_value;
567
};
568
569
template <typename T>
570
class SharedLockGuarded
571
{
572
public:
573
  explicit SharedLockGuarded(const T& value) :
574
    d_value(value)
575
  {
576
  }
577
578
  explicit SharedLockGuarded(T&& value) :
579
    d_value(std::move(value))
580
  {
581
  }
582
583
  explicit SharedLockGuarded() = default;
584
585
  SharedLockGuardedTryHolder<T> try_write_lock()
586
  {
587
    return SharedLockGuardedTryHolder<T>(d_value, d_mutex);
588
  }
589
590
  SharedLockGuardedHolder<T> write_lock()
591
  {
592
    return SharedLockGuardedHolder<T>(d_value, d_mutex);
593
  }
594
595
  SharedLockGuardedNonExclusiveTryHolder<T> try_read_lock()
596
  {
597
    return SharedLockGuardedNonExclusiveTryHolder<T>(d_value, d_mutex);
598
  }
599
600
  SharedLockGuardedNonExclusiveHolder<T> read_lock()
601
  {
602
    return SharedLockGuardedNonExclusiveHolder<T>(d_value, d_mutex);
603
  }
604
605
private:
606
  std::shared_mutex d_mutex;
607
  T d_value;
608
};