userver: userver/engine/semaphore.hpp Source File
⚠️ This is the documentation for an old userver version. Click here to switch to the latest version.
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages Concepts
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// TODO remove extra includes
15#include <memory>
16#include <userver/utils/assert.hpp>
17
18USERVER_NAMESPACE_BEGIN
19
20namespace engine {
21
22/// Thrown by engine::Semaphore when an amount of locks greater than its current
23/// capacity is requested.
24class UnreachableSemaphoreLockError final : public std::runtime_error {
25 public:
26 using std::runtime_error::runtime_error;
27};
28
29/// Thrown by engine::CancellableSemaphore on current task cancellation
30class SemaphoreLockCancelledError final : public std::runtime_error {
31 public:
32 using std::runtime_error::runtime_error;
33};
34
35/// @ingroup userver_concurrency
36///
37/// @brief Class that allows up to `max_simultaneous_locks` concurrent accesses
38/// to the critical section. It honours task cancellation, unlike Semaphore.
39///
40/// ## Example usage:
41///
42/// @snippet engine/semaphore_test.cpp Sample engine::Semaphore usage
43///
44/// @see @ref scripts/docs/en/userver/synchronization.md
45class CancellableSemaphore final {
46 public:
47 using Counter = std::size_t;
48
49 /// Creates a semaphore with predefined number of available locks
50 /// @param capacity initial number of available locks
51 explicit CancellableSemaphore(Counter capacity);
52
53 ~CancellableSemaphore();
54
55 CancellableSemaphore(CancellableSemaphore&&) = delete;
56 CancellableSemaphore(const CancellableSemaphore&) = delete;
57 CancellableSemaphore& operator=(CancellableSemaphore&&) = delete;
58 CancellableSemaphore& operator=(const CancellableSemaphore&) = delete;
59
60 /// Sets the total number of available locks. If the lock count decreases, the
61 /// current acquired lock count may temporarily go above the limit.
62 void SetCapacity(Counter capacity);
63
64 /// Gets the total number of available locks.
65 Counter GetCapacity() const noexcept;
66
67 /// Returns an approximate number of available locks, use only for statistics.
68 std::size_t RemainingApprox() const;
69
70 /// Returns an approximate number of used locks, use only for statistics.
71 std::size_t UsedApprox() const;
72
73 /// Decrements internal semaphore lock counter. Blocks if current counter is
74 /// zero until the subsequent call to unlock_shared() by another coroutine.
75 /// @note the user must eventually call unlock_shared() to increment the lock
76 /// counter.
77 /// @note the method doesn't wait for the semaphore if the current task is
78 /// cancelled. If a task waits on CancellableSemaphore and the cancellation
79 /// is requested, the waiting is aborted with an exception.
80 /// @throws UnreachableSemaphoreLockError if `capacity == 0`
81 /// @throws SemaphoreLockCancelledError if the current task is cancelled
83
84 /// Increments internal semaphore lock counter. If there is a user waiting in
85 /// lock_shared() on the same semaphore, it will be waken up.
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 /// @note it is allowed to call lock_shared() in one coroutine and
89 /// subsequently call unlock_shared() in another coroutine. In particular, it
90 /// is allowed to pass std::shared_lock<engine::Semaphore> across coroutines.
92
93 [[nodiscard]] bool try_lock_shared();
94
95 template <typename Rep, typename Period>
96 [[nodiscard]] bool try_lock_shared_for(std::chrono::duration<Rep, Period>);
97
98 template <typename Clock, typename Duration>
99 [[nodiscard]] bool try_lock_shared_until(
100 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,
111 Counter count);
112
113 private:
114 enum class TryLockStatus { kSuccess, kTransientFailure, kPermanentFailure };
115
116 TryLockStatus DoTryLock(Counter count);
117 TryLockStatus LockFastPath(Counter count);
118 bool LockSlowPath(Deadline, Counter count);
119
120 impl::FastPimplWaitList lock_waiters_;
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
136class 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 Counter GetCapacity() const noexcept;
157
158 /// Returns an approximate number of available locks, use only for statistics.
159 std::size_t RemainingApprox() const;
160
161 /// Returns an approximate number of used locks, use only for statistics.
162 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`
172
173 /// Increments internal semaphore lock counter. If there is a user waiting in
174 /// lock_shared() on the same semaphore, it will be waken 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.
181
182 [[nodiscard]] bool try_lock_shared();
183
184 template <typename Rep, typename Period>
185 [[nodiscard]] bool try_lock_shared_for(std::chrono::duration<Rep, Period>);
186
187 template <typename Clock, typename Duration>
188 [[nodiscard]] bool try_lock_shared_until(
189 std::chrono::time_point<Clock, Duration>);
190
191 [[nodiscard]] bool try_lock_shared_until(Deadline deadline);
192
193 void lock_shared_count(Counter count);
194
195 void unlock_shared_count(Counter count);
196
197 [[nodiscard]] bool try_lock_shared_count(Counter count);
198
199 [[nodiscard]] bool try_lock_shared_until_count(Deadline deadline,
200 Counter count);
201
202 private:
203 CancellableSemaphore sem_;
204};
205
206/// A replacement for std::shared_lock that accepts Deadline arguments
207class SemaphoreLock final {
208 public:
209 SemaphoreLock() noexcept = default;
210 explicit SemaphoreLock(Semaphore&);
211 SemaphoreLock(Semaphore&, std::defer_lock_t) noexcept;
212 SemaphoreLock(Semaphore&, std::try_to_lock_t);
213 SemaphoreLock(Semaphore&, std::adopt_lock_t) noexcept;
214
215 template <typename Rep, typename Period>
216 SemaphoreLock(Semaphore&, std::chrono::duration<Rep, Period>);
217
218 template <typename Clock, typename Duration>
219 SemaphoreLock(Semaphore&, std::chrono::time_point<Clock, Duration>);
220
221 SemaphoreLock(Semaphore&, Deadline);
222
223 ~SemaphoreLock();
224
225 SemaphoreLock(const SemaphoreLock&) = delete;
226 SemaphoreLock(SemaphoreLock&&) noexcept;
227 SemaphoreLock& operator=(const SemaphoreLock&) = delete;
228 SemaphoreLock& operator=(SemaphoreLock&&) noexcept;
229
230 bool OwnsLock() const noexcept;
231 explicit operator bool() const noexcept { return OwnsLock(); }
232
233 void Lock();
234 bool TryLock();
235
236 template <typename Rep, typename Period>
237 bool TryLockFor(std::chrono::duration<Rep, Period>);
238
239 template <typename Clock, typename Duration>
240 bool TryLockUntil(std::chrono::time_point<Clock, Duration>);
241
242 bool TryLockUntil(Deadline);
243
244 void Unlock();
245 void Release();
246
247 private:
248 Semaphore* sem_{nullptr};
249 bool owns_lock_{false};
250};
251
252template <typename Rep, typename Period>
253bool Semaphore::try_lock_shared_for(
254 std::chrono::duration<Rep, Period> duration) {
255 return try_lock_shared_until(Deadline::FromDuration(duration));
256}
257
258template <typename Clock, typename Duration>
259bool Semaphore::try_lock_shared_until(
260 std::chrono::time_point<Clock, Duration> until) {
261 return try_lock_shared_until(Deadline::FromTimePoint(until));
262}
263
264template <typename Rep, typename Period>
265bool CancellableSemaphore::try_lock_shared_for(
266 std::chrono::duration<Rep, Period> duration) {
267 return try_lock_shared_until(Deadline::FromDuration(duration));
268}
269
270template <typename Clock, typename Duration>
271bool CancellableSemaphore::try_lock_shared_until(
272 std::chrono::time_point<Clock, Duration> until) {
273 return try_lock_shared_until(Deadline::FromTimePoint(until));
274}
275
276template <typename Rep, typename Period>
277SemaphoreLock::SemaphoreLock(Semaphore& sem,
278 std::chrono::duration<Rep, Period> duration)
279 : sem_(&sem) {
280 TryLockFor(duration);
281}
282
283template <typename Clock, typename Duration>
284SemaphoreLock::SemaphoreLock(Semaphore& sem,
285 std::chrono::time_point<Clock, Duration> until)
286 : sem_(&sem) {
287 TryLockUntil(until);
288}
289
290template <typename Rep, typename Period>
291bool SemaphoreLock::TryLockFor(std::chrono::duration<Rep, Period> duration) {
292 return TryLockUntil(Deadline::FromDuration(duration));
293}
294
295template <typename Clock, typename Duration>
296bool SemaphoreLock::TryLockUntil(
297 std::chrono::time_point<Clock, Duration> until) {
298 return TryLockUntil(Deadline::FromTimePoint(until));
299}
300
301} // namespace engine
302
303USERVER_NAMESPACE_END