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 {
30public:
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(const std::chrono::time_point<Clock, Duration>&);
72
73 /// @overload
74 [[nodiscard]] bool try_lock_until(Deadline deadline);
75
76 /// Locks the mutex for shared ownership. Blocks current coroutine if the
77 /// mutex is locked by another coroutine for reading or writing.
78 ///
79 /// @note The method waits for the mutex even if the current task is
80 /// cancelled.
82
83 /// Unlocks the mutex for shared ownership. Before calling this method the
84 /// mutex should be locked for shared ownership by current coroutine.
85 ///
86 /// @note the order of coroutines to unblock is unspecified. Any code assuming
87 /// any specific order (e.g. FIFO) is incorrect and should be fixed.
89
90 /// Tries to lock the mutex for shared ownership without blocking the
91 /// coroutine, returns true if succeeded.
92 [[nodiscard]] bool try_lock_shared();
93
94 /// Tries to lock the mutex for shared ownership in specified duration.
95 /// Blocks current coroutine if
96 /// the mutex is locked by another coroutine up to the provided duration.
97 ///
98 /// @returns true if the locking succeeded
99 template <typename Rep, typename Period>
100 [[nodiscard]] bool try_lock_shared_for(const std::chrono::duration<Rep, Period>&);
101
102 /// Tries to lock the mutex for shared ownership till specified time point.
103 /// Blocks current coroutine if
104 /// the mutex is locked by another coroutine up to the provided duration.
105 ///
106 /// @returns true if the locking succeeded
107 template <typename Clock, typename Duration>
108 [[nodiscard]] bool try_lock_shared_until(const std::chrono::time_point<Clock, Duration>&);
109
110 /// @overload
111 [[nodiscard]] bool try_lock_shared_until(Deadline deadline);
112
113private:
114 bool HasWaitingWriter() const noexcept;
115
116 bool WaitForNoWaitingWriters(Deadline deadline);
117
118 void DecWaitingWriters();
119
120 /* Semaphore can be get by 1 or by SIZE_MAX.
121 * 1 = reader, SIZE_MAX = writer.
122 *
123 * Three possible cases:
124 * 1) semaphore is free
125 * 2) there are readers in critical section (any count)
126 * 3) there is a single writer in critical section
127 */
128 Semaphore semaphore_;
129
130 /* Readers don't try to hold semaphore_ if there is at least one
131 * waiting writer => writers don't starve.
132 */
133 std::atomic_size_t waiting_writers_count_;
134 Mutex waiting_writers_count_mutex_;
135 ConditionVariable waiting_writers_count_cv_;
136};
137
138template <typename Rep, typename Period>
139bool SharedMutex::try_lock_for(const std::chrono::duration<Rep, Period>& duration) {
140 return try_lock_until(Deadline::FromDuration(duration));
141}
142
143template <typename Rep, typename Period>
144bool SharedMutex::try_lock_shared_for(const std::chrono::duration<Rep, Period>& duration) {
145 return try_lock_shared_until(Deadline::FromDuration(duration));
146}
147
148template <typename Clock, typename Duration>
149bool SharedMutex::try_lock_until(const std::chrono::time_point<Clock, Duration>& until) {
150 return try_lock_until(Deadline::FromTimePoint(until));
151}
152
153template <typename Clock, typename Duration>
154bool SharedMutex::try_lock_shared_until(const std::chrono::time_point<Clock, Duration>& until) {
155 return try_lock_shared_until(Deadline::FromTimePoint(until));
156}
157
158} // namespace engine
159
160USERVER_NAMESPACE_END