From bb18ff48912e2f219147f96a443336f90b51b98c Mon Sep 17 00:00:00 2001 From: Marcelo Zimbres Date: Tue, 7 Mar 2023 20:24:53 +0100 Subject: [PATCH 01/32] Adds missing include header. --- .github/workflows/coverage.yml | 2 +- include/boost/redis/adapter/result.hpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 00bb498e..8b944b26 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -3,7 +3,7 @@ name: Coverage on: push: branches: - - master + - develop jobs: posix: defaults: diff --git a/include/boost/redis/adapter/result.hpp b/include/boost/redis/adapter/result.hpp index d36e3ca9..a1f29f0e 100644 --- a/include/boost/redis/adapter/result.hpp +++ b/include/boost/redis/adapter/result.hpp @@ -9,6 +9,7 @@ #define BOOST_REDIS_ADAPTER_RESULT_HPP #include +#include #include #include From 728b35cfe08a1149eab2e9dc25306be2d0031030 Mon Sep 17 00:00:00 2001 From: Marcelo Zimbres Date: Fri, 10 Mar 2023 14:49:16 +0100 Subject: [PATCH 02/32] Adds protobuf example. --- .github/workflows/ci.yml | 2 + CMakeLists.txt | 31 ++++++-- README.md | 51 +++--------- ..._json_serialization.cpp => cpp20_json.cpp} | 56 +++++-------- examples/cpp20_protobuf.cpp | 78 +++++++++++++++++++ {include/boost/redis => examples}/json.hpp | 7 +- examples/person.proto | 9 +++ examples/protobuf.hpp | 40 ++++++++++ 8 files changed, 186 insertions(+), 88 deletions(-) rename examples/{cpp20_json_serialization.cpp => cpp20_json.cpp} (51%) create mode 100644 examples/cpp20_protobuf.cpp rename {include/boost/redis => examples}/json.hpp (73%) create mode 100644 examples/person.proto create mode 100644 examples/protobuf.hpp diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dc1e2c08..7ccf47e8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -115,6 +115,8 @@ jobs: uses: actions/checkout@v3 - name: Install CMake run: sudo apt-get -y install cmake + - name: Install protobuf + run: sudo apt-get -y install protobuf-compiler - name: Install compiler run: sudo apt-get install -y ${{ matrix.install }} - name: Install Redis diff --git a/CMakeLists.txt b/CMakeLists.txt index 113f4902..21b59820 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,6 +42,12 @@ write_basic_package_version_file( ) find_package(Boost 1.80 REQUIRED) + +# We test the protobuf example only on gcc. +if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + find_package(Protobuf) # For the protobuf example. +endif() + include_directories(${Boost_INCLUDE_DIRS}) find_package(OpenSSL REQUIRED) @@ -130,13 +136,26 @@ if (MSVC) target_compile_definitions(cpp20_resolve_with_sentinel PRIVATE _WIN32_WINNT=0x0601) endif() -add_executable(cpp20_json_serialization examples/cpp20_json_serialization.cpp) -target_compile_features(cpp20_json_serialization PUBLIC cxx_std_20) -target_link_libraries(cpp20_json_serialization common) -add_test(cpp20_json_serialization cpp20_json_serialization) +add_executable(cpp20_json examples/cpp20_json.cpp) +target_compile_features(cpp20_json PUBLIC cxx_std_20) +target_link_libraries(cpp20_json common) +add_test(cpp20_json cpp20_json) if (MSVC) - target_compile_options(cpp20_json_serialization PRIVATE /bigobj) - target_compile_definitions(cpp20_json_serialization PRIVATE _WIN32_WINNT=0x0601) + target_compile_options(cpp20_json PRIVATE /bigobj) + target_compile_definitions(cpp20_json PRIVATE _WIN32_WINNT=0x0601) +endif() + +if (Protobuf_FOUND) + protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS examples/person.proto) + add_executable(cpp20_protobuf examples/cpp20_protobuf.cpp ${PROTO_SRCS} ${PROTO_HDRS}) + target_compile_features(cpp20_protobuf PUBLIC cxx_std_20) + target_link_libraries(cpp20_protobuf common ${Protobuf_LIBRARIES}) + target_include_directories(cpp20_protobuf PUBLIC ${Protobuf_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR}) + add_test(cpp20_protobuf cpp20_protobuf) + if (MSVC) + target_compile_options(cpp20_protobuf PRIVATE /bigobj) + target_compile_definitions(cpp20_protobuf PRIVATE _WIN32_WINNT=0x0601) + endif() endif() if (NOT MSVC) diff --git a/README.md b/README.md index d2ce421e..50324d55 100644 --- a/README.md +++ b/README.md @@ -513,56 +513,24 @@ and other data structures in general. ## Serialization -Boost.Redis provides native support for serialization with Boost.Json. -To use it - -* Include boost/redis/serialization.hpp -* Describe your class with Boost.Describe. - -For example - -```cpp -#include - -struct user { - std::string name; - std::string age; - std::string country; -}; - -BOOST_DESCRIBE_STRUCT(user, (), (name, age, country)) -``` - -After that you will be able to user your described `struct` both in -requests and responses, for example - -```cpp -user foo{"Joao", "58", "Brazil"} - -request req; -req.push("PING", foo); - -response resp; - -co_await conn->async_exec(req, resp); -``` - -For other serialization formats it is necessary to define the -serialization functions `boost_redis_to_bulk` and `boost_redis_from_bulk` and -import them onto the global namespace so they become available over -ADL. They must have the following signature +Boost.Redis supports serialization of user defined types by means of +the following customization points ```cpp -// Serialize +// Serialize. void boost_redis_to_bulk(std::string& to, mystruct const& obj); // Deserialize void boost_redis_from_bulk(mystruct& obj, char const* p, std::size_t size, boost::system::error_code& ec) ``` -Example cpp20_json_serialization.cpp shows how store json strings in Redis. +These functions are accessed over ADL and therefore they must be +imported in the global namespace by the user. In the +[Examples](#Examples) section the reader can find examples showing how +to serialize using json and [protobuf](https://protobuf.dev/). + ## Examples The examples below show how to use the features discussed so far @@ -571,7 +539,8 @@ The examples below show how to use the features discussed so far * cpp20_intro.cpp: Does not use awaitable operators. * cpp20_intro_tls.cpp: Communicates over TLS. * cpp20_containers.cpp: Shows how to send and receive STL containers and how to use transactions. -* cpp20_json_serialization.cpp: Shows how to serialize types using Boost.Json. +* cpp20_json.cpp: Shows how to serialize types using Boost.Json. +* cpp20_protobuf.cpp: Shows how to serialize types using protobuf. * cpp20_resolve_with_sentinel.cpp: Shows how to resolve a master address using sentinels. * cpp20_subscriber.cpp: Shows how to implement pubsub with reconnection re-subscription. * cpp20_echo_server.cpp: A simple TCP echo server. diff --git a/examples/cpp20_json_serialization.cpp b/examples/cpp20_json.cpp similarity index 51% rename from examples/cpp20_json_serialization.cpp rename to examples/cpp20_json.cpp index c47171a8..968889c4 100644 --- a/examples/cpp20_json_serialization.cpp +++ b/examples/cpp20_json.cpp @@ -10,11 +10,10 @@ #define BOOST_CONTAINER_NO_LIB #include #include -#include -#include #include #include #include "common/common.hpp" +#include "json.hpp" // Include this in no more than one .cpp file. #include @@ -27,18 +26,23 @@ using boost::redis::response; using boost::redis::operation; using boost::redis::ignore_t; +// Struct that will be stored in Redis using json serialization. struct user { std::string name; std::string age; std::string country; - - friend - auto operator<(user const& a, user const& b) - { return std::tie(a.name, a.age, a.country) < std::tie(b.name, b.age, b.country); } }; +// The type must be described for serialization to work. BOOST_DESCRIBE_STRUCT(user, (), (name, age, country)) +// Boost.Redis customization points (examples/json.hpp) +void boost_redis_to_bulk(std::string& to, user const& u) + { boost::redis::json::to_bulk(to, u); } + +void boost_redis_from_bulk(user& u, std::string_view sv, boost::system::error_code& ec) + { boost::redis::json::from_bulk(u, sv, ec); } + auto run(std::shared_ptr conn, std::string host, std::string port) -> net::awaitable { co_await connect(conn, host, port); @@ -51,44 +55,24 @@ net::awaitable co_main(std::string host, std::string port) auto conn = std::make_shared(ex); net::co_spawn(ex, run(conn, host, port), net::detached); - // A set of users that will be automatically serialized to json. - std::set users - {{"Joao", "58", "Brazil"} , {"Serge", "60", "France"}}; + // user object that will be stored in Redis in json format. + user const u{"Joao", "58", "Brazil"}; - // To simplify we send the set and retrieve it in the same - // resquest. + // Stores and retrieves in the same request. request req; req.push("HELLO", 3); + req.push("SET", "json-key", u); // Stores in Redis. + req.push("GET", "json-key"); // Retrieves from Redis. - // Stores a std::set in a Redis set data structure. - req.push_range("SADD", "sadd-key", users); - - // Sends a ping and retrieves it as a string to show what json - // serialization looks like. - req.push("PING", *users.begin()); - - // Sends another ping and retrieves it directly in a user type. - req.push("PING", *users.begin()); + response resp; - // Retrieves the set we have just stored. - req.push("SMEMBERS", "sadd-key"); - - response> resp; - - // Sends the request and receives the response. co_await conn->async_exec(req, resp); // Prints the first ping - auto const& pong1 = std::get<2>(resp).value(); - std::cout << pong1 << "\n"; - - // Prints the second ping. - auto const& pong2 = std::get<3>(resp).value(); - std::cout << pong2.name << " " << pong2.age << " " << pong2.country << "\n"; - - // Prints the set. - for (auto const& e: std::get<4>(resp).value()) - std::cout << e.name << " " << e.age << " " << e.country << "\n"; + std::cout + << "Name: " << std::get<2>(resp).value().name << "\n" + << "Age: " << std::get<2>(resp).value().age << "\n" + << "Country: " << std::get<2>(resp).value().country << "\n"; conn->cancel(operation::run); } diff --git a/examples/cpp20_protobuf.cpp b/examples/cpp20_protobuf.cpp new file mode 100644 index 00000000..cb807370 --- /dev/null +++ b/examples/cpp20_protobuf.cpp @@ -0,0 +1,78 @@ +/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com) + * + * Distributed under the Boost Software License, Version 1.0. (See + * accompanying file LICENSE.txt) + */ + +#include +#if defined(BOOST_ASIO_HAS_CO_AWAIT) +#include +#include +#include "common/common.hpp" +#include "protobuf.hpp" + +// See the definition in person.proto. This header is automatically +// generated by CMakeLists.txt. +#include "person.pb.h" + +namespace net = boost::asio; +namespace redis = boost::redis; +using boost::redis::request; +using boost::redis::response; +using boost::redis::operation; +using boost::redis::ignore_t; + +// The protobuf type described in examples/person.proto +using tutorial::person; + +// Boost.Redis customization points (examples/protobuf.hpp) +namespace tutorial +{ + +void boost_redis_to_bulk(std::string& to, person const& u) + { boost::redis::protobuf::to_bulk(to, u); } + +void boost_redis_from_bulk(person& u, std::string_view sv, boost::system::error_code& ec) + { boost::redis::protobuf::from_bulk(u, sv, ec); } + +} // tutorial + +using tutorial::boost_redis_to_bulk; +using tutorial::boost_redis_from_bulk; + +auto run(std::shared_ptr conn, std::string host, std::string port) -> net::awaitable +{ + co_await connect(conn, host, port); + co_await conn->async_run(); +} + +net::awaitable co_main(std::string host, std::string port) +{ + auto ex = co_await net::this_coro::executor; + auto conn = std::make_shared(ex); + net::co_spawn(ex, run(conn, host, port), net::detached); + + person p; + p.set_name("Louis"); + p.set_id(3); + p.set_email("No email yet."); + + request req; + req.push("HELLO", 3); + req.push("SET", "protobuf-key", p); + req.push("GET", "protobuf-key"); + + response resp; + + // Sends the request and receives the response. + co_await conn->async_exec(req, resp); + + std::cout + << "Name: " << std::get<2>(resp).value().name() << "\n" + << "Age: " << std::get<2>(resp).value().id() << "\n" + << "Email: " << std::get<2>(resp).value().email() << "\n"; + + conn->cancel(operation::run); +} + +#endif // defined(BOOST_ASIO_HAS_CO_AWAIT) diff --git a/include/boost/redis/json.hpp b/examples/json.hpp similarity index 73% rename from include/boost/redis/json.hpp rename to examples/json.hpp index 2d75c941..af9d333c 100644 --- a/include/boost/redis/json.hpp +++ b/examples/json.hpp @@ -14,13 +14,13 @@ namespace boost::redis::json { template -void boost_redis_to_bulk(std::string& to, T const& u) +void to_bulk(std::string& to, T const& u) { redis::resp3::boost_redis_to_bulk(to, boost::json::serialize(boost::json::value_from(u))); } template -void boost_redis_from_bulk(T& u, std::string_view sv, system::error_code&) +void from_bulk(T& u, std::string_view sv, system::error_code&) { auto const jv = boost::json::parse(sv); u = boost::json::value_to(jv); @@ -28,7 +28,4 @@ void boost_redis_from_bulk(T& u, std::string_view sv, system::error_code&) } // boost::redis::json -using boost::redis::json::boost_redis_to_bulk; -using boost::redis::json::boost_redis_from_bulk; - #endif // BOOST_REDIS_JSON_HPP diff --git a/examples/person.proto b/examples/person.proto new file mode 100644 index 00000000..bec5b2c2 --- /dev/null +++ b/examples/person.proto @@ -0,0 +1,9 @@ +syntax = "proto2"; + +package tutorial; + +message person { + optional string name = 1; + optional int32 id = 2; + optional string email = 3; +} diff --git a/examples/protobuf.hpp b/examples/protobuf.hpp new file mode 100644 index 00000000..b1ba1231 --- /dev/null +++ b/examples/protobuf.hpp @@ -0,0 +1,40 @@ +/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com) + * + * Distributed under the Boost Software License, Version 1.0. (See + * accompanying file LICENSE.txt) + */ + +#ifndef BOOST_REDIS_PROTOBUF_HPP +#define BOOST_REDIS_PROTOBUF_HPP + +#include +#include + +namespace boost::redis::protobuf +{ + +// Below I am using a Boost.Redis to indicate a protobuf error, this +// is ok for an example, users however might want to define their own +// error codes. + +template +void to_bulk(std::string& to, T const& u) +{ + std::string tmp; + if (!u.SerializeToString(&tmp)) + throw system::system_error(redis::error::invalid_data_type); + + boost::redis::resp3::boost_redis_to_bulk(to, tmp); +} + +template +void from_bulk(T& u, std::string_view sv, system::error_code& ec) +{ + std::string const tmp {sv}; + if (!u.ParseFromString(tmp)) + ec = redis::error::invalid_data_type; +} + +} // boost::redis::json + +#endif // BOOST_REDIS_PROTOBUF_HPP From fd967204dfdac00529475252188456ef7ead4de2 Mon Sep 17 00:00:00 2001 From: Marcelo Zimbres Date: Sun, 12 Mar 2023 21:54:32 +0100 Subject: [PATCH 03/32] Implements non-member async_run for plain connections. This function will resolve and connect before calling member async_run. --- CMakeLists.txt | 18 +- README.md | 3 + examples/{common => }/boost_redis.cpp | 0 examples/common/common.cpp | 68 ------ examples/common/common.hpp | 33 --- examples/cpp17_intro.cpp | 74 ++---- examples/cpp17_intro_sync.cpp | 27 ++- examples/cpp20_chat_room.cpp | 11 +- examples/cpp20_containers.cpp | 10 +- examples/cpp20_echo_server.cpp | 10 +- examples/cpp20_intro.cpp | 13 +- examples/cpp20_intro_awaitable_ops.cpp | 9 +- examples/cpp20_json.cpp | 6 +- examples/cpp20_protobuf.cpp | 6 +- examples/cpp20_resolve_with_sentinel.cpp | 8 +- examples/cpp20_subscriber.cpp | 13 +- examples/{common => }/main.cpp | 4 +- examples/start.cpp | 33 +++ examples/start.hpp | 17 ++ include/boost/redis.hpp | 1 + .../run.hpp => check_health.hpp} | 28 ++- include/boost/redis/connection.hpp | 8 +- .../boost/redis/detail/connection_base.hpp | 15 +- include/boost/redis/detail/runner.hpp | 225 ++++++++++++++++++ include/boost/redis/error.hpp | 6 + include/boost/redis/impl/error.ipp | 2 + include/boost/redis/operation.hpp | 2 + include/boost/redis/run.hpp | 63 +++++ include/boost/redis/ssl/connection.hpp | 2 +- tests/common.hpp | 15 +- tests/conn_check_health.cpp | 13 +- tests/conn_echo_stress.cpp | 10 +- tests/conn_exec.cpp | 10 +- tests/conn_exec_cancel.cpp | 19 +- tests/conn_exec_error.cpp | 27 +-- tests/conn_exec_retry.cpp | 15 +- tests/conn_push.cpp | 190 ++++++++------- tests/conn_quit.cpp | 10 +- tests/conn_reconnect.cpp | 17 +- tests/conn_run_cancel.cpp | 16 +- tests/conn_tls.cpp | 12 + tests/issue_50.cpp | 13 +- tests/run.cpp | 76 ++++++ 43 files changed, 719 insertions(+), 439 deletions(-) rename examples/{common => }/boost_redis.cpp (100%) delete mode 100644 examples/common/common.cpp delete mode 100644 examples/common/common.hpp rename examples/{common => }/main.cpp (92%) create mode 100644 examples/start.cpp create mode 100644 examples/start.hpp rename include/boost/redis/{experimental/run.hpp => check_health.hpp} (85%) create mode 100644 include/boost/redis/detail/runner.hpp create mode 100644 include/boost/redis/run.hpp create mode 100644 tests/run.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 21b59820..5c30a93c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -59,9 +59,9 @@ include_directories(include) #======================================================================= add_library(common STATIC - examples/common/common.cpp - examples/common/main.cpp - examples/common/boost_redis.cpp + examples/start.cpp + examples/main.cpp + examples/boost_redis.cpp ) target_compile_features(common PUBLIC cxx_std_20) if (MSVC) @@ -98,12 +98,10 @@ if (MSVC) target_compile_definitions(cpp17_intro PRIVATE _WIN32_WINNT=0x0601) endif() +if (NOT MSVC) add_executable(cpp17_intro_sync examples/cpp17_intro_sync.cpp) target_compile_features(cpp17_intro_sync PUBLIC cxx_std_17) add_test(cpp17_intro_sync cpp17_intro_sync) -if (MSVC) - target_compile_options(cpp17_intro_sync PRIVATE /bigobj) - target_compile_definitions(cpp17_intro_sync PRIVATE _WIN32_WINNT=0x0601) endif() if (NOT MSVC) @@ -320,6 +318,14 @@ target_link_libraries(test_conn_check_health common) add_test(test_conn_check_health test_conn_check_health) endif() +add_executable(test_run tests/run.cpp) +target_compile_features(test_run PUBLIC cxx_std_17) +add_test(test_run test_run) +if (MSVC) + target_compile_options(test_run PRIVATE /bigobj) + target_compile_definitions(test_run PRIVATE _WIN32_WINNT=0x0601) +endif() + # Install #======================================================================= diff --git a/README.md b/README.md index 50324d55..ae682a4c 100644 --- a/README.md +++ b/README.md @@ -831,6 +831,9 @@ https://lists.boost.org/Archives/boost/2023/01/253944.php. * Adds a function that performs health checks, see `boost::redis::experimental::async_check_health`. +* Adds non-member `async_run` function that resolves, connects and + calls member `async_run` on a connection object. + ### v1.4.0-1 * Renames `retry_on_connection_lost` to `cancel_if_unresponded`. (v1.4.1) diff --git a/examples/common/boost_redis.cpp b/examples/boost_redis.cpp similarity index 100% rename from examples/common/boost_redis.cpp rename to examples/boost_redis.cpp diff --git a/examples/common/common.cpp b/examples/common/common.cpp deleted file mode 100644 index 8b6725c7..00000000 --- a/examples/common/common.cpp +++ /dev/null @@ -1,68 +0,0 @@ -/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com) - * - * Distributed under the Boost Software License, Version 1.0. (See - * accompanying file LICENSE.txt) - */ - -#include "common.hpp" - -#include -#if defined(BOOST_ASIO_HAS_CO_AWAIT) -#include -#include - -namespace net = boost::asio; -using namespace net::experimental::awaitable_operators; -using resolver = net::use_awaitable_t<>::as_default_on_t; -using timer_type = net::use_awaitable_t<>::as_default_on_t; -using boost::redis::request; -using boost::redis::operation; - -namespace -{ -auto redir(boost::system::error_code& ec) - { return net::redirect_error(net::use_awaitable, ec); } -} - -auto -connect( - std::shared_ptr conn, - std::string const& host, - std::string const& port) -> net::awaitable -{ - auto ex = co_await net::this_coro::executor; - resolver resv{ex}; - timer_type timer{ex}; - - boost::system::error_code ec; - timer.expires_after(std::chrono::seconds{5}); - auto const addrs = co_await (resv.async_resolve(host, port) || timer.async_wait(redir(ec))); - if (!ec) - throw std::runtime_error("Resolve timeout"); - - timer.expires_after(std::chrono::seconds{5}); - co_await (net::async_connect(conn->next_layer(), std::get<0>(addrs)) || timer.async_wait(redir(ec))); - if (!ec) - throw std::runtime_error("Connect timeout"); -} - -auto run(net::awaitable op) -> int -{ - try { - net::io_context ioc; - net::co_spawn(ioc, std::move(op), [](std::exception_ptr p) { - if (p) - std::rethrow_exception(p); - }); - ioc.run(); - - return 0; - - } catch (std::exception const& e) { - std::cerr << "Error: " << e.what() << std::endl; - } - - return 1; -} - -#endif // defined(BOOST_ASIO_HAS_CO_AWAIT) diff --git a/examples/common/common.hpp b/examples/common/common.hpp deleted file mode 100644 index dc5a462c..00000000 --- a/examples/common/common.hpp +++ /dev/null @@ -1,33 +0,0 @@ -/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com) - * - * Distributed under the Boost Software License, Version 1.0. (See - * accompanying file LICENSE.txt) - */ - -#ifndef BOOST_REDIS_EXAMPLES_COMMON_HPP -#define BOOST_REDIS_EXAMPLES_COMMON_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#if defined(BOOST_ASIO_HAS_CO_AWAIT) - -using connection = boost::asio::use_awaitable_t<>::as_default_on_t; - -auto -connect( - std::shared_ptr conn, - std::string const& host, - std::string const& port) -> boost::asio::awaitable; - -auto run(boost::asio::awaitable op) -> int; - -#endif // defined(BOOST_ASIO_HAS_CO_AWAIT) -#endif // BOOST_REDIS_EXAMPLES_COMMON_HPP diff --git a/examples/cpp17_intro.cpp b/examples/cpp17_intro.cpp index e1d34f18..d53ee587 100644 --- a/examples/cpp17_intro.cpp +++ b/examples/cpp17_intro.cpp @@ -10,16 +10,12 @@ #include namespace net = boost::asio; -namespace redis = boost::redis; -using redis::operation; -using redis::request; -using redis::response; -using redis::ignore_t; - -void log(boost::system::error_code const& ec, char const* prefix) -{ - std::clog << prefix << ec.message() << std::endl; -} +using boost::redis::connection; +using boost::redis::request; +using boost::redis::response; +using boost::redis::ignore_t; +using boost::redis::async_run; +using namespace std::chrono_literals; auto main(int argc, char * argv[]) -> int { @@ -36,59 +32,22 @@ auto main(int argc, char * argv[]) -> int request req; req.push("HELLO", 3); req.push("PING", "Hello world"); - req.push("QUIT"); // The response. - response resp; + response resp; net::io_context ioc; + connection conn{ioc}; - // IO objects. - net::ip::tcp::resolver resv{ioc}; - redis::connection conn{ioc}; - - // Resolve endpoints. - net::ip::tcp::resolver::results_type endpoints; - - // async_run callback. - auto on_run = [](auto ec) - { - if (ec) - return log(ec, "on_run: "); - }; - - // async_exec callback. - auto on_exec = [&](auto ec, auto) - { - if (ec) { - conn.cancel(operation::run); - return log(ec, "on_exec: "); - } + async_run(conn, host, port, 10s, 10s, [&](auto){ + conn.cancel(); + }); - std::cout << "PING: " << std::get<1>(resp).value() << std::endl; - }; - - // Connect callback. - auto on_connect = [&](auto ec, auto) - { - if (ec) - return log(ec, "on_connect: "); - - conn.async_run(on_run); - conn.async_exec(req, resp, on_exec); - }; - - // Resolve callback. - auto on_resolve = [&](auto ec, auto const& addrs) - { - if (ec) - return log(ec, "on_resolve: "); - - endpoints = addrs; - net::async_connect(conn.next_layer(), endpoints, on_connect); - }; - - resv.async_resolve(host, port, on_resolve); + conn.async_exec(req, resp, [&](auto ec, auto){ + if (!ec) + std::cout << "PING: " << std::get<1>(resp).value() << std::endl; + conn.cancel(); + }); ioc.run(); return 0; @@ -96,7 +55,6 @@ auto main(int argc, char * argv[]) -> int } catch (std::exception const& e) { std::cerr << "Error: " << e.what() << std::endl; } - return 1; } diff --git a/examples/cpp17_intro_sync.cpp b/examples/cpp17_intro_sync.cpp index e6ba8b96..5621b665 100644 --- a/examples/cpp17_intro_sync.cpp +++ b/examples/cpp17_intro_sync.cpp @@ -6,19 +6,25 @@ #include #include +#include #include #include #include #include +#include // Include this in no more than one .cpp file. #include namespace net = boost::asio; using connection = boost::redis::connection; +using boost::redis::operation; using boost::redis::request; using boost::redis::response; using boost::redis::ignore_t; +using boost::redis::async_run; +using boost::redis::async_check_health; +using namespace std::chrono_literals; template auto exec(std::shared_ptr conn, request const& req, Response& resp) @@ -29,9 +35,6 @@ auto exec(std::shared_ptr conn, request const& req, Response& resp) (net::use_future).get(); } -auto logger = [](auto const& ec) - { std::clog << "Run: " << ec.message() << std::endl; }; - auto main(int argc, char * argv[]) -> int { try { @@ -47,17 +50,17 @@ auto main(int argc, char * argv[]) -> int auto conn = std::make_shared(ioc); - // Resolves the address - net::ip::tcp::resolver resv{ioc}; - auto const res = resv.resolve(host, port); + // Starts a thread that will can io_context::run on which the + // connection will run. + std::thread t{[&ioc, conn, host, port]() { + async_run(*conn, host, port, 10s, 10s, [conn](auto){ + conn->cancel(); + }); - // Connect to Redis - net::connect(conn->next_layer(), res); + async_check_health(*conn, "Boost.Redis", 2s, [conn](auto) { + conn->cancel(); + }); - // Starts a thread that will can io_context::run on which - // the connection will run. - std::thread t{[conn, &ioc]() { - conn->async_run(logger); ioc.run(); }}; diff --git a/examples/cpp20_chat_room.cpp b/examples/cpp20_chat_room.cpp index 744fa70a..f02bb541 100644 --- a/examples/cpp20_chat_room.cpp +++ b/examples/cpp20_chat_room.cpp @@ -11,17 +11,17 @@ namespace net = boost::asio; #if defined(BOOST_ASIO_HAS_POSIX_STREAM_DESCRIPTOR) #include #include -#include +#include #include -#include "common/common.hpp" - using namespace net::experimental::awaitable_operators; using stream_descriptor = net::use_awaitable_t<>::as_default_on_t; using signal_set = net::use_awaitable_t<>::as_default_on_t; using boost::redis::request; using boost::redis::generic_response; -using boost::redis::experimental::async_check_health; +using boost::redis::async_check_health; +using boost::redis::async_run; +using connection = boost::asio::use_awaitable_t<>::as_default_on_t; // Chat over Redis pubsub. To test, run this program from multiple // terminals and type messages to stdin. @@ -60,8 +60,7 @@ auto co_main(std::string host, std::string port) -> net::awaitable req.push("HELLO", 3); req.push("SUBSCRIBE", "chat-channel"); - co_await connect(conn, host, port); - co_await ((conn->async_run() || publisher(stream, conn) || receiver(conn) || + co_await ((async_run(*conn, host, port) || publisher(stream, conn) || receiver(conn) || async_check_health(*conn) || sig.async_wait()) && conn->async_exec(req)); } diff --git a/examples/cpp20_containers.cpp b/examples/cpp20_containers.cpp index e29fc900..f000a6ef 100644 --- a/examples/cpp20_containers.cpp +++ b/examples/cpp20_containers.cpp @@ -6,19 +6,18 @@ #include #if defined(BOOST_ASIO_HAS_CO_AWAIT) -#include #include #include #include - -#include "common/common.hpp" +#include namespace net = boost::asio; namespace redis = boost::redis; -using namespace net::experimental::awaitable_operators; using boost::redis::request; using boost::redis::response; using boost::redis::ignore_t; +using boost::redis::async_run; +using connection = boost::asio::use_awaitable_t<>::as_default_on_t; void print(std::map const& cont) { @@ -34,8 +33,7 @@ void print(std::vector const& cont) auto run(std::shared_ptr conn, std::string host, std::string port) -> net::awaitable { - co_await connect(conn, host, port); - co_await conn->async_run(); + co_await async_run(*conn, host, port); } // Stores the content of some STL containers in Redis. diff --git a/examples/cpp20_echo_server.cpp b/examples/cpp20_echo_server.cpp index 292f7098..0be953c4 100644 --- a/examples/cpp20_echo_server.cpp +++ b/examples/cpp20_echo_server.cpp @@ -8,8 +8,7 @@ #if defined(BOOST_ASIO_HAS_CO_AWAIT) #include #include -#include -#include "common/common.hpp" +#include namespace net = boost::asio; using namespace net::experimental::awaitable_operators; @@ -18,7 +17,9 @@ using tcp_acceptor = net::use_awaitable_t<>::as_default_on_t::as_default_on_t; using boost::redis::request; using boost::redis::response; -using boost::redis::experimental::async_check_health; +using boost::redis::async_check_health; +using boost::redis::async_run; +using connection = boost::asio::use_awaitable_t<>::as_default_on_t; auto echo_server_session(tcp_socket socket, std::shared_ptr conn) -> net::awaitable { @@ -55,8 +56,7 @@ auto co_main(std::string host, std::string port) -> net::awaitable request req; req.push("HELLO", 3); - co_await connect(conn, host, port); - co_await ((conn->async_run() || listener(conn) || async_check_health(*conn) || + co_await ((async_run(*conn, host, port) || listener(conn) || async_check_health(*conn) || sig.async_wait()) && conn->async_exec(req)); } diff --git a/examples/cpp20_intro.cpp b/examples/cpp20_intro.cpp index 1dc92079..fc75a724 100644 --- a/examples/cpp20_intro.cpp +++ b/examples/cpp20_intro.cpp @@ -7,25 +7,22 @@ #include #if defined(BOOST_ASIO_HAS_CO_AWAIT) #include -#include "common/common.hpp" +#include namespace net = boost::asio; -using boost::redis::operation; using boost::redis::request; using boost::redis::response; using boost::redis::ignore_t; +using boost::redis::async_run; +using connection = boost::asio::use_awaitable_t<>::as_default_on_t; auto run(std::shared_ptr conn, std::string host, std::string port) -> net::awaitable { - // From examples/common.hpp to avoid vebosity - co_await connect(conn, host, port); - // async_run coordinate read and write operations. - co_await conn->async_run(); + co_await async_run(*conn, host, port); // Cancel pending operations, if any. - conn->cancel(operation::exec); - conn->cancel(operation::receive); + conn->cancel(); } // Called from the main function (see main.cpp) diff --git a/examples/cpp20_intro_awaitable_ops.cpp b/examples/cpp20_intro_awaitable_ops.cpp index 7759fbc2..2c303e48 100644 --- a/examples/cpp20_intro_awaitable_ops.cpp +++ b/examples/cpp20_intro_awaitable_ops.cpp @@ -4,17 +4,19 @@ * accompanying file LICENSE.txt) */ +#include #include #if defined(BOOST_ASIO_HAS_CO_AWAIT) #include #include -#include "common/common.hpp" namespace net = boost::asio; using namespace net::experimental::awaitable_operators; using boost::redis::request; using boost::redis::response; using boost::redis::ignore_t; +using boost::redis::async_run; +using connection = boost::asio::use_awaitable_t<>::as_default_on_t; // Called from the main function (see main.cpp) auto co_main(std::string host, std::string port) -> net::awaitable @@ -27,9 +29,8 @@ auto co_main(std::string host, std::string port) -> net::awaitable response resp; - auto conn = std::make_shared(co_await net::this_coro::executor); - co_await connect(conn, host, port); - co_await (conn->async_run() || conn->async_exec(req, resp)); + connection conn{co_await net::this_coro::executor}; + co_await (async_run(conn, host, port) || conn.async_exec(req, resp)); std::cout << "PING: " << std::get<1>(resp).value() << std::endl; } catch (std::exception const& e) { diff --git a/examples/cpp20_json.cpp b/examples/cpp20_json.cpp index 968889c4..8a922259 100644 --- a/examples/cpp20_json.cpp +++ b/examples/cpp20_json.cpp @@ -12,7 +12,6 @@ #include #include #include -#include "common/common.hpp" #include "json.hpp" // Include this in no more than one .cpp file. @@ -25,6 +24,8 @@ using boost::redis::request; using boost::redis::response; using boost::redis::operation; using boost::redis::ignore_t; +using boost::redis::async_run; +using connection = boost::asio::use_awaitable_t<>::as_default_on_t; // Struct that will be stored in Redis using json serialization. struct user { @@ -45,8 +46,7 @@ void boost_redis_from_bulk(user& u, std::string_view sv, boost::system::error_co auto run(std::shared_ptr conn, std::string host, std::string port) -> net::awaitable { - co_await connect(conn, host, port); - co_await conn->async_run(); + co_await async_run(*conn, host, port); } net::awaitable co_main(std::string host, std::string port) diff --git a/examples/cpp20_protobuf.cpp b/examples/cpp20_protobuf.cpp index cb807370..9bc89264 100644 --- a/examples/cpp20_protobuf.cpp +++ b/examples/cpp20_protobuf.cpp @@ -8,7 +8,6 @@ #if defined(BOOST_ASIO_HAS_CO_AWAIT) #include #include -#include "common/common.hpp" #include "protobuf.hpp" // See the definition in person.proto. This header is automatically @@ -21,6 +20,8 @@ using boost::redis::request; using boost::redis::response; using boost::redis::operation; using boost::redis::ignore_t; +using boost::redis::async_run; +using connection = boost::asio::use_awaitable_t<>::as_default_on_t; // The protobuf type described in examples/person.proto using tutorial::person; @@ -42,8 +43,7 @@ using tutorial::boost_redis_from_bulk; auto run(std::shared_ptr conn, std::string host, std::string port) -> net::awaitable { - co_await connect(conn, host, port); - co_await conn->async_run(); + co_await async_run(*conn, host, port); } net::awaitable co_main(std::string host, std::string port) diff --git a/examples/cpp20_resolve_with_sentinel.cpp b/examples/cpp20_resolve_with_sentinel.cpp index d461513d..71cc1d87 100644 --- a/examples/cpp20_resolve_with_sentinel.cpp +++ b/examples/cpp20_resolve_with_sentinel.cpp @@ -4,19 +4,20 @@ * accompanying file LICENSE.txt) */ +#include #include #if defined(BOOST_ASIO_HAS_CO_AWAIT) #include #include -#include "common/common.hpp" - namespace net = boost::asio; using namespace net::experimental::awaitable_operators; using endpoints = net::ip::tcp::resolver::results_type; using boost::redis::request; using boost::redis::response; using boost::redis::ignore_t; +using boost::redis::async_run; +using connection = boost::asio::use_awaitable_t<>::as_default_on_t; auto redir(boost::system::error_code& ec) { return net::redirect_error(net::use_awaitable, ec); } @@ -40,8 +41,7 @@ auto resolve_master_address(std::vector
const& endpoints) -> net::await response>, ignore_t> addr; for (auto ep : endpoints) { boost::system::error_code ec; - co_await connect(conn, ep.host, ep.port); - co_await (conn->async_run() && conn->async_exec(req, addr, redir(ec))); + co_await (async_run(*conn, ep.host, ep.port) && conn->async_exec(req, addr, redir(ec))); conn->reset_stream(); if (std::get<0>(addr)) co_return address{std::get<0>(addr).value().value().at(0), std::get<0>(addr).value().value().at(1)}; diff --git a/examples/cpp20_subscriber.cpp b/examples/cpp20_subscriber.cpp index 44f9c953..b34c5e8f 100644 --- a/examples/cpp20_subscriber.cpp +++ b/examples/cpp20_subscriber.cpp @@ -4,20 +4,21 @@ * accompanying file LICENSE.txt) */ +#include #include #if defined(BOOST_ASIO_HAS_CO_AWAIT) #include #include -#include - -#include "common/common.hpp" +#include namespace net = boost::asio; using namespace net::experimental::awaitable_operators; using steady_timer = net::use_awaitable_t<>::as_default_on_t; using boost::redis::request; +using boost::redis::async_run; using boost::redis::generic_response; -using boost::redis::experimental::async_check_health; +using boost::redis::async_check_health; +using connection = boost::asio::use_awaitable_t<>::as_default_on_t; /* This example will subscribe and read pushes indefinitely. * @@ -57,9 +58,7 @@ auto co_main(std::string host, std::string port) -> net::awaitable // The loop will reconnect on connection lost. To exit type Ctrl-C twice. for (;;) { - co_await connect(conn, host, port); - co_await ((conn->async_run() || async_check_health(*conn) || receiver(conn)) && conn->async_exec(req)); - + co_await ((async_run(*conn, host, port) || async_check_health(*conn) || receiver(conn)) && conn->async_exec(req)); conn->reset_stream(); timer.expires_after(std::chrono::seconds{1}); co_await timer.async_wait(); diff --git a/examples/common/main.cpp b/examples/main.cpp similarity index 92% rename from examples/common/main.cpp rename to examples/main.cpp index 33b56d78..f86a0949 100644 --- a/examples/common/main.cpp +++ b/examples/main.cpp @@ -8,7 +8,7 @@ #if defined(BOOST_ASIO_HAS_CO_AWAIT) -#include "common.hpp" +#include "start.hpp" extern boost::asio::awaitable co_main(std::string, std::string); @@ -22,7 +22,7 @@ auto main(int argc, char * argv[]) -> int port = argv[2]; } - return run(co_main(host, port)); + return start(co_main(host, port)); } #else // defined(BOOST_ASIO_HAS_CO_AWAIT) diff --git a/examples/start.cpp b/examples/start.cpp new file mode 100644 index 00000000..7cceb56b --- /dev/null +++ b/examples/start.cpp @@ -0,0 +1,33 @@ +/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com) + * + * Distributed under the Boost Software License, Version 1.0. (See + * accompanying file LICENSE.txt) + */ + +#include "start.hpp" +#include +#if defined(BOOST_ASIO_HAS_CO_AWAIT) +#include + +namespace net = boost::asio; + +auto start(net::awaitable op) -> int +{ + try { + net::io_context ioc; + net::co_spawn(ioc, std::move(op), [](std::exception_ptr p) { + if (p) + std::rethrow_exception(p); + }); + ioc.run(); + + return 0; + + } catch (std::exception const& e) { + std::cerr << "Error: " << e.what() << std::endl; + } + + return 1; +} + +#endif // defined(BOOST_ASIO_HAS_CO_AWAIT) diff --git a/examples/start.hpp b/examples/start.hpp new file mode 100644 index 00000000..6388686d --- /dev/null +++ b/examples/start.hpp @@ -0,0 +1,17 @@ +/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com) + * + * Distributed under the Boost Software License, Version 1.0. (See + * accompanying file LICENSE.txt) + */ + +#ifndef BOOST_REDIS_EXAMPLES_START_HPP +#define BOOST_REDIS_EXAMPLES_START_HPP + +#include + +#if defined(BOOST_ASIO_HAS_CO_AWAIT) + +auto start(boost::asio::awaitable op) -> int; + +#endif // defined(BOOST_ASIO_HAS_CO_AWAIT) +#endif // BOOST_REDIS_EXAMPLES_START_HPP diff --git a/include/boost/redis.hpp b/include/boost/redis.hpp index 0972fe0c..9eac385d 100644 --- a/include/boost/redis.hpp +++ b/include/boost/redis.hpp @@ -11,6 +11,7 @@ #include #include #include +#include /** @defgroup high-level-api Reference * diff --git a/include/boost/redis/experimental/run.hpp b/include/boost/redis/check_health.hpp similarity index 85% rename from include/boost/redis/experimental/run.hpp rename to include/boost/redis/check_health.hpp index c71b47cb..a18960f0 100644 --- a/include/boost/redis/experimental/run.hpp +++ b/include/boost/redis/check_health.hpp @@ -4,17 +4,22 @@ * accompanying file LICENSE.txt) */ -#ifndef BOOST_REDIS_RUN_HPP -#define BOOST_REDIS_RUN_HPP +#ifndef BOOST_REDIS_CHECK_HEALTH_HPP +#define BOOST_REDIS_CHECK_HEALTH_HPP // Has to included before promise.hpp to build on msvc. +#include +#include +#include +#include #include #include #include #include #include +#include -namespace boost::redis::experimental { +namespace boost::redis { namespace detail { template @@ -23,14 +28,17 @@ class check_health_op { using executor_type = typename Connection::executor_type; struct state { - using clock_type = std::chrono::steady_clock; - using clock_traits_type = asio::wait_traits; - using timer_type = asio::basic_waitable_timer; using promise_type = asio::experimental::promise; + using timer_type = + asio::basic_waitable_timer< + std::chrono::steady_clock, + asio::wait_traits, + executor_type>; + timer_type timer_; - request req_; - generic_response resp_; + redis::request req_; + redis::generic_response resp_; std::optional prom_; std::chrono::steady_clock::duration interval_; @@ -124,6 +132,6 @@ async_check_health( >(detail::check_health_op{conn, msg, interval}, token, conn); } -} // boost::redis::experimental +} // boost::redis -#endif // BOOST_REDIS_RUN_HPP +#endif // BOOST_REDIS_CHECK_HEALTH_HPP diff --git a/include/boost/redis/connection.hpp b/include/boost/redis/connection.hpp index 8c03ab80..84dd22d5 100644 --- a/include/boost/redis/connection.hpp +++ b/include/boost/redis/connection.hpp @@ -201,15 +201,15 @@ class basic_connection : * @li `operation::exec`: Cancels operations started with * `async_exec`. Affects only requests that haven't been written * yet. - * @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. + * @li operation::run: Cancels the `async_run` operation. * @li operation::receive: Cancels any ongoing calls to * `async_receive`. + * @li operation::all: Cancels all operations listed above. This + * is the default argument. * * @param op: The operation to be cancelled. * @returns The number of operations that have been canceled. */ - auto cancel(operation op) -> std::size_t + auto cancel(operation op = operation::all) -> std::size_t { return base_type::cancel(op); } /// Sets the maximum size of the read buffer. diff --git a/include/boost/redis/detail/connection_base.hpp b/include/boost/redis/detail/connection_base.hpp index 4e63f72a..b478d13c 100644 --- a/include/boost/redis/detail/connection_base.hpp +++ b/include/boost/redis/detail/connection_base.hpp @@ -52,7 +52,7 @@ class connection_base { auto get_executor() {return writer_timer_.get_executor();} - auto cancel(operation op) -> std::size_t + auto cancel_impl(operation op) -> std::size_t { switch (op) { case operation::exec: @@ -77,6 +77,19 @@ class connection_base { } } + auto cancel(operation op) -> std::size_t + { + if (op == operation::all) { + std::size_t ret = 0; + ret += cancel_impl(operation::run); + ret += cancel_impl(operation::receive); + ret += cancel_impl(operation::exec); + return ret; + } + + return cancel_impl(op); + } + auto cancel_unwritten_requests() -> std::size_t { auto f = [](auto const& ptr) diff --git a/include/boost/redis/detail/runner.hpp b/include/boost/redis/detail/runner.hpp new file mode 100644 index 00000000..33bfae9d --- /dev/null +++ b/include/boost/redis/detail/runner.hpp @@ -0,0 +1,225 @@ +/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com) + * + * Distributed under the Boost Software License, Version 1.0. (See + * accompanying file LICENSE.txt) + */ + +#ifndef BOOST_REDIS_RUNNER_HPP +#define BOOST_REDIS_RUNNER_HPP + +// Has to included before promise.hpp to build on msvc. +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace boost::redis::detail { + +template +struct resolve_op { + Runner* runner = nullptr; + std::string_view host; + std::string_view port; + asio::coroutine coro{}; + + template + void operator()( Self& self + , std::array order = {} + , system::error_code ec1 = {} + , asio::ip::tcp::resolver::results_type res = {} + , system::error_code ec2 = {}) + { + BOOST_ASIO_CORO_REENTER (coro) + { + BOOST_ASIO_CORO_YIELD + asio::experimental::make_parallel_group( + [this](auto token) { return runner->resv_.async_resolve(host.data(), port.data(), token);}, + [this](auto token) { return runner->timer_.async_wait(token);} + ).async_wait( + asio::experimental::wait_for_one(), + std::move(self)); + + if (is_cancelled(self)) { + self.complete(asio::error::operation_aborted); + return; + } + + switch (order[0]) { + case 0: { + // Resolver completed first. + runner->endpoints_ = res; + self.complete(ec1); + } break; + + case 1: { + if (ec2) { + // Timer completed first with error, perhaps a + // cancellation going on. + self.complete(ec2); + } else { + // Timer completed first without an error, this is a + // resolve timeout. + self.complete(error::resolve_timeout); + } + } break; + + default: BOOST_ASSERT(false); + } + } + } +}; + +template +struct connect_op { + Runner* runner = nullptr; + Stream* stream = nullptr; + asio::coroutine coro{}; + + template + void operator()( Self& self + , std::array order = {} + , system::error_code ec1 = {} + , asio::ip::tcp::endpoint const& = {} + , system::error_code ec2 = {}) + { + BOOST_ASIO_CORO_REENTER (coro) + { + BOOST_ASIO_CORO_YIELD + asio::experimental::make_parallel_group( + [this](auto token) + { + auto f = [](system::error_code const&, auto const&) { return true; }; + return asio::async_connect(*stream, runner->endpoints_, f, token); + }, + [this](auto token) { return runner->timer_.async_wait(token);} + ).async_wait( + asio::experimental::wait_for_one(), + std::move(self)); + + if (is_cancelled(self)) { + self.complete(asio::error::operation_aborted); + return; + } + + switch (order[0]) { + case 0: { + self.complete(ec1); + } break; + case 1: + { + if (ec2) { + self.complete(ec2); + } else { + self.complete(error::connect_timeout); + } + } break; + + default: BOOST_ASSERT(false); + } + } + } +}; + +template +struct runner_op { + Runner* runner = nullptr; + Connection* conn = nullptr; + std::string_view host; + std::string_view port; + std::chrono::steady_clock::duration resolve_timeout; + std::chrono::steady_clock::duration connect_timeout; + asio::coroutine coro{}; + + template + void operator()(Self& self, system::error_code ec = {}) + { + BOOST_ASIO_CORO_REENTER (coro) + { + runner->timer_.expires_after(resolve_timeout); + BOOST_ASIO_CORO_YIELD + runner->async_resolve(host, port, std::move(self)); + AEDIS_CHECK_OP0(;) + + runner->timer_.expires_after(connect_timeout); + BOOST_ASIO_CORO_YIELD + runner->async_connect(conn->next_layer(), std::move(self)); + AEDIS_CHECK_OP0(;) + + BOOST_ASIO_CORO_YIELD + conn->async_run(std::move(self)); + AEDIS_CHECK_OP0(;) + self.complete({}); + } + } +}; + +template +class runner { +public: + runner(Executor ex): resv_{ex}, timer_{ex} {} + + template + auto + async_resolve( + std::string_view host, + std::string_view port, + CompletionToken&& token) + { + return asio::async_compose + < CompletionToken + , void(system::error_code) + >(resolve_op{this, host, port}, token, resv_); + } + + template + auto async_connect(Stream& stream, CompletionToken&& token) + { + return asio::async_compose + < CompletionToken + , void(system::error_code) + >(connect_op{this, &stream}, token, resv_); + } + + template + auto + async_run( + Connection& conn, + std::string_view host, + std::string_view port, + std::chrono::steady_clock::duration resolve_timeout, + std::chrono::steady_clock::duration connect_timeout, + CompletionToken&& token) + { + return asio::async_compose + < CompletionToken + , void(system::error_code) + >(runner_op{this, &conn, host, port, resolve_timeout, connect_timeout}, token, resv_); + } + +private: + using resolver_type = asio::ip::basic_resolver; + using timer_type = + asio::basic_waitable_timer< + std::chrono::steady_clock, + asio::wait_traits, + Executor>; + + + template friend struct runner_op; + template friend struct connect_op; + template friend struct resolve_op; + + resolver_type resv_; + timer_type timer_; + asio::ip::tcp::resolver::results_type endpoints_; +}; + +} // boost::redis::detail + +#endif // BOOST_REDIS_RUNNER_HPP diff --git a/include/boost/redis/error.hpp b/include/boost/redis/error.hpp index b154dc56..f61325af 100644 --- a/include/boost/redis/error.hpp +++ b/include/boost/redis/error.hpp @@ -63,6 +63,12 @@ enum class error /// There is no stablished connection. not_connected, + + /// Resolve timeout + resolve_timeout, + + /// Connect timeout + connect_timeout, }; /** \internal diff --git a/include/boost/redis/impl/error.ipp b/include/boost/redis/impl/error.ipp index 42e72e76..502bd9ec 100644 --- a/include/boost/redis/impl/error.ipp +++ b/include/boost/redis/impl/error.ipp @@ -38,6 +38,8 @@ struct error_category_impl : system::error_category { case error::not_a_double: return "Not a double."; case error::resp3_null: return "Got RESP3 null."; case error::not_connected: return "Not connected."; + case error::resolve_timeout: return "Resolve timeout."; + case error::connect_timeout: return "Connect timeout."; default: BOOST_ASSERT(false); return "Boost.Redis error."; } } diff --git a/include/boost/redis/operation.hpp b/include/boost/redis/operation.hpp index 99d4f2a9..58f3c6de 100644 --- a/include/boost/redis/operation.hpp +++ b/include/boost/redis/operation.hpp @@ -22,6 +22,8 @@ enum class operation { run, /// Refers to `connection::async_receive` operations. receive, + /// Refers to all operations. + all, }; } // boost::redis diff --git a/include/boost/redis/run.hpp b/include/boost/redis/run.hpp new file mode 100644 index 00000000..1ca54fe1 --- /dev/null +++ b/include/boost/redis/run.hpp @@ -0,0 +1,63 @@ +/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com) + * + * Distributed under the Boost Software License, Version 1.0. (See + * accompanying file LICENSE.txt) + */ + +#ifndef BOOST_REDIS_RUN_HPP +#define BOOST_REDIS_RUN_HPP + +// Has to included before promise.hpp to build on msvc. +#include +#include +#include +#include +#include +#include + +namespace boost::redis { + +/** @brief Call async_run on the connection. + * @ingroup high-level-api + * + * This is a facility function that + * 1. Resoves the endpoint. + * 2. Connects to one of the endpoints from 1. + * 3. Calls async_run on the underlying connection. + * + * @param host Redis host to connect to. + * @param port Redis port to connect to. + * @param resolve_timeout Time the resolve operation is allowed to take. + * @param connect_timeout Time the connect operation is allowed to take. + * @param token Completion token. + */ +template < + class Socket, + class CompletionToken = asio::default_completion_token_t +> +auto +async_run( + basic_connection& conn, + std::string_view host = "127.0.0.1", + std::string_view port = "6379", + std::chrono::steady_clock::duration resolve_timeout = std::chrono::seconds{10}, + std::chrono::steady_clock::duration connect_timeout = std::chrono::seconds{10}, + CompletionToken token = CompletionToken{}) +{ + using executor_type = typename Socket::executor_type; + using runner_type = detail::runner; + auto runner = std::make_shared(conn.get_executor()); + + return + runner->async_run( + conn, + host, + port, + resolve_timeout, + connect_timeout, + asio::consign(std::move(token), runner)); +} + +} // boost::redis + +#endif // BOOST_REDIS_RUN_HPP diff --git a/include/boost/redis/ssl/connection.hpp b/include/boost/redis/ssl/connection.hpp index 7b249d67..b9e983f2 100644 --- a/include/boost/redis/ssl/connection.hpp +++ b/include/boost/redis/ssl/connection.hpp @@ -122,7 +122,7 @@ class basic_connection> : * * See redis::connection::cancel for more information. */ - auto cancel(operation op) -> std::size_t + auto cancel(operation op = operation::all) -> std::size_t { return base_type::cancel(op); } auto& lowest_layer() noexcept { return stream_.lowest_layer(); } diff --git a/tests/common.hpp b/tests/common.hpp index a6eb4ba2..362cc9c3 100644 --- a/tests/common.hpp +++ b/tests/common.hpp @@ -1,22 +1,9 @@ #pragma once #include -#include - -namespace net = boost::asio; -using endpoints = net::ip::tcp::resolver::results_type; - -auto -resolve( - std::string const& host = "127.0.0.1", - std::string const& port = "6379") -> endpoints -{ - net::io_context ioc; - net::ip::tcp::resolver resv{ioc}; - return resv.resolve(host, port); -} #ifdef BOOST_ASIO_HAS_CO_AWAIT +namespace net = boost::asio; inline auto redir(boost::system::error_code& ec) { return net::redirect_error(net::use_awaitable, ec); } diff --git a/tests/conn_check_health.cpp b/tests/conn_check_health.cpp index 5b7e2962..096a7c52 100644 --- a/tests/conn_check_health.cpp +++ b/tests/conn_check_health.cpp @@ -12,7 +12,7 @@ #include #include -#include +#include #include #include "common.hpp" @@ -24,7 +24,9 @@ using boost::redis::request; using boost::redis::ignore; using boost::redis::operation; using boost::redis::generic_response; -using boost::redis::experimental::async_check_health; +using boost::redis::async_check_health; +using boost::redis::async_run; +using namespace std::chrono_literals; std::chrono::seconds const interval{1}; @@ -74,14 +76,11 @@ BOOST_AUTO_TEST_CASE(check_health) { net::io_context ioc; - auto const endpoints = resolve(); connection conn{ioc}; - net::connect(conn.next_layer(), endpoints); // It looks like client pause does not work for clients that are // sending MONITOR. I will therefore open a second connection. connection conn2{ioc}; - net::connect(conn2.next_layer(), endpoints); std::string const msg = "test-check-health"; @@ -108,12 +107,12 @@ BOOST_AUTO_TEST_CASE(check_health) generic_response resp; push_callback{&conn, &conn2, &resp, &req2}(); // Starts reading pushes. - conn.async_run([](auto ec){ + async_run(conn, "127.0.0.1", "6379", 10s, 10s, [](auto ec){ std::cout << "B" << std::endl; BOOST_TEST(!!ec); }); - conn2.async_run([](auto ec){ + async_run(conn2, "127.0.0.1", "6379", 10s, 10s, [](auto ec){ std::cout << "C" << std::endl; BOOST_TEST(!!ec); }); diff --git a/tests/conn_echo_stress.cpp b/tests/conn_echo_stress.cpp index f6ef4156..ffbd3b5d 100644 --- a/tests/conn_echo_stress.cpp +++ b/tests/conn_echo_stress.cpp @@ -13,7 +13,7 @@ #include #include #include "common.hpp" -#include "../examples/common/common.hpp" +#include "../examples/start.hpp" namespace net = boost::asio; using error_code = boost::system::error_code; @@ -22,6 +22,9 @@ using boost::redis::request; using boost::redis::response; using boost::redis::ignore; using boost::redis::ignore_t; +using boost::redis::async_run; +using connection = boost::asio::use_awaitable_t<>::as_default_on_t; +using namespace std::chrono_literals; auto push_consumer(std::shared_ptr conn, int expected) -> net::awaitable { @@ -74,13 +77,12 @@ auto async_echo_stress() -> net::awaitable for (int i = 0; i < sessions; ++i) net::co_spawn(ex, echo_session(conn, std::to_string(i), msgs), net::detached); - co_await connect(conn, "127.0.0.1", "6379"); - co_await conn->async_run(); + co_await async_run(*conn); } BOOST_AUTO_TEST_CASE(echo_stress) { - run(async_echo_stress()); + start(async_echo_stress()); } #else diff --git a/tests/conn_exec.cpp b/tests/conn_exec.cpp index 71ccf97c..37606271 100644 --- a/tests/conn_exec.cpp +++ b/tests/conn_exec.cpp @@ -28,6 +28,8 @@ using boost::redis::request; using boost::redis::response; using boost::redis::ignore; using boost::redis::ignore_t; +using boost::redis::async_run; +using namespace std::chrono_literals; BOOST_AUTO_TEST_CASE(hello_priority) { @@ -47,9 +49,7 @@ BOOST_AUTO_TEST_CASE(hello_priority) net::io_context ioc; - auto const endpoints = resolve(); connection conn{ioc}; - net::connect(conn.next_layer(), endpoints); bool seen1 = false; bool seen2 = false; @@ -77,7 +77,7 @@ BOOST_AUTO_TEST_CASE(hello_priority) seen3 = true; }); - conn.async_run([](auto ec){ + async_run(conn, "127.0.0.1", "6379", 10s, 10s, [](auto ec){ BOOST_TEST(!ec); }); @@ -94,14 +94,12 @@ BOOST_AUTO_TEST_CASE(wrong_response_data_type) response resp; net::io_context ioc; - auto const endpoints = resolve(); connection conn{ioc}; - net::connect(conn.next_layer(), endpoints); conn.async_exec(req, resp, [](auto ec, auto){ BOOST_CHECK_EQUAL(ec, boost::redis::error::not_a_number); }); - conn.async_run([](auto ec){ + async_run(conn, "127.0.0.1", "6379", 10s, 10s, [](auto ec){ BOOST_CHECK_EQUAL(ec, boost::asio::error::basic_errors::operation_aborted); }); diff --git a/tests/conn_exec_cancel.cpp b/tests/conn_exec_cancel.cpp index 228d9659..ee195f5d 100644 --- a/tests/conn_exec_cancel.cpp +++ b/tests/conn_exec_cancel.cpp @@ -14,7 +14,7 @@ #include #include #include "common.hpp" -#include "../examples/common/common.hpp" +#include "../examples/start.hpp" // NOTE1: Sends hello separately. I have observed that if hello and // blpop are sent toguether, Redis will send the response of hello @@ -30,6 +30,9 @@ using boost::redis::response; using boost::redis::generic_response; using boost::redis::ignore; using boost::redis::ignore_t; +using boost::redis::async_run; +using connection = boost::asio::use_awaitable_t<>::as_default_on_t; +using namespace std::chrono_literals; auto async_ignore_explicit_cancel_of_req_written() -> net::awaitable { @@ -37,9 +40,8 @@ auto async_ignore_explicit_cancel_of_req_written() -> net::awaitable generic_response gresp; auto conn = std::make_shared(ex); - co_await connect(conn, "127.0.0.1", "6379"); - conn->async_run([conn](auto ec) { + async_run(*conn, "127.0.0.1", "6379", 10s, 10s, [conn](auto ec) { std::cout << "async_run: " << ec.message() << std::endl; BOOST_TEST(!ec); }); @@ -85,11 +87,10 @@ auto ignore_implicit_cancel_of_req_written() -> net::awaitable { auto ex = co_await net::this_coro::executor; auto conn = std::make_shared(ex); - co_await connect(conn, "127.0.0.1", "6379"); // Calls async_run separately from the group of ops below to avoid // having it canceled when the timer fires. - conn->async_run([conn](auto ec) { + async_run(*conn, "127.0.0.1", "6379", 10s, 10s, [conn](auto ec) { BOOST_CHECK_EQUAL(ec, net::error::basic_errors::operation_aborted); }); @@ -118,20 +119,18 @@ auto ignore_implicit_cancel_of_req_written() -> net::awaitable BOOST_AUTO_TEST_CASE(test_ignore_explicit_cancel_of_req_written) { - run(async_ignore_explicit_cancel_of_req_written()); + start(async_ignore_explicit_cancel_of_req_written()); } BOOST_AUTO_TEST_CASE(test_ignore_implicit_cancel_of_req_written) { - run(ignore_implicit_cancel_of_req_written()); + start(ignore_implicit_cancel_of_req_written()); } BOOST_AUTO_TEST_CASE(test_cancel_of_req_written_on_run_canceled) { net::io_context ioc; - auto const endpoints = resolve(); connection conn{ioc}; - net::connect(conn.next_layer(), endpoints); request req0; req0.push("HELLO", 3); @@ -156,7 +155,7 @@ BOOST_AUTO_TEST_CASE(test_cancel_of_req_written_on_run_canceled) conn.async_exec(req0, ignore, c0); - conn.async_run([](auto ec){ + async_run(conn, "127.0.0.1", "6379", 10s, 10s, [](auto ec){ BOOST_CHECK_EQUAL(ec, net::error::basic_errors::operation_aborted); }); diff --git a/tests/conn_exec_error.cpp b/tests/conn_exec_error.cpp index 000b54ac..61b0a94e 100644 --- a/tests/conn_exec_error.cpp +++ b/tests/conn_exec_error.cpp @@ -27,6 +27,8 @@ using boost::redis::generic_response; using boost::redis::ignore; using boost::redis::ignore_t; using boost::redis::error; +using boost::redis::async_run; +using namespace std::chrono_literals; BOOST_AUTO_TEST_CASE(no_ignore_error) { @@ -37,16 +39,13 @@ BOOST_AUTO_TEST_CASE(no_ignore_error) net::io_context ioc; - auto const endpoints = resolve(); connection conn{ioc}; - net::connect(conn.next_layer(), endpoints); conn.async_exec(req, ignore, [&](auto ec, auto){ BOOST_CHECK_EQUAL(ec, error::resp3_simple_error); conn.cancel(redis::operation::run); }); - - conn.async_run([](auto ec){ + async_run(conn, "127.0.0.1", "6379", 10s, 10s, [](auto ec){ BOOST_CHECK_EQUAL(ec, boost::asio::error::basic_errors::operation_aborted); }); @@ -66,9 +65,7 @@ BOOST_AUTO_TEST_CASE(has_diagnostic) net::io_context ioc; - auto const endpoints = resolve(); connection conn{ioc}; - net::connect(conn.next_layer(), endpoints); response resp; conn.async_exec(req, resp, [&](auto ec, auto){ @@ -87,8 +84,7 @@ BOOST_AUTO_TEST_CASE(has_diagnostic) conn.cancel(redis::operation::run); }); - - conn.async_run([](auto ec){ + async_run(conn, "127.0.0.1", "6379", 10s, 10s, [](auto ec){ BOOST_CHECK_EQUAL(ec, boost::asio::error::basic_errors::operation_aborted); }); @@ -111,9 +107,7 @@ BOOST_AUTO_TEST_CASE(resp3_error_in_cmd_pipeline) response resp2; net::io_context ioc; - auto const endpoints = resolve(); connection conn{ioc}; - net::connect(conn.next_layer(), endpoints); auto c2 = [&](auto ec, auto) { @@ -139,8 +133,7 @@ BOOST_AUTO_TEST_CASE(resp3_error_in_cmd_pipeline) }; conn.async_exec(req1, resp1, c1); - - conn.async_run([](auto ec){ + async_run(conn, "127.0.0.1", "6379", 10s, 10s, [](auto ec){ BOOST_CHECK_EQUAL(ec, boost::asio::error::basic_errors::operation_aborted); }); @@ -171,9 +164,7 @@ BOOST_AUTO_TEST_CASE(error_in_transaction) net::io_context ioc; - auto const endpoints = resolve(); connection conn{ioc}; - net::connect(conn.next_layer(), endpoints); conn.async_exec(req, resp, [&](auto ec, auto){ BOOST_TEST(!ec); @@ -205,8 +196,7 @@ BOOST_AUTO_TEST_CASE(error_in_transaction) conn.cancel(redis::operation::run); }); - - conn.async_run([](auto ec){ + async_run(conn, "127.0.0.1", "6379", 10s, 10s, [](auto ec){ BOOST_CHECK_EQUAL(ec, boost::asio::error::basic_errors::operation_aborted); }); @@ -226,9 +216,7 @@ BOOST_AUTO_TEST_CASE(subscriber_wrong_syntax) req2.push("SUBSCRIBE"); // Wrong command synthax. net::io_context ioc; - auto const endpoints = resolve(); connection conn{ioc}; - net::connect(conn.next_layer(), endpoints); auto c2 = [&](auto ec, auto) { @@ -258,8 +246,7 @@ BOOST_AUTO_TEST_CASE(subscriber_wrong_syntax) }; conn.async_receive(gresp, c3); - - conn.async_run([](auto ec){ + async_run(conn, "127.0.0.1", "6379", 10s, 10s, [](auto ec){ std::cout << "async_run" << std::endl; BOOST_CHECK_EQUAL(ec, boost::asio::error::basic_errors::operation_aborted); }); diff --git a/tests/conn_exec_retry.cpp b/tests/conn_exec_retry.cpp index c47808a0..7371a995 100644 --- a/tests/conn_exec_retry.cpp +++ b/tests/conn_exec_retry.cpp @@ -23,6 +23,8 @@ using boost::redis::operation; using boost::redis::request; using boost::redis::response; using boost::redis::ignore; +using boost::redis::async_run; +using namespace std::chrono_literals; BOOST_AUTO_TEST_CASE(request_retry_false) { @@ -53,9 +55,6 @@ BOOST_AUTO_TEST_CASE(request_retry_false) conn.cancel(operation::run); }); - auto const endpoints = resolve(); - net::connect(conn.next_layer(), endpoints); - auto c2 = [&](auto ec, auto){ BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled); }; @@ -72,7 +71,7 @@ BOOST_AUTO_TEST_CASE(request_retry_false) conn.async_exec(req0, ignore, c0); - conn.async_run([](auto ec){ + async_run(conn, "127.0.0.1", "6379", 10s, 10s, [](auto ec){ BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled); }); @@ -112,9 +111,6 @@ BOOST_AUTO_TEST_CASE(request_retry_true) conn.cancel(boost::redis::operation::run); }); - auto const endpoints = resolve(); - net::connect(conn.next_layer(), endpoints); - auto c3 = [&](auto ec, auto){ BOOST_TEST(!ec); }; @@ -136,14 +132,13 @@ BOOST_AUTO_TEST_CASE(request_retry_true) conn.async_exec(req0, ignore, c0); - conn.async_run([&](auto ec){ + async_run(conn, "127.0.0.1", "6379", 10s, 10s, [&](auto ec){ // The first cacellation. BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled); conn.reset_stream(); // Reconnects and runs again to test req3. - net::connect(conn.next_layer(), endpoints); - conn.async_run([&](auto ec){ + async_run(conn, "127.0.0.1", "6379", 10s, 10s, [](auto ec){ std::cout << ec.message() << std::endl; BOOST_TEST(!ec); }); diff --git a/tests/conn_push.cpp b/tests/conn_push.cpp index a097f2cb..53c969a7 100644 --- a/tests/conn_push.cpp +++ b/tests/conn_push.cpp @@ -28,13 +28,99 @@ using boost::redis::request; using boost::redis::response; using boost::redis::ignore; using boost::redis::ignore_t; +using boost::redis::async_run; +using namespace std::chrono_literals; + +BOOST_AUTO_TEST_CASE(receives_push_waiting_resps) +{ + request req1; + req1.push("HELLO", 3); + req1.push("PING", "Message1"); + + request req2; + req2.push("SUBSCRIBE", "channel"); + + request req3; + req3.push("PING", "Message2"); + req3.push("QUIT"); + + net::io_context ioc; + + connection conn{ioc}; + + auto c3 =[](auto ec, auto...) + { + BOOST_TEST(!!ec); + }; + + auto c2 =[&](auto ec, auto...) + { + BOOST_TEST(!ec); + conn.async_exec(req3, ignore, c3); + }; + + auto c1 =[&](auto ec, auto...) + { + BOOST_TEST(!ec); + conn.async_exec(req2, ignore, c2); + }; + + conn.async_exec(req1, ignore, c1); + + async_run(conn, "127.0.0.1", "6379", 10s, 10s, [&](auto ec){ + BOOST_TEST(!ec); + conn.cancel(operation::receive); + }); + + bool push_received = false; + conn.async_receive(ignore, [&](auto ec, auto){ + std::cout << "async_receive" << std::endl; + BOOST_TEST(!ec); + conn.cancel(operation::run); + push_received = true; + }); + + ioc.run(); + + BOOST_TEST(push_received); +} + +BOOST_AUTO_TEST_CASE(push_received1) +{ + net::io_context ioc; + connection conn{ioc}; + + request req; + req.push("HELLO", 3); + req.push("SUBSCRIBE", "channel"); + + conn.async_exec(req, ignore, [](auto ec, auto){ + std::cout << "async_exec" << std::endl; + BOOST_TEST(!ec); + }); + + async_run(conn, "127.0.0.1", "6379", 10s, 10s, [&](auto ec){ + std::cout << "async_run: " << ec.message() << std::endl; + conn.cancel(operation::receive); + }); + + bool push_received = false; + conn.async_receive(ignore, [&](auto ec, auto){ + std::cout << "async_receive" << std::endl; + BOOST_TEST(!ec); + conn.cancel(operation::run); + push_received = true; + }); + + ioc.run(); + + BOOST_TEST(push_received); +} BOOST_AUTO_TEST_CASE(push_filtered_out) { net::io_context ioc; - auto const endpoints = resolve(); connection conn{ioc}; - net::connect(conn.next_layer(), endpoints); request req; req.push("HELLO", 3); @@ -51,7 +137,7 @@ BOOST_AUTO_TEST_CASE(push_filtered_out) BOOST_TEST(!ec); }); - conn.async_run([](auto ec){ + async_run(conn, "127.0.0.1", "6379", 10s, 10s, [&](auto ec){ BOOST_TEST(!ec); }); @@ -101,9 +187,7 @@ auto boost_redis_adapt(response_error_tag&) BOOST_AUTO_TEST_CASE(test_push_adapter) { net::io_context ioc; - auto const endpoints = resolve(); connection conn{ioc}; - net::connect(conn.next_layer(), endpoints); request req; req.push("HELLO", 3); @@ -119,7 +203,7 @@ BOOST_AUTO_TEST_CASE(test_push_adapter) BOOST_CHECK_EQUAL(ec, net::experimental::error::channel_errors::channel_cancelled); }); - conn.async_run([](auto ec){ + async_run(conn, "127.0.0.1", "6379", 10s, 10s, [](auto ec){ BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled); }); @@ -131,94 +215,9 @@ BOOST_AUTO_TEST_CASE(test_push_adapter) net::awaitable push_consumer3(connection& conn) { - for (;;) + for (;;) { co_await conn.async_receive(ignore, net::use_awaitable); -} - -BOOST_AUTO_TEST_CASE(push_received1) -{ - net::io_context ioc; - auto const endpoints = resolve(); - connection conn{ioc}; - net::connect(conn.next_layer(), endpoints); - - request req; - req.push("HELLO", 3); - req.push("SUBSCRIBE", "channel"); - req.push("QUIT"); - - conn.async_exec(req, ignore, [](auto ec, auto){ - BOOST_TEST(!ec); - }); - - conn.async_run([&](auto ec){ - BOOST_TEST(!ec); - conn.cancel(operation::receive); - }); - - bool push_received = false; - net::co_spawn( - ioc.get_executor(), - push_consumer1(conn, push_received), - net::detached); - - ioc.run(); - - BOOST_TEST(push_received); -} - -BOOST_AUTO_TEST_CASE(receives_push_waiting_resps) -{ - request req1; - req1.push("HELLO", 3); - req1.push("PING", "Message1"); - - request req2; - req2.push("SUBSCRIBE", "channel"); - - request req3; - req3.push("PING", "Message2"); - req3.push("QUIT"); - - net::io_context ioc; - - auto const endpoints = resolve(); - connection conn{ioc}; - net::connect(conn.next_layer(), endpoints); - - auto c3 =[](auto ec, auto...) - { - BOOST_TEST(!ec); - }; - - auto c2 =[&](auto ec, auto...) - { - BOOST_TEST(!ec); - conn.async_exec(req3, ignore, c3); - }; - - auto c1 =[&](auto ec, auto...) - { - BOOST_TEST(!ec); - conn.async_exec(req2, ignore, c2); - }; - - conn.async_exec(req1, ignore, c1); - - conn.async_run([&](auto ec) { - BOOST_TEST(!ec); - conn.cancel(operation::receive); - }); - - bool push_received = false; - net::co_spawn( - ioc.get_executor(), - push_consumer1(conn, push_received), - net::detached); - - ioc.run(); - - BOOST_TEST(push_received); + } } BOOST_AUTO_TEST_CASE(many_subscribers) @@ -240,12 +239,11 @@ BOOST_AUTO_TEST_CASE(many_subscribers) req3.push("QUIT"); net::io_context ioc; - auto const endpoints = resolve(); connection conn{ioc}; - net::connect(conn.next_layer(), endpoints); auto c11 =[&](auto ec, auto...) { + std::cout << "quit sent" << std::endl; BOOST_TEST(!ec); }; auto c10 =[&](auto ec, auto...) @@ -306,7 +304,7 @@ BOOST_AUTO_TEST_CASE(many_subscribers) conn.async_exec(req0, ignore, c0); - conn.async_run([&](auto ec) { + async_run(conn, "127.0.0.1", "6379", 10s, 10s, [&](auto ec){ BOOST_TEST(!ec); conn.cancel(operation::receive); }); diff --git a/tests/conn_quit.cpp b/tests/conn_quit.cpp index 4c1d2c2b..2d1f57c7 100644 --- a/tests/conn_quit.cpp +++ b/tests/conn_quit.cpp @@ -23,6 +23,8 @@ using operation = boost::redis::operation; using boost::redis::request; using boost::redis::response; using boost::redis::ignore; +using boost::redis::async_run; +using namespace std::chrono_literals; BOOST_AUTO_TEST_CASE(test_quit1) { @@ -32,15 +34,13 @@ BOOST_AUTO_TEST_CASE(test_quit1) req.push("QUIT"); net::io_context ioc; - auto const endpoints = resolve(); connection conn{ioc}; - net::connect(conn.next_layer(), endpoints); conn.async_exec(req, ignore, [](auto ec, auto) { BOOST_TEST(!ec); }); - conn.async_run([](auto ec) { + async_run(conn, "127.0.0.1", "6379", 10s, 10s, [&](auto ec){ BOOST_TEST(!ec); }); @@ -52,9 +52,7 @@ BOOST_AUTO_TEST_CASE(test_quit2) { net::io_context ioc; - auto const endpoints = resolve(); connection conn{ioc}; - net::connect(conn.next_layer(), endpoints); request req1; req1.get_config().cancel_on_connection_lost = false; @@ -93,7 +91,7 @@ BOOST_AUTO_TEST_CASE(test_quit2) conn.async_exec(req1, ignore, c1); - conn.async_run([&](auto ec){ + async_run(conn, "127.0.0.1", "6379", 10s, 10s, [&](auto ec){ BOOST_TEST(!ec); }); diff --git a/tests/conn_reconnect.cpp b/tests/conn_reconnect.cpp index f07264f0..813c2d7c 100644 --- a/tests/conn_reconnect.cpp +++ b/tests/conn_reconnect.cpp @@ -14,13 +14,16 @@ #include #include #include "common.hpp" -#include "../examples/common/common.hpp" +#include "../examples/start.hpp" namespace net = boost::asio; using error_code = boost::system::error_code; using boost::redis::request; using boost::redis::response; using boost::redis::ignore; +using boost::redis::async_run; +using connection = boost::asio::use_awaitable_t<>::as_default_on_t; +using namespace std::chrono_literals; #include using namespace boost::asio::experimental::awaitable_operators; @@ -32,16 +35,14 @@ net::awaitable test_reconnect_impl() request req; req.push("QUIT"); - auto const endpoints = resolve(); connection conn{ex}; int i = 0; for (; i < 5; ++i) { boost::system::error_code ec1, ec2; - net::connect(conn.next_layer(), endpoints); co_await ( conn.async_exec(req, ignore, net::redirect_error(net::use_awaitable, ec1)) && - conn.async_run(net::redirect_error(net::use_awaitable, ec2)) + async_run(conn, "127.0.0.1", "6379", 10s, 10s, net::redirect_error(net::use_awaitable, ec2)) ); BOOST_TEST(!ec1); @@ -77,11 +78,10 @@ auto async_test_reconnect_timeout() -> net::awaitable req1.push("HELLO", 3); req1.push("BLPOP", "any", 0); - co_await connect(conn, "127.0.0.1", "6379"); st.expires_after(std::chrono::seconds{1}); co_await ( conn->async_exec(req1, ignore, redir(ec1)) || - conn->async_run(redir(ec2)) || + async_run(*conn, "127.0.0.1", "6379", 10s, 10s, redir(ec2)) || st.async_wait(redir(ec3)) ); @@ -96,11 +96,10 @@ auto async_test_reconnect_timeout() -> net::awaitable req2.push("HELLO", 3); req2.push("QUIT"); - co_await connect(conn, "127.0.0.1", "6379"); st.expires_after(std::chrono::seconds{1}); co_await ( conn->async_exec(req1, ignore, net::redirect_error(net::use_awaitable, ec1)) || - conn->async_run(net::redirect_error(net::use_awaitable, ec2)) || + async_run(*conn, "127.0.0.1", "6379", 10s, 10s, net::redirect_error(net::use_awaitable, ec2)) || st.async_wait(net::redirect_error(net::use_awaitable, ec3)) ); std::cout << "ccc" << std::endl; @@ -111,7 +110,7 @@ auto async_test_reconnect_timeout() -> net::awaitable BOOST_AUTO_TEST_CASE(test_reconnect_and_idle) { - run(async_test_reconnect_timeout()); + start(async_test_reconnect_timeout()); } #else int main(){} diff --git a/tests/conn_run_cancel.cpp b/tests/conn_run_cancel.cpp index 1218e56f..edc971d0 100644 --- a/tests/conn_run_cancel.cpp +++ b/tests/conn_run_cancel.cpp @@ -26,6 +26,8 @@ using net::experimental::as_tuple; using boost::redis::request; using boost::redis::response; using boost::redis::ignore; +using boost::redis::async_run; +using namespace std::chrono_literals; #include using namespace net::experimental::awaitable_operators; @@ -33,15 +35,13 @@ using namespace net::experimental::awaitable_operators; auto async_cancel_run_with_timer() -> net::awaitable { auto ex = co_await net::this_coro::executor; - auto const endpoints = resolve(); connection conn{ex}; - net::connect(conn.next_layer(), endpoints); net::steady_timer st{ex}; st.expires_after(std::chrono::seconds{1}); boost::system::error_code ec1, ec2; - co_await (conn.async_run(redir(ec1)) || st.async_wait(redir(ec2))); + co_await (async_run(conn, "127.0.0.1", "6379", 10s, 10s, redir(ec1)) || st.async_wait(redir(ec2))); BOOST_CHECK_EQUAL(ec1, boost::asio::error::basic_errors::operation_aborted); BOOST_TEST(!ec2); @@ -58,16 +58,14 @@ auto async_check_cancellation_not_missed(int n, std::chrono::milliseconds ms) -> net::awaitable { auto ex = co_await net::this_coro::executor; - auto const endpoints = resolve(); connection conn{ex}; net::steady_timer timer{ex}; for (auto i = 0; i < n; ++i) { timer.expires_after(ms); - net::connect(conn.next_layer(), endpoints); boost::system::error_code ec1, ec2; - co_await (conn.async_run(redir(ec1)) || timer.async_wait(redir(ec2))); + co_await (async_run(conn, "127.0.0.1", "6379", 10s, 10s, redir(ec1)) || timer.async_wait(redir(ec2))); BOOST_CHECK_EQUAL(ec1, boost::asio::error::basic_errors::operation_aborted); std::cout << "Counter: " << i << std::endl; } @@ -147,10 +145,7 @@ BOOST_AUTO_TEST_CASE(check_implicit_cancel_not_missed_1024) BOOST_AUTO_TEST_CASE(reset_before_run_completes) { net::io_context ioc; - auto const endpoints = resolve(); connection conn{ioc}; - net::connect(conn.next_layer(), endpoints); - // Sends a ping just as a means of waiting until we are connected. request req; @@ -161,8 +156,7 @@ BOOST_AUTO_TEST_CASE(reset_before_run_completes) BOOST_TEST(!ec); conn.reset_stream(); }); - - conn.async_run([&](auto ec){ + async_run(conn, "127.0.0.1", "6379", 10s, 10s, [&](auto ec){ BOOST_CHECK_EQUAL(ec, net::error::operation_aborted); }); diff --git a/tests/conn_tls.cpp b/tests/conn_tls.cpp index 5b9f2fe0..56b85b84 100644 --- a/tests/conn_tls.cpp +++ b/tests/conn_tls.cpp @@ -23,6 +23,18 @@ using boost::redis::request; using boost::redis::response; using boost::redis::ignore_t; +using endpoints = net::ip::tcp::resolver::results_type; + +auto +resolve( + std::string const& host = "127.0.0.1", + std::string const& port = "6379") -> endpoints +{ + net::io_context ioc; + net::ip::tcp::resolver resv{ioc}; + return resv.resolve(host, port); +} + struct endpoint { std::string host; std::string port; diff --git a/tests/issue_50.cpp b/tests/issue_50.cpp index b2675a3a..cb87ca18 100644 --- a/tests/issue_50.cpp +++ b/tests/issue_50.cpp @@ -6,14 +6,15 @@ // Must come before any asio header, otherwise build fails on msvc. #include +#include #include #if defined(BOOST_ASIO_HAS_CO_AWAIT) #include -#include +#include #include -#include "../examples/common/common.hpp" +#include "../examples/start.hpp" namespace net = boost::asio; using namespace net::experimental::awaitable_operators; @@ -21,7 +22,10 @@ using steady_timer = net::use_awaitable_t<>::as_default_on_t; using boost::redis::request; using boost::redis::response; using boost::redis::ignore; -using boost::redis::experimental::async_check_health; +using boost::redis::async_check_health; +using boost::redis::async_run; +using connection = boost::asio::use_awaitable_t<>::as_default_on_t; +using namespace std::chrono_literals; // Push consumer auto receiver(std::shared_ptr conn) -> net::awaitable @@ -65,8 +69,7 @@ auto co_main(std::string host, std::string port) -> net::awaitable // The loop will reconnect on connection lost. To exit type Ctrl-C twice. for (int i = 0; i < 10; ++i) { - co_await connect(conn, host, port); - co_await ((conn->async_run() || receiver(conn) || async_check_health(*conn) || periodic_task(conn)) && + co_await ((async_run(*conn, host, port) || receiver(conn) || async_check_health(*conn) || periodic_task(conn)) && conn->async_exec(req)); conn->reset_stream(); diff --git a/tests/run.cpp b/tests/run.cpp new file mode 100644 index 00000000..4b3edc1e --- /dev/null +++ b/tests/run.cpp @@ -0,0 +1,76 @@ +/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com) + * + * Distributed under the Boost Software License, Version 1.0. (See + * accompanying file LICENSE.txt) + */ + +#define BOOST_TEST_MODULE run +#include + +#include +#include +#include +#include + +namespace net = boost::asio; + +using connection = boost::redis::connection; +using boost::redis::async_run; +using boost::system::error_code; +using namespace std::chrono_literals; + +bool is_host_not_found(error_code ec) +{ + if (ec == net::error::netdb_errors::host_not_found) return true; + if (ec == net::error::netdb_errors::host_not_found_try_again) return true; + return false; +} + +BOOST_AUTO_TEST_CASE(resolve_bad_host) +{ + net::io_context ioc; + + connection conn{ioc}; + async_run(conn, "Atibaia", "6379", 1000s, 1000s, [](auto ec){ + BOOST_TEST(is_host_not_found(ec)); + }); + + ioc.run(); +} + +BOOST_AUTO_TEST_CASE(resolve_with_timeout) +{ + net::io_context ioc; + + connection conn{ioc}; + async_run(conn, "Atibaia", "6379", 1ms, 1ms, [](auto ec){ + BOOST_CHECK_EQUAL(ec, boost::redis::error::resolve_timeout); + }); + + ioc.run(); +} + +BOOST_AUTO_TEST_CASE(connect_bad_port) +{ + net::io_context ioc; + + connection conn{ioc}; + async_run(conn, "127.0.0.1", "1", 1000s, 10s, [](auto ec){ + BOOST_CHECK_EQUAL(ec, net::error::basic_errors::connection_refused); + }); + + ioc.run(); +} + +BOOST_AUTO_TEST_CASE(connect_with_timeout) +{ + net::io_context ioc; + + connection conn{ioc}; + async_run(conn, "example.com", "1", 10s, 1ms, [](auto ec){ + BOOST_CHECK_EQUAL(ec, boost::redis::error::connect_timeout); + }); + + ioc.run(); +} + From 90bcd621fbc4708bf63f83a366cec7db767b9bbb Mon Sep 17 00:00:00 2001 From: Marcelo Zimbres Date: Tue, 14 Mar 2023 14:08:55 +0100 Subject: [PATCH 04/32] Including only necessary headers. --- README.md | 2 +- examples/cpp17_intro.cpp | 1 - examples/cpp17_intro_sync.cpp | 9 +++-- examples/cpp20_chat_room.cpp | 18 +++++---- examples/cpp20_containers.cpp | 9 +++-- examples/cpp20_echo_server.cpp | 11 ++++-- examples/cpp20_intro.cpp | 9 +++-- examples/cpp20_intro_awaitable_ops.cpp | 6 +-- examples/cpp20_intro_tls.cpp | 12 +++--- examples/cpp20_json.cpp | 12 +++--- examples/cpp20_protobuf.cpp | 11 ++++-- examples/cpp20_resolve_with_sentinel.cpp | 7 ++-- examples/cpp20_subscriber.cpp | 11 +++--- examples/json.hpp | 4 +- examples/main.cpp | 7 ++-- examples/start.cpp | 7 +++- examples/start.hpp | 2 +- include/boost/redis/adapter/ignore.hpp | 1 + include/boost/redis/check_health.hpp | 1 + include/boost/redis/detail/connection_ops.hpp | 21 ++++++----- include/boost/redis/detail/helper.hpp | 37 +++++++++++++++++++ include/boost/redis/detail/read_ops.hpp | 25 ++----------- include/boost/redis/detail/runner.hpp | 9 +++-- include/boost/redis/resp3/parser.hpp | 4 +- include/boost/redis/resp3/type.hpp | 1 + include/boost/redis/run.hpp | 1 + include/boost/redis/ssl/connection.hpp | 1 + tests/common.hpp | 4 +- tests/conn_check_health.cpp | 12 ++---- tests/conn_echo_stress.cpp | 12 +++--- tests/conn_exec.cpp | 10 ++--- tests/conn_exec_cancel.cpp | 10 ++--- tests/conn_exec_error.cpp | 10 ++--- tests/conn_push.cpp | 15 ++++---- tests/conn_quit.cpp | 9 ++--- tests/conn_reconnect.cpp | 13 +++---- tests/conn_run_cancel.cpp | 13 +++---- tests/conn_tls.cpp | 9 ++--- tests/cpp17_low_level_sync.cpp | 9 +++-- tests/cpp20_low_level_async.cpp | 9 +++-- tests/issue_50.cpp | 12 +++--- tests/low_level.cpp | 14 ++++--- tests/run.cpp | 4 +- 43 files changed, 221 insertions(+), 183 deletions(-) create mode 100644 include/boost/redis/detail/helper.hpp diff --git a/README.md b/README.md index ae682a4c..0dfb7dc6 100644 --- a/README.md +++ b/README.md @@ -527,7 +527,7 @@ void boost_redis_from_bulk(mystruct& obj, char const* p, std::size_t size, boost These functions are accessed over ADL and therefore they must be imported in the global namespace by the user. In the -[Examples](#Examples) section the reader can find examples showing how +[Examples](#examples) section the reader can find examples showing how to serialize using json and [protobuf](https://protobuf.dev/). diff --git a/examples/cpp17_intro.cpp b/examples/cpp17_intro.cpp index d53ee587..d7be48d6 100644 --- a/examples/cpp17_intro.cpp +++ b/examples/cpp17_intro.cpp @@ -5,7 +5,6 @@ */ #include -#include #include #include diff --git a/examples/cpp17_intro_sync.cpp b/examples/cpp17_intro_sync.cpp index 5621b665..c89d7648 100644 --- a/examples/cpp17_intro_sync.cpp +++ b/examples/cpp17_intro_sync.cpp @@ -4,14 +4,17 @@ * accompanying file LICENSE.txt) */ +#include +#include +#include +#include +#include +#include #include #include #include #include #include -#include -#include -#include // Include this in no more than one .cpp file. #include diff --git a/examples/cpp20_chat_room.cpp b/examples/cpp20_chat_room.cpp index f02bb541..0f88f1ba 100644 --- a/examples/cpp20_chat_room.cpp +++ b/examples/cpp20_chat_room.cpp @@ -4,16 +4,18 @@ * accompanying file LICENSE.txt) */ -#include -#if defined(BOOST_ASIO_HAS_CO_AWAIT) -#include -namespace net = boost::asio; -#if defined(BOOST_ASIO_HAS_POSIX_STREAM_DESCRIPTOR) -#include -#include +#include #include +#include +#include +#include +#include #include +#include +#if defined(BOOST_ASIO_HAS_CO_AWAIT) +#if defined(BOOST_ASIO_HAS_POSIX_STREAM_DESCRIPTOR) +namespace net = boost::asio; using namespace net::experimental::awaitable_operators; using stream_descriptor = net::use_awaitable_t<>::as_default_on_t; using signal_set = net::use_awaitable_t<>::as_default_on_t; @@ -21,7 +23,7 @@ using boost::redis::request; using boost::redis::generic_response; using boost::redis::async_check_health; using boost::redis::async_run; -using connection = boost::asio::use_awaitable_t<>::as_default_on_t; +using connection = net::use_awaitable_t<>::as_default_on_t; // Chat over Redis pubsub. To test, run this program from multiple // terminals and type messages to stdin. diff --git a/examples/cpp20_containers.cpp b/examples/cpp20_containers.cpp index f000a6ef..670c6f8e 100644 --- a/examples/cpp20_containers.cpp +++ b/examples/cpp20_containers.cpp @@ -4,13 +4,16 @@ * accompanying file LICENSE.txt) */ -#include -#if defined(BOOST_ASIO_HAS_CO_AWAIT) -#include +#include +#include +#include +#include #include #include #include +#if defined(BOOST_ASIO_HAS_CO_AWAIT) + namespace net = boost::asio; namespace redis = boost::redis; using boost::redis::request; diff --git a/examples/cpp20_echo_server.cpp b/examples/cpp20_echo_server.cpp index 0be953c4..60d507e9 100644 --- a/examples/cpp20_echo_server.cpp +++ b/examples/cpp20_echo_server.cpp @@ -4,11 +4,14 @@ * accompanying file LICENSE.txt) */ -#include -#if defined(BOOST_ASIO_HAS_CO_AWAIT) -#include -#include +#include #include +#include +#include +#include +#include + +#if defined(BOOST_ASIO_HAS_CO_AWAIT) namespace net = boost::asio; using namespace net::experimental::awaitable_operators; diff --git a/examples/cpp20_intro.cpp b/examples/cpp20_intro.cpp index fc75a724..14b81762 100644 --- a/examples/cpp20_intro.cpp +++ b/examples/cpp20_intro.cpp @@ -4,11 +4,14 @@ * accompanying file LICENSE.txt) */ -#include -#if defined(BOOST_ASIO_HAS_CO_AWAIT) -#include +#include +#include +#include +#include #include +#if defined(BOOST_ASIO_HAS_CO_AWAIT) + namespace net = boost::asio; using boost::redis::request; using boost::redis::response; diff --git a/examples/cpp20_intro_awaitable_ops.cpp b/examples/cpp20_intro_awaitable_ops.cpp index 2c303e48..c60a1bb6 100644 --- a/examples/cpp20_intro_awaitable_ops.cpp +++ b/examples/cpp20_intro_awaitable_ops.cpp @@ -4,11 +4,11 @@ * accompanying file LICENSE.txt) */ +#include +#include +#include #include -#include #if defined(BOOST_ASIO_HAS_CO_AWAIT) -#include -#include namespace net = boost::asio; using namespace net::experimental::awaitable_operators; diff --git a/examples/cpp20_intro_tls.cpp b/examples/cpp20_intro_tls.cpp index 5d607337..f24aab81 100644 --- a/examples/cpp20_intro_tls.cpp +++ b/examples/cpp20_intro_tls.cpp @@ -4,17 +4,17 @@ * accompanying file LICENSE.txt) */ +#include +#include +#include +#include +#include +#include #include #include #include -#include #if defined(BOOST_ASIO_HAS_CO_AWAIT) -#include -#include - -#include -#include namespace net = boost::asio; namespace redis = boost::redis; diff --git a/examples/cpp20_json.cpp b/examples/cpp20_json.cpp index 8a922259..a66e5e43 100644 --- a/examples/cpp20_json.cpp +++ b/examples/cpp20_json.cpp @@ -4,17 +4,19 @@ * accompanying file LICENSE.txt) */ -#include -#if defined(BOOST_ASIO_HAS_CO_AWAIT) -#define BOOST_JSON_NO_LIB -#define BOOST_CONTAINER_NO_LIB -#include +#include +#include +#include +#include #include #include #include #include "json.hpp" +#if defined(BOOST_ASIO_HAS_CO_AWAIT) // Include this in no more than one .cpp file. +#define BOOST_JSON_NO_LIB +#define BOOST_CONTAINER_NO_LIB #include namespace net = boost::asio; diff --git a/examples/cpp20_protobuf.cpp b/examples/cpp20_protobuf.cpp index 9bc89264..799daa47 100644 --- a/examples/cpp20_protobuf.cpp +++ b/examples/cpp20_protobuf.cpp @@ -4,9 +4,10 @@ * accompanying file LICENSE.txt) */ -#include -#if defined(BOOST_ASIO_HAS_CO_AWAIT) -#include +#include +#include +#include +#include #include #include "protobuf.hpp" @@ -14,6 +15,8 @@ // generated by CMakeLists.txt. #include "person.pb.h" +#if defined(BOOST_ASIO_HAS_CO_AWAIT) + namespace net = boost::asio; namespace redis = boost::redis; using boost::redis::request; @@ -21,7 +24,7 @@ using boost::redis::response; using boost::redis::operation; using boost::redis::ignore_t; using boost::redis::async_run; -using connection = boost::asio::use_awaitable_t<>::as_default_on_t; +using connection = net::use_awaitable_t<>::as_default_on_t; // The protobuf type described in examples/person.proto using tutorial::person; diff --git a/examples/cpp20_resolve_with_sentinel.cpp b/examples/cpp20_resolve_with_sentinel.cpp index 71cc1d87..8ed6c7b9 100644 --- a/examples/cpp20_resolve_with_sentinel.cpp +++ b/examples/cpp20_resolve_with_sentinel.cpp @@ -4,11 +4,12 @@ * accompanying file LICENSE.txt) */ +#include +#include +#include +#include #include -#include #if defined(BOOST_ASIO_HAS_CO_AWAIT) -#include -#include namespace net = boost::asio; using namespace net::experimental::awaitable_operators; diff --git a/examples/cpp20_subscriber.cpp b/examples/cpp20_subscriber.cpp index b34c5e8f..d70960a0 100644 --- a/examples/cpp20_subscriber.cpp +++ b/examples/cpp20_subscriber.cpp @@ -4,12 +4,13 @@ * accompanying file LICENSE.txt) */ +#include +#include +#include +#include #include -#include + #if defined(BOOST_ASIO_HAS_CO_AWAIT) -#include -#include -#include namespace net = boost::asio; using namespace net::experimental::awaitable_operators; @@ -18,7 +19,7 @@ using boost::redis::request; using boost::redis::async_run; using boost::redis::generic_response; using boost::redis::async_check_health; -using connection = boost::asio::use_awaitable_t<>::as_default_on_t; +using connection = net::use_awaitable_t<>::as_default_on_t; /* This example will subscribe and read pushes indefinitely. * diff --git a/examples/json.hpp b/examples/json.hpp index af9d333c..ca7e26f8 100644 --- a/examples/json.hpp +++ b/examples/json.hpp @@ -7,7 +7,9 @@ #ifndef BOOST_REDIS_JSON_HPP #define BOOST_REDIS_JSON_HPP -#include +#include +#include +#include #include namespace boost::redis::json diff --git a/examples/main.cpp b/examples/main.cpp index f86a0949..22dbdadf 100644 --- a/examples/main.cpp +++ b/examples/main.cpp @@ -4,12 +4,13 @@ * accompanying file LICENSE.txt) */ -#include +#include "start.hpp" +#include +#include +#include #if defined(BOOST_ASIO_HAS_CO_AWAIT) -#include "start.hpp" - extern boost::asio::awaitable co_main(std::string, std::string); auto main(int argc, char * argv[]) -> int diff --git a/examples/start.cpp b/examples/start.cpp index 7cceb56b..eb8674ac 100644 --- a/examples/start.cpp +++ b/examples/start.cpp @@ -4,10 +4,13 @@ * accompanying file LICENSE.txt) */ +#include +#include +#include +#include #include "start.hpp" -#include + #if defined(BOOST_ASIO_HAS_CO_AWAIT) -#include namespace net = boost::asio; diff --git a/examples/start.hpp b/examples/start.hpp index 6388686d..a4ee0ef5 100644 --- a/examples/start.hpp +++ b/examples/start.hpp @@ -7,7 +7,7 @@ #ifndef BOOST_REDIS_EXAMPLES_START_HPP #define BOOST_REDIS_EXAMPLES_START_HPP -#include +#include #if defined(BOOST_ASIO_HAS_CO_AWAIT) diff --git a/include/boost/redis/adapter/ignore.hpp b/include/boost/redis/adapter/ignore.hpp index a468e966..29a5502c 100644 --- a/include/boost/redis/adapter/ignore.hpp +++ b/include/boost/redis/adapter/ignore.hpp @@ -8,6 +8,7 @@ #define BOOST_REDIS_ADAPTER_IGNORE_HPP #include +#include #include #include diff --git a/include/boost/redis/check_health.hpp b/include/boost/redis/check_health.hpp index a18960f0..1b63b578 100644 --- a/include/boost/redis/check_health.hpp +++ b/include/boost/redis/check_health.hpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include diff --git a/include/boost/redis/detail/connection_ops.hpp b/include/boost/redis/detail/connection_ops.hpp index 36ee4336..1452341b 100644 --- a/include/boost/redis/detail/connection_ops.hpp +++ b/include/boost/redis/detail/connection_ops.hpp @@ -8,6 +8,7 @@ #define BOOST_REDIS_CONNECTION_OPS_HPP #include +#include #include #include #include @@ -37,11 +38,11 @@ struct wait_receive_op { { BOOST_ASIO_CORO_YIELD conn->channel_.async_send(system::error_code{}, 0, std::move(self)); - AEDIS_CHECK_OP0(;); + BOOST_REDIS_CHECK_OP0(;); BOOST_ASIO_CORO_YIELD conn->channel_.async_send(system::error_code{}, 0, std::move(self)); - AEDIS_CHECK_OP0(;); + BOOST_REDIS_CHECK_OP0(;); self.complete({}); } @@ -80,7 +81,7 @@ struct exec_read_op { conn->next_layer(), conn->make_dynamic_buffer(), "\r\n", std::move(self)); - AEDIS_CHECK_OP1(conn->cancel(operation::run);); + BOOST_REDIS_CHECK_OP1(conn->cancel(operation::run);); } // If the next request is a push we have to handle it to @@ -88,7 +89,7 @@ struct exec_read_op { if (resp3::to_type(conn->read_buffer_.front()) == resp3::type::push) { BOOST_ASIO_CORO_YIELD conn->async_wait_receive(std::move(self)); - AEDIS_CHECK_OP1(conn->cancel(operation::run);); + BOOST_REDIS_CHECK_OP1(conn->cancel(operation::run);); continue; } //----------------------------------- @@ -102,7 +103,7 @@ struct exec_read_op { ++index; - AEDIS_CHECK_OP1(conn->cancel(operation::run);); + BOOST_REDIS_CHECK_OP1(conn->cancel(operation::run);); read_size += n; @@ -132,7 +133,7 @@ struct receive_op { { BOOST_ASIO_CORO_YIELD conn->channel_.async_receive(std::move(self)); - AEDIS_CHECK_OP1(;); + BOOST_REDIS_CHECK_OP1(;); BOOST_ASIO_CORO_YIELD redis::detail::async_read(conn->next_layer(), conn->make_dynamic_buffer(), adapter, std::move(self)); @@ -147,7 +148,7 @@ struct receive_op { BOOST_ASIO_CORO_YIELD conn->channel_.async_receive(std::move(self)); - AEDIS_CHECK_OP1(;); + BOOST_REDIS_CHECK_OP1(;); self.complete({}, read_size); return; @@ -228,7 +229,7 @@ struct exec_op { BOOST_ASSERT(conn->reqs_.front() != nullptr); BOOST_ASIO_CORO_YIELD conn->async_exec_read(adapter, conn->reqs_.front()->get_number_of_commands(), std::move(self)); - AEDIS_CHECK_OP1(;); + BOOST_REDIS_CHECK_OP1(;); read_size = n; @@ -302,7 +303,7 @@ struct writer_op { while (conn->coalesce_requests()) { BOOST_ASIO_CORO_YIELD asio::async_write(conn->next_layer(), asio::buffer(conn->write_buffer_), std::move(self)); - AEDIS_CHECK_OP0(conn->cancel(operation::run);); + BOOST_REDIS_CHECK_OP0(conn->cancel(operation::run);); conn->on_write(); @@ -353,7 +354,7 @@ struct reader_op { return self.complete({}); // EOFINAE: EOF is not an error. } - AEDIS_CHECK_OP0(conn->cancel(operation::run);); + BOOST_REDIS_CHECK_OP0(conn->cancel(operation::run);); // We handle unsolicited events in the following way // diff --git a/include/boost/redis/detail/helper.hpp b/include/boost/redis/detail/helper.hpp new file mode 100644 index 00000000..a8a21efe --- /dev/null +++ b/include/boost/redis/detail/helper.hpp @@ -0,0 +1,37 @@ +/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com) + * + * Distributed under the Boost Software License, Version 1.0. (See + * accompanying file LICENSE.txt) + */ + +#ifndef BOOST_REDIS_HELPER_HPP +#define BOOST_REDIS_HELPER_HPP + +#include + +namespace boost::redis::detail +{ + +template +auto is_cancelled(T const& self) +{ + return self.get_cancellation_state().cancelled() != asio::cancellation_type_t::none; +} + +#define BOOST_REDIS_CHECK_OP0(X)\ + if (ec || redis::detail::is_cancelled(self)) {\ + X\ + self.complete(!!ec ? ec : asio::error::operation_aborted);\ + return;\ + } + +#define BOOST_REDIS_CHECK_OP1(X)\ + if (ec || redis::detail::is_cancelled(self)) {\ + X\ + self.complete(!!ec ? ec : asio::error::operation_aborted, {});\ + return;\ + } + +} // boost::redis::detail + +#endif // BOOST_REDIS_HELPER_HPP diff --git a/include/boost/redis/detail/read_ops.hpp b/include/boost/redis/detail/read_ops.hpp index c99af843..70d1546f 100644 --- a/include/boost/redis/detail/read_ops.hpp +++ b/include/boost/redis/detail/read_ops.hpp @@ -8,6 +8,7 @@ #define BOOST_REDIS_READ_OPS_HPP #include +#include #include #include #include @@ -18,26 +19,6 @@ namespace boost::redis::detail { -template -auto is_cancelled(T const& self) -{ - return self.get_cancellation_state().cancelled() != asio::cancellation_type_t::none; -} - -#define AEDIS_CHECK_OP0(X)\ - if (ec || redis::detail::is_cancelled(self)) {\ - X\ - self.complete(!!ec ? ec : asio::error::operation_aborted);\ - return;\ - } - -#define AEDIS_CHECK_OP1(X)\ - if (ec || redis::detail::is_cancelled(self)) {\ - X\ - self.complete(!!ec ? ec : asio::error::operation_aborted, {});\ - return;\ - } - template < class AsyncReadStream, class DynamicBuffer, @@ -68,7 +49,7 @@ class parse_op { if (!parser_.bulk_expected()) { BOOST_ASIO_CORO_YIELD asio::async_read_until(stream_, buf_, "\r\n", std::move(self)); - AEDIS_CHECK_OP1(;); + BOOST_REDIS_CHECK_OP1(;); } else { // On a bulk read we can't read until delimiter since the // payload may contain the delimiter itself so we have to @@ -87,7 +68,7 @@ class parse_op { buf_.data(buffer_size_, parser_.bulk_length() + 2 - buffer_size_), asio::transfer_all(), std::move(self)); - AEDIS_CHECK_OP1(;); + BOOST_REDIS_CHECK_OP1(;); } n = parser_.bulk_length() + 2; diff --git a/include/boost/redis/detail/runner.hpp b/include/boost/redis/detail/runner.hpp index 33bfae9d..121bb6e6 100644 --- a/include/boost/redis/detail/runner.hpp +++ b/include/boost/redis/detail/runner.hpp @@ -8,12 +8,13 @@ #define BOOST_REDIS_RUNNER_HPP // Has to included before promise.hpp to build on msvc. -#include +#include #include #include #include #include #include +#include #include #include #include @@ -144,16 +145,16 @@ struct runner_op { runner->timer_.expires_after(resolve_timeout); BOOST_ASIO_CORO_YIELD runner->async_resolve(host, port, std::move(self)); - AEDIS_CHECK_OP0(;) + BOOST_REDIS_CHECK_OP0(;) runner->timer_.expires_after(connect_timeout); BOOST_ASIO_CORO_YIELD runner->async_connect(conn->next_layer(), std::move(self)); - AEDIS_CHECK_OP0(;) + BOOST_REDIS_CHECK_OP0(;) BOOST_ASIO_CORO_YIELD conn->async_run(std::move(self)); - AEDIS_CHECK_OP0(;) + BOOST_REDIS_CHECK_OP0(;) self.complete({}); } } diff --git a/include/boost/redis/resp3/parser.hpp b/include/boost/redis/resp3/parser.hpp index 07517f3d..5db0b81c 100644 --- a/include/boost/redis/resp3/parser.hpp +++ b/include/boost/redis/resp3/parser.hpp @@ -8,7 +8,7 @@ #define BOOST_REDIS_RESP3_PARSER_HPP #include - +#include #include #include #include @@ -48,7 +48,7 @@ class parser { consume( char const* data, std::size_t n, - boost::system::error_code& ec) -> std::pair; + system::error_code& ec) -> std::pair; // Returns true when the parser is done with the current message. [[nodiscard]] auto done() const noexcept diff --git a/include/boost/redis/resp3/type.hpp b/include/boost/redis/resp3/type.hpp index 4ea37432..32913750 100644 --- a/include/boost/redis/resp3/type.hpp +++ b/include/boost/redis/resp3/type.hpp @@ -7,6 +7,7 @@ #ifndef BOOST_REDIS_RESP3_TYPE_HPP #define BOOST_REDIS_RESP3_TYPE_HPP +#include #include #include #include diff --git a/include/boost/redis/run.hpp b/include/boost/redis/run.hpp index 1ca54fe1..7464fa63 100644 --- a/include/boost/redis/run.hpp +++ b/include/boost/redis/run.hpp @@ -25,6 +25,7 @@ namespace boost::redis { * 2. Connects to one of the endpoints from 1. * 3. Calls async_run on the underlying connection. * + * @param conn A connection to Redis. * @param host Redis host to connect to. * @param port Redis port to connect to. * @param resolve_timeout Time the resolve operation is allowed to take. diff --git a/include/boost/redis/ssl/connection.hpp b/include/boost/redis/ssl/connection.hpp index b9e983f2..e1a895ac 100644 --- a/include/boost/redis/ssl/connection.hpp +++ b/include/boost/redis/ssl/connection.hpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include diff --git a/tests/common.hpp b/tests/common.hpp index 362cc9c3..c7d1e5c3 100644 --- a/tests/common.hpp +++ b/tests/common.hpp @@ -1,6 +1,8 @@ #pragma once -#include +#include +#include +#include #ifdef BOOST_ASIO_HAS_CO_AWAIT namespace net = boost::asio; diff --git a/tests/conn_check_health.cpp b/tests/conn_check_health.cpp index 096a7c52..4a7f8272 100644 --- a/tests/conn_check_health.cpp +++ b/tests/conn_check_health.cpp @@ -4,18 +4,14 @@ * accompanying file LICENSE.txt) */ -#include -#include +#include +#include #include - #define BOOST_TEST_MODULE check-health #include - -#include -#include -#include - +#include #include "common.hpp" +#include namespace net = boost::asio; using error_code = boost::system::error_code; diff --git a/tests/conn_echo_stress.cpp b/tests/conn_echo_stress.cpp index ffbd3b5d..ef2b8af4 100644 --- a/tests/conn_echo_stress.cpp +++ b/tests/conn_echo_stress.cpp @@ -4,16 +4,18 @@ * accompanying file LICENSE.txt) */ -#include -#include -#ifdef BOOST_ASIO_HAS_CO_AWAIT +#include +#include +#include #include #define BOOST_TEST_MODULE echo-stress #include -#include -#include +#include #include "common.hpp" #include "../examples/start.hpp" +#include + +#ifdef BOOST_ASIO_HAS_CO_AWAIT namespace net = boost::asio; using error_code = boost::system::error_code; diff --git a/tests/conn_exec.cpp b/tests/conn_exec.cpp index 37606271..858589c5 100644 --- a/tests/conn_exec.cpp +++ b/tests/conn_exec.cpp @@ -4,17 +4,13 @@ * accompanying file LICENSE.txt) */ -#include -#include +#include #include - #define BOOST_TEST_MODULE conn-exec #include - -#include -#include - +#include #include "common.hpp" +#include // TODO: Test whether HELLO won't be inserted passt commands that have // been already writen. diff --git a/tests/conn_exec_cancel.cpp b/tests/conn_exec_cancel.cpp index ee195f5d..3896d2fb 100644 --- a/tests/conn_exec_cancel.cpp +++ b/tests/conn_exec_cancel.cpp @@ -4,17 +4,17 @@ * accompanying file LICENSE.txt) */ -#include -#include -#ifdef BOOST_ASIO_HAS_CO_AWAIT +#include #include #include #define BOOST_TEST_MODULE conn-exec-cancel #include -#include -#include #include "common.hpp" #include "../examples/start.hpp" +#include +#include + +#ifdef BOOST_ASIO_HAS_CO_AWAIT // NOTE1: Sends hello separately. I have observed that if hello and // blpop are sent toguether, Redis will send the response of hello diff --git a/tests/conn_exec_error.cpp b/tests/conn_exec_error.cpp index 61b0a94e..b06d96fb 100644 --- a/tests/conn_exec_error.cpp +++ b/tests/conn_exec_error.cpp @@ -4,17 +4,13 @@ * accompanying file LICENSE.txt) */ -#include -#include +#include #include - #define BOOST_TEST_MODULE conn-exec-error #include - -#include -#include - #include "common.hpp" +#include +#include namespace net = boost::asio; namespace redis = boost::redis; diff --git a/tests/conn_push.cpp b/tests/conn_push.cpp index 53c969a7..025fcfb2 100644 --- a/tests/conn_push.cpp +++ b/tests/conn_push.cpp @@ -4,21 +4,20 @@ * accompanying file LICENSE.txt) */ -#include -#include -#ifdef BOOST_ASIO_HAS_CO_AWAIT +#include #include +#include +#include #include - #define BOOST_TEST_MODULE conn-push #include - -#include -#include +#include #include "common.hpp" +#include + +#ifdef BOOST_ASIO_HAS_CO_AWAIT namespace net = boost::asio; -namespace resp3 = boost::redis::resp3; using boost::redis::operation; using connection = boost::redis::connection; diff --git a/tests/conn_quit.cpp b/tests/conn_quit.cpp index 2d1f57c7..79fb2ab9 100644 --- a/tests/conn_quit.cpp +++ b/tests/conn_quit.cpp @@ -4,16 +4,13 @@ * accompanying file LICENSE.txt) */ -#include -#include +#include #include - #define BOOST_TEST_MODULE conn-quit #include - -#include -#include #include "common.hpp" +#include +#include namespace net = boost::asio; diff --git a/tests/conn_reconnect.cpp b/tests/conn_reconnect.cpp index 813c2d7c..b501a18f 100644 --- a/tests/conn_reconnect.cpp +++ b/tests/conn_reconnect.cpp @@ -4,17 +4,16 @@ * accompanying file LICENSE.txt) */ -#include -#include -#ifdef BOOST_ASIO_HAS_CO_AWAIT - +#include +#include #define BOOST_TEST_MODULE conn-reconnect #include - -#include -#include +#include #include "common.hpp" #include "../examples/start.hpp" +#include + +#ifdef BOOST_ASIO_HAS_CO_AWAIT namespace net = boost::asio; using error_code = boost::system::error_code; diff --git a/tests/conn_run_cancel.cpp b/tests/conn_run_cancel.cpp index edc971d0..09749bce 100644 --- a/tests/conn_run_cancel.cpp +++ b/tests/conn_run_cancel.cpp @@ -4,18 +4,17 @@ * accompanying file LICENSE.txt) */ -#include -#include -#ifdef BOOST_ASIO_HAS_CO_AWAIT +#include +#include #include #include - #define BOOST_TEST_MODULE conn-run-cancel #include - -#include -#include +#include #include "common.hpp" +#include + +#ifdef BOOST_ASIO_HAS_CO_AWAIT namespace net = boost::asio; diff --git a/tests/conn_tls.cpp b/tests/conn_tls.cpp index 56b85b84..38fcbfde 100644 --- a/tests/conn_tls.cpp +++ b/tests/conn_tls.cpp @@ -4,17 +4,14 @@ * accompanying file LICENSE.txt) */ -#include -#include +#include #include - #define BOOST_TEST_MODULE conn-tls #include - -#include #include -#include +#include #include "common.hpp" +#include namespace net = boost::asio; diff --git a/tests/cpp17_low_level_sync.cpp b/tests/cpp17_low_level_sync.cpp index 434441af..81cd5949 100644 --- a/tests/cpp17_low_level_sync.cpp +++ b/tests/cpp17_low_level_sync.cpp @@ -4,12 +4,13 @@ * accompanying file LICENSE.txt) */ +#include +#include +#include +#include +#include #include #include - -#include -#include -#include #include namespace net = boost::asio; diff --git a/tests/cpp20_low_level_async.cpp b/tests/cpp20_low_level_async.cpp index f357eb16..21614377 100644 --- a/tests/cpp20_low_level_async.cpp +++ b/tests/cpp20_low_level_async.cpp @@ -4,12 +4,15 @@ * accompanying file LICENSE.txt) */ -#include -#if defined(BOOST_ASIO_HAS_CO_AWAIT) -#include +#include +#include #include +#include +#include +#include #include #include +#if defined(BOOST_ASIO_HAS_CO_AWAIT) namespace net = boost::asio; namespace redis = boost::redis; diff --git a/tests/issue_50.cpp b/tests/issue_50.cpp index cb87ca18..b0ec4751 100644 --- a/tests/issue_50.cpp +++ b/tests/issue_50.cpp @@ -5,17 +5,17 @@ */ // Must come before any asio header, otherwise build fails on msvc. -#include -#include -#include -#if defined(BOOST_ASIO_HAS_CO_AWAIT) -#include +#include #include +#include #include - +#include +#include #include "../examples/start.hpp" +#if defined(BOOST_ASIO_HAS_CO_AWAIT) + namespace net = boost::asio; using namespace net::experimental::awaitable_operators; using steady_timer = net::use_awaitable_t<>::as_default_on_t; diff --git a/tests/low_level.cpp b/tests/low_level.cpp index 1e57e3fd..9902aedc 100644 --- a/tests/low_level.cpp +++ b/tests/low_level.cpp @@ -4,11 +4,10 @@ * accompanying file LICENSE.txt) */ -#include -#include -#include -#include - +#include +#include +#include +#include #include #include #include @@ -18,8 +17,11 @@ #include #define BOOST_TEST_MODULE low level #include +#include +#include +#include +#include -#include #include // TODO: Test with empty strings. diff --git a/tests/run.cpp b/tests/run.cpp index 4b3edc1e..f5cc507a 100644 --- a/tests/run.cpp +++ b/tests/run.cpp @@ -4,12 +4,10 @@ * accompanying file LICENSE.txt) */ +#include #define BOOST_TEST_MODULE run #include - #include -#include -#include #include namespace net = boost::asio; From c7f49c667792390cc72d25c777f3377a11357da4 Mon Sep 17 00:00:00 2001 From: Marcelo Zimbres Date: Wed, 15 Mar 2023 21:26:50 +0100 Subject: [PATCH 05/32] Adds address struct. --- CMakePresets.json | 33 ++++++++++++------------ examples/cpp17_intro.cpp | 13 +++++----- examples/cpp17_intro_sync.cpp | 12 ++++----- examples/cpp20_chat_room.cpp | 10 ++++--- examples/cpp20_containers.cpp | 9 ++++--- examples/cpp20_echo_server.cpp | 9 ++++--- examples/cpp20_intro.cpp | 11 ++++---- examples/cpp20_intro_awaitable_ops.cpp | 8 +++--- examples/cpp20_intro_tls.cpp | 6 +++-- examples/cpp20_json.cpp | 11 ++++---- examples/cpp20_protobuf.cpp | 9 ++++--- examples/cpp20_resolve_with_sentinel.cpp | 33 +++++++++++------------- examples/cpp20_subscriber.cpp | 8 +++--- examples/main.cpp | 12 ++++----- include/boost/redis.hpp | 1 + include/boost/redis/address.hpp | 27 +++++++++++++++++++ include/boost/redis/detail/runner.hpp | 27 ++++++++----------- include/boost/redis/run.hpp | 8 +++--- tests/conn_check_health.cpp | 5 ++-- tests/conn_echo_stress.cpp | 10 +++++-- tests/conn_exec.cpp | 6 +++-- tests/conn_exec_cancel.cpp | 14 ++++++---- tests/conn_exec_error.cpp | 11 ++++---- tests/conn_exec_retry.cpp | 12 +++++---- tests/conn_push.cpp | 17 +++++------- tests/conn_quit.cpp | 5 ++-- tests/conn_reconnect.cpp | 16 ++++++++---- tests/conn_run_cancel.cpp | 19 +++++++++----- tests/cpp20_low_level_async.cpp | 6 +++-- tests/issue_50.cpp | 7 ++--- tests/run.cpp | 9 ++++--- 31 files changed, 223 insertions(+), 161 deletions(-) create mode 100644 include/boost/redis/address.hpp diff --git a/CMakePresets.json b/CMakePresets.json index 7efcf951..3fba81f0 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -40,11 +40,11 @@ } }, { - "name": "g++-11-cpp17", + "name": "g++-11", "generator": "Unix Makefiles", "hidden": false, "inherits": ["cmake-pedantic"], - "binaryDir": "${sourceDir}/build/g++-11-cpp17", + "binaryDir": "${sourceDir}/build/g++-11", "cacheVariables": { "CMAKE_BUILD_TYPE": "Debug", "CMAKE_CXX_EXTENSIONS": "OFF", @@ -52,16 +52,16 @@ "CMAKE_CXX_COMPILER": "g++-11", "CMAKE_SHARED_LINKER_FLAGS": "-fsanitize=address", "CMAKE_CXX_STANDARD_REQUIRED": "ON", - "PROJECT_BINARY_DIR": "${sourceDir}/build/g++-11-cpp17", - "DOXYGEN_OUTPUT_DIRECTORY": "${sourceDir}/build/g++-11-cpp17/doc/" + "PROJECT_BINARY_DIR": "${sourceDir}/build/g++-11", + "DOXYGEN_OUTPUT_DIRECTORY": "${sourceDir}/build/g++-11/doc/" } }, { - "name": "g++-11-cpp20", + "name": "clang++-13", "generator": "Unix Makefiles", "hidden": false, "inherits": ["cmake-pedantic"], - "binaryDir": "${sourceDir}/build/g++-11-cpp20", + "binaryDir": "${sourceDir}/build/clang++-13", "cacheVariables": { "CMAKE_BUILD_TYPE": "Debug", "CMAKE_CXX_EXTENSIONS": "OFF", @@ -69,8 +69,8 @@ "CMAKE_CXX_COMPILER": "g++-11", "CMAKE_SHARED_LINKER_FLAGS": "-fsanitize=address", "CMAKE_CXX_STANDARD_REQUIRED": "ON", - "PROJECT_BINARY_DIR": "${sourceDir}/build/g++-11-cpp20", - "DOXYGEN_OUTPUT_DIRECTORY": "${sourceDir}/build/g++-11-cpp20/doc/" + "PROJECT_BINARY_DIR": "${sourceDir}/build/clang++-13", + "DOXYGEN_OUTPUT_DIRECTORY": "${sourceDir}/build/clang++-13/doc/" } }, { @@ -113,7 +113,7 @@ "name": "clang-tidy", "generator": "Unix Makefiles", "hidden": false, - "inherits": ["g++-11-cpp17"], + "inherits": ["g++-11"], "binaryDir": "${sourceDir}/build/clang-tidy", "cacheVariables": { "CMAKE_CXX_CLANG_TIDY": "clang-tidy;--header-filter=${sourceDir}/include/*", @@ -123,8 +123,8 @@ ], "buildPresets": [ { "name": "coverage", "configurePreset": "coverage" }, - { "name": "g++-11-cpp17", "configurePreset": "g++-11-cpp17" }, - { "name": "g++-11-cpp20", "configurePreset": "g++-11-cpp20" }, + { "name": "g++-11", "configurePreset": "g++-11" }, + { "name": "clang++-13", "configurePreset": "clang++-13" }, { "name": "libc++-14-cpp17", "configurePreset": "libc++-14-cpp17" }, { "name": "libc++-14-cpp20", "configurePreset": "libc++-14-cpp20" }, { "name": "clang-tidy", "configurePreset": "clang-tidy" } @@ -136,10 +136,11 @@ "output": {"outputOnFailure": true}, "execution": {"noTestsAction": "error", "stopOnFailure": true} }, - { "name": "coverage", "configurePreset": "coverage", "inherits": ["test"] }, - { "name": "g++-11-cpp17", "configurePreset": "g++-11-cpp17", "inherits": ["test"] }, - { "name": "libc++-14-cpp17", "configurePreset": "libc++-14-cpp17", "inherits": ["test"] }, - { "name": "libc++-14-cpp20", "configurePreset": "libc++-14-cpp20", "inherits": ["test"] }, - { "name": "clang-tidy", "configurePreset": "clang-tidy", "inherits": ["test"] } + { "name": "coverage", "configurePreset": "coverage", "inherits": ["test"] }, + { "name": "g++-11", "configurePreset": "g++-11", "inherits": ["test"] }, + { "name": "clang++-13", "configurePreset": "clang++-13", "inherits": ["test"] }, + { "name": "libc++-14-cpp17", "configurePreset": "libc++-14-cpp17", "inherits": ["test"] }, + { "name": "libc++-14-cpp20", "configurePreset": "libc++-14-cpp20", "inherits": ["test"] }, + { "name": "clang-tidy", "configurePreset": "clang-tidy", "inherits": ["test"] } ] } diff --git a/examples/cpp17_intro.cpp b/examples/cpp17_intro.cpp index d7be48d6..33c091f3 100644 --- a/examples/cpp17_intro.cpp +++ b/examples/cpp17_intro.cpp @@ -5,7 +5,8 @@ */ #include -#include +#include +#include #include namespace net = boost::asio; @@ -14,17 +15,17 @@ using boost::redis::request; using boost::redis::response; using boost::redis::ignore_t; using boost::redis::async_run; +using boost::redis::address; using namespace std::chrono_literals; auto main(int argc, char * argv[]) -> int { try { - std::string host = "127.0.0.1"; - std::string port = "6379"; + address addr; if (argc == 3) { - host = argv[1]; - port = argv[2]; + addr.host = argv[1]; + addr.port = argv[2]; } // The request @@ -38,7 +39,7 @@ auto main(int argc, char * argv[]) -> int net::io_context ioc; connection conn{ioc}; - async_run(conn, host, port, 10s, 10s, [&](auto){ + async_run(conn, addr, 10s, 10s, [&](auto){ conn.cancel(); }); diff --git a/examples/cpp17_intro_sync.cpp b/examples/cpp17_intro_sync.cpp index c89d7648..f204da8c 100644 --- a/examples/cpp17_intro_sync.cpp +++ b/examples/cpp17_intro_sync.cpp @@ -26,6 +26,7 @@ using boost::redis::request; using boost::redis::response; using boost::redis::ignore_t; using boost::redis::async_run; +using boost::redis::address; using boost::redis::async_check_health; using namespace std::chrono_literals; @@ -41,12 +42,11 @@ auto exec(std::shared_ptr conn, request const& req, Response& resp) auto main(int argc, char * argv[]) -> int { try { - std::string host = "127.0.0.1"; - std::string port = "6379"; + address addr; if (argc == 3) { - host = argv[1]; - port = argv[2]; + addr.host = argv[1]; + addr.port = argv[2]; } net::io_context ioc{1}; @@ -55,8 +55,8 @@ auto main(int argc, char * argv[]) -> int // Starts a thread that will can io_context::run on which the // connection will run. - std::thread t{[&ioc, conn, host, port]() { - async_run(*conn, host, port, 10s, 10s, [conn](auto){ + std::thread t{[&ioc, conn, addr]() { + async_run(*conn, addr, 10s, 10s, [conn](auto){ conn->cancel(); }); diff --git a/examples/cpp20_chat_room.cpp b/examples/cpp20_chat_room.cpp index 0f88f1ba..c8f77f8e 100644 --- a/examples/cpp20_chat_room.cpp +++ b/examples/cpp20_chat_room.cpp @@ -9,10 +9,11 @@ #include #include #include -#include #include #include + #if defined(BOOST_ASIO_HAS_CO_AWAIT) +#include #if defined(BOOST_ASIO_HAS_POSIX_STREAM_DESCRIPTOR) namespace net = boost::asio; @@ -23,6 +24,7 @@ using boost::redis::request; using boost::redis::generic_response; using boost::redis::async_check_health; using boost::redis::async_run; +using boost::redis::address; using connection = net::use_awaitable_t<>::as_default_on_t; // Chat over Redis pubsub. To test, run this program from multiple @@ -51,7 +53,7 @@ auto publisher(std::shared_ptr in, std::shared_ptr net::awaitable +auto co_main(address const& addr) -> net::awaitable { auto ex = co_await net::this_coro::executor; auto conn = std::make_shared(ex); @@ -62,13 +64,13 @@ auto co_main(std::string host, std::string port) -> net::awaitable req.push("HELLO", 3); req.push("SUBSCRIBE", "chat-channel"); - co_await ((async_run(*conn, host, port) || publisher(stream, conn) || receiver(conn) || + co_await ((async_run(*conn, addr) || publisher(stream, conn) || receiver(conn) || async_check_health(*conn) || sig.async_wait()) && conn->async_exec(req)); } #else // defined(BOOST_ASIO_HAS_POSIX_STREAM_DESCRIPTOR) -auto co_main(std::string host, std::string port) -> net::awaitable +auto co_main(address const&) -> net::awaitable { std::cout << "Requires support for posix streams." << std::endl; co_return; diff --git a/examples/cpp20_containers.cpp b/examples/cpp20_containers.cpp index 670c6f8e..63785a50 100644 --- a/examples/cpp20_containers.cpp +++ b/examples/cpp20_containers.cpp @@ -20,6 +20,7 @@ using boost::redis::request; using boost::redis::response; using boost::redis::ignore_t; using boost::redis::async_run; +using boost::redis::address; using connection = boost::asio::use_awaitable_t<>::as_default_on_t; void print(std::map const& cont) @@ -34,9 +35,9 @@ void print(std::vector const& cont) std::cout << "\n"; } -auto run(std::shared_ptr conn, std::string host, std::string port) -> net::awaitable +auto run(std::shared_ptr conn, address const& addr) -> net::awaitable { - co_await async_run(*conn, host, port); + co_await async_run(*conn, addr); } // Stores the content of some STL containers in Redis. @@ -105,11 +106,11 @@ auto quit(std::shared_ptr conn) -> net::awaitable } // Called from the main function (see main.cpp) -net::awaitable co_main(std::string host, std::string port) +net::awaitable co_main(address const& addr) { auto ex = co_await net::this_coro::executor; auto conn = std::make_shared(ex); - net::co_spawn(ex, run(conn, host, port), net::detached); + net::co_spawn(ex, run(conn, addr), net::detached); co_await store(conn); co_await transaction(conn); co_await hgetall(conn); diff --git a/examples/cpp20_echo_server.cpp b/examples/cpp20_echo_server.cpp index 60d507e9..8d5db7af 100644 --- a/examples/cpp20_echo_server.cpp +++ b/examples/cpp20_echo_server.cpp @@ -9,9 +9,9 @@ #include #include #include -#include #if defined(BOOST_ASIO_HAS_CO_AWAIT) +#include namespace net = boost::asio; using namespace net::experimental::awaitable_operators; @@ -22,7 +22,8 @@ using boost::redis::request; using boost::redis::response; using boost::redis::async_check_health; using boost::redis::async_run; -using connection = boost::asio::use_awaitable_t<>::as_default_on_t; +using boost::redis::address; +using connection = net::use_awaitable_t<>::as_default_on_t; auto echo_server_session(tcp_socket socket, std::shared_ptr conn) -> net::awaitable { @@ -50,7 +51,7 @@ auto listener(std::shared_ptr conn) -> net::awaitable } // Called from the main function (see main.cpp) -auto co_main(std::string host, std::string port) -> net::awaitable +auto co_main(address const& addr) -> net::awaitable { auto ex = co_await net::this_coro::executor; auto conn = std::make_shared(ex); @@ -59,7 +60,7 @@ auto co_main(std::string host, std::string port) -> net::awaitable request req; req.push("HELLO", 3); - co_await ((async_run(*conn, host, port) || listener(conn) || async_check_health(*conn) || + co_await ((async_run(*conn, addr) || listener(conn) || async_check_health(*conn) || sig.async_wait()) && conn->async_exec(req)); } diff --git a/examples/cpp20_intro.cpp b/examples/cpp20_intro.cpp index 14b81762..79ae4b6e 100644 --- a/examples/cpp20_intro.cpp +++ b/examples/cpp20_intro.cpp @@ -17,23 +17,24 @@ using boost::redis::request; using boost::redis::response; using boost::redis::ignore_t; using boost::redis::async_run; -using connection = boost::asio::use_awaitable_t<>::as_default_on_t; +using boost::redis::address; +using connection = net::use_awaitable_t<>::as_default_on_t; -auto run(std::shared_ptr conn, std::string host, std::string port) -> net::awaitable +auto run(std::shared_ptr conn, address addr) -> net::awaitable { // async_run coordinate read and write operations. - co_await async_run(*conn, host, port); + co_await async_run(*conn, addr); // Cancel pending operations, if any. conn->cancel(); } // Called from the main function (see main.cpp) -auto co_main(std::string host, std::string port) -> net::awaitable +auto co_main(address const& addr) -> net::awaitable { auto ex = co_await net::this_coro::executor; auto conn = std::make_shared(ex); - net::co_spawn(ex, run(conn, host, port), net::detached); + net::co_spawn(ex, run(conn, addr), net::detached); // A request can contain multiple commands. request req; diff --git a/examples/cpp20_intro_awaitable_ops.cpp b/examples/cpp20_intro_awaitable_ops.cpp index c60a1bb6..a58b8552 100644 --- a/examples/cpp20_intro_awaitable_ops.cpp +++ b/examples/cpp20_intro_awaitable_ops.cpp @@ -6,9 +6,10 @@ #include #include -#include #include + #if defined(BOOST_ASIO_HAS_CO_AWAIT) +#include namespace net = boost::asio; using namespace net::experimental::awaitable_operators; @@ -16,10 +17,11 @@ using boost::redis::request; using boost::redis::response; using boost::redis::ignore_t; using boost::redis::async_run; +using boost::redis::address; using connection = boost::asio::use_awaitable_t<>::as_default_on_t; // Called from the main function (see main.cpp) -auto co_main(std::string host, std::string port) -> net::awaitable +auto co_main(address const& addr) -> net::awaitable { try { request req; @@ -30,7 +32,7 @@ auto co_main(std::string host, std::string port) -> net::awaitable response resp; connection conn{co_await net::this_coro::executor}; - co_await (async_run(conn, host, port) || conn.async_exec(req, resp)); + co_await (async_run(conn, addr) || conn.async_exec(req, resp)); std::cout << "PING: " << std::get<1>(resp).value() << std::endl; } catch (std::exception const& e) { diff --git a/examples/cpp20_intro_tls.cpp b/examples/cpp20_intro_tls.cpp index f24aab81..2acc07ed 100644 --- a/examples/cpp20_intro_tls.cpp +++ b/examples/cpp20_intro_tls.cpp @@ -6,8 +6,8 @@ #include #include +#include #include -#include #include #include #include @@ -15,6 +15,7 @@ #include #if defined(BOOST_ASIO_HAS_CO_AWAIT) +#include namespace net = boost::asio; namespace redis = boost::redis; @@ -24,6 +25,7 @@ using connection = net::use_awaitable_t<>::as_default_on_t bool { @@ -31,7 +33,7 @@ auto verify_certificate(bool, net::ssl::verify_context&) -> bool return true; } -net::awaitable co_main(std::string, std::string) +net::awaitable co_main(address const&) { request req; req.push("HELLO", 3, "AUTH", "aedis", "aedis"); diff --git a/examples/cpp20_json.cpp b/examples/cpp20_json.cpp index a66e5e43..0427b03a 100644 --- a/examples/cpp20_json.cpp +++ b/examples/cpp20_json.cpp @@ -11,12 +11,12 @@ #include #include #include -#include "json.hpp" #if defined(BOOST_ASIO_HAS_CO_AWAIT) // Include this in no more than one .cpp file. #define BOOST_JSON_NO_LIB #define BOOST_CONTAINER_NO_LIB +#include "json.hpp" #include namespace net = boost::asio; @@ -27,6 +27,7 @@ using boost::redis::response; using boost::redis::operation; using boost::redis::ignore_t; using boost::redis::async_run; +using boost::redis::address; using connection = boost::asio::use_awaitable_t<>::as_default_on_t; // Struct that will be stored in Redis using json serialization. @@ -46,16 +47,16 @@ void boost_redis_to_bulk(std::string& to, user const& u) void boost_redis_from_bulk(user& u, std::string_view sv, boost::system::error_code& ec) { boost::redis::json::from_bulk(u, sv, ec); } -auto run(std::shared_ptr conn, std::string host, std::string port) -> net::awaitable +auto run(std::shared_ptr conn, address addr) -> net::awaitable { - co_await async_run(*conn, host, port); + co_await async_run(*conn, addr); } -net::awaitable co_main(std::string host, std::string port) +net::awaitable co_main(address const& addr) { auto ex = co_await net::this_coro::executor; auto conn = std::make_shared(ex); - net::co_spawn(ex, run(conn, host, port), net::detached); + net::co_spawn(ex, run(conn, addr), net::detached); // user object that will be stored in Redis in json format. user const u{"Joao", "58", "Brazil"}; diff --git a/examples/cpp20_protobuf.cpp b/examples/cpp20_protobuf.cpp index 799daa47..a3e9cbc3 100644 --- a/examples/cpp20_protobuf.cpp +++ b/examples/cpp20_protobuf.cpp @@ -24,6 +24,7 @@ using boost::redis::response; using boost::redis::operation; using boost::redis::ignore_t; using boost::redis::async_run; +using boost::redis::address; using connection = net::use_awaitable_t<>::as_default_on_t; // The protobuf type described in examples/person.proto @@ -44,16 +45,16 @@ void boost_redis_from_bulk(person& u, std::string_view sv, boost::system::error_ using tutorial::boost_redis_to_bulk; using tutorial::boost_redis_from_bulk; -auto run(std::shared_ptr conn, std::string host, std::string port) -> net::awaitable +auto run(std::shared_ptr conn, address const& addr) -> net::awaitable { - co_await async_run(*conn, host, port); + co_await async_run(*conn, addr); } -net::awaitable co_main(std::string host, std::string port) +net::awaitable co_main(address const& addr) { auto ex = co_await net::this_coro::executor; auto conn = std::make_shared(ex); - net::co_spawn(ex, run(conn, host, port), net::detached); + net::co_spawn(ex, run(conn, addr), net::detached); person p; p.set_name("Louis"); diff --git a/examples/cpp20_resolve_with_sentinel.cpp b/examples/cpp20_resolve_with_sentinel.cpp index 8ed6c7b9..aeb33c2f 100644 --- a/examples/cpp20_resolve_with_sentinel.cpp +++ b/examples/cpp20_resolve_with_sentinel.cpp @@ -7,9 +7,10 @@ #include #include #include -#include #include + #if defined(BOOST_ASIO_HAS_CO_AWAIT) +#include namespace net = boost::asio; using namespace net::experimental::awaitable_operators; @@ -18,20 +19,16 @@ using boost::redis::request; using boost::redis::response; using boost::redis::ignore_t; using boost::redis::async_run; +using boost::redis::address; using connection = boost::asio::use_awaitable_t<>::as_default_on_t; auto redir(boost::system::error_code& ec) { return net::redirect_error(net::use_awaitable, ec); } -struct address { - std::string host; - std::string port; -}; - // For more info see // - https://redis.io/docs/manual/sentinel. // - https://redis.io/docs/reference/sentinel-clients. -auto resolve_master_address(std::vector
const& endpoints) -> net::awaitable
+auto resolve_master_address(std::vector
const& addresses) -> net::awaitable
{ request req; req.push("SENTINEL", "get-master-addr-by-name", "mymaster"); @@ -39,29 +36,29 @@ auto resolve_master_address(std::vector
const& endpoints) -> net::await auto conn = std::make_shared(co_await net::this_coro::executor); - response>, ignore_t> addr; - for (auto ep : endpoints) { + response>, ignore_t> resp; + for (auto addr : addresses) { boost::system::error_code ec; - co_await (async_run(*conn, ep.host, ep.port) && conn->async_exec(req, addr, redir(ec))); + co_await (async_run(*conn, addr) && conn->async_exec(req, resp, redir(ec))); conn->reset_stream(); - if (std::get<0>(addr)) - co_return address{std::get<0>(addr).value().value().at(0), std::get<0>(addr).value().value().at(1)}; + if (std::get<0>(resp)) + co_return address{std::get<0>(resp).value().value().at(0), std::get<0>(resp).value().value().at(1)}; } co_return address{}; } -auto co_main(std::string host, std::string port) -> net::awaitable +auto co_main(address const& addr) -> net::awaitable { // A list of sentinel addresses from which only one is responsive. // This simulates sentinels that are down. - std::vector
const endpoints - { {"foo", "26379"} - , {"bar", "26379"} - , {host, port} + std::vector
const addresses + { address{"foo", "26379"} + , address{"bar", "26379"} + , addr }; - auto const ep = co_await resolve_master_address(endpoints); + auto const ep = co_await resolve_master_address(addresses); std::clog << "Host: " << ep.host << "\n" diff --git a/examples/cpp20_subscriber.cpp b/examples/cpp20_subscriber.cpp index d70960a0..b09fd85c 100644 --- a/examples/cpp20_subscriber.cpp +++ b/examples/cpp20_subscriber.cpp @@ -6,11 +6,12 @@ #include #include +#include #include -#include #include #if defined(BOOST_ASIO_HAS_CO_AWAIT) +#include namespace net = boost::asio; using namespace net::experimental::awaitable_operators; @@ -19,6 +20,7 @@ using boost::redis::request; using boost::redis::async_run; using boost::redis::generic_response; using boost::redis::async_check_health; +using boost::redis::address; using connection = net::use_awaitable_t<>::as_default_on_t; /* This example will subscribe and read pushes indefinitely. @@ -47,7 +49,7 @@ auto receiver(std::shared_ptr conn) -> net::awaitable } } -auto co_main(std::string host, std::string port) -> net::awaitable +auto co_main(address const& addr) -> net::awaitable { auto ex = co_await net::this_coro::executor; auto conn = std::make_shared(ex); @@ -59,7 +61,7 @@ auto co_main(std::string host, std::string port) -> net::awaitable // The loop will reconnect on connection lost. To exit type Ctrl-C twice. for (;;) { - co_await ((async_run(*conn, host, port) || async_check_health(*conn) || receiver(conn)) && conn->async_exec(req)); + co_await ((async_run(*conn, addr) || async_check_health(*conn) || receiver(conn)) && conn->async_exec(req)); conn->reset_stream(); timer.expires_after(std::chrono::seconds{1}); co_await timer.async_wait(); diff --git a/examples/main.cpp b/examples/main.cpp index 22dbdadf..ff0b527d 100644 --- a/examples/main.cpp +++ b/examples/main.cpp @@ -5,25 +5,25 @@ */ #include "start.hpp" +#include #include #include #include #if defined(BOOST_ASIO_HAS_CO_AWAIT) -extern boost::asio::awaitable co_main(std::string, std::string); +extern boost::asio::awaitable co_main(boost::redis::address const&); auto main(int argc, char * argv[]) -> int { - std::string host = "127.0.0.1"; - std::string port = "6379"; + boost::redis::address addr; if (argc == 3) { - host = argv[1]; - port = argv[2]; + addr.host = argv[1]; + addr.port = argv[2]; } - return start(co_main(host, port)); + return start(co_main(addr)); } #else // defined(BOOST_ASIO_HAS_CO_AWAIT) diff --git a/include/boost/redis.hpp b/include/boost/redis.hpp index 9eac385d..cd64c3fb 100644 --- a/include/boost/redis.hpp +++ b/include/boost/redis.hpp @@ -12,6 +12,7 @@ #include #include #include +#include /** @defgroup high-level-api Reference * diff --git a/include/boost/redis/address.hpp b/include/boost/redis/address.hpp new file mode 100644 index 00000000..a4af57d8 --- /dev/null +++ b/include/boost/redis/address.hpp @@ -0,0 +1,27 @@ +/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com) + * + * Distributed under the Boost Software License, Version 1.0. (See + * accompanying file LICENSE.txt) + */ + +#ifndef BOOST_REDIS_ADDRESS_HPP +#define BOOST_REDIS_ADDRESS_HPP + +#include + +namespace boost::redis +{ + +/** @brief Address of a Redis server + * @ingroup high-level-api + */ +struct address { + /// Redis host. + std::string host = "127.0.0.1"; + /// Redis port. + std::string port = "6379"; +}; + +} // boost::redis + +#endif // BOOST_REDIS_ADDRESS_HPP diff --git a/include/boost/redis/detail/runner.hpp b/include/boost/redis/detail/runner.hpp index 121bb6e6..b571efb9 100644 --- a/include/boost/redis/detail/runner.hpp +++ b/include/boost/redis/detail/runner.hpp @@ -10,6 +10,7 @@ // Has to included before promise.hpp to build on msvc. #include #include +#include #include #include #include @@ -25,8 +26,6 @@ namespace boost::redis::detail { template struct resolve_op { Runner* runner = nullptr; - std::string_view host; - std::string_view port; asio::coroutine coro{}; template @@ -40,7 +39,10 @@ struct resolve_op { { BOOST_ASIO_CORO_YIELD asio::experimental::make_parallel_group( - [this](auto token) { return runner->resv_.async_resolve(host.data(), port.data(), token);}, + [this](auto token) + { + return runner->resv_.async_resolve(runner->addr_.host, runner->addr_.port, token); + }, [this](auto token) { return runner->timer_.async_wait(token);} ).async_wait( asio::experimental::wait_for_one(), @@ -131,8 +133,6 @@ template struct runner_op { Runner* runner = nullptr; Connection* conn = nullptr; - std::string_view host; - std::string_view port; std::chrono::steady_clock::duration resolve_timeout; std::chrono::steady_clock::duration connect_timeout; asio::coroutine coro{}; @@ -144,7 +144,7 @@ struct runner_op { { runner->timer_.expires_after(resolve_timeout); BOOST_ASIO_CORO_YIELD - runner->async_resolve(host, port, std::move(self)); + runner->async_resolve(std::move(self)); BOOST_REDIS_CHECK_OP0(;) runner->timer_.expires_after(connect_timeout); @@ -163,19 +163,15 @@ struct runner_op { template class runner { public: - runner(Executor ex): resv_{ex}, timer_{ex} {} + runner(Executor ex, address addr): resv_{ex}, timer_{ex}, addr_{addr} {} template - auto - async_resolve( - std::string_view host, - std::string_view port, - CompletionToken&& token) + auto async_resolve(CompletionToken&& token) { return asio::async_compose < CompletionToken , void(system::error_code) - >(resolve_op{this, host, port}, token, resv_); + >(resolve_op{this}, token, resv_); } template @@ -191,8 +187,6 @@ class runner { auto async_run( Connection& conn, - std::string_view host, - std::string_view port, std::chrono::steady_clock::duration resolve_timeout, std::chrono::steady_clock::duration connect_timeout, CompletionToken&& token) @@ -200,7 +194,7 @@ class runner { return asio::async_compose < CompletionToken , void(system::error_code) - >(runner_op{this, &conn, host, port, resolve_timeout, connect_timeout}, token, resv_); + >(runner_op{this, &conn, resolve_timeout, connect_timeout}, token, resv_); } private: @@ -218,6 +212,7 @@ class runner { resolver_type resv_; timer_type timer_; + address addr_; asio::ip::tcp::resolver::results_type endpoints_; }; diff --git a/include/boost/redis/run.hpp b/include/boost/redis/run.hpp index 7464fa63..3b081f6f 100644 --- a/include/boost/redis/run.hpp +++ b/include/boost/redis/run.hpp @@ -10,6 +10,7 @@ // Has to included before promise.hpp to build on msvc. #include #include +#include #include #include #include @@ -39,21 +40,18 @@ template < auto async_run( basic_connection& conn, - std::string_view host = "127.0.0.1", - std::string_view port = "6379", + address addr = address{"127.0.0.1", "6379"}, std::chrono::steady_clock::duration resolve_timeout = std::chrono::seconds{10}, std::chrono::steady_clock::duration connect_timeout = std::chrono::seconds{10}, CompletionToken token = CompletionToken{}) { using executor_type = typename Socket::executor_type; using runner_type = detail::runner; - auto runner = std::make_shared(conn.get_executor()); + auto runner = std::make_shared(conn.get_executor(), addr); return runner->async_run( conn, - host, - port, resolve_timeout, connect_timeout, asio::consign(std::move(token), runner)); diff --git a/tests/conn_check_health.cpp b/tests/conn_check_health.cpp index 4a7f8272..cc410c0a 100644 --- a/tests/conn_check_health.cpp +++ b/tests/conn_check_health.cpp @@ -22,6 +22,7 @@ using boost::redis::operation; using boost::redis::generic_response; using boost::redis::async_check_health; using boost::redis::async_run; +using boost::redis::address; using namespace std::chrono_literals; std::chrono::seconds const interval{1}; @@ -103,12 +104,12 @@ BOOST_AUTO_TEST_CASE(check_health) generic_response resp; push_callback{&conn, &conn2, &resp, &req2}(); // Starts reading pushes. - async_run(conn, "127.0.0.1", "6379", 10s, 10s, [](auto ec){ + async_run(conn, address{}, 10s, 10s, [](auto ec){ std::cout << "B" << std::endl; BOOST_TEST(!!ec); }); - async_run(conn2, "127.0.0.1", "6379", 10s, 10s, [](auto ec){ + async_run(conn2, address{}, 10s, 10s, [](auto ec){ std::cout << "C" << std::endl; BOOST_TEST(!!ec); }); diff --git a/tests/conn_echo_stress.cpp b/tests/conn_echo_stress.cpp index ef2b8af4..8b815837 100644 --- a/tests/conn_echo_stress.cpp +++ b/tests/conn_echo_stress.cpp @@ -25,6 +25,7 @@ using boost::redis::response; using boost::redis::ignore; using boost::redis::ignore_t; using boost::redis::async_run; +using boost::redis::address; using connection = boost::asio::use_awaitable_t<>::as_default_on_t; using namespace std::chrono_literals; @@ -79,7 +80,9 @@ auto async_echo_stress() -> net::awaitable for (int i = 0; i < sessions; ++i) net::co_spawn(ex, echo_session(conn, std::to_string(i), msgs), net::detached); - co_await async_run(*conn); + + address addr; + co_await async_run(*conn, addr); } BOOST_AUTO_TEST_CASE(echo_stress) @@ -88,5 +91,8 @@ BOOST_AUTO_TEST_CASE(echo_stress) } #else -int main(){} +BOOST_AUTO_TEST_CASE(dummy) +{ + BOOST_TEST(true); +} #endif diff --git a/tests/conn_exec.cpp b/tests/conn_exec.cpp index 858589c5..050d0db3 100644 --- a/tests/conn_exec.cpp +++ b/tests/conn_exec.cpp @@ -5,6 +5,7 @@ */ #include +#include #include #define BOOST_TEST_MODULE conn-exec #include @@ -25,6 +26,7 @@ using boost::redis::response; using boost::redis::ignore; using boost::redis::ignore_t; using boost::redis::async_run; +using boost::redis::address; using namespace std::chrono_literals; BOOST_AUTO_TEST_CASE(hello_priority) @@ -73,7 +75,7 @@ BOOST_AUTO_TEST_CASE(hello_priority) seen3 = true; }); - async_run(conn, "127.0.0.1", "6379", 10s, 10s, [](auto ec){ + async_run(conn, address{}, 10s, 10s, [](auto ec){ BOOST_TEST(!ec); }); @@ -95,7 +97,7 @@ BOOST_AUTO_TEST_CASE(wrong_response_data_type) conn.async_exec(req, resp, [](auto ec, auto){ BOOST_CHECK_EQUAL(ec, boost::redis::error::not_a_number); }); - async_run(conn, "127.0.0.1", "6379", 10s, 10s, [](auto ec){ + async_run(conn, address{}, 10s, 10s, [](auto ec){ BOOST_CHECK_EQUAL(ec, boost::asio::error::basic_errors::operation_aborted); }); diff --git a/tests/conn_exec_cancel.cpp b/tests/conn_exec_cancel.cpp index 3896d2fb..7e2ecabe 100644 --- a/tests/conn_exec_cancel.cpp +++ b/tests/conn_exec_cancel.cpp @@ -6,7 +6,6 @@ #include #include -#include #define BOOST_TEST_MODULE conn-exec-cancel #include #include "common.hpp" @@ -15,6 +14,7 @@ #include #ifdef BOOST_ASIO_HAS_CO_AWAIT +#include // NOTE1: Sends hello separately. I have observed that if hello and // blpop are sent toguether, Redis will send the response of hello @@ -31,6 +31,7 @@ using boost::redis::generic_response; using boost::redis::ignore; using boost::redis::ignore_t; using boost::redis::async_run; +using boost::redis::address; using connection = boost::asio::use_awaitable_t<>::as_default_on_t; using namespace std::chrono_literals; @@ -41,7 +42,7 @@ auto async_ignore_explicit_cancel_of_req_written() -> net::awaitable generic_response gresp; auto conn = std::make_shared(ex); - async_run(*conn, "127.0.0.1", "6379", 10s, 10s, [conn](auto ec) { + async_run(*conn, address{}, 10s, 10s, [conn](auto ec) { std::cout << "async_run: " << ec.message() << std::endl; BOOST_TEST(!ec); }); @@ -90,7 +91,7 @@ auto ignore_implicit_cancel_of_req_written() -> net::awaitable // Calls async_run separately from the group of ops below to avoid // having it canceled when the timer fires. - async_run(*conn, "127.0.0.1", "6379", 10s, 10s, [conn](auto ec) { + async_run(*conn, address{}, 10s, 10s, [conn](auto ec) { BOOST_CHECK_EQUAL(ec, net::error::basic_errors::operation_aborted); }); @@ -155,7 +156,7 @@ BOOST_AUTO_TEST_CASE(test_cancel_of_req_written_on_run_canceled) conn.async_exec(req0, ignore, c0); - async_run(conn, "127.0.0.1", "6379", 10s, 10s, [](auto ec){ + async_run(conn, address{}, 10s, 10s, [](auto ec){ BOOST_CHECK_EQUAL(ec, net::error::basic_errors::operation_aborted); }); @@ -170,5 +171,8 @@ BOOST_AUTO_TEST_CASE(test_cancel_of_req_written_on_run_canceled) } #else -int main(){} +BOOST_AUTO_TEST_CASE(dummy) +{ + BOOST_TEST(true); +} #endif diff --git a/tests/conn_exec_error.cpp b/tests/conn_exec_error.cpp index b06d96fb..75675201 100644 --- a/tests/conn_exec_error.cpp +++ b/tests/conn_exec_error.cpp @@ -24,6 +24,7 @@ using boost::redis::ignore; using boost::redis::ignore_t; using boost::redis::error; using boost::redis::async_run; +using boost::redis::address; using namespace std::chrono_literals; BOOST_AUTO_TEST_CASE(no_ignore_error) @@ -41,7 +42,7 @@ BOOST_AUTO_TEST_CASE(no_ignore_error) BOOST_CHECK_EQUAL(ec, error::resp3_simple_error); conn.cancel(redis::operation::run); }); - async_run(conn, "127.0.0.1", "6379", 10s, 10s, [](auto ec){ + async_run(conn, address{}, 10s, 10s, [](auto ec){ BOOST_CHECK_EQUAL(ec, boost::asio::error::basic_errors::operation_aborted); }); @@ -80,7 +81,7 @@ BOOST_AUTO_TEST_CASE(has_diagnostic) conn.cancel(redis::operation::run); }); - async_run(conn, "127.0.0.1", "6379", 10s, 10s, [](auto ec){ + async_run(conn, address{}, 10s, 10s, [](auto ec){ BOOST_CHECK_EQUAL(ec, boost::asio::error::basic_errors::operation_aborted); }); @@ -129,7 +130,7 @@ BOOST_AUTO_TEST_CASE(resp3_error_in_cmd_pipeline) }; conn.async_exec(req1, resp1, c1); - async_run(conn, "127.0.0.1", "6379", 10s, 10s, [](auto ec){ + async_run(conn, address{}, 10s, 10s, [](auto ec){ BOOST_CHECK_EQUAL(ec, boost::asio::error::basic_errors::operation_aborted); }); @@ -192,7 +193,7 @@ BOOST_AUTO_TEST_CASE(error_in_transaction) conn.cancel(redis::operation::run); }); - async_run(conn, "127.0.0.1", "6379", 10s, 10s, [](auto ec){ + async_run(conn, address{}, 10s, 10s, [](auto ec){ BOOST_CHECK_EQUAL(ec, boost::asio::error::basic_errors::operation_aborted); }); @@ -242,7 +243,7 @@ BOOST_AUTO_TEST_CASE(subscriber_wrong_syntax) }; conn.async_receive(gresp, c3); - async_run(conn, "127.0.0.1", "6379", 10s, 10s, [](auto ec){ + async_run(conn, address{}, 10s, 10s, [](auto ec){ std::cout << "async_run" << std::endl; BOOST_CHECK_EQUAL(ec, boost::asio::error::basic_errors::operation_aborted); }); diff --git a/tests/conn_exec_retry.cpp b/tests/conn_exec_retry.cpp index 7371a995..87a97139 100644 --- a/tests/conn_exec_retry.cpp +++ b/tests/conn_exec_retry.cpp @@ -5,13 +5,14 @@ */ #include -#include +#include #include #define BOOST_TEST_MODULE conn-exec-retry #include -#include +#include +#include #include #include "common.hpp" @@ -24,6 +25,7 @@ using boost::redis::request; using boost::redis::response; using boost::redis::ignore; using boost::redis::async_run; +using boost::redis::address; using namespace std::chrono_literals; BOOST_AUTO_TEST_CASE(request_retry_false) @@ -71,7 +73,7 @@ BOOST_AUTO_TEST_CASE(request_retry_false) conn.async_exec(req0, ignore, c0); - async_run(conn, "127.0.0.1", "6379", 10s, 10s, [](auto ec){ + async_run(conn, address{}, 10s, 10s, [](auto ec){ BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled); }); @@ -132,13 +134,13 @@ BOOST_AUTO_TEST_CASE(request_retry_true) conn.async_exec(req0, ignore, c0); - async_run(conn, "127.0.0.1", "6379", 10s, 10s, [&](auto ec){ + async_run(conn, address{}, 10s, 10s, [&](auto ec){ // The first cacellation. BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled); conn.reset_stream(); // Reconnects and runs again to test req3. - async_run(conn, "127.0.0.1", "6379", 10s, 10s, [](auto ec){ + async_run(conn, address{}, 10s, 10s, [](auto ec){ std::cout << ec.message() << std::endl; BOOST_TEST(!ec); }); diff --git a/tests/conn_push.cpp b/tests/conn_push.cpp index 025fcfb2..089d8204 100644 --- a/tests/conn_push.cpp +++ b/tests/conn_push.cpp @@ -15,8 +15,6 @@ #include "common.hpp" #include -#ifdef BOOST_ASIO_HAS_CO_AWAIT - namespace net = boost::asio; using boost::redis::operation; @@ -28,6 +26,7 @@ using boost::redis::response; using boost::redis::ignore; using boost::redis::ignore_t; using boost::redis::async_run; +using boost::redis::address; using namespace std::chrono_literals; BOOST_AUTO_TEST_CASE(receives_push_waiting_resps) @@ -66,7 +65,7 @@ BOOST_AUTO_TEST_CASE(receives_push_waiting_resps) conn.async_exec(req1, ignore, c1); - async_run(conn, "127.0.0.1", "6379", 10s, 10s, [&](auto ec){ + async_run(conn, address{}, 10s, 10s, [&](auto ec){ BOOST_TEST(!ec); conn.cancel(operation::receive); }); @@ -98,7 +97,7 @@ BOOST_AUTO_TEST_CASE(push_received1) BOOST_TEST(!ec); }); - async_run(conn, "127.0.0.1", "6379", 10s, 10s, [&](auto ec){ + async_run(conn, address{}, 10s, 10s, [&](auto ec){ std::cout << "async_run: " << ec.message() << std::endl; conn.cancel(operation::receive); }); @@ -136,7 +135,7 @@ BOOST_AUTO_TEST_CASE(push_filtered_out) BOOST_TEST(!ec); }); - async_run(conn, "127.0.0.1", "6379", 10s, 10s, [&](auto ec){ + async_run(conn, address{}, 10s, 10s, [&](auto ec){ BOOST_TEST(!ec); }); @@ -202,7 +201,7 @@ BOOST_AUTO_TEST_CASE(test_push_adapter) BOOST_CHECK_EQUAL(ec, net::experimental::error::channel_errors::channel_cancelled); }); - async_run(conn, "127.0.0.1", "6379", 10s, 10s, [](auto ec){ + async_run(conn, address{}, 10s, 10s, [](auto ec){ BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled); }); @@ -303,7 +302,7 @@ BOOST_AUTO_TEST_CASE(many_subscribers) conn.async_exec(req0, ignore, c0); - async_run(conn, "127.0.0.1", "6379", 10s, 10s, [&](auto ec){ + async_run(conn, address{}, 10s, 10s, [&](auto ec){ BOOST_TEST(!ec); conn.cancel(operation::receive); }); @@ -312,7 +311,3 @@ BOOST_AUTO_TEST_CASE(many_subscribers) ioc.run(); } #endif - -#else -int main() {} -#endif diff --git a/tests/conn_quit.cpp b/tests/conn_quit.cpp index 79fb2ab9..4a2f6ab5 100644 --- a/tests/conn_quit.cpp +++ b/tests/conn_quit.cpp @@ -21,6 +21,7 @@ using boost::redis::request; using boost::redis::response; using boost::redis::ignore; using boost::redis::async_run; +using boost::redis::address; using namespace std::chrono_literals; BOOST_AUTO_TEST_CASE(test_quit1) @@ -37,7 +38,7 @@ BOOST_AUTO_TEST_CASE(test_quit1) BOOST_TEST(!ec); }); - async_run(conn, "127.0.0.1", "6379", 10s, 10s, [&](auto ec){ + async_run(conn, address{}, 10s, 10s, [&](auto ec){ BOOST_TEST(!ec); }); @@ -88,7 +89,7 @@ BOOST_AUTO_TEST_CASE(test_quit2) conn.async_exec(req1, ignore, c1); - async_run(conn, "127.0.0.1", "6379", 10s, 10s, [&](auto ec){ + async_run(conn, address{}, 10s, 10s, [&](auto ec){ BOOST_TEST(!ec); }); diff --git a/tests/conn_reconnect.cpp b/tests/conn_reconnect.cpp index b501a18f..0b866fda 100644 --- a/tests/conn_reconnect.cpp +++ b/tests/conn_reconnect.cpp @@ -14,6 +14,7 @@ #include #ifdef BOOST_ASIO_HAS_CO_AWAIT +#include namespace net = boost::asio; using error_code = boost::system::error_code; @@ -21,10 +22,10 @@ using boost::redis::request; using boost::redis::response; using boost::redis::ignore; using boost::redis::async_run; +using boost::redis::address; using connection = boost::asio::use_awaitable_t<>::as_default_on_t; using namespace std::chrono_literals; -#include using namespace boost::asio::experimental::awaitable_operators; net::awaitable test_reconnect_impl() @@ -37,11 +38,12 @@ net::awaitable test_reconnect_impl() connection conn{ex}; int i = 0; + address addr; for (; i < 5; ++i) { boost::system::error_code ec1, ec2; co_await ( conn.async_exec(req, ignore, net::redirect_error(net::use_awaitable, ec1)) && - async_run(conn, "127.0.0.1", "6379", 10s, 10s, net::redirect_error(net::use_awaitable, ec2)) + async_run(conn, addr, 10s, 10s, net::redirect_error(net::use_awaitable, ec2)) ); BOOST_TEST(!ec1); @@ -78,9 +80,10 @@ auto async_test_reconnect_timeout() -> net::awaitable req1.push("BLPOP", "any", 0); st.expires_after(std::chrono::seconds{1}); + address addr; co_await ( conn->async_exec(req1, ignore, redir(ec1)) || - async_run(*conn, "127.0.0.1", "6379", 10s, 10s, redir(ec2)) || + async_run(*conn, addr, 10s, 10s, redir(ec2)) || st.async_wait(redir(ec3)) ); @@ -98,7 +101,7 @@ auto async_test_reconnect_timeout() -> net::awaitable st.expires_after(std::chrono::seconds{1}); co_await ( conn->async_exec(req1, ignore, net::redirect_error(net::use_awaitable, ec1)) || - async_run(*conn, "127.0.0.1", "6379", 10s, 10s, net::redirect_error(net::use_awaitable, ec2)) || + async_run(*conn, addr, 10s, 10s, net::redirect_error(net::use_awaitable, ec2)) || st.async_wait(net::redirect_error(net::use_awaitable, ec3)) ); std::cout << "ccc" << std::endl; @@ -112,5 +115,8 @@ BOOST_AUTO_TEST_CASE(test_reconnect_and_idle) start(async_test_reconnect_timeout()); } #else -int main(){} +BOOST_AUTO_TEST_CASE(dummy) +{ + BOOST_TEST(true); +} #endif diff --git a/tests/conn_run_cancel.cpp b/tests/conn_run_cancel.cpp index 09749bce..83bde896 100644 --- a/tests/conn_run_cancel.cpp +++ b/tests/conn_run_cancel.cpp @@ -7,7 +7,6 @@ #include #include #include -#include #define BOOST_TEST_MODULE conn-run-cancel #include #include @@ -15,6 +14,8 @@ #include #ifdef BOOST_ASIO_HAS_CO_AWAIT +#include +#include namespace net = boost::asio; @@ -26,9 +27,9 @@ using boost::redis::request; using boost::redis::response; using boost::redis::ignore; using boost::redis::async_run; +using boost::redis::address; using namespace std::chrono_literals; -#include using namespace net::experimental::awaitable_operators; auto async_cancel_run_with_timer() -> net::awaitable @@ -40,7 +41,8 @@ auto async_cancel_run_with_timer() -> net::awaitable st.expires_after(std::chrono::seconds{1}); boost::system::error_code ec1, ec2; - co_await (async_run(conn, "127.0.0.1", "6379", 10s, 10s, redir(ec1)) || st.async_wait(redir(ec2))); + address addr; + co_await (async_run(conn, addr, 10s, 10s, redir(ec1)) || st.async_wait(redir(ec2))); BOOST_CHECK_EQUAL(ec1, boost::asio::error::basic_errors::operation_aborted); BOOST_TEST(!ec2); @@ -64,7 +66,8 @@ async_check_cancellation_not_missed(int n, std::chrono::milliseconds ms) -> net: for (auto i = 0; i < n; ++i) { timer.expires_after(ms); boost::system::error_code ec1, ec2; - co_await (async_run(conn, "127.0.0.1", "6379", 10s, 10s, redir(ec1)) || timer.async_wait(redir(ec2))); + address addr; + co_await (async_run(conn, addr, 10s, 10s, redir(ec1)) || timer.async_wait(redir(ec2))); BOOST_CHECK_EQUAL(ec1, boost::asio::error::basic_errors::operation_aborted); std::cout << "Counter: " << i << std::endl; } @@ -155,7 +158,8 @@ BOOST_AUTO_TEST_CASE(reset_before_run_completes) BOOST_TEST(!ec); conn.reset_stream(); }); - async_run(conn, "127.0.0.1", "6379", 10s, 10s, [&](auto ec){ + address addr; + async_run(conn, addr, 10s, 10s, [&](auto ec){ BOOST_CHECK_EQUAL(ec, net::error::operation_aborted); }); @@ -163,5 +167,8 @@ BOOST_AUTO_TEST_CASE(reset_before_run_completes) } #else -int main(){} +BOOST_AUTO_TEST_CASE(dummy) +{ + BOOST_TEST(true); +} #endif diff --git a/tests/cpp20_low_level_async.cpp b/tests/cpp20_low_level_async.cpp index 21614377..eba51e5a 100644 --- a/tests/cpp20_low_level_async.cpp +++ b/tests/cpp20_low_level_async.cpp @@ -5,6 +5,7 @@ */ #include +#include #include #include #include @@ -21,14 +22,15 @@ using tcp_socket = net::use_awaitable_t<>::as_default_on_t using boost::redis::adapter::adapt2; using net::ip::tcp; using boost::redis::request; +using boost::redis::address; using boost::redis::adapter::result; -auto co_main(std::string host, std::string port) -> net::awaitable +auto co_main(address const& addr) -> net::awaitable { auto ex = co_await net::this_coro::executor; resolver resv{ex}; - auto const addrs = co_await resv.async_resolve(host, port); + auto const addrs = co_await resv.async_resolve(addr.host, addr.port); tcp_socket socket{ex}; co_await net::async_connect(socket, addrs); diff --git a/tests/issue_50.cpp b/tests/issue_50.cpp index b0ec4751..3d6fba35 100644 --- a/tests/issue_50.cpp +++ b/tests/issue_50.cpp @@ -9,12 +9,12 @@ #include #include #include -#include #include #include #include "../examples/start.hpp" #if defined(BOOST_ASIO_HAS_CO_AWAIT) +#include namespace net = boost::asio; using namespace net::experimental::awaitable_operators; @@ -24,6 +24,7 @@ using boost::redis::response; using boost::redis::ignore; using boost::redis::async_check_health; using boost::redis::async_run; +using boost::redis::address; using connection = boost::asio::use_awaitable_t<>::as_default_on_t; using namespace std::chrono_literals; @@ -57,7 +58,7 @@ auto periodic_task(std::shared_ptr conn) -> net::awaitable std::cout << "Periodic task done!" << std::endl; } -auto co_main(std::string host, std::string port) -> net::awaitable +auto co_main(address const& addr) -> net::awaitable { auto ex = co_await net::this_coro::executor; auto conn = std::make_shared(ex); @@ -69,7 +70,7 @@ auto co_main(std::string host, std::string port) -> net::awaitable // The loop will reconnect on connection lost. To exit type Ctrl-C twice. for (int i = 0; i < 10; ++i) { - co_await ((async_run(*conn, host, port) || receiver(conn) || async_check_health(*conn) || periodic_task(conn)) && + co_await ((async_run(*conn, addr) || receiver(conn) || async_check_health(*conn) || periodic_task(conn)) && conn->async_exec(req)); conn->reset_stream(); diff --git a/tests/run.cpp b/tests/run.cpp index f5cc507a..8fda0989 100644 --- a/tests/run.cpp +++ b/tests/run.cpp @@ -14,6 +14,7 @@ namespace net = boost::asio; using connection = boost::redis::connection; using boost::redis::async_run; +using boost::redis::address; using boost::system::error_code; using namespace std::chrono_literals; @@ -29,7 +30,7 @@ BOOST_AUTO_TEST_CASE(resolve_bad_host) net::io_context ioc; connection conn{ioc}; - async_run(conn, "Atibaia", "6379", 1000s, 1000s, [](auto ec){ + async_run(conn, address{{"Atibaia"}, {"6379"}}, 1000s, 1000s, [](auto ec){ BOOST_TEST(is_host_not_found(ec)); }); @@ -41,7 +42,7 @@ BOOST_AUTO_TEST_CASE(resolve_with_timeout) net::io_context ioc; connection conn{ioc}; - async_run(conn, "Atibaia", "6379", 1ms, 1ms, [](auto ec){ + async_run(conn, address{{"Atibaia"}, {"6379"}}, 1ms, 1ms, [](auto ec){ BOOST_CHECK_EQUAL(ec, boost::redis::error::resolve_timeout); }); @@ -53,7 +54,7 @@ BOOST_AUTO_TEST_CASE(connect_bad_port) net::io_context ioc; connection conn{ioc}; - async_run(conn, "127.0.0.1", "1", 1000s, 10s, [](auto ec){ + async_run(conn, address{{"127.0.0.1"}, {"1"}}, 1000s, 10s, [](auto ec){ BOOST_CHECK_EQUAL(ec, net::error::basic_errors::connection_refused); }); @@ -65,7 +66,7 @@ BOOST_AUTO_TEST_CASE(connect_with_timeout) net::io_context ioc; connection conn{ioc}; - async_run(conn, "example.com", "1", 10s, 1ms, [](auto ec){ + async_run(conn, address{{"example.com"}, {"1"}}, 10s, 1ms, [](auto ec){ BOOST_CHECK_EQUAL(ec, boost::redis::error::connect_timeout); }); From 0bcb7dcf160783435d78f77d740ad8bb3ba1100e Mon Sep 17 00:00:00 2001 From: Marcelo Zimbres Date: Mon, 20 Mar 2023 01:26:50 +0100 Subject: [PATCH 06/32] Uses consign to simplify the check-health operation. --- include/boost/redis/check_health.hpp | 122 ++++++++++++++------------- 1 file changed, 64 insertions(+), 58 deletions(-) diff --git a/include/boost/redis/check_health.hpp b/include/boost/redis/check_health.hpp index 1b63b578..63d6a925 100644 --- a/include/boost/redis/check_health.hpp +++ b/include/boost/redis/check_health.hpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -23,79 +24,84 @@ namespace boost::redis { namespace detail { -template +template class check_health_op { -private: - using executor_type = typename Connection::executor_type; - - struct state { - using promise_type = asio::experimental::promise; - using timer_type = - asio::basic_waitable_timer< - std::chrono::steady_clock, - asio::wait_traits, - executor_type>; - - - timer_type timer_; - redis::request req_; - redis::generic_response resp_; - std::optional prom_; - std::chrono::steady_clock::duration interval_; - - state( - executor_type ex, - std::string const& msg, - std::chrono::steady_clock::duration interval) - : timer_{ex} - , interval_{interval} - { - req_.push("PING", msg); - } - - void reset() - { - resp_.value().clear(); - prom_.reset(); - } - }; - - Connection* conn_ = nullptr; - std::shared_ptr state_ = nullptr; - asio::coroutine coro_{}; - public: - check_health_op( - Connection& conn, - std::string const& msg, - std::chrono::steady_clock::duration interval) - : conn_{&conn} - , state_{std::make_shared(conn.get_executor(), msg, interval)} - { } + HealthChecker* checker = nullptr; + Connection* conn = nullptr; + asio::coroutine coro_{}; template void operator()(Self& self, system::error_code ec = {}, std::size_t = 0) { BOOST_ASIO_CORO_REENTER (coro_) for (;;) { - state_->prom_.emplace(conn_->async_exec(state_->req_, state_->resp_, asio::experimental::use_promise)); + checker->prom_.emplace(conn->async_exec(checker->req_, checker->resp_, asio::experimental::use_promise)); - state_->timer_.expires_after(state_->interval_); + checker->timer_.expires_after(checker->interval_); BOOST_ASIO_CORO_YIELD - state_->timer_.async_wait(std::move(self)); - if (ec || is_cancelled(self) || state_->resp_.value().empty()) { - conn_->cancel(operation::run); + checker->timer_.async_wait(std::move(self)); + if (ec || is_cancelled(self) || checker->resp_.value().empty()) { + conn->cancel(operation::run); BOOST_ASIO_CORO_YIELD - std::move(*state_->prom_)(std::move(self)); + std::move(*checker->prom_)(std::move(self)); self.complete({}); return; } - state_->reset(); + checker->reset(); } } }; +template +class health_checker { +private: + using promise_type = asio::experimental::promise; + using timer_type = + asio::basic_waitable_timer< + std::chrono::steady_clock, + asio::wait_traits, + Executor>; + +public: + health_checker( + Executor ex, + std::string const& msg, + std::chrono::steady_clock::duration interval) + : timer_{ex} + , interval_{interval} + { + req_.push("PING", msg); + } + + template < + class Connection, + class CompletionToken = asio::default_completion_token_t + > + auto async_check_health(Connection& conn, CompletionToken token = CompletionToken{}) + { + return asio::async_compose + < CompletionToken + , void(system::error_code) + >(check_health_op{this, &conn}, token, conn); + } + + void reset() + { + resp_.value().clear(); + prom_.reset(); + } + +private: + template friend class check_health_op; + timer_type timer_; + std::optional prom_; + redis::request req_; + redis::generic_response resp_; + std::chrono::steady_clock::duration interval_; +}; + } // detail /** @brief Checks Redis health asynchronously @@ -127,10 +133,10 @@ async_check_health( std::chrono::steady_clock::duration interval = std::chrono::seconds{2}, CompletionToken token = CompletionToken{}) { - return asio::async_compose - < CompletionToken - , void(system::error_code) - >(detail::check_health_op{conn, msg, interval}, token, conn); + using executor_type = typename Connection::executor_type; + using health_checker_type = detail::health_checker; + auto checker = std::make_shared(conn.get_executor(), msg, interval); + return checker->async_check_health(conn, asio::consign(std::move(token), checker)); } } // boost::redis From 7a085888080423b8b11bd4c86ee5325e2dc19167 Mon Sep 17 00:00:00 2001 From: Marcelo Zimbres Date: Wed, 22 Mar 2023 02:16:33 +0100 Subject: [PATCH 07/32] Progresses with the subscriber. --- examples/cpp17_intro.cpp | 4 ++- examples/cpp17_intro_sync.cpp | 4 ++- examples/cpp20_subscriber.cpp | 48 ++++++++++++--------------- include/boost/redis/check_health.hpp | 13 ++++++-- include/boost/redis/detail/runner.hpp | 40 +++++++++++++++------- include/boost/redis/logger.hpp | 45 +++++++++++++++++++++++++ include/boost/redis/run.hpp | 11 +++--- tests/conn_check_health.cpp | 6 ++-- tests/conn_exec.cpp | 6 ++-- tests/conn_exec_cancel.cpp | 8 +++-- tests/conn_exec_error.cpp | 12 ++++--- tests/conn_exec_retry.cpp | 7 ++-- tests/conn_push.cpp | 12 ++++--- tests/conn_quit.cpp | 6 ++-- tests/conn_reconnect.cpp | 8 +++-- tests/conn_run_cancel.cpp | 8 +++-- tests/run.cpp | 10 +++--- 17 files changed, 169 insertions(+), 79 deletions(-) create mode 100644 include/boost/redis/logger.hpp diff --git a/examples/cpp17_intro.cpp b/examples/cpp17_intro.cpp index 33c091f3..f482d306 100644 --- a/examples/cpp17_intro.cpp +++ b/examples/cpp17_intro.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include namespace net = boost::asio; @@ -15,6 +16,7 @@ using boost::redis::request; using boost::redis::response; using boost::redis::ignore_t; using boost::redis::async_run; +using boost::redis::logger; using boost::redis::address; using namespace std::chrono_literals; @@ -39,7 +41,7 @@ auto main(int argc, char * argv[]) -> int net::io_context ioc; connection conn{ioc}; - async_run(conn, addr, 10s, 10s, [&](auto){ + async_run(conn, addr, 10s, 10s, logger{}, [&](auto){ conn.cancel(); }); diff --git a/examples/cpp17_intro_sync.cpp b/examples/cpp17_intro_sync.cpp index f204da8c..20aff7ef 100644 --- a/examples/cpp17_intro_sync.cpp +++ b/examples/cpp17_intro_sync.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -27,6 +28,7 @@ using boost::redis::response; using boost::redis::ignore_t; using boost::redis::async_run; using boost::redis::address; +using boost::redis::logger; using boost::redis::async_check_health; using namespace std::chrono_literals; @@ -56,7 +58,7 @@ auto main(int argc, char * argv[]) -> int // Starts a thread that will can io_context::run on which the // connection will run. std::thread t{[&ioc, conn, addr]() { - async_run(*conn, addr, 10s, 10s, [conn](auto){ + async_run(*conn, addr, 10s, 10s, logger{}, [conn](auto){ conn->cancel(); }); diff --git a/examples/cpp20_subscriber.cpp b/examples/cpp20_subscriber.cpp index b09fd85c..a403dcc1 100644 --- a/examples/cpp20_subscriber.cpp +++ b/examples/cpp20_subscriber.cpp @@ -4,24 +4,25 @@ * accompanying file LICENSE.txt) */ -#include -#include +#include +#include #include +#include #include +#include +#include #include #if defined(BOOST_ASIO_HAS_CO_AWAIT) -#include namespace net = boost::asio; -using namespace net::experimental::awaitable_operators; -using steady_timer = net::use_awaitable_t<>::as_default_on_t; -using boost::redis::request; -using boost::redis::async_run; -using boost::redis::generic_response; -using boost::redis::async_check_health; -using boost::redis::address; -using connection = net::use_awaitable_t<>::as_default_on_t; +namespace redis = boost::redis; +using redis::generic_response; +using redis::address; +using redis::logger; +using redis::experimental::async_connect; +using redis::experimental::connect_config; +using connection = net::use_awaitable_t<>::as_default_on_t; /* This example will subscribe and read pushes indefinitely. * @@ -39,12 +40,16 @@ using connection = net::use_awaitable_t<>::as_default_on_t CLIENT kill TYPE pubsub */ -// Receives pushes. +// Receives server pushes. auto receiver(std::shared_ptr conn) -> net::awaitable { for (generic_response resp;;) { co_await conn->async_receive(resp); - std::cout << resp.value().at(1).value << " " << resp.value().at(2).value << " " << resp.value().at(3).value << std::endl; + std::cout + << resp.value().at(1).value + << " " << resp.value().at(2).value + << " " << resp.value().at(3).value + << std::endl; resp.value().clear(); } } @@ -53,19 +58,10 @@ auto co_main(address const& addr) -> net::awaitable { auto ex = co_await net::this_coro::executor; auto conn = std::make_shared(ex); - steady_timer timer{ex}; - - request req; - req.push("HELLO", 3); - req.push("SUBSCRIBE", "channel"); - - // The loop will reconnect on connection lost. To exit type Ctrl-C twice. - for (;;) { - co_await ((async_run(*conn, addr) || async_check_health(*conn) || receiver(conn)) && conn->async_exec(req)); - conn->reset_stream(); - timer.expires_after(std::chrono::seconds{1}); - co_await timer.async_wait(); - } + connect_config cfg; + cfg.addr = addr; + net::co_spawn(ex, receiver(conn), net::detached); + redis::experimental::async_connect(*conn, cfg, logger{}, net::consign(net::detached, conn)); } #endif // defined(BOOST_ASIO_HAS_CO_AWAIT) diff --git a/include/boost/redis/check_health.hpp b/include/boost/redis/check_health.hpp index 63d6a925..f76efc83 100644 --- a/include/boost/redis/check_health.hpp +++ b/include/boost/redis/check_health.hpp @@ -38,7 +38,7 @@ class check_health_op { { checker->prom_.emplace(conn->async_exec(checker->req_, checker->resp_, asio::experimental::use_promise)); - checker->timer_.expires_after(checker->interval_); + checker->timer_.expires_after(checker->timeout_); BOOST_ASIO_CORO_YIELD checker->timer_.async_wait(std::move(self)); if (ec || is_cancelled(self) || checker->resp_.value().empty()) { @@ -70,7 +70,7 @@ class health_checker { std::string const& msg, std::chrono::steady_clock::duration interval) : timer_{ex} - , interval_{interval} + , timeout_{interval} { req_.push("PING", msg); } @@ -93,13 +93,20 @@ class health_checker { prom_.reset(); } + void cancel() + { + timer_.cancel(); + if (prom_) + prom_.cancel(); + } + private: template friend class check_health_op; timer_type timer_; std::optional prom_; redis::request req_; redis::generic_response resp_; - std::chrono::steady_clock::duration interval_; + std::chrono::steady_clock::duration timeout_; }; } // detail diff --git a/include/boost/redis/detail/runner.hpp b/include/boost/redis/detail/runner.hpp index b571efb9..6a86d219 100644 --- a/include/boost/redis/detail/runner.hpp +++ b/include/boost/redis/detail/runner.hpp @@ -48,6 +48,8 @@ struct resolve_op { asio::experimental::wait_for_one(), std::move(self)); + runner->logger_.on_resolve(ec1, res); + if (is_cancelled(self)) { self.complete(asio::error::operation_aborted); return; @@ -86,10 +88,10 @@ struct connect_op { template void operator()( Self& self - , std::array order = {} - , system::error_code ec1 = {} - , asio::ip::tcp::endpoint const& = {} - , system::error_code ec2 = {}) + , std::array const& order = {} + , system::error_code const& ec1 = {} + , asio::ip::tcp::endpoint const& ep= {} + , system::error_code const& ec2 = {}) { BOOST_ASIO_CORO_REENTER (coro) { @@ -105,6 +107,8 @@ struct connect_op { asio::experimental::wait_for_one(), std::move(self)); + runner->logger_.on_connect(ec1, ep); + if (is_cancelled(self)) { self.complete(asio::error::operation_aborted); return; @@ -160,10 +164,21 @@ struct runner_op { } }; -template +template class runner { public: - runner(Executor ex, address addr): resv_{ex}, timer_{ex}, addr_{addr} {} + using timer_type = + asio::basic_waitable_timer< + std::chrono::steady_clock, + asio::wait_traits, + Executor>; + + runner(Executor ex, address addr, Logger l = Logger{}) + : resv_{ex} + , timer_{ex} + , addr_{addr} + , logger_{l} + {} template auto async_resolve(CompletionToken&& token) @@ -197,14 +212,14 @@ class runner { >(runner_op{this, &conn, resolve_timeout, connect_timeout}, token, resv_); } + void cancel() + { + resv_.cancel(); + timer_.cancel(); + } + private: using resolver_type = asio::ip::basic_resolver; - using timer_type = - asio::basic_waitable_timer< - std::chrono::steady_clock, - asio::wait_traits, - Executor>; - template friend struct runner_op; template friend struct connect_op; @@ -214,6 +229,7 @@ class runner { timer_type timer_; address addr_; asio::ip::tcp::resolver::results_type endpoints_; + Logger logger_; }; } // boost::redis::detail diff --git a/include/boost/redis/logger.hpp b/include/boost/redis/logger.hpp new file mode 100644 index 00000000..be52bec9 --- /dev/null +++ b/include/boost/redis/logger.hpp @@ -0,0 +1,45 @@ +/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com) + * + * Distributed under the Boost Software License, Version 1.0. (See + * accompanying file LICENSE.txt) + */ + +#ifndef BOOST_REDIS_LOGGER_HPP +#define BOOST_REDIS_LOGGER_HPP + +#include +#include +#include + +namespace boost::redis { + +// TODO: Move to ipp file. +// TODO: Implement filter. +class logger { +public: + void on_resolve(system::error_code const& ec, asio::ip::tcp::resolver::results_type const& res) + { + // TODO: Print the endpoints + std::clog << "on_resolve: " << ec.message() << std::endl; + } + + void on_connect(system::error_code const& ec, asio::ip::tcp::endpoint const& ep) + { + // TODO: Print the endpoint + std::clog << "on_connect: " << ec.message() << std::endl; + } + + void on_connection_lost() + { + std::clog << "on_connection_lost: " << std::endl; + } + + void on_hello(system::error_code const& ec) + { + std::clog << "on_hello: " << ec.message() << std::endl; + } +}; + +} // boost::redis + +#endif // BOOST_REDIS_LOGGER_HPP diff --git a/include/boost/redis/run.hpp b/include/boost/redis/run.hpp index 3b081f6f..5da3dabb 100644 --- a/include/boost/redis/run.hpp +++ b/include/boost/redis/run.hpp @@ -7,16 +7,17 @@ #ifndef BOOST_REDIS_RUN_HPP #define BOOST_REDIS_RUN_HPP -// Has to included before promise.hpp to build on msvc. #include #include #include +#include #include #include #include #include -namespace boost::redis { +namespace boost::redis +{ /** @brief Call async_run on the connection. * @ingroup high-level-api @@ -35,6 +36,7 @@ namespace boost::redis { */ template < class Socket, + class Logger = logger, class CompletionToken = asio::default_completion_token_t > auto @@ -43,11 +45,12 @@ async_run( address addr = address{"127.0.0.1", "6379"}, std::chrono::steady_clock::duration resolve_timeout = std::chrono::seconds{10}, std::chrono::steady_clock::duration connect_timeout = std::chrono::seconds{10}, + Logger l = Logger{}, CompletionToken token = CompletionToken{}) { using executor_type = typename Socket::executor_type; - using runner_type = detail::runner; - auto runner = std::make_shared(conn.get_executor(), addr); + using runner_type = detail::runner; + auto runner = std::make_shared(conn.get_executor(), addr, l); return runner->async_run( diff --git a/tests/conn_check_health.cpp b/tests/conn_check_health.cpp index cc410c0a..ffc770e8 100644 --- a/tests/conn_check_health.cpp +++ b/tests/conn_check_health.cpp @@ -5,6 +5,7 @@ */ #include +#include #include #include #define BOOST_TEST_MODULE check-health @@ -22,6 +23,7 @@ using boost::redis::operation; using boost::redis::generic_response; using boost::redis::async_check_health; using boost::redis::async_run; +using boost::redis::logger; using boost::redis::address; using namespace std::chrono_literals; @@ -104,12 +106,12 @@ BOOST_AUTO_TEST_CASE(check_health) generic_response resp; push_callback{&conn, &conn2, &resp, &req2}(); // Starts reading pushes. - async_run(conn, address{}, 10s, 10s, [](auto ec){ + async_run(conn, address{}, 10s, 10s, logger{}, [](auto ec){ std::cout << "B" << std::endl; BOOST_TEST(!!ec); }); - async_run(conn2, address{}, 10s, 10s, [](auto ec){ + async_run(conn2, address{}, 10s, 10s, logger{}, [](auto ec){ std::cout << "C" << std::endl; BOOST_TEST(!!ec); }); diff --git a/tests/conn_exec.cpp b/tests/conn_exec.cpp index 050d0db3..a4dcfffc 100644 --- a/tests/conn_exec.cpp +++ b/tests/conn_exec.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #define BOOST_TEST_MODULE conn-exec #include @@ -26,6 +27,7 @@ using boost::redis::response; using boost::redis::ignore; using boost::redis::ignore_t; using boost::redis::async_run; +using boost::redis::logger; using boost::redis::address; using namespace std::chrono_literals; @@ -75,7 +77,7 @@ BOOST_AUTO_TEST_CASE(hello_priority) seen3 = true; }); - async_run(conn, address{}, 10s, 10s, [](auto ec){ + async_run(conn, address{}, 10s, 10s, logger{}, [](auto ec){ BOOST_TEST(!ec); }); @@ -97,7 +99,7 @@ BOOST_AUTO_TEST_CASE(wrong_response_data_type) conn.async_exec(req, resp, [](auto ec, auto){ BOOST_CHECK_EQUAL(ec, boost::redis::error::not_a_number); }); - async_run(conn, address{}, 10s, 10s, [](auto ec){ + async_run(conn, address{}, 10s, 10s, logger{}, [](auto ec){ BOOST_CHECK_EQUAL(ec, boost::asio::error::basic_errors::operation_aborted); }); diff --git a/tests/conn_exec_cancel.cpp b/tests/conn_exec_cancel.cpp index 7e2ecabe..3425b438 100644 --- a/tests/conn_exec_cancel.cpp +++ b/tests/conn_exec_cancel.cpp @@ -5,6 +5,7 @@ */ #include +#include #include #define BOOST_TEST_MODULE conn-exec-cancel #include @@ -31,6 +32,7 @@ using boost::redis::generic_response; using boost::redis::ignore; using boost::redis::ignore_t; using boost::redis::async_run; +using boost::redis::logger; using boost::redis::address; using connection = boost::asio::use_awaitable_t<>::as_default_on_t; using namespace std::chrono_literals; @@ -42,7 +44,7 @@ auto async_ignore_explicit_cancel_of_req_written() -> net::awaitable generic_response gresp; auto conn = std::make_shared(ex); - async_run(*conn, address{}, 10s, 10s, [conn](auto ec) { + async_run(*conn, address{}, 10s, 10s, logger{}, [conn](auto ec) { std::cout << "async_run: " << ec.message() << std::endl; BOOST_TEST(!ec); }); @@ -91,7 +93,7 @@ auto ignore_implicit_cancel_of_req_written() -> net::awaitable // Calls async_run separately from the group of ops below to avoid // having it canceled when the timer fires. - async_run(*conn, address{}, 10s, 10s, [conn](auto ec) { + async_run(*conn, address{}, 10s, 10s, logger{}, [conn](auto ec) { BOOST_CHECK_EQUAL(ec, net::error::basic_errors::operation_aborted); }); @@ -156,7 +158,7 @@ BOOST_AUTO_TEST_CASE(test_cancel_of_req_written_on_run_canceled) conn.async_exec(req0, ignore, c0); - async_run(conn, address{}, 10s, 10s, [](auto ec){ + async_run(conn, address{}, 10s, 10s, logger{}, [](auto ec){ BOOST_CHECK_EQUAL(ec, net::error::basic_errors::operation_aborted); }); diff --git a/tests/conn_exec_error.cpp b/tests/conn_exec_error.cpp index 75675201..93ae5bff 100644 --- a/tests/conn_exec_error.cpp +++ b/tests/conn_exec_error.cpp @@ -5,6 +5,7 @@ */ #include +#include #include #define BOOST_TEST_MODULE conn-exec-error #include @@ -24,6 +25,7 @@ using boost::redis::ignore; using boost::redis::ignore_t; using boost::redis::error; using boost::redis::async_run; +using boost::redis::logger; using boost::redis::address; using namespace std::chrono_literals; @@ -42,7 +44,7 @@ BOOST_AUTO_TEST_CASE(no_ignore_error) BOOST_CHECK_EQUAL(ec, error::resp3_simple_error); conn.cancel(redis::operation::run); }); - async_run(conn, address{}, 10s, 10s, [](auto ec){ + async_run(conn, address{}, 10s, 10s, logger{}, [](auto ec){ BOOST_CHECK_EQUAL(ec, boost::asio::error::basic_errors::operation_aborted); }); @@ -81,7 +83,7 @@ BOOST_AUTO_TEST_CASE(has_diagnostic) conn.cancel(redis::operation::run); }); - async_run(conn, address{}, 10s, 10s, [](auto ec){ + async_run(conn, address{}, 10s, 10s, logger{}, [](auto ec){ BOOST_CHECK_EQUAL(ec, boost::asio::error::basic_errors::operation_aborted); }); @@ -130,7 +132,7 @@ BOOST_AUTO_TEST_CASE(resp3_error_in_cmd_pipeline) }; conn.async_exec(req1, resp1, c1); - async_run(conn, address{}, 10s, 10s, [](auto ec){ + async_run(conn, address{}, 10s, 10s, logger{}, [](auto ec){ BOOST_CHECK_EQUAL(ec, boost::asio::error::basic_errors::operation_aborted); }); @@ -193,7 +195,7 @@ BOOST_AUTO_TEST_CASE(error_in_transaction) conn.cancel(redis::operation::run); }); - async_run(conn, address{}, 10s, 10s, [](auto ec){ + async_run(conn, address{}, 10s, 10s, logger{}, [](auto ec){ BOOST_CHECK_EQUAL(ec, boost::asio::error::basic_errors::operation_aborted); }); @@ -243,7 +245,7 @@ BOOST_AUTO_TEST_CASE(subscriber_wrong_syntax) }; conn.async_receive(gresp, c3); - async_run(conn, address{}, 10s, 10s, [](auto ec){ + async_run(conn, address{}, 10s, 10s, logger{}, [](auto ec){ std::cout << "async_run" << std::endl; BOOST_CHECK_EQUAL(ec, boost::asio::error::basic_errors::operation_aborted); }); diff --git a/tests/conn_exec_retry.cpp b/tests/conn_exec_retry.cpp index 87a97139..bf70475b 100644 --- a/tests/conn_exec_retry.cpp +++ b/tests/conn_exec_retry.cpp @@ -25,6 +25,7 @@ using boost::redis::request; using boost::redis::response; using boost::redis::ignore; using boost::redis::async_run; +using boost::redis::logger; using boost::redis::address; using namespace std::chrono_literals; @@ -73,7 +74,7 @@ BOOST_AUTO_TEST_CASE(request_retry_false) conn.async_exec(req0, ignore, c0); - async_run(conn, address{}, 10s, 10s, [](auto ec){ + async_run(conn, address{}, 10s, 10s, logger{}, [](auto ec){ BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled); }); @@ -134,13 +135,13 @@ BOOST_AUTO_TEST_CASE(request_retry_true) conn.async_exec(req0, ignore, c0); - async_run(conn, address{}, 10s, 10s, [&](auto ec){ + async_run(conn, address{}, 10s, 10s, logger{}, [&](auto ec){ // The first cacellation. BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled); conn.reset_stream(); // Reconnects and runs again to test req3. - async_run(conn, address{}, 10s, 10s, [](auto ec){ + async_run(conn, address{}, 10s, 10s, logger{}, [](auto ec){ std::cout << ec.message() << std::endl; BOOST_TEST(!ec); }); diff --git a/tests/conn_push.cpp b/tests/conn_push.cpp index 089d8204..e6d07333 100644 --- a/tests/conn_push.cpp +++ b/tests/conn_push.cpp @@ -5,6 +5,7 @@ */ #include +#include #include #include #include @@ -26,6 +27,7 @@ using boost::redis::response; using boost::redis::ignore; using boost::redis::ignore_t; using boost::redis::async_run; +using boost::redis::logger; using boost::redis::address; using namespace std::chrono_literals; @@ -65,7 +67,7 @@ BOOST_AUTO_TEST_CASE(receives_push_waiting_resps) conn.async_exec(req1, ignore, c1); - async_run(conn, address{}, 10s, 10s, [&](auto ec){ + async_run(conn, address{}, 10s, 10s, logger{}, [&](auto ec){ BOOST_TEST(!ec); conn.cancel(operation::receive); }); @@ -97,7 +99,7 @@ BOOST_AUTO_TEST_CASE(push_received1) BOOST_TEST(!ec); }); - async_run(conn, address{}, 10s, 10s, [&](auto ec){ + async_run(conn, address{}, 10s, 10s, logger{}, [&](auto ec){ std::cout << "async_run: " << ec.message() << std::endl; conn.cancel(operation::receive); }); @@ -135,7 +137,7 @@ BOOST_AUTO_TEST_CASE(push_filtered_out) BOOST_TEST(!ec); }); - async_run(conn, address{}, 10s, 10s, [&](auto ec){ + async_run(conn, address{}, 10s, 10s, logger{}, [&](auto ec){ BOOST_TEST(!ec); }); @@ -201,7 +203,7 @@ BOOST_AUTO_TEST_CASE(test_push_adapter) BOOST_CHECK_EQUAL(ec, net::experimental::error::channel_errors::channel_cancelled); }); - async_run(conn, address{}, 10s, 10s, [](auto ec){ + async_run(conn, address{}, 10s, 10s, logger{}, [](auto ec){ BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled); }); @@ -302,7 +304,7 @@ BOOST_AUTO_TEST_CASE(many_subscribers) conn.async_exec(req0, ignore, c0); - async_run(conn, address{}, 10s, 10s, [&](auto ec){ + async_run(conn, address{}, 10s, 10s, logger{}, [&](auto ec){ BOOST_TEST(!ec); conn.cancel(operation::receive); }); diff --git a/tests/conn_quit.cpp b/tests/conn_quit.cpp index 4a2f6ab5..77661544 100644 --- a/tests/conn_quit.cpp +++ b/tests/conn_quit.cpp @@ -5,6 +5,7 @@ */ #include +#include #include #define BOOST_TEST_MODULE conn-quit #include @@ -21,6 +22,7 @@ using boost::redis::request; using boost::redis::response; using boost::redis::ignore; using boost::redis::async_run; +using boost::redis::logger; using boost::redis::address; using namespace std::chrono_literals; @@ -38,7 +40,7 @@ BOOST_AUTO_TEST_CASE(test_quit1) BOOST_TEST(!ec); }); - async_run(conn, address{}, 10s, 10s, [&](auto ec){ + async_run(conn, address{}, 10s, 10s, logger{}, [&](auto ec){ BOOST_TEST(!ec); }); @@ -89,7 +91,7 @@ BOOST_AUTO_TEST_CASE(test_quit2) conn.async_exec(req1, ignore, c1); - async_run(conn, address{}, 10s, 10s, [&](auto ec){ + async_run(conn, address{}, 10s, 10s, logger{}, [&](auto ec){ BOOST_TEST(!ec); }); diff --git a/tests/conn_reconnect.cpp b/tests/conn_reconnect.cpp index 0b866fda..fab680ad 100644 --- a/tests/conn_reconnect.cpp +++ b/tests/conn_reconnect.cpp @@ -5,6 +5,7 @@ */ #include +#include #include #define BOOST_TEST_MODULE conn-reconnect #include @@ -22,6 +23,7 @@ using boost::redis::request; using boost::redis::response; using boost::redis::ignore; using boost::redis::async_run; +using boost::redis::logger; using boost::redis::address; using connection = boost::asio::use_awaitable_t<>::as_default_on_t; using namespace std::chrono_literals; @@ -43,7 +45,7 @@ net::awaitable test_reconnect_impl() boost::system::error_code ec1, ec2; co_await ( conn.async_exec(req, ignore, net::redirect_error(net::use_awaitable, ec1)) && - async_run(conn, addr, 10s, 10s, net::redirect_error(net::use_awaitable, ec2)) + async_run(conn, addr, 10s, 10s, logger{}, net::redirect_error(net::use_awaitable, ec2)) ); BOOST_TEST(!ec1); @@ -83,7 +85,7 @@ auto async_test_reconnect_timeout() -> net::awaitable address addr; co_await ( conn->async_exec(req1, ignore, redir(ec1)) || - async_run(*conn, addr, 10s, 10s, redir(ec2)) || + async_run(*conn, addr, 10s, 10s, logger{}, redir(ec2)) || st.async_wait(redir(ec3)) ); @@ -101,7 +103,7 @@ auto async_test_reconnect_timeout() -> net::awaitable st.expires_after(std::chrono::seconds{1}); co_await ( conn->async_exec(req1, ignore, net::redirect_error(net::use_awaitable, ec1)) || - async_run(*conn, addr, 10s, 10s, net::redirect_error(net::use_awaitable, ec2)) || + async_run(*conn, addr, 10s, 10s, logger{}, net::redirect_error(net::use_awaitable, ec2)) || st.async_wait(net::redirect_error(net::use_awaitable, ec3)) ); std::cout << "ccc" << std::endl; diff --git a/tests/conn_run_cancel.cpp b/tests/conn_run_cancel.cpp index 83bde896..3f0a8abd 100644 --- a/tests/conn_run_cancel.cpp +++ b/tests/conn_run_cancel.cpp @@ -5,6 +5,7 @@ */ #include +#include #include #include #define BOOST_TEST_MODULE conn-run-cancel @@ -27,6 +28,7 @@ using boost::redis::request; using boost::redis::response; using boost::redis::ignore; using boost::redis::async_run; +using boost::redis::logger; using boost::redis::address; using namespace std::chrono_literals; @@ -42,7 +44,7 @@ auto async_cancel_run_with_timer() -> net::awaitable boost::system::error_code ec1, ec2; address addr; - co_await (async_run(conn, addr, 10s, 10s, redir(ec1)) || st.async_wait(redir(ec2))); + co_await (async_run(conn, addr, 10s, 10s, logger{}, redir(ec1)) || st.async_wait(redir(ec2))); BOOST_CHECK_EQUAL(ec1, boost::asio::error::basic_errors::operation_aborted); BOOST_TEST(!ec2); @@ -67,7 +69,7 @@ async_check_cancellation_not_missed(int n, std::chrono::milliseconds ms) -> net: timer.expires_after(ms); boost::system::error_code ec1, ec2; address addr; - co_await (async_run(conn, addr, 10s, 10s, redir(ec1)) || timer.async_wait(redir(ec2))); + co_await (async_run(conn, addr, 10s, 10s, logger{}, redir(ec1)) || timer.async_wait(redir(ec2))); BOOST_CHECK_EQUAL(ec1, boost::asio::error::basic_errors::operation_aborted); std::cout << "Counter: " << i << std::endl; } @@ -159,7 +161,7 @@ BOOST_AUTO_TEST_CASE(reset_before_run_completes) conn.reset_stream(); }); address addr; - async_run(conn, addr, 10s, 10s, [&](auto ec){ + async_run(conn, addr, 10s, 10s, logger{}, [&](auto ec){ BOOST_CHECK_EQUAL(ec, net::error::operation_aborted); }); diff --git a/tests/run.cpp b/tests/run.cpp index 8fda0989..34c29d8b 100644 --- a/tests/run.cpp +++ b/tests/run.cpp @@ -5,6 +5,7 @@ */ #include +#include #define BOOST_TEST_MODULE run #include #include @@ -14,6 +15,7 @@ namespace net = boost::asio; using connection = boost::redis::connection; using boost::redis::async_run; +using boost::redis::logger; using boost::redis::address; using boost::system::error_code; using namespace std::chrono_literals; @@ -30,7 +32,7 @@ BOOST_AUTO_TEST_CASE(resolve_bad_host) net::io_context ioc; connection conn{ioc}; - async_run(conn, address{{"Atibaia"}, {"6379"}}, 1000s, 1000s, [](auto ec){ + async_run(conn, address{{"Atibaia"}, {"6379"}}, 1000s, 1000s, logger{}, [](auto ec){ BOOST_TEST(is_host_not_found(ec)); }); @@ -42,7 +44,7 @@ BOOST_AUTO_TEST_CASE(resolve_with_timeout) net::io_context ioc; connection conn{ioc}; - async_run(conn, address{{"Atibaia"}, {"6379"}}, 1ms, 1ms, [](auto ec){ + async_run(conn, address{{"Atibaia"}, {"6379"}}, 1ms, 1ms, logger{}, [](auto ec){ BOOST_CHECK_EQUAL(ec, boost::redis::error::resolve_timeout); }); @@ -54,7 +56,7 @@ BOOST_AUTO_TEST_CASE(connect_bad_port) net::io_context ioc; connection conn{ioc}; - async_run(conn, address{{"127.0.0.1"}, {"1"}}, 1000s, 10s, [](auto ec){ + async_run(conn, address{{"127.0.0.1"}, {"1"}}, 1000s, 10s, logger{}, [](auto ec){ BOOST_CHECK_EQUAL(ec, net::error::basic_errors::connection_refused); }); @@ -66,7 +68,7 @@ BOOST_AUTO_TEST_CASE(connect_with_timeout) net::io_context ioc; connection conn{ioc}; - async_run(conn, address{{"example.com"}, {"1"}}, 10s, 1ms, [](auto ec){ + async_run(conn, address{{"example.com"}, {"1"}}, 10s, 1ms, logger{}, [](auto ec){ BOOST_CHECK_EQUAL(ec, boost::redis::error::connect_timeout); }); From 5ac4f7e8ade2a72a1014e3ba70a102e115f94a7b Mon Sep 17 00:00:00 2001 From: Marcelo Zimbres Date: Thu, 23 Mar 2023 16:49:37 +0100 Subject: [PATCH 08/32] Removes dependency on asio::promise as it does not compile on windows. --- examples/cpp20_subscriber.cpp | 2 +- include/boost/redis/check_health.hpp | 161 +++++++-- include/boost/redis/connection.hpp | 18 + include/boost/redis/error.hpp | 3 + .../boost/redis/experimental/connector.hpp | 313 ++++++++++++++++++ include/boost/redis/impl/error.ipp | 1 + include/boost/redis/logger.hpp | 4 +- tests/conn_check_health.cpp | 6 +- tests/issue_50.cpp | 56 ++-- 9 files changed, 498 insertions(+), 66 deletions(-) create mode 100644 include/boost/redis/experimental/connector.hpp diff --git a/examples/cpp20_subscriber.cpp b/examples/cpp20_subscriber.cpp index a403dcc1..6085af42 100644 --- a/examples/cpp20_subscriber.cpp +++ b/examples/cpp20_subscriber.cpp @@ -7,9 +7,9 @@ #include #include #include -#include #include #include +#include #include #include diff --git a/include/boost/redis/check_health.hpp b/include/boost/redis/check_health.hpp index f76efc83..aaf6d46b 100644 --- a/include/boost/redis/check_health.hpp +++ b/include/boost/redis/check_health.hpp @@ -11,24 +11,23 @@ #include #include #include -#include -#include -#include +#include #include #include #include +#include +#include #include #include -#include namespace boost::redis { namespace detail { template -class check_health_op { +class ping_op { public: - HealthChecker* checker = nullptr; - Connection* conn = nullptr; + HealthChecker* checker_ = nullptr; + Connection* conn_ = nullptr; asio::coroutine coro_{}; template @@ -36,20 +35,98 @@ class check_health_op { { BOOST_ASIO_CORO_REENTER (coro_) for (;;) { - checker->prom_.emplace(conn->async_exec(checker->req_, checker->resp_, asio::experimental::use_promise)); + if (checker_->checker_has_exited_) { + self.complete({}); + return; + } - checker->timer_.expires_after(checker->timeout_); BOOST_ASIO_CORO_YIELD - checker->timer_.async_wait(std::move(self)); - if (ec || is_cancelled(self) || checker->resp_.value().empty()) { - conn->cancel(operation::run); - BOOST_ASIO_CORO_YIELD - std::move(*checker->prom_)(std::move(self)); + conn_->async_exec(checker_->req_, checker_->resp_, std::move(self)); + BOOST_REDIS_CHECK_OP0(checker_->wait_timer_.cancel();) + + // Wait before pinging again. + checker_->ping_timer_.expires_after(checker_->ping_interval_); + BOOST_ASIO_CORO_YIELD + checker_->ping_timer_.async_wait(std::move(self)); + BOOST_REDIS_CHECK_OP0(;) + } + } +}; + +template +class check_timeout_op { +public: + HealthChecker* checker_ = nullptr; + Connection* conn_ = nullptr; + asio::coroutine coro_{}; + + template + void operator()(Self& self, system::error_code ec = {}) + { + BOOST_ASIO_CORO_REENTER (coro_) for (;;) + { + checker_->wait_timer_.expires_after(2 * checker_->ping_interval_); + BOOST_ASIO_CORO_YIELD + checker_->wait_timer_.async_wait(std::move(self)); + BOOST_REDIS_CHECK_OP0(;) + + if (!checker_->resp_.has_value()) { self.complete({}); return; } - checker->reset(); + if (checker_->resp_.value().empty()) { + checker_->ping_timer_.cancel(); + conn_->cancel(operation::run); + checker_->checker_has_exited_ = true; + self.complete(error::pong_timeout); + return; + } + + checker_->resp_.value().clear(); + + if (checker_->resp_.has_value()) { + checker_->resp_.value().clear(); + } + } + } +}; + +template +class check_health_op { +public: + HealthChecker* checker_ = nullptr; + Connection* conn_ = nullptr; + asio::coroutine coro_{}; + + template + void + operator()( + Self& self, + std::array order = {}, + system::error_code ec1 = {}, + system::error_code ec2 = {}) + { + BOOST_ASIO_CORO_REENTER (coro_) + { + BOOST_ASIO_CORO_YIELD + asio::experimental::make_parallel_group( + [this](auto token) { return checker_->async_ping(*conn_, token); }, + [this](auto token) { return checker_->async_check_timeout(*conn_, token);} + ).async_wait( + asio::experimental::wait_for_one(), + std::move(self)); + + if (is_cancelled(self)) { + self.complete(asio::error::operation_aborted); + return; + } + + switch (order[0]) { + case 0: self.complete(ec1); return; + case 1: self.complete(ec2); return; + default: BOOST_ASSERT(false); + } } } }; @@ -57,7 +134,6 @@ class check_health_op { template class health_checker { private: - using promise_type = asio::experimental::promise; using timer_type = asio::basic_waitable_timer< std::chrono::steady_clock, @@ -68,9 +144,10 @@ class health_checker { health_checker( Executor ex, std::string const& msg, - std::chrono::steady_clock::duration interval) - : timer_{ex} - , timeout_{interval} + std::chrono::steady_clock::duration ping_interval) + : ping_timer_{ex} + , wait_timer_{ex} + , ping_interval_{ping_interval} { req_.push("PING", msg); } @@ -87,26 +164,41 @@ class health_checker { >(check_health_op{this, &conn}, token, conn); } - void reset() + void cancel() + { + ping_timer_.cancel(); + wait_timer_.cancel(); + } + +private: + template + auto async_ping(Connection& conn, CompletionToken token) { - resp_.value().clear(); - prom_.reset(); + return asio::async_compose + < CompletionToken + , void(system::error_code) + >(ping_op{this, &conn}, token, conn, ping_timer_); } - void cancel() + template + auto async_check_timeout(Connection& conn, CompletionToken token) { - timer_.cancel(); - if (prom_) - prom_.cancel(); + return asio::async_compose + < CompletionToken + , void(system::error_code) + >(check_timeout_op{this, &conn}, token, conn, wait_timer_); } -private: + template friend class ping_op; + template friend class check_timeout_op; template friend class check_health_op; - timer_type timer_; - std::optional prom_; + + timer_type ping_timer_; + timer_type wait_timer_; redis::request req_; redis::generic_response resp_; - std::chrono::steady_clock::duration timeout_; + std::chrono::steady_clock::duration ping_interval_; + bool checker_has_exited_ = false; }; } // detail @@ -120,7 +212,7 @@ class health_checker { * * @param conn A connection to the Redis server. * @param msg The message to be sent with the [PING](https://redis.io/commands/ping/) command. Seting a proper and unique id will help users identify which connections are active. - * @param interval Ping interval. + * @param ping_interval Ping ping_interval. * @param token The completion token * * The completion token must have the following signature @@ -128,6 +220,9 @@ class health_checker { * @code * void f(system::error_code); * @endcode + * + * Completion occurs when a pong response is not receive within two + * times the ping interval. */ template < class Connection, @@ -137,12 +232,12 @@ auto async_check_health( Connection& conn, std::string const& msg = "Boost.Redis", - std::chrono::steady_clock::duration interval = std::chrono::seconds{2}, + std::chrono::steady_clock::duration ping_interval = std::chrono::seconds{2}, CompletionToken token = CompletionToken{}) { using executor_type = typename Connection::executor_type; using health_checker_type = detail::health_checker; - auto checker = std::make_shared(conn.get_executor(), msg, interval); + auto checker = std::make_shared(conn.get_executor(), msg, ping_interval); return checker->async_check_health(conn, asio::consign(std::move(token), checker)); } diff --git a/include/boost/redis/connection.hpp b/include/boost/redis/connection.hpp index 84dd22d5..abdaa9d4 100644 --- a/include/boost/redis/connection.hpp +++ b/include/boost/redis/connection.hpp @@ -51,6 +51,7 @@ class basic_connection : basic_connection(executor_type ex) : base_type{ex} , stream_{ex} + , reconnect_{true} {} /// Contructs from a context. @@ -227,6 +228,22 @@ class basic_connection : void reserve(std::size_t read, std::size_t write) { base_type::reserve(read, write); } + /** @brief Enable reconnection + * + * This property plays any role only when used with + * `boost::redis::async_run`. + */ + void enable_reconnection() noexcept {reconnect_ = true;} + + /** @brief Disable reconnection + * + * This property plays any role only when used with + * `boost::redis::async_run`. + */ + void disable_reconnection() noexcept {reconnect_ = false;} + + bool reconnect() const noexcept {return reconnect_;} + private: using this_type = basic_connection; @@ -244,6 +261,7 @@ class basic_connection : auto lowest_layer() noexcept -> auto& { return stream_.lowest_layer(); } Socket stream_; + bool reconnect_; }; /** \brief A connection that uses a asio::ip::tcp::socket. diff --git a/include/boost/redis/error.hpp b/include/boost/redis/error.hpp index f61325af..0cc18fa1 100644 --- a/include/boost/redis/error.hpp +++ b/include/boost/redis/error.hpp @@ -69,6 +69,9 @@ enum class error /// Connect timeout connect_timeout, + + /// Connect timeout + pong_timeout, }; /** \internal diff --git a/include/boost/redis/experimental/connector.hpp b/include/boost/redis/experimental/connector.hpp new file mode 100644 index 00000000..13dbce97 --- /dev/null +++ b/include/boost/redis/experimental/connector.hpp @@ -0,0 +1,313 @@ +/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com) + * + * Distributed under the Boost Software License, Version 1.0. (See + * accompanying file LICENSE.txt) + */ + +#ifndef BOOST_REDIS_CONNECTOR_HPP +#define BOOST_REDIS_CONNECTOR_HPP + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace boost::redis::experimental +{ + +struct connect_config { + address addr = address{"127.0.0.1", "6379"}; + std::string username; + std::string password; + std::string clientname = "Boost.Redis"; + std::string health_check_id = "Boost.Redis"; + std::chrono::steady_clock::duration resolve_timeout = std::chrono::seconds{10}; + std::chrono::steady_clock::duration connect_timeout = std::chrono::seconds{10}; + std::chrono::steady_clock::duration health_check_timeout = std::chrono::seconds{2}; + std::chrono::steady_clock::duration reconnect_wait_interval = std::chrono::seconds{1}; +}; + +namespace detail +{ + +template +struct hello_op { + Connector* ctor_ = nullptr; + Connection* conn_ = nullptr; + asio::coroutine coro_{}; + + template + void operator()(Self& self, system::error_code ec = {}, std::size_t = 0) + { + BOOST_ASIO_CORO_REENTER (coro_) + { + ctor_->req_hello_.clear(); + ctor_->resp_hello_.value().clear(); + ctor_->add_hello(); + + BOOST_ASIO_CORO_YIELD + conn_->async_exec(ctor_->req_hello_, ctor_->resp_hello_, std::move(self)); + + ctor_->logger_.on_hello(ec); + + BOOST_REDIS_CHECK_OP0(conn_->cancel(redis::operation::run);) + + if (ctor_->resp_hello_.has_error()) { + conn_->cancel(redis::operation::run); + switch (ctor_->resp_hello_.error().data_type) { + case resp3::type::simple_error: + self.complete(error::resp3_simple_error); + break; + + case resp3::type::blob_error: + self.complete(error::resp3_blob_error); + break; + + default: BOOST_ASSERT_MSG(false, "Unexpects error data type."); + } + } else { + self.complete({}); + } + } + } +}; + +template +struct run_check_exec_op { + Connector* ctor_ = nullptr; + Connection* conn_ = nullptr; + asio::coroutine coro_{}; + + template + void operator()( Self& self + , std::array order = {} + , system::error_code ec1 = {} + , system::error_code ec2 = {} + , std::size_t = 0) + { + BOOST_ASIO_CORO_REENTER (coro_) + { + BOOST_ASIO_CORO_YIELD + asio::experimental::make_parallel_group( + [this](auto token) { return ctor_->async_run_check(*conn_, token); }, + [this](auto token) { return ctor_->async_hello(*conn_, token); } + ).async_wait( + asio::experimental::wait_for_all(), + std::move(self) + ); + + if (is_cancelled(self)) { + self.complete(asio::error::operation_aborted); + return; + } + + // TODO: Which op should we use to complete? + switch (order[0]) { + case 0: self.complete(ec1); break; + case 1: self.complete(ec2); break; + default: BOOST_ASSERT(false); + } + } + } +}; + +template +struct run_check_op { + Connector* ctor_ = nullptr; + Connection* conn_ = nullptr; + asio::coroutine coro_{}; + + template + void operator()( Self& self + , std::array order = {} + , system::error_code ec1 = {} + , system::error_code ec2 = {}) + { + BOOST_ASIO_CORO_REENTER (coro_) + { + BOOST_ASIO_CORO_YIELD + asio::experimental::make_parallel_group( + [this](auto token) + { + return ctor_->runner_.async_run(*conn_, ctor_->cfg_.resolve_timeout, ctor_->cfg_.connect_timeout, token); + }, + [this](auto token) + { + return ctor_->health_checker_.async_check_health(*conn_, token); + } + ).async_wait( + asio::experimental::wait_for_one(), + std::move(self)); + + if (is_cancelled(self)) { + self.complete(asio::error::operation_aborted); + return; + } + + switch (order[0]) { + case 0: self.complete(ec1); break; + case 1: self.complete(ec2); break; + default: BOOST_ASSERT(false); + } + } + } +}; + +template +struct connect_op { + Connector* ctor_ = nullptr; + Connection* conn_ = nullptr; + asio::coroutine coro_{}; + + template + void operator()(Self& self, system::error_code ec = {}) + { + BOOST_ASIO_CORO_REENTER (coro_) for (;;) + { + BOOST_ASIO_CORO_YIELD + ctor_->async_run_check_exec(*conn_, std::move(self)); + ctor_->logger_.on_connection_lost(); + if (is_cancelled(self)) { + self.complete(asio::error::operation_aborted); + return; + } + + conn_->reset_stream(); + + if (!conn_->reconnect()) { + self.complete({}); + return; + } + + // Wait some time before trying to reconnect. + ctor_->reconnect_wait_timer_.expires_after(ctor_->cfg_.reconnect_wait_interval); + BOOST_ASIO_CORO_YIELD + ctor_->reconnect_wait_timer_.async_wait(std::move(self)); + BOOST_REDIS_CHECK_OP0(;) + } + } +}; + +template +class connector { +public: + connector(Executor ex, connect_config cfg, Logger l) + : runner_{ex, cfg.addr, l} + , health_checker_{ex, cfg.health_check_id, cfg.health_check_timeout} + , reconnect_wait_timer_{ex} + , cfg_{cfg} + , logger_{l} + { } + + template < + class Connection, + class CompletionToken = asio::default_completion_token_t + > + auto async_connect(Connection& conn, CompletionToken token = CompletionToken{}) + { + return asio::async_compose + < CompletionToken + , void(system::error_code) + >(connect_op{this, &conn}, token, conn); + } + + void cancel() + { + runner_.cancel(); + health_checker_.cancel(); + reconnect_wait_timer_.cancel(); + } + +private: + using runner_type = redis::detail::runner; + using health_checker_type = redis::detail::health_checker; + using timer_type = typename runner_type::timer_type; + + template friend struct connect_op; + template friend struct run_check_exec_op; + template friend struct run_check_op; + template friend struct hello_op; + + template + auto async_run_check(Connection& conn, CompletionToken token) + { + return asio::async_compose + < CompletionToken + , void(system::error_code) + >(run_check_op{this, &conn}, token, conn); + } + + template + auto async_run_check_exec(Connection& conn, CompletionToken token) + { + return asio::async_compose + < CompletionToken + , void(system::error_code) + >(run_check_exec_op{this, &conn}, token, conn); + } + + template + auto async_hello(Connection& conn, CompletionToken token) + { + return asio::async_compose + < CompletionToken + , void(system::error_code) + >(hello_op{this, &conn}, token, conn); + } + + void add_hello() + { + if (!cfg_.username.empty() && !cfg_.password.empty() && !cfg_.clientname.empty()) + req_hello_.push("HELLO", "3", "AUTH", cfg_.username, cfg_.password, "SETNAME", cfg_.clientname); + else if (cfg_.username.empty() && cfg_.password.empty() && cfg_.clientname.empty()) + req_hello_.push("HELLO", "3"); + else if (cfg_.clientname.empty()) + req_hello_.push("HELLO", "3", "AUTH", cfg_.username, cfg_.password); + else + req_hello_.push("HELLO", "3", "SETNAME", cfg_.clientname); + + // Subscribe to channels in the same request that sends HELLO + // because it has priority over all other requests. + // TODO: Subscribe to actual channels. + req_hello_.push("SUBSCRIBE", "channel"); + } + + runner_type runner_; + health_checker_type health_checker_; + timer_type reconnect_wait_timer_; + request req_hello_; + generic_response resp_hello_; + connect_config cfg_; + Logger logger_; +}; + +} // detail + +template < + class Socket, + class Logger = logger, + class CompletionToken = asio::default_completion_token_t +> +auto +async_connect( + basic_connection& conn, + connect_config cfg = connect_config{}, + Logger l = logger{}, + CompletionToken token = CompletionToken{}) +{ + using executor_type = typename Socket::executor_type; + using connector_type = detail::connector; + auto ctor = std::make_shared(conn.get_executor(), cfg, l); + return ctor->async_connect(conn, asio::consign(std::move(token), ctor)); +} + +} // boost::redis::experimental + +#endif // BOOST_REDIS_CONNECTOR_HPP diff --git a/include/boost/redis/impl/error.ipp b/include/boost/redis/impl/error.ipp index 502bd9ec..9f5c06eb 100644 --- a/include/boost/redis/impl/error.ipp +++ b/include/boost/redis/impl/error.ipp @@ -40,6 +40,7 @@ struct error_category_impl : system::error_category { case error::not_connected: return "Not connected."; case error::resolve_timeout: return "Resolve timeout."; case error::connect_timeout: return "Connect timeout."; + case error::pong_timeout: return "Pong timeout."; default: BOOST_ASSERT(false); return "Boost.Redis error."; } } diff --git a/include/boost/redis/logger.hpp b/include/boost/redis/logger.hpp index be52bec9..fd21256e 100644 --- a/include/boost/redis/logger.hpp +++ b/include/boost/redis/logger.hpp @@ -17,13 +17,13 @@ namespace boost::redis { // TODO: Implement filter. class logger { public: - void on_resolve(system::error_code const& ec, asio::ip::tcp::resolver::results_type const& res) + void on_resolve(system::error_code const& ec, asio::ip::tcp::resolver::results_type const&) { // TODO: Print the endpoints std::clog << "on_resolve: " << ec.message() << std::endl; } - void on_connect(system::error_code const& ec, asio::ip::tcp::endpoint const& ep) + void on_connect(system::error_code const& ec, asio::ip::tcp::endpoint const&) { // TODO: Print the endpoint std::clog << "on_connect: " << ec.message() << std::endl; diff --git a/tests/conn_check_health.cpp b/tests/conn_check_health.cpp index ffc770e8..87d26949 100644 --- a/tests/conn_check_health.cpp +++ b/tests/conn_check_health.cpp @@ -85,8 +85,8 @@ BOOST_AUTO_TEST_CASE(check_health) bool seen = false; async_check_health(conn, msg, interval, [&](auto ec) { - BOOST_TEST(!ec); - std::cout << "async_check_health: completed." << std::endl; + BOOST_CHECK_EQUAL(ec, boost::redis::error::pong_timeout); + std::cout << "async_check_health: completed: " << ec.message() << std::endl; seen = true; }); @@ -101,7 +101,7 @@ BOOST_AUTO_TEST_CASE(check_health) request req2; req2.push("HELLO", "3"); - req2.push("CLIENT", "PAUSE", "3000", "ALL"); + req2.push("CLIENT", "PAUSE", "5000", "ALL"); generic_response resp; push_callback{&conn, &conn2, &resp, &req2}(); // Starts reading pushes. diff --git a/tests/issue_50.cpp b/tests/issue_50.cpp index 3d6fba35..c5394333 100644 --- a/tests/issue_50.cpp +++ b/tests/issue_50.cpp @@ -6,33 +6,40 @@ // Must come before any asio header, otherwise build fails on msvc. -#include -#include +#include +#include #include +#include +#include +#include +#include +#include +#include #include #include #include "../examples/start.hpp" #if defined(BOOST_ASIO_HAS_CO_AWAIT) -#include namespace net = boost::asio; -using namespace net::experimental::awaitable_operators; +namespace redis = boost::redis; using steady_timer = net::use_awaitable_t<>::as_default_on_t; -using boost::redis::request; -using boost::redis::response; -using boost::redis::ignore; -using boost::redis::async_check_health; -using boost::redis::async_run; -using boost::redis::address; +using redis::request; +using redis::response; +using redis::ignore; +using redis::address; +using redis::logger; +using redis::experimental::async_connect; +using redis::experimental::connect_config; using connection = boost::asio::use_awaitable_t<>::as_default_on_t; using namespace std::chrono_literals; // Push consumer auto receiver(std::shared_ptr conn) -> net::awaitable { - for (;;) - co_await conn->async_receive(); + boost::system::error_code ec; + while (!ec) + co_await conn->async_receive(ignore, net::redirect_error(net::use_awaitable, ec)); } auto periodic_task(std::shared_ptr conn) -> net::awaitable @@ -56,27 +63,22 @@ auto periodic_task(std::shared_ptr conn) -> net::awaitable } std::cout << "Periodic task done!" << std::endl; + conn->disable_reconnection(); + conn->cancel(redis::operation::run); + conn->cancel(redis::operation::receive); } auto co_main(address const& addr) -> net::awaitable { - auto ex = co_await net::this_coro::executor; - auto conn = std::make_shared(ex); - steady_timer timer{ex}; + auto ex = co_await net::this_coro::executor; + auto conn = std::make_shared(ex); - request req; - req.push("HELLO", 3); - req.push("SUBSCRIBE", "channel"); + connect_config cfg; + cfg.addr = addr; - // The loop will reconnect on connection lost. To exit type Ctrl-C twice. - for (int i = 0; i < 10; ++i) { - co_await ((async_run(*conn, addr) || receiver(conn) || async_check_health(*conn) || periodic_task(conn)) && - conn->async_exec(req)); - - conn->reset_stream(); - timer.expires_after(std::chrono::seconds{1}); - co_await timer.async_wait(); - } + net::co_spawn(ex, receiver(conn), net::detached); + net::co_spawn(ex, periodic_task(conn), net::detached); + redis::experimental::async_connect(*conn, cfg, logger{}, net::consign(net::detached, conn)); } #endif // defined(BOOST_ASIO_HAS_CO_AWAIT) From a6cb4ca323f15ea647634b0319c0c13fb6a88334 Mon Sep 17 00:00:00 2001 From: Marcelo Zimbres Date: Fri, 24 Mar 2023 19:55:15 +0100 Subject: [PATCH 09/32] Adds high-level functionality to connection::async_run. --- CMakeLists.txt | 53 +- README.md | 183 ++----- examples/cpp17_intro.cpp | 34 +- examples/cpp17_intro_sync.cpp | 68 +-- examples/cpp20_chat_room.cpp | 73 ++- examples/cpp20_containers.cpp | 51 +- examples/cpp20_echo_server.cpp | 49 +- examples/cpp20_intro.cpp | 42 +- examples/cpp20_intro_awaitable_ops.cpp | 43 -- examples/cpp20_intro_tls.cpp | 53 +- examples/cpp20_json.cpp | 33 +- examples/cpp20_protobuf.cpp | 32 +- examples/cpp20_resolve_with_sentinel.cpp | 31 +- examples/cpp20_subscriber.cpp | 63 ++- examples/main.cpp | 14 +- examples/start.cpp | 2 +- examples/sync_connection.hpp | 63 +++ include/boost/redis.hpp | 3 + .../boost/redis/adapter/detail/adapters.hpp | 1 - .../redis/adapter/detail/result_traits.hpp | 1 + include/boost/redis/address.hpp | 27 - include/boost/redis/config.hpp | 72 +++ include/boost/redis/connection.hpp | 168 +++--- .../boost/redis/detail/connection_base.hpp | 482 ++++++++++++++++-- include/boost/redis/detail/connection_ops.hpp | 403 --------------- include/boost/redis/detail/connector.hpp | 133 +++++ .../health_checker.hpp} | 94 ++-- include/boost/redis/detail/reconnection.hpp | 135 +++++ include/boost/redis/detail/resolver.hpp | 137 +++++ include/boost/redis/detail/runner.hpp | 295 +++++------ include/boost/redis/error.hpp | 3 + .../boost/redis/experimental/connector.hpp | 313 ------------ include/boost/redis/impl/logger.ipp | 128 +++++ include/boost/redis/logger.hpp | 122 ++++- include/boost/redis/operation.hpp | 14 +- include/boost/redis/request.hpp | 24 +- .../boost/redis/resp3/impl/serialization.ipp | 1 - include/boost/redis/resp3/serialization.hpp | 1 - include/boost/redis/run.hpp | 65 --- include/boost/redis/src.hpp | 1 + include/boost/redis/ssl/connection.hpp | 82 ++- include/boost/redis/ssl/detail/handshaker.hpp | 124 +++++ tests/common.cpp | 29 ++ tests/common.hpp | 17 +- tests/conn_check_health.cpp | 134 ++--- tests/conn_echo_stress.cpp | 17 +- tests/conn_exec.cpp | 61 ++- tests/conn_exec_cancel.cpp | 108 +--- tests/conn_exec_cancel2.cpp | 96 ++++ tests/conn_exec_error.cpp | 72 ++- tests/conn_exec_retry.cpp | 65 ++- tests/conn_push.cpp | 109 ++-- tests/conn_quit.cpp | 48 +- tests/conn_reconnect.cpp | 43 +- tests/conn_run_cancel.cpp | 51 +- tests/conn_tls.cpp | 51 +- tests/cpp20_low_level_async.cpp | 8 +- tests/issue_50.cpp | 58 ++- tests/run.cpp | 76 ++- 59 files changed, 2617 insertions(+), 2142 deletions(-) delete mode 100644 examples/cpp20_intro_awaitable_ops.cpp create mode 100644 examples/sync_connection.hpp delete mode 100644 include/boost/redis/address.hpp create mode 100644 include/boost/redis/config.hpp delete mode 100644 include/boost/redis/detail/connection_ops.hpp create mode 100644 include/boost/redis/detail/connector.hpp rename include/boost/redis/{check_health.hpp => detail/health_checker.hpp} (71%) create mode 100644 include/boost/redis/detail/reconnection.hpp create mode 100644 include/boost/redis/detail/resolver.hpp delete mode 100644 include/boost/redis/experimental/connector.hpp create mode 100644 include/boost/redis/impl/logger.ipp delete mode 100644 include/boost/redis/run.hpp create mode 100644 include/boost/redis/ssl/detail/handshaker.hpp create mode 100644 tests/common.cpp create mode 100644 tests/conn_exec_cancel2.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 5c30a93c..22a30ae5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,6 +58,17 @@ include_directories(include) # Main function for the examples. #======================================================================= +add_library(test_common STATIC + tests/common.cpp +) +target_compile_features(test_common PUBLIC cxx_std_17) +if (MSVC) + target_compile_options(test_common PRIVATE /bigobj) + target_compile_definitions(test_common PRIVATE _WIN32_WINNT=0x0601) +endif() + +#======================================================================= + add_library(common STATIC examples/start.cpp examples/main.cpp @@ -81,15 +92,6 @@ if (MSVC) target_compile_definitions(cpp20_intro PRIVATE _WIN32_WINNT=0x0601) endif() -add_executable(cpp20_intro_awaitable_ops examples/cpp20_intro_awaitable_ops.cpp) -target_link_libraries(cpp20_intro_awaitable_ops common) -target_compile_features(cpp20_intro_awaitable_ops PUBLIC cxx_std_20) -add_test(cpp20_intro_awaitable_ops cpp20_intro_awaitable_ops) -if (MSVC) - target_compile_options(cpp20_intro_awaitable_ops PRIVATE /bigobj) - target_compile_definitions(cpp20_intro_awaitable_ops PRIVATE _WIN32_WINNT=0x0601) -endif() - add_executable(cpp17_intro examples/cpp17_intro.cpp) target_compile_features(cpp17_intro PUBLIC cxx_std_17) add_test(cpp17_intro cpp17_intro) @@ -156,10 +158,12 @@ if (Protobuf_FOUND) endif() endif() -if (NOT MSVC) add_executable(cpp20_subscriber examples/cpp20_subscriber.cpp) target_compile_features(cpp20_subscriber PUBLIC cxx_std_20) target_link_libraries(cpp20_subscriber common) +if (MSVC) + target_compile_options(cpp20_subscriber PRIVATE /bigobj) + target_compile_definitions(cpp20_subscriber PRIVATE _WIN32_WINNT=0x0601) endif() add_executable(cpp20_intro_tls examples/cpp20_intro_tls.cpp) @@ -205,6 +209,7 @@ endif() add_executable(test_conn_exec tests/conn_exec.cpp) target_compile_features(test_conn_exec PUBLIC cxx_std_20) +target_link_libraries(test_conn_exec test_common) add_test(test_conn_exec test_conn_exec) if (MSVC) target_compile_options(test_conn_exec PRIVATE /bigobj) @@ -213,6 +218,7 @@ endif() add_executable(test_conn_exec_retry tests/conn_exec_retry.cpp) target_compile_features(test_conn_exec_retry PUBLIC cxx_std_20) +target_link_libraries(test_conn_exec_retry test_common) add_test(test_conn_exec_retry test_conn_exec_retry) if (MSVC) target_compile_options(test_conn_exec_retry PRIVATE /bigobj) @@ -221,6 +227,7 @@ endif() add_executable(test_conn_push tests/conn_push.cpp) target_compile_features(test_conn_push PUBLIC cxx_std_20) +target_link_libraries(test_conn_push test_common) add_test(test_conn_push test_conn_push) if (MSVC) target_compile_options(test_conn_push PRIVATE /bigobj) @@ -237,7 +244,7 @@ endif() add_executable(test_conn_reconnect tests/conn_reconnect.cpp) target_compile_features(test_conn_reconnect PUBLIC cxx_std_20) -target_link_libraries(test_conn_reconnect common) +target_link_libraries(test_conn_reconnect common test_common) add_test(test_conn_reconnect test_conn_reconnect) if (MSVC) target_compile_options(test_conn_reconnect PRIVATE /bigobj) @@ -271,16 +278,25 @@ endif() add_executable(test_conn_exec_cancel tests/conn_exec_cancel.cpp) target_compile_features(test_conn_exec_cancel PUBLIC cxx_std_20) -target_link_libraries(test_conn_exec_cancel common) +target_link_libraries(test_conn_exec_cancel common test_common) add_test(test_conn_exec_cancel test_conn_exec_cancel) if (MSVC) target_compile_options(test_conn_exec_cancel PRIVATE /bigobj) target_compile_definitions(test_conn_exec_cancel PRIVATE _WIN32_WINNT=0x0601) endif() +add_executable(test_conn_exec_cancel2 tests/conn_exec_cancel2.cpp) +target_compile_features(test_conn_exec_cancel2 PUBLIC cxx_std_20) +target_link_libraries(test_conn_exec_cancel2 common test_common) +add_test(test_conn_exec_cancel2 test_conn_exec_cancel2) +if (MSVC) + target_compile_options(test_conn_exec_cancel2 PRIVATE /bigobj) + target_compile_definitions(test_conn_exec_cancel2 PRIVATE _WIN32_WINNT=0x0601) +endif() + add_executable(test_conn_exec_error tests/conn_exec_error.cpp) target_compile_features(test_conn_exec_error PUBLIC cxx_std_17) -target_link_libraries(test_conn_exec_error common) +target_link_libraries(test_conn_exec_error common test_common) add_test(test_conn_exec_error test_conn_exec_error) if (MSVC) target_compile_options(test_conn_exec_error PRIVATE /bigobj) @@ -289,7 +305,7 @@ endif() add_executable(test_conn_echo_stress tests/conn_echo_stress.cpp) target_compile_features(test_conn_echo_stress PUBLIC cxx_std_20) -target_link_libraries(test_conn_echo_stress common) +target_link_libraries(test_conn_echo_stress common test_common) add_test(test_conn_echo_stress test_conn_echo_stress) if (MSVC) target_compile_options(test_conn_echo_stress PRIVATE /bigobj) @@ -304,22 +320,27 @@ if (MSVC) target_compile_definitions(test_request PRIVATE _WIN32_WINNT=0x0601) endif() -if (NOT MSVC) add_executable(test_issue_50 tests/issue_50.cpp) target_compile_features(test_issue_50 PUBLIC cxx_std_20) target_link_libraries(test_issue_50 common) add_test(test_issue_50 test_issue_50) +if (MSVC) + target_compile_options(test_issue_50 PRIVATE /bigobj) + target_compile_definitions(test_issue_50 PRIVATE _WIN32_WINNT=0x0601) endif() -if (NOT MSVC) add_executable(test_conn_check_health tests/conn_check_health.cpp) target_compile_features(test_conn_check_health PUBLIC cxx_std_17) target_link_libraries(test_conn_check_health common) add_test(test_conn_check_health test_conn_check_health) +if (MSVC) + target_compile_options(test_conn_check_health PRIVATE /bigobj) + target_compile_definitions(test_conn_check_health PRIVATE _WIN32_WINNT=0x0601) endif() add_executable(test_run tests/run.cpp) target_compile_features(test_run PUBLIC cxx_std_17) +target_link_libraries(test_run test_common) add_test(test_run test_run) if (MSVC) target_compile_options(test_run PRIVATE /bigobj) diff --git a/README.md b/README.md index 0dfb7dc6..58ca1daa 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ Boost.Redis is a [Redis](https://redis.io/) client library built on top of [Boost.Asio](https://www.boost.org/doc/libs/release/doc/html/boost_asio.html) -that implements -[RESP3](https://github.com/redis/redis-specifications/blob/master/protocol/RESP3.md), -a plain text protocol which can multiplex any number of client +that implements Redis plain text protocol +[RESP3](https://github.com/redis/redis-specifications/blob/master/protocol/RESP3.md). +It can multiplex any number of client requests, responses, and server pushes onto a single active socket connection to the Redis server. The library hides low-level code away from the user, which, in the majority of the cases will be concerned @@ -17,7 +17,7 @@ with only three library entities STL containers and user defined data types. * `boost::redis::response`: Container of Redis responses. -In the next sections we will cover all those points in detail with +In the next sections we will cover all these points in detail with examples. The requirements for using Boost.Redis are * Boost 1.81 or greater. @@ -40,7 +40,7 @@ examples and tests cmake is supported, for example ```cpp # Linux -$ BOOST_ROOT=/opt/boost_1_81_0 cmake --preset dev +$ BOOST_ROOT=/opt/boost_1_81_0 cmake --preset g++-11 # Windows $ cmake -G "Visual Studio 17 2022" -A x64 -B bin64 -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake @@ -65,107 +65,34 @@ connection to send a [ping](https://redis.io/commands/ping/) command to Redis ```cpp -auto run(std::shared_ptr conn, std::string host, std::string port) -> net::awaitable +auto co_main(config const& cfg) -> net::awaitable { - // From examples/common.hpp to avoid vebosity - co_await connect(conn, host, port); + auto conn = std::make_shared(co_await net::this_coro::executor); + conn->async_run(cfg, {}, net::consign(net::detached, conn)); - // async_run coordinates read and write operations. - co_await conn->async_run(); - - // Cancel pending operations, if any. - conn->cancel(operation::exec); - conn->cancel(operation::receive); -} - -auto co_main(std::string host, std::string port) -> net::awaitable -{ - auto ex = co_await net::this_coro::executor; - auto conn = std::make_shared(ex); - net::co_spawn(ex, run(conn, host, port), net::detached); - - // A request can contain multiple commands. + // A request containing only a ping command. request req; - req.push("HELLO", 3); req.push("PING", "Hello world"); - req.push("QUIT"); - // Stores responses of each individual command. The responses to - // HELLO and QUIT are being ignored for simplicity. - response resp; + // Response where the PONG response will be stored. + response resp; // Executes the request. co_await conn->async_exec(req, resp); + conn->cancel(); - std::cout << "PING: " << std::get<1>(resp).value() << std::endl; + std::cout << "PING: " << std::get<0>(resp).value() << std::endl; } ``` + The roles played by the `async_run` and `async_exec` functions are * `connection::async_exec`: Execute the commands contained in the request and store the individual responses in the `resp` object. Can be called from multiple places in your code concurrently. -* `connection::async_run`: Coordinate low-level read and write - operations. More specifically, it will hand IO control to - `async_exec` when a response arrives and to `async_receive` when a - server-push is received. It is also responsible for triggering - writes of pending requests. - -Depending on the user's requirements, there are different styles of -calling `async_run`. For example, in a short-lived connection where -there is only one active client communicating with the server, the -easiest way to call `async_run` is to only run it simultaneously with -the `async_exec` call, this is exemplified in -cpp20_intro_awaitable_ops.cpp. If there are many in-process clients -performing simultaneous requests, an alternative is to launch a -long-running coroutine which calls `async_run` detached from other -operations as shown in the example above, cpp20_intro.cpp and -cpp20_echo_server.cpp. The list of examples below will help users -comparing different ways of implementing the ping example shown above - -* cpp20_intro_awaitable_ops.cpp: Uses awaitable operators. -* cpp20_intro.cpp: Calls `async_run` detached from other operations. -* cpp20_intro_tls.cpp: Communicates over TLS. -* cpp17_intro.cpp: Uses callbacks and requires C++17. -* cpp17_intro_sync.cpp: Runs `async_run` in a separate thread and - performs synchronous calls to `async_exec`. - -While calling `async_run` is a sufficient condition for maintaining -active two-way communication with the Redis server, most production -deployments will want to do more. For example, they may want to -reconnect if the connection goes down, either to the same server or a -failover server. They may want to perform health checks and more. The -example below shows for example how to use a loop to keep reconnecting -to the same address when a disconnection occurs (see -cpp20_subscriber.cpp) - -```cpp -auto run(std::shared_ptr conn) -> net::awaitable -{ - steady_timer timer{co_await net::this_coro::executor}; - - for (;;) { - co_await connect(conn, "127.0.0.1", "6379"); - co_await (conn->async_run() || health_check(conn) || receiver(conn)); - - // Prepare the stream for a new connection. - conn->reset_stream(); - - // Waits one second before trying to reconnect. - timer.expires_after(std::chrono::seconds{1}); - co_await timer.async_wait(); - } -} -``` - -The ability to reconnect the same connection object results in -considerable simplification of backend code and makes it easier to -write failover-safe applications. For example, a Websocket server -might have a 10k sessions communicating with Redis at the time the -connection is lost (or maybe killed by the server admin to force a -failover). It would be concerning if each individual section were to -throw exceptions and handle error. With the pattern shown above the -only place that has to manage the error is the run function. +* `connection::async_run`: Resolve, connect, ssl-handshake, + resp3-handshake, health-checks reconnection and coordinate low-level + read and write operations.. ### Server pushes @@ -181,49 +108,35 @@ The connection class supports server pushes by means of the to used it ```cpp -auto receiver(std::shared_ptr conn) -> net::awaitable +auto +receiver(std::shared_ptr conn) -> net::awaitable { - for (generic_response resp;;) { - co_await conn->async_receive(resp); - // Use resp and clear the response for a new push. - resp.clear(); + request req; + req.push("SUBSCRIBE", "channel"); + + while (!conn->is_cancelled()) { + + // Reconnect to channels. + co_await conn->async_exec(req); + + // Loop reading Redis pushs messages. + for (generic_response resp;;) { + error_code ec; + co_await conn->async_receive(resp, net::redirect_error(net::use_awaitable, ec)); + if (ec) + break; // Connection lost, break so we can reconnect to channels. + std::cout + << resp.value().at(1).value + << " " << resp.value().at(2).value + << " " << resp.value().at(3).value + << std::endl; + resp.value().clear(); + } } } -``` - -### Cancellation - -Boost.Redis supports both implicit and explicit cancellation of connection -operations. Explicit cancellation is supported by means of the -`boost::redis::connection::cancel` member function. Implicit -terminal-cancellation, like those that happen when using Asio -awaitable `operator ||` will be discussed with more detail below. - -```cpp -co_await (conn.async_run(...) || conn.async_exec(...)) -``` -* Useful for short-lived connections that are meant to be closed after - a command has been executed. - -```cpp -co_await (conn.async_exec(...) || time.async_wait(...)) -``` - -* Provides a way to limit how long the execution of a single request - should last. -* WARNING: If the timer fires after the request has been sent but before the - response has been received, the connection will be closed. -* It is usually a better idea to have a healthy checker than adding - per request timeout, see cpp20_subscriber.cpp for an example. - -```cpp -co_await (conn.async_exec(...) || conn.async_exec(...) || ... || conn.async_exec(...)) ``` -* This works but is unnecessary, the connection will automatically - merge the individual requests into a single payload. - ## Requests @@ -535,7 +448,6 @@ to serialize using json and [protobuf](https://protobuf.dev/). The examples below show how to use the features discussed so far -* cpp20_intro_awaitable_ops.cpp: The version shown above. * cpp20_intro.cpp: Does not use awaitable operators. * cpp20_intro_tls.cpp: Communicates over TLS. * cpp20_containers.cpp: Shows how to send and receive STL containers and how to use transactions. @@ -793,7 +705,7 @@ https://lists.boost.org/Archives/boost/2023/01/253944.php. * Renames the project to Boost.Redis and moves the code into namespace `boost::redis`. -* As pointed out in the reviews the `to_buld` and `from_buld` names were too +* As pointed out in the reviews the `to_bulk` and `from_bulk` names were too generic for ADL customization points. They gained the prefix `boost_redis_`. * Moves `boost::redis::resp3::request` to `boost::redis::request`. @@ -820,19 +732,16 @@ https://lists.boost.org/Archives/boost/2023/01/253944.php. became unnecessary and was removed. I could measure significative performance gains with theses changes. -* Adds native json support for Boost.Describe'd classes. To use it include - `` and decribe you class as of Boost.Describe, see - cpp20_json_serialization.cpp for more details. +* Improves serialization examples using Boost.Describe to serialize to JSON and protobuf. See + cpp20_json.cpp and cpp20_protobuf.cpp for more details. * Upgrades to Boost 1.81.0. * Fixes build with libc++. -* Adds a function that performs health checks, see - `boost::redis::experimental::async_check_health`. - -* Adds non-member `async_run` function that resolves, connects and - calls member `async_run` on a connection object. +* Adds high-level functionality to the connection classes. For + example, `boost::redis::connection::async_run` will automatically + resolve, connect, reconnect and perform health checks. ### v1.4.0-1 diff --git a/examples/cpp17_intro.cpp b/examples/cpp17_intro.cpp index f482d306..c6cbd6c8 100644 --- a/examples/cpp17_intro.cpp +++ b/examples/cpp17_intro.cpp @@ -4,59 +4,49 @@ * accompanying file LICENSE.txt) */ +#include +#include #include -#include -#include -#include + #include namespace net = boost::asio; using boost::redis::connection; using boost::redis::request; using boost::redis::response; -using boost::redis::ignore_t; -using boost::redis::async_run; -using boost::redis::logger; -using boost::redis::address; -using namespace std::chrono_literals; +using boost::redis::config; auto main(int argc, char * argv[]) -> int { try { - address addr; + config cfg; if (argc == 3) { - addr.host = argv[1]; - addr.port = argv[2]; + cfg.addr.host = argv[1]; + cfg.addr.port = argv[2]; } - // The request request req; - req.push("HELLO", 3); req.push("PING", "Hello world"); - // The response. - response resp; + response resp; net::io_context ioc; connection conn{ioc}; - async_run(conn, addr, 10s, 10s, logger{}, [&](auto){ - conn.cancel(); - }); + conn.async_run(cfg, {}, net::detached); - conn.async_exec(req, resp, [&](auto ec, auto){ + conn.async_exec(req, resp, [&](auto ec, auto) { if (!ec) - std::cout << "PING: " << std::get<1>(resp).value() << std::endl; + std::cout << "PING: " << std::get<0>(resp).value() << std::endl; conn.cancel(); }); ioc.run(); - return 0; } catch (std::exception const& e) { std::cerr << "Error: " << e.what() << std::endl; + return 1; } - return 1; } diff --git a/examples/cpp17_intro_sync.cpp b/examples/cpp17_intro_sync.cpp index 20aff7ef..1969c3ce 100644 --- a/examples/cpp17_intro_sync.cpp +++ b/examples/cpp17_intro_sync.cpp @@ -4,84 +4,42 @@ * accompanying file LICENSE.txt) */ -#include -#include -#include -#include -#include -#include -#include -#include +#include "sync_connection.hpp" + #include -#include -#include #include // Include this in no more than one .cpp file. #include -namespace net = boost::asio; -using connection = boost::redis::connection; -using boost::redis::operation; +using boost::redis::sync_connection; using boost::redis::request; using boost::redis::response; -using boost::redis::ignore_t; -using boost::redis::async_run; -using boost::redis::address; -using boost::redis::logger; -using boost::redis::async_check_health; -using namespace std::chrono_literals; - -template -auto exec(std::shared_ptr conn, request const& req, Response& resp) -{ - net::dispatch( - conn->get_executor(), - net::deferred([&]() { return conn->async_exec(req, resp, net::deferred); })) - (net::use_future).get(); -} +using boost::redis::config; auto main(int argc, char * argv[]) -> int { try { - address addr; + config cfg; if (argc == 3) { - addr.host = argv[1]; - addr.port = argv[2]; + cfg.addr.host = argv[1]; + cfg.addr.port = argv[2]; } - net::io_context ioc{1}; - - auto conn = std::make_shared(ioc); - - // Starts a thread that will can io_context::run on which the - // connection will run. - std::thread t{[&ioc, conn, addr]() { - async_run(*conn, addr, 10s, 10s, logger{}, [conn](auto){ - conn->cancel(); - }); - - async_check_health(*conn, "Boost.Redis", 2s, [conn](auto) { - conn->cancel(); - }); - - ioc.run(); - }}; + sync_connection conn; + conn.run(cfg); request req; - req.push("HELLO", 3); req.push("PING"); - req.push("QUIT"); - response resp; + response resp; - // Executes commands synchronously. - exec(conn, req, resp); + conn.exec(req, resp); + conn.stop(); - std::cout << "Response: " << std::get<1>(resp).value() << std::endl; + std::cout << "Response: " << std::get<0>(resp).value() << std::endl; - t.join(); } catch (std::exception const& e) { std::cerr << e.what() << std::endl; } diff --git a/examples/cpp20_chat_room.cpp b/examples/cpp20_chat_room.cpp index c8f77f8e..57df7901 100644 --- a/examples/cpp20_chat_room.cpp +++ b/examples/cpp20_chat_room.cpp @@ -4,39 +4,58 @@ * accompanying file LICENSE.txt) */ -#include -#include -#include +#include +#include #include +#include +#include +#include #include #include #include #if defined(BOOST_ASIO_HAS_CO_AWAIT) -#include #if defined(BOOST_ASIO_HAS_POSIX_STREAM_DESCRIPTOR) namespace net = boost::asio; -using namespace net::experimental::awaitable_operators; -using stream_descriptor = net::use_awaitable_t<>::as_default_on_t; -using signal_set = net::use_awaitable_t<>::as_default_on_t; +using stream_descriptor = net::deferred_t::as_default_on_t; +using connection = net::deferred_t::as_default_on_t; +using signal_set = net::deferred_t::as_default_on_t; using boost::redis::request; using boost::redis::generic_response; -using boost::redis::async_check_health; -using boost::redis::async_run; -using boost::redis::address; -using connection = net::use_awaitable_t<>::as_default_on_t; +using boost::redis::config; +using net::redirect_error; +using net::use_awaitable; +using boost::system::error_code; +using namespace std::chrono_literals; // Chat over Redis pubsub. To test, run this program from multiple // terminals and type messages to stdin. -// Receives Redis pushes. -auto receiver(std::shared_ptr conn) -> net::awaitable +auto +receiver(std::shared_ptr conn) -> net::awaitable { - for (generic_response resp;;) { - co_await conn->async_receive(resp); - std::cout << resp.value().at(1).value << " " << resp.value().at(2).value << " " << resp.value().at(3).value << std::endl; - resp.value().clear(); + request req; + req.push("SUBSCRIBE", "channel"); + + while (!conn->is_cancelled()) { + + // Subscribe to channels. + co_await conn->async_exec(req); + + // Loop reading Redis push messages. + for (generic_response resp;;) { + error_code ec; + co_await conn->async_receive(resp, redirect_error(use_awaitable, ec)); + if (ec) + break; // Connection lost, break so we can reconnect to channels. + std::cout + << resp.value().at(1).value + << " " << resp.value().at(2).value + << " " << resp.value().at(3).value + << std::endl; + resp.value().clear(); + } } } @@ -46,31 +65,31 @@ auto publisher(std::shared_ptr in, std::shared_ptrasync_exec(req); msg.erase(0, n); } } // Called from the main function (see main.cpp) -auto co_main(address const& addr) -> net::awaitable +auto co_main(config const& cfg) -> net::awaitable { auto ex = co_await net::this_coro::executor; auto conn = std::make_shared(ex); auto stream = std::make_shared(ex, ::dup(STDIN_FILENO)); - signal_set sig{ex, SIGINT, SIGTERM}; - request req; - req.push("HELLO", 3); - req.push("SUBSCRIBE", "chat-channel"); + net::co_spawn(ex, receiver(conn), net::detached); + net::co_spawn(ex, publisher(stream, conn), net::detached); + conn->async_run(cfg, {}, net::consign(net::detached, conn)); - co_await ((async_run(*conn, addr) || publisher(stream, conn) || receiver(conn) || - async_check_health(*conn) || sig.async_wait()) && - conn->async_exec(req)); + signal_set sig_set{ex, SIGINT, SIGTERM}; + co_await sig_set.async_wait(); + conn->cancel(); + stream->cancel(); } #else // defined(BOOST_ASIO_HAS_POSIX_STREAM_DESCRIPTOR) -auto co_main(address const&) -> net::awaitable +auto co_main(config const&) -> net::awaitable { std::cout << "Requires support for posix streams." << std::endl; co_return; diff --git a/examples/cpp20_containers.cpp b/examples/cpp20_containers.cpp index 63785a50..277a8575 100644 --- a/examples/cpp20_containers.cpp +++ b/examples/cpp20_containers.cpp @@ -4,8 +4,8 @@ * accompanying file LICENSE.txt) */ -#include -#include +#include +#include #include #include #include @@ -16,12 +16,12 @@ namespace net = boost::asio; namespace redis = boost::redis; -using boost::redis::request; -using boost::redis::response; -using boost::redis::ignore_t; -using boost::redis::async_run; -using boost::redis::address; -using connection = boost::asio::use_awaitable_t<>::as_default_on_t; +using redis::request; +using redis::response; +using redis::ignore_t; +using redis::ignore; +using redis::config; +using connection = net::deferred_t::as_default_on_t; void print(std::map const& cont) { @@ -35,11 +35,6 @@ void print(std::vector const& cont) std::cout << "\n"; } -auto run(std::shared_ptr conn, address const& addr) -> net::awaitable -{ - co_await async_run(*conn, addr); -} - // Stores the content of some STL containers in Redis. auto store(std::shared_ptr conn) -> net::awaitable { @@ -50,7 +45,6 @@ auto store(std::shared_ptr conn) -> net::awaitable {{"key1", "value1"}, {"key2", "value2"}, {"key3", "value3"}}; request req; - req.push("HELLO", 3); req.push_range("RPUSH", "rpush-key", vec); req.push_range("HSET", "hset-key", map); @@ -61,30 +55,27 @@ auto hgetall(std::shared_ptr conn) -> net::awaitable { // A request contains multiple commands. request req; - req.push("HELLO", 3); req.push("HGETALL", "hset-key"); // Responses as tuple elements. - response> resp; + response> resp; // Executes the request and reads the response. co_await conn->async_exec(req, resp); - print(std::get<1>(resp).value()); + print(std::get<0>(resp).value()); } // Retrieves in a transaction. auto transaction(std::shared_ptr conn) -> net::awaitable { request req; - req.push("HELLO", 3); req.push("MULTI"); req.push("LRANGE", "rpush-key", 0, -1); // Retrieves req.push("HGETALL", "hset-key"); // Retrieves req.push("EXEC"); response< - ignore_t, // hello ignore_t, // multi ignore_t, // lrange ignore_t, // hgetall @@ -93,28 +84,20 @@ auto transaction(std::shared_ptr conn) -> net::awaitable co_await conn->async_exec(req, resp); - print(std::get<0>(std::get<4>(resp).value()).value().value()); - print(std::get<1>(std::get<4>(resp).value()).value().value()); -} - -auto quit(std::shared_ptr conn) -> net::awaitable -{ - request req; - req.push("QUIT"); - - co_await conn->async_exec(req); + print(std::get<0>(std::get<3>(resp).value()).value().value()); + print(std::get<1>(std::get<3>(resp).value()).value().value()); } // Called from the main function (see main.cpp) -net::awaitable co_main(address const& addr) +net::awaitable co_main(config const& cfg) { - auto ex = co_await net::this_coro::executor; - auto conn = std::make_shared(ex); - net::co_spawn(ex, run(conn, addr), net::detached); + auto conn = std::make_shared(co_await net::this_coro::executor); + conn->async_run(cfg, {}, net::consign(net::detached, conn)); + co_await store(conn); co_await transaction(conn); co_await hgetall(conn); - co_await quit(conn); + conn->cancel(); } #endif // defined(BOOST_ASIO_HAS_CO_AWAIT) diff --git a/examples/cpp20_echo_server.cpp b/examples/cpp20_echo_server.cpp index 8d5db7af..44b7b657 100644 --- a/examples/cpp20_echo_server.cpp +++ b/examples/cpp20_echo_server.cpp @@ -4,26 +4,26 @@ * accompanying file LICENSE.txt) */ -#include -#include -#include +#include +#include #include #include +#include +#include +#include #if defined(BOOST_ASIO_HAS_CO_AWAIT) -#include namespace net = boost::asio; -using namespace net::experimental::awaitable_operators; -using tcp_socket = net::use_awaitable_t<>::as_default_on_t; -using tcp_acceptor = net::use_awaitable_t<>::as_default_on_t; -using signal_set = net::use_awaitable_t<>::as_default_on_t; +using tcp_socket = net::deferred_t::as_default_on_t; +using tcp_acceptor = net::deferred_t::as_default_on_t; +using signal_set = net::deferred_t::as_default_on_t; +using connection = net::deferred_t::as_default_on_t; using boost::redis::request; using boost::redis::response; -using boost::redis::async_check_health; -using boost::redis::async_run; -using boost::redis::address; -using connection = net::use_awaitable_t<>::as_default_on_t; +using boost::redis::config; +using boost::system::error_code; +using namespace std::chrono_literals; auto echo_server_session(tcp_socket socket, std::shared_ptr conn) -> net::awaitable { @@ -44,24 +44,27 @@ auto echo_server_session(tcp_socket socket, std::shared_ptr conn) -> // Listens for tcp connections. auto listener(std::shared_ptr conn) -> net::awaitable { - auto ex = co_await net::this_coro::executor; - tcp_acceptor acc(ex, {net::ip::tcp::v4(), 55555}); - for (;;) - net::co_spawn(ex, echo_server_session(co_await acc.async_accept(), conn), net::detached); + try { + auto ex = co_await net::this_coro::executor; + tcp_acceptor acc(ex, {net::ip::tcp::v4(), 55555}); + for (;;) + net::co_spawn(ex, echo_server_session(co_await acc.async_accept(), conn), net::detached); + } catch (std::exception const& e) { + std::clog << "Listener: " << e.what() << std::endl; + } } // Called from the main function (see main.cpp) -auto co_main(address const& addr) -> net::awaitable +auto co_main(config const& cfg) -> net::awaitable { auto ex = co_await net::this_coro::executor; auto conn = std::make_shared(ex); - signal_set sig{ex, SIGINT, SIGTERM}; - - request req; - req.push("HELLO", 3); + net::co_spawn(ex, listener(conn), net::detached); + conn->async_run(cfg, {}, net::consign(net::detached, conn)); - co_await ((async_run(*conn, addr) || listener(conn) || async_check_health(*conn) || - sig.async_wait()) && conn->async_exec(req)); + signal_set sig_set(ex, SIGINT, SIGTERM); + co_await sig_set.async_wait(); + conn->cancel(); } #endif // defined(BOOST_ASIO_HAS_CO_AWAIT) diff --git a/examples/cpp20_intro.cpp b/examples/cpp20_intro.cpp index 79ae4b6e..a67db286 100644 --- a/examples/cpp20_intro.cpp +++ b/examples/cpp20_intro.cpp @@ -4,10 +4,11 @@ * accompanying file LICENSE.txt) */ -#include -#include +#include +#include #include #include +#include #include #if defined(BOOST_ASIO_HAS_CO_AWAIT) @@ -15,41 +16,28 @@ namespace net = boost::asio; using boost::redis::request; using boost::redis::response; -using boost::redis::ignore_t; -using boost::redis::async_run; -using boost::redis::address; -using connection = net::use_awaitable_t<>::as_default_on_t; - -auto run(std::shared_ptr conn, address addr) -> net::awaitable -{ - // async_run coordinate read and write operations. - co_await async_run(*conn, addr); - - // Cancel pending operations, if any. - conn->cancel(); -} +using boost::redis::config; +using boost::redis::logger; +using connection = net::deferred_t::as_default_on_t; // Called from the main function (see main.cpp) -auto co_main(address const& addr) -> net::awaitable +auto co_main(config const& cfg) -> net::awaitable { - auto ex = co_await net::this_coro::executor; - auto conn = std::make_shared(ex); - net::co_spawn(ex, run(conn, addr), net::detached); + auto conn = std::make_shared(co_await net::this_coro::executor); + conn->async_run(cfg, {}, net::consign(net::detached, conn)); - // A request can contain multiple commands. + // A request containing only a ping command. request req; - req.push("HELLO", 3); req.push("PING", "Hello world"); - req.push("QUIT"); - // Stores responses of each individual command. The responses to - // HELLO and QUIT are being ignored for simplicity. - response resp; + // Response where the PONG response will be stored. + response resp; - // Executtes the request. + // Executes the request. co_await conn->async_exec(req, resp); + conn->cancel(); - std::cout << "PING: " << std::get<1>(resp).value() << std::endl; + std::cout << "PING: " << std::get<0>(resp).value() << std::endl; } #endif // defined(BOOST_ASIO_HAS_CO_AWAIT) diff --git a/examples/cpp20_intro_awaitable_ops.cpp b/examples/cpp20_intro_awaitable_ops.cpp deleted file mode 100644 index a58b8552..00000000 --- a/examples/cpp20_intro_awaitable_ops.cpp +++ /dev/null @@ -1,43 +0,0 @@ -/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com) - * - * Distributed under the Boost Software License, Version 1.0. (See - * accompanying file LICENSE.txt) - */ - -#include -#include -#include - -#if defined(BOOST_ASIO_HAS_CO_AWAIT) -#include - -namespace net = boost::asio; -using namespace net::experimental::awaitable_operators; -using boost::redis::request; -using boost::redis::response; -using boost::redis::ignore_t; -using boost::redis::async_run; -using boost::redis::address; -using connection = boost::asio::use_awaitable_t<>::as_default_on_t; - -// Called from the main function (see main.cpp) -auto co_main(address const& addr) -> net::awaitable -{ - try { - request req; - req.push("HELLO", 3); - req.push("PING", "Hello world"); - req.push("QUIT"); - - response resp; - - connection conn{co_await net::this_coro::executor}; - co_await (async_run(conn, addr) || conn.async_exec(req, resp)); - - std::cout << "PING: " << std::get<1>(resp).value() << std::endl; - } catch (std::exception const& e) { - std::cout << e.what() << std::endl; - } -} - -#endif // defined(BOOST_ASIO_HAS_CO_AWAIT) diff --git a/examples/cpp20_intro_tls.cpp b/examples/cpp20_intro_tls.cpp index 2acc07ed..00c1c9a4 100644 --- a/examples/cpp20_intro_tls.cpp +++ b/examples/cpp20_intro_tls.cpp @@ -5,27 +5,20 @@ */ #include -#include -#include +#include #include -#include -#include -#include -#include +#include +#include #include #if defined(BOOST_ASIO_HAS_CO_AWAIT) -#include namespace net = boost::asio; -namespace redis = boost::redis; -using namespace net::experimental::awaitable_operators; -using resolver = net::use_awaitable_t<>::as_default_on_t; -using connection = net::use_awaitable_t<>::as_default_on_t; using boost::redis::request; using boost::redis::response; -using boost::redis::ignore_t; -using boost::redis::address; +using boost::redis::config; +using boost::redis::logger; +using connection = net::deferred_t::as_default_on_t; auto verify_certificate(bool, net::ssl::verify_context&) -> bool { @@ -33,30 +26,30 @@ auto verify_certificate(bool, net::ssl::verify_context&) -> bool return true; } -net::awaitable co_main(address const&) +auto co_main(config const&) -> net::awaitable { + config cfg; + cfg.username = "aedis"; + cfg.password = "aedis"; + cfg.addr.host = "db.occase.de"; + cfg.addr.port = "6380"; + + net::ssl::context ctx{net::ssl::context::sslv23}; + auto conn = std::make_shared(co_await net::this_coro::executor, ctx); + conn->async_run(cfg, {}, net::consign(net::detached, conn)); + request req; - req.push("HELLO", 3, "AUTH", "aedis", "aedis"); req.push("PING"); - req.push("QUIT"); - - response resp; - // Resolve - auto ex = co_await net::this_coro::executor; - resolver resv{ex}; - auto const endpoints = co_await resv.async_resolve("db.occase.de", "6380"); + response resp; - net::ssl::context ctx{net::ssl::context::sslv23}; - connection conn{ex, ctx}; - conn.next_layer().set_verify_mode(net::ssl::verify_peer); - conn.next_layer().set_verify_callback(verify_certificate); + conn->next_layer().set_verify_mode(net::ssl::verify_peer); + conn->next_layer().set_verify_callback(verify_certificate); - co_await net::async_connect(conn.lowest_layer(), endpoints); - co_await conn.next_layer().async_handshake(net::ssl::stream_base::client); - co_await (conn.async_run() || conn.async_exec(req, resp)); + co_await conn->async_exec(req, resp); + conn->cancel(); - std::cout << "Response: " << std::get<1>(resp).value() << std::endl; + std::cout << "Response: " << std::get<0>(resp).value() << std::endl; } #endif // defined(BOOST_ASIO_HAS_CO_AWAIT) diff --git a/examples/cpp20_json.cpp b/examples/cpp20_json.cpp index 0427b03a..5b204b2f 100644 --- a/examples/cpp20_json.cpp +++ b/examples/cpp20_json.cpp @@ -4,31 +4,29 @@ * accompanying file LICENSE.txt) */ -#include -#include +#include +#include #include #include #include +#include #include #include #if defined(BOOST_ASIO_HAS_CO_AWAIT) -// Include this in no more than one .cpp file. #define BOOST_JSON_NO_LIB #define BOOST_CONTAINER_NO_LIB #include "json.hpp" #include namespace net = boost::asio; -namespace redis = boost::redis; using namespace boost::describe; +using connection = net::deferred_t::as_default_on_t; using boost::redis::request; using boost::redis::response; using boost::redis::operation; using boost::redis::ignore_t; -using boost::redis::async_run; -using boost::redis::address; -using connection = boost::asio::use_awaitable_t<>::as_default_on_t; +using boost::redis::config; // Struct that will be stored in Redis using json serialization. struct user { @@ -47,37 +45,30 @@ void boost_redis_to_bulk(std::string& to, user const& u) void boost_redis_from_bulk(user& u, std::string_view sv, boost::system::error_code& ec) { boost::redis::json::from_bulk(u, sv, ec); } -auto run(std::shared_ptr conn, address addr) -> net::awaitable -{ - co_await async_run(*conn, addr); -} - -net::awaitable co_main(address const& addr) +net::awaitable co_main(config const& cfg) { auto ex = co_await net::this_coro::executor; auto conn = std::make_shared(ex); - net::co_spawn(ex, run(conn, addr), net::detached); + conn->async_run(cfg, {}, net::consign(net::detached, conn)); // user object that will be stored in Redis in json format. user const u{"Joao", "58", "Brazil"}; // Stores and retrieves in the same request. request req; - req.push("HELLO", 3); req.push("SET", "json-key", u); // Stores in Redis. req.push("GET", "json-key"); // Retrieves from Redis. - response resp; + response resp; co_await conn->async_exec(req, resp); + conn->cancel(); // Prints the first ping std::cout - << "Name: " << std::get<2>(resp).value().name << "\n" - << "Age: " << std::get<2>(resp).value().age << "\n" - << "Country: " << std::get<2>(resp).value().country << "\n"; - - conn->cancel(operation::run); + << "Name: " << std::get<1>(resp).value().name << "\n" + << "Age: " << std::get<1>(resp).value().age << "\n" + << "Country: " << std::get<1>(resp).value().country << "\n"; } #endif // defined(BOOST_ASIO_HAS_CO_AWAIT) diff --git a/examples/cpp20_protobuf.cpp b/examples/cpp20_protobuf.cpp index a3e9cbc3..200bfe1d 100644 --- a/examples/cpp20_protobuf.cpp +++ b/examples/cpp20_protobuf.cpp @@ -4,10 +4,11 @@ * accompanying file LICENSE.txt) */ -#include -#include +#include +#include #include #include +#include #include #include "protobuf.hpp" @@ -18,14 +19,12 @@ #if defined(BOOST_ASIO_HAS_CO_AWAIT) namespace net = boost::asio; -namespace redis = boost::redis; using boost::redis::request; using boost::redis::response; using boost::redis::operation; using boost::redis::ignore_t; -using boost::redis::async_run; -using boost::redis::address; -using connection = net::use_awaitable_t<>::as_default_on_t; +using boost::redis::config; +using connection = net::deferred_t::as_default_on_t; // The protobuf type described in examples/person.proto using tutorial::person; @@ -45,16 +44,11 @@ void boost_redis_from_bulk(person& u, std::string_view sv, boost::system::error_ using tutorial::boost_redis_to_bulk; using tutorial::boost_redis_from_bulk; -auto run(std::shared_ptr conn, address const& addr) -> net::awaitable -{ - co_await async_run(*conn, addr); -} - -net::awaitable co_main(address const& addr) +net::awaitable co_main(config const& cfg) { auto ex = co_await net::this_coro::executor; auto conn = std::make_shared(ex); - net::co_spawn(ex, run(conn, addr), net::detached); + conn->async_run(cfg, {}, net::consign(net::detached, conn)); person p; p.set_name("Louis"); @@ -62,21 +56,19 @@ net::awaitable co_main(address const& addr) p.set_email("No email yet."); request req; - req.push("HELLO", 3); req.push("SET", "protobuf-key", p); req.push("GET", "protobuf-key"); - response resp; + response resp; // Sends the request and receives the response. co_await conn->async_exec(req, resp); + conn->cancel(); std::cout - << "Name: " << std::get<2>(resp).value().name() << "\n" - << "Age: " << std::get<2>(resp).value().id() << "\n" - << "Email: " << std::get<2>(resp).value().email() << "\n"; - - conn->cancel(operation::run); + << "Name: " << std::get<1>(resp).value().name() << "\n" + << "Age: " << std::get<1>(resp).value().id() << "\n" + << "Email: " << std::get<1>(resp).value().email() << "\n"; } #endif // defined(BOOST_ASIO_HAS_CO_AWAIT) diff --git a/examples/cpp20_resolve_with_sentinel.cpp b/examples/cpp20_resolve_with_sentinel.cpp index aeb33c2f..1dbb01f3 100644 --- a/examples/cpp20_resolve_with_sentinel.cpp +++ b/examples/cpp20_resolve_with_sentinel.cpp @@ -4,22 +4,22 @@ * accompanying file LICENSE.txt) */ -#include +#include #include #include +#include #include #if defined(BOOST_ASIO_HAS_CO_AWAIT) -#include namespace net = boost::asio; -using namespace net::experimental::awaitable_operators; +namespace redis = boost::redis; using endpoints = net::ip::tcp::resolver::results_type; -using boost::redis::request; -using boost::redis::response; -using boost::redis::ignore_t; -using boost::redis::async_run; -using boost::redis::address; +using redis::request; +using redis::response; +using redis::ignore_t; +using redis::config; +using redis::address; using connection = boost::asio::use_awaitable_t<>::as_default_on_t; auto redir(boost::system::error_code& ec) @@ -39,23 +39,30 @@ auto resolve_master_address(std::vector
const& addresses) -> net::await response>, ignore_t> resp; for (auto addr : addresses) { boost::system::error_code ec; - co_await (async_run(*conn, addr) && conn->async_exec(req, resp, redir(ec))); + config cfg; + cfg.addr = addr; + // TODO: async_run and async_exec should be lauched in + // parallel here so we can wait for async_run completion + // before eventually calling it again. + conn->async_run(cfg, {}, net::consign(net::detached, conn)); + co_await conn->async_exec(req, resp, redir(ec)); + conn->cancel(); conn->reset_stream(); - if (std::get<0>(resp)) + if (!ec && std::get<0>(resp)) co_return address{std::get<0>(resp).value().value().at(0), std::get<0>(resp).value().value().at(1)}; } co_return address{}; } -auto co_main(address const& addr) -> net::awaitable +auto co_main(config const& cfg) -> net::awaitable { // A list of sentinel addresses from which only one is responsive. // This simulates sentinels that are down. std::vector
const addresses { address{"foo", "26379"} , address{"bar", "26379"} - , addr + , cfg.addr }; auto const ep = co_await resolve_master_address(addresses); diff --git a/examples/cpp20_subscriber.cpp b/examples/cpp20_subscriber.cpp index 6085af42..717b46fb 100644 --- a/examples/cpp20_subscriber.cpp +++ b/examples/cpp20_subscriber.cpp @@ -4,25 +4,29 @@ * accompanying file LICENSE.txt) */ -#include +#include #include #include #include +#include #include #include #include +#include +#include #include #if defined(BOOST_ASIO_HAS_CO_AWAIT) namespace net = boost::asio; -namespace redis = boost::redis; -using redis::generic_response; -using redis::address; -using redis::logger; -using redis::experimental::async_connect; -using redis::experimental::connect_config; -using connection = net::use_awaitable_t<>::as_default_on_t; +using boost::redis::request; +using boost::redis::generic_response; +using boost::redis::logger; +using boost::redis::config; +using boost::system::error_code; +using connection = net::deferred_t::as_default_on_t; +using signal_set = net::deferred_t::as_default_on_t; +using namespace std::chrono_literals; /* This example will subscribe and read pushes indefinitely. * @@ -41,27 +45,44 @@ using connection = net::use_awaitable_t<>::as_default_on_t; */ // Receives server pushes. -auto receiver(std::shared_ptr conn) -> net::awaitable +auto +receiver(std::shared_ptr conn) -> net::awaitable { - for (generic_response resp;;) { - co_await conn->async_receive(resp); - std::cout - << resp.value().at(1).value - << " " << resp.value().at(2).value - << " " << resp.value().at(3).value - << std::endl; - resp.value().clear(); + request req; + req.push("SUBSCRIBE", "channel"); + + while (!conn->is_cancelled()) { + + // Reconnect to channels. + co_await conn->async_exec(req); + + // Loop reading Redis pushs messages. + for (generic_response resp;;) { + error_code ec; + co_await conn->async_receive(resp, net::redirect_error(net::use_awaitable, ec)); + if (ec) + break; // Connection lost, break so we can reconnect to channels. + std::cout + << resp.value().at(1).value + << " " << resp.value().at(2).value + << " " << resp.value().at(3).value + << std::endl; + resp.value().clear(); + } } } -auto co_main(address const& addr) -> net::awaitable +auto co_main(config const& cfg) -> net::awaitable { auto ex = co_await net::this_coro::executor; auto conn = std::make_shared(ex); - connect_config cfg; - cfg.addr = addr; net::co_spawn(ex, receiver(conn), net::detached); - redis::experimental::async_connect(*conn, cfg, logger{}, net::consign(net::detached, conn)); + conn->async_run(cfg, {}, net::consign(net::detached, conn)); + + signal_set sig_set(ex, SIGINT, SIGTERM); + co_await sig_set.async_wait(); + + conn->cancel(); } #endif // defined(BOOST_ASIO_HAS_CO_AWAIT) diff --git a/examples/main.cpp b/examples/main.cpp index ff0b527d..b35146ac 100644 --- a/examples/main.cpp +++ b/examples/main.cpp @@ -5,25 +5,27 @@ */ #include "start.hpp" -#include +#include #include #include #include #if defined(BOOST_ASIO_HAS_CO_AWAIT) -extern boost::asio::awaitable co_main(boost::redis::address const&); +using boost::redis::config; + +extern boost::asio::awaitable co_main(config const&); auto main(int argc, char * argv[]) -> int { - boost::redis::address addr; + config cfg; if (argc == 3) { - addr.host = argv[1]; - addr.port = argv[2]; + cfg.addr.host = argv[1]; + cfg.addr.port = argv[2]; } - return start(co_main(addr)); + return start(co_main(cfg)); } #else // defined(BOOST_ASIO_HAS_CO_AWAIT) diff --git a/examples/start.cpp b/examples/start.cpp index eb8674ac..fb6a9f01 100644 --- a/examples/start.cpp +++ b/examples/start.cpp @@ -27,7 +27,7 @@ auto start(net::awaitable op) -> int return 0; } catch (std::exception const& e) { - std::cerr << "Error: " << e.what() << std::endl; + std::cerr << "start> " << e.what() << std::endl; } return 1; diff --git a/examples/sync_connection.hpp b/examples/sync_connection.hpp new file mode 100644 index 00000000..cc982f84 --- /dev/null +++ b/examples/sync_connection.hpp @@ -0,0 +1,63 @@ + +/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com) + * + * Distributed under the Boost Software License, Version 1.0. (See + * accompanying file LICENSE.txt) + */ + +#include +#include +#include +#include +#include +#include +#include + +using namespace std::chrono_literals; + +namespace boost::redis +{ + +class sync_connection { +public: + sync_connection() + : ioc_{1} + , conn_{std::make_shared(ioc_)} + { } + + ~sync_connection() + { + thread_.join(); + } + + void run(config cfg) + { + // Starts a thread that will can io_context::run on which the + // connection will run. + thread_ = std::thread{[this, cfg]() { + conn_->async_run(cfg, {}, asio::detached); + ioc_.run(); + }}; + } + + void stop() + { + asio::dispatch(ioc_, [this]() { conn_->cancel(); }); + } + + template + auto exec(request const& req, Response& resp) + { + asio::dispatch( + conn_->get_executor(), + asio::deferred([this, &req, &resp]() { return conn_->async_exec(req, resp, asio::deferred); })) + (asio::use_future).get(); + } + +private: + asio::io_context ioc_{1}; + std::shared_ptr conn_; + std::thread thread_; +}; + +} diff --git a/include/boost/redis.hpp b/include/boost/redis.hpp index cd64c3fb..15515c9f 100644 --- a/include/boost/redis.hpp +++ b/include/boost/redis.hpp @@ -7,12 +7,15 @@ #ifndef BOOST_REDIS_HPP #define BOOST_REDIS_HPP +#include #include #include #include #include #include #include +#include +#include /** @defgroup high-level-api Reference * diff --git a/include/boost/redis/adapter/detail/adapters.hpp b/include/boost/redis/adapter/detail/adapters.hpp index 46705bb2..43bf6866 100644 --- a/include/boost/redis/adapter/detail/adapters.hpp +++ b/include/boost/redis/adapter/detail/adapters.hpp @@ -12,7 +12,6 @@ #include #include #include -#include #include #include diff --git a/include/boost/redis/adapter/detail/result_traits.hpp b/include/boost/redis/adapter/detail/result_traits.hpp index 2cc6afc9..09c3b520 100644 --- a/include/boost/redis/adapter/detail/result_traits.hpp +++ b/include/boost/redis/adapter/detail/result_traits.hpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include diff --git a/include/boost/redis/address.hpp b/include/boost/redis/address.hpp deleted file mode 100644 index a4af57d8..00000000 --- a/include/boost/redis/address.hpp +++ /dev/null @@ -1,27 +0,0 @@ -/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com) - * - * Distributed under the Boost Software License, Version 1.0. (See - * accompanying file LICENSE.txt) - */ - -#ifndef BOOST_REDIS_ADDRESS_HPP -#define BOOST_REDIS_ADDRESS_HPP - -#include - -namespace boost::redis -{ - -/** @brief Address of a Redis server - * @ingroup high-level-api - */ -struct address { - /// Redis host. - std::string host = "127.0.0.1"; - /// Redis port. - std::string port = "6379"; -}; - -} // boost::redis - -#endif // BOOST_REDIS_ADDRESS_HPP diff --git a/include/boost/redis/config.hpp b/include/boost/redis/config.hpp new file mode 100644 index 00000000..0abc4016 --- /dev/null +++ b/include/boost/redis/config.hpp @@ -0,0 +1,72 @@ +/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com) + * + * Distributed under the Boost Software License, Version 1.0. (See + * accompanying file LICENSE.txt) + */ + +#ifndef BOOST_REDIS_CONFIG_HPP +#define BOOST_REDIS_CONFIG_HPP + +#include +#include + +namespace boost::redis +{ + +/** @brief Address of a Redis server + * @ingroup high-level-api + */ +struct address { + /// Redis host. + std::string host = "127.0.0.1"; + /// Redis port. + std::string port = "6379"; +}; + +/** @brief Configure parameters used by the connection classes + * @ingroup high-level-api + */ +struct config { + /// Address of the Redis server. + address addr = address{"127.0.0.1", "6379"}; + + /** @brief Username passed to the + * [HELLO](https://redis.io/commands/hello/) command. If left + * empty `HELLO` will be sent without authentication parameters. + */ + std::string username; + + /** @brief Password passed to the + * [HELLO](https://redis.io/commands/hello/) command. If left + * empty `HELLO` will be sent without authentication parameters. + */ + std::string password; + + /// Client name parameter of the [HELLO](https://redis.io/commands/hello/) command. + std::string clientname = "Boost.Redis"; + + /// Message used by the health-checker in `boost::redis::connection::async_run`. + std::string health_check_id = "Boost.Redis"; + + /// Logger prefix, see `boost::redis::logger`. + std::string log_prefix = "(Boost.Redis) "; + + /// Time the resolve operation is allowed to last. + std::chrono::steady_clock::duration resolve_timeout = std::chrono::seconds{10}; + + /// Time the connect operation is allowed to last. + std::chrono::steady_clock::duration connect_timeout = std::chrono::seconds{10}; + + /// Time the SSL handshake operation is allowed to last. + std::chrono::steady_clock::duration ssl_handshake_timeout = std::chrono::seconds{10}; + + /// @brief Health checks interval. + std::chrono::steady_clock::duration health_check_interval = std::chrono::seconds{2}; + + /// Time waited before trying a reconnection. + std::chrono::steady_clock::duration reconnect_wait_interval = std::chrono::seconds{1}; +}; + +} // boost::redis + +#endif // BOOST_REDIS_CONFIG_HPP diff --git a/include/boost/redis/connection.hpp b/include/boost/redis/connection.hpp index abdaa9d4..931e4fbb 100644 --- a/include/boost/redis/connection.hpp +++ b/include/boost/redis/connection.hpp @@ -8,6 +8,10 @@ #define BOOST_REDIS_CONNECTION_HPP #include +#include +#include +#include +#include #include #include @@ -16,6 +20,27 @@ namespace boost::redis { +namespace detail +{ + +template +class dummy_handshaker { +public: + dummy_handshaker(Executor) {} + + template + auto async_handshake(Stream&, CompletionToken&& token) + { return asio::post(std::move(token)); } + + void set_config(config const&) {} + + std::size_t cancel(operation) { return 0;} + + constexpr bool is_dummy() const noexcept {return true;} +}; + +} + /** @brief A connection to the Redis server. * @ingroup high-level-api * @@ -50,8 +75,9 @@ class basic_connection : explicit basic_connection(executor_type ex) : base_type{ex} + , reconn_{ex} + , runner_{ex, {}} , stream_{ex} - , reconnect_{true} {} /// Contructs from a context. @@ -67,9 +93,9 @@ class basic_connection : void reset_stream() { if (stream_.is_open()) { - system::error_code ignore; - stream_.shutdown(asio::ip::tcp::socket::shutdown_both, ignore); - stream_.close(ignore); + system::error_code ec; + stream_.shutdown(asio::ip::tcp::socket::shutdown_both, ec); + stream_.close(ec); } } @@ -79,13 +105,31 @@ class basic_connection : /// Returns a const reference to the next layer. auto next_layer() const noexcept -> auto const& { return stream_; } - /** @brief Starts read and write operations + /** @brief Starts underlying connection operations. + * + * In more detail, this function will * - * This function starts read and write operations with the Redis + * 1. Resolve the address passed on `boost::redis::config::addr`. + * 2. Connect to one of the results obtained in the resolve operation. + * 3. Send a [HELLO](https://redis.io/commands/hello/) command where each of its parameters are read from `cfg`. + * 4. Start a health-check operation where ping commands are sent + * at intervals specified in + * `boost::redis::config::health_check_interval`. The message passed to + * `PING` will be `boost::redis::config::health_check_id`. Passing a + * timeout with value zero will disable health-checks. If the Redis + * server does not respond to a health-check within two times the value + * specified here, it will be considered unresponsive and the connection + * will be closed and a new connection will be stablished. + * 5. Starts read and write operations with the Redis * server. More specifically it will trigger the write of all * requests i.e. calls to `async_exec` that happened prior to this * call. * + * When a connection is lost for any reason, a new one is stablished automatically. To disable + * reconnection call `boost::redis::connection::cancel(operation::reconnection)`. + * + * @param cfg Configuration paramters. + * @param l Logger object. The interface expected is specified in the class `boost::redis::logger`. * @param token Completion token. * * The completion token must have the following signature @@ -96,51 +140,37 @@ class basic_connection : * * @remarks * - * * This function will complete only when the connection is lost. - * If the error is asio::error::eof this function will complete - * without error. - * * It can can be called multiple times on the same connection - * object. This makes it simple to implement reconnection in a way - * that does not require cancelling any pending connections. - * - * For examples of how to call this function see the examples. For - * example, if reconnection is not necessary, the coroutine below - * is enough - * - * ```cpp - * auto run(std::shared_ptr conn, std::string host, std::string port) -> net::awaitable - * { - * // From examples/common.hpp to avoid vebosity - * co_await connect(conn, host, port); - * - * // async_run coordinate read and write operations. - * co_await conn->async_run(); - * - * // Cancel pending operations, if any. - * conn->cancel(operation::exec); - * conn->cancel(operation::receive); - * } - * ``` + * * This function will complete only if reconnection was disabled and the connection is lost. * - * For a reconnection example see cpp20_subscriber.cpp. + * For example on how to call this function refer to cpp20_intro.cpp or any other example. */ - template > - auto async_run(CompletionToken token = CompletionToken{}) + template < + class Logger = logger, + class CompletionToken = asio::default_completion_token_t> + auto + async_run( + config const& cfg = {}, + Logger l = Logger{}, + CompletionToken token = CompletionToken{}) { - return base_type::async_run(std::move(token)); + reconn_.set_wait_interval(cfg.reconnect_wait_interval); + runner_.set_config(cfg); + l.set_prefix(runner_.get_config().log_prefix); + return reconn_.async_run(*this, l, std::move(token)); } - /** @brief Executes a command on the Redis server asynchronously. + /** @brief Executes commands on the Redis server asynchronously. * * This function sends a request to the Redis server and - * complete after the response has been processed. If the request + * waits for the responses to each individual command in the + * request to arrive. If the request * contains only commands that don't expect a response, the * completion occurs after it has been written to the underlying * stream. Multiple concurrent calls to this function will be * automatically queued by the implementation. * * @param req Request object. - * @param response Response object. + * @param resp Response object. * @param token Asio completion token. * * For an example see cpp20_echo_server.cpp. The completion token must @@ -158,17 +188,17 @@ class basic_connection : class CompletionToken = asio::default_completion_token_t> auto async_exec( request const& req, - Response& response = ignore, + Response& resp = ignore, CompletionToken token = CompletionToken{}) { - return base_type::async_exec(req, response, std::move(token)); + return base_type::async_exec(req, resp, std::move(token)); } /** @brief Receives server side pushes asynchronously. * - * When pushes arrive and there is no async_receive operation in + * When pushes arrive and there is no `async_receive` operation in * progress, pushed data, requests, and responses will be paused - * until async_receive is called again. Apps will usually want to + * until `async_receive` is called again. Apps will usually want to * call `async_receive` in a loop. * * To cancel an ongoing receive operation apps should call @@ -203,15 +233,18 @@ class basic_connection : * `async_exec`. Affects only requests that haven't been written * yet. * @li operation::run: Cancels the `async_run` operation. - * @li operation::receive: Cancels any ongoing calls to * `async_receive`. - * @li operation::all: Cancels all operations listed above. This - * is the default argument. + * @li operation::receive: Cancels any ongoing calls to `async_receive`. + * @li operation::all: Cancels all operations listed above. * * @param op: The operation to be cancelled. * @returns The number of operations that have been canceled. */ auto cancel(operation op = operation::all) -> std::size_t - { return base_type::cancel(op); } + { + reconn_.cancel(op); + runner_.cancel(op); + return base_type::cancel(op); + } /// Sets the maximum size of the read buffer. void set_max_buffer_read_size(std::size_t max_read_size) noexcept @@ -228,40 +261,43 @@ class basic_connection : void reserve(std::size_t read, std::size_t write) { base_type::reserve(read, write); } - /** @brief Enable reconnection - * - * This property plays any role only when used with - * `boost::redis::async_run`. - */ - void enable_reconnection() noexcept {reconnect_ = true;} - - /** @brief Disable reconnection - * - * This property plays any role only when used with - * `boost::redis::async_run`. - */ - void disable_reconnection() noexcept {reconnect_ = false;} - - bool reconnect() const noexcept {return reconnect_;} + /// Returns true if the connection was canceled. + bool is_cancelled() const noexcept + { return reconn_.is_cancelled();} private: + using runner_type = detail::runner; + using reconnection_type = detail::basic_reconnection; using this_type = basic_connection; template friend class detail::connection_base; - template friend struct detail::exec_read_op; + template friend class detail::read_next_op; template friend struct detail::exec_op; template friend struct detail::receive_op; template friend struct detail::reader_op; - template friend struct detail::writer_op; - template friend struct detail::run_op; + template friend struct detail::writer_op; + template friend struct detail::run_op; template friend struct detail::wait_receive_op; + template friend struct detail::run_all_op; + template friend struct detail::reconnection_op; + + template + auto async_run_one(Logger l, CompletionToken token) + { return runner_.async_run(*this, l, std::move(token)); } + + template + auto async_run_impl(Logger l, CompletionToken token) + { return base_type::async_run_impl(l, std::move(token)); } + + void close() + { reset_stream(); } - void close() { stream_.close(); } auto is_open() const noexcept { return stream_.is_open(); } auto lowest_layer() noexcept -> auto& { return stream_.lowest_layer(); } + reconnection_type reconn_; + runner_type runner_; Socket stream_; - bool reconnect_; }; /** \brief A connection that uses a asio::ip::tcp::socket. diff --git a/include/boost/redis/detail/connection_base.hpp b/include/boost/redis/detail/connection_base.hpp index b478d13c..7b51608d 100644 --- a/include/boost/redis/detail/connection_base.hpp +++ b/include/boost/redis/detail/connection_base.hpp @@ -8,24 +8,450 @@ #define BOOST_REDIS_CONNECTION_BASE_HPP #include +#include +#include +#include #include #include -#include -#include -#include -#include +#include + +#include #include #include +#include +#include +#include +#include +#include +#include -#include -#include -#include +#include +#include #include +#include +#include #include +#include #include namespace boost::redis::detail { +template +struct wait_receive_op { + Conn* conn; + asio::coroutine coro{}; + + template + void + operator()(Self& self , system::error_code ec = {}) + { + BOOST_ASIO_CORO_REENTER (coro) + { + BOOST_ASIO_CORO_YIELD + conn->channel_.async_send(system::error_code{}, 0, std::move(self)); + BOOST_REDIS_CHECK_OP0(;); + + BOOST_ASIO_CORO_YIELD + conn->channel_.async_send(system::error_code{}, 0, std::move(self)); + BOOST_REDIS_CHECK_OP0(;); + + self.complete({}); + } + } +}; + +template +class read_next_op { +public: + using req_info_type = typename Conn::req_info; + using req_info_ptr = typename std::shared_ptr; + +private: + Conn* conn_; + req_info_ptr info_; + Adapter adapter_; + std::size_t cmds_ = 0; + std::size_t read_size_ = 0; + std::size_t index_ = 0; + asio::coroutine coro_{}; + +public: + read_next_op(Conn& conn, Adapter adapter, req_info_ptr info) + : conn_{&conn} + , info_{info} + , adapter_{adapter} + , cmds_{info->get_number_of_commands()} + {} + + template + void + operator()( Self& self + , system::error_code ec = {} + , std::size_t n = 0) + { + BOOST_ASIO_CORO_REENTER (coro_) + { + // Loop reading the responses to this request. + while (cmds_ != 0) { + if (info_->stop_requested()) { + self.complete(asio::error::operation_aborted, 0); + return; + } + + //----------------------------------- + // If we detect a push in the middle of a request we have + // to hand it to the push consumer. To do that we need + // some data in the read bufer. + if (conn_->read_buffer_.empty()) { + BOOST_ASIO_CORO_YIELD + asio::async_read_until( + conn_->next_layer(), + conn_->make_dynamic_buffer(), + "\r\n", std::move(self)); + BOOST_REDIS_CHECK_OP1(conn_->cancel(operation::run);); + if (info_->stop_requested()) { + self.complete(asio::error::operation_aborted, 0); + return; + } + } + + // If the next request is a push we have to handle it to + // the receive_op wait for it to be done and continue. + if (resp3::to_type(conn_->read_buffer_.front()) == resp3::type::push) { + BOOST_ASIO_CORO_YIELD + conn_->async_wait_receive(std::move(self)); + BOOST_REDIS_CHECK_OP1(conn_->cancel(operation::run);); + continue; + } + //----------------------------------- + + BOOST_ASIO_CORO_YIELD + redis::detail::async_read( + conn_->next_layer(), + conn_->make_dynamic_buffer(), + [i = index_, adpt = adapter_] (resp3::basic_node const& nd, system::error_code& ec) mutable { adpt(i, nd, ec); }, + std::move(self)); + + ++index_; + + BOOST_REDIS_CHECK_OP1(conn_->cancel(operation::run);); + + read_size_ += n; + + BOOST_ASSERT(cmds_ != 0); + --cmds_; + } + + self.complete({}, read_size_); + } + } +}; + +template +struct receive_op { + Conn* conn; + Adapter adapter; + std::size_t read_size = 0; + asio::coroutine coro{}; + + template + void + operator()( Self& self + , system::error_code ec = {} + , std::size_t n = 0) + { + BOOST_ASIO_CORO_REENTER (coro) + { + BOOST_ASIO_CORO_YIELD + conn->channel_.async_receive(std::move(self)); + BOOST_REDIS_CHECK_OP1(;); + + BOOST_ASIO_CORO_YIELD + redis::detail::async_read(conn->next_layer(), conn->make_dynamic_buffer(), adapter, std::move(self)); + if (ec || is_cancelled(self)) { + conn->cancel(operation::run); + conn->cancel(operation::receive); + self.complete(!!ec ? ec : asio::error::operation_aborted, {}); + return; + } + + read_size = n; + + BOOST_ASIO_CORO_YIELD + conn->channel_.async_receive(std::move(self)); + BOOST_REDIS_CHECK_OP1(;); + + self.complete({}, read_size); + return; + } + } +}; + +template +struct exec_op { + using req_info_type = typename Conn::req_info; + + Conn* conn = nullptr; + request const* req = nullptr; + Adapter adapter{}; + std::shared_ptr info = nullptr; + std::size_t read_size = 0; + asio::coroutine coro{}; + + template + void + operator()( Self& self + , system::error_code ec = {} + , std::size_t n = 0) + { + BOOST_ASIO_CORO_REENTER (coro) + { + // Check whether the user wants to wait for the connection to + // be stablished. + if (req->get_config().cancel_if_not_connected && !conn->is_open()) { + BOOST_ASIO_CORO_YIELD + asio::post(std::move(self)); + return self.complete(error::not_connected, 0); + } + + info = std::allocate_shared(asio::get_associated_allocator(self), *req, conn->get_executor()); + + conn->add_request_info(info); +EXEC_OP_WAIT: + BOOST_ASIO_CORO_YIELD + info->async_wait(std::move(self)); + BOOST_ASSERT(ec == asio::error::operation_aborted); + + if (info->stop_requested()) { + // Don't have to call remove_request as it has already + // been by cancel(exec). + return self.complete(ec, 0); + } + + if (is_cancelled(self)) { + if (info->is_written()) { + using c_t = asio::cancellation_type; + auto const c = self.get_cancellation_state().cancelled(); + if ((c & c_t::terminal) != c_t::none) { + // Cancellation requires closing the connection + // otherwise it stays in inconsistent state. + conn->cancel(operation::run); + return self.complete(ec, 0); + } else { + // Can't implement other cancelation types, ignoring. + self.get_cancellation_state().clear(); + goto EXEC_OP_WAIT; + } + } else { + // Cancelation can be honored. + conn->remove_request(info); + self.complete(ec, 0); + return; + } + } + + BOOST_ASSERT(conn->is_open()); + + if (req->size() == 0) { + // Don't have to call remove_request as it has already + // been removed. + return self.complete({}, 0); + } + + BOOST_ASSERT(!conn->reqs_.empty()); + BOOST_ASSERT(conn->reqs_.front() != nullptr); + BOOST_ASIO_CORO_YIELD + conn->async_read_next(adapter, std::move(self)); + BOOST_REDIS_CHECK_OP1(;); + + read_size = n; + + if (info->stop_requested()) { + // Don't have to call remove_request as it has already + // been by cancel(exec). + return self.complete(ec, 0); + } + + BOOST_ASSERT(!conn->reqs_.empty()); + conn->reqs_.pop_front(); + + if (conn->is_waiting_response()) { + BOOST_ASSERT(!conn->reqs_.empty()); + conn->reqs_.front()->proceed(); + } else { + conn->read_timer_.cancel_one(); + } + + self.complete({}, read_size); + } + } +}; + +template +struct run_op { + Conn* conn = nullptr; + Logger logger_; + asio::coroutine coro{}; + + template + void operator()( Self& self + , std::array order = {} + , system::error_code ec0 = {} + , system::error_code ec1 = {}) + { + BOOST_ASIO_CORO_REENTER (coro) + { + conn->write_buffer_.clear(); + conn->read_buffer_.clear(); + + BOOST_ASIO_CORO_YIELD + asio::experimental::make_parallel_group( + [this](auto token) { return conn->reader(token);}, + [this](auto token) { return conn->writer(logger_, token);} + ).async_wait( + asio::experimental::wait_for_one(), + std::move(self)); + + if (is_cancelled(self)) { + self.complete(asio::error::operation_aborted); + return; + } + + switch (order[0]) { + case 0: self.complete(ec0); break; + case 1: self.complete(ec1); break; + default: BOOST_ASSERT(false); + } + } + } +}; + +template +struct writer_op { + Conn* conn_; + Logger logger_; + asio::coroutine coro{}; + + template + void operator()( Self& self + , system::error_code ec = {} + , std::size_t n = 0) + { + ignore_unused(n); + + BOOST_ASIO_CORO_REENTER (coro) for (;;) + { + while (conn_->coalesce_requests()) { + BOOST_ASIO_CORO_YIELD + asio::async_write(conn_->next_layer(), asio::buffer(conn_->write_buffer_), std::move(self)); + logger_.on_write(ec, conn_->write_buffer_); + BOOST_REDIS_CHECK_OP0(conn_->cancel(operation::run);); + + conn_->on_write(); + + // A socket.close() may have been called while a + // successful write might had already been queued, so we + // have to check here before proceeding. + if (!conn_->is_open()) { + self.complete({}); + return; + } + } + + BOOST_ASIO_CORO_YIELD + conn_->writer_timer_.async_wait(std::move(self)); + if (!conn_->is_open() || is_cancelled(self)) { + // Notice this is not an error of the op, stoping was + // requested from the outside, so we complete with + // success. + self.complete({}); + return; + } + } + } +}; + +template +struct reader_op { + Conn* conn; + asio::coroutine coro{}; + + bool as_push() const + { + return + (resp3::to_type(conn->read_buffer_.front()) == resp3::type::push) + || conn->reqs_.empty() + || (!conn->reqs_.empty() && conn->reqs_.front()->get_number_of_commands() == 0) + || !conn->is_waiting_response(); // Added to deal with MONITOR. + } + + template + void operator()( Self& self + , system::error_code ec = {} + , std::size_t n = 0) + { + ignore_unused(n); + + BOOST_ASIO_CORO_REENTER (coro) for (;;) + { + BOOST_ASIO_CORO_YIELD + asio::async_read_until( + conn->next_layer(), + conn->make_dynamic_buffer(), + "\r\n", std::move(self)); + + if (ec == asio::error::eof) { + conn->cancel(operation::run); + return self.complete({}); // EOFINAE: EOF is not an error. + } + + BOOST_REDIS_CHECK_OP0(conn->cancel(operation::run);); + + // We handle unsolicited events in the following way + // + // 1. Its resp3 type is a push. + // + // 2. A non-push type is received with an empty requests + // queue. I have noticed this is possible (e.g. -MISCONF). + // I expect them to have type push so we can distinguish + // them from responses to commands, but it is a + // simple-error. If we are lucky enough to receive them + // when the command queue is empty we can treat them as + // server pushes, otherwise it is impossible to handle + // them properly + // + // 3. The request does not expect any response but we got + // one. This may happen if for example, subscribe with + // wrong syntax. + // + // Useful links: + // + // - https://github.com/redis/redis/issues/11784 + // - https://github.com/redis/redis/issues/6426 + // + BOOST_ASSERT(!conn->read_buffer_.empty()); + if (as_push()) { + BOOST_ASIO_CORO_YIELD + conn->async_wait_receive(std::move(self)); + } else { + BOOST_ASSERT_MSG(conn->is_waiting_response(), "Not waiting for a response (using MONITOR command perhaps?)"); + BOOST_ASSERT(!conn->reqs_.empty()); + BOOST_ASSERT(conn->reqs_.front()->get_number_of_commands() != 0); + conn->reqs_.front()->proceed(); + BOOST_ASIO_CORO_YIELD + conn->read_timer_.async_wait(std::move(self)); + ec = {}; + } + + if (!conn->is_open() || ec || is_cancelled(self)) { + conn->cancel(operation::run); + self.complete(asio::error::basic_errors::operation_aborted); + return; + } + } + } +}; + /** Base class for high level Redis asynchronous connections. * * This class is not meant to be instantiated directly but as base @@ -64,16 +490,14 @@ class connection_base { derived().close(); read_timer_.cancel(); writer_timer_.cancel(); - cancel_on_conn_lost(); - - return 1U; + return cancel_on_conn_lost(); } case operation::receive: { channel_.cancel(); return 1U; } - default: BOOST_ASSERT(false); return 0; + default: /* ignore */; return 0; } } @@ -150,7 +574,7 @@ class connection_base { return asio::async_compose < CompletionToken , void(system::error_code, std::size_t) - >(redis::detail::exec_op{&derived(), &req, f}, token, writer_timer_); + >(exec_op{&derived(), &req, f}, token, writer_timer_); } template @@ -163,16 +587,16 @@ class connection_base { return asio::async_compose < CompletionToken , void(system::error_code, std::size_t) - >(redis::detail::receive_op{&derived(), f}, token, channel_); + >(receive_op{&derived(), f}, token, channel_); } - template - auto async_run(CompletionToken token) + template + auto async_run_impl(Logger l, CompletionToken token) { return asio::async_compose < CompletionToken , void(system::error_code) - >(detail::run_op{&derived()}, token, writer_timer_); + >(run_op{&derived(), l}, token, writer_timer_); } void set_max_buffer_read_size(std::size_t max_read_size) noexcept @@ -296,13 +720,13 @@ class connection_base { using reqs_type = std::deque>; - template friend struct detail::reader_op; - template friend struct detail::writer_op; - template friend struct detail::run_op; - template friend struct detail::exec_op; - template friend struct detail::exec_read_op; - template friend struct detail::receive_op; - template friend struct detail::wait_receive_op; + template friend struct reader_op; + template friend struct writer_op; + template friend struct run_op; + template friend struct exec_op; + template friend class read_next_op; + template friend struct receive_op; + template friend struct wait_receive_op; template auto async_wait_receive(CompletionToken token) @@ -356,25 +780,25 @@ class connection_base { return asio::async_compose < CompletionToken , void(system::error_code) - >(detail::reader_op{&derived()}, token, writer_timer_); + >(reader_op{&derived()}, token, writer_timer_); } - template - auto writer(CompletionToken&& token) + template + auto writer(Logger l, CompletionToken&& token) { return asio::async_compose < CompletionToken , void(system::error_code) - >(detail::writer_op{&derived()}, token, writer_timer_); + >(writer_op{&derived(), l}, token, writer_timer_); } template - auto async_exec_read(Adapter adapter, std::size_t cmds, CompletionToken token) + auto async_read_next(Adapter adapter, CompletionToken token) { return asio::async_compose < CompletionToken , void(system::error_code, std::size_t) - >(detail::exec_read_op{&derived(), adapter, cmds}, token, writer_timer_); + >(read_next_op{derived(), adapter, reqs_.front()}, token, writer_timer_); } [[nodiscard]] bool coalesce_requests() diff --git a/include/boost/redis/detail/connection_ops.hpp b/include/boost/redis/detail/connection_ops.hpp deleted file mode 100644 index 1452341b..00000000 --- a/include/boost/redis/detail/connection_ops.hpp +++ /dev/null @@ -1,403 +0,0 @@ -/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com) - * - * Distributed under the Boost Software License, Version 1.0. (See - * accompanying file LICENSE.txt) - */ - -#ifndef BOOST_REDIS_CONNECTION_OPS_HPP -#define BOOST_REDIS_CONNECTION_OPS_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -namespace boost::redis::detail { - -template -struct wait_receive_op { - Conn* conn; - asio::coroutine coro{}; - - template - void - operator()(Self& self , system::error_code ec = {}) - { - BOOST_ASIO_CORO_REENTER (coro) - { - BOOST_ASIO_CORO_YIELD - conn->channel_.async_send(system::error_code{}, 0, std::move(self)); - BOOST_REDIS_CHECK_OP0(;); - - BOOST_ASIO_CORO_YIELD - conn->channel_.async_send(system::error_code{}, 0, std::move(self)); - BOOST_REDIS_CHECK_OP0(;); - - self.complete({}); - } - } -}; - -template -struct exec_read_op { - Conn* conn; - Adapter adapter; - std::size_t cmds = 0; - std::size_t read_size = 0; - std::size_t index = 0; - asio::coroutine coro{}; - - template - void - operator()( Self& self - , system::error_code ec = {} - , std::size_t n = 0) - { - BOOST_ASIO_CORO_REENTER (coro) - { - // Loop reading the responses to this request. - BOOST_ASSERT(!conn->reqs_.empty()); - while (cmds != 0) { - BOOST_ASSERT(conn->is_waiting_response()); - - //----------------------------------- - // If we detect a push in the middle of a request we have - // to hand it to the push consumer. To do that we need - // some data in the read bufer. - if (conn->read_buffer_.empty()) { - BOOST_ASIO_CORO_YIELD - asio::async_read_until( - conn->next_layer(), - conn->make_dynamic_buffer(), - "\r\n", std::move(self)); - BOOST_REDIS_CHECK_OP1(conn->cancel(operation::run);); - } - - // If the next request is a push we have to handle it to - // the receive_op wait for it to be done and continue. - if (resp3::to_type(conn->read_buffer_.front()) == resp3::type::push) { - BOOST_ASIO_CORO_YIELD - conn->async_wait_receive(std::move(self)); - BOOST_REDIS_CHECK_OP1(conn->cancel(operation::run);); - continue; - } - //----------------------------------- - - BOOST_ASIO_CORO_YIELD - redis::detail::async_read( - conn->next_layer(), - conn->make_dynamic_buffer(), - [i = index, adpt = adapter] (resp3::basic_node const& nd, system::error_code& ec) mutable { adpt(i, nd, ec); }, - std::move(self)); - - ++index; - - BOOST_REDIS_CHECK_OP1(conn->cancel(operation::run);); - - read_size += n; - - BOOST_ASSERT(cmds != 0); - --cmds; - } - - self.complete({}, read_size); - } - } -}; - -template -struct receive_op { - Conn* conn; - Adapter adapter; - std::size_t read_size = 0; - asio::coroutine coro{}; - - template - void - operator()( Self& self - , system::error_code ec = {} - , std::size_t n = 0) - { - BOOST_ASIO_CORO_REENTER (coro) - { - BOOST_ASIO_CORO_YIELD - conn->channel_.async_receive(std::move(self)); - BOOST_REDIS_CHECK_OP1(;); - - BOOST_ASIO_CORO_YIELD - redis::detail::async_read(conn->next_layer(), conn->make_dynamic_buffer(), adapter, std::move(self)); - if (ec || is_cancelled(self)) { - conn->cancel(operation::run); - conn->cancel(operation::receive); - self.complete(!!ec ? ec : asio::error::operation_aborted, {}); - return; - } - - read_size = n; - - BOOST_ASIO_CORO_YIELD - conn->channel_.async_receive(std::move(self)); - BOOST_REDIS_CHECK_OP1(;); - - self.complete({}, read_size); - return; - } - } -}; - -template -struct exec_op { - using req_info_type = typename Conn::req_info; - - Conn* conn = nullptr; - request const* req = nullptr; - Adapter adapter{}; - std::shared_ptr info = nullptr; - std::size_t read_size = 0; - asio::coroutine coro{}; - - template - void - operator()( Self& self - , system::error_code ec = {} - , std::size_t n = 0) - { - BOOST_ASIO_CORO_REENTER (coro) - { - // Check whether the user wants to wait for the connection to - // be stablished. - if (req->get_config().cancel_if_not_connected && !conn->is_open()) { - return self.complete(error::not_connected, 0); - } - - info = std::allocate_shared(asio::get_associated_allocator(self), *req, conn->get_executor()); - - conn->add_request_info(info); -EXEC_OP_WAIT: - BOOST_ASIO_CORO_YIELD - info->async_wait(std::move(self)); - BOOST_ASSERT(ec == asio::error::operation_aborted); - - if (info->stop_requested()) { - // Don't have to call remove_request as it has already - // been by cancel(exec). - return self.complete(ec, 0); - } - - if (is_cancelled(self)) { - if (info->is_written()) { - using c_t = asio::cancellation_type; - auto const c = self.get_cancellation_state().cancelled(); - if ((c & c_t::terminal) != c_t::none) { - // Cancellation requires closing the connection - // otherwise it stays in inconsistent state. - conn->cancel(operation::run); - return self.complete(ec, 0); - } else { - // Can't implement other cancelation types, ignoring. - self.get_cancellation_state().clear(); - goto EXEC_OP_WAIT; - } - } else { - // Cancelation can be honored. - conn->remove_request(info); - self.complete(ec, 0); - return; - } - } - - BOOST_ASSERT(conn->is_open()); - - if (req->size() == 0) { - // Don't have to call remove_request as it has already - // been removed. - return self.complete({}, 0); - } - - BOOST_ASSERT(!conn->reqs_.empty()); - BOOST_ASSERT(conn->reqs_.front() != nullptr); - BOOST_ASIO_CORO_YIELD - conn->async_exec_read(adapter, conn->reqs_.front()->get_number_of_commands(), std::move(self)); - BOOST_REDIS_CHECK_OP1(;); - - read_size = n; - - BOOST_ASSERT(!conn->reqs_.empty()); - conn->reqs_.pop_front(); - - if (conn->is_waiting_response()) { - BOOST_ASSERT(!conn->reqs_.empty()); - conn->reqs_.front()->proceed(); - } else { - conn->read_timer_.cancel_one(); - } - - self.complete({}, read_size); - } - } -}; - -template -struct run_op { - Conn* conn = nullptr; - asio::coroutine coro{}; - - template - void operator()( Self& self - , std::array order = {} - , system::error_code ec0 = {} - , system::error_code ec1 = {}) - { - BOOST_ASIO_CORO_REENTER (coro) - { - conn->write_buffer_.clear(); - conn->read_buffer_.clear(); - - BOOST_ASIO_CORO_YIELD - asio::experimental::make_parallel_group( - [this](auto token) { return conn->reader(token);}, - [this](auto token) { return conn->writer(token);} - ).async_wait( - asio::experimental::wait_for_one(), - std::move(self)); - - if (is_cancelled(self)) { - self.complete(asio::error::operation_aborted); - return; - } - - switch (order[0]) { - case 0: self.complete(ec0); break; - case 1: self.complete(ec1); break; - default: BOOST_ASSERT(false); - } - } - } -}; - -template -struct writer_op { - Conn* conn; - asio::coroutine coro{}; - - template - void operator()( Self& self - , system::error_code ec = {} - , std::size_t n = 0) - { - ignore_unused(n); - - BOOST_ASIO_CORO_REENTER (coro) for (;;) - { - while (conn->coalesce_requests()) { - BOOST_ASIO_CORO_YIELD - asio::async_write(conn->next_layer(), asio::buffer(conn->write_buffer_), std::move(self)); - BOOST_REDIS_CHECK_OP0(conn->cancel(operation::run);); - - conn->on_write(); - - // A socket.close() may have been called while a - // successful write might had already been queued, so we - // have to check here before proceeding. - if (!conn->is_open()) { - self.complete({}); - return; - } - } - - BOOST_ASIO_CORO_YIELD - conn->writer_timer_.async_wait(std::move(self)); - if (!conn->is_open() || is_cancelled(self)) { - // Notice this is not an error of the op, stoping was - // requested from the outside, so we complete with - // success. - self.complete({}); - return; - } - } - } -}; - -template -struct reader_op { - Conn* conn; - asio::coroutine coro{}; - - template - void operator()( Self& self - , system::error_code ec = {} - , std::size_t n = 0) - { - ignore_unused(n); - - BOOST_ASIO_CORO_REENTER (coro) for (;;) - { - BOOST_ASIO_CORO_YIELD - asio::async_read_until( - conn->next_layer(), - conn->make_dynamic_buffer(), - "\r\n", std::move(self)); - - if (ec == asio::error::eof) { - conn->cancel(operation::run); - return self.complete({}); // EOFINAE: EOF is not an error. - } - - BOOST_REDIS_CHECK_OP0(conn->cancel(operation::run);); - - // We handle unsolicited events in the following way - // - // 1. Its resp3 type is a push. - // - // 2. A non-push type is received with an empty requests - // queue. I have noticed this is possible (e.g. -MISCONF). - // I expect them to have type push so we can distinguish - // them from responses to commands, but it is a - // simple-error. If we are lucky enough to receive them - // when the command queue is empty we can treat them as - // server pushes, otherwise it is impossible to handle - // them properly - // - // 3. The request does not expect any response but we got - // one. This may happen if for example, subscribe with - // wrong syntax. - // - BOOST_ASSERT(!conn->read_buffer_.empty()); - if (resp3::to_type(conn->read_buffer_.front()) == resp3::type::push - || conn->reqs_.empty() - || (!conn->reqs_.empty() && conn->reqs_.front()->get_number_of_commands() == 0)) { - BOOST_ASIO_CORO_YIELD - conn->async_wait_receive(std::move(self)); - } else { - BOOST_ASSERT(conn->is_waiting_response()); - BOOST_ASSERT(!conn->reqs_.empty()); - BOOST_ASSERT(conn->reqs_.front()->get_number_of_commands() != 0); - conn->reqs_.front()->proceed(); - BOOST_ASIO_CORO_YIELD - conn->read_timer_.async_wait(std::move(self)); - ec = {}; - } - - if (!conn->is_open() || ec || is_cancelled(self)) { - conn->cancel(operation::run); - self.complete(asio::error::basic_errors::operation_aborted); - return; - } - } - } -}; - -} // boost::redis::detail - -#endif // BOOST_REDIS_CONNECTION_OPS_HPP diff --git a/include/boost/redis/detail/connector.hpp b/include/boost/redis/detail/connector.hpp new file mode 100644 index 00000000..4e9c1507 --- /dev/null +++ b/include/boost/redis/detail/connector.hpp @@ -0,0 +1,133 @@ +/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com) + * + * Distributed under the Boost Software License, Version 1.0. (See + * accompanying file LICENSE.txt) + */ + +#ifndef BOOST_REDIS_CONNECTOR_HPP +#define BOOST_REDIS_CONNECTOR_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace boost::redis::detail +{ + +template +struct connect_op { + Connector* ctor_ = nullptr; + Stream* stream = nullptr; + asio::ip::tcp::resolver::results_type const* res_ = nullptr; + asio::coroutine coro{}; + + template + void operator()( Self& self + , std::array const& order = {} + , system::error_code const& ec1 = {} + , asio::ip::tcp::endpoint const& ep= {} + , system::error_code const& ec2 = {}) + { + BOOST_ASIO_CORO_REENTER (coro) + { + ctor_->timer_.expires_after(ctor_->timeout_); + + BOOST_ASIO_CORO_YIELD + asio::experimental::make_parallel_group( + [this](auto token) + { + auto f = [](system::error_code const&, auto const&) { return true; }; + return asio::async_connect(*stream, *res_, f, token); + }, + [this](auto token) { return ctor_->timer_.async_wait(token);} + ).async_wait( + asio::experimental::wait_for_one(), + std::move(self)); + + if (is_cancelled(self)) { + self.complete(asio::error::operation_aborted); + return; + } + + switch (order[0]) { + case 0: { + ctor_->endpoint_ = ep; + self.complete(ec1); + } break; + case 1: + { + if (ec2) { + self.complete(ec2); + } else { + self.complete(error::connect_timeout); + } + } break; + + default: BOOST_ASSERT(false); + } + } + } +}; + +template +class connector { +public: + using timer_type = + asio::basic_waitable_timer< + std::chrono::steady_clock, + asio::wait_traits, + Executor>; + + connector(Executor ex) + : timer_{ex} + {} + + void set_config(config const& cfg) + { timeout_ = cfg.connect_timeout; } + + template + auto + async_connect( + Stream& stream, + asio::ip::tcp::resolver::results_type const& res, + CompletionToken&& token) + { + return asio::async_compose + < CompletionToken + , void(system::error_code) + >(connect_op{this, &stream, &res}, token, timer_); + } + + std::size_t cancel(operation op) + { + switch (op) { + case operation::connect: + case operation::all: + timer_.cancel(); + break; + default: /* ignore */; + } + + return 0; + } + + auto const& endpoint() const noexcept { return endpoint_;} + +private: + template friend struct connect_op; + + timer_type timer_; + std::chrono::steady_clock::duration timeout_ = std::chrono::seconds{2}; + asio::ip::tcp::endpoint endpoint_; +}; + +} // boost::redis::detail + +#endif // BOOST_REDIS_CONNECTOR_HPP diff --git a/include/boost/redis/check_health.hpp b/include/boost/redis/detail/health_checker.hpp similarity index 71% rename from include/boost/redis/check_health.hpp rename to include/boost/redis/detail/health_checker.hpp index aaf6d46b..374f2b87 100644 --- a/include/boost/redis/check_health.hpp +++ b/include/boost/redis/detail/health_checker.hpp @@ -4,14 +4,15 @@ * accompanying file LICENSE.txt) */ -#ifndef BOOST_REDIS_CHECK_HEALTH_HPP -#define BOOST_REDIS_CHECK_HEALTH_HPP +#ifndef BOOST_REDIS_HEALTH_CHECKER_HPP +#define BOOST_REDIS_HEALTH_CHECKER_HPP // Has to included before promise.hpp to build on msvc. #include #include #include #include +#include #include #include #include @@ -20,8 +21,7 @@ #include #include -namespace boost::redis { -namespace detail { +namespace boost::redis::detail { template class ping_op { @@ -70,7 +70,7 @@ class check_timeout_op { checker_->wait_timer_.async_wait(std::move(self)); BOOST_REDIS_CHECK_OP0(;) - if (!checker_->resp_.has_value()) { + if (checker_->resp_.has_error()) { self.complete({}); return; } @@ -83,8 +83,6 @@ class check_timeout_op { return; } - checker_->resp_.value().clear(); - if (checker_->resp_.has_value()) { checker_->resp_.value().clear(); } @@ -109,6 +107,13 @@ class check_health_op { { BOOST_ASIO_CORO_REENTER (coro_) { + if (checker_->ping_interval_.count() == 0) { + BOOST_ASIO_CORO_YIELD + asio::post(std::move(self)); + self.complete({}); + return; + } + BOOST_ASIO_CORO_YIELD asio::experimental::make_parallel_group( [this](auto token) { return checker_->async_ping(*conn_, token); }, @@ -141,15 +146,18 @@ class health_checker { Executor>; public: - health_checker( - Executor ex, - std::string const& msg, - std::chrono::steady_clock::duration ping_interval) + health_checker(Executor ex) : ping_timer_{ex} , wait_timer_{ex} - , ping_interval_{ping_interval} { - req_.push("PING", msg); + req_.push("PING", "Boost.Redis"); + } + + void set_config(config const& cfg) + { + req_.clear(); + req_.push("PING", cfg.health_check_id); + ping_interval_ = cfg.health_check_interval; } template < @@ -164,10 +172,18 @@ class health_checker { >(check_health_op{this, &conn}, token, conn); } - void cancel() + std::size_t cancel(operation op) { - ping_timer_.cancel(); - wait_timer_.cancel(); + switch (op) { + case operation::health_check: + case operation::all: + ping_timer_.cancel(); + wait_timer_.cancel(); + break; + default: /* ignore */; + } + + return 0; } private: @@ -197,50 +213,10 @@ class health_checker { timer_type wait_timer_; redis::request req_; redis::generic_response resp_; - std::chrono::steady_clock::duration ping_interval_; + std::chrono::steady_clock::duration ping_interval_ = std::chrono::seconds{5}; bool checker_has_exited_ = false; }; -} // detail +} // boost::redis::detail -/** @brief Checks Redis health asynchronously - * @ingroup high-level-api - * - * This function will ping the Redis server periodically until a ping - * timesout or an error occurs. On timeout this function will - * complete with success. - * - * @param conn A connection to the Redis server. - * @param msg The message to be sent with the [PING](https://redis.io/commands/ping/) command. Seting a proper and unique id will help users identify which connections are active. - * @param ping_interval Ping ping_interval. - * @param token The completion token - * - * The completion token must have the following signature - * - * @code - * void f(system::error_code); - * @endcode - * - * Completion occurs when a pong response is not receive within two - * times the ping interval. - */ -template < - class Connection, - class CompletionToken = asio::default_completion_token_t -> -auto -async_check_health( - Connection& conn, - std::string const& msg = "Boost.Redis", - std::chrono::steady_clock::duration ping_interval = std::chrono::seconds{2}, - CompletionToken token = CompletionToken{}) -{ - using executor_type = typename Connection::executor_type; - using health_checker_type = detail::health_checker; - auto checker = std::make_shared(conn.get_executor(), msg, ping_interval); - return checker->async_check_health(conn, asio::consign(std::move(token), checker)); -} - -} // boost::redis - -#endif // BOOST_REDIS_CHECK_HEALTH_HPP +#endif // BOOST_REDIS_HEALTH_CHECKER_HPP diff --git a/include/boost/redis/detail/reconnection.hpp b/include/boost/redis/detail/reconnection.hpp new file mode 100644 index 00000000..d021a3d9 --- /dev/null +++ b/include/boost/redis/detail/reconnection.hpp @@ -0,0 +1,135 @@ +/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com) + * + * Distributed under the Boost Software License, Version 1.0. (See + * accompanying file LICENSE.txt) + */ + +#ifndef BOOST_REDIS_RECONNECTION_HPP +#define BOOST_REDIS_RECONNECTION_HPP + +#include +#include +#include +#include +#include + +namespace boost::redis::detail +{ + +template +struct reconnection_op { + Reconnector* reconn_ = nullptr; + Connection* conn_ = nullptr; + Logger logger_; + asio::coroutine coro_{}; + + template + void operator()(Self& self, system::error_code ec = {}) + { + BOOST_ASIO_CORO_REENTER (coro_) for (;;) + { + BOOST_ASIO_CORO_YIELD + conn_->async_run_one(logger_, std::move(self)); + conn_->reset_stream(); + conn_->cancel(operation::receive); + logger_.on_connection_lost(ec); + if (reconn_->is_cancelled() || is_cancelled(self)) { + reconn_->cancel(operation::reconnection); + self.complete(!!ec ? ec : asio::error::operation_aborted); + return; + } + + reconn_->timer_.expires_after(reconn_->wait_interval_); + BOOST_ASIO_CORO_YIELD + reconn_->timer_.async_wait(std::move(self)); + BOOST_REDIS_CHECK_OP0(;) + if (reconn_->is_cancelled()) { + self.complete(asio::error::operation_aborted); + return; + } + } + } +}; + +// NOTE: wait_interval could be an async_run parameter. + +template +class basic_reconnection { +public: + /// Executor type. + using executor_type = Executor; + + basic_reconnection(Executor ex) + : timer_{ex} + , is_cancelled_{false} + {} + + basic_reconnection(asio::io_context& ioc, std::chrono::steady_clock::duration wait_interval) + : basic_reconnection{ioc.get_executor(), wait_interval} + {} + + /// Rebinds to a new executor type. + template + struct rebind_executor + { + using other = basic_reconnection; + }; + + template < + class Connection, + class Logger = logger, + class CompletionToken = asio::default_completion_token_t + > + auto + async_run( + Connection& conn, + Logger l = Logger{}, + CompletionToken token = CompletionToken{}) + { + return asio::async_compose + < CompletionToken + , void(system::error_code) + >(detail::reconnection_op{this, &conn, l}, token, conn); + } + + void set_wait_interval(std::chrono::steady_clock::duration wait_interval) + { + wait_interval_ = wait_interval; + } + + std::size_t cancel(operation op) + { + switch (op) { + case operation::reconnection: + case operation::all: + is_cancelled_ = true; + timer_.cancel(); + break; + default: /* ignore */; + } + + return 0U; + } + + bool is_cancelled() const noexcept {return is_cancelled_;} + void reset() noexcept {is_cancelled_ = false;} + +private: + using timer_type = + asio::basic_waitable_timer< + std::chrono::steady_clock, + asio::wait_traits, + Executor>; + + template friend struct detail::reconnection_op; + + timer_type timer_; + std::chrono::steady_clock::duration wait_interval_ = std::chrono::seconds{1}; + bool is_cancelled_; +}; + +using reconnection = basic_reconnection; + +} // boost::redis + +#endif // BOOST_REDIS_RECONNECTION_HPP diff --git a/include/boost/redis/detail/resolver.hpp b/include/boost/redis/detail/resolver.hpp new file mode 100644 index 00000000..f4a31036 --- /dev/null +++ b/include/boost/redis/detail/resolver.hpp @@ -0,0 +1,137 @@ +/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com) + * + * Distributed under the Boost Software License, Version 1.0. (See + * accompanying file LICENSE.txt) + */ + +#ifndef BOOST_REDIS_RESOLVER_HPP +#define BOOST_REDIS_RESOLVER_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace boost::redis::detail +{ + +template +struct resolve_op { + Resolver* resv_ = nullptr; + asio::coroutine coro{}; + + template + void operator()( Self& self + , std::array order = {} + , system::error_code ec1 = {} + , asio::ip::tcp::resolver::results_type res = {} + , system::error_code ec2 = {}) + { + BOOST_ASIO_CORO_REENTER (coro) + { + resv_->timer_.expires_after(resv_->timeout_); + + BOOST_ASIO_CORO_YIELD + asio::experimental::make_parallel_group( + [this](auto token) + { + return resv_->resv_.async_resolve(resv_->addr_.host, resv_->addr_.port, token); + }, + [this](auto token) { return resv_->timer_.async_wait(token);} + ).async_wait( + asio::experimental::wait_for_one(), + std::move(self)); + + if (is_cancelled(self)) { + self.complete(asio::error::operation_aborted); + return; + } + + switch (order[0]) { + case 0: { + // Resolver completed first. + resv_->results_ = res; + self.complete(ec1); + } break; + + case 1: { + if (ec2) { + // Timer completed first with error, perhaps a + // cancellation going on. + self.complete(ec2); + } else { + // Timer completed first without an error, this is a + // resolve timeout. + self.complete(error::resolve_timeout); + } + } break; + + default: BOOST_ASSERT(false); + } + } + } +}; + +template +class resolver { +public: + using timer_type = + asio::basic_waitable_timer< + std::chrono::steady_clock, + asio::wait_traits, + Executor>; + + resolver(Executor ex) : resv_{ex} , timer_{ex} {} + + template + auto async_resolve(CompletionToken&& token) + { + return asio::async_compose + < CompletionToken + , void(system::error_code) + >(resolve_op{this}, token, resv_); + } + + std::size_t cancel(operation op) + { + switch (op) { + case operation::resolve: + case operation::all: + resv_.cancel(); + timer_.cancel(); + break; + default: /* ignore */; + } + + return 0; + } + + auto const& results() const noexcept + { return results_;} + + void set_config(config const& cfg) + { + addr_ = cfg.addr; + timeout_ = cfg.resolve_timeout; + } + +private: + using resolver_type = asio::ip::basic_resolver; + template friend struct resolve_op; + + resolver_type resv_; + timer_type timer_; + address addr_; + std::chrono::steady_clock::duration timeout_; + asio::ip::tcp::resolver::results_type results_; +}; + +} // boost::redis::detail + +#endif // BOOST_REDIS_RESOLVER_HPP diff --git a/include/boost/redis/detail/runner.hpp b/include/boost/redis/detail/runner.hpp index 6a86d219..b9329187 100644 --- a/include/boost/redis/detail/runner.hpp +++ b/include/boost/redis/detail/runner.hpp @@ -7,229 +7,238 @@ #ifndef BOOST_REDIS_RUNNER_HPP #define BOOST_REDIS_RUNNER_HPP -// Has to included before promise.hpp to build on msvc. +#include +#include +#include #include #include -#include +#include +#include +#include +#include #include -#include -#include #include #include #include #include +#include +#include #include #include -namespace boost::redis::detail { +namespace boost::redis::detail +{ -template -struct resolve_op { - Runner* runner = nullptr; - asio::coroutine coro{}; +template +struct hello_op { + Runner* runner_ = nullptr; + Connection* conn_ = nullptr; + Logger logger_; + asio::coroutine coro_{}; template - void operator()( Self& self - , std::array order = {} - , system::error_code ec1 = {} - , asio::ip::tcp::resolver::results_type res = {} - , system::error_code ec2 = {}) + void operator()(Self& self, system::error_code ec = {}, std::size_t = 0) { - BOOST_ASIO_CORO_REENTER (coro) + BOOST_ASIO_CORO_REENTER (coro_) { - BOOST_ASIO_CORO_YIELD - asio::experimental::make_parallel_group( - [this](auto token) - { - return runner->resv_.async_resolve(runner->addr_.host, runner->addr_.port, token); - }, - [this](auto token) { return runner->timer_.async_wait(token);} - ).async_wait( - asio::experimental::wait_for_one(), - std::move(self)); - - runner->logger_.on_resolve(ec1, res); - - if (is_cancelled(self)) { - self.complete(asio::error::operation_aborted); - return; - } + runner_->hello_req_.clear(); + if (runner_->hello_resp_.has_value()) + runner_->hello_resp_.value().clear(); + runner_->add_hello(); - switch (order[0]) { - case 0: { - // Resolver completed first. - runner->endpoints_ = res; - self.complete(ec1); - } break; - - case 1: { - if (ec2) { - // Timer completed first with error, perhaps a - // cancellation going on. - self.complete(ec2); - } else { - // Timer completed first without an error, this is a - // resolve timeout. - self.complete(error::resolve_timeout); - } - } break; - - default: BOOST_ASSERT(false); - } + BOOST_ASIO_CORO_YIELD + conn_->async_exec(runner_->hello_req_, runner_->hello_resp_, std::move(self)); + logger_.on_hello(ec, runner_->hello_resp_); + BOOST_REDIS_CHECK_OP0(conn_->cancel(operation::run);) + self.complete(ec); } } }; -template -struct connect_op { - Runner* runner = nullptr; - Stream* stream = nullptr; - asio::coroutine coro{}; +template +class runner_op { +private: + Runner* runner_ = nullptr; + Connection* conn_ = nullptr; + Logger logger_; + asio::coroutine coro_{}; + +public: + runner_op(Runner* runner, Connection* conn, Logger l) + : runner_{runner} + , conn_{conn} + , logger_{l} + {} template void operator()( Self& self - , std::array const& order = {} - , system::error_code const& ec1 = {} - , asio::ip::tcp::endpoint const& ep= {} - , system::error_code const& ec2 = {}) + , std::array order = {} + , system::error_code ec0 = {} + , system::error_code ec1 = {} + , system::error_code ec2 = {} + , std::size_t = 0) { - BOOST_ASIO_CORO_REENTER (coro) + BOOST_ASIO_CORO_REENTER (coro_) { BOOST_ASIO_CORO_YIELD asio::experimental::make_parallel_group( - [this](auto token) - { - auto f = [](system::error_code const&, auto const&) { return true; }; - return asio::async_connect(*stream, runner->endpoints_, f, token); - }, - [this](auto token) { return runner->timer_.async_wait(token);} + [this](auto token) { return runner_->async_run_all(*conn_, logger_, token); }, + [this](auto token) { return runner_->health_checker_.async_check_health(*conn_, token); }, + [this](auto token) { return runner_->async_hello(*conn_, logger_, token); } ).async_wait( - asio::experimental::wait_for_one(), + asio::experimental::wait_for_all(), std::move(self)); - runner->logger_.on_connect(ec1, ep); - if (is_cancelled(self)) { self.complete(asio::error::operation_aborted); return; } - switch (order[0]) { - case 0: { - self.complete(ec1); - } break; - case 1: - { - if (ec2) { - self.complete(ec2); - } else { - self.complete(error::connect_timeout); - } - } break; - - default: BOOST_ASSERT(false); + if (ec0 == error::connect_timeout || ec0 == error::resolve_timeout) { + self.complete(ec0); + return; + } + + if (order[0] == 2 && !!ec2) { + self.complete(ec2); + return; } + + if (order[0] == 1 && ec1 == error::pong_timeout) { + self.complete(ec1); + return; + } + + self.complete(ec0); } } }; -template -struct runner_op { - Runner* runner = nullptr; - Connection* conn = nullptr; - std::chrono::steady_clock::duration resolve_timeout; - std::chrono::steady_clock::duration connect_timeout; - asio::coroutine coro{}; +template +struct run_all_op { + Runner* runner_ = nullptr; + Connection* conn_ = nullptr; + Logger logger_; + asio::coroutine coro_{}; template - void operator()(Self& self, system::error_code ec = {}) + void operator()(Self& self, system::error_code ec = {}, std::size_t = 0) { - BOOST_ASIO_CORO_REENTER (coro) + BOOST_ASIO_CORO_REENTER (coro_) { - runner->timer_.expires_after(resolve_timeout); BOOST_ASIO_CORO_YIELD - runner->async_resolve(std::move(self)); - BOOST_REDIS_CHECK_OP0(;) + runner_->resv_.async_resolve(std::move(self)); + logger_.on_resolve(ec, runner_->resv_.results()); + BOOST_REDIS_CHECK_OP0(conn_->cancel(operation::run);) - runner->timer_.expires_after(connect_timeout); BOOST_ASIO_CORO_YIELD - runner->async_connect(conn->next_layer(), std::move(self)); - BOOST_REDIS_CHECK_OP0(;) + runner_->ctor_.async_connect(conn_->lowest_layer(), runner_->resv_.results(), std::move(self)); + logger_.on_connect(ec, runner_->ctor_.endpoint()); + BOOST_REDIS_CHECK_OP0(conn_->cancel(operation::run);) + + if (!runner_->hsher_.is_dummy()) { + BOOST_ASIO_CORO_YIELD + runner_->hsher_.async_handshake(conn_->next_layer(), std::move(self)); + logger_.on_ssl_handshake(ec); + BOOST_REDIS_CHECK_OP0(conn_->cancel(operation::run);) + } BOOST_ASIO_CORO_YIELD - conn->async_run(std::move(self)); + conn_->async_run_impl(logger_, std::move(self)); BOOST_REDIS_CHECK_OP0(;) - self.complete({}); + self.complete(ec); } } }; -template +template class Handshaker> class runner { public: - using timer_type = - asio::basic_waitable_timer< - std::chrono::steady_clock, - asio::wait_traits, - Executor>; - - runner(Executor ex, address addr, Logger l = Logger{}) + runner(Executor ex, config cfg) : resv_{ex} - , timer_{ex} - , addr_{addr} - , logger_{l} - {} + , ctor_{ex} + , hsher_{ex} + , health_checker_{ex} + , cfg_{cfg} + { } + + std::size_t cancel(operation op) + { + resv_.cancel(op); + ctor_.cancel(op); + hsher_.cancel(op); + health_checker_.cancel(op); + return 0U; + } + + void set_config(config const& cfg) + { + cfg_ = cfg; + resv_.set_config(cfg); + ctor_.set_config(cfg); + hsher_.set_config(cfg); + health_checker_.set_config(cfg); + } - template - auto async_resolve(CompletionToken&& token) + template + auto async_run(Connection& conn, Logger l, CompletionToken token) { return asio::async_compose < CompletionToken , void(system::error_code) - >(resolve_op{this}, token, resv_); + >(runner_op{this, &conn, l}, token, conn); } - template - auto async_connect(Stream& stream, CompletionToken&& token) + config const& get_config() const noexcept {return cfg_;} + +private: + using resolver_type = resolver; + using connector_type = connector; + using handshaker_type = Handshaker; + using health_checker_type = health_checker; + using timer_type = typename connector_type::timer_type; + + template friend struct run_all_op; + template friend class runner_op; + template friend struct hello_op; + + template + auto async_run_all(Connection& conn, Logger l, CompletionToken token) { return asio::async_compose < CompletionToken , void(system::error_code) - >(connect_op{this, &stream}, token, resv_); + >(run_all_op{this, &conn, l}, token, conn); } - template - auto - async_run( - Connection& conn, - std::chrono::steady_clock::duration resolve_timeout, - std::chrono::steady_clock::duration connect_timeout, - CompletionToken&& token) + template + auto async_hello(Connection& conn, Logger l, CompletionToken token) { return asio::async_compose < CompletionToken , void(system::error_code) - >(runner_op{this, &conn, resolve_timeout, connect_timeout}, token, resv_); + >(hello_op{this, &conn, l}, token, conn); } - void cancel() + void add_hello() { - resv_.cancel(); - timer_.cancel(); + if (!cfg_.username.empty() && !cfg_.password.empty() && !cfg_.clientname.empty()) + hello_req_.push("HELLO", "3", "AUTH", cfg_.username, cfg_.password, "SETNAME", cfg_.clientname); + else if (cfg_.username.empty() && cfg_.password.empty() && cfg_.clientname.empty()) + hello_req_.push("HELLO", "3"); + else if (cfg_.clientname.empty()) + hello_req_.push("HELLO", "3", "AUTH", cfg_.username, cfg_.password); + else + hello_req_.push("HELLO", "3", "SETNAME", cfg_.clientname); } -private: - using resolver_type = asio::ip::basic_resolver; - - template friend struct runner_op; - template friend struct connect_op; - template friend struct resolve_op; - resolver_type resv_; - timer_type timer_; - address addr_; - asio::ip::tcp::resolver::results_type endpoints_; - Logger logger_; + connector_type ctor_; + handshaker_type hsher_; + health_checker_type health_checker_; + request hello_req_; + generic_response hello_resp_; + config cfg_; }; } // boost::redis::detail diff --git a/include/boost/redis/error.hpp b/include/boost/redis/error.hpp index 0cc18fa1..7424aea7 100644 --- a/include/boost/redis/error.hpp +++ b/include/boost/redis/error.hpp @@ -72,6 +72,9 @@ enum class error /// Connect timeout pong_timeout, + + /// SSL handshake timeout + ssl_handshake_timeout, }; /** \internal diff --git a/include/boost/redis/experimental/connector.hpp b/include/boost/redis/experimental/connector.hpp deleted file mode 100644 index 13dbce97..00000000 --- a/include/boost/redis/experimental/connector.hpp +++ /dev/null @@ -1,313 +0,0 @@ -/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com) - * - * Distributed under the Boost Software License, Version 1.0. (See - * accompanying file LICENSE.txt) - */ - -#ifndef BOOST_REDIS_CONNECTOR_HPP -#define BOOST_REDIS_CONNECTOR_HPP - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -namespace boost::redis::experimental -{ - -struct connect_config { - address addr = address{"127.0.0.1", "6379"}; - std::string username; - std::string password; - std::string clientname = "Boost.Redis"; - std::string health_check_id = "Boost.Redis"; - std::chrono::steady_clock::duration resolve_timeout = std::chrono::seconds{10}; - std::chrono::steady_clock::duration connect_timeout = std::chrono::seconds{10}; - std::chrono::steady_clock::duration health_check_timeout = std::chrono::seconds{2}; - std::chrono::steady_clock::duration reconnect_wait_interval = std::chrono::seconds{1}; -}; - -namespace detail -{ - -template -struct hello_op { - Connector* ctor_ = nullptr; - Connection* conn_ = nullptr; - asio::coroutine coro_{}; - - template - void operator()(Self& self, system::error_code ec = {}, std::size_t = 0) - { - BOOST_ASIO_CORO_REENTER (coro_) - { - ctor_->req_hello_.clear(); - ctor_->resp_hello_.value().clear(); - ctor_->add_hello(); - - BOOST_ASIO_CORO_YIELD - conn_->async_exec(ctor_->req_hello_, ctor_->resp_hello_, std::move(self)); - - ctor_->logger_.on_hello(ec); - - BOOST_REDIS_CHECK_OP0(conn_->cancel(redis::operation::run);) - - if (ctor_->resp_hello_.has_error()) { - conn_->cancel(redis::operation::run); - switch (ctor_->resp_hello_.error().data_type) { - case resp3::type::simple_error: - self.complete(error::resp3_simple_error); - break; - - case resp3::type::blob_error: - self.complete(error::resp3_blob_error); - break; - - default: BOOST_ASSERT_MSG(false, "Unexpects error data type."); - } - } else { - self.complete({}); - } - } - } -}; - -template -struct run_check_exec_op { - Connector* ctor_ = nullptr; - Connection* conn_ = nullptr; - asio::coroutine coro_{}; - - template - void operator()( Self& self - , std::array order = {} - , system::error_code ec1 = {} - , system::error_code ec2 = {} - , std::size_t = 0) - { - BOOST_ASIO_CORO_REENTER (coro_) - { - BOOST_ASIO_CORO_YIELD - asio::experimental::make_parallel_group( - [this](auto token) { return ctor_->async_run_check(*conn_, token); }, - [this](auto token) { return ctor_->async_hello(*conn_, token); } - ).async_wait( - asio::experimental::wait_for_all(), - std::move(self) - ); - - if (is_cancelled(self)) { - self.complete(asio::error::operation_aborted); - return; - } - - // TODO: Which op should we use to complete? - switch (order[0]) { - case 0: self.complete(ec1); break; - case 1: self.complete(ec2); break; - default: BOOST_ASSERT(false); - } - } - } -}; - -template -struct run_check_op { - Connector* ctor_ = nullptr; - Connection* conn_ = nullptr; - asio::coroutine coro_{}; - - template - void operator()( Self& self - , std::array order = {} - , system::error_code ec1 = {} - , system::error_code ec2 = {}) - { - BOOST_ASIO_CORO_REENTER (coro_) - { - BOOST_ASIO_CORO_YIELD - asio::experimental::make_parallel_group( - [this](auto token) - { - return ctor_->runner_.async_run(*conn_, ctor_->cfg_.resolve_timeout, ctor_->cfg_.connect_timeout, token); - }, - [this](auto token) - { - return ctor_->health_checker_.async_check_health(*conn_, token); - } - ).async_wait( - asio::experimental::wait_for_one(), - std::move(self)); - - if (is_cancelled(self)) { - self.complete(asio::error::operation_aborted); - return; - } - - switch (order[0]) { - case 0: self.complete(ec1); break; - case 1: self.complete(ec2); break; - default: BOOST_ASSERT(false); - } - } - } -}; - -template -struct connect_op { - Connector* ctor_ = nullptr; - Connection* conn_ = nullptr; - asio::coroutine coro_{}; - - template - void operator()(Self& self, system::error_code ec = {}) - { - BOOST_ASIO_CORO_REENTER (coro_) for (;;) - { - BOOST_ASIO_CORO_YIELD - ctor_->async_run_check_exec(*conn_, std::move(self)); - ctor_->logger_.on_connection_lost(); - if (is_cancelled(self)) { - self.complete(asio::error::operation_aborted); - return; - } - - conn_->reset_stream(); - - if (!conn_->reconnect()) { - self.complete({}); - return; - } - - // Wait some time before trying to reconnect. - ctor_->reconnect_wait_timer_.expires_after(ctor_->cfg_.reconnect_wait_interval); - BOOST_ASIO_CORO_YIELD - ctor_->reconnect_wait_timer_.async_wait(std::move(self)); - BOOST_REDIS_CHECK_OP0(;) - } - } -}; - -template -class connector { -public: - connector(Executor ex, connect_config cfg, Logger l) - : runner_{ex, cfg.addr, l} - , health_checker_{ex, cfg.health_check_id, cfg.health_check_timeout} - , reconnect_wait_timer_{ex} - , cfg_{cfg} - , logger_{l} - { } - - template < - class Connection, - class CompletionToken = asio::default_completion_token_t - > - auto async_connect(Connection& conn, CompletionToken token = CompletionToken{}) - { - return asio::async_compose - < CompletionToken - , void(system::error_code) - >(connect_op{this, &conn}, token, conn); - } - - void cancel() - { - runner_.cancel(); - health_checker_.cancel(); - reconnect_wait_timer_.cancel(); - } - -private: - using runner_type = redis::detail::runner; - using health_checker_type = redis::detail::health_checker; - using timer_type = typename runner_type::timer_type; - - template friend struct connect_op; - template friend struct run_check_exec_op; - template friend struct run_check_op; - template friend struct hello_op; - - template - auto async_run_check(Connection& conn, CompletionToken token) - { - return asio::async_compose - < CompletionToken - , void(system::error_code) - >(run_check_op{this, &conn}, token, conn); - } - - template - auto async_run_check_exec(Connection& conn, CompletionToken token) - { - return asio::async_compose - < CompletionToken - , void(system::error_code) - >(run_check_exec_op{this, &conn}, token, conn); - } - - template - auto async_hello(Connection& conn, CompletionToken token) - { - return asio::async_compose - < CompletionToken - , void(system::error_code) - >(hello_op{this, &conn}, token, conn); - } - - void add_hello() - { - if (!cfg_.username.empty() && !cfg_.password.empty() && !cfg_.clientname.empty()) - req_hello_.push("HELLO", "3", "AUTH", cfg_.username, cfg_.password, "SETNAME", cfg_.clientname); - else if (cfg_.username.empty() && cfg_.password.empty() && cfg_.clientname.empty()) - req_hello_.push("HELLO", "3"); - else if (cfg_.clientname.empty()) - req_hello_.push("HELLO", "3", "AUTH", cfg_.username, cfg_.password); - else - req_hello_.push("HELLO", "3", "SETNAME", cfg_.clientname); - - // Subscribe to channels in the same request that sends HELLO - // because it has priority over all other requests. - // TODO: Subscribe to actual channels. - req_hello_.push("SUBSCRIBE", "channel"); - } - - runner_type runner_; - health_checker_type health_checker_; - timer_type reconnect_wait_timer_; - request req_hello_; - generic_response resp_hello_; - connect_config cfg_; - Logger logger_; -}; - -} // detail - -template < - class Socket, - class Logger = logger, - class CompletionToken = asio::default_completion_token_t -> -auto -async_connect( - basic_connection& conn, - connect_config cfg = connect_config{}, - Logger l = logger{}, - CompletionToken token = CompletionToken{}) -{ - using executor_type = typename Socket::executor_type; - using connector_type = detail::connector; - auto ctor = std::make_shared(conn.get_executor(), cfg, l); - return ctor->async_connect(conn, asio::consign(std::move(token), ctor)); -} - -} // boost::redis::experimental - -#endif // BOOST_REDIS_CONNECTOR_HPP diff --git a/include/boost/redis/impl/logger.ipp b/include/boost/redis/impl/logger.ipp new file mode 100644 index 00000000..d2c4db8c --- /dev/null +++ b/include/boost/redis/impl/logger.ipp @@ -0,0 +1,128 @@ +/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com) + * + * Distributed under the Boost Software License, Version 1.0. (See + * accompanying file LICENSE.txt) + */ + +#include +#include +#include +#include + +namespace boost::redis +{ + +void logger::write_prefix() +{ + if (!std::empty(prefix_)) + std::clog << prefix_; +} + +void logger::on_resolve(system::error_code const& ec, asio::ip::tcp::resolver::results_type const& res) +{ + if (level_ < level::info) + return; + + write_prefix(); + + std::clog << "Resolve results: "; + + if (ec) { + std::clog << ec.message() << std::endl; + } else { + auto begin = std::cbegin(res); + auto end = std::cend(res); + + if (begin == end) + return; + + std::clog << begin->endpoint(); + for (auto iter = std::next(begin); iter != end; ++iter) + std::clog << ", " << iter->endpoint(); + } + + std::clog << std::endl; +} + +void logger::on_connect(system::error_code const& ec, asio::ip::tcp::endpoint const& ep) +{ + if (level_ < level::info) + return; + + write_prefix(); + + std::clog << "Connected to endpoint: "; + + if (ec) + std::clog << ec.message() << std::endl; + else + std::clog << ep; + + std::clog << std::endl; +} + +void logger::on_ssl_handshake(system::error_code const& ec) +{ + if (level_ < level::info) + return; + + write_prefix(); + + std::clog << "SSL handshake: " << ec.message() << std::endl; +} + +void logger::on_connection_lost(system::error_code const& ec) +{ + if (level_ < level::info) + return; + + write_prefix(); + + if (ec) + std::clog << "Connection lost: " << ec.message(); + else + std::clog << "Connection lost."; + + std::clog << std::endl; +} + +void +logger::on_write( + system::error_code const& ec, + std::string const& payload) +{ + if (level_ < level::info) + return; + + write_prefix(); + + if (ec) + std::clog << "Write: " << ec.message(); + else + std::clog << "Bytes written: " << std::size(payload); + + std::clog << std::endl; +} + +void +logger::on_hello( + system::error_code const& ec, + generic_response const& resp) +{ + if (level_ < level::info) + return; + + write_prefix(); + + if (ec) { + std::clog << "Hello: " << ec.message(); + if (resp.has_error()) + std::clog << " (" << resp.error().diagnostic << ")"; + } else { + std::clog << "Hello: Success"; + } + + std::clog << std::endl; +} + +} // boost::redis diff --git a/include/boost/redis/logger.hpp b/include/boost/redis/logger.hpp index fd21256e..e3a1cd35 100644 --- a/include/boost/redis/logger.hpp +++ b/include/boost/redis/logger.hpp @@ -7,37 +7,119 @@ #ifndef BOOST_REDIS_LOGGER_HPP #define BOOST_REDIS_LOGGER_HPP +#include #include -#include -#include +#include + +namespace boost::system {class error_code;} namespace boost::redis { -// TODO: Move to ipp file. -// TODO: Implement filter. +/** @brief Logger class + * @ingroup high-level-api + * + * The class can be passed to the connection objects to log to `std::clog` + */ class logger { public: - void on_resolve(system::error_code const& ec, asio::ip::tcp::resolver::results_type const&) - { - // TODO: Print the endpoints - std::clog << "on_resolve: " << ec.message() << std::endl; - } + /** @brief Syslog-like log levels + * @ingroup high-level-api + */ + enum class level + { /// Emergency + emerg, - void on_connect(system::error_code const& ec, asio::ip::tcp::endpoint const&) - { - // TODO: Print the endpoint - std::clog << "on_connect: " << ec.message() << std::endl; - } + /// Alert + alert, - void on_connection_lost() - { - std::clog << "on_connection_lost: " << std::endl; - } + /// Critical + crit, + + /// Error + err, + + /// Warning + warning, - void on_hello(system::error_code const& ec) + /// Notice + notice, + + /// Info + info, + + /// Debug + debug + }; + + /** @brief Constructor + * @ingroup high-level-api + * + * @param l Log level. + */ + logger(level l = level::info) + : level_{l} + {} + + /** @brief Called when the resolve operation completes. + * @ingroup high-level-api + * + * @param ec Error returned by the resolve operation. + * @param res Resolve results. + */ + void on_resolve(system::error_code const& ec, asio::ip::tcp::resolver::results_type const& res); + + /** @brief Called when the connect operation completes. + * @ingroup high-level-api + * + * @param ec Error returned by the connect operation. + * @param ep Endpoint to which the connection connected. + */ + void on_connect(system::error_code const& ec, asio::ip::tcp::endpoint const& ep); + + /** @brief Called when the ssl handshake operation completes. + * @ingroup high-level-api + * + * @param ec Error returned by the handshake operation. + */ + void on_ssl_handshake(system::error_code const& ec); + + /** @brief Called when the connection is lost. + * @ingroup high-level-api + * + * @param ec Error returned when the connection is lost. + */ + void on_connection_lost(system::error_code const& ec); + + /** @brief Called when the write operation completes. + * @ingroup high-level-api + * + * @param ec Error code returned by the write operation. + * @param payload The payload written to the socket. + */ + void on_write(system::error_code const& ec, std::string const& payload); + + /** @brief Called when the `HELLO` request completes. + * @ingroup high-level-api + * + * @param ec Error code returned by the async_exec operation. + * @param resp Response sent by the Redis server. + */ + void on_hello(system::error_code const& ec, generic_response const& resp); + + /** @brief Sets a prefix to every log message + * @ingroup high-level-api + * + * @param prefix The prefix. + */ + void set_prefix(std::string_view prefix) { - std::clog << "on_hello: " << ec.message() << std::endl; + prefix_ = prefix; } + +private: + void write_prefix(); + level level_; + std::string_view prefix_; }; } // boost::redis diff --git a/include/boost/redis/operation.hpp b/include/boost/redis/operation.hpp index 58f3c6de..d37145c7 100644 --- a/include/boost/redis/operation.hpp +++ b/include/boost/redis/operation.hpp @@ -9,19 +9,29 @@ namespace boost::redis { -/** \brief Connection operations that can be cancelled. - * \ingroup high-level-api +/** @brief Connection operations that can be cancelled. + * @ingroup high-level-api * * The operations listed below can be passed to the * `boost::redis::connection::cancel` member function. */ enum class operation { + /// Resolve operation. + resolve, + /// Connect operation. + connect, + /// SSL handshake operation. + ssl_handshake, /// Refers to `connection::async_exec` operations. exec, /// Refers to `connection::async_run` operations. run, /// Refers to `connection::async_receive` operations. receive, + /// Cancels reconnection. + reconnection, + /// Health check operation. + health_check, /// Refers to all operations. all, }; diff --git a/include/boost/redis/request.hpp b/include/boost/redis/request.hpp index 69f269f0..08006b8a 100644 --- a/include/boost/redis/request.hpp +++ b/include/boost/redis/request.hpp @@ -12,6 +12,7 @@ #include #include +#include // NOTE: For some commands like hset it would be a good idea to assert // the value type is a pair. @@ -138,12 +139,14 @@ class request { template void push(std::string_view cmd, Ts const&... args) { + auto const size_before = std::size(payload_); auto constexpr pack_size = sizeof...(Ts); resp3::add_header(payload_, resp3::type::array, 1 + pack_size); resp3::add_bulk(payload_, cmd); resp3::add_bulk(payload_, std::tie(std::forward(args)...)); + auto const size_after = std::size(payload_); - check_cmd(cmd); + check_cmd(cmd, size_after - size_before); } /** @brief Appends a new command to the end of the request. @@ -191,6 +194,7 @@ class request { if (begin == end) return; + auto const size_before = std::size(payload_); auto constexpr size = resp3::bulk_counter::size; auto const distance = std::distance(begin, end); resp3::add_header(payload_, resp3::type::array, 2 + size * distance); @@ -200,7 +204,9 @@ class request { for (; begin != end; ++begin) resp3::add_bulk(payload_, *begin); - check_cmd(cmd); + auto const size_after = std::size(payload_); + + check_cmd(cmd, size_after - size_before); } /** @brief Appends a new command to the end of the request. @@ -243,6 +249,7 @@ class request { if (begin == end) return; + auto const size_before = std::size(payload_); auto constexpr size = resp3::bulk_counter::size; auto const distance = std::distance(begin, end); resp3::add_header(payload_, resp3::type::array, 1 + size * distance); @@ -251,7 +258,9 @@ class request { for (; begin != end; ++begin) resp3::add_bulk(payload_, *begin); - check_cmd(cmd); + auto const size_after = std::size(payload_); + + check_cmd(cmd, size_after - size_before); } /** @brief Appends a new command to the end of the request. @@ -299,13 +308,18 @@ class request { } private: - void check_cmd(std::string_view cmd) + void check_cmd(std::string_view cmd, std::size_t n) { if (!detail::has_response(cmd)) ++commands_; - if (cmd == "HELLO") + if (cmd == "HELLO") { has_hello_priority_ = cfg_.hello_with_priority; + if (has_hello_priority_) { + auto const shift = std::size(payload_) - n; + std::rotate(std::begin(payload_), std::begin(payload_) + shift, std::end(payload_)); + } + } } config cfg_; diff --git a/include/boost/redis/resp3/impl/serialization.ipp b/include/boost/redis/resp3/impl/serialization.ipp index b4efc6d2..5fcbb77f 100644 --- a/include/boost/redis/resp3/impl/serialization.ipp +++ b/include/boost/redis/resp3/impl/serialization.ipp @@ -21,7 +21,6 @@ void boost_redis_to_bulk(std::string& payload, std::string_view data) void add_header(std::string& payload, type t, std::size_t size) { - // TODO: Call reserve. auto const str = std::to_string(size); payload += to_code(t); diff --git a/include/boost/redis/resp3/serialization.hpp b/include/boost/redis/resp3/serialization.hpp index b1a8e38c..5d36db3f 100644 --- a/include/boost/redis/resp3/serialization.hpp +++ b/include/boost/redis/resp3/serialization.hpp @@ -84,7 +84,6 @@ void add_header(std::string& payload, type t, std::size_t size); template void add_bulk(std::string& payload, T const& data) { - // TODO: Call reserve. add_bulk_impl::add(payload, data); } diff --git a/include/boost/redis/run.hpp b/include/boost/redis/run.hpp deleted file mode 100644 index 5da3dabb..00000000 --- a/include/boost/redis/run.hpp +++ /dev/null @@ -1,65 +0,0 @@ -/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com) - * - * Distributed under the Boost Software License, Version 1.0. (See - * accompanying file LICENSE.txt) - */ - -#ifndef BOOST_REDIS_RUN_HPP -#define BOOST_REDIS_RUN_HPP - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace boost::redis -{ - -/** @brief Call async_run on the connection. - * @ingroup high-level-api - * - * This is a facility function that - * 1. Resoves the endpoint. - * 2. Connects to one of the endpoints from 1. - * 3. Calls async_run on the underlying connection. - * - * @param conn A connection to Redis. - * @param host Redis host to connect to. - * @param port Redis port to connect to. - * @param resolve_timeout Time the resolve operation is allowed to take. - * @param connect_timeout Time the connect operation is allowed to take. - * @param token Completion token. - */ -template < - class Socket, - class Logger = logger, - class CompletionToken = asio::default_completion_token_t -> -auto -async_run( - basic_connection& conn, - address addr = address{"127.0.0.1", "6379"}, - std::chrono::steady_clock::duration resolve_timeout = std::chrono::seconds{10}, - std::chrono::steady_clock::duration connect_timeout = std::chrono::seconds{10}, - Logger l = Logger{}, - CompletionToken token = CompletionToken{}) -{ - using executor_type = typename Socket::executor_type; - using runner_type = detail::runner; - auto runner = std::make_shared(conn.get_executor(), addr, l); - - return - runner->async_run( - conn, - resolve_timeout, - connect_timeout, - asio::consign(std::move(token), runner)); -} - -} // boost::redis - -#endif // BOOST_REDIS_RUN_HPP diff --git a/include/boost/redis/src.hpp b/include/boost/redis/src.hpp index bec18b70..ef978afc 100644 --- a/include/boost/redis/src.hpp +++ b/include/boost/redis/src.hpp @@ -5,6 +5,7 @@ */ #include +#include #include #include #include diff --git a/include/boost/redis/ssl/connection.hpp b/include/boost/redis/ssl/connection.hpp index e1a895ac..37da52a8 100644 --- a/include/boost/redis/ssl/connection.hpp +++ b/include/boost/redis/ssl/connection.hpp @@ -8,7 +8,11 @@ #define BOOST_REDIS_SSL_CONNECTION_HPP #include +#include +#include +#include #include +#include #include #include @@ -56,7 +60,10 @@ class basic_connection> : explicit basic_connection(executor_type ex, asio::ssl::context& ctx) : base_type{ex} - , stream_{ex, ctx} + , ctx_{&ctx} + , reconn_{ex} + , runner_{ex, {}} + , stream_{std::make_unique(ex, ctx)} { } /// Constructor @@ -66,28 +73,40 @@ class basic_connection> : { } /// Returns the associated executor. - auto get_executor() {return stream_.get_executor();} + auto get_executor() {return stream_->get_executor();} /// Reset the underlying stream. - void reset_stream(asio::ssl::context& ctx) + void reset_stream() { - stream_ = next_layer_type{stream_.get_executor(), ctx}; + if (stream_->next_layer().is_open()) { + stream_->next_layer().close(); + stream_ = std::make_unique(stream_->get_executor(), *ctx_); + } } /// Returns a reference to the next layer. - auto& next_layer() noexcept { return stream_; } + auto& next_layer() noexcept { return *stream_; } /// Returns a const reference to the next layer. - auto const& next_layer() const noexcept { return stream_; } + auto const& next_layer() const noexcept { return *stream_; } /** @brief Establishes a connection with the Redis server asynchronously. * * See redis::connection::async_run for more information. */ - template > - auto async_run(CompletionToken token = CompletionToken{}) + template < + class Logger = logger, + class CompletionToken = asio::default_completion_token_t> + auto + async_run( + config const& cfg = {}, + Logger l = Logger{}, + CompletionToken token = CompletionToken{}) { - return base_type::async_run(std::move(token)); + reconn_.set_wait_interval(cfg.reconnect_wait_interval); + runner_.set_config(cfg); + l.set_prefix(runner_.get_config().log_prefix); + return reconn_.async_run(*this, l, std::move(token)); } /** @brief Executes a command on the Redis server asynchronously. @@ -124,9 +143,13 @@ class basic_connection> : * See redis::connection::cancel for more information. */ auto cancel(operation op = operation::all) -> std::size_t - { return base_type::cancel(op); } + { + reconn_.cancel(op); + runner_.cancel(op); + return base_type::cancel(op); + } - auto& lowest_layer() noexcept { return stream_.lowest_layer(); } + auto& lowest_layer() noexcept { return stream_->lowest_layer(); } /// Sets the maximum size of the read buffer. void set_max_buffer_read_size(std::size_t max_read_size) noexcept @@ -143,22 +166,43 @@ class basic_connection> : void reserve(std::size_t read, std::size_t write) { base_type::reserve(read, write); } + /// Returns true if the connection was canceled. + bool is_cancelled() const noexcept + { return reconn_.is_cancelled();} + private: + using runner_type = redis::detail::runner; + using reconnection_type = redis::detail::basic_reconnection; using this_type = basic_connection; + template + auto async_run_one(Logger l, CompletionToken token) + { return runner_.async_run(*this, l, std::move(token)); } + + template + auto async_run_impl(Logger l, CompletionToken token) + { return base_type::async_run_impl(l, std::move(token)); } + template friend class redis::detail::connection_base; + template friend class redis::detail::read_next_op; template friend struct redis::detail::exec_op; - template friend struct redis::detail::exec_read_op; - template friend struct detail::receive_op; - template friend struct redis::detail::run_op; - template friend struct redis::detail::writer_op; + template friend struct redis::detail::receive_op; + template friend struct redis::detail::run_op; + template friend struct redis::detail::writer_op; template friend struct redis::detail::reader_op; - template friend struct detail::wait_receive_op; + template friend struct redis::detail::wait_receive_op; + template friend struct redis::detail::run_all_op; + template friend struct redis::detail::reconnection_op; + + auto is_open() const noexcept { return stream_->next_layer().is_open(); } - auto is_open() const noexcept { return stream_.next_layer().is_open(); } - void close() { stream_.next_layer().close(); } + void close() + { reset_stream(); } - next_layer_type stream_; + asio::ssl::context* ctx_; + reconnection_type reconn_; + runner_type runner_; + std::unique_ptr stream_; }; /** \brief A connection that uses a boost::asio::ssl::stream. diff --git a/include/boost/redis/ssl/detail/handshaker.hpp b/include/boost/redis/ssl/detail/handshaker.hpp new file mode 100644 index 00000000..987c6ef3 --- /dev/null +++ b/include/boost/redis/ssl/detail/handshaker.hpp @@ -0,0 +1,124 @@ +/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com) + * + * Distributed under the Boost Software License, Version 1.0. (See + * accompanying file LICENSE.txt) + */ + +#ifndef BOOST_REDIS_SSL_CONNECTOR_HPP +#define BOOST_REDIS_SSL_CONNECTOR_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace boost::redis::ssl::detail +{ + +template +struct handshake_op { + Handshaker* hsher_ = nullptr; + Stream* stream_ = nullptr; + asio::coroutine coro{}; + + template + void operator()( Self& self + , std::array const& order = {} + , system::error_code const& ec1 = {} + , system::error_code const& ec2 = {}) + { + BOOST_ASIO_CORO_REENTER (coro) + { + hsher_->timer_.expires_after(hsher_->timeout_); + + BOOST_ASIO_CORO_YIELD + asio::experimental::make_parallel_group( + [this](auto token) { return stream_->async_handshake(asio::ssl::stream_base::client, token); }, + [this](auto token) { return hsher_->timer_.async_wait(token);} + ).async_wait( + asio::experimental::wait_for_one(), + std::move(self)); + + if (is_cancelled(self)) { + self.complete(asio::error::operation_aborted); + return; + } + + switch (order[0]) { + case 0: { + self.complete(ec1); + } break; + case 1: + { + if (ec2) { + self.complete(ec2); + } else { + self.complete(error::ssl_handshake_timeout); + } + } break; + + default: BOOST_ASSERT(false); + } + } + } +}; + +template +class handshaker { +public: + using timer_type = + asio::basic_waitable_timer< + std::chrono::steady_clock, + asio::wait_traits, + Executor>; + + handshaker(Executor ex) + : timer_{ex} + {} + + template + auto + async_handshake(Stream& stream, CompletionToken&& token) + { + return asio::async_compose + < CompletionToken + , void(system::error_code) + >(handshake_op{this, &stream}, token, timer_); + } + + std::size_t cancel(operation op) + { + switch (op) { + case operation::ssl_handshake: + case operation::all: + timer_.cancel(); + break; + default: /* ignore */; + } + + return 0; + } + + constexpr bool is_dummy() const noexcept + {return false;} + + void set_config(config const& cfg) + { timeout_ = cfg.ssl_handshake_timeout; } + +private: + template friend struct handshake_op; + + timer_type timer_; + std::chrono::steady_clock::duration timeout_; +}; + +} // boost::redis::ssl::detail + +#endif // BOOST_REDIS_SSL_CONNECTOR_HPP diff --git a/tests/common.cpp b/tests/common.cpp new file mode 100644 index 00000000..6583ffd5 --- /dev/null +++ b/tests/common.cpp @@ -0,0 +1,29 @@ +#include "common.hpp" +#include +#include + +#include + +struct run_callback { + std::shared_ptr conn; + boost::redis::operation op; + boost::system::error_code expected; + + void operator()(boost::system::error_code const& ec) const + { + std::cout << "async_run: " << ec.message() << std::endl; + //BOOST_CHECK_EQUAL(ec, expected); + conn->cancel(op); + } +}; + +void +run( + std::shared_ptr conn, + boost::redis::config cfg, + boost::system::error_code ec, + boost::redis::operation op) +{ + conn->async_run(cfg, {}, run_callback{conn, op, ec}); +} + diff --git a/tests/common.hpp b/tests/common.hpp index c7d1e5c3..3f8909d2 100644 --- a/tests/common.hpp +++ b/tests/common.hpp @@ -3,10 +3,23 @@ #include #include #include +#include +#include +#include -#ifdef BOOST_ASIO_HAS_CO_AWAIT namespace net = boost::asio; + +#ifdef BOOST_ASIO_HAS_CO_AWAIT + inline auto redir(boost::system::error_code& ec) - { return net::redirect_error(net::use_awaitable, ec); } + { return net::redirect_error(boost::asio::use_awaitable, ec); } #endif // BOOST_ASIO_HAS_CO_AWAIT + +void +run( + std::shared_ptr conn, + boost::redis::config cfg = {}, + boost::system::error_code ec = boost::asio::error::operation_aborted, + boost::redis::operation op = boost::redis::operation::receive); + diff --git a/tests/conn_check_health.cpp b/tests/conn_check_health.cpp index 87d26949..34a22496 100644 --- a/tests/conn_check_health.cpp +++ b/tests/conn_check_health.cpp @@ -4,9 +4,7 @@ * accompanying file LICENSE.txt) */ -#include -#include -#include +#include #include #define BOOST_TEST_MODULE check-health #include @@ -15,58 +13,59 @@ #include namespace net = boost::asio; +namespace redis = boost::redis; using error_code = boost::system::error_code; using connection = boost::redis::connection; using boost::redis::request; using boost::redis::ignore; using boost::redis::operation; using boost::redis::generic_response; -using boost::redis::async_check_health; -using boost::redis::async_run; using boost::redis::logger; -using boost::redis::address; -using namespace std::chrono_literals; +using redis::config; + +// TODO: Test cancel(health_check) std::chrono::seconds const interval{1}; struct push_callback { - connection* conn; + connection* conn1; connection* conn2; - generic_response* resp; - request* req; + generic_response* resp2; + request* req1; int i = 0; + boost::asio::coroutine coro{}; void operator()(error_code ec = {}, std::size_t = 0) { - ++i; - if (ec) { - std::clog << "Exiting." << std::endl; - return; - } - - if (resp->value().empty()) { - // First call - BOOST_TEST(!ec); - conn2->async_receive(*resp, *this); - } else if (i == 5) { - std::clog << "Pausing the server" << std::endl; - // Pause the redis server to test if the health-check exits. - conn->async_exec(*req, ignore, [](auto ec, auto) { - std::clog << "Pausing callback> " << ec.message() << std::endl; + BOOST_ASIO_CORO_REENTER (coro) for (;;) + { + resp2->value().clear(); + BOOST_ASIO_CORO_YIELD + conn2->async_receive(*resp2, *this); + if (ec) { + std::clog << "Exiting." << std::endl; + return; + } + + BOOST_TEST(resp2->has_value()); + BOOST_TEST(!resp2->value().empty()); + std::clog << "Event> " << resp2->value().front().value << std::endl; + + ++i; + + if (i == 5) { + std::clog << "Pausing the server" << std::endl; + // Pause the redis server to test if the health-check exits. + BOOST_ASIO_CORO_YIELD + conn1->async_exec(*req1, ignore, *this); + std::clog << "After pausing> " << ec.message() << std::endl; // Don't know in CI we are getting: Got RESP3 simple-error. //BOOST_TEST(!ec); - }); - conn2->cancel(operation::run); - conn2->cancel(operation::receive); - } else { - BOOST_TEST(!ec); - // Expect 3 pongs and pause the clients so check-health exists - // without error. - BOOST_TEST(resp->has_value()); - BOOST_TEST(!resp->value().empty()); - std::clog << "Event> " << resp->value().front().value << std::endl; - resp->value().clear(); - conn2->async_receive(*resp, *this); + conn2->cancel(operation::run); + conn2->cancel(operation::receive); + conn2->cancel(operation::reconnection); + return; + } } }; }; @@ -75,48 +74,51 @@ BOOST_AUTO_TEST_CASE(check_health) { net::io_context ioc; - connection conn{ioc}; - // It looks like client pause does not work for clients that are - // sending MONITOR. I will therefore open a second connection. - connection conn2{ioc}; + connection conn1{ioc}; + conn1.cancel(operation::reconnection); - std::string const msg = "test-check-health"; + request req1; + req1.push("CLIENT", "PAUSE", "10000", "ALL"); - bool seen = false; - async_check_health(conn, msg, interval, [&](auto ec) { - BOOST_CHECK_EQUAL(ec, boost::redis::error::pong_timeout); - std::cout << "async_check_health: completed: " << ec.message() << std::endl; - seen = true; + config cfg1; + cfg1.health_check_id = "conn1"; + error_code res1; + conn1.async_run(cfg1, {}, [&](auto ec) { + std::cout << "async_run 1 completed: " << ec.message() << std::endl; + res1 = ec; }); - request req; - req.push("HELLO", 3); - req.push("MONITOR"); + //-------------------------------- - conn2.async_exec(req, ignore, [](auto ec, auto) { - std::cout << "A" << std::endl; - BOOST_TEST(!ec); + // It looks like client pause does not work for clients that are + // sending MONITOR. I will therefore open a second connection. + connection conn2{ioc}; + + config cfg2; + cfg2.health_check_id = "conn2"; + error_code res2; + conn2.async_run(cfg2, {}, [&](auto ec){ + std::cout << "async_run 2 completed: " << ec.message() << std::endl; + res2 = ec; }); request req2; - req2.push("HELLO", "3"); - req2.push("CLIENT", "PAUSE", "5000", "ALL"); - - generic_response resp; - push_callback{&conn, &conn2, &resp, &req2}(); // Starts reading pushes. + req2.push("MONITOR"); + generic_response resp2; - async_run(conn, address{}, 10s, 10s, logger{}, [](auto ec){ - std::cout << "B" << std::endl; - BOOST_TEST(!!ec); + conn2.async_exec(req2, ignore, [](auto ec, auto) { + std::cout << "async_exec: " << std::endl; + BOOST_TEST(!ec); }); - async_run(conn2, address{}, 10s, 10s, logger{}, [](auto ec){ - std::cout << "C" << std::endl; - BOOST_TEST(!!ec); - }); + //-------------------------------- + + push_callback{&conn1, &conn2, &resp2, &req1}(); // Starts reading pushes. ioc.run(); - BOOST_TEST(seen); + + BOOST_TEST(!!res1); + BOOST_TEST(!!res2); } diff --git a/tests/conn_echo_stress.cpp b/tests/conn_echo_stress.cpp index 8b815837..3adb1a0f 100644 --- a/tests/conn_echo_stress.cpp +++ b/tests/conn_echo_stress.cpp @@ -4,7 +4,7 @@ * accompanying file LICENSE.txt) */ -#include +#include #include #include #include @@ -24,10 +24,9 @@ using boost::redis::request; using boost::redis::response; using boost::redis::ignore; using boost::redis::ignore_t; -using boost::redis::async_run; -using boost::redis::address; -using connection = boost::asio::use_awaitable_t<>::as_default_on_t; -using namespace std::chrono_literals; +using boost::redis::logger; +using boost::redis::config; +using boost::redis::connection; auto push_consumer(std::shared_ptr conn, int expected) -> net::awaitable { @@ -38,10 +37,7 @@ auto push_consumer(std::shared_ptr conn, int expected) -> net::await break; } - request req; - req.push("HELLO", 3); - req.push("QUIT"); - co_await conn->async_exec(req, ignore); + conn->cancel(); } auto echo_session(std::shared_ptr conn, std::string id, int n) -> net::awaitable @@ -81,8 +77,7 @@ auto async_echo_stress() -> net::awaitable net::co_spawn(ex, echo_session(conn, std::to_string(i), msgs), net::detached); - address addr; - co_await async_run(*conn, addr); + run(conn); } BOOST_AUTO_TEST_CASE(echo_stress) diff --git a/tests/conn_exec.cpp b/tests/conn_exec.cpp index a4dcfffc..c9eaeac2 100644 --- a/tests/conn_exec.cpp +++ b/tests/conn_exec.cpp @@ -4,9 +4,7 @@ * accompanying file LICENSE.txt) */ -#include -#include -#include +#include #include #define BOOST_TEST_MODULE conn-exec #include @@ -20,17 +18,14 @@ // container. namespace net = boost::asio; -using error_code = boost::system::error_code; -using connection = boost::redis::connection; +using boost::redis::connection; using boost::redis::request; using boost::redis::response; using boost::redis::ignore; -using boost::redis::ignore_t; -using boost::redis::async_run; -using boost::redis::logger; -using boost::redis::address; -using namespace std::chrono_literals; +using boost::redis::operation; +// Sends three requests where one of them has a hello with a priority +// set, which means it should be executed first. BOOST_AUTO_TEST_CASE(hello_priority) { request req1; @@ -40,7 +35,6 @@ BOOST_AUTO_TEST_CASE(hello_priority) req2.get_config().hello_with_priority = false; req2.push("HELLO", 3); req2.push("PING", "req2"); - req2.push("QUIT"); request req3; req3.get_config().hello_with_priority = true; @@ -49,60 +43,63 @@ BOOST_AUTO_TEST_CASE(hello_priority) net::io_context ioc; - connection conn{ioc}; + auto conn = std::make_shared(ioc); bool seen1 = false; bool seen2 = false; bool seen3 = false; - conn.async_exec(req1, ignore, [&](auto ec, auto){ - std::cout << "bbb" << std::endl; + conn->async_exec(req1, ignore, [&](auto ec, auto){ + // Second callback to the called. + std::cout << "req1" << std::endl; BOOST_TEST(!ec); BOOST_TEST(!seen2); BOOST_TEST(seen3); seen1 = true; }); - conn.async_exec(req2, ignore, [&](auto ec, auto){ - std::cout << "ccc" << std::endl; + + conn->async_exec(req2, ignore, [&](auto ec, auto){ + // Last callback to the called. + std::cout << "req2" << std::endl; BOOST_TEST(!ec); BOOST_TEST(seen1); BOOST_TEST(seen3); seen2 = true; + conn->cancel(operation::run); + conn->cancel(operation::reconnection); }); - conn.async_exec(req3, ignore, [&](auto ec, auto){ - std::cout << "ddd" << std::endl; + + conn->async_exec(req3, ignore, [&](auto ec, auto){ + // Callback that will be called first. + std::cout << "req3" << std::endl; BOOST_TEST(!ec); BOOST_TEST(!seen1); BOOST_TEST(!seen2); seen3 = true; }); - async_run(conn, address{}, 10s, 10s, logger{}, [](auto ec){ - BOOST_TEST(!ec); - }); - + run(conn); ioc.run(); } +// Tries to receive a string in an int and gets an error. BOOST_AUTO_TEST_CASE(wrong_response_data_type) { request req; - req.push("HELLO", 3); - req.push("QUIT"); + req.push("PING"); // Wrong data type. - response resp; + response resp; net::io_context ioc; - connection conn{ioc}; + auto conn = std::make_shared(ioc); - conn.async_exec(req, resp, [](auto ec, auto){ + conn->async_exec(req, resp, [conn](auto ec, auto){ BOOST_CHECK_EQUAL(ec, boost::redis::error::not_a_number); - }); - async_run(conn, address{}, 10s, 10s, logger{}, [](auto ec){ - BOOST_CHECK_EQUAL(ec, boost::asio::error::basic_errors::operation_aborted); + conn->cancel(operation::reconnection); }); + run(conn); ioc.run(); } @@ -110,13 +107,13 @@ BOOST_AUTO_TEST_CASE(cancel_request_if_not_connected) { request req; req.get_config().cancel_if_not_connected = true; - req.push("HELLO", 3); req.push("PING"); net::io_context ioc; auto conn = std::make_shared(ioc); - conn->async_exec(req, ignore, [](auto ec, auto){ + conn->async_exec(req, ignore, [conn](auto ec, auto){ BOOST_CHECK_EQUAL(ec, boost::redis::error::not_connected); + conn->cancel(); }); ioc.run(); diff --git a/tests/conn_exec_cancel.cpp b/tests/conn_exec_cancel.cpp index 3425b438..882698f4 100644 --- a/tests/conn_exec_cancel.cpp +++ b/tests/conn_exec_cancel.cpp @@ -4,8 +4,7 @@ * accompanying file LICENSE.txt) */ -#include -#include +#include #include #define BOOST_TEST_MODULE conn-exec-cancel #include @@ -17,89 +16,37 @@ #ifdef BOOST_ASIO_HAS_CO_AWAIT #include -// NOTE1: Sends hello separately. I have observed that if hello and +// NOTE1: I have observed that if hello and // blpop are sent toguether, Redis will send the response of hello -// right away, not waiting for blpop. That is why we have to send it -// separately here. +// right away, not waiting for blpop. namespace net = boost::asio; using error_code = boost::system::error_code; using namespace net::experimental::awaitable_operators; using boost::redis::operation; +using boost::redis::error; using boost::redis::request; using boost::redis::response; using boost::redis::generic_response; using boost::redis::ignore; using boost::redis::ignore_t; -using boost::redis::async_run; +using boost::redis::config; using boost::redis::logger; -using boost::redis::address; -using connection = boost::asio::use_awaitable_t<>::as_default_on_t; +using boost::redis::connection; using namespace std::chrono_literals; -auto async_ignore_explicit_cancel_of_req_written() -> net::awaitable -{ - auto ex = co_await net::this_coro::executor; - - generic_response gresp; - auto conn = std::make_shared(ex); - - async_run(*conn, address{}, 10s, 10s, logger{}, [conn](auto ec) { - std::cout << "async_run: " << ec.message() << std::endl; - BOOST_TEST(!ec); - }); - - net::steady_timer st{ex}; - st.expires_after(std::chrono::seconds{1}); - - // See NOTE1. - request req0; - req0.push("HELLO", 3); - co_await conn->async_exec(req0, gresp, net::use_awaitable); - - request req1; - req1.push("BLPOP", "any", 3); - - // Should not be canceled. - bool seen = false; - conn->async_exec(req1, gresp, [&](auto ec, auto) mutable{ - std::cout << "async_exec (1): " << ec.message() << std::endl; - BOOST_TEST(!ec); - seen = true; - }); - - // Will complete while BLPOP is pending. - boost::system::error_code ec1; - co_await st.async_wait(net::redirect_error(net::use_awaitable, ec1)); - conn->cancel(operation::exec); - - BOOST_TEST(!ec1); - - request req3; - req3.push("QUIT"); - - // Test whether the connection remains usable after a call to - // cancel(exec). - co_await conn->async_exec(req3, gresp, net::redirect_error(net::use_awaitable, ec1)); - - BOOST_TEST(!ec1); - BOOST_TEST(seen); -} - -auto ignore_implicit_cancel_of_req_written() -> net::awaitable +auto implicit_cancel_of_req_written() -> net::awaitable { auto ex = co_await net::this_coro::executor; auto conn = std::make_shared(ex); - // Calls async_run separately from the group of ops below to avoid - // having it canceled when the timer fires. - async_run(*conn, address{}, 10s, 10s, logger{}, [conn](auto ec) { - BOOST_CHECK_EQUAL(ec, net::error::basic_errors::operation_aborted); - }); + config cfg; + cfg.health_check_interval = std::chrono::seconds{0}; + run(conn, cfg); // See NOTE1. request req0; - req0.push("HELLO", 3); + req0.push("PING"); co_await conn->async_exec(req0, ignore, net::use_awaitable); // Will be cancelled after it has been written but before the @@ -110,33 +57,33 @@ auto ignore_implicit_cancel_of_req_written() -> net::awaitable net::steady_timer st{ex}; st.expires_after(std::chrono::seconds{1}); + // Achieves implicit cancellation when the timer fires. boost::system::error_code ec1, ec2; co_await ( conn->async_exec(req1, ignore, redir(ec1)) || st.async_wait(redir(ec2)) ); - BOOST_CHECK_EQUAL(ec1, net::error::basic_errors::operation_aborted); - BOOST_TEST(!ec2); -} + conn->cancel(); -BOOST_AUTO_TEST_CASE(test_ignore_explicit_cancel_of_req_written) -{ - start(async_ignore_explicit_cancel_of_req_written()); + // I have observed this produces terminal cancellation so it can't + // be ignored, an error is expected. + BOOST_CHECK_EQUAL(ec1, net::error::operation_aborted); + BOOST_TEST(!ec2); } BOOST_AUTO_TEST_CASE(test_ignore_implicit_cancel_of_req_written) { - start(ignore_implicit_cancel_of_req_written()); + start(implicit_cancel_of_req_written()); } BOOST_AUTO_TEST_CASE(test_cancel_of_req_written_on_run_canceled) { net::io_context ioc; - connection conn{ioc}; + auto conn = std::make_shared(ioc); request req0; - req0.push("HELLO", 3); + req0.push("PING"); // Sends a request that will be blocked forever, so we can test // canceling it while waiting for a response. @@ -147,26 +94,27 @@ BOOST_AUTO_TEST_CASE(test_cancel_of_req_written_on_run_canceled) auto c1 = [&](auto ec, auto) { - BOOST_CHECK_EQUAL(ec, net::error::basic_errors::operation_aborted); + BOOST_CHECK_EQUAL(ec, net::error::operation_aborted); }; auto c0 = [&](auto ec, auto) { BOOST_TEST(!ec); - conn.async_exec(req1, ignore, c1); + conn->async_exec(req1, ignore, c1); }; - conn.async_exec(req0, ignore, c0); + conn->async_exec(req0, ignore, c0); - async_run(conn, address{}, 10s, 10s, logger{}, [](auto ec){ - BOOST_CHECK_EQUAL(ec, net::error::basic_errors::operation_aborted); - }); + config cfg; + cfg.health_check_interval = std::chrono::seconds{5}; + run(conn); net::steady_timer st{ioc}; st.expires_after(std::chrono::seconds{1}); st.async_wait([&](auto ec){ BOOST_TEST(!ec); - conn.cancel(operation::run); + conn->cancel(operation::run); + conn->cancel(operation::reconnection); }); ioc.run(); diff --git a/tests/conn_exec_cancel2.cpp b/tests/conn_exec_cancel2.cpp new file mode 100644 index 00000000..83b383c4 --- /dev/null +++ b/tests/conn_exec_cancel2.cpp @@ -0,0 +1,96 @@ +/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com) + * + * Distributed under the Boost Software License, Version 1.0. (See + * accompanying file LICENSE.txt) + */ + +#include +#include +#define BOOST_TEST_MODULE conn-exec-cancel +#include +#include "common.hpp" +#include "../examples/start.hpp" +#include +#include + +#ifdef BOOST_ASIO_HAS_CO_AWAIT +#include + +// NOTE1: Sends hello separately. I have observed that if hello and +// blpop are sent toguether, Redis will send the response of hello +// right away, not waiting for blpop. That is why we have to send it +// separately. + +namespace net = boost::asio; +using error_code = boost::system::error_code; +using namespace net::experimental::awaitable_operators; +using boost::redis::operation; +using boost::redis::request; +using boost::redis::response; +using boost::redis::generic_response; +using boost::redis::ignore; +using boost::redis::ignore_t; +using boost::redis::config; +using boost::redis::logger; +using boost::redis::connection; +using namespace std::chrono_literals; + +auto async_ignore_explicit_cancel_of_req_written() -> net::awaitable +{ + auto ex = co_await net::this_coro::executor; + + generic_response gresp; + auto conn = std::make_shared(ex); + + run(conn); + + net::steady_timer st{ex}; + st.expires_after(std::chrono::seconds{1}); + + // See NOTE1. + request req0; + req0.push("PING", "async_ignore_explicit_cancel_of_req_written"); + co_await conn->async_exec(req0, gresp, net::use_awaitable); + + request req1; + req1.push("BLPOP", "any", 3); + + bool seen = false; + conn->async_exec(req1, gresp, [&](auto ec, auto) mutable{ + // No error should occur since the cancelation should be + // ignored. + std::cout << "async_exec (1): " << ec.message() << std::endl; + BOOST_TEST(!ec); + seen = true; + }); + + // Will complete while BLPOP is pending. + boost::system::error_code ec1; + co_await st.async_wait(net::redirect_error(net::use_awaitable, ec1)); + conn->cancel(operation::exec); + + BOOST_TEST(!ec1); + + request req3; + req3.push("PING"); + + // Test whether the connection remains usable after a call to + // cancel(exec). + co_await conn->async_exec(req3, gresp, net::redirect_error(net::use_awaitable, ec1)); + conn->cancel(); + + BOOST_TEST(!ec1); + BOOST_TEST(seen); +} + +BOOST_AUTO_TEST_CASE(test_ignore_explicit_cancel_of_req_written) +{ + start(async_ignore_explicit_cancel_of_req_written()); +} + +#else +BOOST_AUTO_TEST_CASE(dummy) +{ + BOOST_TEST(true); +} +#endif diff --git a/tests/conn_exec_error.cpp b/tests/conn_exec_error.cpp index 93ae5bff..45b9de2c 100644 --- a/tests/conn_exec_error.cpp +++ b/tests/conn_exec_error.cpp @@ -4,7 +4,7 @@ * accompanying file LICENSE.txt) */ -#include +#include #include #include #define BOOST_TEST_MODULE conn-exec-error @@ -24,9 +24,9 @@ using boost::redis::generic_response; using boost::redis::ignore; using boost::redis::ignore_t; using boost::redis::error; -using boost::redis::async_run; using boost::redis::logger; -using boost::redis::address; +using boost::redis::operation; +using redis::config; using namespace std::chrono_literals; BOOST_AUTO_TEST_CASE(no_ignore_error) @@ -38,16 +38,16 @@ BOOST_AUTO_TEST_CASE(no_ignore_error) net::io_context ioc; - connection conn{ioc}; + auto conn = std::make_shared(ioc); - conn.async_exec(req, ignore, [&](auto ec, auto){ + conn->async_exec(req, ignore, [&](auto ec, auto){ BOOST_CHECK_EQUAL(ec, error::resp3_simple_error); - conn.cancel(redis::operation::run); - }); - async_run(conn, address{}, 10s, 10s, logger{}, [](auto ec){ - BOOST_CHECK_EQUAL(ec, boost::asio::error::basic_errors::operation_aborted); + conn->cancel(operation::run); + conn->cancel(operation::reconnection); }); + run(conn); + ioc.run(); } @@ -64,10 +64,10 @@ BOOST_AUTO_TEST_CASE(has_diagnostic) net::io_context ioc; - connection conn{ioc}; + auto conn = std::make_shared(ioc); response resp; - conn.async_exec(req, resp, [&](auto ec, auto){ + conn->async_exec(req, resp, [&](auto ec, auto){ BOOST_TEST(!ec); // HELLO @@ -81,12 +81,12 @@ BOOST_AUTO_TEST_CASE(has_diagnostic) BOOST_TEST(std::get<1>(resp).has_value()); BOOST_CHECK_EQUAL(std::get<1>(resp).value(), "Barra do Una"); - conn.cancel(redis::operation::run); - }); - async_run(conn, address{}, 10s, 10s, logger{}, [](auto ec){ - BOOST_CHECK_EQUAL(ec, boost::asio::error::basic_errors::operation_aborted); + conn->cancel(operation::run); + conn->cancel(operation::reconnection); }); + run(conn); + ioc.run(); } @@ -106,14 +106,15 @@ BOOST_AUTO_TEST_CASE(resp3_error_in_cmd_pipeline) response resp2; net::io_context ioc; - connection conn{ioc}; + auto conn = std::make_shared(ioc); auto c2 = [&](auto ec, auto) { BOOST_TEST(!ec); BOOST_TEST(std::get<0>(resp2).has_value()); BOOST_CHECK_EQUAL(std::get<0>(resp2).value(), "req2-msg1"); - conn.cancel(redis::operation::run); + conn->cancel(operation::run); + conn->cancel(operation::reconnection); }; auto c1 = [&](auto ec, auto) @@ -128,13 +129,11 @@ BOOST_AUTO_TEST_CASE(resp3_error_in_cmd_pipeline) BOOST_TEST(std::get<3>(resp1).has_value()); BOOST_CHECK_EQUAL(std::get<3>(resp1).value(), "req1-msg3"); - conn.async_exec(req2, resp2, c2); + conn->async_exec(req2, resp2, c2); }; - conn.async_exec(req1, resp1, c1); - async_run(conn, address{}, 10s, 10s, logger{}, [](auto ec){ - BOOST_CHECK_EQUAL(ec, boost::asio::error::basic_errors::operation_aborted); - }); + conn->async_exec(req1, resp1, c1); + run(conn); ioc.run(); } @@ -163,9 +162,9 @@ BOOST_AUTO_TEST_CASE(error_in_transaction) net::io_context ioc; - connection conn{ioc}; + auto conn = std::make_shared(ioc); - conn.async_exec(req, resp, [&](auto ec, auto){ + conn->async_exec(req, resp, [&](auto ec, auto){ BOOST_TEST(!ec); BOOST_TEST(std::get<0>(resp).has_value()); @@ -193,12 +192,12 @@ BOOST_AUTO_TEST_CASE(error_in_transaction) BOOST_TEST(std::get<6>(resp).has_value()); BOOST_CHECK_EQUAL(std::get<6>(resp).value(), "PONG"); - conn.cancel(redis::operation::run); - }); - async_run(conn, address{}, 10s, 10s, logger{}, [](auto ec){ - BOOST_CHECK_EQUAL(ec, boost::asio::error::basic_errors::operation_aborted); + conn->cancel(operation::run); + conn->cancel(operation::reconnection); }); + run(conn); + ioc.run(); } @@ -215,7 +214,7 @@ BOOST_AUTO_TEST_CASE(subscriber_wrong_syntax) req2.push("SUBSCRIBE"); // Wrong command synthax. net::io_context ioc; - connection conn{ioc}; + auto conn = std::make_shared(ioc); auto c2 = [&](auto ec, auto) { @@ -227,10 +226,10 @@ BOOST_AUTO_TEST_CASE(subscriber_wrong_syntax) { std::cout << "async_exec: hello" << std::endl; BOOST_TEST(!ec); - conn.async_exec(req2, ignore, c2); + conn->async_exec(req2, ignore, c2); }; - conn.async_exec(req1, ignore, c1); + conn->async_exec(req1, ignore, c1); generic_response gresp; auto c3 = [&](auto ec, auto) @@ -241,14 +240,13 @@ BOOST_AUTO_TEST_CASE(subscriber_wrong_syntax) BOOST_CHECK_EQUAL(gresp.error().data_type, resp3::type::simple_error); BOOST_TEST(!std::empty(gresp.error().diagnostic)); std::cout << gresp.error().diagnostic << std::endl; - conn.cancel(redis::operation::run); + conn->cancel(operation::run); + conn->cancel(operation::reconnection); }; - conn.async_receive(gresp, c3); - async_run(conn, address{}, 10s, 10s, logger{}, [](auto ec){ - std::cout << "async_run" << std::endl; - BOOST_CHECK_EQUAL(ec, boost::asio::error::basic_errors::operation_aborted); - }); + conn->async_receive(gresp, c3); + + run(conn); ioc.run(); } diff --git a/tests/conn_exec_retry.cpp b/tests/conn_exec_retry.cpp index bf70475b..41ca94cb 100644 --- a/tests/conn_exec_retry.cpp +++ b/tests/conn_exec_retry.cpp @@ -4,19 +4,17 @@ * accompanying file LICENSE.txt) */ -#include +#include #include #include #define BOOST_TEST_MODULE conn-exec-retry #include +#include +#include "common.hpp" -#include -#include #include -#include "common.hpp" - namespace net = boost::asio; using error_code = boost::system::error_code; using connection = boost::redis::connection; @@ -24,9 +22,8 @@ using boost::redis::operation; using boost::redis::request; using boost::redis::response; using boost::redis::ignore; -using boost::redis::async_run; using boost::redis::logger; -using boost::redis::address; +using boost::redis::config; using namespace std::chrono_literals; BOOST_AUTO_TEST_CASE(request_retry_false) @@ -45,7 +42,7 @@ BOOST_AUTO_TEST_CASE(request_retry_false) req2.push("PING"); net::io_context ioc; - connection conn{ioc}; + auto conn = std::make_shared(ioc); net::steady_timer st{ioc}; st.expires_after(std::chrono::seconds{1}); @@ -55,28 +52,33 @@ BOOST_AUTO_TEST_CASE(request_retry_false) // although it has cancel_on_connection_lost = false. The reason // being it has already been written so // cancel_on_connection_lost does not apply. - conn.cancel(operation::run); + conn->cancel(operation::run); + conn->cancel(operation::reconnection); + std::cout << "async_wait" << std::endl; }); auto c2 = [&](auto ec, auto){ + std::cout << "c2" << std::endl; BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled); }; auto c1 = [&](auto ec, auto){ + std::cout << "c1" << std::endl; BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled); }; auto c0 = [&](auto ec, auto){ + std::cout << "c0" << std::endl; BOOST_TEST(!ec); - conn.async_exec(req1, ignore, c1); - conn.async_exec(req2, ignore, c2); + conn->async_exec(req1, ignore, c1); + conn->async_exec(req2, ignore, c2); }; - conn.async_exec(req0, ignore, c0); + conn->async_exec(req0, ignore, c0); - async_run(conn, address{}, 10s, 10s, logger{}, [](auto ec){ - BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled); - }); + config cfg; + cfg.health_check_interval = 5s; + run(conn); ioc.run(); } @@ -102,25 +104,27 @@ BOOST_AUTO_TEST_CASE(request_retry_true) req3.push("QUIT"); net::io_context ioc; - connection conn{ioc}; + auto conn = std::make_shared(ioc); net::steady_timer st{ioc}; st.expires_after(std::chrono::seconds{1}); st.async_wait([&](auto){ // Cancels the request before receiving the response. This // should cause the thrid request to not complete with error - // since it has cancel_if_unresponded = true and cancellation commes - // after it was written. - conn.cancel(boost::redis::operation::run); + // since it has cancel_if_unresponded = true and cancellation + // comes after it was written. + conn->cancel(operation::run); }); auto c3 = [&](auto ec, auto){ + std::cout << "c3: " << ec.message() << std::endl; BOOST_TEST(!ec); + conn->cancel(); }; auto c2 = [&](auto ec, auto){ BOOST_TEST(!ec); - conn.async_exec(req3, ignore, c3); + conn->async_exec(req3, ignore, c3); }; auto c1 = [](auto ec, auto){ @@ -129,22 +133,17 @@ BOOST_AUTO_TEST_CASE(request_retry_true) auto c0 = [&](auto ec, auto){ BOOST_TEST(!ec); - conn.async_exec(req1, ignore, c1); - conn.async_exec(req2, ignore, c2); + conn->async_exec(req1, ignore, c1); + conn->async_exec(req2, ignore, c2); }; - conn.async_exec(req0, ignore, c0); - - async_run(conn, address{}, 10s, 10s, logger{}, [&](auto ec){ - // The first cacellation. - BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled); - conn.reset_stream(); + conn->async_exec(req0, ignore, c0); - // Reconnects and runs again to test req3. - async_run(conn, address{}, 10s, 10s, logger{}, [](auto ec){ - std::cout << ec.message() << std::endl; - BOOST_TEST(!ec); - }); + config cfg; + cfg.health_check_interval = 5s; + conn->async_run(cfg, {}, [&](auto ec){ + std::cout << ec.message() << std::endl; + BOOST_TEST(!!ec); }); ioc.run(); diff --git a/tests/conn_push.cpp b/tests/conn_push.cpp index e6d07333..d2a9399b 100644 --- a/tests/conn_push.cpp +++ b/tests/conn_push.cpp @@ -4,7 +4,7 @@ * accompanying file LICENSE.txt) */ -#include +#include #include #include #include @@ -17,6 +17,7 @@ #include namespace net = boost::asio; +namespace redis = boost::redis; using boost::redis::operation; using connection = boost::redis::connection; @@ -26,9 +27,8 @@ using boost::redis::request; using boost::redis::response; using boost::redis::ignore; using boost::redis::ignore_t; -using boost::redis::async_run; +using redis::config; using boost::redis::logger; -using boost::redis::address; using namespace std::chrono_literals; BOOST_AUTO_TEST_CASE(receives_push_waiting_resps) @@ -46,37 +46,35 @@ BOOST_AUTO_TEST_CASE(receives_push_waiting_resps) net::io_context ioc; - connection conn{ioc}; + auto conn = std::make_shared(ioc); auto c3 =[](auto ec, auto...) { BOOST_TEST(!!ec); }; - auto c2 =[&](auto ec, auto...) + auto c2 =[&, conn](auto ec, auto...) { BOOST_TEST(!ec); - conn.async_exec(req3, ignore, c3); + conn->async_exec(req3, ignore, c3); }; - auto c1 =[&](auto ec, auto...) + auto c1 =[&, conn](auto ec, auto...) { BOOST_TEST(!ec); - conn.async_exec(req2, ignore, c2); + conn->async_exec(req2, ignore, c2); }; - conn.async_exec(req1, ignore, c1); + conn->async_exec(req1, ignore, c1); - async_run(conn, address{}, 10s, 10s, logger{}, [&](auto ec){ - BOOST_TEST(!ec); - conn.cancel(operation::receive); - }); + run(conn, {}, {}); bool push_received = false; - conn.async_receive(ignore, [&](auto ec, auto){ + conn->async_receive(ignore, [&, conn](auto ec, auto){ std::cout << "async_receive" << std::endl; BOOST_TEST(!ec); - conn.cancel(operation::run); + conn->cancel(operation::run); + conn->cancel(operation::reconnection); push_received = true; }); @@ -88,27 +86,25 @@ BOOST_AUTO_TEST_CASE(receives_push_waiting_resps) BOOST_AUTO_TEST_CASE(push_received1) { net::io_context ioc; - connection conn{ioc}; + auto conn = std::make_shared(ioc); request req; - req.push("HELLO", 3); + //req.push("HELLO", 3); req.push("SUBSCRIBE", "channel"); - conn.async_exec(req, ignore, [](auto ec, auto){ + conn->async_exec(req, ignore, [conn](auto ec, auto){ std::cout << "async_exec" << std::endl; BOOST_TEST(!ec); }); - async_run(conn, address{}, 10s, 10s, logger{}, [&](auto ec){ - std::cout << "async_run: " << ec.message() << std::endl; - conn.cancel(operation::receive); - }); + run(conn); bool push_received = false; - conn.async_receive(ignore, [&](auto ec, auto){ + conn->async_receive(ignore, [&, conn](auto ec, auto){ std::cout << "async_receive" << std::endl; BOOST_TEST(!ec); - conn.cancel(operation::run); + conn->cancel(operation::run); + conn->cancel(operation::reconnection); push_received = true; }); @@ -120,7 +116,7 @@ BOOST_AUTO_TEST_CASE(push_received1) BOOST_AUTO_TEST_CASE(push_filtered_out) { net::io_context ioc; - connection conn{ioc}; + auto conn = std::make_shared(ioc); request req; req.push("HELLO", 3); @@ -129,17 +125,16 @@ BOOST_AUTO_TEST_CASE(push_filtered_out) req.push("QUIT"); response resp; - conn.async_exec(req, resp, [](auto ec, auto){ + conn->async_exec(req, resp, [conn](auto ec, auto){ BOOST_TEST(!ec); }); - conn.async_receive(ignore, [](auto ec, auto){ + conn->async_receive(ignore, [conn](auto ec, auto){ BOOST_TEST(!ec); + conn->cancel(operation::reconnection); }); - async_run(conn, address{}, 10s, 10s, logger{}, [&](auto ec){ - BOOST_TEST(!ec); - }); + run(conn); ioc.run(); @@ -148,15 +143,16 @@ BOOST_AUTO_TEST_CASE(push_filtered_out) } #ifdef BOOST_ASIO_HAS_CO_AWAIT -net::awaitable push_consumer1(connection& conn, bool& push_received) +net::awaitable +push_consumer1(std::shared_ptr conn, bool& push_received) { { - auto [ec, ev] = co_await conn.async_receive(ignore, as_tuple(net::use_awaitable)); + auto [ec, ev] = co_await conn->async_receive(ignore, as_tuple(net::use_awaitable)); BOOST_TEST(!ec); } { - auto [ec, ev] = co_await conn.async_receive(ignore, as_tuple(net::use_awaitable)); + auto [ec, ev] = co_await conn->async_receive(ignore, as_tuple(net::use_awaitable)); BOOST_CHECK_EQUAL(ec, net::experimental::channel_errc::channel_cancelled); } @@ -187,7 +183,7 @@ auto boost_redis_adapt(response_error_tag&) BOOST_AUTO_TEST_CASE(test_push_adapter) { net::io_context ioc; - connection conn{ioc}; + auto conn = std::make_shared(ioc); request req; req.push("HELLO", 3); @@ -195,17 +191,16 @@ BOOST_AUTO_TEST_CASE(test_push_adapter) req.push("SUBSCRIBE", "channel"); req.push("PING"); - conn.async_receive(error_tag_obj, [](auto ec, auto) { + conn->async_receive(error_tag_obj, [conn](auto ec, auto) { BOOST_CHECK_EQUAL(ec, boost::redis::error::incompatible_size); + conn->cancel(operation::reconnection); }); - conn.async_exec(req, ignore, [](auto ec, auto){ + conn->async_exec(req, ignore, [](auto ec, auto){ BOOST_CHECK_EQUAL(ec, net::experimental::error::channel_errors::channel_cancelled); }); - async_run(conn, address{}, 10s, 10s, logger{}, [](auto ec){ - BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled); - }); + run(conn); ioc.run(); @@ -213,10 +208,10 @@ BOOST_AUTO_TEST_CASE(test_push_adapter) // reconnection is possible after an error. } -net::awaitable push_consumer3(connection& conn) +net::awaitable push_consumer3(std::shared_ptr conn) { for (;;) { - co_await conn.async_receive(ignore, net::use_awaitable); + co_await conn->async_receive(ignore, net::use_awaitable); } } @@ -239,75 +234,73 @@ BOOST_AUTO_TEST_CASE(many_subscribers) req3.push("QUIT"); net::io_context ioc; - connection conn{ioc}; + auto conn = std::make_shared(ioc); auto c11 =[&](auto ec, auto...) { std::cout << "quit sent" << std::endl; + conn->cancel(operation::reconnection); BOOST_TEST(!ec); }; auto c10 =[&](auto ec, auto...) { BOOST_TEST(!ec); - conn.async_exec(req3, ignore, c11); + conn->async_exec(req3, ignore, c11); }; auto c9 =[&](auto ec, auto...) { BOOST_TEST(!ec); - conn.async_exec(req2, ignore, c10); + conn->async_exec(req2, ignore, c10); }; auto c8 =[&](auto ec, auto...) { BOOST_TEST(!ec); - conn.async_exec(req1, ignore, c9); + conn->async_exec(req1, ignore, c9); }; auto c7 =[&](auto ec, auto...) { BOOST_TEST(!ec); - conn.async_exec(req2, ignore, c8); + conn->async_exec(req2, ignore, c8); }; auto c6 =[&](auto ec, auto...) { BOOST_TEST(!ec); - conn.async_exec(req2, ignore, c7); + conn->async_exec(req2, ignore, c7); }; auto c5 =[&](auto ec, auto...) { BOOST_TEST(!ec); - conn.async_exec(req1, ignore, c6); + conn->async_exec(req1, ignore, c6); }; auto c4 =[&](auto ec, auto...) { BOOST_TEST(!ec); - conn.async_exec(req2, ignore, c5); + conn->async_exec(req2, ignore, c5); }; auto c3 =[&](auto ec, auto...) { BOOST_TEST(!ec); - conn.async_exec(req1, ignore, c4); + conn->async_exec(req1, ignore, c4); }; auto c2 =[&](auto ec, auto...) { BOOST_TEST(!ec); - conn.async_exec(req2, ignore, c3); + conn->async_exec(req2, ignore, c3); }; auto c1 =[&](auto ec, auto...) { BOOST_TEST(!ec); - conn.async_exec(req2, ignore, c2); + conn->async_exec(req2, ignore, c2); }; auto c0 =[&](auto ec, auto...) { BOOST_TEST(!ec); - conn.async_exec(req1, ignore, c1); + conn->async_exec(req1, ignore, c1); }; - conn.async_exec(req0, ignore, c0); + conn->async_exec(req0, ignore, c0); - async_run(conn, address{}, 10s, 10s, logger{}, [&](auto ec){ - BOOST_TEST(!ec); - conn.cancel(operation::receive); - }); + run(conn, {}, {}); net::co_spawn(ioc.get_executor(), push_consumer3(conn), net::detached); ioc.run(); diff --git a/tests/conn_quit.cpp b/tests/conn_quit.cpp index 77661544..0d5ba9a2 100644 --- a/tests/conn_quit.cpp +++ b/tests/conn_quit.cpp @@ -4,55 +4,53 @@ * accompanying file LICENSE.txt) */ -#include -#include +#include #include #define BOOST_TEST_MODULE conn-quit #include -#include "common.hpp" #include + +// TODO: Move this to a lib. #include namespace net = boost::asio; - -using connection = boost::redis::connection; -using error_code = boost::system::error_code; -using operation = boost::redis::operation; +using boost::redis::connection; +using boost::system::error_code; +using boost::redis::operation; using boost::redis::request; using boost::redis::response; using boost::redis::ignore; -using boost::redis::async_run; -using boost::redis::logger; -using boost::redis::address; +using boost::redis::config; using namespace std::chrono_literals; -BOOST_AUTO_TEST_CASE(test_quit1) +BOOST_AUTO_TEST_CASE(test_eof_no_error) { request req; req.get_config().cancel_on_connection_lost = false; - req.push("HELLO", 3); req.push("QUIT"); net::io_context ioc; connection conn{ioc}; - conn.async_exec(req, ignore, [](auto ec, auto) { + conn.async_exec(req, ignore, [&](auto ec, auto) { BOOST_TEST(!ec); + conn.cancel(operation::reconnection); }); - async_run(conn, address{}, 10s, 10s, logger{}, [&](auto ec){ - BOOST_TEST(!ec); + conn.async_run({}, {}, [](auto ec){ + BOOST_TEST(!!ec); }); ioc.run(); } // Test if quit causes async_run to exit. -BOOST_AUTO_TEST_CASE(test_quit2) +BOOST_AUTO_TEST_CASE(test_async_run_exits) { net::io_context ioc; connection conn{ioc}; + conn.cancel(operation::reconnection); request req1; req1.get_config().cancel_on_connection_lost = false; @@ -62,37 +60,39 @@ BOOST_AUTO_TEST_CASE(test_quit2) req2.get_config().cancel_on_connection_lost = false; req2.push("QUIT"); + // Should fail since this request will be sent after quit. request req3; - // Should cause the request to fail since this request will be sent - // after quit. req3.get_config().cancel_if_not_connected = true; req3.push("PING"); auto c3 = [](auto ec, auto) { - std::cout << "3--> " << ec.message() << std::endl; + std::clog << "c3: " << ec.message() << std::endl; BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled); }; auto c2 = [&](auto ec, auto) { - std::cout << "2--> " << ec.message() << std::endl; + std::clog << "c2: " << ec.message() << std::endl; BOOST_TEST(!ec); conn.async_exec(req3, ignore, c3); }; auto c1 = [&](auto ec, auto) { - std::cout << "1--> " << ec.message() << std::endl; + std::cout << "c3: " << ec.message() << std::endl; BOOST_TEST(!ec); - conn.async_exec(req2, ignore, c2); }; conn.async_exec(req1, ignore, c1); - async_run(conn, address{}, 10s, 10s, logger{}, [&](auto ec){ - BOOST_TEST(!ec); + // The healthy checker should not be the cause of async_run + // completing, so we set a long timeout. + config cfg; + cfg.health_check_interval = 10000s; + conn.async_run({}, {}, [&](auto ec){ + BOOST_TEST(!!ec); }); ioc.run(); diff --git a/tests/conn_reconnect.cpp b/tests/conn_reconnect.cpp index fab680ad..ce948709 100644 --- a/tests/conn_reconnect.cpp +++ b/tests/conn_reconnect.cpp @@ -4,8 +4,7 @@ * accompanying file LICENSE.txt) */ -#include -#include +#include #include #define BOOST_TEST_MODULE conn-reconnect #include @@ -18,14 +17,14 @@ #include namespace net = boost::asio; -using error_code = boost::system::error_code; +using boost::system::error_code; using boost::redis::request; using boost::redis::response; using boost::redis::ignore; -using boost::redis::async_run; +using boost::redis::config; using boost::redis::logger; -using boost::redis::address; -using connection = boost::asio::use_awaitable_t<>::as_default_on_t; +using boost::redis::operation; +using boost::redis::connection; using namespace std::chrono_literals; using namespace boost::asio::experimental::awaitable_operators; @@ -37,22 +36,20 @@ net::awaitable test_reconnect_impl() request req; req.push("QUIT"); - connection conn{ex}; + auto conn = std::make_shared(ex); + run(conn); int i = 0; - address addr; for (; i < 5; ++i) { - boost::system::error_code ec1, ec2; - co_await ( - conn.async_exec(req, ignore, net::redirect_error(net::use_awaitable, ec1)) && - async_run(conn, addr, 10s, 10s, logger{}, net::redirect_error(net::use_awaitable, ec2)) - ); - - BOOST_TEST(!ec1); - BOOST_TEST(!ec2); - conn.reset_stream(); + error_code ec1, ec2; + config cfg; + logger l; + co_await conn->async_exec(req, ignore, net::redirect_error(net::use_awaitable, ec1)); + //BOOST_TEST(!ec); + std::cout << "test_reconnect: " << i << " " << ec2.message() << " " << ec1.message() << std::endl; } + conn->cancel(); BOOST_CHECK_EQUAL(i, 5); co_return; } @@ -72,44 +69,40 @@ auto async_test_reconnect_timeout() -> net::awaitable net::steady_timer st{ex}; auto conn = std::make_shared(ex); - boost::system::error_code ec1, ec2, ec3; + error_code ec1, ec3; request req1; req1.get_config().cancel_if_not_connected = false; req1.get_config().cancel_on_connection_lost = true; req1.get_config().cancel_if_unresponded = true; - req1.push("HELLO", 3); req1.push("BLPOP", "any", 0); st.expires_after(std::chrono::seconds{1}); - address addr; + config cfg; co_await ( conn->async_exec(req1, ignore, redir(ec1)) || - async_run(*conn, addr, 10s, 10s, logger{}, redir(ec2)) || st.async_wait(redir(ec3)) ); //BOOST_TEST(!ec1); - BOOST_CHECK_EQUAL(ec2, boost::system::errc::errc_t::operation_canceled); //BOOST_TEST(!ec3); request req2; req2.get_config().cancel_if_not_connected = false; req2.get_config().cancel_on_connection_lost = true; req2.get_config().cancel_if_unresponded= true; - req2.push("HELLO", 3); req2.push("QUIT"); st.expires_after(std::chrono::seconds{1}); co_await ( conn->async_exec(req1, ignore, net::redirect_error(net::use_awaitable, ec1)) || - async_run(*conn, addr, 10s, 10s, logger{}, net::redirect_error(net::use_awaitable, ec2)) || st.async_wait(net::redirect_error(net::use_awaitable, ec3)) ); + conn->cancel(); + std::cout << "ccc" << std::endl; BOOST_CHECK_EQUAL(ec1, boost::system::errc::errc_t::operation_canceled); - BOOST_CHECK_EQUAL(ec2, boost::asio::error::basic_errors::operation_aborted); } BOOST_AUTO_TEST_CASE(test_reconnect_and_idle) diff --git a/tests/conn_run_cancel.cpp b/tests/conn_run_cancel.cpp index 3f0a8abd..d9f383f1 100644 --- a/tests/conn_run_cancel.cpp +++ b/tests/conn_run_cancel.cpp @@ -4,7 +4,7 @@ * accompanying file LICENSE.txt) */ -#include +#include #include #include #include @@ -21,15 +21,14 @@ namespace net = boost::asio; using boost::redis::operation; -using connection = boost::redis::connection; -using error_code = boost::system::error_code; +using boost::redis::config; +using boost::redis::connection; +using boost::system::error_code; using net::experimental::as_tuple; using boost::redis::request; using boost::redis::response; using boost::redis::ignore; -using boost::redis::async_run; using boost::redis::logger; -using boost::redis::address; using namespace std::chrono_literals; using namespace net::experimental::awaitable_operators; @@ -40,13 +39,14 @@ auto async_cancel_run_with_timer() -> net::awaitable connection conn{ex}; net::steady_timer st{ex}; - st.expires_after(std::chrono::seconds{1}); + st.expires_after(1s); - boost::system::error_code ec1, ec2; - address addr; - co_await (async_run(conn, addr, 10s, 10s, logger{}, redir(ec1)) || st.async_wait(redir(ec2))); + error_code ec1, ec2; + config cfg; + logger l; + co_await (conn.async_run(cfg, l, redir(ec1)) || st.async_wait(redir(ec2))); - BOOST_CHECK_EQUAL(ec1, boost::asio::error::basic_errors::operation_aborted); + BOOST_CHECK_EQUAL(ec1, boost::asio::error::operation_aborted); BOOST_TEST(!ec2); } @@ -67,10 +67,11 @@ async_check_cancellation_not_missed(int n, std::chrono::milliseconds ms) -> net: for (auto i = 0; i < n; ++i) { timer.expires_after(ms); - boost::system::error_code ec1, ec2; - address addr; - co_await (async_run(conn, addr, 10s, 10s, logger{}, redir(ec1)) || timer.async_wait(redir(ec2))); - BOOST_CHECK_EQUAL(ec1, boost::asio::error::basic_errors::operation_aborted); + error_code ec1, ec2; + config cfg; + logger l; + co_await (conn.async_run(cfg, l, redir(ec1)) || timer.async_wait(redir(ec2))); + BOOST_CHECK_EQUAL(ec1, boost::asio::error::operation_aborted); std::cout << "Counter: " << i << std::endl; } } @@ -146,28 +147,6 @@ BOOST_AUTO_TEST_CASE(check_implicit_cancel_not_missed_1024) ioc.run(); } -BOOST_AUTO_TEST_CASE(reset_before_run_completes) -{ - net::io_context ioc; - connection conn{ioc}; - - // Sends a ping just as a means of waiting until we are connected. - request req; - req.push("HELLO", 3); - req.push("PING"); - - conn.async_exec(req, ignore, [&](auto ec, auto){ - BOOST_TEST(!ec); - conn.reset_stream(); - }); - address addr; - async_run(conn, addr, 10s, 10s, logger{}, [&](auto ec){ - BOOST_CHECK_EQUAL(ec, net::error::operation_aborted); - }); - - ioc.run(); -} - #else BOOST_AUTO_TEST_CASE(dummy) { diff --git a/tests/conn_tls.cpp b/tests/conn_tls.cpp index 38fcbfde..7a62b3b1 100644 --- a/tests/conn_tls.cpp +++ b/tests/conn_tls.cpp @@ -4,13 +4,12 @@ * accompanying file LICENSE.txt) */ -#include -#include +#include #define BOOST_TEST_MODULE conn-tls #include -#include #include #include "common.hpp" + #include namespace net = boost::asio; @@ -18,24 +17,8 @@ namespace net = boost::asio; using connection = boost::redis::ssl::connection; using boost::redis::request; using boost::redis::response; -using boost::redis::ignore_t; - -using endpoints = net::ip::tcp::resolver::results_type; - -auto -resolve( - std::string const& host = "127.0.0.1", - std::string const& port = "6379") -> endpoints -{ - net::io_context ioc; - net::ip::tcp::resolver resv{ioc}; - return resv.resolve(host, port); -} - -struct endpoint { - std::string host; - std::string port; -}; +using boost::redis::config; +using boost::redis::operation; bool verify_certificate(bool, net::ssl::verify_context&) { @@ -45,17 +28,18 @@ bool verify_certificate(bool, net::ssl::verify_context&) BOOST_AUTO_TEST_CASE(ping) { + config cfg; + cfg.username = "aedis"; + cfg.password = "aedis"; + cfg.addr.host = "db.occase.de"; + cfg.addr.port = "6380"; + std::string const in = "Kabuf"; request req; - req.get_config().cancel_on_connection_lost = true; - req.push("HELLO", 3, "AUTH", "aedis", "aedis"); req.push("PING", in); - req.push("QUIT"); - - response resp; - auto const endpoints = resolve("db.occase.de", "6380"); + response resp; net::io_context ioc; net::ssl::context ctx{net::ssl::context::sslv23}; @@ -63,19 +47,16 @@ BOOST_AUTO_TEST_CASE(ping) conn.next_layer().set_verify_mode(net::ssl::verify_peer); conn.next_layer().set_verify_callback(verify_certificate); - net::connect(conn.lowest_layer(), endpoints); - conn.next_layer().handshake(net::ssl::stream_base::client); - - conn.async_exec(req, resp, [](auto ec, auto) { + conn.async_exec(req, resp, [&](auto ec, auto) { BOOST_TEST(!ec); + conn.cancel(); }); - conn.async_run([](auto ec) { - BOOST_TEST(!ec); - }); + conn.async_run(cfg, {}, [](auto) { }); ioc.run(); - BOOST_CHECK_EQUAL(in, std::get<1>(resp).value()); + BOOST_CHECK_EQUAL(in, std::get<0>(resp).value()); + std::cout << "===============================" << std::endl; } diff --git a/tests/cpp20_low_level_async.cpp b/tests/cpp20_low_level_async.cpp index eba51e5a..cb6776b7 100644 --- a/tests/cpp20_low_level_async.cpp +++ b/tests/cpp20_low_level_async.cpp @@ -4,8 +4,8 @@ * accompanying file LICENSE.txt) */ +#include #include -#include #include #include #include @@ -22,15 +22,15 @@ using tcp_socket = net::use_awaitable_t<>::as_default_on_t using boost::redis::adapter::adapt2; using net::ip::tcp; using boost::redis::request; -using boost::redis::address; using boost::redis::adapter::result; +using redis::config; -auto co_main(address const& addr) -> net::awaitable +auto co_main(config const& cfg) -> net::awaitable { auto ex = co_await net::this_coro::executor; resolver resv{ex}; - auto const addrs = co_await resv.async_resolve(addr.host, addr.port); + auto const addrs = co_await resv.async_resolve(cfg.addr.host, cfg.addr.port); tcp_socket socket{ex}; co_await net::async_connect(socket, addrs); diff --git a/tests/issue_50.cpp b/tests/issue_50.cpp index c5394333..7b27df6b 100644 --- a/tests/issue_50.cpp +++ b/tests/issue_50.cpp @@ -6,7 +6,7 @@ // Must come before any asio header, otherwise build fails on msvc. -#include +#include #include #include #include @@ -22,31 +22,44 @@ #if defined(BOOST_ASIO_HAS_CO_AWAIT) namespace net = boost::asio; -namespace redis = boost::redis; using steady_timer = net::use_awaitable_t<>::as_default_on_t; -using redis::request; -using redis::response; -using redis::ignore; -using redis::address; -using redis::logger; -using redis::experimental::async_connect; -using redis::experimental::connect_config; +using boost::redis::request; +using boost::redis::response; +using boost::redis::ignore; +using boost::redis::logger; +using boost::redis::config; +using boost::redis::operation; +using boost::system::error_code; +using boost::asio::use_awaitable; +using boost::asio::redirect_error; using connection = boost::asio::use_awaitable_t<>::as_default_on_t; using namespace std::chrono_literals; // Push consumer -auto receiver(std::shared_ptr conn) -> net::awaitable +auto +receiver(std::shared_ptr conn) -> net::awaitable { - boost::system::error_code ec; - while (!ec) - co_await conn->async_receive(ignore, net::redirect_error(net::use_awaitable, ec)); + std::cout << "uuu" << std::endl; + while (!conn->is_cancelled()) { + std::cout << "dddd" << std::endl; + // Loop reading Redis pushs messages. + for (;;) { + std::cout << "aaaa" << std::endl; + error_code ec; + co_await conn->async_receive(ignore, redirect_error(use_awaitable, ec)); + if (ec) + break; + } + } } -auto periodic_task(std::shared_ptr conn) -> net::awaitable +auto +periodic_task(std::shared_ptr conn) -> net::awaitable { net::steady_timer timer{co_await net::this_coro::executor}; for (int i = 0; i < 10; ++i) { - timer.expires_after(std::chrono::seconds(2)); + std::cout << "In the loop: " << i << std::endl; + timer.expires_after(std::chrono::milliseconds(50)); co_await timer.async_wait(net::use_awaitable); // Key is not set so it will cause an error since we are passing @@ -56,29 +69,26 @@ auto periodic_task(std::shared_ptr conn) -> net::awaitable req.push("GET", "mykey"); auto [ec, u] = co_await conn->async_exec(req, ignore, net::as_tuple(net::use_awaitable)); if (ec) { - std::cout << "Error: " << ec << std::endl; + std::cout << "(1)Error: " << ec << std::endl; } else { std::cout << "no error: " << std::endl; } } std::cout << "Periodic task done!" << std::endl; - conn->disable_reconnection(); - conn->cancel(redis::operation::run); - conn->cancel(redis::operation::receive); + conn->cancel(operation::run); + conn->cancel(operation::receive); + conn->cancel(operation::reconnection); } -auto co_main(address const& addr) -> net::awaitable +auto co_main(config const& cfg) -> net::awaitable { auto ex = co_await net::this_coro::executor; auto conn = std::make_shared(ex); - connect_config cfg; - cfg.addr = addr; - net::co_spawn(ex, receiver(conn), net::detached); net::co_spawn(ex, periodic_task(conn), net::detached); - redis::experimental::async_connect(*conn, cfg, logger{}, net::consign(net::detached, conn)); + conn->async_run(cfg, {}, net::consign(net::detached, conn)); } #endif // defined(BOOST_ASIO_HAS_CO_AWAIT) diff --git a/tests/run.cpp b/tests/run.cpp index 34c29d8b..3e2740e9 100644 --- a/tests/run.cpp +++ b/tests/run.cpp @@ -4,19 +4,20 @@ * accompanying file LICENSE.txt) */ -#include -#include +#include #define BOOST_TEST_MODULE run #include #include #include +#include "common.hpp" namespace net = boost::asio; +namespace redis = boost::redis; -using connection = boost::redis::connection; -using boost::redis::async_run; -using boost::redis::logger; -using boost::redis::address; +using connection = redis::connection; +using redis::config; +using redis::logger; +using redis::operation; using boost::system::error_code; using namespace std::chrono_literals; @@ -31,8 +32,16 @@ BOOST_AUTO_TEST_CASE(resolve_bad_host) { net::io_context ioc; + config cfg; + cfg.addr.host = "Atibaia"; + cfg.addr.port = "6379"; + cfg.resolve_timeout = 10h; + cfg.connect_timeout = 10h; + cfg.health_check_interval = 10h; + connection conn{ioc}; - async_run(conn, address{{"Atibaia"}, {"6379"}}, 1000s, 1000s, logger{}, [](auto ec){ + conn.cancel(operation::reconnection); + conn.async_run(cfg, {}, [](auto ec){ BOOST_TEST(is_host_not_found(ec)); }); @@ -43,11 +52,16 @@ BOOST_AUTO_TEST_CASE(resolve_with_timeout) { net::io_context ioc; - connection conn{ioc}; - async_run(conn, address{{"Atibaia"}, {"6379"}}, 1ms, 1ms, logger{}, [](auto ec){ - BOOST_CHECK_EQUAL(ec, boost::redis::error::resolve_timeout); - }); + config cfg; + cfg.addr.host = "occase.de"; + cfg.addr.port = "6379"; + cfg.resolve_timeout = 1ms; + cfg.connect_timeout = 1ms; + cfg.health_check_interval = 10h; + auto conn = std::make_shared(ioc); + conn->cancel(operation::reconnection); + run(conn, cfg); ioc.run(); } @@ -55,23 +69,33 @@ BOOST_AUTO_TEST_CASE(connect_bad_port) { net::io_context ioc; - connection conn{ioc}; - async_run(conn, address{{"127.0.0.1"}, {"1"}}, 1000s, 10s, logger{}, [](auto ec){ - BOOST_CHECK_EQUAL(ec, net::error::basic_errors::connection_refused); - }); + config cfg; + cfg.addr.host = "127.0.0.1"; + cfg.addr.port = "1"; + cfg.resolve_timeout = 10h; + cfg.connect_timeout = 10s; + cfg.health_check_interval = 10h; + auto conn = std::make_shared(ioc); + conn->cancel(operation::reconnection); + run(conn, cfg, net::error::connection_refused); ioc.run(); } -BOOST_AUTO_TEST_CASE(connect_with_timeout) -{ - net::io_context ioc; - - connection conn{ioc}; - async_run(conn, address{{"example.com"}, {"1"}}, 10s, 1ms, logger{}, [](auto ec){ - BOOST_CHECK_EQUAL(ec, boost::redis::error::connect_timeout); - }); - - ioc.run(); -} +// Hard to test. +//BOOST_AUTO_TEST_CASE(connect_with_timeout) +//{ +// net::io_context ioc; +// +// config cfg; +// cfg.addr.host = "example.com"; +// cfg.addr.port = "80"; +// cfg.resolve_timeout = 10s; +// cfg.connect_timeout = 1ns; +// cfg.health_check_interval = 10h; +// +// auto conn = std::make_shared(ioc); +// run(conn, cfg, boost::redis::error::connect_timeout); +// ioc.run(); +//} From 607a9e9dd6eeed3a76d10ab851013ad351b51569 Mon Sep 17 00:00:00 2001 From: bram Date: Wed, 15 Mar 2023 21:51:56 +0100 Subject: [PATCH 10/32] Added example cpp20_streams, which reproduces an assertion. --- CMakeLists.txt | 9 ++++ examples/cpp20_streams.cpp | 91 +++++++++++++++++++++++++++++++++++ examples/cpp20_subscriber.cpp | 2 +- 3 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 examples/cpp20_streams.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 22a30ae5..3b2cfdde 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -92,6 +92,15 @@ if (MSVC) target_compile_definitions(cpp20_intro PRIVATE _WIN32_WINNT=0x0601) endif() +add_executable(cpp20_streams examples/cpp20_streams.cpp) +target_link_libraries(cpp20_streams common) +target_compile_features(cpp20_streams PUBLIC cxx_std_20) +add_test(cpp20_streams cpp20_streams) +if (MSVC) + target_compile_options(cpp20_streams PRIVATE /bigobj) + target_compile_definitions(cpp20_streams PRIVATE _WIN32_WINNT=0x0601) +endif() + add_executable(cpp17_intro examples/cpp17_intro.cpp) target_compile_features(cpp17_intro PUBLIC cxx_std_17) add_test(cpp17_intro cpp17_intro) diff --git a/examples/cpp20_streams.cpp b/examples/cpp20_streams.cpp new file mode 100644 index 00000000..d0cb6cca --- /dev/null +++ b/examples/cpp20_streams.cpp @@ -0,0 +1,91 @@ +/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com) + * + * Distributed under the Boost Software License, Version 1.0. (See + * accompanying file LICENSE.txt) + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(BOOST_ASIO_HAS_CO_AWAIT) + +namespace net = boost::asio; +using boost::redis::config; +using boost::redis::generic_response; +using boost::redis::operation; +using boost::redis::request; +using connection = net::deferred_t::as_default_on_t; +using signal_set = net::deferred_t::as_default_on_t; + +auto stream_reader(std::shared_ptr conn) -> net::awaitable +{ + std::string redisStreamKey_; + request req; + generic_response resp; + + std::string stream_id{"$"}; + std::string const field = "myfield"; + + for (;;) { + req.push("XREAD", "BLOCK", "0", "STREAMS", "test-topic", stream_id); + co_await conn->async_exec(req, resp); + + // std::cout << "Response: "; + // for (int i = 0; i < resp->value().size(); ++i) { + // std::cout << resp->value().at(i).value << ", "; + // } + // std::cout << std::endl; + + // The following approach was taken in order to be able to + // deal with the responses, as generated by redis in the case + // that there are multiple stream 'records' within a single + // generic_response. The nesting and number of values in + // resp.value() are different, depending on the contents + // of the stream in redis. Uncomment the above commented-out + // code for examples while running the XADD command. + + std::size_t item_index = 0; + while (item_index < std::size(resp.value())) { + auto const& val = resp.value().at(item_index).value; + + if (field.compare(val) == 0) { + // We've hit a myfield field. + // The streamId is located at item_index - 2 + // The payload is located at item_index + 1 + stream_id = resp.value().at(item_index - 2).value; + std::cout + << "StreamId: " << stream_id << ", " + << "MyField: " << resp.value().at(item_index + 1).value + << std::endl; + ++item_index; // We can increase so we don't read this again + } + + ++item_index; + } + + req.clear(); + resp.value().clear(); + } + +} + +// Run this in another terminal: +// redis-cli -r 100000 -i 0.0001 XADD "test-topic" "*" "myfield" "myfieldvalue1" +auto co_main(config cfg) -> net::awaitable +{ + auto ex = co_await net::this_coro::executor; + auto conn = std::make_shared(ex); + net::co_spawn(ex, stream_reader(conn), net::detached); + conn->async_run(cfg, {}, net::consign(net::detached, conn)); + + signal_set sig_set(ex, SIGINT, SIGTERM); + co_await sig_set.async_wait(); + conn->cancel(); +} +#endif // defined(BOOST_ASIO_HAS_CO_AWAIT) diff --git a/examples/cpp20_subscriber.cpp b/examples/cpp20_subscriber.cpp index 717b46fb..e7c1f383 100644 --- a/examples/cpp20_subscriber.cpp +++ b/examples/cpp20_subscriber.cpp @@ -19,6 +19,7 @@ #if defined(BOOST_ASIO_HAS_CO_AWAIT) namespace net = boost::asio; +using namespace std::chrono_literals; using boost::redis::request; using boost::redis::generic_response; using boost::redis::logger; @@ -26,7 +27,6 @@ using boost::redis::config; using boost::system::error_code; using connection = net::deferred_t::as_default_on_t; using signal_set = net::deferred_t::as_default_on_t; -using namespace std::chrono_literals; /* This example will subscribe and read pushes indefinitely. * From 3808fec0e3d52d994b7f76aa8052884f3a60177e Mon Sep 17 00:00:00 2001 From: bram Date: Thu, 16 Mar 2023 09:47:43 +0100 Subject: [PATCH 11/32] Cleaned up a bit Removed unused stuff Using request and response as shared_ptrs. Removed (unnecessary?) calls to net::post. --- CMakeLists.txt | 1 - examples/cpp20_chat_room.cpp | 2 +- examples/cpp20_containers.cpp | 2 +- examples/cpp20_echo_server.cpp | 2 +- examples/cpp20_intro.cpp | 2 +- examples/cpp20_intro_tls.cpp | 2 +- examples/cpp20_json.cpp | 2 +- examples/cpp20_resolve_with_sentinel.cpp | 2 +- examples/cpp20_streams.cpp | 9 ++++++++- examples/cpp20_subscriber.cpp | 2 +- examples/main.cpp | 2 +- 11 files changed, 17 insertions(+), 11 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3b2cfdde..9b8fdd6c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -95,7 +95,6 @@ endif() add_executable(cpp20_streams examples/cpp20_streams.cpp) target_link_libraries(cpp20_streams common) target_compile_features(cpp20_streams PUBLIC cxx_std_20) -add_test(cpp20_streams cpp20_streams) if (MSVC) target_compile_options(cpp20_streams PRIVATE /bigobj) target_compile_definitions(cpp20_streams PRIVATE _WIN32_WINNT=0x0601) diff --git a/examples/cpp20_chat_room.cpp b/examples/cpp20_chat_room.cpp index 57df7901..7ef27c1e 100644 --- a/examples/cpp20_chat_room.cpp +++ b/examples/cpp20_chat_room.cpp @@ -72,7 +72,7 @@ auto publisher(std::shared_ptr in, std::shared_ptr net::awaitable +auto co_main(config cfg) -> net::awaitable { auto ex = co_await net::this_coro::executor; auto conn = std::make_shared(ex); diff --git a/examples/cpp20_containers.cpp b/examples/cpp20_containers.cpp index 277a8575..cbea9330 100644 --- a/examples/cpp20_containers.cpp +++ b/examples/cpp20_containers.cpp @@ -89,7 +89,7 @@ auto transaction(std::shared_ptr conn) -> net::awaitable } // Called from the main function (see main.cpp) -net::awaitable co_main(config const& cfg) +net::awaitable co_main(config cfg) { auto conn = std::make_shared(co_await net::this_coro::executor); conn->async_run(cfg, {}, net::consign(net::detached, conn)); diff --git a/examples/cpp20_echo_server.cpp b/examples/cpp20_echo_server.cpp index 44b7b657..6d2691fb 100644 --- a/examples/cpp20_echo_server.cpp +++ b/examples/cpp20_echo_server.cpp @@ -55,7 +55,7 @@ auto listener(std::shared_ptr conn) -> net::awaitable } // Called from the main function (see main.cpp) -auto co_main(config const& cfg) -> net::awaitable +auto co_main(config cfg) -> net::awaitable { auto ex = co_await net::this_coro::executor; auto conn = std::make_shared(ex); diff --git a/examples/cpp20_intro.cpp b/examples/cpp20_intro.cpp index a67db286..3526950d 100644 --- a/examples/cpp20_intro.cpp +++ b/examples/cpp20_intro.cpp @@ -21,7 +21,7 @@ using boost::redis::logger; using connection = net::deferred_t::as_default_on_t; // Called from the main function (see main.cpp) -auto co_main(config const& cfg) -> net::awaitable +auto co_main(config cfg) -> net::awaitable { auto conn = std::make_shared(co_await net::this_coro::executor); conn->async_run(cfg, {}, net::consign(net::detached, conn)); diff --git a/examples/cpp20_intro_tls.cpp b/examples/cpp20_intro_tls.cpp index 00c1c9a4..503b9fe7 100644 --- a/examples/cpp20_intro_tls.cpp +++ b/examples/cpp20_intro_tls.cpp @@ -26,7 +26,7 @@ auto verify_certificate(bool, net::ssl::verify_context&) -> bool return true; } -auto co_main(config const&) -> net::awaitable +auto co_main(config cfg) -> net::awaitable { config cfg; cfg.username = "aedis"; diff --git a/examples/cpp20_json.cpp b/examples/cpp20_json.cpp index 5b204b2f..6117dedd 100644 --- a/examples/cpp20_json.cpp +++ b/examples/cpp20_json.cpp @@ -45,7 +45,7 @@ void boost_redis_to_bulk(std::string& to, user const& u) void boost_redis_from_bulk(user& u, std::string_view sv, boost::system::error_code& ec) { boost::redis::json::from_bulk(u, sv, ec); } -net::awaitable co_main(config const& cfg) +auto co_main(config cfg) -> net::awaitable { auto ex = co_await net::this_coro::executor; auto conn = std::make_shared(ex); diff --git a/examples/cpp20_resolve_with_sentinel.cpp b/examples/cpp20_resolve_with_sentinel.cpp index 1dbb01f3..70aeaed0 100644 --- a/examples/cpp20_resolve_with_sentinel.cpp +++ b/examples/cpp20_resolve_with_sentinel.cpp @@ -55,7 +55,7 @@ auto resolve_master_address(std::vector
const& addresses) -> net::await co_return address{}; } -auto co_main(config const& cfg) -> net::awaitable +auto co_main(config cfg) -> net::awaitable { // A list of sentinel addresses from which only one is responsive. // This simulates sentinels that are down. diff --git a/examples/cpp20_streams.cpp b/examples/cpp20_streams.cpp index d0cb6cca..5dcae830 100644 --- a/examples/cpp20_streams.cpp +++ b/examples/cpp20_streams.cpp @@ -15,6 +15,11 @@ #if defined(BOOST_ASIO_HAS_CO_AWAIT) +#include +#include +#include +#include + namespace net = boost::asio; using boost::redis::config; using boost::redis::generic_response; @@ -72,7 +77,6 @@ auto stream_reader(std::shared_ptr conn) -> net::awaitable req.clear(); resp.value().clear(); } - } // Run this in another terminal: @@ -82,6 +86,9 @@ auto co_main(config cfg) -> net::awaitable auto ex = co_await net::this_coro::executor; auto conn = std::make_shared(ex); net::co_spawn(ex, stream_reader(conn), net::detached); + + // Disable health checks. + cfg.health_check_interval = std::chrono::seconds{0}; conn->async_run(cfg, {}, net::consign(net::detached, conn)); signal_set sig_set(ex, SIGINT, SIGTERM); diff --git a/examples/cpp20_subscriber.cpp b/examples/cpp20_subscriber.cpp index e7c1f383..5bc76a7d 100644 --- a/examples/cpp20_subscriber.cpp +++ b/examples/cpp20_subscriber.cpp @@ -72,7 +72,7 @@ receiver(std::shared_ptr conn) -> net::awaitable } } -auto co_main(config const& cfg) -> net::awaitable +auto co_main(config cfg) -> net::awaitable { auto ex = co_await net::this_coro::executor; auto conn = std::make_shared(ex); diff --git a/examples/main.cpp b/examples/main.cpp index b35146ac..ffd67d9b 100644 --- a/examples/main.cpp +++ b/examples/main.cpp @@ -14,7 +14,7 @@ using boost::redis::config; -extern boost::asio::awaitable co_main(config const&); +extern boost::asio::awaitable co_main(config); auto main(int argc, char * argv[]) -> int { From 1f9b3e800814d772505b50194c77f01ceaedc9eb Mon Sep 17 00:00:00 2001 From: Marcelo Zimbres Date: Sat, 6 May 2023 20:48:32 +0200 Subject: [PATCH 12/32] Rebase the branch on develop. --- README.md | 3 +++ examples/cpp20_intro_tls.cpp | 1 - examples/cpp20_protobuf.cpp | 2 +- tests/cpp20_low_level_async.cpp | 2 +- tests/issue_50.cpp | 2 +- 5 files changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 58ca1daa..4184916a 100644 --- a/README.md +++ b/README.md @@ -678,6 +678,7 @@ Acknowledgement to people that helped shape Boost.Redis * Mohammad Nejati ([ashtum](https://github.com/ashtum)): For pointing out scenarios where calls to `async_exec` should fail when the connection is lost. * Klemens Morgenstern ([klemens-morgenstern](https://github.com/klemens-morgenstern)): For useful discussion about timeouts, cancellation, synchronous interfaces and general help with Asio. * Vinnie Falco ([vinniefalco](https://github.com/vinniefalco)): For general suggestions about how to improve the code and the documentation. +* Bram Veldhoen ([bveldhoen](https://github.com/bveldhoen)): For contributing a Redis streams example. Also many thanks to all individuals that participated in the Boost review @@ -702,6 +703,8 @@ https://lists.boost.org/Archives/boost/2023/01/253944.php. ### master (incorporates changes to conform the boost review and more) +* Adds Redis stream example. + * Renames the project to Boost.Redis and moves the code into namespace `boost::redis`. diff --git a/examples/cpp20_intro_tls.cpp b/examples/cpp20_intro_tls.cpp index 503b9fe7..33c36e0b 100644 --- a/examples/cpp20_intro_tls.cpp +++ b/examples/cpp20_intro_tls.cpp @@ -28,7 +28,6 @@ auto verify_certificate(bool, net::ssl::verify_context&) -> bool auto co_main(config cfg) -> net::awaitable { - config cfg; cfg.username = "aedis"; cfg.password = "aedis"; cfg.addr.host = "db.occase.de"; diff --git a/examples/cpp20_protobuf.cpp b/examples/cpp20_protobuf.cpp index 200bfe1d..d372798f 100644 --- a/examples/cpp20_protobuf.cpp +++ b/examples/cpp20_protobuf.cpp @@ -44,7 +44,7 @@ void boost_redis_from_bulk(person& u, std::string_view sv, boost::system::error_ using tutorial::boost_redis_to_bulk; using tutorial::boost_redis_from_bulk; -net::awaitable co_main(config const& cfg) +net::awaitable co_main(config cfg) { auto ex = co_await net::this_coro::executor; auto conn = std::make_shared(ex); diff --git a/tests/cpp20_low_level_async.cpp b/tests/cpp20_low_level_async.cpp index cb6776b7..23e8189e 100644 --- a/tests/cpp20_low_level_async.cpp +++ b/tests/cpp20_low_level_async.cpp @@ -25,7 +25,7 @@ using boost::redis::request; using boost::redis::adapter::result; using redis::config; -auto co_main(config const& cfg) -> net::awaitable +auto co_main(config cfg) -> net::awaitable { auto ex = co_await net::this_coro::executor; diff --git a/tests/issue_50.cpp b/tests/issue_50.cpp index 7b27df6b..4905cbed 100644 --- a/tests/issue_50.cpp +++ b/tests/issue_50.cpp @@ -81,7 +81,7 @@ periodic_task(std::shared_ptr conn) -> net::awaitable conn->cancel(operation::reconnection); } -auto co_main(config const& cfg) -> net::awaitable +auto co_main(config cfg) -> net::awaitable { auto ex = co_await net::this_coro::executor; auto conn = std::make_shared(ex); From 6f9fd5b2fbf814102bad6b5d8d79b1bc5478bc4b Mon Sep 17 00:00:00 2001 From: Marcelo Zimbres Date: Sun, 7 May 2023 15:40:13 +0200 Subject: [PATCH 13/32] Unifies ssl and plain connections. --- CMakeLists.txt | 51 +++-- README.md | 2 +- examples/cpp17_intro.cpp | 3 +- examples/cpp17_intro_sync.cpp | 4 +- examples/cpp20_chat_room.cpp | 7 +- examples/cpp20_containers.cpp | 5 +- examples/cpp20_echo_server.cpp | 5 +- examples/cpp20_intro.cpp | 5 +- examples/cpp20_intro_tls.cpp | 11 +- examples/cpp20_json.cpp | 5 +- examples/cpp20_protobuf.cpp | 5 +- examples/cpp20_resolve_with_sentinel.cpp | 5 +- examples/cpp20_streams.cpp | 5 +- examples/cpp20_subscriber.cpp | 7 +- examples/sync_connection.hpp | 4 +- include/boost/redis/config.hpp | 13 +- include/boost/redis/connection.hpp | 108 ++++----- .../boost/redis/detail/connection_base.hpp | 49 ++-- .../redis/{ssl => }/detail/handshaker.hpp | 4 +- include/boost/redis/detail/health_checker.hpp | 2 +- include/boost/redis/detail/reconnection.hpp | 20 +- include/boost/redis/detail/runner.hpp | 2 +- include/boost/redis/ssl/connection.hpp | 215 ------------------ tests/conn_check_health.cpp | 7 +- tests/conn_echo_stress.cpp | 3 +- tests/conn_exec.cpp | 9 +- tests/conn_exec_cancel.cpp | 6 +- tests/conn_exec_cancel2.cpp | 3 +- tests/conn_exec_error.cpp | 15 +- tests/conn_exec_retry.cpp | 6 +- tests/conn_push.cpp | 15 +- tests/conn_quit.cpp | 34 ++- tests/conn_reconnect.cpp | 6 +- tests/conn_run_cancel.cpp | 6 +- tests/conn_tls.cpp | 7 +- tests/issue_50.cpp | 7 +- tests/run.cpp | 15 +- 37 files changed, 251 insertions(+), 425 deletions(-) rename include/boost/redis/{ssl => }/detail/handshaker.hpp (97%) delete mode 100644 include/boost/redis/ssl/connection.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 9b8fdd6c..fb31b99f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -84,7 +84,7 @@ endif() #======================================================================= add_executable(cpp20_intro examples/cpp20_intro.cpp) -target_link_libraries(cpp20_intro common) +target_link_libraries(cpp20_intro PRIVATE OpenSSL::Crypto OpenSSL::SSL common) target_compile_features(cpp20_intro PUBLIC cxx_std_20) add_test(cpp20_intro cpp20_intro) if (MSVC) @@ -93,7 +93,7 @@ if (MSVC) endif() add_executable(cpp20_streams examples/cpp20_streams.cpp) -target_link_libraries(cpp20_streams common) +target_link_libraries(cpp20_streams PRIVATE OpenSSL::Crypto OpenSSL::SSL common) target_compile_features(cpp20_streams PUBLIC cxx_std_20) if (MSVC) target_compile_options(cpp20_streams PRIVATE /bigobj) @@ -101,6 +101,7 @@ if (MSVC) endif() add_executable(cpp17_intro examples/cpp17_intro.cpp) +target_link_libraries(cpp17_intro PRIVATE OpenSSL::Crypto OpenSSL::SSL) target_compile_features(cpp17_intro PUBLIC cxx_std_17) add_test(cpp17_intro cpp17_intro) if (MSVC) @@ -111,18 +112,19 @@ endif() if (NOT MSVC) add_executable(cpp17_intro_sync examples/cpp17_intro_sync.cpp) target_compile_features(cpp17_intro_sync PUBLIC cxx_std_17) +target_link_libraries(cpp17_intro_sync PRIVATE OpenSSL::Crypto OpenSSL::SSL) add_test(cpp17_intro_sync cpp17_intro_sync) endif() if (NOT MSVC) add_executable(cpp20_chat_room examples/cpp20_chat_room.cpp) target_compile_features(cpp20_chat_room PUBLIC cxx_std_20) -target_link_libraries(cpp20_chat_room common) +target_link_libraries(cpp20_chat_room PRIVATE OpenSSL::Crypto OpenSSL::SSL common) endif() add_executable(cpp20_containers examples/cpp20_containers.cpp) target_compile_features(cpp20_containers PUBLIC cxx_std_20) -target_link_libraries(cpp20_containers common) +target_link_libraries(cpp20_containers PRIVATE OpenSSL::Crypto OpenSSL::SSL common) add_test(cpp20_containers cpp20_containers) if (MSVC) target_compile_options(cpp20_containers PRIVATE /bigobj) @@ -132,12 +134,12 @@ endif() if (NOT MSVC) add_executable(cpp20_echo_server examples/cpp20_echo_server.cpp) target_compile_features(cpp20_echo_server PUBLIC cxx_std_20) -target_link_libraries(cpp20_echo_server common) +target_link_libraries(cpp20_echo_server PRIVATE OpenSSL::Crypto OpenSSL::SSL common) endif() add_executable(cpp20_resolve_with_sentinel examples/cpp20_resolve_with_sentinel.cpp) target_compile_features(cpp20_resolve_with_sentinel PUBLIC cxx_std_20) -target_link_libraries(cpp20_resolve_with_sentinel common) +target_link_libraries(cpp20_resolve_with_sentinel PRIVATE OpenSSL::Crypto OpenSSL::SSL common) #add_test(cpp20_resolve_with_sentinel cpp20_resolve_with_sentinel) if (MSVC) target_compile_options(cpp20_resolve_with_sentinel PRIVATE /bigobj) @@ -146,7 +148,7 @@ endif() add_executable(cpp20_json examples/cpp20_json.cpp) target_compile_features(cpp20_json PUBLIC cxx_std_20) -target_link_libraries(cpp20_json common) +target_link_libraries(cpp20_json PRIVATE OpenSSL::Crypto OpenSSL::SSL common) add_test(cpp20_json cpp20_json) if (MSVC) target_compile_options(cpp20_json PRIVATE /bigobj) @@ -157,7 +159,7 @@ if (Protobuf_FOUND) protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS examples/person.proto) add_executable(cpp20_protobuf examples/cpp20_protobuf.cpp ${PROTO_SRCS} ${PROTO_HDRS}) target_compile_features(cpp20_protobuf PUBLIC cxx_std_20) - target_link_libraries(cpp20_protobuf common ${Protobuf_LIBRARIES}) + target_link_libraries(cpp20_protobuf PRIVATE OpenSSL::Crypto OpenSSL::SSL common ${Protobuf_LIBRARIES}) target_include_directories(cpp20_protobuf PUBLIC ${Protobuf_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR}) add_test(cpp20_protobuf cpp20_protobuf) if (MSVC) @@ -168,7 +170,7 @@ endif() add_executable(cpp20_subscriber examples/cpp20_subscriber.cpp) target_compile_features(cpp20_subscriber PUBLIC cxx_std_20) -target_link_libraries(cpp20_subscriber common) +target_link_libraries(cpp20_subscriber PRIVATE OpenSSL::Crypto OpenSSL::SSL common) if (MSVC) target_compile_options(cpp20_subscriber PRIVATE /bigobj) target_compile_definitions(cpp20_subscriber PRIVATE _WIN32_WINNT=0x0601) @@ -177,8 +179,7 @@ endif() add_executable(cpp20_intro_tls examples/cpp20_intro_tls.cpp) target_compile_features(cpp20_intro_tls PUBLIC cxx_std_20) add_test(cpp20_intro_tls cpp20_intro_tls) -target_link_libraries(cpp20_intro_tls OpenSSL::Crypto OpenSSL::SSL) -target_link_libraries(cpp20_intro_tls common) +target_link_libraries(cpp20_intro_tls PRIVATE OpenSSL::Crypto OpenSSL::SSL common) if (MSVC) target_compile_options(cpp20_intro_tls PRIVATE /bigobj) target_compile_definitions(cpp20_intro_tls PRIVATE _WIN32_WINNT=0x0601) @@ -187,7 +188,7 @@ endif() add_executable(cpp20_low_level_async tests/cpp20_low_level_async.cpp) target_compile_features(cpp20_low_level_async PUBLIC cxx_std_20) add_test(cpp20_low_level_async cpp20_low_level_async) -target_link_libraries(cpp20_low_level_async common) +target_link_libraries(cpp20_low_level_async PRIVATE OpenSSL::Crypto OpenSSL::SSL common) if (MSVC) target_compile_options(cpp20_low_level_async PRIVATE /bigobj) target_compile_definitions(cpp20_low_level_async PRIVATE _WIN32_WINNT=0x0601) @@ -217,7 +218,7 @@ endif() add_executable(test_conn_exec tests/conn_exec.cpp) target_compile_features(test_conn_exec PUBLIC cxx_std_20) -target_link_libraries(test_conn_exec test_common) +target_link_libraries(test_conn_exec PRIVATE OpenSSL::Crypto OpenSSL::SSL test_common) add_test(test_conn_exec test_conn_exec) if (MSVC) target_compile_options(test_conn_exec PRIVATE /bigobj) @@ -226,7 +227,7 @@ endif() add_executable(test_conn_exec_retry tests/conn_exec_retry.cpp) target_compile_features(test_conn_exec_retry PUBLIC cxx_std_20) -target_link_libraries(test_conn_exec_retry test_common) +target_link_libraries(test_conn_exec_retry PRIVATE OpenSSL::Crypto OpenSSL::SSL test_common) add_test(test_conn_exec_retry test_conn_exec_retry) if (MSVC) target_compile_options(test_conn_exec_retry PRIVATE /bigobj) @@ -235,7 +236,7 @@ endif() add_executable(test_conn_push tests/conn_push.cpp) target_compile_features(test_conn_push PUBLIC cxx_std_20) -target_link_libraries(test_conn_push test_common) +target_link_libraries(test_conn_push PRIVATE OpenSSL::Crypto OpenSSL::SSL test_common) add_test(test_conn_push test_conn_push) if (MSVC) target_compile_options(test_conn_push PRIVATE /bigobj) @@ -244,6 +245,7 @@ endif() add_executable(test_conn_quit tests/conn_quit.cpp) target_compile_features(test_conn_quit PUBLIC cxx_std_17) +target_link_libraries(test_conn_quit PRIVATE OpenSSL::Crypto OpenSSL::SSL test_common) add_test(test_conn_quit test_conn_quit) if (MSVC) target_compile_options(test_conn_quit PRIVATE /bigobj) @@ -252,7 +254,7 @@ endif() add_executable(test_conn_reconnect tests/conn_reconnect.cpp) target_compile_features(test_conn_reconnect PUBLIC cxx_std_20) -target_link_libraries(test_conn_reconnect common test_common) +target_link_libraries(test_conn_reconnect PRIVATE OpenSSL::Crypto OpenSSL::SSL common test_common) add_test(test_conn_reconnect test_conn_reconnect) if (MSVC) target_compile_options(test_conn_reconnect PRIVATE /bigobj) @@ -262,7 +264,7 @@ endif() add_executable(test_conn_tls tests/conn_tls.cpp) add_test(test_conn_tls test_conn_tls) target_compile_features(test_conn_tls PUBLIC cxx_std_17) -target_link_libraries(test_conn_tls OpenSSL::Crypto OpenSSL::SSL) +target_link_libraries(test_conn_tls PRIVATE OpenSSL::Crypto OpenSSL::SSL) if (MSVC) target_compile_options(test_conn_tls PRIVATE /bigobj) target_compile_definitions(test_conn_tls PRIVATE _WIN32_WINNT=0x0601) @@ -279,6 +281,7 @@ endif() add_executable(test_conn_run_cancel tests/conn_run_cancel.cpp) target_compile_features(test_conn_run_cancel PUBLIC cxx_std_20) add_test(test_conn_run_cancel test_conn_run_cancel) +target_link_libraries(test_conn_run_cancel PRIVATE OpenSSL::Crypto OpenSSL::SSL) if (MSVC) target_compile_options(test_conn_run_cancel PRIVATE /bigobj) target_compile_definitions(test_conn_run_cancel PRIVATE _WIN32_WINNT=0x0601) @@ -286,7 +289,7 @@ endif() add_executable(test_conn_exec_cancel tests/conn_exec_cancel.cpp) target_compile_features(test_conn_exec_cancel PUBLIC cxx_std_20) -target_link_libraries(test_conn_exec_cancel common test_common) +target_link_libraries(test_conn_exec_cancel PRIVATE OpenSSL::Crypto OpenSSL::SSL common test_common) add_test(test_conn_exec_cancel test_conn_exec_cancel) if (MSVC) target_compile_options(test_conn_exec_cancel PRIVATE /bigobj) @@ -295,7 +298,7 @@ endif() add_executable(test_conn_exec_cancel2 tests/conn_exec_cancel2.cpp) target_compile_features(test_conn_exec_cancel2 PUBLIC cxx_std_20) -target_link_libraries(test_conn_exec_cancel2 common test_common) +target_link_libraries(test_conn_exec_cancel2 PRIVATE OpenSSL::Crypto OpenSSL::SSL common test_common) add_test(test_conn_exec_cancel2 test_conn_exec_cancel2) if (MSVC) target_compile_options(test_conn_exec_cancel2 PRIVATE /bigobj) @@ -304,7 +307,7 @@ endif() add_executable(test_conn_exec_error tests/conn_exec_error.cpp) target_compile_features(test_conn_exec_error PUBLIC cxx_std_17) -target_link_libraries(test_conn_exec_error common test_common) +target_link_libraries(test_conn_exec_error PRIVATE OpenSSL::Crypto OpenSSL::SSL common test_common) add_test(test_conn_exec_error test_conn_exec_error) if (MSVC) target_compile_options(test_conn_exec_error PRIVATE /bigobj) @@ -313,7 +316,7 @@ endif() add_executable(test_conn_echo_stress tests/conn_echo_stress.cpp) target_compile_features(test_conn_echo_stress PUBLIC cxx_std_20) -target_link_libraries(test_conn_echo_stress common test_common) +target_link_libraries(test_conn_echo_stress PRIVATE OpenSSL::Crypto OpenSSL::SSL common test_common) add_test(test_conn_echo_stress test_conn_echo_stress) if (MSVC) target_compile_options(test_conn_echo_stress PRIVATE /bigobj) @@ -330,7 +333,7 @@ endif() add_executable(test_issue_50 tests/issue_50.cpp) target_compile_features(test_issue_50 PUBLIC cxx_std_20) -target_link_libraries(test_issue_50 common) +target_link_libraries(test_issue_50 PRIVATE OpenSSL::Crypto OpenSSL::SSL common) add_test(test_issue_50 test_issue_50) if (MSVC) target_compile_options(test_issue_50 PRIVATE /bigobj) @@ -339,7 +342,7 @@ endif() add_executable(test_conn_check_health tests/conn_check_health.cpp) target_compile_features(test_conn_check_health PUBLIC cxx_std_17) -target_link_libraries(test_conn_check_health common) +target_link_libraries(test_conn_check_health PRIVATE OpenSSL::Crypto OpenSSL::SSL common) add_test(test_conn_check_health test_conn_check_health) if (MSVC) target_compile_options(test_conn_check_health PRIVATE /bigobj) @@ -348,7 +351,7 @@ endif() add_executable(test_run tests/run.cpp) target_compile_features(test_run PUBLIC cxx_std_17) -target_link_libraries(test_run test_common) +target_link_libraries(test_run PRIVATE OpenSSL::Crypto OpenSSL::SSL test_common) add_test(test_run test_run) if (MSVC) target_compile_options(test_run PRIVATE /bigobj) diff --git a/README.md b/README.md index 4184916a..7bc5da0f 100644 --- a/README.md +++ b/README.md @@ -678,7 +678,7 @@ Acknowledgement to people that helped shape Boost.Redis * Mohammad Nejati ([ashtum](https://github.com/ashtum)): For pointing out scenarios where calls to `async_exec` should fail when the connection is lost. * Klemens Morgenstern ([klemens-morgenstern](https://github.com/klemens-morgenstern)): For useful discussion about timeouts, cancellation, synchronous interfaces and general help with Asio. * Vinnie Falco ([vinniefalco](https://github.com/vinniefalco)): For general suggestions about how to improve the code and the documentation. -* Bram Veldhoen ([bveldhoen](https://github.com/bveldhoen)): For contributing a Redis streams example. +* Bram Veldhoen ([bveldhoen](https://github.com/bveldhoen)): For contributing a Redis-streams example. Also many thanks to all individuals that participated in the Boost review diff --git a/examples/cpp17_intro.cpp b/examples/cpp17_intro.cpp index c6cbd6c8..d94bd2b2 100644 --- a/examples/cpp17_intro.cpp +++ b/examples/cpp17_intro.cpp @@ -32,7 +32,8 @@ auto main(int argc, char * argv[]) -> int response resp; net::io_context ioc; - connection conn{ioc}; + net::ssl::context ctx{net::ssl::context::tls_client}; + connection conn{ioc, ctx}; conn.async_run(cfg, {}, net::detached); diff --git a/examples/cpp17_intro_sync.cpp b/examples/cpp17_intro_sync.cpp index 1969c3ce..06ba47a1 100644 --- a/examples/cpp17_intro_sync.cpp +++ b/examples/cpp17_intro_sync.cpp @@ -12,6 +12,7 @@ // Include this in no more than one .cpp file. #include +namespace net = boost::asio; using boost::redis::sync_connection; using boost::redis::request; using boost::redis::response; @@ -27,7 +28,8 @@ auto main(int argc, char * argv[]) -> int cfg.addr.port = argv[2]; } - sync_connection conn; + net::ssl::context ctx{net::ssl::context::tls_client}; + sync_connection conn{ctx}; conn.run(cfg); request req; diff --git a/examples/cpp20_chat_room.cpp b/examples/cpp20_chat_room.cpp index 7ef27c1e..29025031 100644 --- a/examples/cpp20_chat_room.cpp +++ b/examples/cpp20_chat_room.cpp @@ -38,7 +38,7 @@ receiver(std::shared_ptr conn) -> net::awaitable request req; req.push("SUBSCRIBE", "channel"); - while (!conn->is_cancelled()) { + while (conn->will_reconnect()) { // Subscribe to channels. co_await conn->async_exec(req); @@ -75,12 +75,13 @@ auto publisher(std::shared_ptr in, std::shared_ptr net::awaitable { auto ex = co_await net::this_coro::executor; - auto conn = std::make_shared(ex); + auto ctx = std::make_shared(net::ssl::context::tls_client); + auto conn = std::make_shared(ex, *ctx); auto stream = std::make_shared(ex, ::dup(STDIN_FILENO)); net::co_spawn(ex, receiver(conn), net::detached); net::co_spawn(ex, publisher(stream, conn), net::detached); - conn->async_run(cfg, {}, net::consign(net::detached, conn)); + conn->async_run(cfg, {}, net::consign(net::detached, conn, ctx)); signal_set sig_set{ex, SIGINT, SIGTERM}; co_await sig_set.async_wait(); diff --git a/examples/cpp20_containers.cpp b/examples/cpp20_containers.cpp index cbea9330..ea4b440c 100644 --- a/examples/cpp20_containers.cpp +++ b/examples/cpp20_containers.cpp @@ -91,8 +91,9 @@ auto transaction(std::shared_ptr conn) -> net::awaitable // Called from the main function (see main.cpp) net::awaitable co_main(config cfg) { - auto conn = std::make_shared(co_await net::this_coro::executor); - conn->async_run(cfg, {}, net::consign(net::detached, conn)); + auto ctx = std::make_shared(net::ssl::context::tls_client); + auto conn = std::make_shared(co_await net::this_coro::executor, *ctx); + conn->async_run(cfg, {}, net::consign(net::detached, conn, ctx)); co_await store(conn); co_await transaction(conn); diff --git a/examples/cpp20_echo_server.cpp b/examples/cpp20_echo_server.cpp index 6d2691fb..f69b2bff 100644 --- a/examples/cpp20_echo_server.cpp +++ b/examples/cpp20_echo_server.cpp @@ -58,9 +58,10 @@ auto listener(std::shared_ptr conn) -> net::awaitable auto co_main(config cfg) -> net::awaitable { auto ex = co_await net::this_coro::executor; - auto conn = std::make_shared(ex); + auto ctx = std::make_shared(net::ssl::context::tls_client); + auto conn = std::make_shared(ex, *ctx); net::co_spawn(ex, listener(conn), net::detached); - conn->async_run(cfg, {}, net::consign(net::detached, conn)); + conn->async_run(cfg, {}, net::consign(net::detached, conn, ctx)); signal_set sig_set(ex, SIGINT, SIGTERM); co_await sig_set.async_wait(); diff --git a/examples/cpp20_intro.cpp b/examples/cpp20_intro.cpp index 3526950d..f4c21406 100644 --- a/examples/cpp20_intro.cpp +++ b/examples/cpp20_intro.cpp @@ -23,8 +23,9 @@ using connection = net::deferred_t::as_default_on_t; // Called from the main function (see main.cpp) auto co_main(config cfg) -> net::awaitable { - auto conn = std::make_shared(co_await net::this_coro::executor); - conn->async_run(cfg, {}, net::consign(net::detached, conn)); + auto ctx = std::make_shared(net::ssl::context::tls_client); + auto conn = std::make_shared(co_await net::this_coro::executor, *ctx); + conn->async_run(cfg, {}, net::consign(net::detached, conn, ctx)); // A request containing only a ping command. request req; diff --git a/examples/cpp20_intro_tls.cpp b/examples/cpp20_intro_tls.cpp index 33c36e0b..5a27bfac 100644 --- a/examples/cpp20_intro_tls.cpp +++ b/examples/cpp20_intro_tls.cpp @@ -4,7 +4,7 @@ * accompanying file LICENSE.txt) */ -#include +#include #include #include #include @@ -18,7 +18,7 @@ using boost::redis::request; using boost::redis::response; using boost::redis::config; using boost::redis::logger; -using connection = net::deferred_t::as_default_on_t; +using connection = net::deferred_t::as_default_on_t; auto verify_certificate(bool, net::ssl::verify_context&) -> bool { @@ -28,14 +28,15 @@ auto verify_certificate(bool, net::ssl::verify_context&) -> bool auto co_main(config cfg) -> net::awaitable { + cfg.use_ssl = true; cfg.username = "aedis"; cfg.password = "aedis"; cfg.addr.host = "db.occase.de"; cfg.addr.port = "6380"; - net::ssl::context ctx{net::ssl::context::sslv23}; - auto conn = std::make_shared(co_await net::this_coro::executor, ctx); - conn->async_run(cfg, {}, net::consign(net::detached, conn)); + auto ctx = std::make_shared(net::ssl::context::tls_client); + auto conn = std::make_shared(co_await net::this_coro::executor, *ctx); + conn->async_run(cfg, {}, net::consign(net::detached, conn, ctx)); request req; req.push("PING"); diff --git a/examples/cpp20_json.cpp b/examples/cpp20_json.cpp index 6117dedd..b5ab5d55 100644 --- a/examples/cpp20_json.cpp +++ b/examples/cpp20_json.cpp @@ -48,8 +48,9 @@ void boost_redis_from_bulk(user& u, std::string_view sv, boost::system::error_co auto co_main(config cfg) -> net::awaitable { auto ex = co_await net::this_coro::executor; - auto conn = std::make_shared(ex); - conn->async_run(cfg, {}, net::consign(net::detached, conn)); + auto ctx = std::make_shared(net::ssl::context::tls_client); + auto conn = std::make_shared(ex, *ctx); + conn->async_run(cfg, {}, net::consign(net::detached, conn, ctx)); // user object that will be stored in Redis in json format. user const u{"Joao", "58", "Brazil"}; diff --git a/examples/cpp20_protobuf.cpp b/examples/cpp20_protobuf.cpp index d372798f..a1754860 100644 --- a/examples/cpp20_protobuf.cpp +++ b/examples/cpp20_protobuf.cpp @@ -47,8 +47,9 @@ using tutorial::boost_redis_from_bulk; net::awaitable co_main(config cfg) { auto ex = co_await net::this_coro::executor; - auto conn = std::make_shared(ex); - conn->async_run(cfg, {}, net::consign(net::detached, conn)); + auto ctx = std::make_shared(net::ssl::context::tls_client); + auto conn = std::make_shared(ex, *ctx); + conn->async_run(cfg, {}, net::consign(net::detached, conn, ctx)); person p; p.set_name("Louis"); diff --git a/examples/cpp20_resolve_with_sentinel.cpp b/examples/cpp20_resolve_with_sentinel.cpp index 70aeaed0..a624fd12 100644 --- a/examples/cpp20_resolve_with_sentinel.cpp +++ b/examples/cpp20_resolve_with_sentinel.cpp @@ -34,7 +34,8 @@ auto resolve_master_address(std::vector
const& addresses) -> net::await req.push("SENTINEL", "get-master-addr-by-name", "mymaster"); req.push("QUIT"); - auto conn = std::make_shared(co_await net::this_coro::executor); + auto ctx = std::make_shared(net::ssl::context::tls_client); + auto conn = std::make_shared(co_await net::this_coro::executor, *ctx); response>, ignore_t> resp; for (auto addr : addresses) { @@ -44,7 +45,7 @@ auto resolve_master_address(std::vector
const& addresses) -> net::await // TODO: async_run and async_exec should be lauched in // parallel here so we can wait for async_run completion // before eventually calling it again. - conn->async_run(cfg, {}, net::consign(net::detached, conn)); + conn->async_run(cfg, {}, net::consign(net::detached, conn, ctx)); co_await conn->async_exec(req, resp, redir(ec)); conn->cancel(); conn->reset_stream(); diff --git a/examples/cpp20_streams.cpp b/examples/cpp20_streams.cpp index 5dcae830..909f4204 100644 --- a/examples/cpp20_streams.cpp +++ b/examples/cpp20_streams.cpp @@ -84,12 +84,13 @@ auto stream_reader(std::shared_ptr conn) -> net::awaitable auto co_main(config cfg) -> net::awaitable { auto ex = co_await net::this_coro::executor; - auto conn = std::make_shared(ex); + auto ctx = std::make_shared(net::ssl::context::tls_client); + auto conn = std::make_shared(ex, *ctx); net::co_spawn(ex, stream_reader(conn), net::detached); // Disable health checks. cfg.health_check_interval = std::chrono::seconds{0}; - conn->async_run(cfg, {}, net::consign(net::detached, conn)); + conn->async_run(cfg, {}, net::consign(net::detached, conn, ctx)); signal_set sig_set(ex, SIGINT, SIGTERM); co_await sig_set.async_wait(); diff --git a/examples/cpp20_subscriber.cpp b/examples/cpp20_subscriber.cpp index 5bc76a7d..c7aaa98f 100644 --- a/examples/cpp20_subscriber.cpp +++ b/examples/cpp20_subscriber.cpp @@ -51,7 +51,7 @@ receiver(std::shared_ptr conn) -> net::awaitable request req; req.push("SUBSCRIBE", "channel"); - while (!conn->is_cancelled()) { + while (conn->will_reconnect()) { // Reconnect to channels. co_await conn->async_exec(req); @@ -75,9 +75,10 @@ receiver(std::shared_ptr conn) -> net::awaitable auto co_main(config cfg) -> net::awaitable { auto ex = co_await net::this_coro::executor; - auto conn = std::make_shared(ex); + auto ctx = std::make_shared(net::ssl::context::tls_client); + auto conn = std::make_shared(ex, *ctx); net::co_spawn(ex, receiver(conn), net::detached); - conn->async_run(cfg, {}, net::consign(net::detached, conn)); + conn->async_run(cfg, {}, net::consign(net::detached, conn, ctx)); signal_set sig_set(ex, SIGINT, SIGTERM); co_await sig_set.async_wait(); diff --git a/examples/sync_connection.hpp b/examples/sync_connection.hpp index cc982f84..a432ad91 100644 --- a/examples/sync_connection.hpp +++ b/examples/sync_connection.hpp @@ -20,9 +20,9 @@ namespace boost::redis class sync_connection { public: - sync_connection() + sync_connection(boost::asio::ssl::context& ctx) : ioc_{1} - , conn_{std::make_shared(ioc_)} + , conn_{std::make_shared(ioc_, ctx)} { } ~sync_connection() diff --git a/include/boost/redis/config.hpp b/include/boost/redis/config.hpp index 0abc4016..01e4caa6 100644 --- a/include/boost/redis/config.hpp +++ b/include/boost/redis/config.hpp @@ -27,6 +27,9 @@ struct address { * @ingroup high-level-api */ struct config { + /// Uses SSL instead of a plain connection. + bool use_ssl = false; + /// Address of the Redis server. address addr = address{"127.0.0.1", "6379"}; @@ -60,10 +63,16 @@ struct config { /// Time the SSL handshake operation is allowed to last. std::chrono::steady_clock::duration ssl_handshake_timeout = std::chrono::seconds{10}; - /// @brief Health checks interval. + /** Health checks interval. + * + * To disable health-checks pass zero as duration. + */ std::chrono::steady_clock::duration health_check_interval = std::chrono::seconds{2}; - /// Time waited before trying a reconnection. + /** @brief Time waited before trying a reconnection. + * + * To disable reconnection pass zero as duration. + */ std::chrono::steady_clock::duration reconnect_wait_interval = std::chrono::seconds{1}; }; diff --git a/include/boost/redis/connection.hpp b/include/boost/redis/connection.hpp index 931e4fbb..d8deb2e5 100644 --- a/include/boost/redis/connection.hpp +++ b/include/boost/redis/connection.hpp @@ -9,101 +9,79 @@ #include #include +#include #include #include #include #include +#include #include +#include #include #include namespace boost::redis { -namespace detail -{ - -template -class dummy_handshaker { -public: - dummy_handshaker(Executor) {} - - template - auto async_handshake(Stream&, CompletionToken&& token) - { return asio::post(std::move(token)); } - - void set_config(config const&) {} - - std::size_t cancel(operation) { return 0;} - - constexpr bool is_dummy() const noexcept {return true;} -}; - -} - -/** @brief A connection to the Redis server. - * @ingroup high-level-api +/** \brief A SSL connection to the Redis server. + * \ingroup high-level-api * - * For more details, please see the documentation of each individual - * function. + * This class keeps a healthy connection to the Redis instance where + * commands can be sent at any time. For more details, please see the + * documentation of each individual function. * * @tparam Socket The socket type e.g. asio::ip::tcp::socket. + * */ -template -class basic_connection : - private detail::connection_base< - typename Socket::executor_type, - basic_connection> { +template +class basic_connection : private detail::connection_base> { public: - /// Executor type. - using executor_type = typename Socket::executor_type; - /// Type of the next layer - using next_layer_type = Socket; + using next_layer_type = asio::ssl::stream>; + + /// Executor type. + using executor_type = Executor; /// Rebinds the socket type to another executor. template struct rebind_executor { - /// The socket type when rebound to the specified executor. - using other = basic_connection::other>; + /// The connection type when rebound to the specified executor. + using other = basic_connection; }; - using base_type = detail::connection_base>; + using base_type = redis::detail::connection_base>; /// Contructs from an executor. explicit - basic_connection(executor_type ex) + basic_connection(executor_type ex, asio::ssl::context& ctx) : base_type{ex} + , ctx_{&ctx} , reconn_{ex} , runner_{ex, {}} - , stream_{ex} - {} + , stream_{std::make_unique(ex, ctx)} + { } /// Contructs from a context. explicit - basic_connection(asio::io_context& ioc) - : basic_connection(ioc.get_executor()) + basic_connection(asio::io_context& ioc, asio::ssl::context& ctx) + : basic_connection(ioc.get_executor(), ctx) { } /// Returns the associated executor. - auto get_executor() {return stream_.get_executor();} + auto get_executor() {return stream_->get_executor();} - /// Resets the underlying stream. + /// Reset the underlying stream. void reset_stream() { - if (stream_.is_open()) { - system::error_code ec; - stream_.shutdown(asio::ip::tcp::socket::shutdown_both, ec); - stream_.close(ec); - } + stream_ = std::make_unique(stream_->get_executor(), *ctx_); } /// Returns a reference to the next layer. - auto next_layer() noexcept -> auto& { return stream_; } + auto& next_layer() noexcept { return *stream_; } /// Returns a const reference to the next layer. - auto next_layer() const noexcept -> auto const& { return stream_; } + auto const& next_layer() const noexcept { return *stream_; } /** @brief Starts underlying connection operations. * @@ -153,7 +131,8 @@ class basic_connection : Logger l = Logger{}, CompletionToken token = CompletionToken{}) { - reconn_.set_wait_interval(cfg.reconnect_wait_interval); + use_ssl_ = cfg.use_ssl; + reconn_.set_config(cfg.reconnect_wait_interval); runner_.set_config(cfg); l.set_prefix(runner_.get_config().log_prefix); return reconn_.async_run(*this, l, std::move(token)); @@ -262,11 +241,11 @@ class basic_connection : { base_type::reserve(read, write); } /// Returns true if the connection was canceled. - bool is_cancelled() const noexcept - { return reconn_.is_cancelled();} + bool will_reconnect() const noexcept + { return reconn_.will_reconnect();} private: - using runner_type = detail::runner; + using runner_type = detail::runner; using reconnection_type = detail::basic_reconnection; using this_type = basic_connection; @@ -290,20 +269,27 @@ class basic_connection : { return base_type::async_run_impl(l, std::move(token)); } void close() - { reset_stream(); } + { + if (stream_->next_layer().is_open()) + stream_->next_layer().close(); + } + + auto is_open() const noexcept { return stream_->next_layer().is_open(); } + auto& lowest_layer() noexcept { return stream_->lowest_layer(); } - auto is_open() const noexcept { return stream_.is_open(); } - auto lowest_layer() noexcept -> auto& { return stream_.lowest_layer(); } + auto use_ssl() const noexcept { return use_ssl_;} + bool use_ssl_ = false; + asio::ssl::context* ctx_; reconnection_type reconn_; runner_type runner_; - Socket stream_; + std::unique_ptr stream_; }; -/** \brief A connection that uses a asio::ip::tcp::socket. +/** \brief A connection that uses the asio::any_io_executor. * \ingroup high-level-api */ -using connection = basic_connection; +using connection = basic_connection; } // boost::redis diff --git a/include/boost/redis/detail/connection_base.hpp b/include/boost/redis/detail/connection_base.hpp index 7b51608d..de22bd5d 100644 --- a/include/boost/redis/detail/connection_base.hpp +++ b/include/boost/redis/detail/connection_base.hpp @@ -83,6 +83,11 @@ class read_next_op { , cmds_{info->get_number_of_commands()} {} + auto make_adapter() noexcept + { + return [i = index_, adpt = adapter_] (resp3::basic_node const& nd, system::error_code& ec) mutable { adpt(i, nd, ec); }; + } + template void operator()( Self& self @@ -103,11 +108,12 @@ class read_next_op { // to hand it to the push consumer. To do that we need // some data in the read bufer. if (conn_->read_buffer_.empty()) { - BOOST_ASIO_CORO_YIELD - asio::async_read_until( - conn_->next_layer(), - conn_->make_dynamic_buffer(), - "\r\n", std::move(self)); + + if (conn_->derived().use_ssl()) + BOOST_ASIO_CORO_YIELD asio::async_read_until(conn_->next_layer(), conn_->make_dynamic_buffer(), "\r\n", std::move(self)); + else + BOOST_ASIO_CORO_YIELD asio::async_read_until(conn_->next_layer().next_layer(), conn_->make_dynamic_buffer(), "\r\n", std::move(self)); + BOOST_REDIS_CHECK_OP1(conn_->cancel(operation::run);); if (info_->stop_requested()) { self.complete(asio::error::operation_aborted, 0); @@ -125,12 +131,10 @@ class read_next_op { } //----------------------------------- - BOOST_ASIO_CORO_YIELD - redis::detail::async_read( - conn_->next_layer(), - conn_->make_dynamic_buffer(), - [i = index_, adpt = adapter_] (resp3::basic_node const& nd, system::error_code& ec) mutable { adpt(i, nd, ec); }, - std::move(self)); + if (conn_->derived().use_ssl()) + BOOST_ASIO_CORO_YIELD redis::detail::async_read(conn_->next_layer(), conn_->make_dynamic_buffer(), make_adapter(), std::move(self)); + else + BOOST_ASIO_CORO_YIELD redis::detail::async_read(conn_->next_layer().next_layer(), conn_->make_dynamic_buffer(), make_adapter(), std::move(self)); ++index_; @@ -166,8 +170,11 @@ struct receive_op { conn->channel_.async_receive(std::move(self)); BOOST_REDIS_CHECK_OP1(;); - BOOST_ASIO_CORO_YIELD - redis::detail::async_read(conn->next_layer(), conn->make_dynamic_buffer(), adapter, std::move(self)); + if (conn->derived().use_ssl()) + BOOST_ASIO_CORO_YIELD redis::detail::async_read(conn->next_layer(), conn->make_dynamic_buffer(), adapter, std::move(self)); + else + BOOST_ASIO_CORO_YIELD redis::detail::async_read(conn->next_layer().next_layer(), conn->make_dynamic_buffer(), adapter, std::move(self)); + if (ec || is_cancelled(self)) { conn->cancel(operation::run); conn->cancel(operation::receive); @@ -342,8 +349,11 @@ struct writer_op { BOOST_ASIO_CORO_REENTER (coro) for (;;) { while (conn_->coalesce_requests()) { - BOOST_ASIO_CORO_YIELD - asio::async_write(conn_->next_layer(), asio::buffer(conn_->write_buffer_), std::move(self)); + if (conn_->derived().use_ssl()) + BOOST_ASIO_CORO_YIELD asio::async_write(conn_->next_layer(), asio::buffer(conn_->write_buffer_), std::move(self)); + else + BOOST_ASIO_CORO_YIELD asio::async_write(conn_->next_layer().next_layer(), asio::buffer(conn_->write_buffer_), std::move(self)); + logger_.on_write(ec, conn_->write_buffer_); BOOST_REDIS_CHECK_OP0(conn_->cancel(operation::run);); @@ -394,11 +404,10 @@ struct reader_op { BOOST_ASIO_CORO_REENTER (coro) for (;;) { - BOOST_ASIO_CORO_YIELD - asio::async_read_until( - conn->next_layer(), - conn->make_dynamic_buffer(), - "\r\n", std::move(self)); + if (conn->derived().use_ssl()) + BOOST_ASIO_CORO_YIELD asio::async_read_until(conn->next_layer(), conn->make_dynamic_buffer(), "\r\n", std::move(self)); + else + BOOST_ASIO_CORO_YIELD asio::async_read_until(conn->next_layer().next_layer(), conn->make_dynamic_buffer(), "\r\n", std::move(self)); if (ec == asio::error::eof) { conn->cancel(operation::run); diff --git a/include/boost/redis/ssl/detail/handshaker.hpp b/include/boost/redis/detail/handshaker.hpp similarity index 97% rename from include/boost/redis/ssl/detail/handshaker.hpp rename to include/boost/redis/detail/handshaker.hpp index 987c6ef3..6658aa9b 100644 --- a/include/boost/redis/ssl/detail/handshaker.hpp +++ b/include/boost/redis/detail/handshaker.hpp @@ -19,7 +19,7 @@ #include #include -namespace boost::redis::ssl::detail +namespace boost::redis::detail { template @@ -119,6 +119,6 @@ class handshaker { std::chrono::steady_clock::duration timeout_; }; -} // boost::redis::ssl::detail +} // boost::redis::detail #endif // BOOST_REDIS_SSL_CONNECTOR_HPP diff --git a/include/boost/redis/detail/health_checker.hpp b/include/boost/redis/detail/health_checker.hpp index 374f2b87..d6c2143c 100644 --- a/include/boost/redis/detail/health_checker.hpp +++ b/include/boost/redis/detail/health_checker.hpp @@ -107,7 +107,7 @@ class check_health_op { { BOOST_ASIO_CORO_REENTER (coro_) { - if (checker_->ping_interval_.count() == 0) { + if (checker_->ping_interval_ == std::chrono::seconds::zero()) { BOOST_ASIO_CORO_YIELD asio::post(std::move(self)); self.complete({}); diff --git a/include/boost/redis/detail/reconnection.hpp b/include/boost/redis/detail/reconnection.hpp index d021a3d9..88ee1817 100644 --- a/include/boost/redis/detail/reconnection.hpp +++ b/include/boost/redis/detail/reconnection.hpp @@ -30,10 +30,9 @@ struct reconnection_op { { BOOST_ASIO_CORO_YIELD conn_->async_run_one(logger_, std::move(self)); - conn_->reset_stream(); conn_->cancel(operation::receive); logger_.on_connection_lost(ec); - if (reconn_->is_cancelled() || is_cancelled(self)) { + if (!reconn_->will_reconnect() || is_cancelled(self)) { reconn_->cancel(operation::reconnection); self.complete(!!ec ? ec : asio::error::operation_aborted); return; @@ -43,10 +42,11 @@ struct reconnection_op { BOOST_ASIO_CORO_YIELD reconn_->timer_.async_wait(std::move(self)); BOOST_REDIS_CHECK_OP0(;) - if (reconn_->is_cancelled()) { + if (!reconn_->will_reconnect()) { self.complete(asio::error::operation_aborted); return; } + conn_->reset_stream(); } } }; @@ -56,19 +56,16 @@ struct reconnection_op { template class basic_reconnection { public: - /// Executor type. using executor_type = Executor; basic_reconnection(Executor ex) : timer_{ex} - , is_cancelled_{false} {} basic_reconnection(asio::io_context& ioc, std::chrono::steady_clock::duration wait_interval) : basic_reconnection{ioc.get_executor(), wait_interval} {} - /// Rebinds to a new executor type. template struct rebind_executor { @@ -92,7 +89,7 @@ class basic_reconnection { >(detail::reconnection_op{this, &conn, l}, token, conn); } - void set_wait_interval(std::chrono::steady_clock::duration wait_interval) + void set_config(std::chrono::steady_clock::duration wait_interval) { wait_interval_ = wait_interval; } @@ -102,7 +99,7 @@ class basic_reconnection { switch (op) { case operation::reconnection: case operation::all: - is_cancelled_ = true; + wait_interval_ = std::chrono::seconds::zero(); timer_.cancel(); break; default: /* ignore */; @@ -111,8 +108,8 @@ class basic_reconnection { return 0U; } - bool is_cancelled() const noexcept {return is_cancelled_;} - void reset() noexcept {is_cancelled_ = false;} + bool will_reconnect() const noexcept + { return wait_interval_ != std::chrono::seconds::zero();} private: using timer_type = @@ -125,11 +122,8 @@ class basic_reconnection { timer_type timer_; std::chrono::steady_clock::duration wait_interval_ = std::chrono::seconds{1}; - bool is_cancelled_; }; -using reconnection = basic_reconnection; - } // boost::redis #endif // BOOST_REDIS_RECONNECTION_HPP diff --git a/include/boost/redis/detail/runner.hpp b/include/boost/redis/detail/runner.hpp index b9329187..1fa4c0c7 100644 --- a/include/boost/redis/detail/runner.hpp +++ b/include/boost/redis/detail/runner.hpp @@ -136,7 +136,7 @@ struct run_all_op { logger_.on_connect(ec, runner_->ctor_.endpoint()); BOOST_REDIS_CHECK_OP0(conn_->cancel(operation::run);) - if (!runner_->hsher_.is_dummy()) { + if (conn_->use_ssl()) { BOOST_ASIO_CORO_YIELD runner_->hsher_.async_handshake(conn_->next_layer(), std::move(self)); logger_.on_ssl_handshake(ec); diff --git a/include/boost/redis/ssl/connection.hpp b/include/boost/redis/ssl/connection.hpp deleted file mode 100644 index 37da52a8..00000000 --- a/include/boost/redis/ssl/connection.hpp +++ /dev/null @@ -1,215 +0,0 @@ -/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com) - * - * Distributed under the Boost Software License, Version 1.0. (See - * accompanying file LICENSE.txt) - */ - -#ifndef BOOST_REDIS_SSL_CONNECTION_HPP -#define BOOST_REDIS_SSL_CONNECTION_HPP - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -namespace boost::redis::ssl { - -template -class basic_connection; - -/** \brief A SSL connection to the Redis server. - * \ingroup high-level-api - * - * This class keeps a healthy connection to the Redis instance where - * commands can be sent at any time. For more details, please see the - * documentation of each individual function. - * - * @tparam Socket The socket type e.g. asio::ip::tcp::socket. - * - */ -template -class basic_connection> : - private redis::detail::connection_base< - typename asio::ssl::stream::executor_type, - basic_connection>> { -public: - /// Type of the next layer - using next_layer_type = asio::ssl::stream; - - /// Executor type. - using executor_type = typename next_layer_type::executor_type; - - /// Rebinds the socket type to another executor. - template - struct rebind_executor - { - /// The socket type when rebound to the specified executor. - using other = basic_connection::other>>; - }; - - using base_type = redis::detail::connection_base>>; - - /// Constructor - explicit - basic_connection(executor_type ex, asio::ssl::context& ctx) - : base_type{ex} - , ctx_{&ctx} - , reconn_{ex} - , runner_{ex, {}} - , stream_{std::make_unique(ex, ctx)} - { } - - /// Constructor - explicit - basic_connection(asio::io_context& ioc, asio::ssl::context& ctx) - : basic_connection(ioc.get_executor(), ctx) - { } - - /// Returns the associated executor. - auto get_executor() {return stream_->get_executor();} - - /// Reset the underlying stream. - void reset_stream() - { - if (stream_->next_layer().is_open()) { - stream_->next_layer().close(); - stream_ = std::make_unique(stream_->get_executor(), *ctx_); - } - } - - /// Returns a reference to the next layer. - auto& next_layer() noexcept { return *stream_; } - - /// Returns a const reference to the next layer. - auto const& next_layer() const noexcept { return *stream_; } - - /** @brief Establishes a connection with the Redis server asynchronously. - * - * See redis::connection::async_run for more information. - */ - template < - class Logger = logger, - class CompletionToken = asio::default_completion_token_t> - auto - async_run( - config const& cfg = {}, - Logger l = Logger{}, - CompletionToken token = CompletionToken{}) - { - reconn_.set_wait_interval(cfg.reconnect_wait_interval); - runner_.set_config(cfg); - l.set_prefix(runner_.get_config().log_prefix); - return reconn_.async_run(*this, l, std::move(token)); - } - - /** @brief Executes a command on the Redis server asynchronously. - * - * See redis::connection::async_exec for more information. - */ - template < - class Response = ignore_t, - class CompletionToken = asio::default_completion_token_t> - auto async_exec( - request const& req, - Response& response = ignore, - CompletionToken token = CompletionToken{}) - { - return base_type::async_exec(req, response, std::move(token)); - } - - /** @brief Receives server side pushes asynchronously. - * - * See redis::connection::async_receive for detailed information. - */ - template < - class Response = ignore_t, - class CompletionToken = asio::default_completion_token_t> - auto async_receive( - Response& response = ignore, - CompletionToken token = CompletionToken{}) - { - return base_type::async_receive(response, std::move(token)); - } - - /** @brief Cancel operations. - * - * See redis::connection::cancel for more information. - */ - auto cancel(operation op = operation::all) -> std::size_t - { - reconn_.cancel(op); - runner_.cancel(op); - return base_type::cancel(op); - } - - auto& lowest_layer() noexcept { return stream_->lowest_layer(); } - - /// Sets the maximum size of the read buffer. - void set_max_buffer_read_size(std::size_t max_read_size) noexcept - { base_type::set_max_buffer_read_size(max_read_size); } - - /** @brief Reserve memory on the read and write internal buffers. - * - * This function will call `std::string::reserve` on the - * underlying buffers. - * - * @param read The new capacity of the read buffer. - * @param write The new capacity of the write buffer. - */ - void reserve(std::size_t read, std::size_t write) - { base_type::reserve(read, write); } - - /// Returns true if the connection was canceled. - bool is_cancelled() const noexcept - { return reconn_.is_cancelled();} - -private: - using runner_type = redis::detail::runner; - using reconnection_type = redis::detail::basic_reconnection; - using this_type = basic_connection; - - template - auto async_run_one(Logger l, CompletionToken token) - { return runner_.async_run(*this, l, std::move(token)); } - - template - auto async_run_impl(Logger l, CompletionToken token) - { return base_type::async_run_impl(l, std::move(token)); } - - template friend class redis::detail::connection_base; - template friend class redis::detail::read_next_op; - template friend struct redis::detail::exec_op; - template friend struct redis::detail::receive_op; - template friend struct redis::detail::run_op; - template friend struct redis::detail::writer_op; - template friend struct redis::detail::reader_op; - template friend struct redis::detail::wait_receive_op; - template friend struct redis::detail::run_all_op; - template friend struct redis::detail::reconnection_op; - - auto is_open() const noexcept { return stream_->next_layer().is_open(); } - - void close() - { reset_stream(); } - - asio::ssl::context* ctx_; - reconnection_type reconn_; - runner_type runner_; - std::unique_ptr stream_; -}; - -/** \brief A connection that uses a boost::asio::ssl::stream. - * \ingroup high-level-api - */ -using connection = basic_connection>; - -} // boost::redis::ssl - -#endif // BOOST_REDIS_SSL_CONNECTION_HPP diff --git a/tests/conn_check_health.cpp b/tests/conn_check_health.cpp index 34a22496..25d76d8a 100644 --- a/tests/conn_check_health.cpp +++ b/tests/conn_check_health.cpp @@ -75,14 +75,15 @@ BOOST_AUTO_TEST_CASE(check_health) net::io_context ioc; - connection conn1{ioc}; - conn1.cancel(operation::reconnection); + net::ssl::context ctx{net::ssl::context::tls_client}; + connection conn1{ioc, ctx}; request req1; req1.push("CLIENT", "PAUSE", "10000", "ALL"); config cfg1; cfg1.health_check_id = "conn1"; + cfg1.reconnect_wait_interval = std::chrono::seconds::zero(); error_code res1; conn1.async_run(cfg1, {}, [&](auto ec) { std::cout << "async_run 1 completed: " << ec.message() << std::endl; @@ -93,7 +94,7 @@ BOOST_AUTO_TEST_CASE(check_health) // It looks like client pause does not work for clients that are // sending MONITOR. I will therefore open a second connection. - connection conn2{ioc}; + connection conn2{ioc, ctx}; config cfg2; cfg2.health_check_id = "conn2"; diff --git a/tests/conn_echo_stress.cpp b/tests/conn_echo_stress.cpp index 3adb1a0f..c76e9e10 100644 --- a/tests/conn_echo_stress.cpp +++ b/tests/conn_echo_stress.cpp @@ -65,7 +65,8 @@ auto echo_session(std::shared_ptr conn, std::string id, int n) -> ne auto async_echo_stress() -> net::awaitable { auto ex = co_await net::this_coro::executor; - auto conn = std::make_shared(ex); + net::ssl::context ctx{net::ssl::context::tls_client}; + auto conn = std::make_shared(ex, ctx); int const sessions = 500; int const msgs = 1000; diff --git a/tests/conn_exec.cpp b/tests/conn_exec.cpp index c9eaeac2..7340656b 100644 --- a/tests/conn_exec.cpp +++ b/tests/conn_exec.cpp @@ -43,7 +43,8 @@ BOOST_AUTO_TEST_CASE(hello_priority) net::io_context ioc; - auto conn = std::make_shared(ioc); + net::ssl::context ctx{net::ssl::context::tls_client}; + auto conn = std::make_shared(ioc, ctx); bool seen1 = false; bool seen2 = false; @@ -92,7 +93,8 @@ BOOST_AUTO_TEST_CASE(wrong_response_data_type) response resp; net::io_context ioc; - auto conn = std::make_shared(ioc); + net::ssl::context ctx{net::ssl::context::tls_client}; + auto conn = std::make_shared(ioc, ctx); conn->async_exec(req, resp, [conn](auto ec, auto){ BOOST_CHECK_EQUAL(ec, boost::redis::error::not_a_number); @@ -110,7 +112,8 @@ BOOST_AUTO_TEST_CASE(cancel_request_if_not_connected) req.push("PING"); net::io_context ioc; - auto conn = std::make_shared(ioc); + net::ssl::context ctx{net::ssl::context::tls_client}; + auto conn = std::make_shared(ioc, ctx); conn->async_exec(req, ignore, [conn](auto ec, auto){ BOOST_CHECK_EQUAL(ec, boost::redis::error::not_connected); conn->cancel(); diff --git a/tests/conn_exec_cancel.cpp b/tests/conn_exec_cancel.cpp index 882698f4..d3114b3a 100644 --- a/tests/conn_exec_cancel.cpp +++ b/tests/conn_exec_cancel.cpp @@ -38,7 +38,8 @@ using namespace std::chrono_literals; auto implicit_cancel_of_req_written() -> net::awaitable { auto ex = co_await net::this_coro::executor; - auto conn = std::make_shared(ex); + net::ssl::context ctx{net::ssl::context::tls_client}; + auto conn = std::make_shared(ex, ctx); config cfg; cfg.health_check_interval = std::chrono::seconds{0}; @@ -80,7 +81,8 @@ BOOST_AUTO_TEST_CASE(test_ignore_implicit_cancel_of_req_written) BOOST_AUTO_TEST_CASE(test_cancel_of_req_written_on_run_canceled) { net::io_context ioc; - auto conn = std::make_shared(ioc); + net::ssl::context ctx{net::ssl::context::tls_client}; + auto conn = std::make_shared(ioc, ctx); request req0; req0.push("PING"); diff --git a/tests/conn_exec_cancel2.cpp b/tests/conn_exec_cancel2.cpp index 83b383c4..9db25137 100644 --- a/tests/conn_exec_cancel2.cpp +++ b/tests/conn_exec_cancel2.cpp @@ -40,7 +40,8 @@ auto async_ignore_explicit_cancel_of_req_written() -> net::awaitable auto ex = co_await net::this_coro::executor; generic_response gresp; - auto conn = std::make_shared(ex); + net::ssl::context ctx{net::ssl::context::tls_client}; + auto conn = std::make_shared(ex, ctx); run(conn); diff --git a/tests/conn_exec_error.cpp b/tests/conn_exec_error.cpp index 45b9de2c..59c7941b 100644 --- a/tests/conn_exec_error.cpp +++ b/tests/conn_exec_error.cpp @@ -38,7 +38,8 @@ BOOST_AUTO_TEST_CASE(no_ignore_error) net::io_context ioc; - auto conn = std::make_shared(ioc); + net::ssl::context ctx{net::ssl::context::tls_client}; + auto conn = std::make_shared(ioc, ctx); conn->async_exec(req, ignore, [&](auto ec, auto){ BOOST_CHECK_EQUAL(ec, error::resp3_simple_error); @@ -64,7 +65,8 @@ BOOST_AUTO_TEST_CASE(has_diagnostic) net::io_context ioc; - auto conn = std::make_shared(ioc); + net::ssl::context ctx{net::ssl::context::tls_client}; + auto conn = std::make_shared(ioc, ctx); response resp; conn->async_exec(req, resp, [&](auto ec, auto){ @@ -106,7 +108,8 @@ BOOST_AUTO_TEST_CASE(resp3_error_in_cmd_pipeline) response resp2; net::io_context ioc; - auto conn = std::make_shared(ioc); + net::ssl::context ctx{net::ssl::context::tls_client}; + auto conn = std::make_shared(ioc, ctx); auto c2 = [&](auto ec, auto) { @@ -162,7 +165,8 @@ BOOST_AUTO_TEST_CASE(error_in_transaction) net::io_context ioc; - auto conn = std::make_shared(ioc); + net::ssl::context ctx{net::ssl::context::tls_client}; + auto conn = std::make_shared(ioc, ctx); conn->async_exec(req, resp, [&](auto ec, auto){ BOOST_TEST(!ec); @@ -214,7 +218,8 @@ BOOST_AUTO_TEST_CASE(subscriber_wrong_syntax) req2.push("SUBSCRIBE"); // Wrong command synthax. net::io_context ioc; - auto conn = std::make_shared(ioc); + net::ssl::context ctx{net::ssl::context::tls_client}; + auto conn = std::make_shared(ioc, ctx); auto c2 = [&](auto ec, auto) { diff --git a/tests/conn_exec_retry.cpp b/tests/conn_exec_retry.cpp index 41ca94cb..5feeaeec 100644 --- a/tests/conn_exec_retry.cpp +++ b/tests/conn_exec_retry.cpp @@ -42,7 +42,8 @@ BOOST_AUTO_TEST_CASE(request_retry_false) req2.push("PING"); net::io_context ioc; - auto conn = std::make_shared(ioc); + net::ssl::context ctx{net::ssl::context::tls_client}; + auto conn = std::make_shared(ioc, ctx); net::steady_timer st{ioc}; st.expires_after(std::chrono::seconds{1}); @@ -104,7 +105,8 @@ BOOST_AUTO_TEST_CASE(request_retry_true) req3.push("QUIT"); net::io_context ioc; - auto conn = std::make_shared(ioc); + net::ssl::context ctx{net::ssl::context::tls_client}; + auto conn = std::make_shared(ioc, ctx); net::steady_timer st{ioc}; st.expires_after(std::chrono::seconds{1}); diff --git a/tests/conn_push.cpp b/tests/conn_push.cpp index d2a9399b..a85c2752 100644 --- a/tests/conn_push.cpp +++ b/tests/conn_push.cpp @@ -46,7 +46,8 @@ BOOST_AUTO_TEST_CASE(receives_push_waiting_resps) net::io_context ioc; - auto conn = std::make_shared(ioc); + net::ssl::context ctx{net::ssl::context::tls_client}; + auto conn = std::make_shared(ioc, ctx); auto c3 =[](auto ec, auto...) { @@ -86,7 +87,8 @@ BOOST_AUTO_TEST_CASE(receives_push_waiting_resps) BOOST_AUTO_TEST_CASE(push_received1) { net::io_context ioc; - auto conn = std::make_shared(ioc); + net::ssl::context ctx{net::ssl::context::tls_client}; + auto conn = std::make_shared(ioc, ctx); request req; //req.push("HELLO", 3); @@ -116,7 +118,8 @@ BOOST_AUTO_TEST_CASE(push_received1) BOOST_AUTO_TEST_CASE(push_filtered_out) { net::io_context ioc; - auto conn = std::make_shared(ioc); + net::ssl::context ctx{net::ssl::context::tls_client}; + auto conn = std::make_shared(ioc, ctx); request req; req.push("HELLO", 3); @@ -183,7 +186,8 @@ auto boost_redis_adapt(response_error_tag&) BOOST_AUTO_TEST_CASE(test_push_adapter) { net::io_context ioc; - auto conn = std::make_shared(ioc); + net::ssl::context ctx{net::ssl::context::tls_client}; + auto conn = std::make_shared(ioc, ctx); request req; req.push("HELLO", 3); @@ -234,7 +238,8 @@ BOOST_AUTO_TEST_CASE(many_subscribers) req3.push("QUIT"); net::io_context ioc; - auto conn = std::make_shared(ioc); + net::ssl::context ctx{net::ssl::context::tls_client}; + auto conn = std::make_shared(ioc, ctx); auto c11 =[&](auto ec, auto...) { diff --git a/tests/conn_quit.cpp b/tests/conn_quit.cpp index 0d5ba9a2..fcca1fa6 100644 --- a/tests/conn_quit.cpp +++ b/tests/conn_quit.cpp @@ -9,6 +9,7 @@ #define BOOST_TEST_MODULE conn-quit #include #include +#include "common.hpp" // TODO: Move this to a lib. #include @@ -30,17 +31,15 @@ BOOST_AUTO_TEST_CASE(test_eof_no_error) req.push("QUIT"); net::io_context ioc; - connection conn{ioc}; + net::ssl::context ctx{net::ssl::context::tls_client}; + auto conn = std::make_shared(ioc, ctx); - conn.async_exec(req, ignore, [&](auto ec, auto) { + conn->async_exec(req, ignore, [&](auto ec, auto) { BOOST_TEST(!ec); - conn.cancel(operation::reconnection); - }); - - conn.async_run({}, {}, [](auto ec){ - BOOST_TEST(!!ec); + conn->cancel(operation::reconnection); }); + run(conn); ioc.run(); } @@ -49,8 +48,8 @@ BOOST_AUTO_TEST_CASE(test_async_run_exits) { net::io_context ioc; - connection conn{ioc}; - conn.cancel(operation::reconnection); + net::ssl::context ctx{net::ssl::context::tls_client}; + auto conn = std::make_shared(ioc, ctx); request req1; req1.get_config().cancel_on_connection_lost = false; @@ -75,25 +74,24 @@ BOOST_AUTO_TEST_CASE(test_async_run_exits) { std::clog << "c2: " << ec.message() << std::endl; BOOST_TEST(!ec); - conn.async_exec(req3, ignore, c3); + conn->async_exec(req3, ignore, c3); }; auto c1 = [&](auto ec, auto) { - std::cout << "c3: " << ec.message() << std::endl; + std::cout << "c1: " << ec.message() << std::endl; BOOST_TEST(!ec); - conn.async_exec(req2, ignore, c2); + conn->async_exec(req2, ignore, c2); }; - conn.async_exec(req1, ignore, c1); + conn->async_exec(req1, ignore, c1); // The healthy checker should not be the cause of async_run - // completing, so we set a long timeout. + // completing, so we disable. config cfg; - cfg.health_check_interval = 10000s; - conn.async_run({}, {}, [&](auto ec){ - BOOST_TEST(!!ec); - }); + cfg.health_check_interval = 0s; + cfg.reconnect_wait_interval = 0s; + run(conn, cfg); ioc.run(); } diff --git a/tests/conn_reconnect.cpp b/tests/conn_reconnect.cpp index ce948709..ec18a315 100644 --- a/tests/conn_reconnect.cpp +++ b/tests/conn_reconnect.cpp @@ -36,7 +36,8 @@ net::awaitable test_reconnect_impl() request req; req.push("QUIT"); - auto conn = std::make_shared(ex); + net::ssl::context ctx{net::ssl::context::tls_client}; + auto conn = std::make_shared(ex, ctx); run(conn); int i = 0; @@ -68,7 +69,8 @@ auto async_test_reconnect_timeout() -> net::awaitable net::steady_timer st{ex}; - auto conn = std::make_shared(ex); + net::ssl::context ctx{net::ssl::context::tls_client}; + auto conn = std::make_shared(ex, ctx); error_code ec1, ec3; request req1; diff --git a/tests/conn_run_cancel.cpp b/tests/conn_run_cancel.cpp index d9f383f1..ec4a512a 100644 --- a/tests/conn_run_cancel.cpp +++ b/tests/conn_run_cancel.cpp @@ -36,7 +36,8 @@ using namespace net::experimental::awaitable_operators; auto async_cancel_run_with_timer() -> net::awaitable { auto ex = co_await net::this_coro::executor; - connection conn{ex}; + net::ssl::context ctx{net::ssl::context::tls_client}; + connection conn{ex, ctx}; net::steady_timer st{ex}; st.expires_after(1s); @@ -61,7 +62,8 @@ auto async_check_cancellation_not_missed(int n, std::chrono::milliseconds ms) -> net::awaitable { auto ex = co_await net::this_coro::executor; - connection conn{ex}; + net::ssl::context ctx{net::ssl::context::tls_client}; + connection conn{ex, ctx}; net::steady_timer timer{ex}; diff --git a/tests/conn_tls.cpp b/tests/conn_tls.cpp index 7a62b3b1..4f792438 100644 --- a/tests/conn_tls.cpp +++ b/tests/conn_tls.cpp @@ -4,7 +4,7 @@ * accompanying file LICENSE.txt) */ -#include +#include #define BOOST_TEST_MODULE conn-tls #include #include @@ -14,7 +14,7 @@ namespace net = boost::asio; -using connection = boost::redis::ssl::connection; +using connection = boost::redis::connection; using boost::redis::request; using boost::redis::response; using boost::redis::config; @@ -29,6 +29,7 @@ bool verify_certificate(bool, net::ssl::verify_context&) BOOST_AUTO_TEST_CASE(ping) { config cfg; + cfg.use_ssl = true; cfg.username = "aedis"; cfg.password = "aedis"; cfg.addr.host = "db.occase.de"; @@ -42,7 +43,7 @@ BOOST_AUTO_TEST_CASE(ping) response resp; net::io_context ioc; - net::ssl::context ctx{net::ssl::context::sslv23}; + net::ssl::context ctx{net::ssl::context::tls_client}; connection conn{ioc, ctx}; conn.next_layer().set_verify_mode(net::ssl::verify_peer); conn.next_layer().set_verify_callback(verify_certificate); diff --git a/tests/issue_50.cpp b/tests/issue_50.cpp index 4905cbed..31224016 100644 --- a/tests/issue_50.cpp +++ b/tests/issue_50.cpp @@ -40,7 +40,7 @@ auto receiver(std::shared_ptr conn) -> net::awaitable { std::cout << "uuu" << std::endl; - while (!conn->is_cancelled()) { + while (conn->will_reconnect()) { std::cout << "dddd" << std::endl; // Loop reading Redis pushs messages. for (;;) { @@ -84,11 +84,12 @@ periodic_task(std::shared_ptr conn) -> net::awaitable auto co_main(config cfg) -> net::awaitable { auto ex = co_await net::this_coro::executor; - auto conn = std::make_shared(ex); + auto ctx = std::make_shared(net::ssl::context::tls_client); + auto conn = std::make_shared(ex, *ctx); net::co_spawn(ex, receiver(conn), net::detached); net::co_spawn(ex, periodic_task(conn), net::detached); - conn->async_run(cfg, {}, net::consign(net::detached, conn)); + conn->async_run(cfg, {}, net::consign(net::detached, conn, ctx)); } #endif // defined(BOOST_ASIO_HAS_CO_AWAIT) diff --git a/tests/run.cpp b/tests/run.cpp index 3e2740e9..a00f3ec7 100644 --- a/tests/run.cpp +++ b/tests/run.cpp @@ -38,9 +38,10 @@ BOOST_AUTO_TEST_CASE(resolve_bad_host) cfg.resolve_timeout = 10h; cfg.connect_timeout = 10h; cfg.health_check_interval = 10h; + cfg.reconnect_wait_interval = 0s; - connection conn{ioc}; - conn.cancel(operation::reconnection); + net::ssl::context ctx{net::ssl::context::tls_client}; + connection conn{ioc, ctx}; conn.async_run(cfg, {}, [](auto ec){ BOOST_TEST(is_host_not_found(ec)); }); @@ -58,9 +59,10 @@ BOOST_AUTO_TEST_CASE(resolve_with_timeout) cfg.resolve_timeout = 1ms; cfg.connect_timeout = 1ms; cfg.health_check_interval = 10h; + cfg.reconnect_wait_interval = 0s; - auto conn = std::make_shared(ioc); - conn->cancel(operation::reconnection); + net::ssl::context ctx{net::ssl::context::tls_client}; + auto conn = std::make_shared(ioc, ctx); run(conn, cfg); ioc.run(); } @@ -75,9 +77,10 @@ BOOST_AUTO_TEST_CASE(connect_bad_port) cfg.resolve_timeout = 10h; cfg.connect_timeout = 10s; cfg.health_check_interval = 10h; + cfg.reconnect_wait_interval = 0s; - auto conn = std::make_shared(ioc); - conn->cancel(operation::reconnection); + net::ssl::context ctx{net::ssl::context::tls_client}; + auto conn = std::make_shared(ioc, ctx); run(conn, cfg, net::error::connection_refused); ioc.run(); } From c0aa4356ea523b1314f31e50d1575ecaab2c7df9 Mon Sep 17 00:00:00 2001 From: Marcelo Zimbres Date: Wed, 10 May 2023 23:25:09 +0200 Subject: [PATCH 14/32] The ssl::context is now owned by the connection. --- CMakeLists.txt | 1 + examples/cpp17_intro.cpp | 3 +-- examples/cpp17_intro_sync.cpp | 3 +-- examples/cpp20_chat_room.cpp | 5 ++--- examples/cpp20_containers.cpp | 5 ++--- examples/cpp20_echo_server.cpp | 5 ++--- examples/cpp20_intro.cpp | 5 ++--- examples/cpp20_intro_tls.cpp | 5 ++--- examples/cpp20_json.cpp | 5 ++--- examples/cpp20_protobuf.cpp | 5 ++--- examples/cpp20_resolve_with_sentinel.cpp | 5 ++--- examples/cpp20_streams.cpp | 5 ++--- examples/cpp20_subscriber.cpp | 5 ++--- examples/sync_connection.hpp | 4 ++-- include/boost/redis/connection.hpp | 25 ++++++++++++++++-------- tests/conn_check_health.cpp | 5 ++--- tests/conn_echo_stress.cpp | 3 +-- tests/conn_exec.cpp | 9 +++------ tests/conn_exec_cancel.cpp | 6 ++---- tests/conn_exec_cancel2.cpp | 3 +-- tests/conn_exec_error.cpp | 15 +++++--------- tests/conn_exec_retry.cpp | 6 ++---- tests/conn_push.cpp | 15 +++++--------- tests/conn_quit.cpp | 6 ++---- tests/conn_reconnect.cpp | 6 ++---- tests/conn_run_cancel.cpp | 6 ++---- tests/conn_tls.cpp | 3 +-- tests/issue_50.cpp | 5 ++--- tests/run.cpp | 9 +++------ 29 files changed, 75 insertions(+), 108 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fb31b99f..faaf18b9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,6 +61,7 @@ include_directories(include) add_library(test_common STATIC tests/common.cpp ) +target_link_libraries(test_common PUBLIC OpenSSL::Crypto OpenSSL::SSL) target_compile_features(test_common PUBLIC cxx_std_17) if (MSVC) target_compile_options(test_common PRIVATE /bigobj) diff --git a/examples/cpp17_intro.cpp b/examples/cpp17_intro.cpp index d94bd2b2..c6cbd6c8 100644 --- a/examples/cpp17_intro.cpp +++ b/examples/cpp17_intro.cpp @@ -32,8 +32,7 @@ auto main(int argc, char * argv[]) -> int response resp; net::io_context ioc; - net::ssl::context ctx{net::ssl::context::tls_client}; - connection conn{ioc, ctx}; + connection conn{ioc}; conn.async_run(cfg, {}, net::detached); diff --git a/examples/cpp17_intro_sync.cpp b/examples/cpp17_intro_sync.cpp index 06ba47a1..bd106d29 100644 --- a/examples/cpp17_intro_sync.cpp +++ b/examples/cpp17_intro_sync.cpp @@ -28,8 +28,7 @@ auto main(int argc, char * argv[]) -> int cfg.addr.port = argv[2]; } - net::ssl::context ctx{net::ssl::context::tls_client}; - sync_connection conn{ctx}; + sync_connection conn; conn.run(cfg); request req; diff --git a/examples/cpp20_chat_room.cpp b/examples/cpp20_chat_room.cpp index 29025031..489e5952 100644 --- a/examples/cpp20_chat_room.cpp +++ b/examples/cpp20_chat_room.cpp @@ -75,13 +75,12 @@ auto publisher(std::shared_ptr in, std::shared_ptr net::awaitable { auto ex = co_await net::this_coro::executor; - auto ctx = std::make_shared(net::ssl::context::tls_client); - auto conn = std::make_shared(ex, *ctx); + auto conn = std::make_shared(ex); auto stream = std::make_shared(ex, ::dup(STDIN_FILENO)); net::co_spawn(ex, receiver(conn), net::detached); net::co_spawn(ex, publisher(stream, conn), net::detached); - conn->async_run(cfg, {}, net::consign(net::detached, conn, ctx)); + conn->async_run(cfg, {}, net::consign(net::detached, conn)); signal_set sig_set{ex, SIGINT, SIGTERM}; co_await sig_set.async_wait(); diff --git a/examples/cpp20_containers.cpp b/examples/cpp20_containers.cpp index ea4b440c..cbea9330 100644 --- a/examples/cpp20_containers.cpp +++ b/examples/cpp20_containers.cpp @@ -91,9 +91,8 @@ auto transaction(std::shared_ptr conn) -> net::awaitable // Called from the main function (see main.cpp) net::awaitable co_main(config cfg) { - auto ctx = std::make_shared(net::ssl::context::tls_client); - auto conn = std::make_shared(co_await net::this_coro::executor, *ctx); - conn->async_run(cfg, {}, net::consign(net::detached, conn, ctx)); + auto conn = std::make_shared(co_await net::this_coro::executor); + conn->async_run(cfg, {}, net::consign(net::detached, conn)); co_await store(conn); co_await transaction(conn); diff --git a/examples/cpp20_echo_server.cpp b/examples/cpp20_echo_server.cpp index f69b2bff..6d2691fb 100644 --- a/examples/cpp20_echo_server.cpp +++ b/examples/cpp20_echo_server.cpp @@ -58,10 +58,9 @@ auto listener(std::shared_ptr conn) -> net::awaitable auto co_main(config cfg) -> net::awaitable { auto ex = co_await net::this_coro::executor; - auto ctx = std::make_shared(net::ssl::context::tls_client); - auto conn = std::make_shared(ex, *ctx); + auto conn = std::make_shared(ex); net::co_spawn(ex, listener(conn), net::detached); - conn->async_run(cfg, {}, net::consign(net::detached, conn, ctx)); + conn->async_run(cfg, {}, net::consign(net::detached, conn)); signal_set sig_set(ex, SIGINT, SIGTERM); co_await sig_set.async_wait(); diff --git a/examples/cpp20_intro.cpp b/examples/cpp20_intro.cpp index f4c21406..3526950d 100644 --- a/examples/cpp20_intro.cpp +++ b/examples/cpp20_intro.cpp @@ -23,9 +23,8 @@ using connection = net::deferred_t::as_default_on_t; // Called from the main function (see main.cpp) auto co_main(config cfg) -> net::awaitable { - auto ctx = std::make_shared(net::ssl::context::tls_client); - auto conn = std::make_shared(co_await net::this_coro::executor, *ctx); - conn->async_run(cfg, {}, net::consign(net::detached, conn, ctx)); + auto conn = std::make_shared(co_await net::this_coro::executor); + conn->async_run(cfg, {}, net::consign(net::detached, conn)); // A request containing only a ping command. request req; diff --git a/examples/cpp20_intro_tls.cpp b/examples/cpp20_intro_tls.cpp index 5a27bfac..23b491e1 100644 --- a/examples/cpp20_intro_tls.cpp +++ b/examples/cpp20_intro_tls.cpp @@ -34,9 +34,8 @@ auto co_main(config cfg) -> net::awaitable cfg.addr.host = "db.occase.de"; cfg.addr.port = "6380"; - auto ctx = std::make_shared(net::ssl::context::tls_client); - auto conn = std::make_shared(co_await net::this_coro::executor, *ctx); - conn->async_run(cfg, {}, net::consign(net::detached, conn, ctx)); + auto conn = std::make_shared(co_await net::this_coro::executor); + conn->async_run(cfg, {}, net::consign(net::detached, conn)); request req; req.push("PING"); diff --git a/examples/cpp20_json.cpp b/examples/cpp20_json.cpp index b5ab5d55..6117dedd 100644 --- a/examples/cpp20_json.cpp +++ b/examples/cpp20_json.cpp @@ -48,9 +48,8 @@ void boost_redis_from_bulk(user& u, std::string_view sv, boost::system::error_co auto co_main(config cfg) -> net::awaitable { auto ex = co_await net::this_coro::executor; - auto ctx = std::make_shared(net::ssl::context::tls_client); - auto conn = std::make_shared(ex, *ctx); - conn->async_run(cfg, {}, net::consign(net::detached, conn, ctx)); + auto conn = std::make_shared(ex); + conn->async_run(cfg, {}, net::consign(net::detached, conn)); // user object that will be stored in Redis in json format. user const u{"Joao", "58", "Brazil"}; diff --git a/examples/cpp20_protobuf.cpp b/examples/cpp20_protobuf.cpp index a1754860..d372798f 100644 --- a/examples/cpp20_protobuf.cpp +++ b/examples/cpp20_protobuf.cpp @@ -47,9 +47,8 @@ using tutorial::boost_redis_from_bulk; net::awaitable co_main(config cfg) { auto ex = co_await net::this_coro::executor; - auto ctx = std::make_shared(net::ssl::context::tls_client); - auto conn = std::make_shared(ex, *ctx); - conn->async_run(cfg, {}, net::consign(net::detached, conn, ctx)); + auto conn = std::make_shared(ex); + conn->async_run(cfg, {}, net::consign(net::detached, conn)); person p; p.set_name("Louis"); diff --git a/examples/cpp20_resolve_with_sentinel.cpp b/examples/cpp20_resolve_with_sentinel.cpp index a624fd12..70aeaed0 100644 --- a/examples/cpp20_resolve_with_sentinel.cpp +++ b/examples/cpp20_resolve_with_sentinel.cpp @@ -34,8 +34,7 @@ auto resolve_master_address(std::vector
const& addresses) -> net::await req.push("SENTINEL", "get-master-addr-by-name", "mymaster"); req.push("QUIT"); - auto ctx = std::make_shared(net::ssl::context::tls_client); - auto conn = std::make_shared(co_await net::this_coro::executor, *ctx); + auto conn = std::make_shared(co_await net::this_coro::executor); response>, ignore_t> resp; for (auto addr : addresses) { @@ -45,7 +44,7 @@ auto resolve_master_address(std::vector
const& addresses) -> net::await // TODO: async_run and async_exec should be lauched in // parallel here so we can wait for async_run completion // before eventually calling it again. - conn->async_run(cfg, {}, net::consign(net::detached, conn, ctx)); + conn->async_run(cfg, {}, net::consign(net::detached, conn)); co_await conn->async_exec(req, resp, redir(ec)); conn->cancel(); conn->reset_stream(); diff --git a/examples/cpp20_streams.cpp b/examples/cpp20_streams.cpp index 909f4204..5dcae830 100644 --- a/examples/cpp20_streams.cpp +++ b/examples/cpp20_streams.cpp @@ -84,13 +84,12 @@ auto stream_reader(std::shared_ptr conn) -> net::awaitable auto co_main(config cfg) -> net::awaitable { auto ex = co_await net::this_coro::executor; - auto ctx = std::make_shared(net::ssl::context::tls_client); - auto conn = std::make_shared(ex, *ctx); + auto conn = std::make_shared(ex); net::co_spawn(ex, stream_reader(conn), net::detached); // Disable health checks. cfg.health_check_interval = std::chrono::seconds{0}; - conn->async_run(cfg, {}, net::consign(net::detached, conn, ctx)); + conn->async_run(cfg, {}, net::consign(net::detached, conn)); signal_set sig_set(ex, SIGINT, SIGTERM); co_await sig_set.async_wait(); diff --git a/examples/cpp20_subscriber.cpp b/examples/cpp20_subscriber.cpp index c7aaa98f..1753095f 100644 --- a/examples/cpp20_subscriber.cpp +++ b/examples/cpp20_subscriber.cpp @@ -75,10 +75,9 @@ receiver(std::shared_ptr conn) -> net::awaitable auto co_main(config cfg) -> net::awaitable { auto ex = co_await net::this_coro::executor; - auto ctx = std::make_shared(net::ssl::context::tls_client); - auto conn = std::make_shared(ex, *ctx); + auto conn = std::make_shared(ex); net::co_spawn(ex, receiver(conn), net::detached); - conn->async_run(cfg, {}, net::consign(net::detached, conn, ctx)); + conn->async_run(cfg, {}, net::consign(net::detached, conn)); signal_set sig_set(ex, SIGINT, SIGTERM); co_await sig_set.async_wait(); diff --git a/examples/sync_connection.hpp b/examples/sync_connection.hpp index a432ad91..cc982f84 100644 --- a/examples/sync_connection.hpp +++ b/examples/sync_connection.hpp @@ -20,9 +20,9 @@ namespace boost::redis class sync_connection { public: - sync_connection(boost::asio::ssl::context& ctx) + sync_connection() : ioc_{1} - , conn_{std::make_shared(ioc_, ctx)} + , conn_{std::make_shared(ioc_)} { } ~sync_connection() diff --git a/include/boost/redis/connection.hpp b/include/boost/redis/connection.hpp index d8deb2e5..7360e848 100644 --- a/include/boost/redis/connection.hpp +++ b/include/boost/redis/connection.hpp @@ -54,27 +54,36 @@ class basic_connection : private detail::connection_base(ex, ctx)} + , stream_{std::make_unique(ex, ctx_)} { } /// Contructs from a context. explicit - basic_connection(asio::io_context& ioc, asio::ssl::context& ctx) - : basic_connection(ioc.get_executor(), ctx) + basic_connection(asio::io_context& ioc, asio::ssl::context::method method = asio::ssl::context::tls_client) + : basic_connection(ioc.get_executor(), method) { } /// Returns the associated executor. - auto get_executor() {return stream_->get_executor();} + auto get_executor() + {return stream_->get_executor();} + + /// Returns the ssl context. + auto const& get_ssl_context() const noexcept + { return ctx_;} + + /// Returns the ssl context. + auto& get_ssl_context() noexcept + { return ctx_;} /// Reset the underlying stream. void reset_stream() { - stream_ = std::make_unique(stream_->get_executor(), *ctx_); + stream_ = std::make_unique(stream_->get_executor(), ctx_); } /// Returns a reference to the next layer. @@ -280,7 +289,7 @@ class basic_connection : private detail::connection_base stream_; diff --git a/tests/conn_check_health.cpp b/tests/conn_check_health.cpp index 25d76d8a..c69db4f9 100644 --- a/tests/conn_check_health.cpp +++ b/tests/conn_check_health.cpp @@ -75,8 +75,7 @@ BOOST_AUTO_TEST_CASE(check_health) net::io_context ioc; - net::ssl::context ctx{net::ssl::context::tls_client}; - connection conn1{ioc, ctx}; + connection conn1{ioc}; request req1; req1.push("CLIENT", "PAUSE", "10000", "ALL"); @@ -94,7 +93,7 @@ BOOST_AUTO_TEST_CASE(check_health) // It looks like client pause does not work for clients that are // sending MONITOR. I will therefore open a second connection. - connection conn2{ioc, ctx}; + connection conn2{ioc}; config cfg2; cfg2.health_check_id = "conn2"; diff --git a/tests/conn_echo_stress.cpp b/tests/conn_echo_stress.cpp index c76e9e10..3adb1a0f 100644 --- a/tests/conn_echo_stress.cpp +++ b/tests/conn_echo_stress.cpp @@ -65,8 +65,7 @@ auto echo_session(std::shared_ptr conn, std::string id, int n) -> ne auto async_echo_stress() -> net::awaitable { auto ex = co_await net::this_coro::executor; - net::ssl::context ctx{net::ssl::context::tls_client}; - auto conn = std::make_shared(ex, ctx); + auto conn = std::make_shared(ex); int const sessions = 500; int const msgs = 1000; diff --git a/tests/conn_exec.cpp b/tests/conn_exec.cpp index 7340656b..c9eaeac2 100644 --- a/tests/conn_exec.cpp +++ b/tests/conn_exec.cpp @@ -43,8 +43,7 @@ BOOST_AUTO_TEST_CASE(hello_priority) net::io_context ioc; - net::ssl::context ctx{net::ssl::context::tls_client}; - auto conn = std::make_shared(ioc, ctx); + auto conn = std::make_shared(ioc); bool seen1 = false; bool seen2 = false; @@ -93,8 +92,7 @@ BOOST_AUTO_TEST_CASE(wrong_response_data_type) response resp; net::io_context ioc; - net::ssl::context ctx{net::ssl::context::tls_client}; - auto conn = std::make_shared(ioc, ctx); + auto conn = std::make_shared(ioc); conn->async_exec(req, resp, [conn](auto ec, auto){ BOOST_CHECK_EQUAL(ec, boost::redis::error::not_a_number); @@ -112,8 +110,7 @@ BOOST_AUTO_TEST_CASE(cancel_request_if_not_connected) req.push("PING"); net::io_context ioc; - net::ssl::context ctx{net::ssl::context::tls_client}; - auto conn = std::make_shared(ioc, ctx); + auto conn = std::make_shared(ioc); conn->async_exec(req, ignore, [conn](auto ec, auto){ BOOST_CHECK_EQUAL(ec, boost::redis::error::not_connected); conn->cancel(); diff --git a/tests/conn_exec_cancel.cpp b/tests/conn_exec_cancel.cpp index d3114b3a..882698f4 100644 --- a/tests/conn_exec_cancel.cpp +++ b/tests/conn_exec_cancel.cpp @@ -38,8 +38,7 @@ using namespace std::chrono_literals; auto implicit_cancel_of_req_written() -> net::awaitable { auto ex = co_await net::this_coro::executor; - net::ssl::context ctx{net::ssl::context::tls_client}; - auto conn = std::make_shared(ex, ctx); + auto conn = std::make_shared(ex); config cfg; cfg.health_check_interval = std::chrono::seconds{0}; @@ -81,8 +80,7 @@ BOOST_AUTO_TEST_CASE(test_ignore_implicit_cancel_of_req_written) BOOST_AUTO_TEST_CASE(test_cancel_of_req_written_on_run_canceled) { net::io_context ioc; - net::ssl::context ctx{net::ssl::context::tls_client}; - auto conn = std::make_shared(ioc, ctx); + auto conn = std::make_shared(ioc); request req0; req0.push("PING"); diff --git a/tests/conn_exec_cancel2.cpp b/tests/conn_exec_cancel2.cpp index 9db25137..83b383c4 100644 --- a/tests/conn_exec_cancel2.cpp +++ b/tests/conn_exec_cancel2.cpp @@ -40,8 +40,7 @@ auto async_ignore_explicit_cancel_of_req_written() -> net::awaitable auto ex = co_await net::this_coro::executor; generic_response gresp; - net::ssl::context ctx{net::ssl::context::tls_client}; - auto conn = std::make_shared(ex, ctx); + auto conn = std::make_shared(ex); run(conn); diff --git a/tests/conn_exec_error.cpp b/tests/conn_exec_error.cpp index 59c7941b..45b9de2c 100644 --- a/tests/conn_exec_error.cpp +++ b/tests/conn_exec_error.cpp @@ -38,8 +38,7 @@ BOOST_AUTO_TEST_CASE(no_ignore_error) net::io_context ioc; - net::ssl::context ctx{net::ssl::context::tls_client}; - auto conn = std::make_shared(ioc, ctx); + auto conn = std::make_shared(ioc); conn->async_exec(req, ignore, [&](auto ec, auto){ BOOST_CHECK_EQUAL(ec, error::resp3_simple_error); @@ -65,8 +64,7 @@ BOOST_AUTO_TEST_CASE(has_diagnostic) net::io_context ioc; - net::ssl::context ctx{net::ssl::context::tls_client}; - auto conn = std::make_shared(ioc, ctx); + auto conn = std::make_shared(ioc); response resp; conn->async_exec(req, resp, [&](auto ec, auto){ @@ -108,8 +106,7 @@ BOOST_AUTO_TEST_CASE(resp3_error_in_cmd_pipeline) response resp2; net::io_context ioc; - net::ssl::context ctx{net::ssl::context::tls_client}; - auto conn = std::make_shared(ioc, ctx); + auto conn = std::make_shared(ioc); auto c2 = [&](auto ec, auto) { @@ -165,8 +162,7 @@ BOOST_AUTO_TEST_CASE(error_in_transaction) net::io_context ioc; - net::ssl::context ctx{net::ssl::context::tls_client}; - auto conn = std::make_shared(ioc, ctx); + auto conn = std::make_shared(ioc); conn->async_exec(req, resp, [&](auto ec, auto){ BOOST_TEST(!ec); @@ -218,8 +214,7 @@ BOOST_AUTO_TEST_CASE(subscriber_wrong_syntax) req2.push("SUBSCRIBE"); // Wrong command synthax. net::io_context ioc; - net::ssl::context ctx{net::ssl::context::tls_client}; - auto conn = std::make_shared(ioc, ctx); + auto conn = std::make_shared(ioc); auto c2 = [&](auto ec, auto) { diff --git a/tests/conn_exec_retry.cpp b/tests/conn_exec_retry.cpp index 5feeaeec..41ca94cb 100644 --- a/tests/conn_exec_retry.cpp +++ b/tests/conn_exec_retry.cpp @@ -42,8 +42,7 @@ BOOST_AUTO_TEST_CASE(request_retry_false) req2.push("PING"); net::io_context ioc; - net::ssl::context ctx{net::ssl::context::tls_client}; - auto conn = std::make_shared(ioc, ctx); + auto conn = std::make_shared(ioc); net::steady_timer st{ioc}; st.expires_after(std::chrono::seconds{1}); @@ -105,8 +104,7 @@ BOOST_AUTO_TEST_CASE(request_retry_true) req3.push("QUIT"); net::io_context ioc; - net::ssl::context ctx{net::ssl::context::tls_client}; - auto conn = std::make_shared(ioc, ctx); + auto conn = std::make_shared(ioc); net::steady_timer st{ioc}; st.expires_after(std::chrono::seconds{1}); diff --git a/tests/conn_push.cpp b/tests/conn_push.cpp index a85c2752..d2a9399b 100644 --- a/tests/conn_push.cpp +++ b/tests/conn_push.cpp @@ -46,8 +46,7 @@ BOOST_AUTO_TEST_CASE(receives_push_waiting_resps) net::io_context ioc; - net::ssl::context ctx{net::ssl::context::tls_client}; - auto conn = std::make_shared(ioc, ctx); + auto conn = std::make_shared(ioc); auto c3 =[](auto ec, auto...) { @@ -87,8 +86,7 @@ BOOST_AUTO_TEST_CASE(receives_push_waiting_resps) BOOST_AUTO_TEST_CASE(push_received1) { net::io_context ioc; - net::ssl::context ctx{net::ssl::context::tls_client}; - auto conn = std::make_shared(ioc, ctx); + auto conn = std::make_shared(ioc); request req; //req.push("HELLO", 3); @@ -118,8 +116,7 @@ BOOST_AUTO_TEST_CASE(push_received1) BOOST_AUTO_TEST_CASE(push_filtered_out) { net::io_context ioc; - net::ssl::context ctx{net::ssl::context::tls_client}; - auto conn = std::make_shared(ioc, ctx); + auto conn = std::make_shared(ioc); request req; req.push("HELLO", 3); @@ -186,8 +183,7 @@ auto boost_redis_adapt(response_error_tag&) BOOST_AUTO_TEST_CASE(test_push_adapter) { net::io_context ioc; - net::ssl::context ctx{net::ssl::context::tls_client}; - auto conn = std::make_shared(ioc, ctx); + auto conn = std::make_shared(ioc); request req; req.push("HELLO", 3); @@ -238,8 +234,7 @@ BOOST_AUTO_TEST_CASE(many_subscribers) req3.push("QUIT"); net::io_context ioc; - net::ssl::context ctx{net::ssl::context::tls_client}; - auto conn = std::make_shared(ioc, ctx); + auto conn = std::make_shared(ioc); auto c11 =[&](auto ec, auto...) { diff --git a/tests/conn_quit.cpp b/tests/conn_quit.cpp index fcca1fa6..98df54a5 100644 --- a/tests/conn_quit.cpp +++ b/tests/conn_quit.cpp @@ -31,8 +31,7 @@ BOOST_AUTO_TEST_CASE(test_eof_no_error) req.push("QUIT"); net::io_context ioc; - net::ssl::context ctx{net::ssl::context::tls_client}; - auto conn = std::make_shared(ioc, ctx); + auto conn = std::make_shared(ioc); conn->async_exec(req, ignore, [&](auto ec, auto) { BOOST_TEST(!ec); @@ -48,8 +47,7 @@ BOOST_AUTO_TEST_CASE(test_async_run_exits) { net::io_context ioc; - net::ssl::context ctx{net::ssl::context::tls_client}; - auto conn = std::make_shared(ioc, ctx); + auto conn = std::make_shared(ioc); request req1; req1.get_config().cancel_on_connection_lost = false; diff --git a/tests/conn_reconnect.cpp b/tests/conn_reconnect.cpp index ec18a315..ce948709 100644 --- a/tests/conn_reconnect.cpp +++ b/tests/conn_reconnect.cpp @@ -36,8 +36,7 @@ net::awaitable test_reconnect_impl() request req; req.push("QUIT"); - net::ssl::context ctx{net::ssl::context::tls_client}; - auto conn = std::make_shared(ex, ctx); + auto conn = std::make_shared(ex); run(conn); int i = 0; @@ -69,8 +68,7 @@ auto async_test_reconnect_timeout() -> net::awaitable net::steady_timer st{ex}; - net::ssl::context ctx{net::ssl::context::tls_client}; - auto conn = std::make_shared(ex, ctx); + auto conn = std::make_shared(ex); error_code ec1, ec3; request req1; diff --git a/tests/conn_run_cancel.cpp b/tests/conn_run_cancel.cpp index ec4a512a..d9f383f1 100644 --- a/tests/conn_run_cancel.cpp +++ b/tests/conn_run_cancel.cpp @@ -36,8 +36,7 @@ using namespace net::experimental::awaitable_operators; auto async_cancel_run_with_timer() -> net::awaitable { auto ex = co_await net::this_coro::executor; - net::ssl::context ctx{net::ssl::context::tls_client}; - connection conn{ex, ctx}; + connection conn{ex}; net::steady_timer st{ex}; st.expires_after(1s); @@ -62,8 +61,7 @@ auto async_check_cancellation_not_missed(int n, std::chrono::milliseconds ms) -> net::awaitable { auto ex = co_await net::this_coro::executor; - net::ssl::context ctx{net::ssl::context::tls_client}; - connection conn{ex, ctx}; + connection conn{ex}; net::steady_timer timer{ex}; diff --git a/tests/conn_tls.cpp b/tests/conn_tls.cpp index 4f792438..f368132c 100644 --- a/tests/conn_tls.cpp +++ b/tests/conn_tls.cpp @@ -43,8 +43,7 @@ BOOST_AUTO_TEST_CASE(ping) response resp; net::io_context ioc; - net::ssl::context ctx{net::ssl::context::tls_client}; - connection conn{ioc, ctx}; + connection conn{ioc}; conn.next_layer().set_verify_mode(net::ssl::verify_peer); conn.next_layer().set_verify_callback(verify_certificate); diff --git a/tests/issue_50.cpp b/tests/issue_50.cpp index 31224016..bf4b912e 100644 --- a/tests/issue_50.cpp +++ b/tests/issue_50.cpp @@ -84,12 +84,11 @@ periodic_task(std::shared_ptr conn) -> net::awaitable auto co_main(config cfg) -> net::awaitable { auto ex = co_await net::this_coro::executor; - auto ctx = std::make_shared(net::ssl::context::tls_client); - auto conn = std::make_shared(ex, *ctx); + auto conn = std::make_shared(ex); net::co_spawn(ex, receiver(conn), net::detached); net::co_spawn(ex, periodic_task(conn), net::detached); - conn->async_run(cfg, {}, net::consign(net::detached, conn, ctx)); + conn->async_run(cfg, {}, net::consign(net::detached, conn)); } #endif // defined(BOOST_ASIO_HAS_CO_AWAIT) diff --git a/tests/run.cpp b/tests/run.cpp index a00f3ec7..1b95903e 100644 --- a/tests/run.cpp +++ b/tests/run.cpp @@ -40,8 +40,7 @@ BOOST_AUTO_TEST_CASE(resolve_bad_host) cfg.health_check_interval = 10h; cfg.reconnect_wait_interval = 0s; - net::ssl::context ctx{net::ssl::context::tls_client}; - connection conn{ioc, ctx}; + connection conn{ioc}; conn.async_run(cfg, {}, [](auto ec){ BOOST_TEST(is_host_not_found(ec)); }); @@ -61,8 +60,7 @@ BOOST_AUTO_TEST_CASE(resolve_with_timeout) cfg.health_check_interval = 10h; cfg.reconnect_wait_interval = 0s; - net::ssl::context ctx{net::ssl::context::tls_client}; - auto conn = std::make_shared(ioc, ctx); + auto conn = std::make_shared(ioc); run(conn, cfg); ioc.run(); } @@ -79,8 +77,7 @@ BOOST_AUTO_TEST_CASE(connect_bad_port) cfg.health_check_interval = 10h; cfg.reconnect_wait_interval = 0s; - net::ssl::context ctx{net::ssl::context::tls_client}; - auto conn = std::make_shared(ioc, ctx); + auto conn = std::make_shared(ioc); run(conn, cfg, net::error::connection_refused); ioc.run(); } From 663e9ac67168d7b6d52f1089278e07f15e365f0b Mon Sep 17 00:00:00 2001 From: Marcelo Zimbres Date: Sat, 13 May 2023 10:22:11 +0200 Subject: [PATCH 15/32] Simplifications. --- CMakeLists.txt | 74 +++++++++++++++++++--------------- examples/main.cpp | 40 +++++++++++------- examples/start.cpp | 36 ----------------- examples/start.hpp | 17 -------- tests/common.cpp | 24 +++++++++++ tests/common.hpp | 7 ++-- tests/conn_check_health.cpp | 1 - tests/conn_echo_stress.cpp | 2 - tests/conn_exec.cpp | 1 - tests/conn_exec_cancel.cpp | 2 - tests/conn_exec_cancel2.cpp | 2 - tests/conn_exec_error.cpp | 1 - tests/conn_exec_retry.cpp | 2 - tests/conn_push.cpp | 1 - tests/conn_quit.cpp | 3 -- tests/conn_reconnect.cpp | 6 +-- tests/conn_run_cancel.cpp | 1 - tests/conn_tls.cpp | 2 - tests/cpp17_low_level_sync.cpp | 1 - tests/issue_50.cpp | 1 - tests/low_level.cpp | 2 - tests/request.cpp | 3 -- tests/run.cpp | 1 - 23 files changed, 94 insertions(+), 136 deletions(-) delete mode 100644 examples/start.cpp delete mode 100644 examples/start.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index faaf18b9..5ea1d836 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -55,13 +55,21 @@ find_package(OpenSSL REQUIRED) enable_testing() include_directories(include) +#======================================================================= + +add_library(boost_redis_src STATIC examples/boost_redis.cpp) +target_compile_features(boost_redis_src PUBLIC cxx_std_20) +if (MSVC) + target_compile_options(boost_redis_src PRIVATE /bigobj) + target_compile_definitions(boost_redis_src PRIVATE _WIN32_WINNT=0x0601) +endif() + # Main function for the examples. #======================================================================= -add_library(test_common STATIC - tests/common.cpp -) -target_link_libraries(test_common PUBLIC OpenSSL::Crypto OpenSSL::SSL) +add_library(test_common STATIC) +target_sources(test_common PUBLIC tests/common.cpp) +target_link_libraries(test_common PUBLIC OpenSSL::Crypto OpenSSL::SSL boost_redis_src) target_compile_features(test_common PUBLIC cxx_std_17) if (MSVC) target_compile_options(test_common PRIVATE /bigobj) @@ -70,22 +78,19 @@ endif() #======================================================================= -add_library(common STATIC - examples/start.cpp - examples/main.cpp - examples/boost_redis.cpp -) -target_compile_features(common PUBLIC cxx_std_20) +add_library(examples_common STATIC examples/main.cpp) +target_compile_features(examples_common PUBLIC cxx_std_20) +target_link_libraries(examples_common PRIVATE boost_redis_src) if (MSVC) - target_compile_options(common PRIVATE /bigobj) - target_compile_definitions(common PRIVATE _WIN32_WINNT=0x0601) + target_compile_options(examples_common PRIVATE /bigobj) + target_compile_definitions(examples_common PRIVATE _WIN32_WINNT=0x0601) endif() # Executables #======================================================================= add_executable(cpp20_intro examples/cpp20_intro.cpp) -target_link_libraries(cpp20_intro PRIVATE OpenSSL::Crypto OpenSSL::SSL common) +target_link_libraries(cpp20_intro PRIVATE OpenSSL::Crypto OpenSSL::SSL examples_common) target_compile_features(cpp20_intro PUBLIC cxx_std_20) add_test(cpp20_intro cpp20_intro) if (MSVC) @@ -94,7 +99,7 @@ if (MSVC) endif() add_executable(cpp20_streams examples/cpp20_streams.cpp) -target_link_libraries(cpp20_streams PRIVATE OpenSSL::Crypto OpenSSL::SSL common) +target_link_libraries(cpp20_streams PRIVATE OpenSSL::Crypto OpenSSL::SSL examples_common) target_compile_features(cpp20_streams PUBLIC cxx_std_20) if (MSVC) target_compile_options(cpp20_streams PRIVATE /bigobj) @@ -120,12 +125,12 @@ endif() if (NOT MSVC) add_executable(cpp20_chat_room examples/cpp20_chat_room.cpp) target_compile_features(cpp20_chat_room PUBLIC cxx_std_20) -target_link_libraries(cpp20_chat_room PRIVATE OpenSSL::Crypto OpenSSL::SSL common) +target_link_libraries(cpp20_chat_room PRIVATE OpenSSL::Crypto OpenSSL::SSL examples_common) endif() add_executable(cpp20_containers examples/cpp20_containers.cpp) target_compile_features(cpp20_containers PUBLIC cxx_std_20) -target_link_libraries(cpp20_containers PRIVATE OpenSSL::Crypto OpenSSL::SSL common) +target_link_libraries(cpp20_containers PRIVATE OpenSSL::Crypto OpenSSL::SSL examples_common) add_test(cpp20_containers cpp20_containers) if (MSVC) target_compile_options(cpp20_containers PRIVATE /bigobj) @@ -135,12 +140,12 @@ endif() if (NOT MSVC) add_executable(cpp20_echo_server examples/cpp20_echo_server.cpp) target_compile_features(cpp20_echo_server PUBLIC cxx_std_20) -target_link_libraries(cpp20_echo_server PRIVATE OpenSSL::Crypto OpenSSL::SSL common) +target_link_libraries(cpp20_echo_server PRIVATE OpenSSL::Crypto OpenSSL::SSL examples_common) endif() add_executable(cpp20_resolve_with_sentinel examples/cpp20_resolve_with_sentinel.cpp) target_compile_features(cpp20_resolve_with_sentinel PUBLIC cxx_std_20) -target_link_libraries(cpp20_resolve_with_sentinel PRIVATE OpenSSL::Crypto OpenSSL::SSL common) +target_link_libraries(cpp20_resolve_with_sentinel PRIVATE OpenSSL::Crypto OpenSSL::SSL examples_common) #add_test(cpp20_resolve_with_sentinel cpp20_resolve_with_sentinel) if (MSVC) target_compile_options(cpp20_resolve_with_sentinel PRIVATE /bigobj) @@ -149,7 +154,7 @@ endif() add_executable(cpp20_json examples/cpp20_json.cpp) target_compile_features(cpp20_json PUBLIC cxx_std_20) -target_link_libraries(cpp20_json PRIVATE OpenSSL::Crypto OpenSSL::SSL common) +target_link_libraries(cpp20_json PRIVATE OpenSSL::Crypto OpenSSL::SSL examples_common) add_test(cpp20_json cpp20_json) if (MSVC) target_compile_options(cpp20_json PRIVATE /bigobj) @@ -160,7 +165,7 @@ if (Protobuf_FOUND) protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS examples/person.proto) add_executable(cpp20_protobuf examples/cpp20_protobuf.cpp ${PROTO_SRCS} ${PROTO_HDRS}) target_compile_features(cpp20_protobuf PUBLIC cxx_std_20) - target_link_libraries(cpp20_protobuf PRIVATE OpenSSL::Crypto OpenSSL::SSL common ${Protobuf_LIBRARIES}) + target_link_libraries(cpp20_protobuf PRIVATE OpenSSL::Crypto OpenSSL::SSL examples_common ${Protobuf_LIBRARIES}) target_include_directories(cpp20_protobuf PUBLIC ${Protobuf_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR}) add_test(cpp20_protobuf cpp20_protobuf) if (MSVC) @@ -171,7 +176,7 @@ endif() add_executable(cpp20_subscriber examples/cpp20_subscriber.cpp) target_compile_features(cpp20_subscriber PUBLIC cxx_std_20) -target_link_libraries(cpp20_subscriber PRIVATE OpenSSL::Crypto OpenSSL::SSL common) +target_link_libraries(cpp20_subscriber PRIVATE OpenSSL::Crypto OpenSSL::SSL examples_common) if (MSVC) target_compile_options(cpp20_subscriber PRIVATE /bigobj) target_compile_definitions(cpp20_subscriber PRIVATE _WIN32_WINNT=0x0601) @@ -180,7 +185,7 @@ endif() add_executable(cpp20_intro_tls examples/cpp20_intro_tls.cpp) target_compile_features(cpp20_intro_tls PUBLIC cxx_std_20) add_test(cpp20_intro_tls cpp20_intro_tls) -target_link_libraries(cpp20_intro_tls PRIVATE OpenSSL::Crypto OpenSSL::SSL common) +target_link_libraries(cpp20_intro_tls PRIVATE OpenSSL::Crypto OpenSSL::SSL examples_common) if (MSVC) target_compile_options(cpp20_intro_tls PRIVATE /bigobj) target_compile_definitions(cpp20_intro_tls PRIVATE _WIN32_WINNT=0x0601) @@ -189,7 +194,7 @@ endif() add_executable(cpp20_low_level_async tests/cpp20_low_level_async.cpp) target_compile_features(cpp20_low_level_async PUBLIC cxx_std_20) add_test(cpp20_low_level_async cpp20_low_level_async) -target_link_libraries(cpp20_low_level_async PRIVATE OpenSSL::Crypto OpenSSL::SSL common) +target_link_libraries(cpp20_low_level_async PRIVATE OpenSSL::Crypto OpenSSL::SSL examples_common boost_redis_src) if (MSVC) target_compile_options(cpp20_low_level_async PRIVATE /bigobj) target_compile_definitions(cpp20_low_level_async PRIVATE _WIN32_WINNT=0x0601) @@ -211,6 +216,7 @@ endif() add_executable(cpp17_low_level_sync tests/cpp17_low_level_sync.cpp) target_compile_features(cpp17_low_level_sync PUBLIC cxx_std_17) +target_link_libraries(cpp17_low_level_sync PRIVATE OpenSSL::Crypto OpenSSL::SSL examples_common boost_redis_src) add_test(cpp17_low_level_sync cpp17_low_level_sync) if (MSVC) target_compile_options(cpp17_low_level_sync PRIVATE /bigobj) @@ -255,7 +261,7 @@ endif() add_executable(test_conn_reconnect tests/conn_reconnect.cpp) target_compile_features(test_conn_reconnect PUBLIC cxx_std_20) -target_link_libraries(test_conn_reconnect PRIVATE OpenSSL::Crypto OpenSSL::SSL common test_common) +target_link_libraries(test_conn_reconnect PRIVATE OpenSSL::Crypto OpenSSL::SSL test_common) add_test(test_conn_reconnect test_conn_reconnect) if (MSVC) target_compile_options(test_conn_reconnect PRIVATE /bigobj) @@ -265,7 +271,7 @@ endif() add_executable(test_conn_tls tests/conn_tls.cpp) add_test(test_conn_tls test_conn_tls) target_compile_features(test_conn_tls PUBLIC cxx_std_17) -target_link_libraries(test_conn_tls PRIVATE OpenSSL::Crypto OpenSSL::SSL) +target_link_libraries(test_conn_tls PRIVATE OpenSSL::Crypto OpenSSL::SSL boost_redis_src) if (MSVC) target_compile_options(test_conn_tls PRIVATE /bigobj) target_compile_definitions(test_conn_tls PRIVATE _WIN32_WINNT=0x0601) @@ -273,6 +279,7 @@ endif() add_executable(test_low_level tests/low_level.cpp) target_compile_features(test_low_level PUBLIC cxx_std_17) +target_link_libraries(test_low_level PRIVATE OpenSSL::Crypto OpenSSL::SSL boost_redis_src) add_test(test_low_level test_low_level) if (MSVC) target_compile_options(test_low_level PRIVATE /bigobj) @@ -282,7 +289,7 @@ endif() add_executable(test_conn_run_cancel tests/conn_run_cancel.cpp) target_compile_features(test_conn_run_cancel PUBLIC cxx_std_20) add_test(test_conn_run_cancel test_conn_run_cancel) -target_link_libraries(test_conn_run_cancel PRIVATE OpenSSL::Crypto OpenSSL::SSL) +target_link_libraries(test_conn_run_cancel PRIVATE OpenSSL::Crypto OpenSSL::SSL boost_redis_src) if (MSVC) target_compile_options(test_conn_run_cancel PRIVATE /bigobj) target_compile_definitions(test_conn_run_cancel PRIVATE _WIN32_WINNT=0x0601) @@ -290,7 +297,7 @@ endif() add_executable(test_conn_exec_cancel tests/conn_exec_cancel.cpp) target_compile_features(test_conn_exec_cancel PUBLIC cxx_std_20) -target_link_libraries(test_conn_exec_cancel PRIVATE OpenSSL::Crypto OpenSSL::SSL common test_common) +target_link_libraries(test_conn_exec_cancel PRIVATE OpenSSL::Crypto OpenSSL::SSL test_common boost_redis_src) add_test(test_conn_exec_cancel test_conn_exec_cancel) if (MSVC) target_compile_options(test_conn_exec_cancel PRIVATE /bigobj) @@ -299,7 +306,7 @@ endif() add_executable(test_conn_exec_cancel2 tests/conn_exec_cancel2.cpp) target_compile_features(test_conn_exec_cancel2 PUBLIC cxx_std_20) -target_link_libraries(test_conn_exec_cancel2 PRIVATE OpenSSL::Crypto OpenSSL::SSL common test_common) +target_link_libraries(test_conn_exec_cancel2 PRIVATE OpenSSL::Crypto OpenSSL::SSL test_common boost_redis_src) add_test(test_conn_exec_cancel2 test_conn_exec_cancel2) if (MSVC) target_compile_options(test_conn_exec_cancel2 PRIVATE /bigobj) @@ -308,7 +315,7 @@ endif() add_executable(test_conn_exec_error tests/conn_exec_error.cpp) target_compile_features(test_conn_exec_error PUBLIC cxx_std_17) -target_link_libraries(test_conn_exec_error PRIVATE OpenSSL::Crypto OpenSSL::SSL common test_common) +target_link_libraries(test_conn_exec_error PRIVATE OpenSSL::Crypto OpenSSL::SSL test_common boost_redis_src) add_test(test_conn_exec_error test_conn_exec_error) if (MSVC) target_compile_options(test_conn_exec_error PRIVATE /bigobj) @@ -317,7 +324,7 @@ endif() add_executable(test_conn_echo_stress tests/conn_echo_stress.cpp) target_compile_features(test_conn_echo_stress PUBLIC cxx_std_20) -target_link_libraries(test_conn_echo_stress PRIVATE OpenSSL::Crypto OpenSSL::SSL common test_common) +target_link_libraries(test_conn_echo_stress PRIVATE OpenSSL::Crypto OpenSSL::SSL test_common boost_redis_src) add_test(test_conn_echo_stress test_conn_echo_stress) if (MSVC) target_compile_options(test_conn_echo_stress PRIVATE /bigobj) @@ -326,6 +333,7 @@ endif() add_executable(test_request tests/request.cpp) target_compile_features(test_request PUBLIC cxx_std_17) +target_link_libraries(test_request PRIVATE OpenSSL::Crypto OpenSSL::SSL test_common boost_redis_src) add_test(test_request test_request) if (MSVC) target_compile_options(test_request PRIVATE /bigobj) @@ -334,7 +342,7 @@ endif() add_executable(test_issue_50 tests/issue_50.cpp) target_compile_features(test_issue_50 PUBLIC cxx_std_20) -target_link_libraries(test_issue_50 PRIVATE OpenSSL::Crypto OpenSSL::SSL common) +target_link_libraries(test_issue_50 PRIVATE OpenSSL::Crypto OpenSSL::SSL examples_common boost_redis_src) add_test(test_issue_50 test_issue_50) if (MSVC) target_compile_options(test_issue_50 PRIVATE /bigobj) @@ -343,7 +351,7 @@ endif() add_executable(test_conn_check_health tests/conn_check_health.cpp) target_compile_features(test_conn_check_health PUBLIC cxx_std_17) -target_link_libraries(test_conn_check_health PRIVATE OpenSSL::Crypto OpenSSL::SSL common) +target_link_libraries(test_conn_check_health PRIVATE OpenSSL::Crypto OpenSSL::SSL examples_common boost_redis_src) add_test(test_conn_check_health test_conn_check_health) if (MSVC) target_compile_options(test_conn_check_health PRIVATE /bigobj) @@ -352,7 +360,7 @@ endif() add_executable(test_run tests/run.cpp) target_compile_features(test_run PUBLIC cxx_std_17) -target_link_libraries(test_run PRIVATE OpenSSL::Crypto OpenSSL::SSL test_common) +target_link_libraries(test_run PRIVATE OpenSSL::Crypto OpenSSL::SSL test_common boost_redis_src) add_test(test_run test_run) if (MSVC) target_compile_options(test_run PRIVATE /bigobj) diff --git a/examples/main.cpp b/examples/main.cpp index ffd67d9b..e8501954 100644 --- a/examples/main.cpp +++ b/examples/main.cpp @@ -4,34 +4,44 @@ * accompanying file LICENSE.txt) */ -#include "start.hpp" #include -#include -#include +#include +#include +#include #include -#if defined(BOOST_ASIO_HAS_CO_AWAIT) - +namespace net = boost::asio; using boost::redis::config; -extern boost::asio::awaitable co_main(config); +#if defined(BOOST_ASIO_HAS_CO_AWAIT) + +extern net::awaitable co_main(config); auto main(int argc, char * argv[]) -> int { - config cfg; - - if (argc == 3) { - cfg.addr.host = argv[1]; - cfg.addr.port = argv[2]; + try { + config cfg; + + if (argc == 3) { + cfg.addr.host = argv[1]; + cfg.addr.port = argv[2]; + } + + net::io_context ioc; + net::co_spawn(ioc, std::move(co_main(cfg)), [](std::exception_ptr p) { + if (p) + std::rethrow_exception(p); + }); + ioc.run(); + + } catch (std::exception const& e) { + std::cerr << "(main) " << e.what() << std::endl; + return 1; } - - return start(co_main(cfg)); } #else // defined(BOOST_ASIO_HAS_CO_AWAIT) -#include - auto main() -> int { std::cout << "Requires coroutine support." << std::endl; diff --git a/examples/start.cpp b/examples/start.cpp deleted file mode 100644 index fb6a9f01..00000000 --- a/examples/start.cpp +++ /dev/null @@ -1,36 +0,0 @@ -/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com) - * - * Distributed under the Boost Software License, Version 1.0. (See - * accompanying file LICENSE.txt) - */ - -#include -#include -#include -#include -#include "start.hpp" - -#if defined(BOOST_ASIO_HAS_CO_AWAIT) - -namespace net = boost::asio; - -auto start(net::awaitable op) -> int -{ - try { - net::io_context ioc; - net::co_spawn(ioc, std::move(op), [](std::exception_ptr p) { - if (p) - std::rethrow_exception(p); - }); - ioc.run(); - - return 0; - - } catch (std::exception const& e) { - std::cerr << "start> " << e.what() << std::endl; - } - - return 1; -} - -#endif // defined(BOOST_ASIO_HAS_CO_AWAIT) diff --git a/examples/start.hpp b/examples/start.hpp deleted file mode 100644 index a4ee0ef5..00000000 --- a/examples/start.hpp +++ /dev/null @@ -1,17 +0,0 @@ -/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com) - * - * Distributed under the Boost Software License, Version 1.0. (See - * accompanying file LICENSE.txt) - */ - -#ifndef BOOST_REDIS_EXAMPLES_START_HPP -#define BOOST_REDIS_EXAMPLES_START_HPP - -#include - -#if defined(BOOST_ASIO_HAS_CO_AWAIT) - -auto start(boost::asio::awaitable op) -> int; - -#endif // defined(BOOST_ASIO_HAS_CO_AWAIT) -#endif // BOOST_REDIS_EXAMPLES_START_HPP diff --git a/tests/common.cpp b/tests/common.cpp index 6583ffd5..366cd1d8 100644 --- a/tests/common.cpp +++ b/tests/common.cpp @@ -1,9 +1,12 @@ #include "common.hpp" #include #include +#include #include +namespace net = boost::asio; + struct run_callback { std::shared_ptr conn; boost::redis::operation op; @@ -27,3 +30,24 @@ run( conn->async_run(cfg, {}, run_callback{conn, op, ec}); } +#ifdef BOOST_ASIO_HAS_CO_AWAIT +auto start(net::awaitable op) -> int +{ + try { + net::io_context ioc; + net::co_spawn(ioc, std::move(op), [](std::exception_ptr p) { + if (p) + std::rethrow_exception(p); + }); + ioc.run(); + + return 0; + + } catch (std::exception const& e) { + std::cerr << "start> " << e.what() << std::endl; + } + + return 1; +} + +#endif // BOOST_ASIO_HAS_CO_AWAIT diff --git a/tests/common.hpp b/tests/common.hpp index 3f8909d2..4b917b54 100644 --- a/tests/common.hpp +++ b/tests/common.hpp @@ -2,18 +2,17 @@ #include #include +#include #include #include #include #include -namespace net = boost::asio; - #ifdef BOOST_ASIO_HAS_CO_AWAIT - inline auto redir(boost::system::error_code& ec) - { return net::redirect_error(boost::asio::use_awaitable, ec); } + { return boost::asio::redirect_error(boost::asio::use_awaitable, ec); } +auto start(boost::asio::awaitable op) -> int; #endif // BOOST_ASIO_HAS_CO_AWAIT void diff --git a/tests/conn_check_health.cpp b/tests/conn_check_health.cpp index c69db4f9..494f22c7 100644 --- a/tests/conn_check_health.cpp +++ b/tests/conn_check_health.cpp @@ -10,7 +10,6 @@ #include #include #include "common.hpp" -#include namespace net = boost::asio; namespace redis = boost::redis; diff --git a/tests/conn_echo_stress.cpp b/tests/conn_echo_stress.cpp index 3adb1a0f..bcb1f395 100644 --- a/tests/conn_echo_stress.cpp +++ b/tests/conn_echo_stress.cpp @@ -12,8 +12,6 @@ #include #include #include "common.hpp" -#include "../examples/start.hpp" -#include #ifdef BOOST_ASIO_HAS_CO_AWAIT diff --git a/tests/conn_exec.cpp b/tests/conn_exec.cpp index c9eaeac2..b5fe3a1e 100644 --- a/tests/conn_exec.cpp +++ b/tests/conn_exec.cpp @@ -10,7 +10,6 @@ #include #include #include "common.hpp" -#include // TODO: Test whether HELLO won't be inserted passt commands that have // been already writen. diff --git a/tests/conn_exec_cancel.cpp b/tests/conn_exec_cancel.cpp index 882698f4..1d257387 100644 --- a/tests/conn_exec_cancel.cpp +++ b/tests/conn_exec_cancel.cpp @@ -9,9 +9,7 @@ #define BOOST_TEST_MODULE conn-exec-cancel #include #include "common.hpp" -#include "../examples/start.hpp" #include -#include #ifdef BOOST_ASIO_HAS_CO_AWAIT #include diff --git a/tests/conn_exec_cancel2.cpp b/tests/conn_exec_cancel2.cpp index 83b383c4..37ee49fb 100644 --- a/tests/conn_exec_cancel2.cpp +++ b/tests/conn_exec_cancel2.cpp @@ -9,9 +9,7 @@ #define BOOST_TEST_MODULE conn-exec-cancel #include #include "common.hpp" -#include "../examples/start.hpp" #include -#include #ifdef BOOST_ASIO_HAS_CO_AWAIT #include diff --git a/tests/conn_exec_error.cpp b/tests/conn_exec_error.cpp index 45b9de2c..16f53631 100644 --- a/tests/conn_exec_error.cpp +++ b/tests/conn_exec_error.cpp @@ -10,7 +10,6 @@ #define BOOST_TEST_MODULE conn-exec-error #include #include "common.hpp" -#include #include namespace net = boost::asio; diff --git a/tests/conn_exec_retry.cpp b/tests/conn_exec_retry.cpp index 41ca94cb..7cc1f232 100644 --- a/tests/conn_exec_retry.cpp +++ b/tests/conn_exec_retry.cpp @@ -13,8 +13,6 @@ #include #include "common.hpp" -#include - namespace net = boost::asio; using error_code = boost::system::error_code; using connection = boost::redis::connection; diff --git a/tests/conn_push.cpp b/tests/conn_push.cpp index d2a9399b..c5770026 100644 --- a/tests/conn_push.cpp +++ b/tests/conn_push.cpp @@ -14,7 +14,6 @@ #include #include #include "common.hpp" -#include namespace net = boost::asio; namespace redis = boost::redis; diff --git a/tests/conn_quit.cpp b/tests/conn_quit.cpp index 98df54a5..bf126ba1 100644 --- a/tests/conn_quit.cpp +++ b/tests/conn_quit.cpp @@ -11,9 +11,6 @@ #include #include "common.hpp" -// TODO: Move this to a lib. -#include - namespace net = boost::asio; using boost::redis::connection; using boost::system::error_code; diff --git a/tests/conn_reconnect.cpp b/tests/conn_reconnect.cpp index ce948709..e1d37fe0 100644 --- a/tests/conn_reconnect.cpp +++ b/tests/conn_reconnect.cpp @@ -10,8 +10,6 @@ #include #include #include "common.hpp" -#include "../examples/start.hpp" -#include #ifdef BOOST_ASIO_HAS_CO_AWAIT #include @@ -57,9 +55,7 @@ net::awaitable test_reconnect_impl() // Test whether the client works after a reconnect. BOOST_AUTO_TEST_CASE(test_reconnect) { - net::io_context ioc; - net::co_spawn(ioc, test_reconnect_impl(), net::detached); - ioc.run(); + start(test_reconnect_impl()); } auto async_test_reconnect_timeout() -> net::awaitable diff --git a/tests/conn_run_cancel.cpp b/tests/conn_run_cancel.cpp index d9f383f1..09934c2b 100644 --- a/tests/conn_run_cancel.cpp +++ b/tests/conn_run_cancel.cpp @@ -12,7 +12,6 @@ #include #include #include "common.hpp" -#include #ifdef BOOST_ASIO_HAS_CO_AWAIT #include diff --git a/tests/conn_tls.cpp b/tests/conn_tls.cpp index f368132c..fef978ff 100644 --- a/tests/conn_tls.cpp +++ b/tests/conn_tls.cpp @@ -10,8 +10,6 @@ #include #include "common.hpp" -#include - namespace net = boost::asio; using connection = boost::redis::connection; diff --git a/tests/cpp17_low_level_sync.cpp b/tests/cpp17_low_level_sync.cpp index 81cd5949..78c753f8 100644 --- a/tests/cpp17_low_level_sync.cpp +++ b/tests/cpp17_low_level_sync.cpp @@ -11,7 +11,6 @@ #include #include #include -#include namespace net = boost::asio; namespace redis = boost::redis; diff --git a/tests/issue_50.cpp b/tests/issue_50.cpp index bf4b912e..38bc5f53 100644 --- a/tests/issue_50.cpp +++ b/tests/issue_50.cpp @@ -17,7 +17,6 @@ #include #include #include -#include "../examples/start.hpp" #if defined(BOOST_ASIO_HAS_CO_AWAIT) diff --git a/tests/low_level.cpp b/tests/low_level.cpp index 9902aedc..059d2e6f 100644 --- a/tests/low_level.cpp +++ b/tests/low_level.cpp @@ -22,8 +22,6 @@ #include #include -#include - // TODO: Test with empty strings. namespace std diff --git a/tests/request.cpp b/tests/request.cpp index 8b5a603b..6dd29f04 100644 --- a/tests/request.cpp +++ b/tests/request.cpp @@ -10,9 +10,6 @@ #include #include -#include -#include -#include using boost::redis::request; diff --git a/tests/run.cpp b/tests/run.cpp index 1b95903e..b6837b17 100644 --- a/tests/run.cpp +++ b/tests/run.cpp @@ -8,7 +8,6 @@ #define BOOST_TEST_MODULE run #include #include -#include #include "common.hpp" namespace net = boost::asio; From 22bacbd52c1fdcdab4bab3c3ed6cf85075d1e4d4 Mon Sep 17 00:00:00 2001 From: Marcelo Zimbres Date: Sun, 14 May 2023 10:04:59 +0200 Subject: [PATCH 16/32] Simplifies the CMakeLists.txt. --- CMakeLists.txt | 365 ++++-------------- examples/cpp17_intro.cpp | 2 - examples/cpp17_intro_sync.cpp | 3 - ..._health.cpp => test_conn_check_health.cpp} | 0 ...o_stress.cpp => test_conn_echo_stress.cpp} | 0 tests/{conn_exec.cpp => test_conn_exec.cpp} | 0 ...c_cancel.cpp => test_conn_exec_cancel.cpp} | 0 ...cancel2.cpp => test_conn_exec_cancel2.cpp} | 0 ...xec_error.cpp => test_conn_exec_error.cpp} | 0 ...xec_retry.cpp => test_conn_exec_retry.cpp} | 0 tests/{conn_push.cpp => test_conn_push.cpp} | 0 tests/{conn_quit.cpp => test_conn_quit.cpp} | 0 ..._reconnect.cpp => test_conn_reconnect.cpp} | 0 ...un_cancel.cpp => test_conn_run_cancel.cpp} | 0 tests/{conn_tls.cpp => test_conn_tls.cpp} | 0 tests/{issue_50.cpp => test_issue_50.cpp} | 15 + tests/{low_level.cpp => test_low_level.cpp} | 0 ...vel_async.cpp => test_low_level_async.cpp} | 17 + ...level_sync.cpp => test_low_level_sync.cpp} | 13 +- tests/{request.cpp => test_request.cpp} | 0 tests/{run.cpp => test_run.cpp} | 0 21 files changed, 122 insertions(+), 293 deletions(-) rename tests/{conn_check_health.cpp => test_conn_check_health.cpp} (100%) rename tests/{conn_echo_stress.cpp => test_conn_echo_stress.cpp} (100%) rename tests/{conn_exec.cpp => test_conn_exec.cpp} (100%) rename tests/{conn_exec_cancel.cpp => test_conn_exec_cancel.cpp} (100%) rename tests/{conn_exec_cancel2.cpp => test_conn_exec_cancel2.cpp} (100%) rename tests/{conn_exec_error.cpp => test_conn_exec_error.cpp} (100%) rename tests/{conn_exec_retry.cpp => test_conn_exec_retry.cpp} (100%) rename tests/{conn_push.cpp => test_conn_push.cpp} (100%) rename tests/{conn_quit.cpp => test_conn_quit.cpp} (100%) rename tests/{conn_reconnect.cpp => test_conn_reconnect.cpp} (100%) rename tests/{conn_run_cancel.cpp => test_conn_run_cancel.cpp} (100%) rename tests/{conn_tls.cpp => test_conn_tls.cpp} (100%) rename tests/{issue_50.cpp => test_issue_50.cpp} (90%) rename tests/{low_level.cpp => test_low_level.cpp} (100%) rename tests/{cpp20_low_level_async.cpp => test_low_level_async.cpp} (82%) rename tests/{cpp17_low_level_sync.cpp => test_low_level_sync.cpp} (87%) rename tests/{request.cpp => test_request.cpp} (100%) rename tests/{run.cpp => test_run.cpp} (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5ea1d836..fbdea25c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -57,315 +57,120 @@ include_directories(include) #======================================================================= -add_library(boost_redis_src STATIC examples/boost_redis.cpp) -target_compile_features(boost_redis_src PUBLIC cxx_std_20) -if (MSVC) - target_compile_options(boost_redis_src PRIVATE /bigobj) - target_compile_definitions(boost_redis_src PRIVATE _WIN32_WINNT=0x0601) -endif() +set(libs_common boost_redis_src) +set(libs_cpp17 tests_common) +set(libs_cpp20 examples_main) -# Main function for the examples. -#======================================================================= +foreach(lib IN LISTS libs_cpp20 libs_cpp17 libs_common) + add_library(${lib} STATIC) +endforeach() -add_library(test_common STATIC) -target_sources(test_common PUBLIC tests/common.cpp) -target_link_libraries(test_common PUBLIC OpenSSL::Crypto OpenSSL::SSL boost_redis_src) -target_compile_features(test_common PUBLIC cxx_std_17) -if (MSVC) - target_compile_options(test_common PRIVATE /bigobj) - target_compile_definitions(test_common PRIVATE _WIN32_WINNT=0x0601) -endif() +target_sources(boost_redis_src PUBLIC examples/boost_redis.cpp) +target_sources(tests_common PUBLIC tests/common.cpp) +target_sources(examples_main PUBLIC examples/main.cpp) +# Executables #======================================================================= -add_library(examples_common STATIC examples/main.cpp) -target_compile_features(examples_common PUBLIC cxx_std_20) -target_link_libraries(examples_common PRIVATE boost_redis_src) -if (MSVC) - target_compile_options(examples_common PRIVATE /bigobj) - target_compile_definitions(examples_common PRIVATE _WIN32_WINNT=0x0601) -endif() +set(benchmakrs echo_server_client echo_server_direct) +add_executable(echo_server_client benchmarks/cpp/asio/echo_server_client.cpp) +add_executable(echo_server_direct benchmarks/cpp/asio/echo_server_direct.cpp) -# Executables -#======================================================================= +set(tests_cpp17 + test_conn_quit + test_conn_tls + test_low_level + test_conn_exec_retry + test_conn_exec_error + test_request + test_run + test_low_level_sync + test_conn_check_health) + +set(tests_cpp20 + test_conn_exec + test_conn_push + test_conn_reconnect + test_conn_exec_cancel + test_conn_exec_cancel2 + test_conn_echo_stress + test_low_level_async + test_conn_run_cancel + test_issue_50 +) -add_executable(cpp20_intro examples/cpp20_intro.cpp) -target_link_libraries(cpp20_intro PRIVATE OpenSSL::Crypto OpenSSL::SSL examples_common) -target_compile_features(cpp20_intro PUBLIC cxx_std_20) -add_test(cpp20_intro cpp20_intro) -if (MSVC) - target_compile_options(cpp20_intro PRIVATE /bigobj) - target_compile_definitions(cpp20_intro PRIVATE _WIN32_WINNT=0x0601) -endif() +set(examples_cpp17 + cpp17_intro + cpp17_intro_sync) -add_executable(cpp20_streams examples/cpp20_streams.cpp) -target_link_libraries(cpp20_streams PRIVATE OpenSSL::Crypto OpenSSL::SSL examples_common) -target_compile_features(cpp20_streams PUBLIC cxx_std_20) -if (MSVC) - target_compile_options(cpp20_streams PRIVATE /bigobj) - target_compile_definitions(cpp20_streams PRIVATE _WIN32_WINNT=0x0601) -endif() +set(examples_cpp20 + cpp20_intro + cpp20_streams + cpp20_containers + cpp20_echo_server + cpp20_resolve_with_sentinel + cpp20_json + cpp20_subscriber + cpp20_intro_tls) -add_executable(cpp17_intro examples/cpp17_intro.cpp) -target_link_libraries(cpp17_intro PRIVATE OpenSSL::Crypto OpenSSL::SSL) -target_compile_features(cpp17_intro PUBLIC cxx_std_17) -add_test(cpp17_intro cpp17_intro) -if (MSVC) - target_compile_options(cpp17_intro PRIVATE /bigobj) - target_compile_definitions(cpp17_intro PRIVATE _WIN32_WINNT=0x0601) +if (Protobuf_FOUND) + list(APPEND examples_cpp20 cpp20_protobuf) endif() if (NOT MSVC) -add_executable(cpp17_intro_sync examples/cpp17_intro_sync.cpp) -target_compile_features(cpp17_intro_sync PUBLIC cxx_std_17) -target_link_libraries(cpp17_intro_sync PRIVATE OpenSSL::Crypto OpenSSL::SSL) -add_test(cpp17_intro_sync cpp17_intro_sync) + list(APPEND examples_cpp20 cpp20_chat_room) endif() -if (NOT MSVC) -add_executable(cpp20_chat_room examples/cpp20_chat_room.cpp) -target_compile_features(cpp20_chat_room PUBLIC cxx_std_20) -target_link_libraries(cpp20_chat_room PRIVATE OpenSSL::Crypto OpenSSL::SSL examples_common) -endif() +foreach(exe IN LISTS examples_cpp17 examples_cpp20) + add_executable(${exe} examples/${exe}.cpp) +endforeach() -add_executable(cpp20_containers examples/cpp20_containers.cpp) -target_compile_features(cpp20_containers PUBLIC cxx_std_20) -target_link_libraries(cpp20_containers PRIVATE OpenSSL::Crypto OpenSSL::SSL examples_common) -add_test(cpp20_containers cpp20_containers) -if (MSVC) - target_compile_options(cpp20_containers PRIVATE /bigobj) - target_compile_definitions(cpp20_containers PRIVATE _WIN32_WINNT=0x0601) -endif() +foreach(exe IN LISTS examples_cpp20) + target_link_libraries(${exe} PRIVATE examples_main) +endforeach() -if (NOT MSVC) -add_executable(cpp20_echo_server examples/cpp20_echo_server.cpp) -target_compile_features(cpp20_echo_server PUBLIC cxx_std_20) -target_link_libraries(cpp20_echo_server PRIVATE OpenSSL::Crypto OpenSSL::SSL examples_common) -endif() +foreach(exe IN LISTS tests_cpp17 tests_cpp20) + add_executable(${exe} tests/${exe}.cpp) + target_link_libraries(${exe} PRIVATE tests_common) +endforeach() -add_executable(cpp20_resolve_with_sentinel examples/cpp20_resolve_with_sentinel.cpp) -target_compile_features(cpp20_resolve_with_sentinel PUBLIC cxx_std_20) -target_link_libraries(cpp20_resolve_with_sentinel PRIVATE OpenSSL::Crypto OpenSSL::SSL examples_common) -#add_test(cpp20_resolve_with_sentinel cpp20_resolve_with_sentinel) -if (MSVC) - target_compile_options(cpp20_resolve_with_sentinel PRIVATE /bigobj) - target_compile_definitions(cpp20_resolve_with_sentinel PRIVATE _WIN32_WINNT=0x0601) -endif() +foreach(exe IN LISTS tests_cpp17 tests_cpp20 examples_cpp17 examples_cpp20 libs_cpp17 libs_cpp20 benchmakrs) + target_link_libraries(${exe} PRIVATE OpenSSL::Crypto OpenSSL::SSL boost_redis_src) +endforeach() -add_executable(cpp20_json examples/cpp20_json.cpp) -target_compile_features(cpp20_json PUBLIC cxx_std_20) -target_link_libraries(cpp20_json PRIVATE OpenSSL::Crypto OpenSSL::SSL examples_common) -add_test(cpp20_json cpp20_json) if (MSVC) - target_compile_options(cpp20_json PRIVATE /bigobj) - target_compile_definitions(cpp20_json PRIVATE _WIN32_WINNT=0x0601) + foreach(exe IN LISTS tests_cpp17 tests_cpp20 examples_cpp17 examples_cpp20 libs_cpp17 libs_cpp20 benchmakrs libs_common) + target_compile_options(${exe} PRIVATE /bigobj) + target_compile_definitions(${exe} PRIVATE _WIN32_WINNT=0x0601) + endforeach() endif() +foreach(exe IN LISTS tests_cpp20 examples_cpp20 libs_cpp20 benchmarks) + target_compile_features(${exe} PUBLIC cxx_std_20) +endforeach() + +foreach(exe IN LISTS tests_cpp17 examples_cpp17 libs_cpp17 libs_common) + target_compile_features(${exe} PUBLIC cxx_std_17) +endforeach() + if (Protobuf_FOUND) protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS examples/person.proto) - add_executable(cpp20_protobuf examples/cpp20_protobuf.cpp ${PROTO_SRCS} ${PROTO_HDRS}) - target_compile_features(cpp20_protobuf PUBLIC cxx_std_20) - target_link_libraries(cpp20_protobuf PRIVATE OpenSSL::Crypto OpenSSL::SSL examples_common ${Protobuf_LIBRARIES}) + target_sources(cpp20_protobuf PUBLIC ${PROTO_SRCS} ${PROTO_HDRS}) + target_link_libraries(cpp20_protobuf PRIVATE ${Protobuf_LIBRARIES}) target_include_directories(cpp20_protobuf PUBLIC ${Protobuf_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR}) add_test(cpp20_protobuf cpp20_protobuf) - if (MSVC) - target_compile_options(cpp20_protobuf PRIVATE /bigobj) - target_compile_definitions(cpp20_protobuf PRIVATE _WIN32_WINNT=0x0601) - endif() endif() -add_executable(cpp20_subscriber examples/cpp20_subscriber.cpp) -target_compile_features(cpp20_subscriber PUBLIC cxx_std_20) -target_link_libraries(cpp20_subscriber PRIVATE OpenSSL::Crypto OpenSSL::SSL examples_common) -if (MSVC) - target_compile_options(cpp20_subscriber PRIVATE /bigobj) - target_compile_definitions(cpp20_subscriber PRIVATE _WIN32_WINNT=0x0601) -endif() - -add_executable(cpp20_intro_tls examples/cpp20_intro_tls.cpp) -target_compile_features(cpp20_intro_tls PUBLIC cxx_std_20) +add_test(cpp17_intro cpp17_intro) +add_test(cpp17_intro_sync cpp17_intro_sync) +add_test(cpp20_intro cpp20_intro) +add_test(cpp20_containers cpp20_containers) +add_test(cpp20_json cpp20_json) add_test(cpp20_intro_tls cpp20_intro_tls) -target_link_libraries(cpp20_intro_tls PRIVATE OpenSSL::Crypto OpenSSL::SSL examples_common) -if (MSVC) - target_compile_options(cpp20_intro_tls PRIVATE /bigobj) - target_compile_definitions(cpp20_intro_tls PRIVATE _WIN32_WINNT=0x0601) -endif() - -add_executable(cpp20_low_level_async tests/cpp20_low_level_async.cpp) -target_compile_features(cpp20_low_level_async PUBLIC cxx_std_20) -add_test(cpp20_low_level_async cpp20_low_level_async) -target_link_libraries(cpp20_low_level_async PRIVATE OpenSSL::Crypto OpenSSL::SSL examples_common boost_redis_src) -if (MSVC) - target_compile_options(cpp20_low_level_async PRIVATE /bigobj) - target_compile_definitions(cpp20_low_level_async PRIVATE _WIN32_WINNT=0x0601) -endif() -add_executable(echo_server_client benchmarks/cpp/asio/echo_server_client.cpp) -target_compile_features(echo_server_client PUBLIC cxx_std_20) -if (MSVC) - target_compile_options(echo_server_client PRIVATE /bigobj) - target_compile_definitions(echo_server_client PRIVATE _WIN32_WINNT=0x0601) -endif() - -add_executable(echo_server_direct benchmarks/cpp/asio/echo_server_direct.cpp) -target_compile_features(echo_server_direct PUBLIC cxx_std_20) -if (MSVC) - target_compile_options(echo_server_direct PRIVATE /bigobj) - target_compile_definitions(echo_server_direct PRIVATE _WIN32_WINNT=0x0601) -endif() - -add_executable(cpp17_low_level_sync tests/cpp17_low_level_sync.cpp) -target_compile_features(cpp17_low_level_sync PUBLIC cxx_std_17) -target_link_libraries(cpp17_low_level_sync PRIVATE OpenSSL::Crypto OpenSSL::SSL examples_common boost_redis_src) -add_test(cpp17_low_level_sync cpp17_low_level_sync) -if (MSVC) - target_compile_options(cpp17_low_level_sync PRIVATE /bigobj) - target_compile_definitions(cpp17_low_level_sync PRIVATE _WIN32_WINNT=0x0601) -endif() - -add_executable(test_conn_exec tests/conn_exec.cpp) -target_compile_features(test_conn_exec PUBLIC cxx_std_20) -target_link_libraries(test_conn_exec PRIVATE OpenSSL::Crypto OpenSSL::SSL test_common) -add_test(test_conn_exec test_conn_exec) -if (MSVC) - target_compile_options(test_conn_exec PRIVATE /bigobj) - target_compile_definitions(test_conn_exec PRIVATE _WIN32_WINNT=0x0601) -endif() - -add_executable(test_conn_exec_retry tests/conn_exec_retry.cpp) -target_compile_features(test_conn_exec_retry PUBLIC cxx_std_20) -target_link_libraries(test_conn_exec_retry PRIVATE OpenSSL::Crypto OpenSSL::SSL test_common) -add_test(test_conn_exec_retry test_conn_exec_retry) -if (MSVC) - target_compile_options(test_conn_exec_retry PRIVATE /bigobj) - target_compile_definitions(test_conn_exec_retry PRIVATE _WIN32_WINNT=0x0601) -endif() - -add_executable(test_conn_push tests/conn_push.cpp) -target_compile_features(test_conn_push PUBLIC cxx_std_20) -target_link_libraries(test_conn_push PRIVATE OpenSSL::Crypto OpenSSL::SSL test_common) -add_test(test_conn_push test_conn_push) -if (MSVC) - target_compile_options(test_conn_push PRIVATE /bigobj) - target_compile_definitions(test_conn_push PRIVATE _WIN32_WINNT=0x0601) -endif() - -add_executable(test_conn_quit tests/conn_quit.cpp) -target_compile_features(test_conn_quit PUBLIC cxx_std_17) -target_link_libraries(test_conn_quit PRIVATE OpenSSL::Crypto OpenSSL::SSL test_common) -add_test(test_conn_quit test_conn_quit) -if (MSVC) - target_compile_options(test_conn_quit PRIVATE /bigobj) - target_compile_definitions(test_conn_quit PRIVATE _WIN32_WINNT=0x0601) -endif() - -add_executable(test_conn_reconnect tests/conn_reconnect.cpp) -target_compile_features(test_conn_reconnect PUBLIC cxx_std_20) -target_link_libraries(test_conn_reconnect PRIVATE OpenSSL::Crypto OpenSSL::SSL test_common) -add_test(test_conn_reconnect test_conn_reconnect) -if (MSVC) - target_compile_options(test_conn_reconnect PRIVATE /bigobj) - target_compile_definitions(test_conn_reconnect PRIVATE _WIN32_WINNT=0x0601) -endif() - -add_executable(test_conn_tls tests/conn_tls.cpp) -add_test(test_conn_tls test_conn_tls) -target_compile_features(test_conn_tls PUBLIC cxx_std_17) -target_link_libraries(test_conn_tls PRIVATE OpenSSL::Crypto OpenSSL::SSL boost_redis_src) -if (MSVC) - target_compile_options(test_conn_tls PRIVATE /bigobj) - target_compile_definitions(test_conn_tls PRIVATE _WIN32_WINNT=0x0601) -endif() - -add_executable(test_low_level tests/low_level.cpp) -target_compile_features(test_low_level PUBLIC cxx_std_17) -target_link_libraries(test_low_level PRIVATE OpenSSL::Crypto OpenSSL::SSL boost_redis_src) -add_test(test_low_level test_low_level) -if (MSVC) - target_compile_options(test_low_level PRIVATE /bigobj) - target_compile_definitions(test_low_level PRIVATE _WIN32_WINNT=0x0601) -endif() - -add_executable(test_conn_run_cancel tests/conn_run_cancel.cpp) -target_compile_features(test_conn_run_cancel PUBLIC cxx_std_20) -add_test(test_conn_run_cancel test_conn_run_cancel) -target_link_libraries(test_conn_run_cancel PRIVATE OpenSSL::Crypto OpenSSL::SSL boost_redis_src) -if (MSVC) - target_compile_options(test_conn_run_cancel PRIVATE /bigobj) - target_compile_definitions(test_conn_run_cancel PRIVATE _WIN32_WINNT=0x0601) -endif() - -add_executable(test_conn_exec_cancel tests/conn_exec_cancel.cpp) -target_compile_features(test_conn_exec_cancel PUBLIC cxx_std_20) -target_link_libraries(test_conn_exec_cancel PRIVATE OpenSSL::Crypto OpenSSL::SSL test_common boost_redis_src) -add_test(test_conn_exec_cancel test_conn_exec_cancel) -if (MSVC) - target_compile_options(test_conn_exec_cancel PRIVATE /bigobj) - target_compile_definitions(test_conn_exec_cancel PRIVATE _WIN32_WINNT=0x0601) -endif() - -add_executable(test_conn_exec_cancel2 tests/conn_exec_cancel2.cpp) -target_compile_features(test_conn_exec_cancel2 PUBLIC cxx_std_20) -target_link_libraries(test_conn_exec_cancel2 PRIVATE OpenSSL::Crypto OpenSSL::SSL test_common boost_redis_src) -add_test(test_conn_exec_cancel2 test_conn_exec_cancel2) -if (MSVC) - target_compile_options(test_conn_exec_cancel2 PRIVATE /bigobj) - target_compile_definitions(test_conn_exec_cancel2 PRIVATE _WIN32_WINNT=0x0601) -endif() - -add_executable(test_conn_exec_error tests/conn_exec_error.cpp) -target_compile_features(test_conn_exec_error PUBLIC cxx_std_17) -target_link_libraries(test_conn_exec_error PRIVATE OpenSSL::Crypto OpenSSL::SSL test_common boost_redis_src) -add_test(test_conn_exec_error test_conn_exec_error) -if (MSVC) - target_compile_options(test_conn_exec_error PRIVATE /bigobj) - target_compile_definitions(test_conn_exec_error PRIVATE _WIN32_WINNT=0x0601) -endif() - -add_executable(test_conn_echo_stress tests/conn_echo_stress.cpp) -target_compile_features(test_conn_echo_stress PUBLIC cxx_std_20) -target_link_libraries(test_conn_echo_stress PRIVATE OpenSSL::Crypto OpenSSL::SSL test_common boost_redis_src) -add_test(test_conn_echo_stress test_conn_echo_stress) -if (MSVC) - target_compile_options(test_conn_echo_stress PRIVATE /bigobj) - target_compile_definitions(test_conn_echo_stress PRIVATE _WIN32_WINNT=0x0601) -endif() - -add_executable(test_request tests/request.cpp) -target_compile_features(test_request PUBLIC cxx_std_17) -target_link_libraries(test_request PRIVATE OpenSSL::Crypto OpenSSL::SSL test_common boost_redis_src) -add_test(test_request test_request) -if (MSVC) - target_compile_options(test_request PRIVATE /bigobj) - target_compile_definitions(test_request PRIVATE _WIN32_WINNT=0x0601) -endif() - -add_executable(test_issue_50 tests/issue_50.cpp) -target_compile_features(test_issue_50 PUBLIC cxx_std_20) -target_link_libraries(test_issue_50 PRIVATE OpenSSL::Crypto OpenSSL::SSL examples_common boost_redis_src) -add_test(test_issue_50 test_issue_50) -if (MSVC) - target_compile_options(test_issue_50 PRIVATE /bigobj) - target_compile_definitions(test_issue_50 PRIVATE _WIN32_WINNT=0x0601) -endif() - -add_executable(test_conn_check_health tests/conn_check_health.cpp) -target_compile_features(test_conn_check_health PUBLIC cxx_std_17) -target_link_libraries(test_conn_check_health PRIVATE OpenSSL::Crypto OpenSSL::SSL examples_common boost_redis_src) -add_test(test_conn_check_health test_conn_check_health) -if (MSVC) - target_compile_options(test_conn_check_health PRIVATE /bigobj) - target_compile_definitions(test_conn_check_health PRIVATE _WIN32_WINNT=0x0601) -endif() - -add_executable(test_run tests/run.cpp) -target_compile_features(test_run PUBLIC cxx_std_17) -target_link_libraries(test_run PRIVATE OpenSSL::Crypto OpenSSL::SSL test_common boost_redis_src) -add_test(test_run test_run) -if (MSVC) - target_compile_options(test_run PRIVATE /bigobj) - target_compile_definitions(test_run PRIVATE _WIN32_WINNT=0x0601) -endif() +foreach(exe IN LISTS tests_cpp17 tests_cpp20) + add_test(${exe} ${exe}) +endforeach() # Install #======================================================================= diff --git a/examples/cpp17_intro.cpp b/examples/cpp17_intro.cpp index c6cbd6c8..13a303a0 100644 --- a/examples/cpp17_intro.cpp +++ b/examples/cpp17_intro.cpp @@ -8,8 +8,6 @@ #include #include -#include - namespace net = boost::asio; using boost::redis::connection; using boost::redis::request; diff --git a/examples/cpp17_intro_sync.cpp b/examples/cpp17_intro_sync.cpp index bd106d29..e9a4627d 100644 --- a/examples/cpp17_intro_sync.cpp +++ b/examples/cpp17_intro_sync.cpp @@ -9,9 +9,6 @@ #include #include -// Include this in no more than one .cpp file. -#include - namespace net = boost::asio; using boost::redis::sync_connection; using boost::redis::request; diff --git a/tests/conn_check_health.cpp b/tests/test_conn_check_health.cpp similarity index 100% rename from tests/conn_check_health.cpp rename to tests/test_conn_check_health.cpp diff --git a/tests/conn_echo_stress.cpp b/tests/test_conn_echo_stress.cpp similarity index 100% rename from tests/conn_echo_stress.cpp rename to tests/test_conn_echo_stress.cpp diff --git a/tests/conn_exec.cpp b/tests/test_conn_exec.cpp similarity index 100% rename from tests/conn_exec.cpp rename to tests/test_conn_exec.cpp diff --git a/tests/conn_exec_cancel.cpp b/tests/test_conn_exec_cancel.cpp similarity index 100% rename from tests/conn_exec_cancel.cpp rename to tests/test_conn_exec_cancel.cpp diff --git a/tests/conn_exec_cancel2.cpp b/tests/test_conn_exec_cancel2.cpp similarity index 100% rename from tests/conn_exec_cancel2.cpp rename to tests/test_conn_exec_cancel2.cpp diff --git a/tests/conn_exec_error.cpp b/tests/test_conn_exec_error.cpp similarity index 100% rename from tests/conn_exec_error.cpp rename to tests/test_conn_exec_error.cpp diff --git a/tests/conn_exec_retry.cpp b/tests/test_conn_exec_retry.cpp similarity index 100% rename from tests/conn_exec_retry.cpp rename to tests/test_conn_exec_retry.cpp diff --git a/tests/conn_push.cpp b/tests/test_conn_push.cpp similarity index 100% rename from tests/conn_push.cpp rename to tests/test_conn_push.cpp diff --git a/tests/conn_quit.cpp b/tests/test_conn_quit.cpp similarity index 100% rename from tests/conn_quit.cpp rename to tests/test_conn_quit.cpp diff --git a/tests/conn_reconnect.cpp b/tests/test_conn_reconnect.cpp similarity index 100% rename from tests/conn_reconnect.cpp rename to tests/test_conn_reconnect.cpp diff --git a/tests/conn_run_cancel.cpp b/tests/test_conn_run_cancel.cpp similarity index 100% rename from tests/conn_run_cancel.cpp rename to tests/test_conn_run_cancel.cpp diff --git a/tests/conn_tls.cpp b/tests/test_conn_tls.cpp similarity index 100% rename from tests/conn_tls.cpp rename to tests/test_conn_tls.cpp diff --git a/tests/issue_50.cpp b/tests/test_issue_50.cpp similarity index 90% rename from tests/issue_50.cpp rename to tests/test_issue_50.cpp index 38bc5f53..9abab044 100644 --- a/tests/issue_50.cpp +++ b/tests/test_issue_50.cpp @@ -15,6 +15,8 @@ #include #include #include +#define BOOST_TEST_MODULE conn-quit +#include #include #include @@ -90,4 +92,17 @@ auto co_main(config cfg) -> net::awaitable conn->async_run(cfg, {}, net::consign(net::detached, conn)); } +BOOST_AUTO_TEST_CASE(issue_50) +{ + net::io_context ioc; + net::co_spawn(ioc, std::move(co_main({})), net::detached); + ioc.run(); +} + +#else // defined(BOOST_ASIO_HAS_CO_AWAIT) + +BOOST_AUTO_TEST_CASE(issue_50) +{ +} + #endif // defined(BOOST_ASIO_HAS_CO_AWAIT) diff --git a/tests/low_level.cpp b/tests/test_low_level.cpp similarity index 100% rename from tests/low_level.cpp rename to tests/test_low_level.cpp diff --git a/tests/cpp20_low_level_async.cpp b/tests/test_low_level_async.cpp similarity index 82% rename from tests/cpp20_low_level_async.cpp rename to tests/test_low_level_async.cpp index 23e8189e..01f419da 100644 --- a/tests/cpp20_low_level_async.cpp +++ b/tests/test_low_level_async.cpp @@ -9,10 +9,14 @@ #include #include #include +#include +#include #include #include #include #include +#define BOOST_TEST_MODULE conn-tls +#include #if defined(BOOST_ASIO_HAS_CO_AWAIT) namespace net = boost::asio; @@ -54,4 +58,17 @@ auto co_main(config cfg) -> net::awaitable std::cout << "Ping: " << resp.value() << std::endl; } +BOOST_AUTO_TEST_CASE(low_level_async) +{ + net::io_context ioc; + net::co_spawn(ioc, std::move(co_main({})), net::detached); + ioc.run(); +} + +#else // defined(BOOST_ASIO_HAS_CO_AWAIT) + +BOOST_AUTO_TEST_CASE(low_level_async) +{ +} + #endif // defined(BOOST_ASIO_HAS_CO_AWAIT) diff --git a/tests/cpp17_low_level_sync.cpp b/tests/test_low_level_sync.cpp similarity index 87% rename from tests/cpp17_low_level_sync.cpp rename to tests/test_low_level_sync.cpp index 78c753f8..1abfe652 100644 --- a/tests/cpp17_low_level_sync.cpp +++ b/tests/test_low_level_sync.cpp @@ -9,6 +9,8 @@ #include #include #include +#define BOOST_TEST_MODULE conn-quit +#include #include #include @@ -18,16 +20,11 @@ using boost::redis::adapter::adapt2; using boost::redis::request; using boost::redis::adapter::result; -auto main(int argc, char * argv[]) -> int +BOOST_AUTO_TEST_CASE(low_level_sync) { try { - std::string host = "127.0.0.1"; - std::string port = "6379"; - - if (argc == 3) { - host = argv[1]; - port = argv[2]; - } + std::string const host = "127.0.0.1"; + std::string const port = "6379"; net::io_context ioc; net::ip::tcp::resolver resv{ioc}; diff --git a/tests/request.cpp b/tests/test_request.cpp similarity index 100% rename from tests/request.cpp rename to tests/test_request.cpp diff --git a/tests/run.cpp b/tests/test_run.cpp similarity index 100% rename from tests/run.cpp rename to tests/test_run.cpp From 11eebcf771d82eb600a89a345f607ad800ff3977 Mon Sep 17 00:00:00 2001 From: Marcelo Zimbres Date: Tue, 16 May 2023 19:44:04 +0200 Subject: [PATCH 17/32] Fixes redis.hpp and slightly improves compilation times. --- CMakeLists.txt | 25 +- examples/boost_redis.cpp | 7 + examples/cpp20_containers.cpp | 11 +- examples/cpp20_json.cpp | 3 +- examples/cpp20_resolve_with_sentinel.cpp | 11 +- examples/cpp20_streams.cpp | 2 +- examples/main.cpp | 2 + include/boost/redis.hpp | 2 - include/boost/redis/connection.hpp | 279 ++++--------- .../redis/{detail => }/connection_base.hpp | 390 +++++++++++++----- include/boost/redis/detail/health_checker.hpp | 1 + include/boost/redis/detail/reconnection.hpp | 129 ------ include/boost/redis/detail/runner.hpp | 9 +- tests/test_conn_echo_stress.cpp | 4 +- tests/test_conn_exec_cancel.cpp | 5 +- tests/test_conn_exec_cancel2.cpp | 5 +- tests/test_conn_reconnect.cpp | 8 +- tests/test_run.cpp | 4 +- 18 files changed, 423 insertions(+), 474 deletions(-) rename include/boost/redis/{detail => }/connection_base.hpp (73%) delete mode 100644 include/boost/redis/detail/reconnection.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index fbdea25c..19e92366 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,7 @@ cmake_minimum_required(VERSION 3.14) +set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CMAKE_COMMAND} -E time") + project( boost_redis VERSION 1.4.1 @@ -65,9 +67,9 @@ foreach(lib IN LISTS libs_cpp20 libs_cpp17 libs_common) add_library(${lib} STATIC) endforeach() -target_sources(boost_redis_src PUBLIC examples/boost_redis.cpp) -target_sources(tests_common PUBLIC tests/common.cpp) -target_sources(examples_main PUBLIC examples/main.cpp) +target_sources(boost_redis_src PRIVATE examples/boost_redis.cpp) +target_sources(tests_common PRIVATE tests/common.cpp) +target_sources(examples_main PRIVATE examples/main.cpp) # Executables #======================================================================= @@ -134,16 +136,17 @@ foreach(exe IN LISTS tests_cpp17 tests_cpp20) target_link_libraries(${exe} PRIVATE tests_common) endforeach() -foreach(exe IN LISTS tests_cpp17 tests_cpp20 examples_cpp17 examples_cpp20 libs_cpp17 libs_cpp20 benchmakrs) - target_link_libraries(${exe} PRIVATE OpenSSL::Crypto OpenSSL::SSL boost_redis_src) -endforeach() - -if (MSVC) - foreach(exe IN LISTS tests_cpp17 tests_cpp20 examples_cpp17 examples_cpp20 libs_cpp17 libs_cpp20 benchmakrs libs_common) +foreach(exe IN LISTS tests_cpp17 tests_cpp20 examples_cpp17 examples_cpp20 libs_cpp17 libs_cpp20 libs_common benchmakrs) + target_link_libraries(${exe} PRIVATE OpenSSL::Crypto OpenSSL::SSL) + if (MSVC) target_compile_options(${exe} PRIVATE /bigobj) target_compile_definitions(${exe} PRIVATE _WIN32_WINNT=0x0601) - endforeach() -endif() + endif() +endforeach() + +foreach(exe IN LISTS tests_cpp17 tests_cpp20 examples_cpp17 examples_cpp20 libs_cpp17 libs_cpp20 benchmakrs) + target_link_libraries(${exe} PRIVATE boost_redis_src) +endforeach() foreach(exe IN LISTS tests_cpp20 examples_cpp20 libs_cpp20 benchmarks) target_compile_features(${exe} PUBLIC cxx_std_20) diff --git a/examples/boost_redis.cpp b/examples/boost_redis.cpp index dddc80f2..3870f200 100644 --- a/examples/boost_redis.cpp +++ b/examples/boost_redis.cpp @@ -4,4 +4,11 @@ * accompanying file LICENSE.txt) */ +#include +#include +#include +#include +#include +#include + #include diff --git a/examples/cpp20_containers.cpp b/examples/cpp20_containers.cpp index cbea9330..ec2ee53b 100644 --- a/examples/cpp20_containers.cpp +++ b/examples/cpp20_containers.cpp @@ -15,12 +15,11 @@ #if defined(BOOST_ASIO_HAS_CO_AWAIT) namespace net = boost::asio; -namespace redis = boost::redis; -using redis::request; -using redis::response; -using redis::ignore_t; -using redis::ignore; -using redis::config; +using boost::redis::request; +using boost::redis::response; +using boost::redis::ignore_t; +using boost::redis::ignore; +using boost::redis::config; using connection = net::deferred_t::as_default_on_t; void print(std::map const& cont) diff --git a/examples/cpp20_json.cpp b/examples/cpp20_json.cpp index 6117dedd..68c0879f 100644 --- a/examples/cpp20_json.cpp +++ b/examples/cpp20_json.cpp @@ -12,6 +12,7 @@ #include #include #include + #if defined(BOOST_ASIO_HAS_CO_AWAIT) #define BOOST_JSON_NO_LIB @@ -21,12 +22,12 @@ namespace net = boost::asio; using namespace boost::describe; -using connection = net::deferred_t::as_default_on_t; using boost::redis::request; using boost::redis::response; using boost::redis::operation; using boost::redis::ignore_t; using boost::redis::config; +using connection = net::deferred_t::as_default_on_t; // Struct that will be stored in Redis using json serialization. struct user { diff --git a/examples/cpp20_resolve_with_sentinel.cpp b/examples/cpp20_resolve_with_sentinel.cpp index 70aeaed0..35bcf686 100644 --- a/examples/cpp20_resolve_with_sentinel.cpp +++ b/examples/cpp20_resolve_with_sentinel.cpp @@ -13,13 +13,12 @@ #if defined(BOOST_ASIO_HAS_CO_AWAIT) namespace net = boost::asio; -namespace redis = boost::redis; using endpoints = net::ip::tcp::resolver::results_type; -using redis::request; -using redis::response; -using redis::ignore_t; -using redis::config; -using redis::address; +using boost::redis::request; +using boost::redis::response; +using boost::redis::ignore_t; +using boost::redis::config; +using boost::redis::address; using connection = boost::asio::use_awaitable_t<>::as_default_on_t; auto redir(boost::system::error_code& ec) diff --git a/examples/cpp20_streams.cpp b/examples/cpp20_streams.cpp index 5dcae830..4eca3050 100644 --- a/examples/cpp20_streams.cpp +++ b/examples/cpp20_streams.cpp @@ -88,7 +88,7 @@ auto co_main(config cfg) -> net::awaitable net::co_spawn(ex, stream_reader(conn), net::detached); // Disable health checks. - cfg.health_check_interval = std::chrono::seconds{0}; + cfg.health_check_interval = std::chrono::seconds::zero(); conn->async_run(cfg, {}, net::consign(net::detached, conn)); signal_set sig_set(ex, SIGINT, SIGTERM); diff --git a/examples/main.cpp b/examples/main.cpp index e8501954..78e0a56a 100644 --- a/examples/main.cpp +++ b/examples/main.cpp @@ -4,6 +4,7 @@ * accompanying file LICENSE.txt) */ +#include #include #include #include @@ -12,6 +13,7 @@ namespace net = boost::asio; using boost::redis::config; +using boost::redis::logger; #if defined(BOOST_ASIO_HAS_CO_AWAIT) diff --git a/include/boost/redis.hpp b/include/boost/redis.hpp index 15515c9f..7d3272b8 100644 --- a/include/boost/redis.hpp +++ b/include/boost/redis.hpp @@ -12,8 +12,6 @@ #include #include #include -#include -#include #include #include diff --git a/include/boost/redis/connection.hpp b/include/boost/redis/connection.hpp index 7360e848..4ea560de 100644 --- a/include/boost/redis/connection.hpp +++ b/include/boost/redis/connection.hpp @@ -7,24 +7,58 @@ #ifndef BOOST_REDIS_CONNECTION_HPP #define BOOST_REDIS_CONNECTION_HPP -#include -#include -#include -#include +#include #include #include -#include -#include #include -#include +#include +#include +#include #include #include +#include namespace boost::redis { +namespace detail +{ +template +struct reconnection_op { + Connection* conn_ = nullptr; + Logger logger_; + asio::coroutine coro_{}; + + template + void operator()(Self& self, system::error_code ec = {}) + { + BOOST_ASIO_CORO_REENTER (coro_) for (;;) + { + BOOST_ASIO_CORO_YIELD + conn_->async_run_one(conn_->cfg_, logger_, std::move(self)); + conn_->cancel(operation::receive); + logger_.on_connection_lost(ec); + if (!conn_->will_reconnect() || is_cancelled(self)) { + conn_->cancel(operation::reconnection); + self.complete(!!ec ? ec : asio::error::operation_aborted); + return; + } + + conn_->timer_.expires_after(conn_->cfg_.reconnect_wait_interval); + BOOST_ASIO_CORO_YIELD + conn_->timer_.async_wait(std::move(self)); + BOOST_REDIS_CHECK_OP0(;) + if (!conn_->will_reconnect()) { + self.complete(asio::error::operation_aborted); + return; + } + conn_->reset_stream(); + } + } +}; +} // detail -/** \brief A SSL connection to the Redis server. - * \ingroup high-level-api +/** @brief A SSL connection to the Redis server. + * @ingroup high-level-api * * This class keeps a healthy connection to the Redis instance where * commands can be sent at any time. For more details, please see the @@ -34,10 +68,10 @@ namespace boost::redis { * */ template -class basic_connection : private detail::connection_base> { +class basic_connection : public connection_base { public: - /// Type of the next layer - using next_layer_type = asio::ssl::stream>; + using base_type = connection_base; + using this_type = basic_connection; /// Executor type. using executor_type = Executor; @@ -50,16 +84,11 @@ class basic_connection : private detail::connection_base; }; - using base_type = redis::detail::connection_base>; - /// Contructs from an executor. explicit basic_connection(executor_type ex, asio::ssl::context::method method = asio::ssl::context::tls_client) - : base_type{ex} - , ctx_{method} - , reconn_{ex} - , runner_{ex, {}} - , stream_{std::make_unique(ex, ctx_)} + : base_type{ex, method} + , timer_{ex} { } /// Contructs from a context. @@ -68,52 +97,13 @@ class basic_connection : private detail::connection_baseget_executor();} - - /// Returns the ssl context. - auto const& get_ssl_context() const noexcept - { return ctx_;} - - /// Returns the ssl context. - auto& get_ssl_context() noexcept - { return ctx_;} - - /// Reset the underlying stream. - void reset_stream() - { - stream_ = std::make_unique(stream_->get_executor(), ctx_); - } - - /// Returns a reference to the next layer. - auto& next_layer() noexcept { return *stream_; } - - /// Returns a const reference to the next layer. - auto const& next_layer() const noexcept { return *stream_; } - - /** @brief Starts underlying connection operations. - * - * In more detail, this function will + /** @brief High-level connection to Redis. * - * 1. Resolve the address passed on `boost::redis::config::addr`. - * 2. Connect to one of the results obtained in the resolve operation. - * 3. Send a [HELLO](https://redis.io/commands/hello/) command where each of its parameters are read from `cfg`. - * 4. Start a health-check operation where ping commands are sent - * at intervals specified in - * `boost::redis::config::health_check_interval`. The message passed to - * `PING` will be `boost::redis::config::health_check_id`. Passing a - * timeout with value zero will disable health-checks. If the Redis - * server does not respond to a health-check within two times the value - * specified here, it will be considered unresponsive and the connection - * will be closed and a new connection will be stablished. - * 5. Starts read and write operations with the Redis - * server. More specifically it will trigger the write of all - * requests i.e. calls to `async_exec` that happened prior to this - * call. - * - * When a connection is lost for any reason, a new one is stablished automatically. To disable - * reconnection call `boost::redis::connection::cancel(operation::reconnection)`. + * This connection class adds reconnection functionality to + * `boost::redis::connection_base::async_run_one`. When a + * connection is lost for any reason, a new one is stablished + * automatically. To disable reconnection call + * `boost::redis::connection::cancel(operation::reconnection)`. * * @param cfg Configuration paramters. * @param l Logger object. The interface expected is specified in the class `boost::redis::logger`. @@ -127,9 +117,11 @@ class basic_connection : private detail::connection_base> - auto async_exec( - request const& req, - Response& resp = ignore, - CompletionToken token = CompletionToken{}) - { - return base_type::async_exec(req, resp, std::move(token)); - } - - /** @brief Receives server side pushes asynchronously. - * - * When pushes arrive and there is no `async_receive` operation in - * progress, pushed data, requests, and responses will be paused - * until `async_receive` is called again. Apps will usually want to - * call `async_receive` in a loop. - * - * To cancel an ongoing receive operation apps should call - * `connection::cancel(operation::receive)`. - * - * @param response The response object. - * @param token The Asio completion token. - * - * For an example see cpp20_subscriber.cpp. The completion token must - * have the following signature - * - * @code - * void f(system::error_code, std::size_t); - * @endcode - * - * Where the second parameter is the size of the push in - * bytes. - */ - template < - class Response = ignore_t, - class CompletionToken = asio::default_completion_token_t> - auto async_receive( - Response& response = ignore, - CompletionToken token = CompletionToken{}) - { - return base_type::async_receive(response, std::move(token)); + cfg_ = cfg; + l.set_prefix(cfg_.log_prefix); + return asio::async_compose + < CompletionToken + , void(system::error_code) + >(detail::reconnection_op{this, l}, token, timer_); } /** @brief Cancel operations. @@ -227,72 +152,36 @@ class basic_connection : private detail::connection_base std::size_t + void cancel(operation op = operation::all) override { - reconn_.cancel(op); - runner_.cancel(op); - return base_type::cancel(op); + switch (op) { + case operation::reconnection: + case operation::all: + cfg_.reconnect_wait_interval = std::chrono::seconds::zero(); + timer_.cancel(); + break; + default: /* ignore */; + } + + base_type::cancel(op); } - /// Sets the maximum size of the read buffer. - void set_max_buffer_read_size(std::size_t max_read_size) noexcept - { base_type::set_max_buffer_read_size(max_read_size); } - - /** @brief Reserve memory on the read and write internal buffers. - * - * This function will call `std::string::reserve` on the - * underlying buffers. - * - * @param read The new capacity of the read buffer. - * @param write The new capacity of the write buffer. - */ - void reserve(std::size_t read, std::size_t write) - { base_type::reserve(read, write); } - /// Returns true if the connection was canceled. bool will_reconnect() const noexcept - { return reconn_.will_reconnect();} + { return cfg_.reconnect_wait_interval != std::chrono::seconds::zero();} private: - using runner_type = detail::runner; - using reconnection_type = detail::basic_reconnection; - using this_type = basic_connection; - - template friend class detail::connection_base; - template friend class detail::read_next_op; - template friend struct detail::exec_op; - template friend struct detail::receive_op; - template friend struct detail::reader_op; - template friend struct detail::writer_op; - template friend struct detail::run_op; - template friend struct detail::wait_receive_op; - template friend struct detail::run_all_op; - template friend struct detail::reconnection_op; - - template - auto async_run_one(Logger l, CompletionToken token) - { return runner_.async_run(*this, l, std::move(token)); } - - template - auto async_run_impl(Logger l, CompletionToken token) - { return base_type::async_run_impl(l, std::move(token)); } - - void close() - { - if (stream_->next_layer().is_open()) - stream_->next_layer().close(); - } + config cfg_; - auto is_open() const noexcept { return stream_->next_layer().is_open(); } - auto& lowest_layer() noexcept { return stream_->lowest_layer(); } + using timer_type = + asio::basic_waitable_timer< + std::chrono::steady_clock, + asio::wait_traits, + Executor>; - auto use_ssl() const noexcept { return use_ssl_;} + template friend struct detail::reconnection_op; - bool use_ssl_ = false; - asio::ssl::context ctx_; - reconnection_type reconn_; - runner_type runner_; - std::unique_ptr stream_; + timer_type timer_; }; /** \brief A connection that uses the asio::any_io_executor. diff --git a/include/boost/redis/detail/connection_base.hpp b/include/boost/redis/connection_base.hpp similarity index 73% rename from include/boost/redis/detail/connection_base.hpp rename to include/boost/redis/connection_base.hpp index de22bd5d..a67f8507 100644 --- a/include/boost/redis/detail/connection_base.hpp +++ b/include/boost/redis/connection_base.hpp @@ -14,8 +14,11 @@ #include #include #include +#include +#include #include +#include #include #include #include @@ -24,6 +27,7 @@ #include #include #include +#include #include #include @@ -34,7 +38,8 @@ #include #include -namespace boost::redis::detail { +namespace boost::redis { +namespace detail { template struct wait_receive_op { @@ -109,7 +114,7 @@ class read_next_op { // some data in the read bufer. if (conn_->read_buffer_.empty()) { - if (conn_->derived().use_ssl()) + if (conn_->use_ssl()) BOOST_ASIO_CORO_YIELD asio::async_read_until(conn_->next_layer(), conn_->make_dynamic_buffer(), "\r\n", std::move(self)); else BOOST_ASIO_CORO_YIELD asio::async_read_until(conn_->next_layer().next_layer(), conn_->make_dynamic_buffer(), "\r\n", std::move(self)); @@ -131,7 +136,7 @@ class read_next_op { } //----------------------------------- - if (conn_->derived().use_ssl()) + if (conn_->use_ssl()) BOOST_ASIO_CORO_YIELD redis::detail::async_read(conn_->next_layer(), conn_->make_dynamic_buffer(), make_adapter(), std::move(self)); else BOOST_ASIO_CORO_YIELD redis::detail::async_read(conn_->next_layer().next_layer(), conn_->make_dynamic_buffer(), make_adapter(), std::move(self)); @@ -170,7 +175,7 @@ struct receive_op { conn->channel_.async_receive(std::move(self)); BOOST_REDIS_CHECK_OP1(;); - if (conn->derived().use_ssl()) + if (conn->use_ssl()) BOOST_ASIO_CORO_YIELD redis::detail::async_read(conn->next_layer(), conn->make_dynamic_buffer(), adapter, std::move(self)); else BOOST_ASIO_CORO_YIELD redis::detail::async_read(conn->next_layer().next_layer(), conn->make_dynamic_buffer(), adapter, std::move(self)); @@ -349,7 +354,7 @@ struct writer_op { BOOST_ASIO_CORO_REENTER (coro) for (;;) { while (conn_->coalesce_requests()) { - if (conn_->derived().use_ssl()) + if (conn_->use_ssl()) BOOST_ASIO_CORO_YIELD asio::async_write(conn_->next_layer(), asio::buffer(conn_->write_buffer_), std::move(self)); else BOOST_ASIO_CORO_YIELD asio::async_write(conn_->next_layer().next_layer(), asio::buffer(conn_->write_buffer_), std::move(self)); @@ -404,7 +409,7 @@ struct reader_op { BOOST_ASIO_CORO_REENTER (coro) for (;;) { - if (conn->derived().use_ssl()) + if (conn->use_ssl()) BOOST_ASIO_CORO_YIELD asio::async_read_until(conn->next_layer(), conn->make_dynamic_buffer(), "\r\n", std::move(self)); else BOOST_ASIO_CORO_YIELD asio::async_read_until(conn->next_layer().next_layer(), conn->make_dynamic_buffer(), "\r\n", std::move(self)); @@ -460,89 +465,236 @@ struct reader_op { } } }; +} // detail -/** Base class for high level Redis asynchronous connections. - * - * This class is not meant to be instantiated directly but as base - * class in the CRTP. +/** @brief Base class for high level Redis asynchronous connections. + * @ingroup high-level-api * * @tparam Executor The executor type. - * @tparam Derived The derived class type. * */ -template +template class connection_base { public: + /// Executor type using executor_type = Executor; - using this_type = connection_base; - connection_base(executor_type ex) - : writer_timer_{ex} + /// Type of the next layer + using next_layer_type = asio::ssl::stream>; + + using this_type = connection_base; + + /// Constructs from an executor. + connection_base(executor_type ex, asio::ssl::context::method method = asio::ssl::context::tls_client) + : ctx_{method} + , stream_{std::make_unique(ex, ctx_)} + , writer_timer_{ex} , read_timer_{ex} , channel_{ex} + , runner_{ex, {}} { writer_timer_.expires_at(std::chrono::steady_clock::time_point::max()); read_timer_.expires_at(std::chrono::steady_clock::time_point::max()); } - auto get_executor() {return writer_timer_.get_executor();} + /// Contructs from an execution context. + explicit + connection_base(asio::io_context& ioc, asio::ssl::context::method method = asio::ssl::context::tls_client) + : connection_base(ioc.get_executor(), method) + { } + + /// Returns the ssl context. + auto const& get_ssl_context() const noexcept + { return ctx_;} - auto cancel_impl(operation op) -> std::size_t + /// Returns the ssl context. + auto& get_ssl_context() noexcept + { return ctx_;} + + /// Resets the underlying stream. + void reset_stream() { - switch (op) { - case operation::exec: - { - return cancel_unwritten_requests(); - } - case operation::run: - { - derived().close(); - read_timer_.cancel(); - writer_timer_.cancel(); - return cancel_on_conn_lost(); - } - case operation::receive: - { - channel_.cancel(); - return 1U; - } - default: /* ignore */; return 0; - } + stream_ = std::make_unique(writer_timer_.get_executor(), ctx_); } - auto cancel(operation op) -> std::size_t + /// Returns a reference to the next layer. + auto& next_layer() noexcept { return *stream_; } + + /// Returns a const reference to the next layer. + auto const& next_layer() const noexcept { return *stream_; } + + /// Returns the associated executor. + auto get_executor() {return writer_timer_.get_executor();} + + /// Cancels specific operations. + virtual void cancel(operation op) { + runner_.cancel(op); if (op == operation::all) { - std::size_t ret = 0; - ret += cancel_impl(operation::run); - ret += cancel_impl(operation::receive); - ret += cancel_impl(operation::exec); - return ret; + cancel_impl(operation::run); + cancel_impl(operation::receive); + cancel_impl(operation::exec); + return; } - return cancel_impl(op); + cancel_impl(op); } - auto cancel_unwritten_requests() -> std::size_t + /** @brief Executes commands on the Redis server asynchronously. + * + * This function sends a request to the Redis server and waits for + * the responses to each individual command in the request. If the + * request contains only commands that don't expect a response, + * the completion occurs after it has been written to the + * underlying stream. Multiple concurrent calls to this function + * will be automatically queued by the implementation. + * + * @param req Request. + * @param resp Response. + * @param token Completion token. + * + * For an example see cpp20_echo_server.cpp. The completion token must + * have the following signature + * + * @code + * void f(system::error_code, std::size_t); + * @endcode + * + * Where the second parameter is the size of the response received + * in bytes. + */ + template < + class Response = ignore_t, + class CompletionToken = asio::default_completion_token_t + > + auto + async_exec( + request const& req, + Response& resp = ignore, + CompletionToken token = CompletionToken{}) { - auto f = [](auto const& ptr) - { - BOOST_ASSERT(ptr != nullptr); - return ptr->is_written(); - }; + using namespace boost::redis::adapter; + auto f = boost_redis_adapt(resp); + BOOST_ASSERT_MSG(req.size() <= f.get_supported_response_size(), "Request and response have incompatible sizes."); - auto point = std::stable_partition(std::begin(reqs_), std::end(reqs_), f); + return asio::async_compose + < CompletionToken + , void(system::error_code, std::size_t) + >(redis::detail::exec_op{this, &req, f}, token, writer_timer_); + } - auto const ret = std::distance(point, std::end(reqs_)); + /** @brief Receives server side pushes asynchronously. + * + * When pushes arrive and there is no `async_receive` operation in + * progress, pushed data, requests, and responses will be paused + * until `async_receive` is called again. Apps will usually want + * to call `async_receive` in a loop. + * + * To cancel an ongoing receive operation apps should call + * `connection::cancel(operation::receive)`. + * + * @param response Response object. + * @param token Completion token. + * + * For an example see cpp20_subscriber.cpp. The completion token must + * have the following signature + * + * @code + * void f(system::error_code, std::size_t); + * @endcode + * + * Where the second parameter is the size of the push received in + * bytes. + */ + template < + class Response = ignore_t, + class CompletionToken = asio::default_completion_token_t + > + auto + async_receive( + Response& response, + CompletionToken token = CompletionToken{}) + { + using namespace boost::redis::adapter; + auto g = boost_redis_adapt(response); + auto f = adapter::detail::make_adapter_wrapper(g); - std::for_each(point, std::end(reqs_), [](auto const& ptr) { - ptr->stop(); - }); + return asio::async_compose + < CompletionToken + , void(system::error_code, std::size_t) + >(redis::detail::receive_op{this, f}, token, channel_); + } - reqs_.erase(point, std::end(reqs_)); - return ret; + /** @brief Starts underlying connection operations. + * + * Provides a high-level connection to the Redis server. It will + * perform the following steps + * + * 1. Resolve the address passed on `boost::redis::config::addr`. + * 2. Connect to one of the results obtained in the resolve operation. + * 3. Send a [HELLO](https://redis.io/commands/hello/) command where each of its parameters are read from `cfg`. + * 4. Start a health-check operation where ping commands are sent + * at intervals specified in + * `boost::redis::config::health_check_interval`. The message passed to + * `PING` will be `boost::redis::config::health_check_id`. Passing a + * timeout with value zero will disable health-checks. If the Redis + * server does not respond to a health-check within two times the value + * specified here, it will be considered unresponsive and the connection + * will be closed and a new connection will be stablished. + * 5. Starts read and write operations with the Redis + * server. More specifically it will trigger the write of all + * requests i.e. calls to `async_exec` that happened prior to this + * call. + * + * @param cfg Configuration paramters. + * @param l Logger object. The interface expected is specified in the class `boost::redis::logger`. + * @param token Completion token. + * + * The completion token must have the following signature + * + * @code + * void f(system::error_code); + * @endcode + * + * For example on how to call this function refer to + * cpp20_intro.cpp or any other example. + */ + template + auto async_run_one(config const& cfg, Logger l, CompletionToken token) + { + runner_.set_config(cfg); + l.set_prefix(runner_.get_config().log_prefix); + return runner_.async_run(*this, l, std::move(token)); } + /// Sets the maximum size of the read buffer. + void set_max_buffer_read_size(std::size_t max_read_size) noexcept + {max_read_size_ = max_read_size;} + + /** @brief Reserve memory on the read and write internal buffers. + * + * This function will call `std::string::reserve` on the + * underlying buffers. + * + * @param read The new capacity of the read buffer. + * @param write The new capacity of the write buffer. + */ + void reserve(std::size_t read, std::size_t write) + { + read_buffer_.reserve(read); + write_buffer_.reserve(write); + } + +private: + using clock_type = std::chrono::steady_clock; + using clock_traits_type = asio::wait_traits; + using timer_type = asio::basic_waitable_timer; + using channel_type = asio::experimental::channel; + using runner_type = redis::detail::runner; + + auto use_ssl() const noexcept + { return runner_.get_config().use_ssl;} + auto cancel_on_conn_lost() -> std::size_t { // Must return false if the request should be removed. @@ -573,59 +725,48 @@ class connection_base { return ret; } - template - auto async_exec(request const& req, Response& resp, CompletionToken token) + auto cancel_unwritten_requests() -> std::size_t { - using namespace boost::redis::adapter; - auto f = boost_redis_adapt(resp); - BOOST_ASSERT_MSG(req.size() <= f.get_supported_response_size(), "Request and response have incompatible sizes."); + auto f = [](auto const& ptr) + { + BOOST_ASSERT(ptr != nullptr); + return ptr->is_written(); + }; - return asio::async_compose - < CompletionToken - , void(system::error_code, std::size_t) - >(exec_op{&derived(), &req, f}, token, writer_timer_); - } + auto point = std::stable_partition(std::begin(reqs_), std::end(reqs_), f); - template - auto async_receive(Response& response, CompletionToken token) - { - using namespace boost::redis::adapter; - auto g = boost_redis_adapt(response); - auto f = adapter::detail::make_adapter_wrapper(g); + auto const ret = std::distance(point, std::end(reqs_)); - return asio::async_compose - < CompletionToken - , void(system::error_code, std::size_t) - >(receive_op{&derived(), f}, token, channel_); - } + std::for_each(point, std::end(reqs_), [](auto const& ptr) { + ptr->stop(); + }); - template - auto async_run_impl(Logger l, CompletionToken token) - { - return asio::async_compose - < CompletionToken - , void(system::error_code) - >(run_op{&derived(), l}, token, writer_timer_); + reqs_.erase(point, std::end(reqs_)); + return ret; } - void set_max_buffer_read_size(std::size_t max_read_size) noexcept - {max_read_size_ = max_read_size;} - - // Reserves memory in the read and write buffer. - void reserve(std::size_t read, std::size_t write) + void cancel_impl(operation op) { - read_buffer_.reserve(read); - write_buffer_.reserve(write); + switch (op) { + case operation::exec: + { + cancel_unwritten_requests(); + } break; + case operation::run: + { + close(); + read_timer_.cancel(); + writer_timer_.cancel(); + cancel_on_conn_lost(); + } break; + case operation::receive: + { + channel_.cancel(); + } break; + default: /* ignore */; + } } -private: - using clock_type = std::chrono::steady_clock; - using clock_traits_type = asio::wait_traits; - using timer_type = asio::basic_waitable_timer; - using channel_type = asio::experimental::channel; - - auto derived() -> Derived& { return static_cast(*this); } - void on_write() { // We have to clear the payload right after writing it to use it @@ -729,13 +870,14 @@ class connection_base { using reqs_type = std::deque>; - template friend struct reader_op; - template friend struct writer_op; - template friend struct run_op; - template friend struct exec_op; - template friend class read_next_op; - template friend struct receive_op; - template friend struct wait_receive_op; + template friend struct redis::detail::reader_op; + template friend struct redis::detail::writer_op; + template friend struct redis::detail::run_op; + template friend struct redis::detail::exec_op; + template friend class redis::detail::read_next_op; + template friend struct redis::detail::receive_op; + template friend struct redis::detail::wait_receive_op; + template friend struct redis::detail::run_all_op; template auto async_wait_receive(CompletionToken token) @@ -743,7 +885,7 @@ class connection_base { return asio::async_compose < CompletionToken , void(system::error_code) - >(wait_receive_op{&derived()}, token, channel_); + >(redis::detail::wait_receive_op{this}, token, channel_); } void cancel_push_requests() @@ -776,7 +918,7 @@ class connection_base { std::rotate(std::rbegin(reqs_), std::rbegin(reqs_) + 1, rend); } - if (derived().is_open() && !is_writing()) + if (is_open() && !is_writing()) writer_timer_.cancel(); } @@ -789,7 +931,7 @@ class connection_base { return asio::async_compose < CompletionToken , void(system::error_code) - >(reader_op{&derived()}, token, writer_timer_); + >(redis::detail::reader_op{this}, token, writer_timer_); } template @@ -798,7 +940,7 @@ class connection_base { return asio::async_compose < CompletionToken , void(system::error_code) - >(writer_op{&derived(), l}, token, writer_timer_); + >(redis::detail::writer_op{this, l}, token, writer_timer_); } template @@ -807,7 +949,18 @@ class connection_base { return asio::async_compose < CompletionToken , void(system::error_code, std::size_t) - >(read_next_op{derived(), adapter, reqs_.front()}, token, writer_timer_); + >(redis::detail::read_next_op{*this, adapter, reqs_.front()}, token, writer_timer_); + } + + template + auto async_run_lean(config const& cfg, Logger l, CompletionToken token) + { + runner_.set_config(cfg); + l.set_prefix(runner_.get_config().log_prefix); + return asio::async_compose + < CompletionToken + , void(system::error_code) + >(redis::detail::run_op{this, l}, token, writer_timer_); } [[nodiscard]] bool coalesce_requests() @@ -832,12 +985,25 @@ class connection_base { return !std::empty(reqs_) && reqs_.front()->is_written(); } + void close() + { + if (stream_->next_layer().is_open()) + stream_->next_layer().close(); + } + + auto is_open() const noexcept { return stream_->next_layer().is_open(); } + auto& lowest_layer() noexcept { return stream_->lowest_layer(); } + + asio::ssl::context ctx_; + std::unique_ptr stream_; + // Notice we use a timer to simulate a condition-variable. It is // also more suitable than a channel and the notify operation does // not suspend. timer_type writer_timer_; timer_type read_timer_; channel_type channel_; + runner_type runner_; std::string read_buffer_; std::string write_buffer_; @@ -845,6 +1011,6 @@ class connection_base { std::size_t max_read_size_ = (std::numeric_limits::max)(); }; -} // boost::redis::detail +} // boost::redis #endif // BOOST_REDIS_CONNECTION_BASE_HPP diff --git a/include/boost/redis/detail/health_checker.hpp b/include/boost/redis/detail/health_checker.hpp index d6c2143c..2e99dfa3 100644 --- a/include/boost/redis/detail/health_checker.hpp +++ b/include/boost/redis/detail/health_checker.hpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include diff --git a/include/boost/redis/detail/reconnection.hpp b/include/boost/redis/detail/reconnection.hpp deleted file mode 100644 index 88ee1817..00000000 --- a/include/boost/redis/detail/reconnection.hpp +++ /dev/null @@ -1,129 +0,0 @@ -/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com) - * - * Distributed under the Boost Software License, Version 1.0. (See - * accompanying file LICENSE.txt) - */ - -#ifndef BOOST_REDIS_RECONNECTION_HPP -#define BOOST_REDIS_RECONNECTION_HPP - -#include -#include -#include -#include -#include - -namespace boost::redis::detail -{ - -template -struct reconnection_op { - Reconnector* reconn_ = nullptr; - Connection* conn_ = nullptr; - Logger logger_; - asio::coroutine coro_{}; - - template - void operator()(Self& self, system::error_code ec = {}) - { - BOOST_ASIO_CORO_REENTER (coro_) for (;;) - { - BOOST_ASIO_CORO_YIELD - conn_->async_run_one(logger_, std::move(self)); - conn_->cancel(operation::receive); - logger_.on_connection_lost(ec); - if (!reconn_->will_reconnect() || is_cancelled(self)) { - reconn_->cancel(operation::reconnection); - self.complete(!!ec ? ec : asio::error::operation_aborted); - return; - } - - reconn_->timer_.expires_after(reconn_->wait_interval_); - BOOST_ASIO_CORO_YIELD - reconn_->timer_.async_wait(std::move(self)); - BOOST_REDIS_CHECK_OP0(;) - if (!reconn_->will_reconnect()) { - self.complete(asio::error::operation_aborted); - return; - } - conn_->reset_stream(); - } - } -}; - -// NOTE: wait_interval could be an async_run parameter. - -template -class basic_reconnection { -public: - using executor_type = Executor; - - basic_reconnection(Executor ex) - : timer_{ex} - {} - - basic_reconnection(asio::io_context& ioc, std::chrono::steady_clock::duration wait_interval) - : basic_reconnection{ioc.get_executor(), wait_interval} - {} - - template - struct rebind_executor - { - using other = basic_reconnection; - }; - - template < - class Connection, - class Logger = logger, - class CompletionToken = asio::default_completion_token_t - > - auto - async_run( - Connection& conn, - Logger l = Logger{}, - CompletionToken token = CompletionToken{}) - { - return asio::async_compose - < CompletionToken - , void(system::error_code) - >(detail::reconnection_op{this, &conn, l}, token, conn); - } - - void set_config(std::chrono::steady_clock::duration wait_interval) - { - wait_interval_ = wait_interval; - } - - std::size_t cancel(operation op) - { - switch (op) { - case operation::reconnection: - case operation::all: - wait_interval_ = std::chrono::seconds::zero(); - timer_.cancel(); - break; - default: /* ignore */; - } - - return 0U; - } - - bool will_reconnect() const noexcept - { return wait_interval_ != std::chrono::seconds::zero();} - -private: - using timer_type = - asio::basic_waitable_timer< - std::chrono::steady_clock, - asio::wait_traits, - Executor>; - - template friend struct detail::reconnection_op; - - timer_type timer_; - std::chrono::steady_clock::duration wait_interval_ = std::chrono::seconds{1}; -}; - -} // boost::redis - -#endif // BOOST_REDIS_RECONNECTION_HPP diff --git a/include/boost/redis/detail/runner.hpp b/include/boost/redis/detail/runner.hpp index 1fa4c0c7..8142edd4 100644 --- a/include/boost/redis/detail/runner.hpp +++ b/include/boost/redis/detail/runner.hpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -132,7 +133,7 @@ struct run_all_op { BOOST_REDIS_CHECK_OP0(conn_->cancel(operation::run);) BOOST_ASIO_CORO_YIELD - runner_->ctor_.async_connect(conn_->lowest_layer(), runner_->resv_.results(), std::move(self)); + runner_->ctor_.async_connect(conn_->next_layer().next_layer(), runner_->resv_.results(), std::move(self)); logger_.on_connect(ec, runner_->ctor_.endpoint()); BOOST_REDIS_CHECK_OP0(conn_->cancel(operation::run);) @@ -144,14 +145,14 @@ struct run_all_op { } BOOST_ASIO_CORO_YIELD - conn_->async_run_impl(logger_, std::move(self)); + conn_->async_run_lean(runner_->cfg_, logger_, std::move(self)); BOOST_REDIS_CHECK_OP0(;) self.complete(ec); } } }; -template class Handshaker> +template class runner { public: runner(Executor ex, config cfg) @@ -194,7 +195,7 @@ class runner { private: using resolver_type = resolver; using connector_type = connector; - using handshaker_type = Handshaker; + using handshaker_type = detail::handshaker; using health_checker_type = health_checker; using timer_type = typename connector_type::timer_type; diff --git a/tests/test_conn_echo_stress.cpp b/tests/test_conn_echo_stress.cpp index bcb1f395..902e0cbd 100644 --- a/tests/test_conn_echo_stress.cpp +++ b/tests/test_conn_echo_stress.cpp @@ -80,7 +80,9 @@ auto async_echo_stress() -> net::awaitable BOOST_AUTO_TEST_CASE(echo_stress) { - start(async_echo_stress()); + net::io_context ioc; + net::co_spawn(ioc, async_echo_stress(), net::detached); + ioc.run(); } #else diff --git a/tests/test_conn_exec_cancel.cpp b/tests/test_conn_exec_cancel.cpp index 1d257387..ccf3f4b2 100644 --- a/tests/test_conn_exec_cancel.cpp +++ b/tests/test_conn_exec_cancel.cpp @@ -8,6 +8,7 @@ #include #define BOOST_TEST_MODULE conn-exec-cancel #include +#include #include "common.hpp" #include @@ -72,7 +73,9 @@ auto implicit_cancel_of_req_written() -> net::awaitable BOOST_AUTO_TEST_CASE(test_ignore_implicit_cancel_of_req_written) { - start(implicit_cancel_of_req_written()); + net::io_context ioc; + net::co_spawn(ioc, implicit_cancel_of_req_written(), net::detached); + ioc.run(); } BOOST_AUTO_TEST_CASE(test_cancel_of_req_written_on_run_canceled) diff --git a/tests/test_conn_exec_cancel2.cpp b/tests/test_conn_exec_cancel2.cpp index 37ee49fb..f7ed3395 100644 --- a/tests/test_conn_exec_cancel2.cpp +++ b/tests/test_conn_exec_cancel2.cpp @@ -8,6 +8,7 @@ #include #define BOOST_TEST_MODULE conn-exec-cancel #include +#include #include "common.hpp" #include @@ -83,7 +84,9 @@ auto async_ignore_explicit_cancel_of_req_written() -> net::awaitable BOOST_AUTO_TEST_CASE(test_ignore_explicit_cancel_of_req_written) { - start(async_ignore_explicit_cancel_of_req_written()); + net::io_context ioc; + net::co_spawn(ioc, async_ignore_explicit_cancel_of_req_written(), net::detached); + ioc.run(); } #else diff --git a/tests/test_conn_reconnect.cpp b/tests/test_conn_reconnect.cpp index e1d37fe0..761f041d 100644 --- a/tests/test_conn_reconnect.cpp +++ b/tests/test_conn_reconnect.cpp @@ -55,7 +55,9 @@ net::awaitable test_reconnect_impl() // Test whether the client works after a reconnect. BOOST_AUTO_TEST_CASE(test_reconnect) { - start(test_reconnect_impl()); + net::io_context ioc; + net::co_spawn(ioc, test_reconnect_impl(), net::detached); + ioc.run(); } auto async_test_reconnect_timeout() -> net::awaitable @@ -103,7 +105,9 @@ auto async_test_reconnect_timeout() -> net::awaitable BOOST_AUTO_TEST_CASE(test_reconnect_and_idle) { - start(async_test_reconnect_timeout()); + net::io_context ioc; + net::co_spawn(ioc, async_test_reconnect_timeout(), net::detached); + ioc.run(); } #else BOOST_AUTO_TEST_CASE(dummy) diff --git a/tests/test_run.cpp b/tests/test_run.cpp index b6837b17..4fbf6355 100644 --- a/tests/test_run.cpp +++ b/tests/test_run.cpp @@ -39,8 +39,8 @@ BOOST_AUTO_TEST_CASE(resolve_bad_host) cfg.health_check_interval = 10h; cfg.reconnect_wait_interval = 0s; - connection conn{ioc}; - conn.async_run(cfg, {}, [](auto ec){ + auto conn = std::make_shared(ioc); + conn->async_run(cfg, {}, [](auto ec){ BOOST_TEST(is_host_not_found(ec)); }); From f5f57e370bd2d944e1dff78f4af7dc0d62d5ae5e Mon Sep 17 00:00:00 2001 From: Marcelo Zimbres Date: Sat, 20 May 2023 21:14:50 +0200 Subject: [PATCH 18/32] Improvements in the redis-push stress test. --- tests/common.cpp | 5 +-- tests/common.hpp | 3 +- tests/test_conn_echo_stress.cpp | 54 ++++++++++++++++++++++++++------- 3 files changed, 48 insertions(+), 14 deletions(-) diff --git a/tests/common.cpp b/tests/common.cpp index 366cd1d8..f4972ba2 100644 --- a/tests/common.cpp +++ b/tests/common.cpp @@ -25,9 +25,10 @@ run( std::shared_ptr conn, boost::redis::config cfg, boost::system::error_code ec, - boost::redis::operation op) + boost::redis::operation op, + boost::redis::logger::level l) { - conn->async_run(cfg, {}, run_callback{conn, op, ec}); + conn->async_run(cfg, {l}, run_callback{conn, op, ec}); } #ifdef BOOST_ASIO_HAS_CO_AWAIT diff --git a/tests/common.hpp b/tests/common.hpp index 4b917b54..b5bc7dab 100644 --- a/tests/common.hpp +++ b/tests/common.hpp @@ -20,5 +20,6 @@ run( std::shared_ptr conn, boost::redis::config cfg = {}, boost::system::error_code ec = boost::asio::error::operation_aborted, - boost::redis::operation op = boost::redis::operation::receive); + boost::redis::operation op = boost::redis::operation::receive, + boost::redis::logger::level l = boost::redis::logger::level::info); diff --git a/tests/test_conn_echo_stress.cpp b/tests/test_conn_echo_stress.cpp index 902e0cbd..a967a32f 100644 --- a/tests/test_conn_echo_stress.cpp +++ b/tests/test_conn_echo_stress.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #define BOOST_TEST_MODULE echo-stress #include @@ -38,25 +39,33 @@ auto push_consumer(std::shared_ptr conn, int expected) -> net::await conn->cancel(); } -auto echo_session(std::shared_ptr conn, std::string id, int n) -> net::awaitable +auto +echo_session( + std::shared_ptr conn, + std::shared_ptr pubs, + std::string id, + int n) -> net::awaitable { auto ex = co_await net::this_coro::executor; request req; - response resp; + response resp; for (auto i = 0; i < n; ++i) { auto const msg = id + "/" + std::to_string(i); //std::cout << msg << std::endl; - req.push("HELLO", 3); + req.push("HELLO", 3); // Just to mess around. req.push("PING", msg); - req.push("SUBSCRIBE", "channel"); + req.push("PING", "lsls"); // TODO: Change to HELLO after fixing issue 105. boost::system::error_code ec; co_await conn->async_exec(req, resp, redir(ec)); - BOOST_CHECK_EQUAL(ec, boost::system::error_code{}); - BOOST_CHECK_EQUAL(msg, std::get<1>(resp).value()); + + BOOST_REQUIRE_EQUAL(ec, boost::system::error_code{}); + BOOST_REQUIRE_EQUAL(msg, std::get<1>(resp).value()); req.clear(); std::get<1>(resp).value().clear(); + + co_await conn->async_exec(*pubs, ignore, net::deferred); } } @@ -64,18 +73,41 @@ auto async_echo_stress() -> net::awaitable { auto ex = co_await net::this_coro::executor; auto conn = std::make_shared(ex); + config cfg; + cfg.health_check_interval = std::chrono::seconds::zero(); + run(conn, cfg, + boost::asio::error::operation_aborted, + boost::redis::operation::receive, + boost::redis::logger::level::crit); + + request req; + req.push("SUBSCRIBE", "channel"); + co_await conn->async_exec(req, ignore, net::deferred); + // Number of coroutines that will send pings sharing the same + // connection to redis. int const sessions = 500; + + // The number of pings that will be sent by each session. int const msgs = 1000; - int total = sessions * msgs; - net::co_spawn(ex, push_consumer(conn, total), net::detached); + // The number of publishes that will be sent by each session with + // each message. + int const n_pubs = 10; - for (int i = 0; i < sessions; ++i) - net::co_spawn(ex, echo_session(conn, std::to_string(i), msgs), net::detached); + // This is the total number of pushes we will receive. + int total_pushes = sessions * msgs * n_pubs + 1; + auto pubs = std::make_shared(); + for (int i = 0; i < n_pubs; ++i) + pubs->push("PUBLISH", "channel", "payload"); - run(conn); + // Op that will consume the pushes counting down until all expected + // pushes have been received. + net::co_spawn(ex, push_consumer(conn, total_pushes), net::detached); + + for (int i = 0; i < sessions; ++i) + net::co_spawn(ex, echo_session(conn, pubs, std::to_string(i), msgs), net::detached); } BOOST_AUTO_TEST_CASE(echo_stress) From 538ab8f35fd4e8288da120b8fe683205c8c63264 Mon Sep 17 00:00:00 2001 From: Marcelo Zimbres Date: Sun, 21 May 2023 14:29:41 +0200 Subject: [PATCH 19/32] Reduces the number of rescheduling needed to process a server sent push. Performance improved by close to 10%. --- CMakeLists.txt | 19 ++++----- CMakePresets.json | 2 +- include/boost/redis/connection_base.hpp | 52 ++++++++++++++++--------- tests/test_conn_check_health.cpp | 5 +++ tests/test_conn_exec.cpp | 6 +-- 5 files changed, 52 insertions(+), 32 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 19e92366..43938a63 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -79,15 +79,16 @@ add_executable(echo_server_client benchmarks/cpp/asio/echo_server_client.cpp) add_executable(echo_server_direct benchmarks/cpp/asio/echo_server_direct.cpp) set(tests_cpp17 - test_conn_quit - test_conn_tls - test_low_level - test_conn_exec_retry - test_conn_exec_error - test_request - test_run - test_low_level_sync - test_conn_check_health) + test_conn_quit + test_conn_tls + test_low_level + test_conn_exec_retry + test_conn_exec_error + test_request + test_run + test_low_level_sync + test_conn_check_health +) set(tests_cpp20 test_conn_exec diff --git a/CMakePresets.json b/CMakePresets.json index 3fba81f0..b6a11938 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -66,7 +66,7 @@ "CMAKE_BUILD_TYPE": "Debug", "CMAKE_CXX_EXTENSIONS": "OFF", "CMAKE_CXX_FLAGS": "-Wall -Wextra -fsanitize=address", - "CMAKE_CXX_COMPILER": "g++-11", + "CMAKE_CXX_COMPILER": "clang++-13", "CMAKE_SHARED_LINKER_FLAGS": "-fsanitize=address", "CMAKE_CXX_STANDARD_REQUIRED": "ON", "PROJECT_BINARY_DIR": "${sourceDir}/build/clang++-13", diff --git a/include/boost/redis/connection_base.hpp b/include/boost/redis/connection_base.hpp index a67f8507..5e3af4d0 100644 --- a/include/boost/redis/connection_base.hpp +++ b/include/boost/redis/connection_base.hpp @@ -43,7 +43,7 @@ namespace detail { template struct wait_receive_op { - Conn* conn; + Conn* conn_; asio::coroutine coro{}; template @@ -52,14 +52,14 @@ struct wait_receive_op { { BOOST_ASIO_CORO_REENTER (coro) { - BOOST_ASIO_CORO_YIELD - conn->channel_.async_send(system::error_code{}, 0, std::move(self)); - BOOST_REDIS_CHECK_OP0(;); + conn_->channel_.cancel(); BOOST_ASIO_CORO_YIELD - conn->channel_.async_send(system::error_code{}, 0, std::move(self)); - BOOST_REDIS_CHECK_OP0(;); - + conn_->channel_.async_send(system::error_code{}, 0, std::move(self)); + if (!conn_->is_open() || is_cancelled(self)) { + self.complete(!!ec ? ec : asio::error::operation_aborted); + return; + } self.complete({}); } } @@ -158,7 +158,7 @@ class read_next_op { template struct receive_op { - Conn* conn; + Conn* conn_; Adapter adapter; std::size_t read_size = 0; asio::coroutine coro{}; @@ -171,27 +171,32 @@ struct receive_op { { BOOST_ASIO_CORO_REENTER (coro) { - BOOST_ASIO_CORO_YIELD - conn->channel_.async_receive(std::move(self)); - BOOST_REDIS_CHECK_OP1(;); + if (conn_->wait_read_op_notification_) { + BOOST_ASIO_CORO_YIELD + conn_->channel_.async_receive(std::move(self)); + if (!conn_->is_open() || is_cancelled(self)) { + self.complete(!!ec ? ec : asio::error::operation_aborted, 0); + return; + } + } - if (conn->use_ssl()) - BOOST_ASIO_CORO_YIELD redis::detail::async_read(conn->next_layer(), conn->make_dynamic_buffer(), adapter, std::move(self)); + if (conn_->use_ssl()) + BOOST_ASIO_CORO_YIELD redis::detail::async_read(conn_->next_layer(), conn_->make_dynamic_buffer(), adapter, std::move(self)); else - BOOST_ASIO_CORO_YIELD redis::detail::async_read(conn->next_layer().next_layer(), conn->make_dynamic_buffer(), adapter, std::move(self)); + BOOST_ASIO_CORO_YIELD redis::detail::async_read(conn_->next_layer().next_layer(), conn_->make_dynamic_buffer(), adapter, std::move(self)); if (ec || is_cancelled(self)) { - conn->cancel(operation::run); - conn->cancel(operation::receive); + conn_->cancel(operation::run); + conn_->cancel(operation::receive); self.complete(!!ec ? ec : asio::error::operation_aborted, {}); return; } read_size = n; - BOOST_ASIO_CORO_YIELD - conn->channel_.async_receive(std::move(self)); - BOOST_REDIS_CHECK_OP1(;); + conn_->wait_read_op_notification_ = !conn_->is_next_maybe_push(); + if (conn_->wait_read_op_notification_) + conn_->channel_.cancel(); self.complete({}, read_size); return; @@ -315,6 +320,7 @@ struct run_op { { conn->write_buffer_.clear(); conn->read_buffer_.clear(); + conn->wait_read_op_notification_ = true; BOOST_ASIO_CORO_YIELD asio::experimental::make_parallel_group( @@ -991,6 +997,11 @@ class connection_base { stream_->next_layer().close(); } + bool is_next_maybe_push() const noexcept + { + return !read_buffer_.empty() && (resp3::to_type(read_buffer_.front()) == resp3::type::push); + } + auto is_open() const noexcept { return stream_->next_layer().is_open(); } auto& lowest_layer() noexcept { return stream_->lowest_layer(); } @@ -1009,6 +1020,9 @@ class connection_base { std::string write_buffer_; reqs_type reqs_; std::size_t max_read_size_ = (std::numeric_limits::max)(); + + // Flag that optimizes reading pushes. + bool wait_read_op_notification_ = true; }; } // boost::redis diff --git a/tests/test_conn_check_health.cpp b/tests/test_conn_check_health.cpp index 494f22c7..dc8fa078 100644 --- a/tests/test_conn_check_health.cpp +++ b/tests/test_conn_check_health.cpp @@ -9,6 +9,7 @@ #define BOOST_TEST_MODULE check-health #include #include +#include #include "common.hpp" namespace net = boost::asio; @@ -119,5 +120,9 @@ BOOST_AUTO_TEST_CASE(check_health) BOOST_TEST(!!res1); BOOST_TEST(!!res2); + + // Waits before exiting otherwise it might cause subsequent tests + // to fail. + std::this_thread::sleep_for(std::chrono::seconds{10}); } diff --git a/tests/test_conn_exec.cpp b/tests/test_conn_exec.cpp index b5fe3a1e..73be44cb 100644 --- a/tests/test_conn_exec.cpp +++ b/tests/test_conn_exec.cpp @@ -51,7 +51,7 @@ BOOST_AUTO_TEST_CASE(hello_priority) conn->async_exec(req1, ignore, [&](auto ec, auto){ // Second callback to the called. std::cout << "req1" << std::endl; - BOOST_TEST(!ec); + BOOST_CHECK_EQUAL(ec, boost::system::error_code{}); BOOST_TEST(!seen2); BOOST_TEST(seen3); seen1 = true; @@ -60,7 +60,7 @@ BOOST_AUTO_TEST_CASE(hello_priority) conn->async_exec(req2, ignore, [&](auto ec, auto){ // Last callback to the called. std::cout << "req2" << std::endl; - BOOST_TEST(!ec); + BOOST_CHECK_EQUAL(ec, boost::system::error_code{}); BOOST_TEST(seen1); BOOST_TEST(seen3); seen2 = true; @@ -71,7 +71,7 @@ BOOST_AUTO_TEST_CASE(hello_priority) conn->async_exec(req3, ignore, [&](auto ec, auto){ // Callback that will be called first. std::cout << "req3" << std::endl; - BOOST_TEST(!ec); + BOOST_CHECK_EQUAL(ec, boost::system::error_code{}); BOOST_TEST(!seen1); BOOST_TEST(!seen2); seen3 = true; From 3c02a7662bee311c97cd2300b8acbd76f424c9e0 Mon Sep 17 00:00:00 2001 From: Marcelo Zimbres Date: Sun, 21 May 2023 21:13:58 +0200 Subject: [PATCH 20/32] Replaces connection channel with a timer. --- include/boost/redis/connection_base.hpp | 39 ++++++++++++------------- tests/test_conn_exec_error.cpp | 21 +++++++++---- tests/test_conn_push.cpp | 4 +-- 3 files changed, 37 insertions(+), 27 deletions(-) diff --git a/include/boost/redis/connection_base.hpp b/include/boost/redis/connection_base.hpp index 5e3af4d0..554efe47 100644 --- a/include/boost/redis/connection_base.hpp +++ b/include/boost/redis/connection_base.hpp @@ -20,7 +20,6 @@ #include #include #include -#include #include #include #include @@ -52,10 +51,10 @@ struct wait_receive_op { { BOOST_ASIO_CORO_REENTER (coro) { - conn_->channel_.cancel(); + conn_->read_op_timer_.cancel(); BOOST_ASIO_CORO_YIELD - conn_->channel_.async_send(system::error_code{}, 0, std::move(self)); + conn_->read_op_timer_.async_wait(std::move(self)); if (!conn_->is_open() || is_cancelled(self)) { self.complete(!!ec ? ec : asio::error::operation_aborted); return; @@ -143,7 +142,11 @@ class read_next_op { ++index_; - BOOST_REDIS_CHECK_OP1(conn_->cancel(operation::run);); + if (ec || redis::detail::is_cancelled(self)) { + conn_->cancel(operation::run); + self.complete(!!ec ? ec : asio::error::operation_aborted, {}); + return; + } read_size_ += n; @@ -171,9 +174,9 @@ struct receive_op { { BOOST_ASIO_CORO_REENTER (coro) { - if (conn_->wait_read_op_notification_) { + if (!conn_->is_next_push()) { BOOST_ASIO_CORO_YIELD - conn_->channel_.async_receive(std::move(self)); + conn_->read_op_timer_.async_wait(std::move(self)); if (!conn_->is_open() || is_cancelled(self)) { self.complete(!!ec ? ec : asio::error::operation_aborted, 0); return; @@ -194,9 +197,9 @@ struct receive_op { read_size = n; - conn_->wait_read_op_notification_ = !conn_->is_next_maybe_push(); - if (conn_->wait_read_op_notification_) - conn_->channel_.cancel(); + if (!conn_->is_next_push()) { + conn_->read_op_timer_.cancel(); + } self.complete({}, read_size); return; @@ -320,7 +323,6 @@ struct run_op { { conn->write_buffer_.clear(); conn->read_buffer_.clear(); - conn->wait_read_op_notification_ = true; BOOST_ASIO_CORO_YIELD asio::experimental::make_parallel_group( @@ -496,11 +498,12 @@ class connection_base { , stream_{std::make_unique(ex, ctx_)} , writer_timer_{ex} , read_timer_{ex} - , channel_{ex} + , read_op_timer_{ex} , runner_{ex, {}} { writer_timer_.expires_at(std::chrono::steady_clock::time_point::max()); read_timer_.expires_at(std::chrono::steady_clock::time_point::max()); + read_op_timer_.expires_at(std::chrono::steady_clock::time_point::max()); } /// Contructs from an execution context. @@ -628,7 +631,7 @@ class connection_base { return asio::async_compose < CompletionToken , void(system::error_code, std::size_t) - >(redis::detail::receive_op{this, f}, token, channel_); + >(redis::detail::receive_op{this, f}, token, read_op_timer_); } /** @brief Starts underlying connection operations. @@ -695,7 +698,6 @@ class connection_base { using clock_type = std::chrono::steady_clock; using clock_traits_type = asio::wait_traits; using timer_type = asio::basic_waitable_timer; - using channel_type = asio::experimental::channel; using runner_type = redis::detail::runner; auto use_ssl() const noexcept @@ -767,7 +769,7 @@ class connection_base { } break; case operation::receive: { - channel_.cancel(); + read_op_timer_.cancel(); } break; default: /* ignore */; } @@ -891,7 +893,7 @@ class connection_base { return asio::async_compose < CompletionToken , void(system::error_code) - >(redis::detail::wait_receive_op{this}, token, channel_); + >(redis::detail::wait_receive_op{this}, token, read_op_timer_); } void cancel_push_requests() @@ -997,7 +999,7 @@ class connection_base { stream_->next_layer().close(); } - bool is_next_maybe_push() const noexcept + bool is_next_push() const noexcept { return !read_buffer_.empty() && (resp3::to_type(read_buffer_.front()) == resp3::type::push); } @@ -1013,16 +1015,13 @@ class connection_base { // not suspend. timer_type writer_timer_; timer_type read_timer_; - channel_type channel_; + timer_type read_op_timer_; runner_type runner_; std::string read_buffer_; std::string write_buffer_; reqs_type reqs_; std::size_t max_read_size_ = (std::numeric_limits::max)(); - - // Flag that optimizes reading pushes. - bool wait_read_op_notification_ = true; }; } // boost::redis diff --git a/tests/test_conn_exec_error.cpp b/tests/test_conn_exec_error.cpp index 16f53631..fe465325 100644 --- a/tests/test_conn_exec_error.cpp +++ b/tests/test_conn_exec_error.cpp @@ -200,14 +200,25 @@ BOOST_AUTO_TEST_CASE(error_in_transaction) ioc.run(); } -// This test is important because a subscriber has no response on -// success, but on error, for example when using a wrong syntax, the -// server will send a simple error response the client is not -// expecting. +// This test is important because a SUBSCRIBE command has no response +// on success, but does on error. for example when using a wrong +// syntax, the server will send a simple error response the client is +// not expecting. +// +// Sending the subscribe after the ping command below is just a +// convenience to avoid have it merged in a pipeline making things +// even more complex. For example, without a ping, we might get the +// sequence HELLO + SUBSCRIBE + PING where the hello and ping are +// automatically sent by the implementation. In this case, if the +// subscribe synthax is wrong, redis will send a response, which does +// not exist on success. That response will be interprested as the +// response to the PING command that comes thereafter and won't be +// forwarded to the receive_op, resulting in a difficult to handle +// error. BOOST_AUTO_TEST_CASE(subscriber_wrong_syntax) { request req1; - req1.push("HELLO", 3); + req1.push("PING"); request req2; req2.push("SUBSCRIBE"); // Wrong command synthax. diff --git a/tests/test_conn_push.cpp b/tests/test_conn_push.cpp index c5770026..91d1f274 100644 --- a/tests/test_conn_push.cpp +++ b/tests/test_conn_push.cpp @@ -152,7 +152,7 @@ push_consumer1(std::shared_ptr conn, bool& push_received) { auto [ec, ev] = co_await conn->async_receive(ignore, as_tuple(net::use_awaitable)); - BOOST_CHECK_EQUAL(ec, net::experimental::channel_errc::channel_cancelled); + BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled); } push_received = true; @@ -196,7 +196,7 @@ BOOST_AUTO_TEST_CASE(test_push_adapter) }); conn->async_exec(req, ignore, [](auto ec, auto){ - BOOST_CHECK_EQUAL(ec, net::experimental::error::channel_errors::channel_cancelled); + BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled); }); run(conn); From e09a53ff08c0ad8cb941e72c9870afb5c4800f2d Mon Sep 17 00:00:00 2001 From: Marcelo Zimbres Date: Sun, 4 Jun 2023 16:18:35 +0200 Subject: [PATCH 21/32] Removes payload rotation from request. The user can simply call HELLO before other commands. Altering the order of requests makes it impossible to declare responses. --- examples/boost_redis.cpp | 7 ------- include/boost/redis/request.hpp | 24 ++++++------------------ 2 files changed, 6 insertions(+), 25 deletions(-) diff --git a/examples/boost_redis.cpp b/examples/boost_redis.cpp index 3870f200..dddc80f2 100644 --- a/examples/boost_redis.cpp +++ b/examples/boost_redis.cpp @@ -4,11 +4,4 @@ * accompanying file LICENSE.txt) */ -#include -#include -#include -#include -#include -#include - #include diff --git a/include/boost/redis/request.hpp b/include/boost/redis/request.hpp index 08006b8a..b3508c58 100644 --- a/include/boost/redis/request.hpp +++ b/include/boost/redis/request.hpp @@ -99,6 +99,7 @@ class request { { payload_.clear(); commands_ = 0; + has_hello_priority_ = false; } /// Calls std::string::reserve on the internal storage. @@ -139,14 +140,12 @@ class request { template void push(std::string_view cmd, Ts const&... args) { - auto const size_before = std::size(payload_); auto constexpr pack_size = sizeof...(Ts); resp3::add_header(payload_, resp3::type::array, 1 + pack_size); resp3::add_bulk(payload_, cmd); resp3::add_bulk(payload_, std::tie(std::forward(args)...)); - auto const size_after = std::size(payload_); - check_cmd(cmd, size_after - size_before); + check_cmd(cmd); } /** @brief Appends a new command to the end of the request. @@ -194,7 +193,6 @@ class request { if (begin == end) return; - auto const size_before = std::size(payload_); auto constexpr size = resp3::bulk_counter::size; auto const distance = std::distance(begin, end); resp3::add_header(payload_, resp3::type::array, 2 + size * distance); @@ -204,9 +202,7 @@ class request { for (; begin != end; ++begin) resp3::add_bulk(payload_, *begin); - auto const size_after = std::size(payload_); - - check_cmd(cmd, size_after - size_before); + check_cmd(cmd); } /** @brief Appends a new command to the end of the request. @@ -249,7 +245,6 @@ class request { if (begin == end) return; - auto const size_before = std::size(payload_); auto constexpr size = resp3::bulk_counter::size; auto const distance = std::distance(begin, end); resp3::add_header(payload_, resp3::type::array, 1 + size * distance); @@ -258,9 +253,7 @@ class request { for (; begin != end; ++begin) resp3::add_bulk(payload_, *begin); - auto const size_after = std::size(payload_); - - check_cmd(cmd, size_after - size_before); + check_cmd(cmd); } /** @brief Appends a new command to the end of the request. @@ -308,18 +301,13 @@ class request { } private: - void check_cmd(std::string_view cmd, std::size_t n) + void check_cmd(std::string_view cmd) { if (!detail::has_response(cmd)) ++commands_; - if (cmd == "HELLO") { + if (cmd == "HELLO") has_hello_priority_ = cfg_.hello_with_priority; - if (has_hello_priority_) { - auto const shift = std::size(payload_) - n; - std::rotate(std::begin(payload_), std::begin(payload_) + shift, std::end(payload_)); - } - } } config cfg_; From 635b3608ad66e5f8a3b3a2e83a597974701875c0 Mon Sep 17 00:00:00 2001 From: Marcelo Zimbres Date: Thu, 8 Jun 2023 22:00:07 +0200 Subject: [PATCH 22/32] Removes unnecessary files. --- CMakeLists.txt | 5 +++-- examples/cpp20_json.cpp | 14 +++++++------ examples/cpp20_protobuf.cpp | 20 ++++++++++++++++--- examples/json.hpp | 33 ------------------------------ examples/protobuf.hpp | 40 ------------------------------------- 5 files changed, 28 insertions(+), 84 deletions(-) delete mode 100644 examples/json.hpp delete mode 100644 examples/protobuf.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 43938a63..1ba1d657 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -111,10 +111,11 @@ set(examples_cpp20 cpp20_streams cpp20_containers cpp20_echo_server - cpp20_resolve_with_sentinel cpp20_json cpp20_subscriber - cpp20_intro_tls) + cpp20_intro_tls + cpp20_resolve_with_sentinel +) if (Protobuf_FOUND) list(APPEND examples_cpp20 cpp20_protobuf) diff --git a/examples/cpp20_json.cpp b/examples/cpp20_json.cpp index 68c0879f..063614b1 100644 --- a/examples/cpp20_json.cpp +++ b/examples/cpp20_json.cpp @@ -7,9 +7,9 @@ #include #include #include -#include #include #include +#include #include #include @@ -17,14 +17,16 @@ #define BOOST_JSON_NO_LIB #define BOOST_CONTAINER_NO_LIB -#include "json.hpp" +#include +#include +#include +#include #include namespace net = boost::asio; using namespace boost::describe; using boost::redis::request; using boost::redis::response; -using boost::redis::operation; using boost::redis::ignore_t; using boost::redis::config; using connection = net::deferred_t::as_default_on_t; @@ -41,10 +43,10 @@ BOOST_DESCRIBE_STRUCT(user, (), (name, age, country)) // Boost.Redis customization points (examples/json.hpp) void boost_redis_to_bulk(std::string& to, user const& u) - { boost::redis::json::to_bulk(to, u); } + { boost::redis::resp3::boost_redis_to_bulk(to, boost::json::serialize(boost::json::value_from(u))); } -void boost_redis_from_bulk(user& u, std::string_view sv, boost::system::error_code& ec) - { boost::redis::json::from_bulk(u, sv, ec); } +void boost_redis_from_bulk(user& u, std::string_view sv, boost::system::error_code&) + { u = boost::json::value_to(boost::json::parse(sv)); } auto co_main(config cfg) -> net::awaitable { diff --git a/examples/cpp20_protobuf.cpp b/examples/cpp20_protobuf.cpp index d372798f..32e92396 100644 --- a/examples/cpp20_protobuf.cpp +++ b/examples/cpp20_protobuf.cpp @@ -5,12 +5,13 @@ */ #include +#include #include #include #include #include +#include #include -#include "protobuf.hpp" // See the definition in person.proto. This header is automatically // generated by CMakeLists.txt. @@ -33,11 +34,24 @@ using tutorial::person; namespace tutorial { +// Below I am using a Boost.Redis to indicate a protobuf error, this +// is ok for an example, users however might want to define their own +// error codes. void boost_redis_to_bulk(std::string& to, person const& u) - { boost::redis::protobuf::to_bulk(to, u); } +{ + std::string tmp; + if (!u.SerializeToString(&tmp)) + throw boost::system::system_error(boost::redis::error::invalid_data_type); + + boost::redis::resp3::boost_redis_to_bulk(to, tmp); +} void boost_redis_from_bulk(person& u, std::string_view sv, boost::system::error_code& ec) - { boost::redis::protobuf::from_bulk(u, sv, ec); } +{ + std::string const tmp {sv}; + if (!u.ParseFromString(tmp)) + ec = boost::redis::error::invalid_data_type; +} } // tutorial diff --git a/examples/json.hpp b/examples/json.hpp deleted file mode 100644 index ca7e26f8..00000000 --- a/examples/json.hpp +++ /dev/null @@ -1,33 +0,0 @@ -/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com) - * - * Distributed under the Boost Software License, Version 1.0. (See - * accompanying file LICENSE.txt) - */ - -#ifndef BOOST_REDIS_JSON_HPP -#define BOOST_REDIS_JSON_HPP - -#include -#include -#include -#include - -namespace boost::redis::json -{ - -template -void to_bulk(std::string& to, T const& u) -{ - redis::resp3::boost_redis_to_bulk(to, boost::json::serialize(boost::json::value_from(u))); -} - -template -void from_bulk(T& u, std::string_view sv, system::error_code&) -{ - auto const jv = boost::json::parse(sv); - u = boost::json::value_to(jv); -} - -} // boost::redis::json - -#endif // BOOST_REDIS_JSON_HPP diff --git a/examples/protobuf.hpp b/examples/protobuf.hpp deleted file mode 100644 index b1ba1231..00000000 --- a/examples/protobuf.hpp +++ /dev/null @@ -1,40 +0,0 @@ -/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com) - * - * Distributed under the Boost Software License, Version 1.0. (See - * accompanying file LICENSE.txt) - */ - -#ifndef BOOST_REDIS_PROTOBUF_HPP -#define BOOST_REDIS_PROTOBUF_HPP - -#include -#include - -namespace boost::redis::protobuf -{ - -// Below I am using a Boost.Redis to indicate a protobuf error, this -// is ok for an example, users however might want to define their own -// error codes. - -template -void to_bulk(std::string& to, T const& u) -{ - std::string tmp; - if (!u.SerializeToString(&tmp)) - throw system::system_error(redis::error::invalid_data_type); - - boost::redis::resp3::boost_redis_to_bulk(to, tmp); -} - -template -void from_bulk(T& u, std::string_view sv, system::error_code& ec) -{ - std::string const tmp {sv}; - if (!u.ParseFromString(tmp)) - ec = redis::error::invalid_data_type; -} - -} // boost::redis::json - -#endif // BOOST_REDIS_PROTOBUF_HPP From c99790ab5c63192f86ce975ee7916ec12213e8ce Mon Sep 17 00:00:00 2001 From: Marcelo Zimbres Date: Sun, 11 Jun 2023 09:28:12 +0200 Subject: [PATCH 23/32] Uses choco instead of cinst. --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7ccf47e8..3fe46a04 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -67,8 +67,8 @@ jobs: platform_version: ${{env.BOOST_PLATFORM_VERSION}} arch: null - - name: Install packages - run: cinst openssl + - name: Install openssl + run: choco install openssl - name: Create build directory run: mkdir build From 82430afc8b8e1f3db448ce2aaf4167e9be852c92 Mon Sep 17 00:00:00 2001 From: Marcelo Zimbres Date: Sun, 11 Jun 2023 11:33:07 +0200 Subject: [PATCH 24/32] Make the connection non-generic on the executor type. --- examples/cpp20_chat_room.cpp | 7 +-- examples/cpp20_containers.cpp | 8 +-- examples/cpp20_echo_server.cpp | 4 +- examples/cpp20_intro.cpp | 5 +- examples/cpp20_intro_tls.cpp | 4 +- examples/cpp20_json.cpp | 4 +- examples/cpp20_protobuf.cpp | 4 +- examples/cpp20_resolve_with_sentinel.cpp | 2 +- examples/cpp20_streams.cpp | 4 +- examples/cpp20_subscriber.cpp | 5 +- include/boost/redis/connection.hpp | 62 +++++++++++++++++++++++- include/boost/redis/impl/connection.ipp | 33 +++++++++++++ include/boost/redis/src.hpp | 1 + tests/test_issue_50.cpp | 2 +- 14 files changed, 119 insertions(+), 26 deletions(-) create mode 100644 include/boost/redis/impl/connection.ipp diff --git a/examples/cpp20_chat_room.cpp b/examples/cpp20_chat_room.cpp index 489e5952..24c4a025 100644 --- a/examples/cpp20_chat_room.cpp +++ b/examples/cpp20_chat_room.cpp @@ -19,11 +19,12 @@ namespace net = boost::asio; using stream_descriptor = net::deferred_t::as_default_on_t; -using connection = net::deferred_t::as_default_on_t; using signal_set = net::deferred_t::as_default_on_t; using boost::redis::request; using boost::redis::generic_response; using boost::redis::config; +using boost::redis::connection; +using boost::redis::ignore; using net::redirect_error; using net::use_awaitable; using boost::system::error_code; @@ -41,7 +42,7 @@ receiver(std::shared_ptr conn) -> net::awaitable while (conn->will_reconnect()) { // Subscribe to channels. - co_await conn->async_exec(req); + co_await conn->async_exec(req, ignore, net::deferred); // Loop reading Redis push messages. for (generic_response resp;;) { @@ -66,7 +67,7 @@ auto publisher(std::shared_ptr in, std::shared_ptrasync_exec(req); + co_await conn->async_exec(req, ignore, net::deferred); msg.erase(0, n); } } diff --git a/examples/cpp20_containers.cpp b/examples/cpp20_containers.cpp index ec2ee53b..dfedd82e 100644 --- a/examples/cpp20_containers.cpp +++ b/examples/cpp20_containers.cpp @@ -20,7 +20,7 @@ using boost::redis::response; using boost::redis::ignore_t; using boost::redis::ignore; using boost::redis::config; -using connection = net::deferred_t::as_default_on_t; +using boost::redis::connection; void print(std::map const& cont) { @@ -47,7 +47,7 @@ auto store(std::shared_ptr conn) -> net::awaitable req.push_range("RPUSH", "rpush-key", vec); req.push_range("HSET", "hset-key", map); - co_await conn->async_exec(req); + co_await conn->async_exec(req, ignore, net::deferred); } auto hgetall(std::shared_ptr conn) -> net::awaitable @@ -60,7 +60,7 @@ auto hgetall(std::shared_ptr conn) -> net::awaitable response> resp; // Executes the request and reads the response. - co_await conn->async_exec(req, resp); + co_await conn->async_exec(req, resp, net::deferred); print(std::get<0>(resp).value()); } @@ -81,7 +81,7 @@ auto transaction(std::shared_ptr conn) -> net::awaitable response>, std::optional>> // exec > resp; - co_await conn->async_exec(req, resp); + co_await conn->async_exec(req, resp, net::deferred); print(std::get<0>(std::get<3>(resp).value()).value().value()); print(std::get<1>(std::get<3>(resp).value()).value().value()); diff --git a/examples/cpp20_echo_server.cpp b/examples/cpp20_echo_server.cpp index 6d2691fb..9b637240 100644 --- a/examples/cpp20_echo_server.cpp +++ b/examples/cpp20_echo_server.cpp @@ -18,11 +18,11 @@ namespace net = boost::asio; using tcp_socket = net::deferred_t::as_default_on_t; using tcp_acceptor = net::deferred_t::as_default_on_t; using signal_set = net::deferred_t::as_default_on_t; -using connection = net::deferred_t::as_default_on_t; using boost::redis::request; using boost::redis::response; using boost::redis::config; using boost::system::error_code; +using boost::redis::connection; using namespace std::chrono_literals; auto echo_server_session(tcp_socket socket, std::shared_ptr conn) -> net::awaitable @@ -33,7 +33,7 @@ auto echo_server_session(tcp_socket socket, std::shared_ptr conn) -> for (std::string buffer;;) { auto n = co_await net::async_read_until(socket, net::dynamic_buffer(buffer, 1024), "\n"); req.push("PING", buffer); - co_await conn->async_exec(req, resp); + co_await conn->async_exec(req, resp, net::deferred); co_await net::async_write(socket, net::buffer(std::get<0>(resp).value())); std::get<0>(resp).value().clear(); req.clear(); diff --git a/examples/cpp20_intro.cpp b/examples/cpp20_intro.cpp index 3526950d..195122cf 100644 --- a/examples/cpp20_intro.cpp +++ b/examples/cpp20_intro.cpp @@ -17,8 +17,7 @@ namespace net = boost::asio; using boost::redis::request; using boost::redis::response; using boost::redis::config; -using boost::redis::logger; -using connection = net::deferred_t::as_default_on_t; +using boost::redis::connection; // Called from the main function (see main.cpp) auto co_main(config cfg) -> net::awaitable @@ -34,7 +33,7 @@ auto co_main(config cfg) -> net::awaitable response resp; // Executes the request. - co_await conn->async_exec(req, resp); + co_await conn->async_exec(req, resp, net::deferred); conn->cancel(); std::cout << "PING: " << std::get<0>(resp).value() << std::endl; diff --git a/examples/cpp20_intro_tls.cpp b/examples/cpp20_intro_tls.cpp index 23b491e1..b911af27 100644 --- a/examples/cpp20_intro_tls.cpp +++ b/examples/cpp20_intro_tls.cpp @@ -18,7 +18,7 @@ using boost::redis::request; using boost::redis::response; using boost::redis::config; using boost::redis::logger; -using connection = net::deferred_t::as_default_on_t; +using boost::redis::connection; auto verify_certificate(bool, net::ssl::verify_context&) -> bool { @@ -45,7 +45,7 @@ auto co_main(config cfg) -> net::awaitable conn->next_layer().set_verify_mode(net::ssl::verify_peer); conn->next_layer().set_verify_callback(verify_certificate); - co_await conn->async_exec(req, resp); + co_await conn->async_exec(req, resp, net::deferred); conn->cancel(); std::cout << "Response: " << std::get<0>(resp).value() << std::endl; diff --git a/examples/cpp20_json.cpp b/examples/cpp20_json.cpp index 063614b1..d0c6423c 100644 --- a/examples/cpp20_json.cpp +++ b/examples/cpp20_json.cpp @@ -29,7 +29,7 @@ using boost::redis::request; using boost::redis::response; using boost::redis::ignore_t; using boost::redis::config; -using connection = net::deferred_t::as_default_on_t; +using boost::redis::connection; // Struct that will be stored in Redis using json serialization. struct user { @@ -64,7 +64,7 @@ auto co_main(config cfg) -> net::awaitable response resp; - co_await conn->async_exec(req, resp); + co_await conn->async_exec(req, resp, net::deferred); conn->cancel(); // Prints the first ping diff --git a/examples/cpp20_protobuf.cpp b/examples/cpp20_protobuf.cpp index 32e92396..75eb8fd2 100644 --- a/examples/cpp20_protobuf.cpp +++ b/examples/cpp20_protobuf.cpp @@ -25,7 +25,7 @@ using boost::redis::response; using boost::redis::operation; using boost::redis::ignore_t; using boost::redis::config; -using connection = net::deferred_t::as_default_on_t; +using boost::redis::connection; // The protobuf type described in examples/person.proto using tutorial::person; @@ -76,7 +76,7 @@ net::awaitable co_main(config cfg) response resp; // Sends the request and receives the response. - co_await conn->async_exec(req, resp); + co_await conn->async_exec(req, resp, net::deferred); conn->cancel(); std::cout diff --git a/examples/cpp20_resolve_with_sentinel.cpp b/examples/cpp20_resolve_with_sentinel.cpp index 35bcf686..8401cd1e 100644 --- a/examples/cpp20_resolve_with_sentinel.cpp +++ b/examples/cpp20_resolve_with_sentinel.cpp @@ -19,7 +19,7 @@ using boost::redis::response; using boost::redis::ignore_t; using boost::redis::config; using boost::redis::address; -using connection = boost::asio::use_awaitable_t<>::as_default_on_t; +using boost::redis::connection; auto redir(boost::system::error_code& ec) { return net::redirect_error(net::use_awaitable, ec); } diff --git a/examples/cpp20_streams.cpp b/examples/cpp20_streams.cpp index 4eca3050..f0939756 100644 --- a/examples/cpp20_streams.cpp +++ b/examples/cpp20_streams.cpp @@ -25,7 +25,7 @@ using boost::redis::config; using boost::redis::generic_response; using boost::redis::operation; using boost::redis::request; -using connection = net::deferred_t::as_default_on_t; +using boost::redis::connection; using signal_set = net::deferred_t::as_default_on_t; auto stream_reader(std::shared_ptr conn) -> net::awaitable @@ -39,7 +39,7 @@ auto stream_reader(std::shared_ptr conn) -> net::awaitable for (;;) { req.push("XREAD", "BLOCK", "0", "STREAMS", "test-topic", stream_id); - co_await conn->async_exec(req, resp); + co_await conn->async_exec(req, resp, net::deferred); // std::cout << "Response: "; // for (int i = 0; i < resp->value().size(); ++i) { diff --git a/examples/cpp20_subscriber.cpp b/examples/cpp20_subscriber.cpp index 1753095f..f7c1a4b3 100644 --- a/examples/cpp20_subscriber.cpp +++ b/examples/cpp20_subscriber.cpp @@ -24,8 +24,9 @@ using boost::redis::request; using boost::redis::generic_response; using boost::redis::logger; using boost::redis::config; +using boost::redis::ignore; using boost::system::error_code; -using connection = net::deferred_t::as_default_on_t; +using boost::redis::connection; using signal_set = net::deferred_t::as_default_on_t; /* This example will subscribe and read pushes indefinitely. @@ -54,7 +55,7 @@ receiver(std::shared_ptr conn) -> net::awaitable while (conn->will_reconnect()) { // Reconnect to channels. - co_await conn->async_exec(req); + co_await conn->async_exec(req, ignore, net::deferred); // Loop reading Redis pushs messages. for (generic_response resp;;) { diff --git a/include/boost/redis/connection.hpp b/include/boost/redis/connection.hpp index 4ea560de..db226761 100644 --- a/include/boost/redis/connection.hpp +++ b/include/boost/redis/connection.hpp @@ -14,10 +14,10 @@ #include #include #include +#include #include #include -#include namespace boost::redis { namespace detail @@ -187,7 +187,65 @@ class basic_connection : public connection_base { /** \brief A connection that uses the asio::any_io_executor. * \ingroup high-level-api */ -using connection = basic_connection; +class connection { +public: + /// Executor type. + using executor_type = asio::any_io_executor; + + /// Contructs from an executor. + explicit connection(executor_type ex, asio::ssl::context::method method = asio::ssl::context::tls_client); + + /// Contructs from a context. + explicit connection(asio::io_context& ioc, asio::ssl::context::method method = asio::ssl::context::tls_client); + + executor_type get_executor() noexcept { return impl_.get_executor(); } + + template + auto async_run(config const& cfg, logger l, CompletionToken token) + { + return asio::async_initiate< + CompletionToken, void(boost::system::error_code)>( + [](auto handler, connection* self, config const* cfg, logger l) + { + self->async_run_impl(*cfg, l, std::move(handler)); + }, token, this, &cfg, l); + } + + template + auto async_receive(Response& response, CompletionToken token) + { + return impl_.async_receive(response, std::move(token)); + } + + template + auto async_exec(request const& req, Response& resp, CompletionToken token) + { + return impl_.async_exec(req, resp, std::move(token)); + } + + void cancel(operation op = operation::all); + + /// Returns true if the connection was canceled. + bool will_reconnect() const noexcept + { return impl_.will_reconnect();} + + /// Returns a reference to the next layer. + auto& next_layer() noexcept { return impl_.next_layer(); } + + /// Returns a const reference to the next layer. + auto const& next_layer() const noexcept { return impl_.next_layer(); } + + void reset_stream() { impl_.reset_stream();} + +private: + void + async_run_impl( + config const& cfg, + logger l, + asio::any_completion_handler token); + + basic_connection impl_; +}; } // boost::redis diff --git a/include/boost/redis/impl/connection.ipp b/include/boost/redis/impl/connection.ipp new file mode 100644 index 00000000..977031a5 --- /dev/null +++ b/include/boost/redis/impl/connection.ipp @@ -0,0 +1,33 @@ +/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com) + * + * Distributed under the Boost Software License, Version 1.0. (See + * accompanying file LICENSE.txt) + */ + +#include + +namespace boost::redis { + +connection::connection(executor_type ex, asio::ssl::context::method method) +: impl_{ex, method} +{ } + +connection::connection(asio::io_context& ioc, asio::ssl::context::method method) +: impl_(ioc.get_executor(), method) +{ } + +void +connection::async_run_impl( + config const& cfg, + logger l, + asio::any_completion_handler token) +{ + impl_.async_run(cfg, l, std::move(token)); +} + +void connection::cancel(operation op) +{ + impl_.cancel(op); +} + +} // namespace boost::redis diff --git a/include/boost/redis/src.hpp b/include/boost/redis/src.hpp index ef978afc..3a06c3e0 100644 --- a/include/boost/redis/src.hpp +++ b/include/boost/redis/src.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include diff --git a/tests/test_issue_50.cpp b/tests/test_issue_50.cpp index 9abab044..dabffd84 100644 --- a/tests/test_issue_50.cpp +++ b/tests/test_issue_50.cpp @@ -30,10 +30,10 @@ using boost::redis::ignore; using boost::redis::logger; using boost::redis::config; using boost::redis::operation; +using boost::redis::connection; using boost::system::error_code; using boost::asio::use_awaitable; using boost::asio::redirect_error; -using connection = boost::asio::use_awaitable_t<>::as_default_on_t; using namespace std::chrono_literals; // Push consumer From d29a057fa61ea5498d0bc88a2ffa60b2ee6acf00 Mon Sep 17 00:00:00 2001 From: Marcelo Zimbres Date: Tue, 20 Jun 2023 23:01:17 +0200 Subject: [PATCH 25/32] Uses composition instead of inheritance in the connection class. --- CMakeLists.txt | 2 +- include/boost/redis/connection.hpp | 196 +++++++++++++++--- .../redis/{ => detail}/connection_base.hpp | 97 +-------- 3 files changed, 171 insertions(+), 124 deletions(-) rename include/boost/redis/{ => detail}/connection_base.hpp (88%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1ba1d657..511ee395 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.14) -set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CMAKE_COMMAND} -E time") +#set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CMAKE_COMMAND} -E time") project( boost_redis diff --git a/include/boost/redis/connection.hpp b/include/boost/redis/connection.hpp index db226761..ccce89d2 100644 --- a/include/boost/redis/connection.hpp +++ b/include/boost/redis/connection.hpp @@ -7,7 +7,7 @@ #ifndef BOOST_REDIS_CONNECTION_HPP #define BOOST_REDIS_CONNECTION_HPP -#include +#include #include #include #include @@ -34,7 +34,7 @@ struct reconnection_op { BOOST_ASIO_CORO_REENTER (coro_) for (;;) { BOOST_ASIO_CORO_YIELD - conn_->async_run_one(conn_->cfg_, logger_, std::move(self)); + conn_->impl_.async_run(conn_->cfg_, logger_, std::move(self)); conn_->cancel(operation::receive); logger_.on_connection_lost(ec); if (!conn_->will_reconnect() || is_cancelled(self)) { @@ -68,14 +68,15 @@ struct reconnection_op { * */ template -class basic_connection : public connection_base { +class basic_connection { public: - using base_type = connection_base; - using this_type = basic_connection; - /// Executor type. using executor_type = Executor; + /// Returns the underlying executor. + executor_type get_executor() noexcept + { return impl_.get_executor(); } + /// Rebinds the socket type to another executor. template struct rebind_executor @@ -87,7 +88,7 @@ class basic_connection : public connection_base { /// Contructs from an executor. explicit basic_connection(executor_type ex, asio::ssl::context::method method = asio::ssl::context::tls_client) - : base_type{ex, method} + : impl_{ex, method} , timer_{ex} { } @@ -97,12 +98,28 @@ class basic_connection : public connection_base { : basic_connection(ioc.get_executor(), method) { } - /** @brief High-level connection to Redis. + /** @brief Starts underlying connection operations. + * + * This member function provides the following functionality + * + * 1. Resolve the address passed on `boost::redis::config::addr`. + * 2. Connect to one of the results obtained in the resolve operation. + * 3. Send a [HELLO](https://redis.io/commands/hello/) command where each of its parameters are read from `cfg`. + * 4. Start a health-check operation where ping commands are sent + * at intervals specified in + * `boost::redis::config::health_check_interval`. The message passed to + * `PING` will be `boost::redis::config::health_check_id`. Passing a + * timeout with value zero will disable health-checks. If the Redis + * server does not respond to a health-check within two times the value + * specified here, it will be considered unresponsive and the connection + * will be closed and a new connection will be stablished. + * 5. Starts read and write operations with the Redis + * server. More specifically it will trigger the write of all + * requests i.e. calls to `async_exec` that happened prior to this + * call. * - * This connection class adds reconnection functionality to - * `boost::redis::connection_base::async_run_one`. When a - * connection is lost for any reason, a new one is stablished - * automatically. To disable reconnection call + * When a connection is lost for any reason, a new one is + * stablished automatically. To disable reconnection call * `boost::redis::connection::cancel(operation::reconnection)`. * * @param cfg Configuration paramters. @@ -115,11 +132,6 @@ class basic_connection : public connection_base { * void f(system::error_code); * @endcode * - * @remarks - * - * * This function will complete only if reconnection was disabled - * and the connection is lost. - * * For example on how to call this function refer to * cpp20_intro.cpp or any other example. */ @@ -132,6 +144,8 @@ class basic_connection : public connection_base { Logger l = Logger{}, CompletionToken token = CompletionToken{}) { + using this_type = basic_connection; + cfg_ = cfg; l.set_prefix(cfg_.log_prefix); return asio::async_compose @@ -140,6 +154,77 @@ class basic_connection : public connection_base { >(detail::reconnection_op{this, l}, token, timer_); } + /** @brief Receives server side pushes asynchronously. + * + * When pushes arrive and there is no `async_receive` operation in + * progress, pushed data, requests, and responses will be paused + * until `async_receive` is called again. Apps will usually want + * to call `async_receive` in a loop. + * + * To cancel an ongoing receive operation apps should call + * `connection::cancel(operation::receive)`. + * + * @param response Response object. + * @param token Completion token. + * + * For an example see cpp20_subscriber.cpp. The completion token must + * have the following signature + * + * @code + * void f(system::error_code, std::size_t); + * @endcode + * + * Where the second parameter is the size of the push received in + * bytes. + */ + template < + class Response = ignore_t, + class CompletionToken = asio::default_completion_token_t + > + auto + async_receive( + Response& response, + CompletionToken token = CompletionToken{}) + { + return impl_.async_receive(response, token); + } + + /** @brief Executes commands on the Redis server asynchronously. + * + * This function sends a request to the Redis server and waits for + * the responses to each individual command in the request. If the + * request contains only commands that don't expect a response, + * the completion occurs after it has been written to the + * underlying stream. Multiple concurrent calls to this function + * will be automatically queued by the implementation. + * + * @param req Request. + * @param resp Response. + * @param token Completion token. + * + * For an example see cpp20_echo_server.cpp. The completion token must + * have the following signature + * + * @code + * void f(system::error_code, std::size_t); + * @endcode + * + * Where the second parameter is the size of the response received + * in bytes. + */ + template < + class Response = ignore_t, + class CompletionToken = asio::default_completion_token_t + > + auto + async_exec( + request const& req, + Response& resp = ignore, + CompletionToken token = CompletionToken{}) + { + return impl_.async_exec(req, resp, token); + } + /** @brief Cancel operations. * * @li `operation::exec`: Cancels operations started with @@ -152,7 +237,7 @@ class basic_connection : public connection_base { * @param op: The operation to be cancelled. * @returns The number of operations that have been canceled. */ - void cancel(operation op = operation::all) override + void cancel(operation op = operation::all) { switch (op) { case operation::reconnection: @@ -163,16 +248,51 @@ class basic_connection : public connection_base { default: /* ignore */; } - base_type::cancel(op); + impl_.cancel(op); } /// Returns true if the connection was canceled. bool will_reconnect() const noexcept { return cfg_.reconnect_wait_interval != std::chrono::seconds::zero();} -private: - config cfg_; + /** @brief Reserve memory on the read and write internal buffers. + * + * This function will call `std::string::reserve` on the + * underlying buffers. + * + * @param read The new capacity of the read buffer. + * @param write The new capacity of the write buffer. + */ + void reserve(std::size_t read, std::size_t write) + { + impl_.reserve(read, write); + } + + /// Sets the maximum size of the read buffer. + void set_max_buffer_read_size(std::size_t max_read_size) noexcept + { impl_.set_max_buffer_read_size(max_read_size); } + /// Returns the ssl context. + auto const& get_ssl_context() const noexcept + { return impl_.get_ssl_context();} + + /// Returns the ssl context. + auto& get_ssl_context() noexcept + { return impl_.get_ssl_context();} + + /// Resets the underlying stream. + void reset_stream() + { impl_.reset_stream(); } + + /// Returns a reference to the next layer. + auto& next_layer() noexcept + { return impl_.next_layer(); } + + /// Returns a const reference to the next layer. + auto const& next_layer() const noexcept + { return impl_.next_layer(); } + +private: using timer_type = asio::basic_waitable_timer< std::chrono::steady_clock, @@ -181,11 +301,19 @@ class basic_connection : public connection_base { template friend struct detail::reconnection_op; + config cfg_; + detail::connection_base impl_; timer_type timer_; }; -/** \brief A connection that uses the asio::any_io_executor. +/** \brief A basic_connection that type erases the executor. * \ingroup high-level-api + * + * This connection type uses the asio::any_io_executor and + * asio::any_completion_token to reduce compilation times. + * + * For documentaiton of each member function see + * `boost::redis::basic_connection`. */ class connection { public: @@ -198,8 +326,11 @@ class connection { /// Contructs from a context. explicit connection(asio::io_context& ioc, asio::ssl::context::method method = asio::ssl::context::tls_client); - executor_type get_executor() noexcept { return impl_.get_executor(); } + /// Returns the underlying executor. + executor_type get_executor() noexcept + { return impl_.get_executor(); } + /// Calls `boost::redis::basic_connection::async_run`. template auto async_run(config const& cfg, logger l, CompletionToken token) { @@ -211,31 +342,38 @@ class connection { }, token, this, &cfg, l); } + /// Calls `boost::redis::basic_connection::async_receive`. template auto async_receive(Response& response, CompletionToken token) { return impl_.async_receive(response, std::move(token)); } + /// Calls `boost::redis::basic_connection::async_exec`. template auto async_exec(request const& req, Response& resp, CompletionToken token) { return impl_.async_exec(req, resp, std::move(token)); } + /// Calls `boost::redis::basic_connection::cancel`. void cancel(operation op = operation::all); - /// Returns true if the connection was canceled. + /// Calls `boost::redis::basic_connection::will_reconnect`. bool will_reconnect() const noexcept { return impl_.will_reconnect();} - /// Returns a reference to the next layer. - auto& next_layer() noexcept { return impl_.next_layer(); } + /// Calls `boost::redis::basic_connection::next_layer`. + auto& next_layer() noexcept + { return impl_.next_layer(); } - /// Returns a const reference to the next layer. - auto const& next_layer() const noexcept { return impl_.next_layer(); } + /// Calls `boost::redis::basic_connection::next_layer`. + auto const& next_layer() const noexcept + { return impl_.next_layer(); } - void reset_stream() { impl_.reset_stream();} + /// Calls `boost::redis::basic_connection::reset_stream`. + void reset_stream() + { impl_.reset_stream();} private: void diff --git a/include/boost/redis/connection_base.hpp b/include/boost/redis/detail/connection_base.hpp similarity index 88% rename from include/boost/redis/connection_base.hpp rename to include/boost/redis/detail/connection_base.hpp index 554efe47..f8681769 100644 --- a/include/boost/redis/connection_base.hpp +++ b/include/boost/redis/detail/connection_base.hpp @@ -37,8 +37,7 @@ #include #include -namespace boost::redis { -namespace detail { +namespace boost::redis::detail { template struct wait_receive_op { @@ -473,7 +472,6 @@ struct reader_op { } } }; -} // detail /** @brief Base class for high level Redis asynchronous connections. * @ingroup high-level-api @@ -549,29 +547,6 @@ class connection_base { cancel_impl(op); } - /** @brief Executes commands on the Redis server asynchronously. - * - * This function sends a request to the Redis server and waits for - * the responses to each individual command in the request. If the - * request contains only commands that don't expect a response, - * the completion occurs after it has been written to the - * underlying stream. Multiple concurrent calls to this function - * will be automatically queued by the implementation. - * - * @param req Request. - * @param resp Response. - * @param token Completion token. - * - * For an example see cpp20_echo_server.cpp. The completion token must - * have the following signature - * - * @code - * void f(system::error_code, std::size_t); - * @endcode - * - * Where the second parameter is the size of the response received - * in bytes. - */ template < class Response = ignore_t, class CompletionToken = asio::default_completion_token_t @@ -592,29 +567,6 @@ class connection_base { >(redis::detail::exec_op{this, &req, f}, token, writer_timer_); } - /** @brief Receives server side pushes asynchronously. - * - * When pushes arrive and there is no `async_receive` operation in - * progress, pushed data, requests, and responses will be paused - * until `async_receive` is called again. Apps will usually want - * to call `async_receive` in a loop. - * - * To cancel an ongoing receive operation apps should call - * `connection::cancel(operation::receive)`. - * - * @param response Response object. - * @param token Completion token. - * - * For an example see cpp20_subscriber.cpp. The completion token must - * have the following signature - * - * @code - * void f(system::error_code, std::size_t); - * @endcode - * - * Where the second parameter is the size of the push received in - * bytes. - */ template < class Response = ignore_t, class CompletionToken = asio::default_completion_token_t @@ -634,60 +586,17 @@ class connection_base { >(redis::detail::receive_op{this, f}, token, read_op_timer_); } - /** @brief Starts underlying connection operations. - * - * Provides a high-level connection to the Redis server. It will - * perform the following steps - * - * 1. Resolve the address passed on `boost::redis::config::addr`. - * 2. Connect to one of the results obtained in the resolve operation. - * 3. Send a [HELLO](https://redis.io/commands/hello/) command where each of its parameters are read from `cfg`. - * 4. Start a health-check operation where ping commands are sent - * at intervals specified in - * `boost::redis::config::health_check_interval`. The message passed to - * `PING` will be `boost::redis::config::health_check_id`. Passing a - * timeout with value zero will disable health-checks. If the Redis - * server does not respond to a health-check within two times the value - * specified here, it will be considered unresponsive and the connection - * will be closed and a new connection will be stablished. - * 5. Starts read and write operations with the Redis - * server. More specifically it will trigger the write of all - * requests i.e. calls to `async_exec` that happened prior to this - * call. - * - * @param cfg Configuration paramters. - * @param l Logger object. The interface expected is specified in the class `boost::redis::logger`. - * @param token Completion token. - * - * The completion token must have the following signature - * - * @code - * void f(system::error_code); - * @endcode - * - * For example on how to call this function refer to - * cpp20_intro.cpp or any other example. - */ template - auto async_run_one(config const& cfg, Logger l, CompletionToken token) + auto async_run(config const& cfg, Logger l, CompletionToken token) { runner_.set_config(cfg); l.set_prefix(runner_.get_config().log_prefix); return runner_.async_run(*this, l, std::move(token)); } - /// Sets the maximum size of the read buffer. void set_max_buffer_read_size(std::size_t max_read_size) noexcept {max_read_size_ = max_read_size;} - /** @brief Reserve memory on the read and write internal buffers. - * - * This function will call `std::string::reserve` on the - * underlying buffers. - * - * @param read The new capacity of the read buffer. - * @param write The new capacity of the write buffer. - */ void reserve(std::size_t read, std::size_t write) { read_buffer_.reserve(read); @@ -1024,6 +933,6 @@ class connection_base { std::size_t max_read_size_ = (std::numeric_limits::max)(); }; -} // boost::redis +} // boost::redis::detail #endif // BOOST_REDIS_CONNECTION_BASE_HPP From a715c251bf97dfd5ddb03356a8ddc6ac86481800 Mon Sep 17 00:00:00 2001 From: Marcelo Zimbres Date: Fri, 23 Jun 2023 22:24:34 +0200 Subject: [PATCH 26/32] Improvements in the docs. --- README.md | 121 +++++++++++++--------------------- examples/cpp20_subscriber.cpp | 1 + 2 files changed, 48 insertions(+), 74 deletions(-) diff --git a/README.md b/README.md index 7bc5da0f..6ab6f6ec 100644 --- a/README.md +++ b/README.md @@ -1,41 +1,30 @@ # boost_redis -Boost.Redis is a [Redis](https://redis.io/) client library built on top of +Boost.Redis is a high-level [Redis](https://redis.io/) client library built on top of [Boost.Asio](https://www.boost.org/doc/libs/release/doc/html/boost_asio.html) that implements Redis plain text protocol [RESP3](https://github.com/redis/redis-specifications/blob/master/protocol/RESP3.md). It can multiplex any number of client requests, responses, and server pushes onto a single active socket -connection to the Redis server. The library hides low-level code away -from the user, which, in the majority of the cases will be concerned -with only three library entities - -* `boost::redis::connection`: A full-duplex connection to the Redis - server with high-level functions to execute Redis commands, receive - server pushes and automatic command [pipelines](https://redis.io/docs/manual/pipelining/). -* `boost::redis::request`: A container of Redis commands that supports - STL containers and user defined data types. -* `boost::redis::response`: Container of Redis responses. - -In the next sections we will cover all these points in detail with -examples. The requirements for using Boost.Redis are +connection to the Redis server. The requirements for using Boost.Redis are * Boost 1.81 or greater. * C++17 minimum. * Redis 6 or higher (must support RESP3). * Gcc (10, 11, 12), Clang (11, 13, 14) and Visual Studio (16 2019, 17 2022). -* Have basic-level knowledge about Redis and understand Asio and its asynchronous model. +* Have basic-level knowledge about [Redis](https://redis.io/docs/) + and [Boost.Asio](https://www.boost.org/doc/libs/1_82_0/doc/html/boost_asio/overview.html). -To install Boost.Redis download the latest release on -https://github.com/boostorg/redis/releases. Boost.Redis is a header only -library, so you can starting using it right away by adding the -`include` subdirectory to your project and including +The latest release can be downloaded on +https://github.com/boostorg/redis/releases. The library headers can be +found in the `include` subdirectory and a compilation of the source ```cpp #include ``` -in no more than one source file in your applications. To build the +is required. The simplest way to do it is to included this header in +no more than one source file in your applications. To build the examples and tests cmake is supported, for example ```cpp @@ -45,21 +34,10 @@ $ BOOST_ROOT=/opt/boost_1_81_0 cmake --preset g++-11 # Windows $ cmake -G "Visual Studio 17 2022" -A x64 -B bin64 -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake ``` + ## Connection -Readers that are not familiar with Redis are advised to learn more about -it on https://redis.io/docs/ before we start, in essence - -> Redis is an open source (BSD licensed), in-memory data structure -> store used as a database, cache, message broker, and streaming -> engine. Redis provides data structures such as strings, hashes, -> lists, sets, sorted sets with range queries, bitmaps, hyperloglogs, -> geospatial indexes, and streams. Redis has built-in replication, Lua -> scripting, LRU eviction, transactions, and different levels of -> on-disk persistence, and provides high availability via Redis -> Sentinel and automatic partitioning with Redis Cluster. - Let us start with a simple application that uses a short-lived connection to send a [ping](https://redis.io/commands/ping/) command to Redis @@ -78,7 +56,7 @@ auto co_main(config const& cfg) -> net::awaitable response resp; // Executes the request. - co_await conn->async_exec(req, resp); + co_await conn->async_exec(req, resp, net::deferred); conn->cancel(); std::cout << "PING: " << std::get<0>(resp).value() << std::endl; @@ -87,12 +65,12 @@ auto co_main(config const& cfg) -> net::awaitable The roles played by the `async_run` and `async_exec` functions are -* `connection::async_exec`: Execute the commands contained in the +* `async_exec`: Execute the commands contained in the request and store the individual responses in the `resp` object. Can be called from multiple places in your code concurrently. -* `connection::async_run`: Resolve, connect, ssl-handshake, - resp3-handshake, health-checks reconnection and coordinate low-level - read and write operations.. +* `async_run`: Resolve, connect, ssl-handshake, + resp3-handshake, health-checks, reconnection and coordinate low-level + read and write operations (among other things). ### Server pushes @@ -114,22 +92,22 @@ receiver(std::shared_ptr conn) -> net::awaitable request req; req.push("SUBSCRIBE", "channel"); - while (!conn->is_cancelled()) { + // Loop while reconnection is enabled + while (conn->will_reconnect()) { // Reconnect to channels. - co_await conn->async_exec(req); + co_await conn->async_exec(req, ignore, net::deferred); - // Loop reading Redis pushs messages. + // Loop reading Redis pushes. for (generic_response resp;;) { error_code ec; co_await conn->async_receive(resp, net::redirect_error(net::use_awaitable, ec)); if (ec) break; // Connection lost, break so we can reconnect to channels. - std::cout - << resp.value().at(1).value - << " " << resp.value().at(2).value - << " " << resp.value().at(3).value - << std::endl; + + // Use the response resp in some way and then clear it. + ... + resp.value().clear(); } } @@ -167,22 +145,21 @@ req.push_range("HSET", "key", map); Sending a request to Redis is performed with `boost::redis::connection::async_exec` as already stated. - - ### Config flags The `boost::redis::request::config` object inside the request dictates how the `boost::redis::connection` should handle the request in some important situations. The reader is advised to read it carefully. + ## Responses Boost.Redis uses the following strategy to support Redis responses -* **Static**: For `boost::redis::request` whose sizes are known at compile time use the `response` type. +* `boost::redis::request` is used for requests whose number of commands are not dynamic. * **Dynamic**: Otherwise use `boost::redis::generic_response`. -For example, below is a request with a compile time size +For example, the request below has three commands ```cpp request req; @@ -191,18 +168,19 @@ req.push("INCR", "key"); req.push("QUIT"); ``` -To read the response to this request users can use the following tuple +and its response also has three comamnds and can be read in the +following response object ```cpp response ``` -The pattern might have become apparent to the reader: the tuple must +The response behaves as a tuple and must have as many elements as the request has commands (exceptions below). It is also necessary that each tuple element is capable of storing the response to the command it refers to, otherwise an error will occur. To ignore responses to individual commands in the request use the tag -`boost::redis::ignore_t` +`boost::redis::ignore_t`, for example ```cpp // Ignore the second and last responses. @@ -266,18 +244,14 @@ response< Where both are passed to `async_exec` as showed elsewhere ```cpp -co_await conn->async_exec(req, resp); +co_await conn->async_exec(req, resp, net::deferred); ``` -If the intention is to ignore the response to all commands altogether -use `ignore` +If the intention is to ignore responses altogether use `ignore` ```cpp // Ignores the response -co_await conn->async_exec(req, ignore); - -// The default response argument will also ignore responses. -co_await conn->async_exec(req); +co_await conn->async_exec(req, ignore, net::deferred); ``` Responses that contain nested aggregates or heterogeneous data @@ -294,7 +268,7 @@ Commands that have no response like * `"PSUBSCRIBE"` * `"UNSUBSCRIBE"` -must be **NOT** be included in the response tuple. For example, the request below +must **NOT** be included in the response tuple. For example, the request below ```cpp request req; @@ -304,7 +278,7 @@ req.push("QUIT"); ``` must be read in this tuple `response`, -that has size two. +that has static size two. ### Null @@ -320,17 +294,17 @@ response< ... > resp; -co_await conn->async_exec(req, resp); +co_await conn->async_exec(req, resp, net::deferred); ``` Everything else stays pretty much the same. ### Transactions -To read responses to transactions we must first observe that Redis will -queue the transaction commands and send their individual responses as elements -of an array, the array is itself the response to the `EXEC` command. -For example, to read the response to this request +To read responses to transactions we must first observe that Redis +will queue the transaction commands and send their individual +responses as elements of an array, the array is itself the response to +the `EXEC` command. For example, to read the response to this request ```cpp req.push("MULTI"); @@ -360,7 +334,7 @@ response< exec_resp_type, // exec > resp; -co_await conn->async_exec(req, resp); +co_await conn->async_exec(req, resp, net::deferred); ``` For a complete example see cpp20_containers.cpp. @@ -373,8 +347,8 @@ There are cases where responses to Redis commands won't fit in the model presented above, some examples are * Commands (like `set`) whose responses don't have a fixed -RESP3 type. Expecting an `int` and receiving a blob-string -will result in error. + RESP3 type. Expecting an `int` and receiving a blob-string + will result in error. * RESP3 aggregates that contain nested aggregates can't be read in STL containers. * Transactions with a dynamic number of commands can't be read in a `response`. @@ -408,7 +382,7 @@ using other types ```cpp // Receives any RESP3 simple or aggregate data type. boost::redis::generic_response resp; -co_await conn->async_exec(req, resp); +co_await conn->async_exec(req, resp, net::deferred); ``` For example, suppose we want to retrieve a hash data structure @@ -460,9 +434,8 @@ The examples below show how to use the features discussed so far * cpp17_intro.cpp: Uses callbacks and requires C++17. * cpp17_intro_sync.cpp: Runs `async_run` in a separate thread and performs synchronous calls to `async_exec`. -To avoid repetition code that is common to some examples has been -grouped in common.hpp. The main function used in some async examples -has been factored out in the main.cpp file. +The main function used in some async examples has been factored out in +the main.cpp file. ## Echo server benchmark @@ -701,7 +674,7 @@ https://lists.boost.org/Archives/boost/2023/01/253944.php. ## Changelog -### master (incorporates changes to conform the boost review and more) +### develop (incorporates changes to conform the boost review and more) * Adds Redis stream example. diff --git a/examples/cpp20_subscriber.cpp b/examples/cpp20_subscriber.cpp index f7c1a4b3..69884705 100644 --- a/examples/cpp20_subscriber.cpp +++ b/examples/cpp20_subscriber.cpp @@ -52,6 +52,7 @@ receiver(std::shared_ptr conn) -> net::awaitable request req; req.push("SUBSCRIBE", "channel"); + // Loop while reconnection is enabled while (conn->will_reconnect()) { // Reconnect to channels. From b5f83485989973a359737ed846bddadf09b93d8d Mon Sep 17 00:00:00 2001 From: Cthulhu Date: Sat, 24 Jun 2023 01:28:03 +0330 Subject: [PATCH 27/32] build: add cmake options cmake options for install, tests, examples, and doc instead of building them always. options are enabled by default when building the project directly, otherwise if add_subdirectory (directly or by FetchContent, etc), then unnecessary options will be disabled. issue #115 --- CMakeLists.txt | 308 +++++++++++++++++++++++++------------------------ 1 file changed, 157 insertions(+), 151 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 511ee395..f0a921ae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,6 +2,15 @@ cmake_minimum_required(VERSION 3.14) #set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CMAKE_COMMAND} -E time") +# determine whether it's main/root project +# or being built under another project. +if (NOT DEFINED BOOST_REDIS_MAIN_PROJECT) + set(BOOST_REDIS_MAIN_PROJECT OFF) + if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) + set(BOOST_REDIS_MAIN_PROJECT ON) + endif() +endif() + project( boost_redis VERSION 1.4.1 @@ -10,6 +19,12 @@ project( LANGUAGES CXX ) +option(BOOST_REDIS_INSTALL "Generate install targets." ${BOOST_REDIS_MAIN_PROJECT}) +option(BOOST_REDIS_TESTS "Build tests." ${BOOST_REDIS_MAIN_PROJECT}) +option(BOOST_REDIS_EXAMPLES "Build examples." ${BOOST_REDIS_MAIN_PROJECT}) +option(BOOST_REDIS_BENCHMARKS "Build benchmarks." ${BOOST_REDIS_MAIN_PROJECT}) +option(BOOST_REDIS_DOC "Generate documentations." ${BOOST_REDIS_MAIN_PROJECT}) + add_library(boost_redis INTERFACE) add_library(Boost::redis ALIAS boost_redis) target_include_directories(boost_redis INTERFACE @@ -34,185 +49,181 @@ target_compile_features(boost_redis INTERFACE cxx_std_17) # Asio bases C++ feature detection on __cplusplus. Make MSVC # define it correctly if (MSVC) - target_compile_options(boost_redis INTERFACE /Zc:__cplusplus) + target_compile_options(boost_redis INTERFACE /Zc:__cplusplus) endif() -include(CMakePackageConfigHelpers) -write_basic_package_version_file( - "${PROJECT_BINARY_DIR}/BoostRedisConfigVersion.cmake" - COMPATIBILITY AnyNewerVersion -) - find_package(Boost 1.80 REQUIRED) -# We test the protobuf example only on gcc. -if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - find_package(Protobuf) # For the protobuf example. -endif() - include_directories(${Boost_INCLUDE_DIRS}) find_package(OpenSSL REQUIRED) -enable_testing() include_directories(include) +# Common #======================================================================= -set(libs_common boost_redis_src) -set(libs_cpp17 tests_common) -set(libs_cpp20 examples_main) - -foreach(lib IN LISTS libs_cpp20 libs_cpp17 libs_common) - add_library(${lib} STATIC) -endforeach() +add_library(boost_redis_project_options INTERFACE) +target_link_libraries(boost_redis_project_options INTERFACE OpenSSL::Crypto OpenSSL::SSL) +if (MSVC) + target_compile_options(boost_redis_project_options INTERFACE /bigobj) + target_compile_definitions(boost_redis_project_options INTERFACE _WIN32_WINNT=0x0601) +endif() -target_sources(boost_redis_src PRIVATE examples/boost_redis.cpp) -target_sources(tests_common PRIVATE tests/common.cpp) -target_sources(examples_main PRIVATE examples/main.cpp) +add_library(boost_redis_src STATIC examples/boost_redis.cpp) +target_compile_features(boost_redis_src PRIVATE cxx_std_17) +target_link_libraries(boost_redis_src PRIVATE boost_redis_project_options) # Executables #======================================================================= -set(benchmakrs echo_server_client echo_server_direct) -add_executable(echo_server_client benchmarks/cpp/asio/echo_server_client.cpp) -add_executable(echo_server_direct benchmarks/cpp/asio/echo_server_direct.cpp) - -set(tests_cpp17 - test_conn_quit - test_conn_tls - test_low_level - test_conn_exec_retry - test_conn_exec_error - test_request - test_run - test_low_level_sync - test_conn_check_health -) - -set(tests_cpp20 - test_conn_exec - test_conn_push - test_conn_reconnect - test_conn_exec_cancel - test_conn_exec_cancel2 - test_conn_echo_stress - test_low_level_async - test_conn_run_cancel - test_issue_50 -) +if (BOOST_REDIS_BENCHMARKS) + add_library(benchmarks_options INTERFACE) + target_link_libraries(benchmarks_options INTERFACE boost_redis_src) + target_link_libraries(benchmarks_options INTERFACE boost_redis_project_options) + target_compile_features(benchmarks_options INTERFACE cxx_std_20) -set(examples_cpp17 - cpp17_intro - cpp17_intro_sync) - -set(examples_cpp20 - cpp20_intro - cpp20_streams - cpp20_containers - cpp20_echo_server - cpp20_json - cpp20_subscriber - cpp20_intro_tls - cpp20_resolve_with_sentinel -) + add_executable(echo_server_client benchmarks/cpp/asio/echo_server_client.cpp) + target_link_libraries(echo_server_client PRIVATE benchmarks_options) -if (Protobuf_FOUND) - list(APPEND examples_cpp20 cpp20_protobuf) + add_executable(echo_server_direct benchmarks/cpp/asio/echo_server_direct.cpp) + target_link_libraries(echo_server_direct PRIVATE benchmarks_options) endif() -if (NOT MSVC) - list(APPEND examples_cpp20 cpp20_chat_room) +if (BOOST_REDIS_EXAMPLES) + add_library(examples_main STATIC examples/main.cpp) + target_compile_features(examples_main PRIVATE cxx_std_20) + target_link_libraries(examples_main PRIVATE boost_redis_project_options) + + macro(make_example EXAMPLE_NAME STANDARD) + add_executable(${EXAMPLE_NAME} examples/${EXAMPLE_NAME}.cpp) + target_link_libraries(${EXAMPLE_NAME} PRIVATE boost_redis_src) + target_link_libraries(${EXAMPLE_NAME} PRIVATE boost_redis_project_options) + target_compile_features(${EXAMPLE_NAME} PRIVATE cxx_std_${STANDARD}) + if (${STANDARD} STREQUAL "20") + target_link_libraries(${EXAMPLE_NAME} PRIVATE examples_main) + endif() + endmacro() + + macro(make_testable_example EXAMPLE_NAME STANDARD) + make_example(${EXAMPLE_NAME} ${STANDARD}) + add_test(${EXAMPLE_NAME} ${EXAMPLE_NAME}) + endmacro() + + make_testable_example(cpp17_intro 17) + make_testable_example(cpp17_intro_sync 17) + + make_testable_example(cpp20_intro 20) + make_testable_example(cpp20_containers 20) + make_testable_example(cpp20_json 20) + make_testable_example(cpp20_intro_tls 20) + + make_example(cpp20_subscriber 20) + make_example(cpp20_streams 20) + make_example(cpp20_echo_server 20) + make_example(cpp20_resolve_with_sentinel 20) + + # We test the protobuf example only on gcc. + if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + find_package(Protobuf) + if (Protobuf_FOUND) + protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS examples/person.proto) + make_testable_example(cpp20_protobuf 20) + target_sources(cpp20_protobuf PUBLIC ${PROTO_SRCS} ${PROTO_HDRS}) + target_link_libraries(cpp20_protobuf PRIVATE ${Protobuf_LIBRARIES}) + target_include_directories(cpp20_protobuf PUBLIC ${Protobuf_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR}) + endif() + endif() + + if (NOT MSVC) + make_example(cpp20_chat_room 20) + endif() endif() -foreach(exe IN LISTS examples_cpp17 examples_cpp20) - add_executable(${exe} examples/${exe}.cpp) -endforeach() - -foreach(exe IN LISTS examples_cpp20) - target_link_libraries(${exe} PRIVATE examples_main) -endforeach() - -foreach(exe IN LISTS tests_cpp17 tests_cpp20) - add_executable(${exe} tests/${exe}.cpp) - target_link_libraries(${exe} PRIVATE tests_common) -endforeach() - -foreach(exe IN LISTS tests_cpp17 tests_cpp20 examples_cpp17 examples_cpp20 libs_cpp17 libs_cpp20 libs_common benchmakrs) - target_link_libraries(${exe} PRIVATE OpenSSL::Crypto OpenSSL::SSL) - if (MSVC) - target_compile_options(${exe} PRIVATE /bigobj) - target_compile_definitions(${exe} PRIVATE _WIN32_WINNT=0x0601) - endif() -endforeach() - -foreach(exe IN LISTS tests_cpp17 tests_cpp20 examples_cpp17 examples_cpp20 libs_cpp17 libs_cpp20 benchmakrs) - target_link_libraries(${exe} PRIVATE boost_redis_src) -endforeach() - -foreach(exe IN LISTS tests_cpp20 examples_cpp20 libs_cpp20 benchmarks) - target_compile_features(${exe} PUBLIC cxx_std_20) -endforeach() - -foreach(exe IN LISTS tests_cpp17 examples_cpp17 libs_cpp17 libs_common) - target_compile_features(${exe} PUBLIC cxx_std_17) -endforeach() - -if (Protobuf_FOUND) - protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS examples/person.proto) - target_sources(cpp20_protobuf PUBLIC ${PROTO_SRCS} ${PROTO_HDRS}) - target_link_libraries(cpp20_protobuf PRIVATE ${Protobuf_LIBRARIES}) - target_include_directories(cpp20_protobuf PUBLIC ${Protobuf_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR}) - add_test(cpp20_protobuf cpp20_protobuf) +if (BOOST_REDIS_TESTS) + enable_testing() + + add_library(tests_common STATIC tests/common.cpp) + target_compile_features(tests_common PRIVATE cxx_std_17) + target_link_libraries(tests_common PRIVATE boost_redis_project_options) + + macro(make_test TEST_NAME STANDARD) + add_executable(${TEST_NAME} tests/${TEST_NAME}.cpp) + target_link_libraries(${TEST_NAME} PRIVATE boost_redis_src tests_common) + target_link_libraries(${TEST_NAME} PRIVATE boost_redis_project_options) + target_compile_features(${TEST_NAME} PRIVATE cxx_std_${STANDARD}) + add_test(${TEST_NAME} ${TEST_NAME}) + endmacro() + + make_test(test_conn_quit 17) + make_test(test_conn_tls 17) + make_test(test_low_level 17) + make_test(test_conn_exec_retry 17) + make_test(test_conn_exec_error 17) + make_test(test_request 17) + make_test(test_run 17) + make_test(test_low_level_sync 17) + make_test(test_conn_check_health 17) + + make_test(test_conn_exec 20) + make_test(test_conn_push 20) + make_test(test_conn_reconnect 20) + make_test(test_conn_exec_cancel 20) + make_test(test_conn_exec_cancel2 20) + make_test(test_conn_echo_stress 20) + make_test(test_low_level_async 20) + make_test(test_conn_run_cancel 20) + make_test(test_issue_50 20) endif() -add_test(cpp17_intro cpp17_intro) -add_test(cpp17_intro_sync cpp17_intro_sync) -add_test(cpp20_intro cpp20_intro) -add_test(cpp20_containers cpp20_containers) -add_test(cpp20_json cpp20_json) -add_test(cpp20_intro_tls cpp20_intro_tls) - -foreach(exe IN LISTS tests_cpp17 tests_cpp20) - add_test(${exe} ${exe}) -endforeach() - # Install #======================================================================= -install(TARGETS boost_redis - EXPORT boost_redis - PUBLIC_HEADER DESTINATION include COMPONENT Development -) - -include(CMakePackageConfigHelpers) - -configure_package_config_file( - "${PROJECT_SOURCE_DIR}/cmake/BoostRedisConfig.cmake.in" - "${PROJECT_BINARY_DIR}/BoostRedisConfig.cmake" - INSTALL_DESTINATION lib/cmake/boost/redis -) - -install(EXPORT boost_redis DESTINATION lib/cmake/boost/redis) -install(FILES "${PROJECT_BINARY_DIR}/BoostRedisConfigVersion.cmake" - "${PROJECT_BINARY_DIR}/BoostRedisConfig.cmake" - DESTINATION lib/cmake/boost/redis) +if (BOOST_REDIS_INSTALL) + install(TARGETS boost_redis + EXPORT boost_redis + PUBLIC_HEADER DESTINATION include COMPONENT Development + ) + + include(CMakePackageConfigHelpers) + + configure_package_config_file( + "${PROJECT_SOURCE_DIR}/cmake/BoostRedisConfig.cmake.in" + "${PROJECT_BINARY_DIR}/BoostRedisConfig.cmake" + INSTALL_DESTINATION lib/cmake/boost/redis + ) + + install(EXPORT boost_redis DESTINATION lib/cmake/boost/redis) + install(FILES "${PROJECT_BINARY_DIR}/BoostRedisConfigVersion.cmake" + "${PROJECT_BINARY_DIR}/BoostRedisConfig.cmake" + DESTINATION lib/cmake/boost/redis) + + install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/ DESTINATION include) + + include(CMakePackageConfigHelpers) + write_basic_package_version_file( + "${PROJECT_BINARY_DIR}/BoostRedisConfigVersion.cmake" + COMPATIBILITY AnyNewerVersion + ) -install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/ DESTINATION include) + include(CPack) +endif() # Doxygen #======================================================================= -set(DOXYGEN_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/doc") -configure_file(doc/Doxyfile.in doc/Doxyfile @ONLY) - -add_custom_target( - doc - COMMAND doxygen "${PROJECT_BINARY_DIR}/doc/Doxyfile" - COMMENT "Building documentation using Doxygen" - WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" - VERBATIM -) +if (BOOST_REDIS_DOC) + set(DOXYGEN_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/doc") + configure_file(doc/Doxyfile.in doc/Doxyfile @ONLY) + + add_custom_target( + doc + COMMAND doxygen "${PROJECT_BINARY_DIR}/doc/Doxyfile" + COMMENT "Building documentation using Doxygen" + WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" + VERBATIM + ) +endif() # Coverage #======================================================================= @@ -241,11 +252,6 @@ add_custom_target( VERBATIM ) -# Distribution -#======================================================================= - -include(CPack) - # TODO #======================================================================= From 9dec63515ec6c0bdedc65504d10a5f13c62ee892 Mon Sep 17 00:00:00 2001 From: Marcelo Zimbres Date: Sun, 9 Jul 2023 22:44:23 +0200 Subject: [PATCH 28/32] Simplifications in the parser. --- CMakeLists.txt | 1 + CMakePresets.json | 19 ++ include/boost/redis/connection.hpp | 44 ++- .../boost/redis/detail/connection_base.hpp | 100 +++---- include/boost/redis/detail/read.hpp | 127 +++++--- include/boost/redis/detail/read_ops.hpp | 100 ------- include/boost/redis/impl/connection.ipp | 14 +- include/boost/redis/resp3/impl/parser.ipp | 280 +++++++++++------- .../boost/redis/resp3/impl/serialization.ipp | 11 +- include/boost/redis/resp3/parser.hpp | 39 ++- include/boost/redis/resp3/serialization.hpp | 38 ++- tests/test_low_level.cpp | 11 +- tests/test_low_level_async.cpp | 12 +- tests/test_low_level_sync.cpp | 12 +- tests/test_low_level_sync_sans_io.cpp | 33 +++ 15 files changed, 476 insertions(+), 365 deletions(-) delete mode 100644 include/boost/redis/detail/read_ops.hpp create mode 100644 tests/test_low_level_sync_sans_io.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index f0a921ae..c9022131 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -163,6 +163,7 @@ if (BOOST_REDIS_TESTS) make_test(test_request 17) make_test(test_run 17) make_test(test_low_level_sync 17) + make_test(test_low_level_sync_sans_io 17) make_test(test_conn_check_health 17) make_test(test_conn_exec 20) diff --git a/CMakePresets.json b/CMakePresets.json index b6a11938..c68fbf34 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -56,6 +56,23 @@ "DOXYGEN_OUTPUT_DIRECTORY": "${sourceDir}/build/g++-11/doc/" } }, + { + "name": "g++-11-release", + "generator": "Unix Makefiles", + "hidden": false, + "inherits": ["cmake-pedantic"], + "binaryDir": "${sourceDir}/build/g++-11-release", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "CMAKE_CXX_EXTENSIONS": "OFF", + "CMAKE_CXX_FLAGS": "-Wall -Wextra", + "CMAKE_CXX_COMPILER": "g++-11", + "CMAKE_SHARED_LINKER_FLAGS": "", + "CMAKE_CXX_STANDARD_REQUIRED": "ON", + "PROJECT_BINARY_DIR": "${sourceDir}/build/g++-11-release", + "DOXYGEN_OUTPUT_DIRECTORY": "${sourceDir}/build/g++-11-release/doc/" + } + }, { "name": "clang++-13", "generator": "Unix Makefiles", @@ -124,6 +141,7 @@ "buildPresets": [ { "name": "coverage", "configurePreset": "coverage" }, { "name": "g++-11", "configurePreset": "g++-11" }, + { "name": "g++-11-release", "configurePreset": "g++-11-release" }, { "name": "clang++-13", "configurePreset": "clang++-13" }, { "name": "libc++-14-cpp17", "configurePreset": "libc++-14-cpp17" }, { "name": "libc++-14-cpp20", "configurePreset": "libc++-14-cpp20" }, @@ -138,6 +156,7 @@ }, { "name": "coverage", "configurePreset": "coverage", "inherits": ["test"] }, { "name": "g++-11", "configurePreset": "g++-11", "inherits": ["test"] }, + { "name": "g++-11-release", "configurePreset": "g++-11-release", "inherits": ["test"] }, { "name": "clang++-13", "configurePreset": "clang++-13", "inherits": ["test"] }, { "name": "libc++-14-cpp17", "configurePreset": "libc++-14-cpp17", "inherits": ["test"] }, { "name": "libc++-14-cpp20", "configurePreset": "libc++-14-cpp20", "inherits": ["test"] }, diff --git a/include/boost/redis/connection.hpp b/include/boost/redis/connection.hpp index ccce89d2..84190365 100644 --- a/include/boost/redis/connection.hpp +++ b/include/boost/redis/connection.hpp @@ -18,6 +18,7 @@ #include #include +#include namespace boost::redis { namespace detail @@ -87,15 +88,21 @@ class basic_connection { /// Contructs from an executor. explicit - basic_connection(executor_type ex, asio::ssl::context::method method = asio::ssl::context::tls_client) - : impl_{ex, method} + basic_connection( + executor_type ex, + asio::ssl::context::method method = asio::ssl::context::tls_client, + std::size_t max_read_size = (std::numeric_limits::max)()) + : impl_{ex, method, max_read_size} , timer_{ex} { } /// Contructs from a context. explicit - basic_connection(asio::io_context& ioc, asio::ssl::context::method method = asio::ssl::context::tls_client) - : basic_connection(ioc.get_executor(), method) + basic_connection( + asio::io_context& ioc, + asio::ssl::context::method method = asio::ssl::context::tls_client, + std::size_t max_read_size = (std::numeric_limits::max)()) + : basic_connection(ioc.get_executor(), method, max_read_size) { } /** @brief Starts underlying connection operations. @@ -255,23 +262,6 @@ class basic_connection { bool will_reconnect() const noexcept { return cfg_.reconnect_wait_interval != std::chrono::seconds::zero();} - /** @brief Reserve memory on the read and write internal buffers. - * - * This function will call `std::string::reserve` on the - * underlying buffers. - * - * @param read The new capacity of the read buffer. - * @param write The new capacity of the write buffer. - */ - void reserve(std::size_t read, std::size_t write) - { - impl_.reserve(read, write); - } - - /// Sets the maximum size of the read buffer. - void set_max_buffer_read_size(std::size_t max_read_size) noexcept - { impl_.set_max_buffer_read_size(max_read_size); } - /// Returns the ssl context. auto const& get_ssl_context() const noexcept { return impl_.get_ssl_context();} @@ -321,10 +311,18 @@ class connection { using executor_type = asio::any_io_executor; /// Contructs from an executor. - explicit connection(executor_type ex, asio::ssl::context::method method = asio::ssl::context::tls_client); + explicit + connection( + executor_type ex, + asio::ssl::context::method method = asio::ssl::context::tls_client, + std::size_t max_read_size = (std::numeric_limits::max)()); /// Contructs from a context. - explicit connection(asio::io_context& ioc, asio::ssl::context::method method = asio::ssl::context::tls_client); + explicit + connection( + asio::io_context& ioc, + asio::ssl::context::method method = asio::ssl::context::tls_client, + std::size_t max_read_size = (std::numeric_limits::max)()); /// Returns the underlying executor. executor_type get_executor() noexcept diff --git a/include/boost/redis/detail/connection_base.hpp b/include/boost/redis/detail/connection_base.hpp index f8681769..f2f5f35e 100644 --- a/include/boost/redis/detail/connection_base.hpp +++ b/include/boost/redis/detail/connection_base.hpp @@ -27,12 +27,13 @@ #include #include #include +#include +#include #include #include #include #include -#include #include #include #include @@ -112,10 +113,13 @@ class read_next_op { // some data in the read bufer. if (conn_->read_buffer_.empty()) { - if (conn_->use_ssl()) - BOOST_ASIO_CORO_YIELD asio::async_read_until(conn_->next_layer(), conn_->make_dynamic_buffer(), "\r\n", std::move(self)); - else - BOOST_ASIO_CORO_YIELD asio::async_read_until(conn_->next_layer().next_layer(), conn_->make_dynamic_buffer(), "\r\n", std::move(self)); + if (conn_->use_ssl()) { + BOOST_ASIO_CORO_YIELD + asio::async_read_until(conn_->next_layer(), conn_->dbuf_, resp3::parser::sep, std::move(self)); + } else { + BOOST_ASIO_CORO_YIELD + asio::async_read_until(conn_->next_layer().next_layer(), conn_->dbuf_, resp3::parser::sep, std::move(self)); + } BOOST_REDIS_CHECK_OP1(conn_->cancel(operation::run);); if (info_->stop_requested()) { @@ -134,10 +138,13 @@ class read_next_op { } //----------------------------------- - if (conn_->use_ssl()) - BOOST_ASIO_CORO_YIELD redis::detail::async_read(conn_->next_layer(), conn_->make_dynamic_buffer(), make_adapter(), std::move(self)); - else - BOOST_ASIO_CORO_YIELD redis::detail::async_read(conn_->next_layer().next_layer(), conn_->make_dynamic_buffer(), make_adapter(), std::move(self)); + if (conn_->use_ssl()) { + BOOST_ASIO_CORO_YIELD + redis::detail::async_read(conn_->next_layer(), conn_->dbuf_, make_adapter(), std::move(self)); + } else { + BOOST_ASIO_CORO_YIELD + redis::detail::async_read(conn_->next_layer().next_layer(), conn_->dbuf_, make_adapter(), std::move(self)); + } ++index_; @@ -147,6 +154,7 @@ class read_next_op { return; } + conn_->dbuf_.consume(n); read_size_ += n; BOOST_ASSERT(cmds_ != 0); @@ -162,7 +170,6 @@ template struct receive_op { Conn* conn_; Adapter adapter; - std::size_t read_size = 0; asio::coroutine coro{}; template @@ -182,10 +189,13 @@ struct receive_op { } } - if (conn_->use_ssl()) - BOOST_ASIO_CORO_YIELD redis::detail::async_read(conn_->next_layer(), conn_->make_dynamic_buffer(), adapter, std::move(self)); - else - BOOST_ASIO_CORO_YIELD redis::detail::async_read(conn_->next_layer().next_layer(), conn_->make_dynamic_buffer(), adapter, std::move(self)); + if (conn_->use_ssl()) { + BOOST_ASIO_CORO_YIELD + redis::detail::async_read(conn_->next_layer(), conn_->dbuf_, adapter, std::move(self)); + } else { + BOOST_ASIO_CORO_YIELD + redis::detail::async_read(conn_->next_layer().next_layer(), conn_->dbuf_, adapter, std::move(self)); + } if (ec || is_cancelled(self)) { conn_->cancel(operation::run); @@ -194,13 +204,13 @@ struct receive_op { return; } - read_size = n; + conn_->dbuf_.consume(n); if (!conn_->is_next_push()) { conn_->read_op_timer_.cancel(); } - self.complete({}, read_size); + self.complete({}, n); return; } } @@ -214,7 +224,6 @@ struct exec_op { request const* req = nullptr; Adapter adapter{}; std::shared_ptr info = nullptr; - std::size_t read_size = 0; asio::coroutine coro{}; template @@ -283,8 +292,6 @@ struct exec_op { conn->async_read_next(adapter, std::move(self)); BOOST_REDIS_CHECK_OP1(;); - read_size = n; - if (info->stop_requested()) { // Don't have to call remove_request as it has already // been by cancel(exec). @@ -301,7 +308,7 @@ struct exec_op { conn->read_timer_.cancel_one(); } - self.complete({}, read_size); + self.complete({}, n); } } }; @@ -417,9 +424,9 @@ struct reader_op { BOOST_ASIO_CORO_REENTER (coro) for (;;) { if (conn->use_ssl()) - BOOST_ASIO_CORO_YIELD asio::async_read_until(conn->next_layer(), conn->make_dynamic_buffer(), "\r\n", std::move(self)); + BOOST_ASIO_CORO_YIELD asio::async_read_until(conn->next_layer(), conn->dbuf_, "\r\n", std::move(self)); else - BOOST_ASIO_CORO_YIELD asio::async_read_until(conn->next_layer().next_layer(), conn->make_dynamic_buffer(), "\r\n", std::move(self)); + BOOST_ASIO_CORO_YIELD asio::async_read_until(conn->next_layer().next_layer(), conn->dbuf_, "\r\n", std::move(self)); if (ec == asio::error::eof) { conn->cancel(operation::run); @@ -491,25 +498,23 @@ class connection_base { using this_type = connection_base; /// Constructs from an executor. - connection_base(executor_type ex, asio::ssl::context::method method = asio::ssl::context::tls_client) + connection_base( + executor_type ex, + asio::ssl::context::method method, + std::size_t max_read_size) : ctx_{method} , stream_{std::make_unique(ex, ctx_)} , writer_timer_{ex} , read_timer_{ex} , read_op_timer_{ex} , runner_{ex, {}} + , dbuf_{read_buffer_, max_read_size} { writer_timer_.expires_at(std::chrono::steady_clock::time_point::max()); read_timer_.expires_at(std::chrono::steady_clock::time_point::max()); read_op_timer_.expires_at(std::chrono::steady_clock::time_point::max()); } - /// Contructs from an execution context. - explicit - connection_base(asio::io_context& ioc, asio::ssl::context::method method = asio::ssl::context::tls_client) - : connection_base(ioc.get_executor(), method) - { } - /// Returns the ssl context. auto const& get_ssl_context() const noexcept { return ctx_;} @@ -547,15 +552,8 @@ class connection_base { cancel_impl(op); } - template < - class Response = ignore_t, - class CompletionToken = asio::default_completion_token_t - > - auto - async_exec( - request const& req, - Response& resp = ignore, - CompletionToken token = CompletionToken{}) + template + auto async_exec(request const& req, Response& resp, CompletionToken token) { using namespace boost::redis::adapter; auto f = boost_redis_adapt(resp); @@ -567,14 +565,8 @@ class connection_base { >(redis::detail::exec_op{this, &req, f}, token, writer_timer_); } - template < - class Response = ignore_t, - class CompletionToken = asio::default_completion_token_t - > - auto - async_receive( - Response& response, - CompletionToken token = CompletionToken{}) + template + auto async_receive(Response& response, CompletionToken token) { using namespace boost::redis::adapter; auto g = boost_redis_adapt(response); @@ -594,15 +586,6 @@ class connection_base { return runner_.async_run(*this, l, std::move(token)); } - void set_max_buffer_read_size(std::size_t max_read_size) noexcept - {max_read_size_ = max_read_size;} - - void reserve(std::size_t read, std::size_t write) - { - read_buffer_.reserve(read); - write_buffer_.reserve(write); - } - private: using clock_type = std::chrono::steady_clock; using clock_traits_type = asio::wait_traits; @@ -839,9 +822,6 @@ class connection_base { writer_timer_.cancel(); } - auto make_dynamic_buffer() - { return asio::dynamic_buffer(read_buffer_, max_read_size_); } - template auto reader(CompletionToken&& token) { @@ -927,10 +907,12 @@ class connection_base { timer_type read_op_timer_; runner_type runner_; + using dyn_buffer_type = asio::dynamic_string_buffer, std::allocator>; + std::string read_buffer_; + dyn_buffer_type dbuf_; std::string write_buffer_; reqs_type reqs_; - std::size_t max_read_size_ = (std::numeric_limits::max)(); }; } // boost::redis::detail diff --git a/include/boost/redis/detail/read.hpp b/include/boost/redis/detail/read.hpp index 0dfad6bc..8cc72c01 100644 --- a/include/boost/redis/detail/read.hpp +++ b/include/boost/redis/detail/read.hpp @@ -9,14 +9,84 @@ #include #include -#include #include #include #include -#include +#include +#include + +#include +#include namespace boost::redis::detail { +template +std::string_view buffer_view(DynamicBuffer buf) noexcept +{ + char const* start = static_cast(buf.data(0, buf.size()).data()); + return std::string_view{start, std::size(buf)}; +} + +template < + class AsyncReadStream, + class DynamicBuffer, + class ResponseAdapter> +class parse_op { +private: + AsyncReadStream& stream_; + DynamicBuffer buf_; + resp3::parser parser_; + ResponseAdapter adapter_; + std::size_t tmp_ = 0; + resp3::parser::result res_; + asio::coroutine coro_{}; + + static std::size_t const growth = 1024; + +public: + parse_op(AsyncReadStream& stream, DynamicBuffer buf, ResponseAdapter adapter) + : stream_ {stream} + , buf_ {std::move(buf)} + , adapter_ {std::move(adapter)} + { } + + template + void operator()( Self& self + , system::error_code ec = {} + , std::size_t n = 0) + { + BOOST_ASIO_CORO_REENTER (coro_) for (;;) { + + res_ = parser_.consume(buffer_view(buf_), ec); + if (ec) + return self.complete(ec, 0); + + if (!res_.has_value()) { + tmp_ = buf_.size(); + buf_.grow(parser_.get_suggested_buffer_growth(growth)); + + BOOST_ASIO_CORO_YIELD + stream_.async_read_some( + buf_.data(tmp_, parser_.get_suggested_buffer_growth(growth)), + std::move(self)); + BOOST_REDIS_CHECK_OP1(;); + + buf_.shrink(buf_.size() - tmp_ - n); + continue; + } + + adapter_(res_.value(), ec); + if (ec) + return self.complete(ec, 0); + + if (parser_.done()) { + self.complete({}, parser_.get_consumed()); + return; + } + } + } +}; + /** \brief Reads a complete response to a command sychronously. * * This function reads a complete response to a command or a @@ -58,43 +128,34 @@ read( ResponseAdapter adapter, system::error_code& ec) -> std::size_t { - resp3::parser p; - std::size_t n = 0; - std::size_t consumed = 0; - do { - if (!p.bulk_expected()) { - n = asio::read_until(stream, buf, "\r\n", ec); - if (ec) - return 0; - - } else { - auto const s = buf.size(); - auto const l = p.bulk_length(); - if (s < (l + 2)) { - auto const to_read = l + 2 - s; - buf.grow(to_read); - n = asio::read(stream, buf.data(s, to_read), ec); - if (ec) - return 0; - } - } + static std::size_t const growth = 1024; - auto const* data = static_cast(buf.data(0, n).data()); - auto const res = p.consume(data, n, ec); + resp3::parser parser; + while (!parser.done()) { + auto const res = parser.consume(detail::buffer_view(buf), ec); if (ec) - return 0; + return 0UL; - if (!p.bulk_expected()) { - adapter(res.first, ec); + if (!res.has_value()) { + auto const size_before = buf.size(); + buf.grow(parser.get_suggested_buffer_growth(growth)); + auto const n = + stream.read_some( + buf.data(size_before, parser.get_suggested_buffer_growth(growth)), + ec); if (ec) - return 0; + return 0UL; + + buf.shrink(buf.size() - size_before - n); + continue; } - buf.consume(res.second); - consumed += res.second; - } while (!p.done()); + adapter(res.value(), ec); + if (ec) + return 0UL; + } - return consumed; + return parser.get_consumed(); } /** \brief Reads a complete response to a command sychronously. @@ -173,7 +234,7 @@ auto async_read( return asio::async_compose < CompletionToken , void(system::error_code, std::size_t) - >(detail::parse_op {stream, buffer, adapter}, + >(parse_op {stream, buffer, adapter}, token, stream); } diff --git a/include/boost/redis/detail/read_ops.hpp b/include/boost/redis/detail/read_ops.hpp deleted file mode 100644 index 70d1546f..00000000 --- a/include/boost/redis/detail/read_ops.hpp +++ /dev/null @@ -1,100 +0,0 @@ -/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com) - * - * Distributed under the Boost Software License, Version 1.0. (See - * accompanying file LICENSE.txt) - */ - -#ifndef BOOST_REDIS_READ_OPS_HPP -#define BOOST_REDIS_READ_OPS_HPP - -#include -#include -#include -#include -#include -#include -#include - -#include - -namespace boost::redis::detail -{ -template < - class AsyncReadStream, - class DynamicBuffer, - class ResponseAdapter> -class parse_op { -private: - AsyncReadStream& stream_; - DynamicBuffer buf_; - resp3::parser parser_; - ResponseAdapter adapter_; - std::size_t consumed_ = 0; - std::size_t buffer_size_ = 0; - asio::coroutine coro_{}; - -public: - parse_op(AsyncReadStream& stream, DynamicBuffer buf, ResponseAdapter adapter) - : stream_ {stream} - , buf_ {std::move(buf)} - , adapter_ {std::move(adapter)} - { } - - template - void operator()( Self& self - , system::error_code ec = {} - , std::size_t n = 0) - { - BOOST_ASIO_CORO_REENTER (coro_) for (;;) { - if (!parser_.bulk_expected()) { - BOOST_ASIO_CORO_YIELD - asio::async_read_until(stream_, buf_, "\r\n", std::move(self)); - BOOST_REDIS_CHECK_OP1(;); - } else { - // On a bulk read we can't read until delimiter since the - // payload may contain the delimiter itself so we have to - // read the whole chunk. However if the bulk blob is small - // enough it may be already on the buffer (from the last - // read), in which case there is no need of initiating - // another async op, otherwise we have to read the missing - // bytes. - if (buf_.size() < (parser_.bulk_length() + 2)) { - buffer_size_ = buf_.size(); - buf_.grow(parser_.bulk_length() + 2 - buffer_size_); - - BOOST_ASIO_CORO_YIELD - asio::async_read( - stream_, - buf_.data(buffer_size_, parser_.bulk_length() + 2 - buffer_size_), - asio::transfer_all(), - std::move(self)); - BOOST_REDIS_CHECK_OP1(;); - } - - n = parser_.bulk_length() + 2; - BOOST_ASSERT(buf_.size() >= n); - } - - auto const res = parser_.consume(static_cast(buf_.data(0, n).data()), n, ec); - if (ec) - return self.complete(ec, 0); - - if (!parser_.bulk_expected()) { - adapter_(res.first, ec); - if (ec) - return self.complete(ec, 0); - } - - buf_.consume(res.second); - consumed_ += res.second; - if (parser_.done()) { - self.complete({}, consumed_); - return; - } - } - } -}; - -} // boost::redis::detail - -#endif // BOOST_REDIS_READ_OPS_HPP diff --git a/include/boost/redis/impl/connection.ipp b/include/boost/redis/impl/connection.ipp index 977031a5..9c83c145 100644 --- a/include/boost/redis/impl/connection.ipp +++ b/include/boost/redis/impl/connection.ipp @@ -8,12 +8,18 @@ namespace boost::redis { -connection::connection(executor_type ex, asio::ssl::context::method method) -: impl_{ex, method} +connection::connection( + executor_type ex, + asio::ssl::context::method method, + std::size_t max_read_size) +: impl_{ex, method, max_read_size} { } -connection::connection(asio::io_context& ioc, asio::ssl::context::method method) -: impl_(ioc.get_executor(), method) +connection::connection( + asio::io_context& ioc, + asio::ssl::context::method method, + std::size_t max_read_size) +: impl_{ioc.get_executor(), method, max_read_size} { } void diff --git a/include/boost/redis/resp3/impl/parser.ipp b/include/boost/redis/resp3/impl/parser.ipp index cdaf3e65..89cae23c 100644 --- a/include/boost/redis/resp3/impl/parser.ipp +++ b/include/boost/redis/resp3/impl/parser.ipp @@ -24,132 +24,186 @@ parser::parser() sizes_[0] = 2; // The sentinel must be more than 1. } -auto -parser::consume( - char const* data, - std::size_t n, - system::error_code& ec) -> std::pair +std::size_t +parser::get_suggested_buffer_growth(std::size_t hint) const noexcept { - node_type ret; - if (bulk_expected()) { - n = bulk_length_ + 2; - ret = {bulk_, 1, depth_, {data, bulk_length_}}; - bulk_ = type::invalid; + if (!bulk_expected()) + return hint; + + if (hint < bulk_length_ + 2) + return bulk_length_ + 2; + + return hint; +} + +std::size_t +parser::get_consumed() const noexcept +{ + return consumed_; +} + +bool +parser::done() const noexcept +{ + return depth_ == 0 && bulk_ == type::invalid && consumed_ != 0; +} + +void +parser::commit_elem() noexcept +{ + --sizes_[depth_]; + while (sizes_[depth_] == 0) { + --depth_; --sizes_[depth_]; + } +} - } else if (sizes_[depth_] != 0) { - auto const t = to_type(*data); - switch (t) { - case type::streamed_string_part: - { - to_int(bulk_length_ , std::string_view{data + 1, n - 3}, ec); - if (ec) - return std::make_pair(node_type{}, 0); - - if (bulk_length_ == 0) { - ret = {type::streamed_string_part, 1, depth_, {}}; - sizes_[depth_] = 0; // We are done. - bulk_ = type::invalid; - } else { - bulk_ = type::streamed_string_part; - } - } break; - case type::blob_error: - case type::verbatim_string: - case type::blob_string: - { - if (data[1] == '?') { - // NOTE: This can only be triggered with blob_string. - // Trick: A streamed string is read as an aggregate - // of infinite lenght. When the streaming is done - // the server is supposed to send a part with length - // 0. - sizes_[++depth_] = (std::numeric_limits::max)(); - ret = {type::streamed_string, 0, depth_, {}}; - } else { - to_int(bulk_length_ , std::string_view{data + 1, n - 3} , ec); - if (ec) - return std::make_pair(node_type{}, 0); - - bulk_ = t; - } - } break; - case type::boolean: - { - if (n == 3) { - ec = error::empty_field; - return std::make_pair(node_type{}, 0); - } +auto +parser::consume(std::string_view view, system::error_code& ec) noexcept -> parser::result +{ + switch (bulk_) { + case type::invalid: + { + auto const pos = view.find(sep, consumed_); + if (pos == std::string::npos) + return {}; // Needs more data to proceeed. - if (data[1] != 'f' && data[1] != 't') { - ec = error::unexpected_bool_value; - return std::make_pair(node_type{}, 0); - } + auto const t = to_type(view.at(consumed_)); + auto const content = view.substr(consumed_ + 1, pos - 1 - consumed_); + auto const ret = consume_impl(t, content, ec); + if (ec) + return {}; - ret = {t, 1, depth_, {data + 1, n - 3}}; - --sizes_[depth_]; - } break; - case type::doublean: - case type::big_number: - case type::number: - { - if (n == 3) { - ec = error::empty_field; - return std::make_pair(node_type{}, 0); - } + consumed_ = pos + 2; + if (!bulk_expected()) + return ret; - ret = {t, 1, depth_, {data + 1, n - 3}}; - --sizes_[depth_]; - } break; - case type::simple_error: - case type::simple_string: - { - ret = {t, 1, depth_, {&data[1], n - 3}}; - --sizes_[depth_]; - } break; - case type::null: - { - ret = {type::null, 1, depth_, {}}; - --sizes_[depth_]; - } break; - case type::push: - case type::set: - case type::array: - case type::attribute: - case type::map: - { - int_type l = -1; - to_int(l, std::string_view{data + 1, n - 3}, ec); + } [[fallthrough]]; + + default: // Handles bulk. + { + auto const span = bulk_length_ + 2; + if ((std::size(view) - consumed_) < span) + return {}; // Needs more data to proceeed. + + auto const bulk_view = view.substr(consumed_, bulk_length_); + node_type const ret = {bulk_, 1, depth_, bulk_view}; + bulk_ = type::invalid; + commit_elem(); + + consumed_ += span; + return ret; + } + } +} + +auto +parser::consume_impl( + type t, + std::string_view elem, + system::error_code& ec) -> parser::node_type +{ + BOOST_ASSERT(!bulk_expected()); + + node_type ret; + switch (t) { + case type::streamed_string_part: + { + to_int(bulk_length_ , elem, ec); + if (ec) + return {}; + + if (bulk_length_ == 0) { + ret = {type::streamed_string_part, 1, depth_, {}}; + sizes_[depth_] = 1; // We are done. + bulk_ = type::invalid; + commit_elem(); + } else { + bulk_ = type::streamed_string_part; + } + } break; + case type::blob_error: + case type::verbatim_string: + case type::blob_string: + { + if (elem.at(0) == '?') { + // NOTE: This can only be triggered with blob_string. + // Trick: A streamed string is read as an aggregate of + // infinite length. When the streaming is done the server + // is supposed to send a part with length 0. + sizes_[++depth_] = (std::numeric_limits::max)(); + ret = {type::streamed_string, 0, depth_, {}}; + } else { + to_int(bulk_length_ , elem , ec); if (ec) - return std::make_pair(node_type{}, 0); + return {}; - ret = {t, l, depth_, {}}; - if (l == 0) { - --sizes_[depth_]; - } else { - if (depth_ == max_embedded_depth) { - ec = error::exceeeds_max_nested_depth; - return std::make_pair(node_type{}, 0); - } + bulk_ = t; + } + } break; + case type::boolean: + { + if (std::empty(elem)) { + ec = error::empty_field; + return {}; + } + + if (elem.at(0) != 'f' && elem.at(0) != 't') { + ec = error::unexpected_bool_value; + return {}; + } - ++depth_; + ret = {t, 1, depth_, elem}; + commit_elem(); + } break; + case type::doublean: + case type::big_number: + case type::number: + { + if (std::empty(elem)) { + ec = error::empty_field; + return {}; + } + } [[fallthrough]]; + case type::simple_error: + case type::simple_string: + case type::null: + { + ret = {t, 1, depth_, elem}; + commit_elem(); + } break; + case type::push: + case type::set: + case type::array: + case type::attribute: + case type::map: + { + int_type l = -1; + to_int(l, elem, ec); + if (ec) + return {}; - sizes_[depth_] = l * element_multiplicity(t); + ret = {t, l, depth_, {}}; + if (l == 0) { + commit_elem(); + } else { + if (depth_ == max_embedded_depth) { + ec = error::exceeeds_max_nested_depth; + return {}; } - } break; - default: - { - ec = error::invalid_data_type; - return std::make_pair(node_type{}, 0); + + ++depth_; + + sizes_[depth_] = l * element_multiplicity(t); } + } break; + default: + { + ec = error::invalid_data_type; + return {}; } } - - while (sizes_[depth_] == 0) { - --depth_; - --sizes_[depth_]; - } - - return std::make_pair(ret, n); + + return ret; } } // boost::redis::resp3 diff --git a/include/boost/redis/resp3/impl/serialization.ipp b/include/boost/redis/resp3/impl/serialization.ipp index 5fcbb77f..3af8de4c 100644 --- a/include/boost/redis/resp3/impl/serialization.ipp +++ b/include/boost/redis/resp3/impl/serialization.ipp @@ -5,6 +5,7 @@ */ #include +#include namespace boost::redis::resp3 { @@ -14,9 +15,9 @@ void boost_redis_to_bulk(std::string& payload, std::string_view data) payload += to_code(type::blob_string); payload.append(std::cbegin(str), std::cend(str)); - payload += separator; + payload += parser::sep; payload.append(std::cbegin(data), std::cend(data)); - payload += separator; + payload += parser::sep; } void add_header(std::string& payload, type t, std::size_t size) @@ -25,17 +26,17 @@ void add_header(std::string& payload, type t, std::size_t size) payload += to_code(t); payload.append(std::cbegin(str), std::cend(str)); - payload += separator; + payload += parser::sep; } void add_blob(std::string& payload, std::string_view blob) { payload.append(std::cbegin(blob), std::cend(blob)); - payload += separator; + payload += parser::sep; } void add_separator(std::string& payload) { - payload += separator; + payload += parser::sep; } } // boost::redis::resp3 diff --git a/include/boost/redis/resp3/parser.hpp b/include/boost/redis/resp3/parser.hpp index 5db0b81c..7d8d925a 100644 --- a/include/boost/redis/resp3/parser.hpp +++ b/include/boost/redis/resp3/parser.hpp @@ -13,16 +13,21 @@ #include #include #include +#include namespace boost::redis::resp3 { using int_type = std::uint64_t; class parser { -private: +public: using node_type = basic_node; + using result = std::optional; + static constexpr std::size_t max_embedded_depth = 5; + static constexpr std::string_view sep = "\r\n"; +private: // The current depth. Simple data types will have depth 0, whereas // the elements of aggregates will have depth 1. Embedded types // will have increasing depth. @@ -40,28 +45,32 @@ class parser { // expected. type bulk_ = type::invalid; -public: - parser(); + // The number of bytes consumed from the buffer. + std::size_t consumed_ = 0; // Returns the number of bytes that have been consumed. - auto - consume( - char const* data, - std::size_t n, - system::error_code& ec) -> std::pair; + auto consume_impl(type t, std::string_view elem, system::error_code& ec) -> node_type; - // Returns true when the parser is done with the current message. - [[nodiscard]] auto done() const noexcept - { return depth_ == 0 && bulk_ == type::invalid; } + void commit_elem() noexcept; // The bulk type expected in the next read. If none is expected // returns type::invalid. - [[nodiscard]] auto bulk_expected() const noexcept -> bool + [[nodiscard]] + auto bulk_expected() const noexcept -> bool { return bulk_ != type::invalid; } - // The length expected in the the next bulk. - [[nodiscard]] auto bulk_length() const noexcept - { return bulk_length_; } +public: + parser(); + + // Returns true when the parser is done with the current message. + [[nodiscard]] + auto done() const noexcept -> bool; + + auto get_suggested_buffer_growth(std::size_t hint) const noexcept -> std::size_t; + + auto get_consumed() const noexcept -> std::size_t; + + auto consume(std::string_view view, system::error_code& ec) noexcept -> result; }; } // boost::redis::resp3 diff --git a/include/boost/redis/resp3/serialization.hpp b/include/boost/redis/resp3/serialization.hpp index 5d36db3f..38ec138f 100644 --- a/include/boost/redis/resp3/serialization.hpp +++ b/include/boost/redis/resp3/serialization.hpp @@ -8,6 +8,9 @@ #define BOOST_REDIS_RESP3_SERIALIZATION_HPP #include +#include +#include +#include #include #include @@ -16,7 +19,6 @@ // to calculate the header size correctly. namespace boost::redis::resp3 { -constexpr char const* separator = "\r\n"; /** @brief Adds a bulk to the request. * @relates boost::redis::request @@ -103,6 +105,40 @@ struct bulk_counter> { void add_blob(std::string& payload, std::string_view blob); void add_separator(std::string& payload); +namespace detail +{ + +template +void deserialize(std::string_view const& data, Adapter adapter, system::error_code& ec) +{ + parser parser; + while (!parser.done()) { + auto const res = parser.consume(data, ec); + if (ec) + return; + + BOOST_ASSERT(res.has_value()); + + adapter(res.value(), ec); + if (ec) + return; + } + + BOOST_ASSERT(parser.get_consumed() == std::size(data)); +} + +template +void deserialize(std::string_view const& data, Adapter adapter) +{ + system::error_code ec; + deserialize(data, adapter, ec); + + if (ec) + BOOST_THROW_EXCEPTION(system::system_error{ec}); +} + +} + } // boost::redis::resp3 #endif // BOOST_REDIS_RESP3_SERIALIZATION_HPP diff --git a/tests/test_low_level.cpp b/tests/test_low_level.cpp index 059d2e6f..0fb5b2e3 100644 --- a/tests/test_low_level.cpp +++ b/tests/test_low_level.cpp @@ -100,12 +100,15 @@ void test_sync(net::any_io_executor ex, expect e) ts.append(e.in); Result result; boost::system::error_code ec; - redis::detail::read(ts, net::dynamic_buffer(rbuffer), adapt2(result), ec); + auto dbuf = net::dynamic_buffer(rbuffer); + auto const consumed = redis::detail::read(ts, dbuf, adapt2(result), ec); if (e.ec) { BOOST_CHECK_EQUAL(ec, e.ec); return; } + dbuf.consume(consumed); + BOOST_TEST(!ec); BOOST_TEST(rbuffer.empty()); @@ -145,7 +148,7 @@ class async_test: public std::enable_shared_from_this> { } BOOST_TEST(!ec); - BOOST_TEST(self->rbuffer_.empty()); + //BOOST_TEST(self->rbuffer_.empty()); if (self->result_.has_value()) { auto const res = self->result_ == self->data_.expected; @@ -558,9 +561,9 @@ BOOST_AUTO_TEST_CASE(ignore_adapter_no_error) test_stream ts {ioc}; ts.append(S05b); - redis::detail::read(ts, net::dynamic_buffer(rbuffer), adapt2(ignore), ec); + auto const consumed = redis::detail::read(ts, net::dynamic_buffer(rbuffer), adapt2(ignore), ec); BOOST_TEST(!ec); - BOOST_TEST(rbuffer.empty()); + BOOST_CHECK_EQUAL(rbuffer.size(), consumed); } //----------------------------------------------------------------------------------- diff --git a/tests/test_low_level_async.cpp b/tests/test_low_level_async.cpp index 01f419da..d05345fb 100644 --- a/tests/test_low_level_async.cpp +++ b/tests/test_low_level_async.cpp @@ -49,11 +49,15 @@ auto co_main(config cfg) -> net::awaitable std::string buffer; result resp; + std::size_t consumed = 0; // Reads the responses to all commands in the request. - auto dbuffer = net::dynamic_buffer(buffer); - co_await redis::detail::async_read(socket, dbuffer); - co_await redis::detail::async_read(socket, dbuffer, adapt2(resp)); - co_await redis::detail::async_read(socket, dbuffer); + auto dbuf = net::dynamic_buffer(buffer); + consumed = co_await redis::detail::async_read(socket, dbuf); + dbuf.consume(consumed); + consumed = co_await redis::detail::async_read(socket, dbuf, adapt2(resp)); + dbuf.consume(consumed); + consumed = co_await redis::detail::async_read(socket, dbuf); + dbuf.consume(consumed); std::cout << "Ping: " << resp.value() << std::endl; } diff --git a/tests/test_low_level_sync.cpp b/tests/test_low_level_sync.cpp index 1abfe652..2349fbee 100644 --- a/tests/test_low_level_sync.cpp +++ b/tests/test_low_level_sync.cpp @@ -42,11 +42,15 @@ BOOST_AUTO_TEST_CASE(low_level_sync) std::string buffer; result resp; + std::size_t consumed = 0; // Reads the responses to all commands in the request. - auto dbuffer = net::dynamic_buffer(buffer); - redis::detail::read(socket, dbuffer); - redis::detail::read(socket, dbuffer, adapt2(resp)); - redis::detail::read(socket, dbuffer); + auto dbuf = net::dynamic_buffer(buffer); + consumed = redis::detail::read(socket, dbuf); + dbuf.consume(consumed); + consumed = redis::detail::read(socket, dbuf, adapt2(resp)); + dbuf.consume(consumed); + consumed = redis::detail::read(socket, dbuf); + dbuf.consume(consumed); std::cout << "Ping: " << resp.value() << std::endl; diff --git a/tests/test_low_level_sync_sans_io.cpp b/tests/test_low_level_sync_sans_io.cpp new file mode 100644 index 00000000..441f8098 --- /dev/null +++ b/tests/test_low_level_sync_sans_io.cpp @@ -0,0 +1,33 @@ +/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com) + * + * Distributed under the Boost Software License, Version 1.0. (See + * accompanying file LICENSE.txt) + */ + +#include +#include +#define BOOST_TEST_MODULE conn-quit +#include +#include +#include + +using boost::redis::adapter::adapt2; +using boost::redis::adapter::result; +using boost::redis::resp3::detail::deserialize; + +BOOST_AUTO_TEST_CASE(low_level_sync_sans_io) +{ + try { + result> resp; + + char const* wire = "~6\r\n+orange\r\n+apple\r\n+one\r\n+two\r\n+three\r\n+orange\r\n"; + deserialize(wire, adapt2(resp)); + + for (auto const& e: resp.value()) + std::cout << e << std::endl; + + } catch (std::exception const& e) { + std::cerr << e.what() << std::endl; + exit(EXIT_FAILURE); + } +} From 9ebcc544ae27e6a0900ae97981db97ffd4c014d8 Mon Sep 17 00:00:00 2001 From: Casey Bodley Date: Wed, 2 Aug 2023 11:44:58 -0400 Subject: [PATCH 29/32] connection: async_exec forwards completion token async operations should support move-only completion handlers. forward the CompletionToken argument to avoid an unnecessary copy Fixes: #131 Signed-off-by: Casey Bodley --- include/boost/redis/connection.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/boost/redis/connection.hpp b/include/boost/redis/connection.hpp index 84190365..a3d573ab 100644 --- a/include/boost/redis/connection.hpp +++ b/include/boost/redis/connection.hpp @@ -227,9 +227,9 @@ class basic_connection { async_exec( request const& req, Response& resp = ignore, - CompletionToken token = CompletionToken{}) + CompletionToken&& token = CompletionToken{}) { - return impl_.async_exec(req, resp, token); + return impl_.async_exec(req, resp, std::forward(token)); } /** @brief Cancel operations. From 91014b13bfadd96a5e2b08974b1bba8e90884e2c Mon Sep 17 00:00:00 2001 From: Marcelo Zimbres Date: Sat, 5 Aug 2023 22:20:39 +0200 Subject: [PATCH 30/32] Simplifies parse ops and fixes health-check on reconnection. --- include/boost/redis/detail/health_checker.hpp | 1 + include/boost/redis/detail/read.hpp | 101 +++++++++++++----- include/boost/redis/resp3/parser.hpp | 24 +++++ 3 files changed, 99 insertions(+), 27 deletions(-) diff --git a/include/boost/redis/detail/health_checker.hpp b/include/boost/redis/detail/health_checker.hpp index 2e99dfa3..dcf0292d 100644 --- a/include/boost/redis/detail/health_checker.hpp +++ b/include/boost/redis/detail/health_checker.hpp @@ -167,6 +167,7 @@ class health_checker { > auto async_check_health(Connection& conn, CompletionToken token = CompletionToken{}) { + checker_has_exited_ = false; return asio::async_compose < CompletionToken , void(system::error_code) diff --git a/include/boost/redis/detail/read.hpp b/include/boost/redis/detail/read.hpp index 8cc72c01..9a74a498 100644 --- a/include/boost/redis/detail/read.hpp +++ b/include/boost/redis/detail/read.hpp @@ -10,10 +10,11 @@ #include #include #include +#include #include #include #include -#include +#include #include #include @@ -27,6 +28,59 @@ std::string_view buffer_view(DynamicBuffer buf) noexcept return std::string_view{start, std::size(buf)}; } +template +class append_some_op { +private: + AsyncReadStream& stream_; + DynamicBuffer buf_; + std::size_t size_ = 0; + std::size_t tmp_ = 0; + asio::coroutine coro_{}; + +public: + append_some_op(AsyncReadStream& stream, DynamicBuffer buf, std::size_t size) + : stream_ {stream} + , buf_ {std::move(buf)} + , size_{size} + { } + + template + void operator()( Self& self + , system::error_code ec = {} + , std::size_t n = 0) + { + BOOST_ASIO_CORO_REENTER (coro_) + { + tmp_ = buf_.size(); + buf_.grow(size_); + + BOOST_ASIO_CORO_YIELD + stream_.async_read_some(buf_.data(tmp_, size_), std::move(self)); + if (ec) { + self.complete(ec, 0); + return; + } + + buf_.shrink(buf_.size() - tmp_ - n); + self.complete({}, n); + } + } +}; + +template +auto +async_append_some( + AsyncReadStream& stream, + DynamicBuffer buffer, + std::size_t size, + CompletionToken&& token) +{ + return asio::async_compose + < CompletionToken + , void(system::error_code, std::size_t) + >(append_some_op {stream, buffer, size}, token, stream); +} + template < class AsyncReadStream, class DynamicBuffer, @@ -37,8 +91,8 @@ class parse_op { DynamicBuffer buf_; resp3::parser parser_; ResponseAdapter adapter_; - std::size_t tmp_ = 0; - resp3::parser::result res_; + bool needs_rescheduling_ = true; + system::error_code ec_; asio::coroutine coro_{}; static std::size_t const growth = 1024; @@ -53,36 +107,29 @@ class parse_op { template void operator()( Self& self , system::error_code ec = {} - , std::size_t n = 0) + , std::size_t = 0) { - BOOST_ASIO_CORO_REENTER (coro_) for (;;) { - - res_ = parser_.consume(buffer_view(buf_), ec); - if (ec) - return self.complete(ec, 0); - - if (!res_.has_value()) { - tmp_ = buf_.size(); - buf_.grow(parser_.get_suggested_buffer_growth(growth)); - + BOOST_ASIO_CORO_REENTER (coro_) + { + while (!resp3::parse(parser_, buffer_view(buf_), adapter_, ec)) { + needs_rescheduling_ = false; BOOST_ASIO_CORO_YIELD - stream_.async_read_some( - buf_.data(tmp_, parser_.get_suggested_buffer_growth(growth)), + async_append_some( + stream_, buf_, parser_.get_suggested_buffer_growth(growth), std::move(self)); - BOOST_REDIS_CHECK_OP1(;); - - buf_.shrink(buf_.size() - tmp_ - n); - continue; + if (ec) { + self.complete(ec, 0); + return; + } } - adapter_(res_.value(), ec); - if (ec) - return self.complete(ec, 0); - - if (parser_.done()) { - self.complete({}, parser_.get_consumed()); - return; + ec_ = ec; + if (needs_rescheduling_) { + BOOST_ASIO_CORO_YIELD + asio::post(std::move(self)); } + + self.complete(ec_, parser_.get_consumed()); } } }; diff --git a/include/boost/redis/resp3/parser.hpp b/include/boost/redis/resp3/parser.hpp index 7d8d925a..a66ebbae 100644 --- a/include/boost/redis/resp3/parser.hpp +++ b/include/boost/redis/resp3/parser.hpp @@ -73,6 +73,30 @@ class parser { auto consume(std::string_view view, system::error_code& ec) noexcept -> result; }; +template +bool +parse( + resp3::parser& p, + std::string_view const& msg, + Adapter& adapter, + system::error_code& ec) +{ + while (!p.done()) { + auto const res = p.consume(msg, ec); + if (ec) + return true; + + if (!res) + return false; + + adapter(res.value(), ec); + if (ec) + return true; + } + + return true; +} + } // boost::redis::resp3 #endif // BOOST_REDIS_RESP3_PARSER_HPP From 10603b7d3a057573943eb173cf826bf5e20566f4 Mon Sep 17 00:00:00 2001 From: Marcelo Zimbres Date: Sun, 6 Aug 2023 09:58:18 +0200 Subject: [PATCH 31/32] Sends SELECT right after HELLO after a connection. --- README.md | 9 +++++++ include/boost/redis/config.hpp | 4 +++ include/boost/redis/detail/runner.hpp | 3 +++ tests/test_conn_exec.cpp | 37 +++++++++++++++++++++++++++ 4 files changed, 53 insertions(+) diff --git a/README.md b/README.md index 6ab6f6ec..cf1d4d59 100644 --- a/README.md +++ b/README.md @@ -676,6 +676,15 @@ https://lists.boost.org/Archives/boost/2023/01/253944.php. ### develop (incorporates changes to conform the boost review and more) +* Adds `boost::redis::config::database_index` to make it possible to + choose a database before starting running commands e.g. after an + automatic reconnection. + +* Massive performance improvement. One of my tests went from + 140k req/s to 390k/s. This was possible after a parser + simplification that reduced the number of reschedules and buffer + rotations. + * Adds Redis stream example. * Renames the project to Boost.Redis and moves the code into namespace diff --git a/include/boost/redis/config.hpp b/include/boost/redis/config.hpp index 01e4caa6..f2b1c8e8 100644 --- a/include/boost/redis/config.hpp +++ b/include/boost/redis/config.hpp @@ -9,6 +9,7 @@ #include #include +#include namespace boost::redis { @@ -48,6 +49,9 @@ struct config { /// Client name parameter of the [HELLO](https://redis.io/commands/hello/) command. std::string clientname = "Boost.Redis"; + /// Database that will be passed to the [SELECT](https://redis.io/commands/hello/) command. + std::optional database_index = 0; + /// Message used by the health-checker in `boost::redis::connection::async_run`. std::string health_check_id = "Boost.Redis"; diff --git a/include/boost/redis/detail/runner.hpp b/include/boost/redis/detail/runner.hpp index 8142edd4..e728b090 100644 --- a/include/boost/redis/detail/runner.hpp +++ b/include/boost/redis/detail/runner.hpp @@ -231,6 +231,9 @@ class runner { hello_req_.push("HELLO", "3", "AUTH", cfg_.username, cfg_.password); else hello_req_.push("HELLO", "3", "SETNAME", cfg_.clientname); + + if (cfg_.database_index) + hello_req_.push("SELECT", cfg_.database_index.value()); } resolver_type resv_; diff --git a/tests/test_conn_exec.cpp b/tests/test_conn_exec.cpp index 73be44cb..bd5bc5ed 100644 --- a/tests/test_conn_exec.cpp +++ b/tests/test_conn_exec.cpp @@ -20,8 +20,10 @@ namespace net = boost::asio; using boost::redis::connection; using boost::redis::request; using boost::redis::response; +using boost::redis::generic_response; using boost::redis::ignore; using boost::redis::operation; +using boost::redis::config; // Sends three requests where one of them has a hello with a priority // set, which means it should be executed first. @@ -117,3 +119,38 @@ BOOST_AUTO_TEST_CASE(cancel_request_if_not_connected) ioc.run(); } + +BOOST_AUTO_TEST_CASE(correct_database) +{ + config cfg; + cfg.database_index = 2; + + net::io_context ioc; + + auto conn = std::make_shared(ioc); + + request req; + req.push("CLIENT", "LIST"); + + generic_response resp; + + conn->async_exec(req, resp, [&](auto ec, auto n){ + BOOST_TEST(!ec); + std::clog << "async_exec has completed: " << n << std::endl; + conn->cancel(); + }); + + conn->async_run(cfg, {}, [](auto){ + std::clog << "async_run has exited." << std::endl; + }); + + ioc.run(); + + assert(!resp.value().empty()); + auto const& value = resp.value().front().value; + auto const pos = value.find("db="); + auto const index_str = value.substr(pos + 3, 1); + auto const index = std::stoi(index_str); + BOOST_CHECK_EQUAL(cfg.database_index.value(), index); +} + From 34ff1cea637b05d98978d5cf624279156d63a02c Mon Sep 17 00:00:00 2001 From: Marcelo Zimbres Date: Sun, 6 Aug 2023 10:02:33 +0200 Subject: [PATCH 32/32] Fixes https://github.com/boostorg/redis/issues/121 --- examples/cpp20_streams.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/cpp20_streams.cpp b/examples/cpp20_streams.cpp index f0939756..ae1d0206 100644 --- a/examples/cpp20_streams.cpp +++ b/examples/cpp20_streams.cpp @@ -41,11 +41,11 @@ auto stream_reader(std::shared_ptr conn) -> net::awaitable req.push("XREAD", "BLOCK", "0", "STREAMS", "test-topic", stream_id); co_await conn->async_exec(req, resp, net::deferred); - // std::cout << "Response: "; - // for (int i = 0; i < resp->value().size(); ++i) { - // std::cout << resp->value().at(i).value << ", "; - // } - // std::cout << std::endl; + //std::cout << "Response: "; + //for (auto i = 0UL; i < resp->size(); ++i) { + // std::cout << resp->at(i).value << ", "; + //} + //std::cout << std::endl; // The following approach was taken in order to be able to // deal with the responses, as generated by redis in the case