Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-12-13 09:54:56

0001 //
0002 // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
0003 //
0004 // Distributed under the Boost Software License, Version 1.0. (See accompanying
0005 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
0006 //
0007 
0008 #ifndef BOOST_MYSQL_IMPL_INTERNAL_CONNECTION_POOL_SANSIO_CONNECTION_NODE_HPP
0009 #define BOOST_MYSQL_IMPL_INTERNAL_CONNECTION_POOL_SANSIO_CONNECTION_NODE_HPP
0010 
0011 #include <boost/mysql/diagnostics.hpp>
0012 #include <boost/mysql/error_code.hpp>
0013 
0014 #include <boost/asio/error.hpp>
0015 #include <boost/assert.hpp>
0016 
0017 #include <algorithm>
0018 #include <cstddef>
0019 
0020 namespace boost {
0021 namespace mysql {
0022 namespace detail {
0023 
0024 // The status the connection is in
0025 enum class node_status
0026 {
0027     // Connection task hasn't initiated yet.
0028     // This status doesn't count as pending. This facilitates tracking pending connections.
0029     initial,
0030 
0031     // Connection is trying to connect
0032     connect_in_progress,
0033 
0034     // Connect failed and we're sleeping
0035     sleep_connect_failed_in_progress,
0036 
0037     // Connection is trying to reset
0038     reset_in_progress,
0039 
0040     // Connection is trying to ping
0041     ping_in_progress,
0042 
0043     // Connection can be handed to the user
0044     idle,
0045 
0046     // Connection has been handed to the user
0047     in_use,
0048 
0049     // After cancel
0050     terminated,
0051 };
0052 
0053 // The next I/O action the connection should take. There's
0054 // no 1-1 mapping to node_status
0055 enum class next_connection_action
0056 {
0057     // Do nothing, exit the loop
0058     none,
0059 
0060     // Issue a connect
0061     connect,
0062 
0063     // Connect failed, issue a sleep
0064     sleep_connect_failed,
0065 
0066     // Wait until a collection request is issued or the ping interval elapses
0067     idle_wait,
0068 
0069     // Issue a reset
0070     reset,
0071 
0072     // Issue a ping
0073     ping,
0074 };
0075 
0076 // A collection_state represents the possibility that a connection
0077 // that was in_use was returned by the user
0078 enum class collection_state
0079 {
0080     // Connection wasn't returned
0081     none,
0082 
0083     // Connection was returned and needs reset
0084     needs_collect,
0085 
0086     // Connection was returned and doesn't need reset
0087     needs_collect_with_reset
0088 };
0089 
0090 // CRTP. Derived should implement the entering_xxx and exiting_xxx hook functions.
0091 // Derived must derive from this class
0092 template <class Derived>
0093 class sansio_connection_node
0094 {
0095     node_status status_;
0096 
0097     inline bool is_pending(node_status status) noexcept
0098     {
0099         return status != node_status::initial && status != node_status::idle &&
0100                status != node_status::in_use && status != node_status::terminated;
0101     }
0102 
0103     inline static next_connection_action status_to_action(node_status status) noexcept
0104     {
0105         switch (status)
0106         {
0107         case node_status::connect_in_progress: return next_connection_action::connect;
0108         case node_status::sleep_connect_failed_in_progress:
0109             return next_connection_action::sleep_connect_failed;
0110         case node_status::ping_in_progress: return next_connection_action::ping;
0111         case node_status::reset_in_progress: return next_connection_action::reset;
0112         case node_status::idle:
0113         case node_status::in_use: return next_connection_action::idle_wait;
0114         default: return next_connection_action::none;
0115         }
0116     }
0117 
0118     next_connection_action set_status(node_status new_status)
0119     {
0120         auto& derived = static_cast<Derived&>(*this);
0121 
0122         // Notify we're entering/leaving the idle status
0123         if (new_status == node_status::idle && status_ != node_status::idle)
0124             derived.entering_idle();
0125         else if (new_status != node_status::idle && status_ == node_status::idle)
0126             derived.exiting_idle();
0127 
0128         // Notify we're entering/leaving a pending status
0129         if (!is_pending(status_) && is_pending(new_status))
0130             derived.entering_pending();
0131         else if (is_pending(status_) && !is_pending(new_status))
0132             derived.exiting_pending();
0133 
0134         // Actually update status
0135         status_ = new_status;
0136 
0137         return status_to_action(new_status);
0138     }
0139 
0140 public:
0141     sansio_connection_node(node_status initial_status = node_status::initial) noexcept
0142         : status_(initial_status)
0143     {
0144     }
0145 
0146     void mark_as_in_use() noexcept
0147     {
0148         BOOST_ASSERT(status_ == node_status::idle);
0149         set_status(node_status::in_use);
0150     }
0151 
0152     void cancel() { set_status(node_status::terminated); }
0153 
0154     next_connection_action resume(error_code ec, collection_state col_st)
0155     {
0156         switch (status_)
0157         {
0158         case node_status::initial: return set_status(node_status::connect_in_progress);
0159         case node_status::connect_in_progress:
0160             return ec ? set_status(node_status::sleep_connect_failed_in_progress)
0161                       : set_status(node_status::idle);
0162         case node_status::sleep_connect_failed_in_progress:
0163             return set_status(node_status::connect_in_progress);
0164         case node_status::idle:
0165             // The wait finished with no interruptions, and the connection
0166             // is still idle. Time to ping.
0167             return set_status(node_status::ping_in_progress);
0168         case node_status::in_use:
0169             // If col_st != none, the user has notified us to collect the connection.
0170             // This happens after they return the connection to the pool.
0171             // Update status and continue
0172             if (col_st == collection_state::needs_collect)
0173             {
0174                 // No reset needed, we're idle
0175                 return set_status(node_status::idle);
0176             }
0177             else if (col_st == collection_state::needs_collect_with_reset)
0178             {
0179                 return set_status(node_status::reset_in_progress);
0180             }
0181             else
0182             {
0183                 // The user is still using the connection (it's taking long, but can happen).
0184                 // Idle wait again until they return the connection.
0185                 return next_connection_action::idle_wait;
0186             }
0187         case node_status::ping_in_progress:
0188         case node_status::reset_in_progress:
0189             // Reconnect if there was an error. Otherwise, we're idle
0190             return ec ? set_status(node_status::connect_in_progress) : set_status(node_status::idle);
0191         case node_status::terminated:
0192         default: return next_connection_action::none;
0193         }
0194     }
0195 
0196     // Exposed for testing
0197     node_status status() const noexcept { return status_; }
0198 };
0199 
0200 // Composes a diagnostics object containing info about the last connect error.
0201 // Suitable for the diagnostics output of async_get_connection
0202 inline diagnostics create_connect_diagnostics(error_code connect_ec, const diagnostics& connect_diag)
0203 {
0204     diagnostics res;
0205     if (connect_ec)
0206     {
0207         // Manipulating the internal representations is more efficient here,
0208         // and better than using stringstream
0209         auto& res_impl = access::get_impl(res);
0210         const auto& connect_diag_impl = access::get_impl(connect_diag);
0211 
0212         if (connect_ec == asio::error::operation_aborted)
0213         {
0214             // operation_aborted in this context means timeout
0215             res_impl.msg = "Last connection attempt timed out";
0216             res_impl.is_server = false;
0217         }
0218         else
0219         {
0220             // Add the error code information
0221             res_impl.msg = "Last connection attempt failed with: ";
0222             res_impl.msg += connect_ec.message();
0223             res_impl.msg += " [";
0224             res_impl.msg += connect_ec.to_string();
0225             res_impl.msg += "]";
0226 
0227             // Add any diagnostics
0228             if (connect_diag_impl.msg.empty())
0229             {
0230                 // The resulting object doesn't contain server-supplied info
0231                 res_impl.is_server = false;
0232             }
0233             else
0234             {
0235                 // The resulting object may contain server-supplied info
0236                 res_impl.msg += ": ";
0237                 res_impl.msg += connect_diag_impl.msg;
0238                 res_impl.is_server = connect_diag_impl.is_server;
0239             }
0240         }
0241     }
0242     return res;
0243 }
0244 
0245 // Given config params and the current state, computes the number
0246 // of connections that the pool should create at any given point in time
0247 inline std::size_t num_connections_to_create(
0248     std::size_t initial_size,         // config
0249     std::size_t max_size,             // config
0250     std::size_t current_connections,  // the number of connections in the pool, in any state
0251     std::size_t pending_connections,  // the number of connections in the pool in pending state
0252     std::size_t pending_requests      // the current number of async_get_connection requests that are waiting
0253 )
0254 {
0255     BOOST_ASSERT(initial_size <= max_size);
0256     BOOST_ASSERT(current_connections <= max_size);
0257     BOOST_ASSERT(pending_connections <= current_connections);
0258 
0259     // We aim to have one pending connection per pending request.
0260     // When these connections successfully connect, they will fulfill the pending requests.
0261     std::size_t required_by_requests = pending_requests > pending_connections
0262                                            ? static_cast<std::size_t>(pending_requests - pending_connections)
0263                                            : 0u;
0264 
0265     // We should always have at least min_connections.
0266     // This might not be the case if the pool is just starting.
0267     std::size_t required_by_min = current_connections < initial_size
0268                                       ? static_cast<std::size_t>(initial_size - current_connections)
0269                                       : 0u;
0270 
0271     // We can't excess max_connections. This is the room for new connections that we have
0272     std::size_t room = static_cast<std::size_t>(max_size - current_connections);
0273 
0274     return (std::min)((std::max)(required_by_requests, required_by_min), room);
0275 }
0276 
0277 }  // namespace detail
0278 }  // namespace mysql
0279 }  // namespace boost
0280 
0281 #endif