diff --git a/benchmarks/benchmarks.md b/benchmarks/benchmarks.md index 68249e41..b54ca79b 100644 --- a/benchmarks/benchmarks.md +++ b/benchmarks/benchmarks.md @@ -68,7 +68,7 @@ in the graph, the reasons are I don't know for sure why it is so slow, I suppose it has something to do with its lack of proper [pipelining](https://redis.io/docs/manual/pipelining/) support. - In fact, the more TCP connections I lauch the worst its + In fact, the more TCP connections I lauch the worse its performance gets. * Libuv: I left it out because it would require too much work to diff --git a/include/aedis/connection_base.hpp b/include/aedis/connection_base.hpp index d4639772..e16fa736 100644 --- a/include/aedis/connection_base.hpp +++ b/include/aedis/connection_base.hpp @@ -69,6 +69,7 @@ public: , read_timer_{ex} , push_channel_{ex} , last_data_{std::chrono::time_point::min()} + , req_{true} { writer_timer_.expires_at(std::chrono::steady_clock::time_point::max()); read_timer_.expires_at(std::chrono::steady_clock::time_point::max()); @@ -79,7 +80,9 @@ public: /** @brief Cancel operations. * - * @li `operation::exec`: Cancels operations started with `async_exec`. + * @li `operation::exec`: Cancels operations started with + * `async_exec`. Has precedence over + * `request::close_on_connection_lost()` * @li operation::run: Cancels the `async_run` operation. Notice * that the preferred way to close a connection is to send a * [QUIT](https://redis.io/commands/quit/) command to the server. @@ -118,7 +121,7 @@ public: ping_timer_.cancel(); auto point = std::stable_partition(std::begin(reqs_), std::end(reqs_), [](auto const& ptr) { - return !ptr->req->close_on_run_completion; + return !ptr->req->close_on_connection_lost(); }); // Cancel own pings if there are any waiting. diff --git a/include/aedis/detail/connection_ops.hpp b/include/aedis/detail/connection_ops.hpp index 56f77bbe..a030144e 100644 --- a/include/aedis/detail/connection_ops.hpp +++ b/include/aedis/detail/connection_ops.hpp @@ -217,6 +217,13 @@ struct exec_op { { reenter (coro) { + if (req->close_on_connection_lost() && !conn->is_open()) { + // The user doesn't want to wait for the connection to be + // stablished. + self.complete(error::not_connected, 0); + return; + } + info = std::allocate_shared(boost::asio::get_associated_allocator(self), conn->resv_.get_executor()); info->timer.expires_at(std::chrono::steady_clock::time_point::max()); info->req = req; @@ -293,7 +300,6 @@ struct ping_op { conn->req_.clear(); conn->req_.push("PING"); - conn->req_.close_on_run_completion = true; yield conn->async_exec(conn->req_, adapt(), std::move(self)); if (ec) { @@ -576,8 +582,6 @@ struct runexec_op { { reenter (coro) { - req->close_on_run_completion = true; - yield boost::asio::experimental::make_parallel_group( [this, ep2 = ep](auto token) { return conn->async_run(ep2, token);}, diff --git a/include/aedis/error.hpp b/include/aedis/error.hpp index b6e45960..f28c5125 100644 --- a/include/aedis/error.hpp +++ b/include/aedis/error.hpp @@ -78,6 +78,9 @@ enum class error /// SSL handshake timeout. ssl_handshake_timeout, + + /// There is no stablished connection. + not_connected, }; /** \internal diff --git a/include/aedis/impl/endpoint.ipp b/include/aedis/impl/endpoint.ipp index 7df0a5fb..f54129aa 100644 --- a/include/aedis/impl/endpoint.ipp +++ b/include/aedis/impl/endpoint.ipp @@ -4,6 +4,8 @@ * accompanying file LICENSE.txt) */ +#include + #include namespace aedis { diff --git a/include/aedis/impl/error.ipp b/include/aedis/impl/error.ipp index 8523c5c4..b78d0085 100644 --- a/include/aedis/impl/error.ipp +++ b/include/aedis/impl/error.ipp @@ -42,6 +42,7 @@ struct error_category_impl : boost::system::error_category { case error::resp3_null: return "Got RESP3 null."; case error::unexpected_server_role: return "Unexpected server role."; case error::ssl_handshake_timeout: return "SSL handshake timeout."; + case error::not_connected: return "Not connected."; default: BOOST_ASSERT(false); return "Aedis error."; } } diff --git a/include/aedis/resp3/request.hpp b/include/aedis/resp3/request.hpp index 92dba2ca..3c1dd245 100644 --- a/include/aedis/resp3/request.hpp +++ b/include/aedis/resp3/request.hpp @@ -170,6 +170,18 @@ void add_separator(Request& to) */ class request { public: + /** @brief Constructor + * + * @param close_on_connection_lost If true, the requests started + * with `connection::async_exe` will fail either if the connection + * is lost while the request is pending or if `async_exec` is + * called when there is no connection with Redis. The default + * behaviour is not to close requests. + */ + explicit request(bool close_on_connection_lost = false) + : close_on_connection_lost_{close_on_connection_lost} + {} + //// Returns the number of commands contained in this request. auto size() const noexcept -> std::size_t { return commands_;}; @@ -326,11 +338,12 @@ public: push_range2(cmd, begin(range), end(range)); } - mutable bool close_on_run_completion = false; + auto close_on_connection_lost() const noexcept {return close_on_connection_lost_; } private: std::string payload_; std::size_t commands_ = 0; + bool close_on_connection_lost_ = false; }; } // aedis::resp3 diff --git a/tests/connection_other.cpp b/tests/connection_other.cpp index b781a61d..842fc66a 100644 --- a/tests/connection_other.cpp +++ b/tests/connection_other.cpp @@ -94,6 +94,7 @@ BOOST_AUTO_TEST_CASE(test_idle) BOOST_AUTO_TEST_CASE(test_wrong_data_type) { + std::cout << boost::unit_test::framework::current_test_case().p_name << std::endl; request req; req.push("QUIT"); @@ -108,3 +109,18 @@ BOOST_AUTO_TEST_CASE(test_wrong_data_type) ioc.run(); } + +BOOST_AUTO_TEST_CASE(test_not_connected) +{ + std::cout << boost::unit_test::framework::current_test_case().p_name << std::endl; + request req{true}; + req.push("PING"); + + net::io_context ioc; + auto db = std::make_shared(ioc); + db->async_exec(req, adapt(), [](auto ec, auto){ + BOOST_CHECK_EQUAL(ec, aedis::error::not_connected); + }); + + ioc.run(); +}