Seastar
High performance C++ framework for concurrent servers
gate.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 2014 Cloudius Systems
20 */
21
22#pragma once
23
24#ifndef SEASTAR_MODULE
25#include <boost/intrusive/list.hpp>
26
27#include <seastar/core/future.hh>
28#include <seastar/util/assert.hh>
29#include <seastar/util/std-compat.hh>
30#include <seastar/util/modules.hh>
31#include <cassert>
32#include <exception>
33#include <optional>
34#include <utility>
35#endif
36
37#ifdef SEASTAR_DEBUG
38#define SEASTAR_GATE_HOLDER_DEBUG
39#endif
40
41namespace seastar {
42
45
48SEASTAR_MODULE_EXPORT
49class gate_closed_exception : public std::exception {
50public:
51 virtual const char* what() const noexcept override {
52 return "gate closed";
53 }
54};
55
58SEASTAR_MODULE_EXPORT
60 static constexpr const char* _default_what = "named gate closed";
61 sstring _what;
62public:
63 named_gate_closed_exception(const sstring& name) noexcept : gate_closed_exception() {
64 if (!name.empty()) {
65 try {
66 _what = fmt::format("{} gate closed", name);
67 } catch (...) {
68 // Ignore
69 }
70 }
71 }
72 virtual const char* what() const noexcept override {
73 return _what.empty() ? _default_what : _what.c_str();
74 }
75};
76
82SEASTAR_MODULE_EXPORT
83class gate {
84 size_t _count = 0;
85 std::optional<promise<>> _stopped;
86
87#ifdef SEASTAR_GATE_HOLDER_DEBUG
88 void assert_not_held_when_moved() const noexcept;
89 void assert_not_held_when_destroyed() const noexcept;
90#else // SEASTAR_GATE_HOLDER_DEBUG
91 void assert_not_held_when_moved() const noexcept {}
92 void assert_not_held_when_destroyed() const noexcept {}
93#endif // SEASTAR_GATE_HOLDER_DEBUG
94
95public:
96 // Implemented to force noexcept due to boost:intrusive::list
97 gate() noexcept {};
98 gate(const gate&) = delete;
99 gate(gate&& x) noexcept
100 : _count(std::exchange(x._count, 0)), _stopped(std::exchange(x._stopped, std::nullopt)) {
101 x.assert_not_held_when_moved();
102 }
103 gate& operator=(gate&& x) noexcept {
104 if (this != &x) {
105 SEASTAR_ASSERT(!_count && "gate reassigned with outstanding requests");
106 x.assert_not_held_when_moved();
107 _count = std::exchange(x._count, 0);
108 _stopped = std::exchange(x._stopped, std::nullopt);
109 }
110 return *this;
111 }
112 ~gate() {
113 SEASTAR_ASSERT(!_count && "gate destroyed with outstanding requests");
114 assert_not_held_when_destroyed();
115 }
120 bool try_enter() noexcept {
121 bool opened = !_stopped;
122 if (opened) {
123 ++_count;
124 }
125 return opened;
126 }
131 void enter() {
132 if (!try_enter()) {
133 throw_closed_exception();
134 }
135 }
140 void leave() noexcept {
141 --_count;
142 if (!_count && _stopped) {
143 _stopped->set_value();
144 }
145 }
155 void check() const {
156 if (_stopped) {
157 throw_closed_exception();
158 }
159 }
165 future<> close() noexcept {
166 SEASTAR_ASSERT(!_stopped && "seastar::gate::close() cannot be called more than once");
167 _stopped = std::make_optional(promise<>());
168 if (!_count) {
169 _stopped->set_value();
170 }
171 return _stopped->get_future();
172 }
173
175 size_t get_count() const noexcept {
176 return _count;
177 }
178
180 bool is_closed() const noexcept {
181 return bool(_stopped);
182 }
183
193 class holder {
194 gate* _g;
195#ifdef SEASTAR_GATE_HOLDER_DEBUG
196 using member_hook_t = boost::intrusive::list_member_hook<boost::intrusive::link_mode<boost::intrusive::auto_unlink>>;
197 member_hook_t _link;
198
199 void debug_hold_gate() noexcept {
200 if (_g) {
201 _g->_holders.push_back(*this);
202 }
203 }
204
205 void debug_release_gate() noexcept {
206 _link.unlink();
207 }
208#else // SEASTAR_GATE_HOLDER_DEBUG
209 void debug_hold_gate() noexcept {}
210 void debug_release_gate() noexcept {}
211#endif // SEASTAR_GATE_HOLDER_DEBUG
212
213 // release the holder from the gate without leaving it.
214 // used for release and move.
215 gate* release_gate() noexcept {
216 auto* g = std::exchange(_g, nullptr);
217 if (g) {
218 debug_release_gate();
219 }
220 return g;
221 }
222
223 friend class gate;
224 public:
227 holder() noexcept : _g(nullptr) { }
228
231 explicit holder(gate& g) : _g(&g) {
232 _g->enter();
233 debug_hold_gate();
234 }
235
241 holder(const holder& x) noexcept : _g(x._g) {
242 if (_g) {
243 _g->_count++;
244 debug_hold_gate();
245 }
246 }
247
251 holder(holder&& x) noexcept : _g(std::move(x).release_gate()) {
252 debug_hold_gate();
253 }
254
257 release();
258 }
259
266 holder& operator=(const holder& x) noexcept {
267 if (x._g != _g) {
268 release();
269 _g = x._g;
270 if (_g) {
271 _g->_count++;
272 debug_hold_gate();
273 }
274 }
275 return *this;
276 }
277
282 holder& operator=(holder&& x) noexcept {
283 if (&x != this) {
284 release();
285 _g = std::move(x).release_gate();
286 debug_hold_gate();
287 }
288 return *this;
289 }
290
292 void release() noexcept {
293 if (auto g = release_gate()) {
294 g->leave();
295 }
296 }
297 };
298
302 return holder(*this);
303 }
304
308 std::optional<holder> try_hold() noexcept {
309 return is_closed() ? std::nullopt : std::make_optional<holder>(*this);
310 }
311
312protected:
313 virtual std::exception_ptr make_closed_exception() const {
314 return std::make_exception_ptr(gate_closed_exception{});
315 }
316
317 template <typename Func>
318 friend auto try_with_gate(gate&, Func&&) noexcept;
319private:
320#ifdef SEASTAR_GATE_HOLDER_DEBUG
321 using holders_list_t = boost::intrusive::list<holder,
322 boost::intrusive::member_hook<holder, holder::member_hook_t, &holder::_link>,
323 boost::intrusive::constant_time_size<false>>;
324
325 holders_list_t _holders;
326#endif // SEASTAR_GATE_HOLDER_DEBUG
327
328 [[noreturn]] void throw_closed_exception() const {
329 std::rethrow_exception(make_closed_exception());
330 }
331};
332
333#ifdef SEASTAR_GATE_HOLDER_DEBUG
334SEASTAR_MODULE_EXPORT
335inline void gate::assert_not_held_when_moved() const noexcept {
336 SEASTAR_ASSERT(_holders.empty() && "gate moved with outstanding holders");
337}
338inline void gate::assert_not_held_when_destroyed() const noexcept {
339 SEASTAR_ASSERT(_holders.empty() && "gate destroyed with outstanding holders");
340}
341#endif // SEASTAR_GATE_HOLDER_DEBUG
342
343class named_gate : public gate {
344 sstring _name;
345
346public:
347 named_gate() = default;
348 explicit named_gate(sstring name) noexcept
349 : gate()
350 , _name(std::move(name))
351 {}
352 named_gate(named_gate&&) = default;
353 named_gate& operator=(named_gate&&) = default;
354
355protected:
356 virtual std::exception_ptr make_closed_exception() const override {
357 return std::make_exception_ptr(named_gate_closed_exception(_name));
358 }
359};
360
361namespace internal {
362
363template <typename Func>
364inline
365auto
366invoke_func_with_gate(gate::holder&& gh, Func&& func) noexcept {
367 return futurize_invoke(std::forward<Func>(func)).finally([gh = std::forward<gate::holder>(gh)] {});
368}
369
370} // namespace internal
371
380SEASTAR_MODULE_EXPORT
381template <typename Func>
382inline
383auto
384with_gate(gate& g, Func&& func) {
385 return internal::invoke_func_with_gate(g.hold(), std::forward<Func>(func));
386}
387
399SEASTAR_MODULE_EXPORT
400template <typename Func>
401inline
402auto
403try_with_gate(gate& g, Func&& func) noexcept {
404 if (g.is_closed()) {
405 using futurator = futurize<std::invoke_result_t<Func>>;
406 return futurator::make_exception_future(g.make_closed_exception());
407 }
408 return internal::invoke_func_with_gate(g.hold(), std::forward<Func>(func));
409}
411
412
413}
A representation of a possibly not-yet-computed value.
Definition: future.hh:1195
Definition: gate.hh:193
holder(gate &g)
Definition: gate.hh:231
~holder()
Destroy a holder and leave the referenced gate.
Definition: gate.hh:256
holder & operator=(const holder &x) noexcept
Definition: gate.hh:266
holder(holder &&x) noexcept
Definition: gate.hh:251
holder(const holder &x) noexcept
Definition: gate.hh:241
void release() noexcept
Leave the held gate.
Definition: gate.hh:292
holder() noexcept
Definition: gate.hh:227
holder & operator=(holder &&x) noexcept
Definition: gate.hh:282
Definition: gate.hh:49
Definition: gate.hh:83
bool try_enter() noexcept
Definition: gate.hh:120
bool is_closed() const noexcept
Returns whether the gate is closed.
Definition: gate.hh:180
future close() noexcept
Definition: gate.hh:165
size_t get_count() const noexcept
Returns a current number of registered in-progress requests.
Definition: gate.hh:175
void check() const
Definition: gate.hh:155
std::optional< holder > try_hold() noexcept
Definition: gate.hh:308
holder hold()
Definition: gate.hh:301
void enter()
Definition: gate.hh:131
void leave() noexcept
Definition: gate.hh:140
Definition: gate.hh:343
auto with_gate(gate &g, Func &&func)
Definition: gate.hh:384
auto try_with_gate(gate &g, Func &&func) noexcept
Definition: gate.hh:403
future< T > make_exception_future(std::exception_ptr &&value) noexcept
Creates a future in an available, failed state.
Definition: future.hh:1873
Seastar API namespace.
Definition: abort_on_ebadf.hh:26
Converts a type to a future type, if it isn't already.
Definition: future.hh:1784