userver
C++ Async Framework
Toggle main menu visibility
Loading...
Searching...
No Matches
semaphore.hpp
Go to the documentation of this file.
1
#
pragma
once
2
3
/// @file userver/engine/semaphore.hpp
4
/// @brief @copybrief engine::Semaphore
5
6
#
include
<
atomic
>
7
#
include
<
chrono
>
8
#
include
<
shared_mutex
>
// for std locks
9
#
include
<
stdexcept
>
10
11
#
include
<
userver
/
engine
/
deadline
.
hpp
>
12
#
include
<
userver
/
engine
/
impl
/
wait_list_fwd
.
hpp
>
13
14
USERVER_NAMESPACE_BEGIN
15
16
namespace
engine {
17
18
/// Thrown by engine::Semaphore when an amount of locks greater than its current
19
/// capacity is requested.
20
class
UnreachableSemaphoreLockError
final
:
public
std::runtime_error {
21
public
:
22
using
std::runtime_error::runtime_error;
23
};
24
25
/// Thrown by engine::CancellableSemaphore on current task cancellation
26
class
SemaphoreLockCancelledError
final
:
public
std::runtime_error {
27
public
:
28
using
std::runtime_error::runtime_error;
29
};
30
31
/// @ingroup userver_concurrency
32
///
33
/// @brief Class that allows up to `max_simultaneous_locks` concurrent accesses
34
/// to the critical section. It honours task cancellation, unlike Semaphore.
35
///
36
/// ## Example usage:
37
///
38
/// @snippet engine/semaphore_test.cpp Sample engine::Semaphore usage
39
///
40
/// @see @ref scripts/docs/en/userver/synchronization.md
41
class
CancellableSemaphore
final
{
42
public
:
43
using
Counter = std::size_t;
44
45
/// Creates a semaphore with predefined number of available locks
46
/// @param capacity initial number of available locks
47
explicit
CancellableSemaphore
(Counter capacity);
48
49
~CancellableSemaphore();
50
51
CancellableSemaphore(CancellableSemaphore&&) =
delete
;
52
CancellableSemaphore(
const
CancellableSemaphore&) =
delete
;
53
CancellableSemaphore& operator=(CancellableSemaphore&&) =
delete
;
54
CancellableSemaphore& operator=(
const
CancellableSemaphore&) =
delete
;
55
56
/// Sets the total number of available locks. If the lock count decreases, the
57
/// current acquired lock count may temporarily go above the limit.
58
void
SetCapacity
(Counter capacity);
59
60
/// Gets the total number of available locks.
61
[[nodiscard]] Counter
GetCapacity
()
const
noexcept
;
62
63
/// Returns an approximate number of available locks, use only for statistics.
64
[[nodiscard]] std::size_t
RemainingApprox
()
const
;
65
66
/// Returns an approximate number of used locks, use only for statistics.
67
[[nodiscard]] std::size_t
UsedApprox
()
const
;
68
69
/// Decrements internal semaphore lock counter. Blocks if current counter is
70
/// zero until the subsequent call to unlock_shared() by another coroutine.
71
///
72
/// @note the user should eventually call unlock_shared() to increment the
73
/// lock counter.
74
///
75
/// @note the method doesn't wait for the semaphore if the current task is
76
/// cancelled. If a task waits on CancellableSemaphore and the cancellation
77
/// is requested, the waiting is aborted with an exception.
78
///
79
/// @throws UnreachableSemaphoreLockError if `capacity == 0`
80
/// @throws SemaphoreLockCancelledError if the current task is cancelled
81
void
lock_shared
();
82
83
/// Increments internal semaphore lock counter. If there is a user waiting in
84
/// lock_shared() on the same semaphore, it is woken up.
85
///
86
/// @note the order of coroutines to unblock is unspecified. Any code assuming
87
/// any specific order (e.g. FIFO) is incorrect and must be fixed.
88
///
89
/// @note it is allowed to call lock_shared() in one coroutine and
90
/// subsequently call unlock_shared() in another coroutine. In particular, it
91
/// is allowed to pass std::shared_lock<engine::Semaphore> across coroutines.
92
void
unlock_shared
();
93
94
[[nodiscard]]
bool
try_lock_shared();
95
96
template
<
typename
Rep,
typename
Period>
97
[[nodiscard]]
bool
try_lock_shared_for(std::chrono::duration<Rep, Period>);
98
99
template
<
typename
Clock,
typename
Duration>
100
[[nodiscard]]
bool
try_lock_shared_until(std::chrono::time_point<Clock, Duration>);
101
102
[[nodiscard]]
bool
try_lock_shared_until(Deadline deadline);
103
104
void
lock_shared_count(Counter count);
105
106
void
unlock_shared_count(Counter count);
107
108
[[nodiscard]]
bool
try_lock_shared_count(Counter count);
109
110
[[nodiscard]]
bool
try_lock_shared_until_count(Deadline deadline, Counter count);
111
112
private
:
113
enum
class
TryLockStatus { kSuccess, kTransientFailure, kPermanentFailure };
114
class
SemaphoreAwaitable;
115
116
TryLockStatus DoTryLock(Counter count);
117
TryLockStatus LockFastPath(Counter count);
118
bool
LockSlowPath(Deadline, Counter count);
119
120
impl
::FastPimplWaitList lock_awaiters_;
121
std::atomic<Counter> acquired_locks_;
122
std::atomic<Counter> capacity_;
123
};
124
125
/// @ingroup userver_concurrency
126
///
127
/// @brief Class that allows up to `max_simultaneous_locks` concurrent accesses
128
/// to the critical section. It ignores task cancellation, unlike
129
/// CancellableSemaphore.
130
///
131
/// ## Example usage:
132
///
133
/// @snippet engine/semaphore_test.cpp Sample engine::Semaphore usage
134
///
135
/// @see @ref scripts/docs/en/userver/synchronization.md
136
class
Semaphore
final
{
137
public
:
138
using
Counter = std::size_t;
139
140
/// Creates a semaphore with predefined number of available locks
141
/// @param capacity initial number of available locks
142
explicit
Semaphore
(Counter capacity);
143
144
~Semaphore();
145
146
Semaphore(Semaphore&&) =
delete
;
147
Semaphore(
const
Semaphore&) =
delete
;
148
Semaphore& operator=(Semaphore&&) =
delete
;
149
Semaphore& operator=(
const
Semaphore&) =
delete
;
150
151
/// Sets the total number of available locks. If the lock count decreases, the
152
/// current acquired lock count may temporarily go above the limit.
153
void
SetCapacity
(Counter capacity);
154
155
/// Gets the total number of available locks.
156
[[nodiscard]] Counter
GetCapacity
()
const
noexcept
;
157
158
/// Returns an approximate number of available locks, use only for statistics.
159
[[nodiscard]] std::size_t
RemainingApprox
()
const
;
160
161
/// Returns an approximate number of used locks, use only for statistics.
162
[[nodiscard]] std::size_t
UsedApprox
()
const
;
163
164
/// Decrements internal semaphore lock counter. Blocks if current counter is
165
/// zero until the subsequent call to unlock_shared() by another coroutine.
166
/// @note the user must eventually call unlock_shared() to increment the lock
167
/// counter.
168
/// @note the method waits for the semaphore even if the current task is
169
/// cancelled.
170
/// @throws UnreachableSemaphoreLockError if `capacity == 0`
171
void
lock_shared
();
172
173
/// Increments internal semaphore lock counter. If there is a user waiting in
174
/// lock_shared() on the same semaphore, it is woken up.
175
/// @note the order of coroutines to unblock is unspecified. Any code assuming
176
/// any specific order (e.g. FIFO) is incorrect and must be fixed.
177
/// @note it is allowed to call lock_shared() in one coroutine and
178
/// subsequently call unlock_shared() in another coroutine. In particular, it
179
/// is allowed to pass std::shared_lock<engine::Semaphore> across coroutines.
180
void
unlock_shared
();
181
182
/// Decrements internal semaphore lock counter if current counter is
183
/// not zero.
184
/// @note unlock_shared() should be called later to increment the lock
185
/// counter.
186
[[nodiscard]]
bool
try_lock_shared
();
187
188
template
<
typename
Rep,
typename
Period>
189
[[nodiscard]]
bool
try_lock_shared_for(std::chrono::duration<Rep, Period>);
190
191
template
<
typename
Clock,
typename
Duration>
192
[[nodiscard]]
bool
try_lock_shared_until(std::chrono::time_point<Clock, Duration>);
193
194
[[nodiscard]]
bool
try_lock_shared_until(Deadline deadline);
195
196
void
lock_shared_count(Counter count);
197
198
void
unlock_shared_count(Counter count);
199
200
[[nodiscard]]
bool
try_lock_shared_count(Counter count);
201
202
[[nodiscard]]
bool
try_lock_shared_until_count(Deadline deadline, Counter count);
203
204
private
:
205
CancellableSemaphore sem_;
206
};
207
208
/// A replacement for std::shared_lock that accepts Deadline arguments
209
class
SemaphoreLock
final
{
210
public
:
211
SemaphoreLock()
noexcept
=
default
;
212
explicit
SemaphoreLock(Semaphore&);
213
SemaphoreLock(Semaphore&, std::defer_lock_t)
noexcept
;
214
SemaphoreLock(Semaphore&, std::try_to_lock_t);
215
SemaphoreLock(Semaphore&, std::adopt_lock_t)
noexcept
;
216
217
template
<
typename
Rep,
typename
Period>
218
SemaphoreLock(Semaphore&, std::chrono::duration<Rep, Period>);
219
220
template
<
typename
Clock,
typename
Duration>
221
SemaphoreLock(Semaphore&, std::chrono::time_point<Clock, Duration>);
222
223
SemaphoreLock(Semaphore&, Deadline);
224
225
~SemaphoreLock();
226
227
SemaphoreLock(
const
SemaphoreLock&) =
delete
;
228
SemaphoreLock(SemaphoreLock&&)
noexcept
;
229
SemaphoreLock& operator=(
const
SemaphoreLock&) =
delete
;
230
SemaphoreLock& operator=(SemaphoreLock&&)
noexcept
;
231
232
[[nodiscard]]
bool
OwnsLock()
const
noexcept
;
233
explicit
operator
bool
()
const
noexcept
{
return
OwnsLock(); }
234
235
void
Lock();
236
bool
TryLock();
237
238
template
<
typename
Rep,
typename
Period>
239
bool
TryLockFor(std::chrono::duration<Rep, Period>);
240
241
template
<
typename
Clock,
typename
Duration>
242
bool
TryLockUntil(std::chrono::time_point<Clock, Duration>);
243
244
bool
TryLockUntil(Deadline);
245
246
void
Unlock();
247
void
Release();
248
249
private
:
250
Semaphore* sem_{
nullptr
};
251
bool
owns_lock_{
false
};
252
};
253
254
template
<
typename
Rep,
typename
Period>
255
bool
Semaphore::try_lock_shared_for(std::chrono::duration<Rep, Period> duration) {
256
return
try_lock_shared_until(Deadline::FromDuration(duration));
257
}
258
259
template
<
typename
Clock,
typename
Duration>
260
bool
Semaphore::try_lock_shared_until(std::chrono::time_point<Clock, Duration> until) {
261
return
try_lock_shared_until(Deadline::FromTimePoint(until));
262
}
263
264
template
<
typename
Rep,
typename
Period>
265
bool
CancellableSemaphore::try_lock_shared_for(std::chrono::duration<Rep, Period> duration) {
266
return
try_lock_shared_until(Deadline::FromDuration(duration));
267
}
268
269
template
<
typename
Clock,
typename
Duration>
270
bool
CancellableSemaphore::try_lock_shared_until(std::chrono::time_point<Clock, Duration> until) {
271
return
try_lock_shared_until(Deadline::FromTimePoint(until));
272
}
273
274
template
<
typename
Rep,
typename
Period>
275
SemaphoreLock::SemaphoreLock(Semaphore& sem, std::chrono::duration<Rep, Period> duration)
276
: sem_(&sem)
277
{
278
TryLockFor(duration);
279
}
280
281
template
<
typename
Clock,
typename
Duration>
282
SemaphoreLock::SemaphoreLock(Semaphore& sem, std::chrono::time_point<Clock, Duration> until)
283
: sem_(&sem)
284
{
285
TryLockUntil(until);
286
}
287
288
template
<
typename
Rep,
typename
Period>
289
bool
SemaphoreLock::TryLockFor(std::chrono::duration<Rep, Period> duration) {
290
return
TryLockUntil(Deadline::FromDuration(duration));
291
}
292
293
template
<
typename
Clock,
typename
Duration>
294
bool
SemaphoreLock::TryLockUntil(std::chrono::time_point<Clock, Duration> until) {
295
return
TryLockUntil(Deadline::FromTimePoint(until));
296
}
297
298
}
// namespace engine
299
300
USERVER_NAMESPACE_END
userver
engine
semaphore.hpp
Generated on
for userver by
Doxygen
1.17.0