userver: userver/engine/shared_mutex.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
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