Coverage Report

Created: 2024-04-25 06:27

/src/pdns/pdns/lock.hh
Line
Count
Source (jump to first uncovered line)
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
0
  {
92
0
    return d_lock;
93
0
  }
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
0
  {
104
0
  }
105
106
  ReadLock(ReadWriteLock* lock): ReadLock(lock->getLock())
107
0
  {
108
0
  }
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
0
  {
115
0
  }
116
117
private:
118
  ReadLock(std::shared_mutex& lock) : d_lock(lock)
119
0
  {
120
0
  }
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
0
  {
130
0
  }
131
132
  WriteLock(ReadWriteLock* lock): WriteLock(lock->getLock())
133
0
  {
134
0
  }
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
0
  {
141
0
  }
142
143
private:
144
  WriteLock(std::shared_mutex& lock) : d_lock(lock)
145
0
  {
146
0
  }
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
0
  {
156
0
  }
157
158
  TryReadLock(ReadWriteLock* lock): TryReadLock(lock->getLock())
159
0
  {
160
0
  }
161
162
  TryReadLock(const TryReadLock& rhs) = delete;
163
  TryReadLock& operator=(const TryReadLock& rhs) = delete;
164
165
  bool gotIt() const
166
0
  {
167
0
    return d_lock.owns_lock();
168
0
  }
169
170
private:
171
  TryReadLock(std::shared_mutex& lock) : d_lock(lock, std::try_to_lock)
172
0
  {
173
0
  }
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
0
  {
183
0
  }
184
185
  TryWriteLock(ReadWriteLock* lock): TryWriteLock(lock->getLock())
186
0
  {
187
0
  }
188
189
  TryWriteLock(const TryWriteLock& rhs) = delete;
190
  TryWriteLock& operator=(const TryWriteLock& rhs) = delete;
191
192
  bool gotIt() const
193
0
  {
194
0
    return d_lock.owns_lock();
195
0
  }
196
197
private:
198
  TryWriteLock(std::shared_mutex& lock) : d_lock(lock, std::try_to_lock)
199
0
  {
200
0
  }
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
0
  {
211
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&)
212
213
  T& operator*() const noexcept {
214
    return d_value;
215
  }
216
217
0
  T* operator->() const noexcept {
218
0
    return &d_value;
219
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
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
0
  {
276
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> > >&&)
277
278
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()
279
280
  LockGuardedTryHolder<T> try_lock()
281
  {
282
    return LockGuardedTryHolder<T>(d_value, d_mutex);
283
  }
284
285
  LockGuardedHolder<T> lock()
286
0
  {
287
0
    return LockGuardedHolder<T>(d_value, d_mutex);
288
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()
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
};