connection.hpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438
  1. /* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
  2. *
  3. * Distributed under the Boost Software License, Version 1.0. (See
  4. * accompanying file LICENSE.txt)
  5. */
  6. #ifndef BOOST_REDIS_CONNECTION_HPP
  7. #define BOOST_REDIS_CONNECTION_HPP
  8. #include <boost/redis/detail/connection_base.hpp>
  9. #include <boost/redis/logger.hpp>
  10. #include <boost/redis/config.hpp>
  11. #include <boost/asio/io_context.hpp>
  12. #include <boost/asio/coroutine.hpp>
  13. #include <boost/asio/steady_timer.hpp>
  14. #include <boost/asio/any_io_executor.hpp>
  15. #include <boost/asio/any_completion_handler.hpp>
  16. #include <chrono>
  17. #include <memory>
  18. #include <limits>
  19. namespace boost::redis {
  20. namespace detail
  21. {
  22. template <class Connection, class Logger>
  23. struct reconnection_op {
  24. Connection* conn_ = nullptr;
  25. Logger logger_;
  26. asio::coroutine coro_{};
  27. template <class Self>
  28. void operator()(Self& self, system::error_code ec = {})
  29. {
  30. BOOST_ASIO_CORO_REENTER (coro_) for (;;)
  31. {
  32. BOOST_ASIO_CORO_YIELD
  33. conn_->impl_.async_run(conn_->cfg_, logger_, std::move(self));
  34. conn_->cancel(operation::receive);
  35. logger_.on_connection_lost(ec);
  36. if (!conn_->will_reconnect() || is_cancelled(self)) {
  37. conn_->cancel(operation::reconnection);
  38. self.complete(!!ec ? ec : asio::error::operation_aborted);
  39. return;
  40. }
  41. conn_->timer_.expires_after(conn_->cfg_.reconnect_wait_interval);
  42. BOOST_ASIO_CORO_YIELD
  43. conn_->timer_.async_wait(std::move(self));
  44. BOOST_REDIS_CHECK_OP0(;)
  45. if (!conn_->will_reconnect()) {
  46. self.complete(asio::error::operation_aborted);
  47. return;
  48. }
  49. conn_->reset_stream();
  50. }
  51. }
  52. };
  53. } // detail
  54. /** @brief A SSL connection to the Redis server.
  55. * @ingroup high-level-api
  56. *
  57. * This class keeps a healthy connection to the Redis instance where
  58. * commands can be sent at any time. For more details, please see the
  59. * documentation of each individual function.
  60. *
  61. * @tparam Socket The socket type e.g. asio::ip::tcp::socket.
  62. *
  63. */
  64. template <class Executor>
  65. class basic_connection {
  66. public:
  67. /// Executor type.
  68. using executor_type = Executor;
  69. /// Returns the underlying executor.
  70. executor_type get_executor() noexcept
  71. { return impl_.get_executor(); }
  72. /// Rebinds the socket type to another executor.
  73. template <class Executor1>
  74. struct rebind_executor
  75. {
  76. /// The connection type when rebound to the specified executor.
  77. using other = basic_connection<Executor1>;
  78. };
  79. /// Contructs from an executor.
  80. explicit
  81. basic_connection(
  82. executor_type ex,
  83. asio::ssl::context::method method = asio::ssl::context::tls_client,
  84. std::size_t max_read_size = (std::numeric_limits<std::size_t>::max)())
  85. : impl_{ex, method, max_read_size}
  86. , timer_{ex}
  87. { }
  88. /// Contructs from a context.
  89. explicit
  90. basic_connection(
  91. asio::io_context& ioc,
  92. asio::ssl::context::method method = asio::ssl::context::tls_client,
  93. std::size_t max_read_size = (std::numeric_limits<std::size_t>::max)())
  94. : basic_connection(ioc.get_executor(), method, max_read_size)
  95. { }
  96. /** @brief Starts underlying connection operations.
  97. *
  98. * This member function provides the following functionality
  99. *
  100. * 1. Resolve the address passed on `boost::redis::config::addr`.
  101. * 2. Connect to one of the results obtained in the resolve operation.
  102. * 3. Send a [HELLO](https://redis.io/commands/hello/) command where each of its parameters are read from `cfg`.
  103. * 4. Start a health-check operation where ping commands are sent
  104. * at intervals specified in
  105. * `boost::redis::config::health_check_interval`. The message passed to
  106. * `PING` will be `boost::redis::config::health_check_id`. Passing a
  107. * timeout with value zero will disable health-checks. If the Redis
  108. * server does not respond to a health-check within two times the value
  109. * specified here, it will be considered unresponsive and the connection
  110. * will be closed and a new connection will be stablished.
  111. * 5. Starts read and write operations with the Redis
  112. * server. More specifically it will trigger the write of all
  113. * requests i.e. calls to `async_exec` that happened prior to this
  114. * call.
  115. *
  116. * When a connection is lost for any reason, a new one is
  117. * stablished automatically. To disable reconnection call
  118. * `boost::redis::connection::cancel(operation::reconnection)`.
  119. *
  120. * @param cfg Configuration paramters.
  121. * @param l Logger object. The interface expected is specified in the class `boost::redis::logger`.
  122. * @param token Completion token.
  123. *
  124. * The completion token must have the following signature
  125. *
  126. * @code
  127. * void f(system::error_code);
  128. * @endcode
  129. *
  130. * For example on how to call this function refer to
  131. * cpp20_intro.cpp or any other example.
  132. */
  133. template <
  134. class Logger = logger,
  135. class CompletionToken = asio::default_completion_token_t<executor_type>>
  136. auto
  137. async_run(
  138. config const& cfg = {},
  139. Logger l = Logger{},
  140. CompletionToken token = CompletionToken{})
  141. {
  142. using this_type = basic_connection<executor_type>;
  143. cfg_ = cfg;
  144. l.set_prefix(cfg_.log_prefix);
  145. return asio::async_compose
  146. < CompletionToken
  147. , void(system::error_code)
  148. >(detail::reconnection_op<this_type, Logger>{this, l}, token, timer_);
  149. }
  150. /** @brief Receives server side pushes asynchronously.
  151. *
  152. * When pushes arrive and there is no `async_receive` operation in
  153. * progress, pushed data, requests, and responses will be paused
  154. * until `async_receive` is called again. Apps will usually want
  155. * to call `async_receive` in a loop.
  156. *
  157. * To cancel an ongoing receive operation apps should call
  158. * `connection::cancel(operation::receive)`.
  159. *
  160. * @param token Completion token.
  161. *
  162. * For an example see cpp20_subscriber.cpp. The completion token must
  163. * have the following signature
  164. *
  165. * @code
  166. * void f(system::error_code, std::size_t);
  167. * @endcode
  168. *
  169. * Where the second parameter is the size of the push received in
  170. * bytes.
  171. */
  172. template <class CompletionToken = asio::default_completion_token_t<executor_type>>
  173. auto async_receive(CompletionToken token = CompletionToken{})
  174. { return impl_.async_receive(std::move(token)); }
  175. /** @brief Receives server pushes synchronously without blocking.
  176. *
  177. * Receives a server push synchronously by calling `try_receive` on
  178. * the underlying channel. If the operation fails because
  179. * `try_receive` returns `false`, `ec` will be set to
  180. * `boost::redis::error::sync_receive_push_failed`.
  181. *
  182. * @param ec Contains the error if any occurred.
  183. *
  184. * @returns The number of bytes read from the socket.
  185. */
  186. std::size_t receive(system::error_code& ec)
  187. {
  188. return impl_.receive(ec);
  189. }
  190. template <
  191. class Response = ignore_t,
  192. class CompletionToken = asio::default_completion_token_t<executor_type>
  193. >
  194. [[deprecated("Set the response with set_receive_response and use the other overload.")]]
  195. auto
  196. async_receive(
  197. Response& response,
  198. CompletionToken token = CompletionToken{})
  199. {
  200. return impl_.async_receive(response, token);
  201. }
  202. /** @brief Executes commands on the Redis server asynchronously.
  203. *
  204. * This function sends a request to the Redis server and waits for
  205. * the responses to each individual command in the request. If the
  206. * request contains only commands that don't expect a response,
  207. * the completion occurs after it has been written to the
  208. * underlying stream. Multiple concurrent calls to this function
  209. * will be automatically queued by the implementation.
  210. *
  211. * @param req Request.
  212. * @param resp Response.
  213. * @param token Completion token.
  214. *
  215. * For an example see cpp20_echo_server.cpp. The completion token must
  216. * have the following signature
  217. *
  218. * @code
  219. * void f(system::error_code, std::size_t);
  220. * @endcode
  221. *
  222. * Where the second parameter is the size of the response received
  223. * in bytes.
  224. */
  225. template <
  226. class Response = ignore_t,
  227. class CompletionToken = asio::default_completion_token_t<executor_type>
  228. >
  229. auto
  230. async_exec(
  231. request const& req,
  232. Response& resp = ignore,
  233. CompletionToken&& token = CompletionToken{})
  234. {
  235. return impl_.async_exec(req, resp, std::forward<CompletionToken>(token));
  236. }
  237. /** @brief Cancel operations.
  238. *
  239. * @li `operation::exec`: Cancels operations started with
  240. * `async_exec`. Affects only requests that haven't been written
  241. * yet.
  242. * @li operation::run: Cancels the `async_run` operation.
  243. * @li operation::receive: Cancels any ongoing calls to `async_receive`.
  244. * @li operation::all: Cancels all operations listed above.
  245. *
  246. * @param op: The operation to be cancelled.
  247. */
  248. void cancel(operation op = operation::all)
  249. {
  250. switch (op) {
  251. case operation::reconnection:
  252. case operation::all:
  253. cfg_.reconnect_wait_interval = std::chrono::seconds::zero();
  254. timer_.cancel();
  255. break;
  256. default: /* ignore */;
  257. }
  258. impl_.cancel(op);
  259. }
  260. /// Returns true if the connection was canceled.
  261. bool will_reconnect() const noexcept
  262. { return cfg_.reconnect_wait_interval != std::chrono::seconds::zero();}
  263. /// Returns the ssl context.
  264. auto const& get_ssl_context() const noexcept
  265. { return impl_.get_ssl_context();}
  266. /// Returns the ssl context.
  267. auto& get_ssl_context() noexcept
  268. { return impl_.get_ssl_context();}
  269. /// Resets the underlying stream.
  270. void reset_stream()
  271. { impl_.reset_stream(); }
  272. /// Returns a reference to the next layer.
  273. auto& next_layer() noexcept
  274. { return impl_.next_layer(); }
  275. /// Returns a const reference to the next layer.
  276. auto const& next_layer() const noexcept
  277. { return impl_.next_layer(); }
  278. /// Sets the response object of `async_receive` operations.
  279. template <class Response>
  280. void set_receive_response(Response& response)
  281. { impl_.set_receive_response(response); }
  282. /// Returns connection usage information.
  283. usage get_usage() const noexcept
  284. { return impl_.get_usage(); }
  285. private:
  286. using timer_type =
  287. asio::basic_waitable_timer<
  288. std::chrono::steady_clock,
  289. asio::wait_traits<std::chrono::steady_clock>,
  290. Executor>;
  291. template <class, class> friend struct detail::reconnection_op;
  292. config cfg_;
  293. detail::connection_base<executor_type> impl_;
  294. timer_type timer_;
  295. };
  296. /** \brief A basic_connection that type erases the executor.
  297. * \ingroup high-level-api
  298. *
  299. * This connection type uses the asio::any_io_executor and
  300. * asio::any_completion_token to reduce compilation times.
  301. *
  302. * For documentaiton of each member function see
  303. * `boost::redis::basic_connection`.
  304. */
  305. class connection {
  306. public:
  307. /// Executor type.
  308. using executor_type = asio::any_io_executor;
  309. /// Contructs from an executor.
  310. explicit
  311. connection(
  312. executor_type ex,
  313. asio::ssl::context::method method = asio::ssl::context::tls_client,
  314. std::size_t max_read_size = (std::numeric_limits<std::size_t>::max)());
  315. /// Contructs from a context.
  316. explicit
  317. connection(
  318. asio::io_context& ioc,
  319. asio::ssl::context::method method = asio::ssl::context::tls_client,
  320. std::size_t max_read_size = (std::numeric_limits<std::size_t>::max)());
  321. /// Returns the underlying executor.
  322. executor_type get_executor() noexcept
  323. { return impl_.get_executor(); }
  324. /// Calls `boost::redis::basic_connection::async_run`.
  325. template <class CompletionToken>
  326. auto async_run(config const& cfg, logger l, CompletionToken token)
  327. {
  328. return asio::async_initiate<
  329. CompletionToken, void(boost::system::error_code)>(
  330. [](auto handler, connection* self, config const* cfg, logger l)
  331. {
  332. self->async_run_impl(*cfg, l, std::move(handler));
  333. }, token, this, &cfg, l);
  334. }
  335. /// Calls `boost::redis::basic_connection::async_receive`.
  336. template <class Response, class CompletionToken>
  337. [[deprecated("Set the response with set_receive_response and use the other overload.")]]
  338. auto async_receive(Response& response, CompletionToken token)
  339. {
  340. return impl_.async_receive(response, std::move(token));
  341. }
  342. /// Calls `boost::redis::basic_connection::async_receive`.
  343. template <class CompletionToken>
  344. auto async_receive(CompletionToken token)
  345. { return impl_.async_receive(std::move(token)); }
  346. /// Calls `boost::redis::basic_connection::receive`.
  347. std::size_t receive(system::error_code& ec)
  348. {
  349. return impl_.receive(ec);
  350. }
  351. /// Calls `boost::redis::basic_connection::async_exec`.
  352. template <class Response, class CompletionToken>
  353. auto async_exec(request const& req, Response& resp, CompletionToken token)
  354. {
  355. return impl_.async_exec(req, resp, std::move(token));
  356. }
  357. /// Calls `boost::redis::basic_connection::cancel`.
  358. void cancel(operation op = operation::all);
  359. /// Calls `boost::redis::basic_connection::will_reconnect`.
  360. bool will_reconnect() const noexcept
  361. { return impl_.will_reconnect();}
  362. /// Calls `boost::redis::basic_connection::next_layer`.
  363. auto& next_layer() noexcept
  364. { return impl_.next_layer(); }
  365. /// Calls `boost::redis::basic_connection::next_layer`.
  366. auto const& next_layer() const noexcept
  367. { return impl_.next_layer(); }
  368. /// Calls `boost::redis::basic_connection::reset_stream`.
  369. void reset_stream()
  370. { impl_.reset_stream();}
  371. /// Sets the response object of `async_receive` operations.
  372. template <class Response>
  373. void set_receive_response(Response& response)
  374. { impl_.set_receive_response(response); }
  375. /// Returns connection usage information.
  376. usage get_usage() const noexcept
  377. { return impl_.get_usage(); }
  378. private:
  379. void
  380. async_run_impl(
  381. config const& cfg,
  382. logger l,
  383. asio::any_completion_handler<void(boost::system::error_code)> token);
  384. basic_connection<executor_type> impl_;
  385. };
  386. } // boost::redis
  387. #endif // BOOST_REDIS_CONNECTION_HPP