RESTinio
connection_count_limiter.hpp
Go to the documentation of this file.
1 /*
2  * RESTinio
3  */
4 
5 /*!
6  * @file
7  * @brief Stuff related to limits of active parallel connections.
8  * @since v.0.6.12
9  */
10 
11 #pragma once
12 
13 #include <restinio/null_mutex.hpp>
14 #include <restinio/default_strands.hpp>
15 
16 #include <restinio/utils/tagged_scalar.hpp>
17 
18 #include <cstdint>
19 #include <mutex>
20 #include <utility>
21 
22 namespace restinio
23 {
24 
26 {
27 
28 //
29 // max_parallel_connections_t
30 //
32 
33 /*!
34  * @brief A kind of strict typedef for maximum count of active connections.
35  *
36  * @since v.0.6.12
37  */
40 
41 //
42 // max_active_accepts_t
43 //
45 
46 /*!
47  * @brief A kind of strict typedef for maximum count of active accepts.
48  *
49  * @since v.0.6.12
50  */
53 
54 namespace impl
55 {
56 
57 /*!
58  * @brief An interface of acceptor to be used by connection count limiters.
59  *
60  * An instance of a connection count limiter will receive a reference to the
61  * acceptor. The limiter has to call the acceptor and this interface declares
62  * methods of the acceptor that will be invoked by the limiter.
63  *
64  * The assumed working scheme is:
65  *
66  * - the acceptor calls `accept_next` for the limiter;
67  * - the limiter checks the possibility to call `accept()`. If it is possible,
68  * then the limiter calls `call_accept_now` back (right inside `accept_next`
69  * invocation). If it isn't possible, then the limiter stores the socket's
70  * slot index somewhere inside the limiter;
71  * - sometime later the limiter calls `schedule_next_accept_attempt` for the
72  * acceptor. The acceptor then should perform a new call to `accept_next` in
73  * the appropriate worker context.
74  *
75  * @since v.0.6.12
76  */
78 {
79 public:
80  /*!
81  * This method will be invoked by a limiter when there is a possibility to
82  * call accept() right now.
83  */
84  virtual void
86  //! An index of socket's slot to be used for accept().
87  std::size_t index ) noexcept = 0;
88 
89  /*!
90  * This method will be invoked by a limiter when there is no possibility to
91  * call accept() right now, but the next call to `accept_next` should be
92  * scheduled as soon as possible in the appropriate worker context.
93  *
94  * It is assumed that the acceptor will use asio::post() with a completion
95  * handler that calls the `accept_next` method of the limiter.
96  */
97  virtual void
99  //! An index of socket's slot to be used for accept().
100  std::size_t index ) noexcept = 0;
101 };
102 
103 /*!
104  * @brief Actual implementation of connection count limiter.
105  *
106  * @note
107  * This is not Copyable nor Moveable type.
108  *
109  * @tparam Mutex_Type Type of mutex to be used for protection of limiter
110  * object. It is expected to be std::mutex or null_mutex_t.
111  *
112  * @since v.0.6.12
113  */
114 template< typename Mutex_Type >
116 {
117  //! Lock object to be used.
118  Mutex_Type m_lock;
119 
120  //! Mandatory pointer to the acceptor connected with this limiter.
122 
123  /*!
124  * @brief The counter of active accept() operations.
125  *
126  * Incremented every time the acceptor_callback_iface_t::call_accept_now()
127  * is invoked. Decremented in increment_parallel_connections().
128  *
129  * @attention
130  * It seems to be a fragile scheme because if there won't be a call to
131  * increment_parallel_connections() after the invocation of
132  * acceptor_callback_iface_t::call_accept_now() the value of
133  * m_active_accepts will be incorrect. But it is hard to invent a more
134  * bulletproof solution and it seems that the missing call to
135  * increment_parallel_connections() could be only on the shutdown of the
136  * acceptor.
137  */
139 
140  /*!
141  * @brief The counter of active connections.
142  *
143  * This value is incremented in increment_parallel_connections()
144  * and decremented in decrement_parallel_connections().
145  */
147 
148  //! The limit for parallel connections.
150 
151  /*!
152  * @brief The storage for holding pending socket's slots.
153  *
154  * @note
155  * This storage is used as stack: new indexes are added to the
156  * end and are got from the end of the vector (LIFO working scheme).
157  *
158  * @attention
159  * The capacity for that storage is preallocated in the constructor
160  * so we don't expect any allocations during the usage of
161  * m_pending_indexes. This allows accept_next() method to be
162  * noexcept. But this works only if max_pending_indexes passed
163  * to the constructor is right.
164  */
166 
168  bool
169  has_free_slots() const noexcept
170  {
172  }
173 
174 public:
179  : m_acceptor{ acceptor }
181  {
183  }
184 
185  actual_limiter_t( const actual_limiter_t & ) = delete;
186  actual_limiter_t( actual_limiter_t && ) = delete;
187 
188  void
190  {
192 
193  // Expects that m_active_accepts is always greater than 0.
195 
196  ++m_connections;
197  }
198 
199  // Note: this method is noexcept because it can be called from
200  // destructors.
201  void
203  {
204  // Decrement active connections under acquired lock.
205  // If the count of connections drops below the limit and
206  // there are some pending indexes then one of them will
207  // be returned (wrapped into an optional).
208  auto index_to_activate = [this]() -> optional_t<std::size_t> {
210 
211  // Expects that m_connections is always greater than 0.
212  --m_connections;
213 
215  {
218  return pending_index;
219  }
220  else
221  return nullopt;
222  }();
223 
224  if( index_to_activate )
225  {
227  }
228  }
229 
230  /*!
231  * This method either calls acceptor_callback_iface_t::call_accept_now() (in
232  * that case m_active_accepts is incremented) or stores @a index into the
233  * internal storage.
234  */
235  void
236  accept_next( std::size_t index ) noexcept
237  {
238  // Perform all operations under acquired lock.
239  // The result is a flag that tells can accept() be called right now.
240  const bool accept_now = [this, index]() -> bool {
242 
243  if( has_free_slots() )
244  {
246  return true;
247  }
248  else
249  {
251  return false;
252  }
253  }();
254 
255  if( accept_now )
256  {
258  }
259  }
260 };
261 
262 } /* namespace impl */
263 
264 /*!
265  * @brief An implementation of connection count limiter for the case
266  * when connection count is not limited.
267  *
268  * @since v.0.6.12
269  */
271 {
273 
274 public:
276  not_null_pointer_t< connection_count_limits::impl::acceptor_callback_iface_t > acceptor,
277  max_parallel_connections_t /*max_parallel_connections*/,
278  max_active_accepts_t /*max_pending_indexes*/ )
279  : m_acceptor{ acceptor }
280  {
281  }
282 
283  void
284  increment_parallel_connections() noexcept { /* Nothing to do */ }
285 
286  void
287  decrement_parallel_connections() noexcept { /* Nothing to do */ }
288 
289  /*!
290  * Calls acceptor_callback_iface_t::call_accept_now() directly.
291  * The @a index is never stored anywhere.
292  */
293  void
294  accept_next( std::size_t index ) noexcept
295  {
296  m_acceptor->call_accept_now( index );
297  }
298 };
299 
300 /*!
301  * @brief Template class for connection count limiter for the case when
302  * connection count limit is actually used.
303  *
304  * The actual implementation will be provided by specializations of
305  * that class for specific Strand types.
306  *
307  * @since v.0.6.12
308  */
309 template< typename Strand >
310 class connection_count_limiter_t;
311 
312 /*!
313  * @brief Implementation of connection count limiter for single-threading
314  * mode.
315  *
316  * In single-threading mode there is no need to protect limiter from
317  * access from different threads. So null_mutex_t is used.
318  *
319  * @since v.0.6.12
320  */
321 template<>
324 {
326 
327 public:
328  using base_t::base_t;
329 };
330 
331 /*!
332  * @brief Implementation of connection count limiter for multi-threading
333  * mode.
334  *
335  * In multi-threading mode std::mutex is used for the protection of
336  * limiter object.
337  *
338  * @since v.0.6.12
339  */
340 template<>
343 {
345 
346 public:
347  using base_t::base_t;
348 };
349 
350 /*!
351  * @brief Helper type for controlling the lifetime of the connection.
352  *
353  * Connection count limiter should be informed when a new connection
354  * created and when an existing connection is closed. An instance
355  * of connection_lifetime_monitor_t should be used for that purpose:
356  * a new instance of connection_lifetime_monitor_t should be created
357  * and bound to a connection object. The constructor of
358  * connection_lifetime_monitor_t will inform the limiter about
359  * the creation of a new connection. The destructor of
360  * connection_lifetime_monitor_t will inform the limiter about the
361  * destruction of a connection.
362  *
363  * @note
364  * This type is not Copyable but Movabale.
365  *
366  * @attention
367  * The pointer to Count_Manager passed to the constructor should
368  * remain valid the whole lifetime of connection_lifetime_monitor_t
369  * instance.
370  *
371  * @since v.0.6.12
372  */
373 template< typename Count_Manager >
375 {
377 
378 public:
380  not_null_pointer_t< Count_Manager > manager ) noexcept
381  : m_manager{ manager }
382  {
384  }
385 
387  {
388  if( m_manager )
390  }
391 
393  const connection_lifetime_monitor_t & ) = delete;
394 
395  friend void
398  connection_lifetime_monitor_t & b ) noexcept
399  {
400  using std::swap;
401  swap( a.m_manager, b.m_manager );
402  }
403 
405  connection_lifetime_monitor_t && other ) noexcept
407  {
408  other.m_manager = nullptr;
409  }
410 
413  {
415  swap( *this, tmp );
416  return *this;
417  }
418 
420  operator=( const connection_lifetime_monitor_t & ) = delete;
421 };
422 
423 /*!
424  * @brief Specialization of connection_lifetime_monitor for the case
425  * when connection count limiter is not used at all.
426  *
427  * Holds nothing. Does nothing.
428  *
429  * @since v.0.6.12
430  */
431 template<>
433 {
434 public:
437  {}
438 };
439 
440 } /* namespace connection_count_limits */
441 
442 /*!
443  * @brief A kind of metafunction that deduces actual types related
444  * to connection count limiter in the dependecy of Traits.
445  *
446  * Deduces the following types:
447  *
448  * - limiter_t. The actual type of connection count limiter to be
449  * used in the RESTinio's server;
450  * - lifetime_monitor_t. The actual type of connection_lifetime_monitor
451  * to be used with connection objects.
452  *
453  * @tparam Traits The type with traits for RESTinio's server.
454  *
455  * @since v.0.6.12
456  */
457 template< typename Traits >
459 {
460  using limiter_t = typename std::conditional
461  <
464  typename Traits::strand_t >,
466  >::type;
467 
468  using lifetime_monitor_t =
470 };
471 
472 } /* namespace restinio */
connection_lifetime_monitor_t & operator=(const connection_lifetime_monitor_t &)=delete
An interface of acceptor to be used by connection count limiters.
Helper type for controlling the lifetime of the connection.
std::vector< std::size_t > m_pending_indexes
The storage for holding pending socket&#39;s slots.
std::size_t m_connections
The counter of active connections.
not_null_pointer_t< acceptor_callback_iface_t > m_acceptor
Mandatory pointer to the acceptor connected with this limiter.
std::size_t m_active_accepts
The counter of active accept() operations.
connection_lifetime_monitor_t(connection_lifetime_monitor_t &&other) noexcept
connection_lifetime_monitor_t & operator=(connection_lifetime_monitor_t &&other) noexcept
virtual void schedule_next_accept_attempt(std::size_t index) noexcept=0
noop_connection_count_limiter_t(not_null_pointer_t< connection_count_limits::impl::acceptor_callback_iface_t > acceptor, max_parallel_connections_t, max_active_accepts_t)
virtual void call_accept_now(std::size_t index) noexcept=0
connection_lifetime_monitor_t(const connection_lifetime_monitor_t &)=delete
A kind of metafunction that deduces actual types related to connection count limiter in the dependecy...
friend void swap(connection_lifetime_monitor_t &a, connection_lifetime_monitor_t &b) noexcept
const std::size_t m_max_parallel_connections
The limit for parallel connections.
not_null_pointer_t< connection_count_limits::impl::acceptor_callback_iface_t > m_acceptor
An implementation of connection count limiter for the case when connection count is not limited...
connection_lifetime_monitor_t(not_null_pointer_t< Count_Manager > manager) noexcept
std::enable_if< std::is_same< Parameter_Container, query_string_params_t >::value||std::is_same< Parameter_Container, router::route_params_t >::value, optional_t< Value_Type > >::type opt_value(const Parameter_Container &params, string_view_t key)
Gets the value of a parameter specified by key wrapped in optional_t<Value_Type> if parameter exists ...
Definition: value_or.hpp:64