connect_timeout_cp.hpp 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. /*
  2. * Copyright (c) 2017-2023 zhllxt
  3. *
  4. * author : zhllxt
  5. * email : 37792738@qq.com
  6. *
  7. * Distributed under the Boost Software License, Version 1.0. (See accompanying
  8. * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
  9. */
  10. #ifndef __ASIO2_CONNECT_TIMEOUT_COMPONENT_HPP__
  11. #define __ASIO2_CONNECT_TIMEOUT_COMPONENT_HPP__
  12. #if defined(_MSC_VER) && (_MSC_VER >= 1200)
  13. #pragma once
  14. #endif // defined(_MSC_VER) && (_MSC_VER >= 1200)
  15. #include <chrono>
  16. #include <asio2/base/iopool.hpp>
  17. #include <asio2/base/log.hpp>
  18. namespace asio2::detail
  19. {
  20. template<class derived_t, class args_t>
  21. class connect_timeout_cp
  22. {
  23. public:
  24. /**
  25. * @brief constructor
  26. */
  27. connect_timeout_cp() = default;
  28. /**
  29. * @brief destructor
  30. */
  31. ~connect_timeout_cp() = default;
  32. /**
  33. * @brief get the connect timeout
  34. */
  35. inline std::chrono::steady_clock::duration get_connect_timeout() const noexcept
  36. {
  37. return this->connect_timeout_;
  38. }
  39. /**
  40. * @brief set the connect timeout
  41. */
  42. template<class Rep, class Period>
  43. inline derived_t& set_connect_timeout(std::chrono::duration<Rep, Period> timeout) noexcept
  44. {
  45. if (timeout > std::chrono::duration_cast<
  46. std::chrono::duration<Rep, Period>>((std::chrono::steady_clock::duration::max)()))
  47. this->connect_timeout_ = (std::chrono::steady_clock::duration::max)();
  48. else
  49. this->connect_timeout_ = timeout;
  50. return static_cast<derived_t&>(*this);
  51. }
  52. protected:
  53. template<class Rep, class Period>
  54. inline void _make_connect_timeout_timer(
  55. std::shared_ptr<derived_t> this_ptr, std::chrono::duration<Rep, Period> duration)
  56. {
  57. derived_t& derive = static_cast<derived_t&>(*this);
  58. asio::dispatch(derive.io_->context(), make_allocator(derive.wallocator(),
  59. [this, this_ptr = std::move(this_ptr), duration = std::move(duration)]() mutable
  60. {
  61. derived_t& derive = static_cast<derived_t&>(*this);
  62. if (this->connect_timeout_timer_)
  63. {
  64. this->connect_timeout_timer_->cancel();
  65. }
  66. this->connect_timeout_timer_ = std::make_shared<safe_timer>(derive.io_->context());
  67. derive._post_connect_timeout_timer(std::move(this_ptr), this->connect_timeout_timer_, duration);
  68. }));
  69. }
  70. template<class Rep, class Period>
  71. inline void _post_connect_timeout_timer(std::shared_ptr<derived_t> this_ptr,
  72. std::shared_ptr<safe_timer> timer_ptr, std::chrono::duration<Rep, Period> duration)
  73. {
  74. derived_t& derive = static_cast<derived_t&>(*this);
  75. #if defined(_DEBUG) || defined(DEBUG)
  76. ASIO2_ASSERT(this->is_stop_connect_timeout_timer_called_ == false);
  77. #endif
  78. // a new timer is maked, this is the prev timer, so return directly.
  79. if (timer_ptr.get() != this->connect_timeout_timer_.get())
  80. return;
  81. safe_timer* ptimer = timer_ptr.get();
  82. ptimer->timer.expires_after(duration);
  83. ptimer->timer.async_wait(
  84. [&derive, this_ptr = std::move(this_ptr), timer_ptr = std::move(timer_ptr)]
  85. (const error_code& ec) mutable
  86. {
  87. // bug fixed :
  88. // note : after call derive._handle_connect_timeout_timer(ec, std::move(this_ptr));
  89. // the pointer of "this" can no longer be used immediately, beacuse when
  90. // this_ptr's reference counter is 1, after call
  91. // derive._handle_connect_timeout_timer(ec, std::move(this_ptr)); the this_ptr's
  92. // object will be destroyed, then below code "this->..." will cause crash.
  93. derive._handle_connect_timeout_timer(ec, std::move(this_ptr), std::move(timer_ptr));
  94. // after call derive._handle_connect_timeout_timer(ec, std::move(this_ptr));
  95. // can't do it like below, beacuse "this" maybe deleted already.
  96. // this->...
  97. });
  98. }
  99. template<class D = derived_t>
  100. inline void _handle_connect_timeout_timer(
  101. const error_code& ec, std::shared_ptr<D> this_ptr, std::shared_ptr<safe_timer> timer_ptr)
  102. {
  103. derived_t& derive = static_cast<derived_t&>(*this);
  104. ASIO2_ASSERT((!ec) || ec == asio::error::operation_aborted);
  105. // a new timer is maked, this is the prev timer, so return directly.
  106. if (timer_ptr.get() != this->connect_timeout_timer_.get())
  107. return;
  108. // member variable timer should't be empty
  109. if (!this->connect_timeout_timer_)
  110. {
  111. ASIO2_ASSERT(false);
  112. return;
  113. }
  114. this->connect_timeout_timer_.reset();
  115. // ec maybe zero when timer_canceled_ is true.
  116. if (ec == asio::error::operation_aborted || timer_ptr->canceled.test_and_set())
  117. return;
  118. if constexpr (D::is_session())
  119. {
  120. if (!ec)
  121. {
  122. derive._do_disconnect(asio::error::timed_out, std::move(this_ptr));
  123. }
  124. else
  125. {
  126. // should't go to here
  127. ASIO2_ASSERT(false);
  128. derive._do_disconnect(ec, std::move(this_ptr));
  129. }
  130. }
  131. else
  132. {
  133. // no errors indicating that the client connection timed out
  134. if (!ec)
  135. {
  136. // we close the socket, so the async_connect will returned
  137. // with operation_aborted.
  138. error_code ec_ignore{};
  139. derive.socket().shutdown(asio::socket_base::shutdown_both, ec_ignore);
  140. derive.socket().cancel(ec_ignore);
  141. derive.socket().close(ec_ignore);
  142. ASIO2_ASSERT(!derive.socket().is_open());
  143. }
  144. }
  145. }
  146. inline void _stop_connect_timeout_timer()
  147. {
  148. // 2021-12-10 bug fix : when a client connected, the tcp_session::start will be
  149. // called, and super::start will be called in tcp_session::start, so session::start
  150. // will be called, and session::start will call _post_connect_timeout_timer, but
  151. // session::start is not running in the io thread, so it will post a event, then
  152. // tcp_session::_handle_connect will be called, and tcp_session::_done_connect
  153. // will be called, and _stop_connect_timeout_timer will be called, but at this
  154. // time, the _post_connect_timeout_timer in the session::start has't called yet,
  155. // this will cause the client to be fore disconnect with timeout error.
  156. // so we should ensure the _stop_connect_timeout_timer and _post_connect_timeout_timer
  157. // was called in the io thread.
  158. derived_t& derive = static_cast<derived_t&>(*this);
  159. derive.dispatch([this]() mutable
  160. {
  161. #if defined(_DEBUG) || defined(DEBUG)
  162. this->is_stop_connect_timeout_timer_called_ = true;
  163. #endif
  164. if (this->connect_timeout_timer_)
  165. {
  166. this->connect_timeout_timer_->cancel();
  167. }
  168. });
  169. }
  170. protected:
  171. /// beacuse the connect timeout timer is used only when connect, so we use a pointer
  172. /// to reduce memory space occupied when running
  173. std::shared_ptr<safe_timer> connect_timeout_timer_;
  174. std::chrono::steady_clock::duration connect_timeout_ = std::chrono::seconds(30);
  175. #if defined(_DEBUG) || defined(DEBUG)
  176. bool is_stop_connect_timeout_timer_called_ = false;
  177. #endif
  178. };
  179. }
  180. #endif // !__ASIO2_CONNECT_TIMEOUT_COMPONENT_HPP__