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/std-compat.hh>
29 #include <seastar/util/modules.hh>
30 #include <cassert>
31 #include <exception>
32 #include <optional>
33 #include <utility>
34 #endif
35 
36 #ifdef SEASTAR_DEBUG
37 #define SEASTAR_GATE_HOLDER_DEBUG
38 #endif
39 
40 namespace seastar {
41 
44 
47 SEASTAR_MODULE_EXPORT
48 class gate_closed_exception : public std::exception {
49 public:
50  virtual const char* what() const noexcept override {
51  return "gate closed";
52  }
53 };
54 
60 SEASTAR_MODULE_EXPORT
61 class gate {
62  size_t _count = 0;
63  std::optional<promise<>> _stopped;
64 
65 #ifdef SEASTAR_GATE_HOLDER_DEBUG
66  void assert_not_held_when_moved() const noexcept;
67  void assert_not_held_when_destroyed() const noexcept;
68 #else // SEASTAR_GATE_HOLDER_DEBUG
69  void assert_not_held_when_moved() const noexcept {}
70  void assert_not_held_when_destroyed() const noexcept {}
71 #endif // SEASTAR_GATE_HOLDER_DEBUG
72 
73 public:
74  // Implemented to force noexcept due to boost:intrusive::list
75  gate() noexcept {};
76  gate(const gate&) = delete;
77  gate(gate&& x) noexcept
78  : _count(std::exchange(x._count, 0)), _stopped(std::exchange(x._stopped, std::nullopt)) {
79  x.assert_not_held_when_moved();
80  }
81  gate& operator=(gate&& x) noexcept {
82  if (this != &x) {
83  assert(!_count && "gate reassigned with outstanding requests");
84  x.assert_not_held_when_moved();
85  _count = std::exchange(x._count, 0);
86  _stopped = std::exchange(x._stopped, std::nullopt);
87  }
88  return *this;
89  }
90  ~gate() {
91  assert(!_count && "gate destroyed with outstanding requests");
92  assert_not_held_when_destroyed();
93  }
98  bool try_enter() noexcept {
99  bool opened = !_stopped;
100  if (opened) {
101  ++_count;
102  }
103  return opened;
104  }
109  void enter() {
110  if (!try_enter()) {
111  throw gate_closed_exception();
112  }
113  }
118  void leave() noexcept {
119  --_count;
120  if (!_count && _stopped) {
121  _stopped->set_value();
122  }
123  }
133  void check() {
134  if (_stopped) {
135  throw gate_closed_exception();
136  }
137  }
143  future<> close() noexcept {
144  assert(!_stopped && "seastar::gate::close() cannot be called more than once");
145  _stopped = std::make_optional(promise<>());
146  if (!_count) {
147  _stopped->set_value();
148  }
149  return _stopped->get_future();
150  }
151 
153  size_t get_count() const noexcept {
154  return _count;
155  }
156 
158  bool is_closed() const noexcept {
159  return bool(_stopped);
160  }
161 
171  class holder {
172  gate* _g;
173 #ifdef SEASTAR_GATE_HOLDER_DEBUG
174  using member_hook_t = boost::intrusive::list_member_hook<boost::intrusive::link_mode<boost::intrusive::auto_unlink>>;
175  member_hook_t _link;
176 
177  void debug_hold_gate() noexcept {
178  if (_g) {
179  _g->_holders.push_back(*this);
180  }
181  }
182 
183  void debug_release_gate() noexcept {
184  _link.unlink();
185  }
186 #else // SEASTAR_GATE_HOLDER_DEBUG
187  void debug_hold_gate() noexcept {}
188  void debug_release_gate() noexcept {}
189 #endif // SEASTAR_GATE_HOLDER_DEBUG
190 
191  // release the holder from the gate without leaving it.
192  // used for release and move.
193  gate* release_gate() noexcept {
194  auto* g = std::exchange(_g, nullptr);
195  if (g) {
196  debug_release_gate();
197  }
198  return g;
199  }
200 
201  friend class gate;
202  public:
205  holder() noexcept : _g(nullptr) { }
206 
209  explicit holder(gate& g) : _g(&g) {
210  _g->enter();
211  debug_hold_gate();
212  }
213 
219  holder(const holder& x) noexcept : _g(x._g) {
220  if (_g) {
221  _g->_count++;
222  debug_hold_gate();
223  }
224  }
225 
229  holder(holder&& x) noexcept : _g(std::move(x).release_gate()) {
230  debug_hold_gate();
231  }
232 
235  release();
236  }
237 
244  holder& operator=(const holder& x) noexcept {
245  if (x._g != _g) {
246  release();
247  _g = x._g;
248  if (_g) {
249  _g->_count++;
250  debug_hold_gate();
251  }
252  }
253  return *this;
254  }
255 
260  holder& operator=(holder&& x) noexcept {
261  if (&x != this) {
262  release();
263  _g = std::move(x).release_gate();
264  debug_hold_gate();
265  }
266  return *this;
267  }
268 
270  void release() noexcept {
271  if (auto g = release_gate()) {
272  g->leave();
273  }
274  }
275  };
276 
280  return holder(*this);
281  }
282 
283 private:
284 #ifdef SEASTAR_GATE_HOLDER_DEBUG
285  using holders_list_t = boost::intrusive::list<holder,
286  boost::intrusive::member_hook<holder, holder::member_hook_t, &holder::_link>,
287  boost::intrusive::constant_time_size<false>>;
288 
289  holders_list_t _holders;
290 #endif // SEASTAR_GATE_HOLDER_DEBUG
291 };
292 
293 #ifdef SEASTAR_GATE_HOLDER_DEBUG
294 SEASTAR_MODULE_EXPORT
295 inline void gate::assert_not_held_when_moved() const noexcept {
296  assert(_holders.empty() && "gate moved with outstanding holders");
297 }
298 inline void gate::assert_not_held_when_destroyed() const noexcept {
299  assert(_holders.empty() && "gate destroyed with outstanding holders");
300 }
301 #endif // SEASTAR_GATE_HOLDER_DEBUG
302 
303 namespace internal {
304 
305 template <typename Func>
306 inline
307 auto
308 invoke_func_with_gate(gate::holder&& gh, Func&& func) noexcept {
309  return futurize_invoke(std::forward<Func>(func)).finally([gh = std::forward<gate::holder>(gh)] {});
310 }
311 
312 } // namespace internal
313 
322 SEASTAR_MODULE_EXPORT
323 template <typename Func>
324 inline
325 auto
326 with_gate(gate& g, Func&& func) {
327  return internal::invoke_func_with_gate(g.hold(), std::forward<Func>(func));
328 }
329 
341 SEASTAR_MODULE_EXPORT
342 template <typename Func>
343 inline
344 auto
345 try_with_gate(gate& g, Func&& func) noexcept {
346  if (g.is_closed()) {
347  using futurator = futurize<std::invoke_result_t<Func>>;
349  }
350  return internal::invoke_func_with_gate(g.hold(), std::forward<Func>(func));
351 }
353 
354 
355 }
Definition: gate.hh:171
holder(gate &g)
Definition: gate.hh:209
holder & operator=(holder &&x) noexcept
Definition: gate.hh:260
~holder()
Destroy a holder and leave the referenced gate.
Definition: gate.hh:234
holder(holder &&x) noexcept
Definition: gate.hh:229
holder(const holder &x) noexcept
Definition: gate.hh:219
void release() noexcept
Leave the held gate.
Definition: gate.hh:270
holder & operator=(const holder &x) noexcept
Definition: gate.hh:244
holder() noexcept
Definition: gate.hh:205
Definition: gate.hh:48
Definition: gate.hh:61
bool try_enter() noexcept
Definition: gate.hh:98
bool is_closed() const noexcept
Returns whether the gate is closed.
Definition: gate.hh:158
future close() noexcept
Definition: gate.hh:143
size_t get_count() const noexcept
Returns a current number of registered in-progress requests.
Definition: gate.hh:153
void check()
Definition: gate.hh:133
holder hold()
Definition: gate.hh:279
void enter()
Definition: gate.hh:109
void leave() noexcept
Definition: gate.hh:118
auto with_gate(gate &g, Func &&func)
Definition: gate.hh:326
auto try_with_gate(gate &g, Func &&func) noexcept
Definition: gate.hh:345
future< T > make_exception_future(std::exception_ptr &&value) noexcept
Creates a future in an available, failed state.
Definition: future.hh:1940
Seastar API namespace.
Definition: abort_on_ebadf.hh:26
Converts a type to a future type, if it isn't already.
Definition: future.hh:1843