]>
Commit | Line | Data |
---|---|---|
11fdf7f2 TL |
1 | /* |
2 | * This file is open source software, licensed to you under the terms | |
3 | * of the Apache License, Version 2.0 (the "License"). See the NOTICE file | |
4 | * distributed with this work for additional information regarding copyright | |
5 | * ownership. You may not use this file except in compliance with the License. | |
6 | * | |
7 | * You may obtain a copy of the License at | |
8 | * | |
9 | * http://www.apache.org/licenses/LICENSE-2.0 | |
10 | * | |
11 | * Unless required by applicable law or agreed to in writing, | |
12 | * software distributed under the License is distributed on an | |
13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
14 | * KIND, either express or implied. See the License for the | |
15 | * specific language governing permissions and limitations | |
16 | * under the License. | |
17 | */ | |
18 | /* | |
19 | * Copyright (C) 2015 Cloudius Systems, Ltd. | |
20 | */ | |
21 | ||
22 | #pragma once | |
23 | ||
24 | #include <seastar/core/future.hh> | |
20effc67 | 25 | #include <seastar/core/chunked_fifo.hh> |
11fdf7f2 TL |
26 | |
27 | namespace seastar { | |
28 | ||
29 | /// \addtogroup fiber-module | |
30 | /// @{ | |
31 | ||
32 | /// \brief Shared/exclusive mutual exclusion. | |
33 | /// | |
34 | /// Similar to \c std::shared_mutex, this class provides protection | |
35 | /// for a shared resource, with two levels of access protection: shared | |
36 | /// and exclusive. Shared access allows multiple tasks to access the | |
37 | /// shared resource concurrently, while exclusive access allows just | |
38 | /// one task to access the resource at a time. | |
39 | /// | |
40 | /// Note that many seastar tasks do not require protection at all, | |
41 | /// since the seastar scheduler is not preemptive; however tasks that do | |
42 | /// (by waiting on a future) may require explicit locking. | |
43 | /// | |
44 | /// The \ref with_shared(shared_mutex&, Func&&) and | |
45 | /// \ref with_lock(shared_mutex&, Func&&) provide exception-safe | |
46 | /// wrappers for use with \c shared_mutex. | |
47 | /// | |
48 | /// \see semaphore simpler mutual exclusion | |
49 | class shared_mutex { | |
50 | unsigned _readers = 0; | |
51 | bool _writer = false; | |
52 | struct waiter { | |
53 | waiter(promise<>&& pr, bool for_write) : pr(std::move(pr)), for_write(for_write) {} | |
54 | promise<> pr; | |
55 | bool for_write; | |
56 | }; | |
20effc67 | 57 | chunked_fifo<waiter> _waiters; |
11fdf7f2 TL |
58 | public: |
59 | shared_mutex() = default; | |
60 | shared_mutex(shared_mutex&&) = default; | |
61 | shared_mutex& operator=(shared_mutex&&) = default; | |
62 | shared_mutex(const shared_mutex&) = delete; | |
63 | void operator=(const shared_mutex&) = delete; | |
64 | /// Lock the \c shared_mutex for shared access | |
65 | /// | |
66 | /// \return a future that becomes ready when no exclusive access | |
67 | /// is granted to anyone. | |
20effc67 | 68 | future<> lock_shared() noexcept { |
f67539c2 | 69 | if (try_lock_shared()) { |
11fdf7f2 TL |
70 | return make_ready_future<>(); |
71 | } | |
20effc67 TL |
72 | try { |
73 | _waiters.emplace_back(promise<>(), false); | |
74 | return _waiters.back().pr.get_future(); | |
75 | } catch (...) { | |
76 | return current_exception_as_future(); | |
77 | } | |
11fdf7f2 | 78 | } |
f67539c2 TL |
79 | /// Try to lock the \c shared_mutex for shared access |
80 | /// | |
81 | /// \return true iff could acquire the lock for shared access. | |
82 | bool try_lock_shared() noexcept { | |
83 | if (!_writer && _waiters.empty()) { | |
84 | ++_readers; | |
85 | return true; | |
86 | } | |
87 | return false; | |
88 | } | |
11fdf7f2 | 89 | /// Unlocks a \c shared_mutex after a previous call to \ref lock_shared(). |
20effc67 | 90 | void unlock_shared() noexcept { |
f67539c2 | 91 | assert(_readers > 0); |
11fdf7f2 TL |
92 | --_readers; |
93 | wake(); | |
94 | } | |
95 | /// Lock the \c shared_mutex for exclusive access | |
96 | /// | |
97 | /// \return a future that becomes ready when no access, shared or exclusive | |
98 | /// is granted to anyone. | |
20effc67 | 99 | future<> lock() noexcept { |
f67539c2 | 100 | if (try_lock()) { |
11fdf7f2 TL |
101 | return make_ready_future<>(); |
102 | } | |
20effc67 TL |
103 | try { |
104 | _waiters.emplace_back(promise<>(), true); | |
105 | return _waiters.back().pr.get_future(); | |
106 | } catch (...) { | |
107 | return current_exception_as_future(); | |
108 | } | |
11fdf7f2 | 109 | } |
f67539c2 TL |
110 | /// Try to lock the \c shared_mutex for exclusive access |
111 | /// | |
112 | /// \return true iff could acquire the lock for exclusive access. | |
113 | bool try_lock() noexcept { | |
114 | if (!_readers && !_writer) { | |
115 | _writer = true; | |
116 | return true; | |
117 | } | |
118 | return false; | |
119 | } | |
11fdf7f2 | 120 | /// Unlocks a \c shared_mutex after a previous call to \ref lock(). |
20effc67 | 121 | void unlock() noexcept { |
f67539c2 | 122 | assert(_writer); |
11fdf7f2 TL |
123 | _writer = false; |
124 | wake(); | |
125 | } | |
126 | private: | |
20effc67 | 127 | void wake() noexcept { |
11fdf7f2 TL |
128 | while (!_waiters.empty()) { |
129 | auto& w = _waiters.front(); | |
130 | // note: _writer == false in wake() | |
131 | if (w.for_write) { | |
132 | if (!_readers) { | |
133 | _writer = true; | |
134 | w.pr.set_value(); | |
135 | _waiters.pop_front(); | |
136 | } | |
137 | break; | |
138 | } else { // for read | |
139 | ++_readers; | |
140 | w.pr.set_value(); | |
141 | _waiters.pop_front(); | |
142 | } | |
143 | } | |
144 | } | |
145 | }; | |
146 | ||
147 | /// Executes a function while holding shared access to a resource. | |
148 | /// | |
149 | /// Executes a function while holding shared access to a resource. When | |
150 | /// the function returns, the mutex is automatically unlocked. | |
151 | /// | |
152 | /// \param sm a \ref shared_mutex guarding access to the shared resource | |
153 | /// \param func callable object to invoke while the mutex is held for shared access | |
154 | /// \return whatever \c func returns, as a future | |
155 | /// | |
156 | /// \relates shared_mutex | |
157 | template <typename Func> | |
20effc67 TL |
158 | SEASTAR_CONCEPT( |
159 | requires (std::invocable<Func> && std::is_nothrow_move_constructible_v<Func>) | |
160 | inline | |
161 | futurize_t<std::invoke_result_t<Func>> | |
162 | ) | |
163 | SEASTAR_NO_CONCEPT( | |
164 | inline | |
165 | std::enable_if_t<std::is_nothrow_move_constructible_v<Func>, futurize_t<std::result_of_t<Func ()>>> | |
166 | ) | |
167 | with_shared(shared_mutex& sm, Func&& func) noexcept { | |
f67539c2 TL |
168 | return sm.lock_shared().then([&sm, func = std::forward<Func>(func)] () mutable { |
169 | return futurize_invoke(func).finally([&sm] { | |
170 | sm.unlock_shared(); | |
171 | }); | |
11fdf7f2 TL |
172 | }); |
173 | } | |
174 | ||
20effc67 TL |
175 | template <typename Func> |
176 | SEASTAR_CONCEPT( | |
177 | requires (std::invocable<Func> && !std::is_nothrow_move_constructible_v<Func>) | |
178 | inline | |
179 | futurize_t<std::invoke_result_t<Func>> | |
180 | ) | |
181 | SEASTAR_NO_CONCEPT( | |
182 | inline | |
183 | std::enable_if_t<!std::is_nothrow_move_constructible_v<Func>, futurize_t<std::result_of_t<Func ()>>> | |
184 | ) | |
185 | with_shared(shared_mutex& sm, Func&& func) noexcept { | |
186 | // FIXME: use a coroutine when c++17 support is dropped | |
187 | try { | |
188 | return do_with(std::forward<Func>(func), [&sm] (Func& func) { | |
189 | return sm.lock_shared().then([&func] { | |
190 | return func(); | |
191 | }).finally([&sm] { | |
192 | sm.unlock_shared(); | |
193 | }); | |
194 | }); | |
195 | } catch (...) { | |
1e59de90 | 196 | return futurize<std::invoke_result_t<Func>>::current_exception_as_future(); |
20effc67 TL |
197 | } |
198 | } | |
199 | ||
11fdf7f2 TL |
200 | /// Executes a function while holding exclusive access to a resource. |
201 | /// | |
202 | /// Executes a function while holding exclusive access to a resource. When | |
203 | /// the function returns, the mutex is automatically unlocked. | |
204 | /// | |
205 | /// \param sm a \ref shared_mutex guarding access to the shared resource | |
206 | /// \param func callable object to invoke while the mutex is held for shared access | |
207 | /// \return whatever \c func returns, as a future | |
208 | /// | |
209 | /// \relates shared_mutex | |
210 | template <typename Func> | |
20effc67 TL |
211 | SEASTAR_CONCEPT( |
212 | requires (std::invocable<Func> && std::is_nothrow_move_constructible_v<Func>) | |
213 | inline | |
214 | futurize_t<std::invoke_result_t<Func>> | |
215 | ) | |
216 | SEASTAR_NO_CONCEPT( | |
217 | inline | |
218 | std::enable_if_t<std::is_nothrow_move_constructible_v<Func>, futurize_t<std::result_of_t<Func ()>>> | |
219 | ) | |
220 | with_lock(shared_mutex& sm, Func&& func) noexcept { | |
f67539c2 TL |
221 | return sm.lock().then([&sm, func = std::forward<Func>(func)] () mutable { |
222 | return futurize_invoke(func).finally([&sm] { | |
223 | sm.unlock(); | |
224 | }); | |
11fdf7f2 TL |
225 | }); |
226 | } | |
227 | ||
20effc67 TL |
228 | |
229 | template <typename Func> | |
230 | SEASTAR_CONCEPT( | |
231 | requires (std::invocable<Func> && !std::is_nothrow_move_constructible_v<Func>) | |
232 | inline | |
233 | futurize_t<std::invoke_result_t<Func>> | |
234 | ) | |
235 | SEASTAR_NO_CONCEPT( | |
236 | inline | |
237 | std::enable_if_t<!std::is_nothrow_move_constructible_v<Func>, futurize_t<std::result_of_t<Func ()>>> | |
238 | ) | |
239 | with_lock(shared_mutex& sm, Func&& func) noexcept { | |
240 | // FIXME: use a coroutine when c++17 support is dropped | |
241 | try { | |
242 | return do_with(std::forward<Func>(func), [&sm] (Func& func) { | |
243 | return sm.lock().then([&func] { | |
244 | return func(); | |
245 | }).finally([&sm] { | |
246 | sm.unlock(); | |
247 | }); | |
248 | }); | |
249 | } catch (...) { | |
1e59de90 | 250 | return futurize<std::invoke_result_t<Func>>::current_exception_as_future(); |
20effc67 TL |
251 | } |
252 | } | |
253 | ||
11fdf7f2 TL |
254 | /// @} |
255 | ||
256 | } |