RESTinio
Loading...
Searching...
No Matches
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
22namespace 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 */
39 std::size_t, max_parallel_connections_tag >;
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 */
52 std::size_t, max_active_accepts_tag >;
53
54namespace 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{
79public:
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 */
114template< 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 */
138 std::size_t m_active_accepts{ 0u };
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 */
146 std::size_t m_connections{ 0u };
147
148 //! The limit for parallel connections.
149 const std::size_t m_max_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
167 [[nodiscard]]
168 bool
169 has_free_slots() const noexcept
170 {
172 }
173
174public:
176 not_null_pointer_t< acceptor_callback_iface_t > acceptor,
177 max_parallel_connections_t max_parallel_connections,
178 max_active_accepts_t max_pending_indexes )
181 {
183 }
184
187
188 void
190 {
191 std::lock_guard< Mutex_Type > lock{ m_lock };
192
193 // Expects that m_active_accepts is always greater than 0.
195
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]() -> std::optional<std::size_t> {
209 std::lock_guard< Mutex_Type > lock{ m_lock };
210
211 // Expects that m_connections is always greater than 0.
212 --m_connections;
213
214 if( has_free_slots() && !m_pending_indexes.empty() )
215 {
216 std::size_t pending_index = m_pending_indexes.back();
217 m_pending_indexes.pop_back();
218 return pending_index;
219 }
220 else
221 return std::nullopt;
222 }();
223
224 if( index_to_activate )
225 {
226 m_acceptor->schedule_next_accept_attempt( *index_to_activate );
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 {
241 std::lock_guard< Mutex_Type > lock{ m_lock };
242
243 if( has_free_slots() )
244 {
246 return true;
247 }
248 else
249 {
250 m_pending_indexes.push_back( index );
251 return false;
252 }
253 }();
254
255 if( accept_now )
256 {
257 m_acceptor->call_accept_now( index );
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
274public:
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*/ )
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
295 {
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 */
309template< typename Strand >
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 */
321template<>
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 */
340template<>
343{
345
346public:
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 */
373template< typename Count_Manager >
375{
377
378public:
380 not_null_pointer_t< Count_Manager > manager ) noexcept
381 : m_manager{ manager }
382 {
384 }
385
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 {
414 connection_lifetime_monitor_t tmp{ std::move(other) };
415 swap( *this, tmp );
416 return *this;
417 }
418
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 */
431template<>
433{
434public:
436 not_null_pointer_t< noop_connection_count_limiter_t > ) noexcept
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 */
457template< typename Traits >
459{
460 using limiter_t = typename std::conditional
461 <
462 Traits::use_connection_count_limiter,
464 typename Traits::strand_t >,
466 >::type;
467
470};
471
472} /* namespace restinio */
Implementation of connection count limiter for multi-threading mode.
Implementation of connection count limiter for single-threading mode.
connection_count_limits::impl::actual_limiter_t< null_mutex_t > base_t
Template class for connection count limiter for the case when connection count limit is actually used...
Specialization of connection_lifetime_monitor for the case when connection count limiter is not used ...
Helper type for controlling the lifetime of the connection.
connection_lifetime_monitor_t & operator=(connection_lifetime_monitor_t &&other) noexcept
friend void swap(connection_lifetime_monitor_t &a, connection_lifetime_monitor_t &b) noexcept
connection_lifetime_monitor_t(connection_lifetime_monitor_t &&other) noexcept
connection_lifetime_monitor_t & operator=(const connection_lifetime_monitor_t &)=delete
An interface of acceptor to be used by connection count limiters.
virtual void schedule_next_accept_attempt(std::size_t index) noexcept=0
virtual void call_accept_now(std::size_t index) noexcept=0
Actual implementation of connection count limiter.
std::vector< std::size_t > m_pending_indexes
The storage for holding pending socket's slots.
std::size_t m_active_accepts
The counter of active accept() operations.
const std::size_t m_max_parallel_connections
The limit for parallel connections.
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.
An implementation of connection count limiter for the case when connection count is not limited.
not_null_pointer_t< connection_count_limits::impl::acceptor_callback_iface_t > m_acceptor
Helper template for defining tagged scalar types.
restinio::utils::tagged_scalar_t< std::size_t, max_parallel_connections_tag > max_parallel_connections_t
A kind of strict typedef for maximum count of active connections.
restinio::utils::tagged_scalar_t< std::size_t, max_active_accepts_tag > max_active_accepts_t
A kind of strict typedef for maximum count of active accepts.
asio_ns::strand< default_asio_executor > default_strand_t
A typedef for the default strand type.
default_asio_executor noop_strand_t
A typedef for no-op strand type.
A kind of metafunction that deduces actual types related to connection count limiter in the dependecy...
typename std::conditional< Traits::use_connection_count_limiter, connection_count_limits::connection_count_limiter_t< typename Traits::strand_t >, connection_count_limits::noop_connection_count_limiter_t >::type limiter_t
connection_count_limits::connection_lifetime_monitor_t< limiter_t > lifetime_monitor_t
A class to be used as null_mutex.