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 <any>
19#include <cstdint>
20#include <memory>
21#include <mutex>
22#include <utility>
23
24namespace restinio
25{
26
28{
29
30//
31// max_parallel_connections_t
32//
34
35/*!
36 * @brief A kind of strict typedef for maximum count of active connections.
37 *
38 * @since v.0.6.12
39 */
41 std::size_t, max_parallel_connections_tag >;
42
43//
44// max_active_accepts_t
45//
47
48/*!
49 * @brief A kind of strict typedef for maximum count of active accepts.
50 *
51 * @since v.0.6.12
52 */
54 std::size_t, max_active_accepts_tag >;
55
56namespace impl
57{
58
59/*!
60 * @brief An interface of acceptor to be used by connection count limiters.
61 *
62 * An instance of a connection count limiter will receive a reference to the
63 * acceptor. The limiter has to call the acceptor and this interface declares
64 * methods of the acceptor that will be invoked by the limiter.
65 *
66 * The assumed working scheme is:
67 *
68 * - the acceptor calls `accept_next` for the limiter;
69 * - the limiter checks the possibility to call `accept()`. If it is possible,
70 * then the limiter calls `call_accept_now` back (right inside `accept_next`
71 * invocation). If it isn't possible, then the limiter stores the socket's
72 * slot index somewhere inside the limiter;
73 * - sometime later the limiter calls `schedule_next_accept_attempt` for the
74 * acceptor. The acceptor then should perform a new call to `accept_next` in
75 * the appropriate worker context.
76 *
77 * @since v.0.6.12
78 */
80{
81public:
82 /*!
83 * This method will be invoked by a limiter when there is a possibility to
84 * call accept() right now.
85 */
86 virtual void
88 //! An index of socket's slot to be used for accept().
89 std::size_t index ) noexcept = 0;
90
91 /*!
92 * This method will be invoked by a limiter when there is no possibility to
93 * call accept() right now, but the next call to `accept_next` should be
94 * scheduled as soon as possible in the appropriate worker context.
95 *
96 * It is assumed that the acceptor will use asio::post() with a completion
97 * handler that calls the `accept_next` method of the limiter.
98 */
99 virtual void
101 //! An index of socket's slot to be used for accept().
102 std::size_t index ) noexcept = 0;
103};
104
105/*!
106 * @brief Actual implementation of connection count limiter.
107 *
108 * @note
109 * This is not Copyable nor Moveable type.
110 *
111 * @tparam Mutex_Type Type of mutex to be used for protection of limiter
112 * object. It is expected to be std::mutex or null_mutex_t.
113 *
114 * @since v.0.6.12
115 */
116template< typename Mutex_Type >
118{
119 //! Lock object to be used.
120 Mutex_Type m_lock;
121
122 //! Mandatory pointer to the acceptor connected with this limiter.
124
125 /*!
126 * @brief The counter of active accept() operations.
127 *
128 * Incremented every time the acceptor_callback_iface_t::call_accept_now()
129 * is invoked. Decremented in increment_parallel_connections().
130 *
131 * @attention
132 * It seems to be a fragile scheme because if there won't be a call to
133 * increment_parallel_connections() after the invocation of
134 * acceptor_callback_iface_t::call_accept_now() the value of
135 * m_active_accepts will be incorrect. But it is hard to invent a more
136 * bulletproof solution and it seems that the missing call to
137 * increment_parallel_connections() could be only on the shutdown of the
138 * acceptor.
139 */
140 std::size_t m_active_accepts{ 0u };
141
142 /*!
143 * @brief The counter of active connections.
144 *
145 * This value is incremented in increment_parallel_connections()
146 * and decremented in decrement_parallel_connections().
147 */
148 std::size_t m_connections{ 0u };
149
150 //! The limit for parallel connections.
151 const std::size_t m_max_parallel_connections;
152
153 /*!
154 * @brief The storage for holding pending socket's slots.
155 *
156 * @note
157 * This storage is used as stack: new indexes are added to the
158 * end and are got from the end of the vector (LIFO working scheme).
159 *
160 * @attention
161 * The capacity for that storage is preallocated in the constructor
162 * so we don't expect any allocations during the usage of
163 * m_pending_indexes. This allows accept_next() method to be
164 * noexcept. But this works only if max_pending_indexes passed
165 * to the constructor is right.
166 */
168
169 [[nodiscard]]
170 bool
171 has_free_slots() const noexcept
172 {
174 }
175
176public:
178 not_null_pointer_t< acceptor_callback_iface_t > acceptor,
179 max_parallel_connections_t max_parallel_connections,
180 max_active_accepts_t max_pending_indexes )
183 {
185 }
186
189
190 void
192 {
193 std::lock_guard< Mutex_Type > lock{ m_lock };
194
195 // Expects that m_active_accepts is always greater than 0.
197
199 }
200
201 // Note: this method is noexcept because it can be called from
202 // destructors.
203 void
205 {
206 // Decrement active connections under acquired lock.
207 // If the count of connections drops below the limit and
208 // there are some pending indexes then one of them will
209 // be returned (wrapped into an optional).
210 auto index_to_activate = [this]() -> std::optional<std::size_t> {
211 std::lock_guard< Mutex_Type > lock{ m_lock };
212
213 // Expects that m_connections is always greater than 0.
214 --m_connections;
215
216 if( has_free_slots() && !m_pending_indexes.empty() )
217 {
218 std::size_t pending_index = m_pending_indexes.back();
219 m_pending_indexes.pop_back();
220 return pending_index;
221 }
222 else
223 return std::nullopt;
224 }();
225
226 if( index_to_activate )
227 {
228 m_acceptor->schedule_next_accept_attempt( *index_to_activate );
229 }
230 }
231
232 /*!
233 * This method either calls acceptor_callback_iface_t::call_accept_now() (in
234 * that case m_active_accepts is incremented) or stores @a index into the
235 * internal storage.
236 */
237 void
238 accept_next( std::size_t index ) noexcept
239 {
240 // Perform all operations under acquired lock.
241 // The result is a flag that tells can accept() be called right now.
242 const bool accept_now = [this, index]() -> bool {
243 std::lock_guard< Mutex_Type > lock{ m_lock };
244
245 if( has_free_slots() )
246 {
248 return true;
249 }
250 else
251 {
252 m_pending_indexes.push_back( index );
253 return false;
254 }
255 }();
256
257 if( accept_now )
258 {
259 m_acceptor->call_accept_now( index );
260 }
261 }
262};
263
264} /* namespace impl */
265
266/*!
267 * @brief An implementation of connection count limiter for the case
268 * when connection count is not limited.
269 *
270 * @since v.0.6.12
271 */
273{
275
276public:
278 not_null_pointer_t< connection_count_limits::impl::acceptor_callback_iface_t > acceptor,
279 max_parallel_connections_t /*max_parallel_connections*/,
280 max_active_accepts_t /*max_pending_indexes*/ )
282 {
283 }
284
285 void
286 increment_parallel_connections() noexcept { /* Nothing to do */ }
287
288 void
289 decrement_parallel_connections() noexcept { /* Nothing to do */ }
290
291 /*!
292 * Calls acceptor_callback_iface_t::call_accept_now() directly.
293 * The @a index is never stored anywhere.
294 */
295 void
297 {
299 }
300};
301
302/*!
303 * @brief Template class for connection count limiter for the case when
304 * connection count limit is actually used.
305 *
306 * The actual implementation will be provided by specializations of
307 * that class for specific Strand types.
308 *
309 * @since v.0.6.12
310 */
311template< typename Strand >
313
314/*!
315 * @brief Implementation of connection count limiter for single-threading
316 * mode.
317 *
318 * In single-threading mode there is no need to protect limiter from
319 * access from different threads. So null_mutex_t is used.
320 *
321 * @since v.0.6.12
322 */
323template<>
332
333/*!
334 * @brief Implementation of connection count limiter for multi-threading
335 * mode.
336 *
337 * In multi-threading mode std::mutex is used for the protection of
338 * limiter object.
339 *
340 * @since v.0.6.12
341 */
342template<>
345{
347
348public:
349 using base_t::base_t;
350};
351
352/*!
353 * @brief Helper type for controlling the lifetime of the connection.
354 *
355 * Connection count limiter should be informed when a new connection
356 * created and when an existing connection is closed. An instance
357 * of connection_lifetime_monitor_t should be used for that purpose:
358 * a new instance of connection_lifetime_monitor_t should be created
359 * and bound to a connection object. The constructor of
360 * connection_lifetime_monitor_t will inform the limiter about
361 * the creation of a new connection. The destructor of
362 * connection_lifetime_monitor_t will inform the limiter about the
363 * destruction of a connection.
364 *
365 * @note
366 * This type is not Copyable but Movabale.
367 *
368 * @attention
369 * The pointer to Count_Manager passed to the constructor should
370 * remain valid the whole lifetime of connection_lifetime_monitor_t
371 * instance.
372 *
373 * @since v.0.6.12
374 */
375template< typename Count_Manager >
377{
378// FIXME: there should be a more efficient way that doesn't require std::any
379// instance. The std::any is used for a quick fix for
380// https://github.com/Stiffstream/restinio/issues/246
381
382 /// Holder of std::shared_ptr<acceptor_t>
383 ///
384 /// The acceptor shouldn't be destroyed while this monitor object is alive.
385 /// To ensure this a shared_ptr has to be stored inside monitor instance.
386 /// But acceptor_t depends on Traits type that in not available here. For
387 /// simplicity shared_ptr is wrapped into std::any.
388 std::any m_acceptor;
389
390 /// Pointer to manager object that counts live connections.
391 ///
392 /// @note
393 /// It may become nullptr if the object is moved.
394 Count_Manager * m_manager;
395
396public:
397 template< typename Acceptor_Type >
399 Acceptor_Type & acceptor,
400 not_null_pointer_t< Count_Manager > manager ) noexcept
401 : m_acceptor{ acceptor.shared_from_this() }
402 , m_manager{ manager }
403 {
404 m_manager->increment_parallel_connections();
405 }
406
408 {
409 if( m_manager )
410 m_manager->decrement_parallel_connections();
411 }
412
414 const connection_lifetime_monitor_t & ) = delete;
415
416 friend void
419 connection_lifetime_monitor_t & b ) noexcept
420 {
421 using std::swap;
422 swap( a.m_acceptor, b.m_acceptor );
423 swap( a.m_manager, b.m_manager );
424 }
425
427 connection_lifetime_monitor_t && other ) noexcept
428 : m_acceptor{
429 std::exchange(
430 other.m_acceptor,
431 std::any{} )
432 }
433 , m_manager{ std::exchange( other.m_manager, nullptr ) }
434 {}
435
438 {
439 connection_lifetime_monitor_t tmp{ std::move(other) };
440 swap( *this, tmp );
441 return *this;
442 }
443
446};
447
448/*!
449 * @brief Specialization of connection_lifetime_monitor for the case
450 * when connection count limiter is not used at all.
451 *
452 * Holds nothing. Does nothing.
453 *
454 * @since v.0.6.12
455 */
456template<>
458{
459public:
460 template< typename Acceptor_Type >
462 Acceptor_Type & /* acceptor */,
464 {}
465};
466
467} /* namespace connection_count_limits */
468
469/*!
470 * @brief A kind of metafunction that deduces actual types related
471 * to connection count limiter in the dependecy of Traits.
472 *
473 * Deduces the following types:
474 *
475 * - limiter_t. The actual type of connection count limiter to be
476 * used in the RESTinio's server;
477 * - lifetime_monitor_t. The actual type of connection_lifetime_monitor
478 * to be used with connection objects.
479 *
480 * @tparam Traits The type with traits for RESTinio's server.
481 *
482 * @since v.0.6.12
483 */
484template<
485 typename Traits >
487{
488 using limiter_t = typename std::conditional
489 <
490 Traits::use_connection_count_limiter,
492 typename Traits::strand_t >,
494 >::type;
495
498};
499
500} /* 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 ...
connection_lifetime_monitor_t(Acceptor_Type &, not_null_pointer_t< noop_connection_count_limiter_t >) noexcept
Helper type for controlling the lifetime of the connection.
connection_lifetime_monitor_t & operator=(connection_lifetime_monitor_t &&other) noexcept
Count_Manager * m_manager
Pointer to manager object that counts live connections.
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(Acceptor_Type &acceptor, not_null_pointer_t< Count_Manager > manager) noexcept
connection_lifetime_monitor_t & operator=(const connection_lifetime_monitor_t &)=delete
connection_lifetime_monitor_t(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_active_accepts_tag > max_active_accepts_t
A kind of strict typedef for maximum count of active accepts.
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.
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...
connection_count_limits::connection_lifetime_monitor_t< limiter_t > lifetime_monitor_t
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
A class to be used as null_mutex.