sansio_connection_node.hpp 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. //
  2. // Copyright (c) 2019-2024 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
  3. //
  4. // Distributed under the Boost Software License, Version 1.0. (See accompanying
  5. // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
  6. //
  7. #ifndef BOOST_MYSQL_IMPL_INTERNAL_CONNECTION_POOL_SANSIO_CONNECTION_NODE_HPP
  8. #define BOOST_MYSQL_IMPL_INTERNAL_CONNECTION_POOL_SANSIO_CONNECTION_NODE_HPP
  9. #include <boost/mysql/diagnostics.hpp>
  10. #include <boost/mysql/error_code.hpp>
  11. #include <boost/assert.hpp>
  12. namespace boost {
  13. namespace mysql {
  14. namespace detail {
  15. // The status the connection is in
  16. enum class connection_status
  17. {
  18. // Connection task hasn't initiated yet.
  19. // This status doesn't count as pending. This facilitates tracking pending connections.
  20. initial,
  21. // Connection is trying to connect
  22. connect_in_progress,
  23. // Connect failed and we're sleeping
  24. sleep_connect_failed_in_progress,
  25. // Connection is trying to reset
  26. reset_in_progress,
  27. // Connection is trying to ping
  28. ping_in_progress,
  29. // Connection can be handed to the user
  30. idle,
  31. // Connection has been handed to the user
  32. in_use,
  33. // After cancel
  34. terminated,
  35. };
  36. // The next I/O action the connection should take. There's
  37. // no 1-1 mapping to connection_status
  38. enum class next_connection_action
  39. {
  40. // Do nothing, exit the loop
  41. none,
  42. // Issue a connect
  43. connect,
  44. // Connect failed, issue a sleep
  45. sleep_connect_failed,
  46. // Wait until a collection request is issued or the ping interval elapses
  47. idle_wait,
  48. // Issue a reset
  49. reset,
  50. // Issue a ping
  51. ping,
  52. };
  53. // A collection_state represents the possibility that a connection
  54. // that was in_use was returned by the user
  55. enum class collection_state
  56. {
  57. // Connection wasn't returned
  58. none,
  59. // Connection was returned and needs reset
  60. needs_collect,
  61. // Connection was returned and doesn't need reset
  62. needs_collect_with_reset
  63. };
  64. // CRTP. Derived should implement the entering_xxx and exiting_xxx hook functions.
  65. // Derived must derive from this class
  66. template <class Derived>
  67. class sansio_connection_node
  68. {
  69. connection_status status_;
  70. inline bool is_pending(connection_status status) noexcept
  71. {
  72. return status != connection_status::initial && status != connection_status::idle &&
  73. status != connection_status::in_use && status != connection_status::terminated;
  74. }
  75. inline static next_connection_action status_to_action(connection_status status) noexcept
  76. {
  77. switch (status)
  78. {
  79. case connection_status::connect_in_progress: return next_connection_action::connect;
  80. case connection_status::sleep_connect_failed_in_progress:
  81. return next_connection_action::sleep_connect_failed;
  82. case connection_status::ping_in_progress: return next_connection_action::ping;
  83. case connection_status::reset_in_progress: return next_connection_action::reset;
  84. case connection_status::idle:
  85. case connection_status::in_use: return next_connection_action::idle_wait;
  86. default: return next_connection_action::none;
  87. }
  88. }
  89. next_connection_action set_status(connection_status new_status)
  90. {
  91. auto& derived = static_cast<Derived&>(*this);
  92. // Notify we're entering/leaving the idle status
  93. if (new_status == connection_status::idle && status_ != connection_status::idle)
  94. derived.entering_idle();
  95. else if (new_status != connection_status::idle && status_ == connection_status::idle)
  96. derived.exiting_idle();
  97. // Notify we're entering/leaving a pending status
  98. if (!is_pending(status_) && is_pending(new_status))
  99. derived.entering_pending();
  100. else if (is_pending(status_) && !is_pending(new_status))
  101. derived.exiting_pending();
  102. // Actually update status
  103. status_ = new_status;
  104. return status_to_action(new_status);
  105. }
  106. public:
  107. sansio_connection_node(connection_status initial_status = connection_status::initial) noexcept
  108. : status_(initial_status)
  109. {
  110. }
  111. void mark_as_in_use() noexcept
  112. {
  113. BOOST_ASSERT(status_ == connection_status::idle);
  114. set_status(connection_status::in_use);
  115. }
  116. void cancel() { set_status(connection_status::terminated); }
  117. next_connection_action resume(error_code ec, collection_state col_st)
  118. {
  119. switch (status_)
  120. {
  121. case connection_status::initial: return set_status(connection_status::connect_in_progress);
  122. case connection_status::connect_in_progress:
  123. return ec ? set_status(connection_status::sleep_connect_failed_in_progress)
  124. : set_status(connection_status::idle);
  125. case connection_status::sleep_connect_failed_in_progress:
  126. return set_status(connection_status::connect_in_progress);
  127. case connection_status::idle:
  128. // The wait finished with no interruptions, and the connection
  129. // is still idle. Time to ping.
  130. return set_status(connection_status::ping_in_progress);
  131. case connection_status::in_use:
  132. // If col_st != none, the user has notified us to collect the connection.
  133. // This happens after they return the connection to the pool.
  134. // Update status and continue
  135. if (col_st == collection_state::needs_collect)
  136. {
  137. // No reset needed, we're idle
  138. return set_status(connection_status::idle);
  139. }
  140. else if (col_st == collection_state::needs_collect_with_reset)
  141. {
  142. return set_status(connection_status::reset_in_progress);
  143. }
  144. else
  145. {
  146. // The user is still using the connection (it's taking long, but can happen).
  147. // Idle wait again until they return the connection.
  148. return next_connection_action::idle_wait;
  149. }
  150. case connection_status::ping_in_progress:
  151. case connection_status::reset_in_progress:
  152. // Reconnect if there was an error. Otherwise, we're idle
  153. return ec ? set_status(connection_status::connect_in_progress)
  154. : set_status(connection_status::idle);
  155. case connection_status::terminated:
  156. default: return next_connection_action::none;
  157. }
  158. }
  159. // Exposed for testing
  160. connection_status status() const noexcept { return status_; }
  161. };
  162. } // namespace detail
  163. } // namespace mysql
  164. } // namespace boost
  165. #endif