Seastar
High performance C++ framework for concurrent servers
shared_mutex.hh
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#ifndef SEASTAR_MODULE
25#include <seastar/core/coroutine.hh>
26#include <seastar/core/future.hh>
27#include <seastar/core/chunked_fifo.hh>
28#include <seastar/util/modules.hh>
29#include <cassert>
30#include <mutex>
31#include <utility>
32#include <shared_mutex>
33#endif
34
35namespace seastar {
36
37SEASTAR_MODULE_EXPORT_BEGIN
38
41
60 unsigned _readers = 0;
61 bool _writer = false;
62 struct waiter {
63 waiter(promise<>&& pr, bool for_write) : pr(std::move(pr)), for_write(for_write) {}
64 promise<> pr;
65 bool for_write;
66 };
67 chunked_fifo<waiter> _waiters;
68public:
69 shared_mutex() = default;
70 shared_mutex(shared_mutex&&) = default;
71 shared_mutex& operator=(shared_mutex&&) = default;
72 shared_mutex(const shared_mutex&) = delete;
73 void operator=(const shared_mutex&) = delete;
78 future<> lock_shared() noexcept {
79 if (try_lock_shared()) {
80 return make_ready_future<>();
81 }
82 try {
83 _waiters.emplace_back(promise<>(), false);
84 return _waiters.back().pr.get_future();
85 } catch (...) {
87 }
88 }
92 bool try_lock_shared() noexcept {
93 if (!_writer && _waiters.empty()) {
94 ++_readers;
95 return true;
96 }
97 return false;
98 }
100 void unlock_shared() noexcept {
101 assert(_readers > 0);
102 --_readers;
103 wake();
104 }
109 future<> lock() noexcept {
110 if (try_lock()) {
111 return make_ready_future<>();
112 }
113 try {
114 _waiters.emplace_back(promise<>(), true);
115 return _waiters.back().pr.get_future();
116 } catch (...) {
118 }
119 }
123 bool try_lock() noexcept {
124 if (!_readers && !_writer) {
125 _writer = true;
126 return true;
127 }
128 return false;
129 }
131 void unlock() noexcept {
132 assert(_writer);
133 _writer = false;
134 wake();
135 }
136private:
137 void wake() noexcept {
138 while (!_waiters.empty()) {
139 auto& w = _waiters.front();
140 // note: _writer == false in wake()
141 if (w.for_write) {
142 if (!_readers) {
143 _writer = true;
144 w.pr.set_value();
145 _waiters.pop_front();
146 }
147 break;
148 } else { // for read
149 ++_readers;
150 w.pr.set_value();
151 _waiters.pop_front();
152 }
153 }
154 }
155};
156
167template <std::invocable Func>
168 requires std::is_nothrow_move_constructible_v<Func>
169 inline
170 futurize_t<std::invoke_result_t<Func>>
171with_shared(shared_mutex& sm, Func&& func) noexcept {
172 return sm.lock_shared().then([&sm, func = std::forward<Func>(func)] () mutable {
173 return futurize_invoke(func).finally([&sm] {
174 sm.unlock_shared();
175 });
176 });
177}
178
179template <std::invocable Func>
180 requires (!std::is_nothrow_move_constructible_v<Func>)
181 inline
182 futurize_t<std::invoke_result_t<Func>>
183with_shared(shared_mutex& sm, Func&& func) noexcept {
184 // FIXME: use a coroutine when c++17 support is dropped
185 try {
186 return do_with(std::forward<Func>(func), [&sm] (Func& func) {
187 return sm.lock_shared().then([&func] {
188 return func();
189 }).finally([&sm] {
190 sm.unlock_shared();
191 });
192 });
193 } catch (...) {
194 return futurize<std::invoke_result_t<Func>>::current_exception_as_future();
195 }
196}
197
208template <std::invocable Func>
209 requires std::is_nothrow_move_constructible_v<Func>
210 inline
211 futurize_t<std::invoke_result_t<Func>>
212with_lock(shared_mutex& sm, Func&& func) noexcept {
213 return sm.lock().then([&sm, func = std::forward<Func>(func)] () mutable {
214 return futurize_invoke(func).finally([&sm] {
215 sm.unlock();
216 });
217 });
218}
219
220
221template <std::invocable Func>
222 requires (!std::is_nothrow_move_constructible_v<Func>)
223 inline
224 futurize_t<std::invoke_result_t<Func>>
225with_lock(shared_mutex& sm, Func&& func) noexcept {
226 // FIXME: use a coroutine when c++17 support is dropped
227 try {
228 return do_with(std::forward<Func>(func), [&sm] (Func& func) {
229 return sm.lock().then([&func] {
230 return func();
231 }).finally([&sm] {
232 sm.unlock();
233 });
234 });
235 } catch (...) {
236 return futurize<std::invoke_result_t<Func>>::current_exception_as_future();
237 }
238}
239
240namespace internal {
241
244template <typename T>
245concept FutureBasicLockable = requires (T& t) {
246 { t.lock() } -> std::same_as<future<>>;
247 { t.unlock() } noexcept -> std::same_as<void>;
248};
249
255template <typename T>
256concept FutureBasicSharedLockable = requires (T& t) {
257 { t.lock_shared() } -> std::same_as<future<>>;
258 { t.unlock_shared() } noexcept -> std::same_as<void>;
259};
260
261} // namespace internal
262
271template <internal::FutureBasicSharedLockable T>
273 co_await t.lock_shared();
274
275 try {
276 co_return std::shared_lock<T>{t, std::adopt_lock_t{}};
277 } catch (...) {
278 t.unlock_shared();
279 throw;
280 }
281}
282
291template <internal::FutureBasicLockable T>
293 co_await t.lock();
294
295 try {
296 co_return std::unique_lock<T>{t, std::adopt_lock_t{}};
297 } catch (...) {
298 t.unlock();
299 throw;
300 }
301}
302
304SEASTAR_MODULE_EXPORT_END
305
306}
A representation of a possibly not-yet-computed value.
Definition: future.hh:1240
Result then(Func &&func) noexcept
Schedule a block of code to run when the future is ready.
Definition: future.hh:1425
Shared/exclusive mutual exclusion.
Definition: shared_mutex.hh:59
void unlock_shared() noexcept
Unlocks a shared_mutex after a previous call to lock_shared().
Definition: shared_mutex.hh:100
future lock() noexcept
Definition: shared_mutex.hh:109
bool try_lock() noexcept
Definition: shared_mutex.hh:123
void unlock() noexcept
Unlocks a shared_mutex after a previous call to lock().
Definition: shared_mutex.hh:131
bool try_lock_shared() noexcept
Definition: shared_mutex.hh:92
future lock_shared() noexcept
Definition: shared_mutex.hh:78
future< std::shared_lock< T > > get_shared_lock(T &t)
Construct a RAII-based shared lock corresponding to a given object.
Definition: shared_mutex.hh:272
future< std::unique_lock< T > > get_unique_lock(T &t)
Construct a RAII-based unique lock corresponding to a given object.
Definition: shared_mutex.hh:292
futurize_t< std::invoke_result_t< Func > > with_lock(shared_mutex &sm, Func &&func) noexcept
Definition: shared_mutex.hh:212
futurize_t< std::invoke_result_t< Func > > with_shared(shared_mutex &sm, Func &&func) noexcept
Definition: shared_mutex.hh:171
future< T > current_exception_as_future() noexcept
Returns std::current_exception() wrapped in a future.
Definition: future.hh:1962
auto do_with(T1 &&rv1, T2 &&rv2, More &&... more) noexcept
Definition: do_with.hh:135
auto with_lock(Lock &lock, Func &&func)
Definition: do_with.hh:149
Seastar API namespace.
Definition: abort_on_ebadf.hh:26