userver: userver/engine/shared_mutex.hpp Source File
Loading...
Searching...
No Matches
shared_mutex.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/engine/shared_mutex.hpp
4/// @brief @copybrief engine::SharedMutex
5
6#include <userver/engine/condition_variable.hpp>
7#include <userver/engine/mutex.hpp>
8#include <userver/engine/semaphore.hpp>
9
10USERVER_NAMESPACE_BEGIN
11
12namespace engine {
13
14/// @ingroup userver_concurrency
15///
16/// @brief std::shared_mutex replacement for asynchronous tasks.
17///
18/// Ignores task cancellations (succeeds even if the current task is cancelled).
19///
20/// Writers (unique locks) have priority over readers (shared locks),
21/// thus new shared lock waits for the pending writes to finish, which in turn
22/// waits for existing existing shared locks to unlock first.
23///
24/// ## Example usage:
25///
26/// @snippet engine/shared_mutex_test.cpp Sample engine::SharedMutex usage
27///
28/// @see @ref scripts/docs/en/userver/synchronization.md
29class SharedMutex final {
30 public:
31 SharedMutex();
32 ~SharedMutex() = default;
33
34 SharedMutex(const SharedMutex&) = delete;
35 SharedMutex(SharedMutex&&) = delete;
36 SharedMutex& operator=(const SharedMutex&) = delete;
37 SharedMutex& operator=(SharedMutex&&) = delete;
38
39 /// Locks the mutex for unique ownership. Blocks current coroutine if the
40 /// mutex is locked by another coroutine for reading or writing.
41 ///
42 /// @note The method waits for the mutex even if the current task is
43 /// cancelled.
44 void lock();
45
46 /// Unlocks the mutex for unique ownership. Before calling this method the
47 /// the mutex should be locked for unique ownership by current coroutine.
48 ///
49 /// @note the order of coroutines to unblock is unspecified. Any code assuming
50 /// any specific order (e.g. FIFO) is incorrect and should be fixed.
51 void unlock();
52
53 /// Tries to lock the mutex for unique ownership without blocking the
54 /// coroutine, returns true if succeeded.
55 [[nodiscard]] bool try_lock();
56
57 /// Tries to lock the mutex for unique ownership in specified duration.
58 /// Blocks current coroutine if
59 /// the mutex is locked by another coroutine up to the provided duration.
60 ///
61 /// @returns true if the locking succeeded
62 template <typename Rep, typename Period>
63 [[nodiscard]] bool try_lock_for(const std::chrono::duration<Rep, Period>&);
64
65 /// Tries to lock the mutex for unique ownership till specified time point.
66 /// Blocks current coroutine if
67 /// the mutex is locked by another coroutine up to the provided duration.
68 ///
69 /// @returns true if the locking succeeded
70 template <typename Clock, typename Duration>
71 [[nodiscard]] bool try_lock_until(
72 const std::chrono::time_point<Clock, Duration>&);
73
74 /// @overload
75 [[nodiscard]] bool try_lock_until(Deadline deadline);
76
77 /// Locks the mutex for shared ownership. Blocks current coroutine if the
78 /// mutex is locked by another coroutine for reading or writing.
79 ///
80 /// @note The method waits for the mutex even if the current task is
81 /// cancelled.
83
84 /// Unlocks the mutex for shared ownership. Before calling this method the
85 /// mutex should be locked for shared ownership by current coroutine.
86 ///
87 /// @note the order of coroutines to unblock is unspecified. Any code assuming
88 /// any specific order (e.g. FIFO) is incorrect and should be fixed.
90
91 /// Tries to lock the mutex for shared ownership without blocking the
92 /// coroutine, returns true if succeeded.
93 [[nodiscard]] bool try_lock_shared();
94
95 /// Tries to lock the mutex for shared ownership in specified duration.
96 /// Blocks current coroutine if
97 /// the mutex is locked by another coroutine up to the provided duration.
98 ///
99 /// @returns true if the locking succeeded
100 template <typename Rep, typename Period>
101 [[nodiscard]] bool try_lock_shared_for(
102 const std::chrono::duration<Rep, Period>&);
103
104 /// Tries to lock the mutex for shared ownership till specified time point.
105 /// Blocks current coroutine if
106 /// the mutex is locked by another coroutine up to the provided duration.
107 ///
108 /// @returns true if the locking succeeded
109 template <typename Clock, typename Duration>
110 [[nodiscard]] bool try_lock_shared_until(
111 const std::chrono::time_point<Clock, Duration>&);
112
113 /// @overload
114 [[nodiscard]] bool try_lock_shared_until(Deadline deadline);
115
116 private:
117 bool HasWaitingWriter() const noexcept;
118
119 bool WaitForNoWaitingWriters(Deadline deadline);
120
121 void DecWaitingWriters();
122
123 /* Semaphore can be get by 1 or by SIZE_MAX.
124 * 1 = reader, SIZE_MAX = writer.
125 *
126 * Three possible cases:
127 * 1) semaphore is free
128 * 2) there are readers in critical section (any count)
129 * 3) there is a single writer in critical section
130 */
131 Semaphore semaphore_;
132
133 /* Readers don't try to hold semaphore_ if there is at least one
134 * waiting writer => writers don't starve.
135 */
136 std::atomic_size_t waiting_writers_count_;
137 Mutex waiting_writers_count_mutex_;
138 ConditionVariable waiting_writers_count_cv_;
139};
140
141template <typename Rep, typename Period>
142bool SharedMutex::try_lock_for(
143 const std::chrono::duration<Rep, Period>& duration) {
144 return try_lock_until(Deadline::FromDuration(duration));
145}
146
147template <typename Rep, typename Period>
148bool SharedMutex::try_lock_shared_for(
149 const std::chrono::duration<Rep, Period>& duration) {
150 return try_lock_shared_until(Deadline::FromDuration(duration));
151}
152
153template <typename Clock, typename Duration>
154bool SharedMutex::try_lock_until(
155 const std::chrono::time_point<Clock, Duration>& until) {
156 return try_lock_until(Deadline::FromTimePoint(until));
157}
158
159template <typename Clock, typename Duration>
160bool SharedMutex::try_lock_shared_until(
161 const std::chrono::time_point<Clock, Duration>& until) {
162 return try_lock_shared_until(Deadline::FromTimePoint(until));
163}
164
165} // namespace engine
166
167USERVER_NAMESPACE_END