shutdown_cp.hpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  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_SHUTDOWN_COMPONENT_HPP__
  11. #define __ASIO2_SHUTDOWN_COMPONENT_HPP__
  12. #if defined(_MSC_VER) && (_MSC_VER >= 1200)
  13. #pragma once
  14. #endif // defined(_MSC_VER) && (_MSC_VER >= 1200)
  15. #include <memory>
  16. #include <future>
  17. #include <utility>
  18. #include <string_view>
  19. #include <asio2/base/iopool.hpp>
  20. #include <asio2/base/listener.hpp>
  21. #include <asio2/base/impl/event_queue_cp.hpp>
  22. // Asio end socket functions: cancel, shutdown, close, release :
  23. // https://stackoverflow.com/questions/51468848/asio-end-socket-functions-cancel-shutdown-close-release
  24. // The proper steps are:
  25. // 1.Call shutdown() to indicate that you will not write any more data to the socket.
  26. // 2.Continue to (async-) read from the socket until you get either an error or the connection is closed.
  27. // 3.Now close() the socket (in the async read handler).
  28. // If you don't do this, you may end up closing the connection while the other side is still sending data.
  29. // This will result in an ungraceful close.
  30. // http://www.purecpp.cn/detail?id=2303
  31. // https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-shutdown
  32. //
  33. // To assure that all data is sent and received on a connected socket before it is closed,
  34. // an application should use shutdown to close connection before calling closesocket.
  35. // One method to wait for notification that the remote end has sent all its data and initiated
  36. // a graceful disconnect uses the WSAEventSelect function as follows :
  37. //
  38. // 1. Call WSAEventSelect to register for FD_CLOSE notification.
  39. // 2. Call shutdown with how=SD_SEND.
  40. // 3. When FD_CLOSE received, call the recv or WSARecv until the function completes with success
  41. // and indicates that zero bytes were received. If SOCKET_ERROR is returned, then the graceful
  42. // disconnect is not possible.
  43. // 4. Call closesocket.
  44. //
  45. // Another method to wait for notification that the remote end has sent all its data and initiated
  46. // a graceful disconnect uses overlapped receive calls follows :
  47. //
  48. // 1. Call shutdown with how=SD_SEND.
  49. // 2. Call recv or WSARecv until the function completes with success and indicates zero bytes were
  50. // received. If SOCKET_ERROR is returned, then the graceful disconnect is not possible.
  51. // 3. Call closesocket.
  52. // https://stackoverflow.com/questions/60105082/using-shutdown-for-a-udp-socket
  53. // Calling shutdown() on a UDP socket does nothing on the wire, and only affects the state of the
  54. // socket object.
  55. // after test on windows, call shutdown on udp socket, the recv won't return with a error.
  56. namespace asio2::detail
  57. {
  58. template<class derived_t, class args_t>
  59. class shutdown_cp
  60. {
  61. public:
  62. using self = shutdown_cp<derived_t, args_t>;
  63. public:
  64. /**
  65. * @brief constructor
  66. */
  67. shutdown_cp() noexcept {}
  68. /**
  69. * @brief destructor
  70. */
  71. ~shutdown_cp() = default;
  72. protected:
  73. template<typename DeferEvent>
  74. inline void _do_shutdown(const error_code& ec, std::shared_ptr<derived_t> this_ptr, DeferEvent chain)
  75. {
  76. derived_t& derive = static_cast<derived_t&>(*this);
  77. // call shutdown manual in client:
  78. // 1. the session recv function will return with error of eof,
  79. // call closesocket in the session recv function is ok.
  80. // 2. the client recv function will return with error of eof
  81. // call closesocket in the client recv function is ok.
  82. // but at ssl mode:
  83. // 1. we must call async_shutdown to execute the ssl stream close handshake first.
  84. // 2. then we can call shutdown.
  85. // when the recv function is returned with error of eof, there are two possible that
  86. // can trigger this situation:
  87. // 1. call shutdown manual.
  88. // 2. recvd the shutdown notify of the another peer.
  89. // so:
  90. // 1. when we call shutdown manual, then the recv function will return with error eof,
  91. // at this time, we shouldn't call shutdown and make shutdown timer again, we should
  92. // call closesocket directly.
  93. // 2. when we recvd the shutdown notify of the another peer, then the recv function will
  94. // return with error eof, at this time, we shouldn't call shutdown and make shutdown
  95. // timer again too, we should call closesocket directly too.
  96. // so:
  97. // we should check whether the shutdown timer is empty, if it is true, it means the
  98. // shutdown is not yet called by manual, then we should call shutdown. if it is false,
  99. // it means the shutdown is called already by manual, then we should call closesocket.
  100. ASIO2_ASSERT(derive.io_->running_in_this_thread());
  101. ASIO2_LOG_DEBUG("shutdown_cp::_do_shutdown enter: {} {}", ec.value(), ec.message());
  102. // if disconnect is in progress, and not finished, we can't do a new disconnect again.
  103. // otherwise, this situation will has problem:
  104. // client.post([&]()
  105. // {
  106. // client.stop();
  107. // client.start(...);
  108. // });
  109. // after post a connect event, then a new disconnect event maybe pushed into the queue
  110. // again, this will cause the start has no effect.
  111. if (derive.shutdown_timer_ || derive.disconnecting_)
  112. {
  113. derive._stop_shutdown_timer(std::move(this_ptr));
  114. return;
  115. }
  116. derive.disp_event(
  117. [&derive, ec, this_ptr = std::move(this_ptr), e = chain.move_event()]
  118. (event_queue_guard<derived_t> g) mutable
  119. {
  120. set_last_error(ec);
  121. // make a new chain, if the chain can't be passed to other functions, the chain
  122. // should be destroyed at the end of this function, then the function of this
  123. // chain will be called, otherwise this situation will be happen:
  124. //
  125. // client.post([&]()
  126. // {
  127. // client.stop();
  128. // client.start(...);
  129. // });
  130. //
  131. // client stop will post a event, and this event is before the client.start, and
  132. // at this time the state maybe stopped, so the chain can't be passed to other
  133. // fuctions, then the event guard will destroyed, then the next event "client.start"
  134. // will be called, then the stop's callback "do stop" will be called, but at this
  135. // time, the state is starting, which not equal to stopped.
  136. //
  137. defer_event chain(std::move(e), std::move(g));
  138. ASIO2_LOG_DEBUG("shutdown_cp::_do_shutdown leave: {} {} state={}",
  139. ec.value(), ec.message(), detail::to_string(derive.state_.load()));
  140. state_t expected = state_t::started;
  141. if (derive.state_.compare_exchange_strong(expected, state_t::started))
  142. {
  143. derive.disconnecting_ = true;
  144. return derive._post_shutdown(ec, std::move(this_ptr), std::move(chain));
  145. }
  146. expected = state_t::starting;
  147. if (derive.state_.compare_exchange_strong(expected, state_t::starting))
  148. {
  149. derive.disconnecting_ = true;
  150. return derive._post_shutdown(ec, std::move(this_ptr), std::move(chain));
  151. }
  152. asio::post(derive.io_->context(), make_allocator(derive.wallocator(),
  153. [this_ptr = std::move(this_ptr), chain = std::move(chain)]() mutable
  154. {
  155. detail::ignore_unused(this_ptr, chain);
  156. }));
  157. }, chain.move_guard());
  158. }
  159. template<typename DeferEvent>
  160. inline void _post_shutdown(const error_code& ec, std::shared_ptr<derived_t> this_ptr, DeferEvent chain)
  161. {
  162. derived_t& derive = static_cast<derived_t&>(*this);
  163. ASIO2_ASSERT(derive.io_->running_in_this_thread());
  164. ASIO2_LOG_DEBUG("shutdown_cp::_post_shutdown: {} {}", ec.value(), ec.message());
  165. if constexpr (std::is_same_v<typename derived_t::socket_type::protocol_type, asio::ip::tcp>)
  166. {
  167. derive._post_shutdown_tcp(ec, std::move(this_ptr), std::move(chain));
  168. }
  169. else
  170. {
  171. derive._handle_shutdown(ec, std::move(this_ptr), std::move(chain));
  172. }
  173. }
  174. template<typename DeferEvent>
  175. inline void _post_shutdown_tcp(const error_code& ec, std::shared_ptr<derived_t> this_ptr, DeferEvent chain)
  176. {
  177. derived_t& derive = static_cast<derived_t&>(*this);
  178. // the socket maybe closed already in the connect timeout timer.
  179. if (derive.socket().is_open())
  180. {
  181. error_code ec_linger{};
  182. asio::socket_base::linger lnger{};
  183. derive.socket().lowest_layer().get_option(lnger, ec_linger);
  184. // call socket's close function to notify the _handle_recv function response with
  185. // error > 0 ,then the socket can get notify to exit
  186. // Call shutdown() to indicate that you will not write any more data to the socket.
  187. if (!ec_linger && !(lnger.enabled() == true && lnger.timeout() == 0))
  188. {
  189. error_code ec_shutdown{};
  190. derive.socket().shutdown(asio::socket_base::shutdown_send, ec_shutdown);
  191. // if the reading is true , it means that the shutdown is called by manual.
  192. // if the reading is false, it means that the shutdown is called by the recv
  193. // error.
  194. //
  195. // after call shutdown, the recv function should be returned with error, but
  196. // when use our http server and the client is pc broswer, even if we has called
  197. // shutdown, the recv function still won't be returned, so we must use a timeout
  198. // timer to ensure the closesocket can be called.
  199. //
  200. // and some times, even if we has called shutdown, the http session' recv fucntion
  201. // will returned with no error.
  202. if (!ec_shutdown && derive.reading_)
  203. {
  204. derive._make_shutdown_timer(ec, std::move(this_ptr), std::move(chain));
  205. return;
  206. }
  207. }
  208. }
  209. derive._handle_shutdown(ec, std::move(this_ptr), std::move(chain));
  210. }
  211. template<typename DeferEvent>
  212. inline void _handle_shutdown(const error_code& ec, std::shared_ptr<derived_t> this_ptr, DeferEvent chain)
  213. {
  214. derived_t& derive = static_cast<derived_t&>(*this);
  215. ASIO2_ASSERT(derive.io_->running_in_this_thread());
  216. ASIO2_LOG_DEBUG("shutdown_cp::_handle_shutdown: {} {}", ec.value(), ec.message());
  217. this->shutdown_timer_.reset();
  218. derive._do_close(ec, std::move(this_ptr), std::move(chain));
  219. }
  220. protected:
  221. template<typename DeferEvent>
  222. inline void _make_shutdown_timer(const error_code& ec, std::shared_ptr<derived_t> this_ptr, DeferEvent chain)
  223. {
  224. derived_t& derive = static_cast<derived_t&>(*this);
  225. ASIO2_ASSERT(derive.io_->running_in_this_thread());
  226. asio::dispatch(derive.io_->context(), make_allocator(derive.wallocator(),
  227. [this, ec, this_ptr = std::move(this_ptr), chain = std::move(chain)]() mutable
  228. {
  229. derived_t& derive = static_cast<derived_t&>(*this);
  230. ASIO2_ASSERT(this->shutdown_timer_ == nullptr);
  231. if (this->shutdown_timer_)
  232. {
  233. this->shutdown_timer_->cancel();
  234. }
  235. this->shutdown_timer_ = std::make_shared<safe_timer>(derive.io_->context());
  236. derive._post_shutdown_timer(ec, std::move(this_ptr), std::move(chain),
  237. derive.get_disconnect_timeout(), this->shutdown_timer_);
  238. }));
  239. }
  240. template<typename DeferEvent, class Rep, class Period>
  241. inline void _post_shutdown_timer(
  242. const error_code& ec, std::shared_ptr<derived_t> this_ptr, DeferEvent chain,
  243. std::chrono::duration<Rep, Period> duration, std::shared_ptr<safe_timer> timer_ptr)
  244. {
  245. derived_t& derive = static_cast<derived_t&>(*this);
  246. // a new timer is maked, this is the prev timer, so return directly.
  247. if (timer_ptr.get() != this->shutdown_timer_.get())
  248. return;
  249. safe_timer* ptimer = timer_ptr.get();
  250. ptimer->timer.expires_after(duration);
  251. ptimer->timer.async_wait(
  252. [&derive, ec, this_ptr = std::move(this_ptr), chain = std::move(chain), timer_ptr = std::move(timer_ptr)]
  253. (const error_code& timer_ec) mutable
  254. {
  255. derive._handle_shutdown_timer(
  256. timer_ec, ec, std::move(this_ptr), std::move(chain), std::move(timer_ptr));
  257. });
  258. }
  259. template<typename DeferEvent>
  260. inline void _handle_shutdown_timer(
  261. const error_code& timer_ec,
  262. const error_code& ec, std::shared_ptr<derived_t> this_ptr, DeferEvent chain,
  263. std::shared_ptr<safe_timer> timer_ptr)
  264. {
  265. derived_t& derive = static_cast<derived_t&>(*this);
  266. ASIO2_ASSERT((!timer_ec) || timer_ec == asio::error::operation_aborted);
  267. // a new timer is maked, this is the prev timer, so return directly.
  268. if (timer_ptr.get() != this->shutdown_timer_.get())
  269. return;
  270. // member variable timer should't be empty
  271. if (!this->shutdown_timer_)
  272. {
  273. ASIO2_ASSERT(false);
  274. return;
  275. }
  276. // current timer is canceled by manual
  277. if (timer_ec == asio::error::operation_aborted || timer_ptr->canceled.test_and_set())
  278. {
  279. timer_ptr->canceled.clear();
  280. ASIO2_LOG_DEBUG("shutdown_cp::_handle_shutdown_timer: canceled");
  281. derive._handle_shutdown(ec, std::move(this_ptr), std::move(chain));
  282. }
  283. // timeout
  284. else
  285. {
  286. timer_ptr->canceled.clear();
  287. std::chrono::system_clock::duration silence = derive.get_silence_duration();
  288. // if recvd data in shutdown timeout period, lengthened the timer to remained times.
  289. if (silence < derive.get_disconnect_timeout())
  290. {
  291. derive._post_shutdown_timer(ec, std::move(this_ptr), std::move(chain),
  292. derive.get_disconnect_timeout() - silence, std::move(timer_ptr));
  293. }
  294. else
  295. {
  296. ASIO2_LOG_DEBUG("shutdown_cp::_handle_shutdown_timer: timeout");
  297. derive._handle_shutdown(ec, std::move(this_ptr), std::move(chain));
  298. }
  299. }
  300. }
  301. inline void _stop_shutdown_timer(std::shared_ptr<derived_t> this_ptr)
  302. {
  303. derived_t& derive = static_cast<derived_t&>(*this);
  304. asio::dispatch(derive.io_->context(), make_allocator(derive.wallocator(),
  305. [this, this_ptr = std::move(this_ptr)]() mutable
  306. {
  307. if (this->shutdown_timer_)
  308. {
  309. this->shutdown_timer_->cancel();
  310. }
  311. }));
  312. }
  313. protected:
  314. /// beacuse the shutdown timer is used only when shutdown, so we use a pointer
  315. /// to reduce memory space occupied when running
  316. std::shared_ptr<safe_timer> shutdown_timer_;
  317. };
  318. }
  319. #endif // !__ASIO2_SHUTDOWN_COMPONENT_HPP__