From 7d4902369a7e6a4fe789855efcb4623ea682fa00 Mon Sep 17 00:00:00 2001 From: Marcelo Zimbres Date: Thu, 5 Jan 2023 23:37:55 +0100 Subject: [PATCH] Doc improvements and replaces async_main to co_main. --- README.md | 62 +++++++++++----------- examples/common/main.cpp | 14 +++-- examples/cpp17_intro.cpp | 12 ++++- examples/cpp17_intro_sync.cpp | 12 ++++- examples/cpp17_low_level_sync.cpp | 12 ++++- examples/cpp20_chat_room.cpp | 6 +-- examples/cpp20_containers.cpp | 67 ++++++++++++------------ examples/cpp20_echo_server.cpp | 4 +- examples/cpp20_intro.cpp | 8 +-- examples/cpp20_intro_awaitable_ops.cpp | 4 +- examples/cpp20_intro_tls.cpp | 2 +- examples/cpp20_low_level_async.cpp | 4 +- examples/cpp20_resolve_with_sentinel.cpp | 8 +-- examples/cpp20_serialization.cpp | 4 +- examples/cpp20_subscriber.cpp | 4 +- 15 files changed, 126 insertions(+), 97 deletions(-) diff --git a/README.md b/README.md index 99492ce0..74842ac2 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ The requirements for using Aedis are * Boost 1.80 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. Readers that are not familiar with Redis can learn more about @@ -40,20 +41,20 @@ connection to read Redis [hashes](https://redis.io/docs/data-types/hashes/) in a `std::map` ```cpp -auto async_main() -> net::awaitable +auto co_main() -> net::awaitable { auto conn = std::make_shared(co_await net::this_coro::executor); // From examples/common.hpp to avoid vebosity co_await connect(conn, "127.0.0.1", "6379"); - // A request contains multiple commands. + // A request can contains multiple commands. resp3::request req; req.push("HELLO", 3); req.push("HGETALL", "hset-key"); req.push("QUIT"); - // The tuple elements below will store the response to each + // The tuple elements below will store the responses to each // individual command. The responses to HELLO and QUIT are being // ignored for simplicity. std::tuple, ignore> resp; @@ -75,18 +76,18 @@ played by these functions are * `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 will also trigger writes of pending - requests when a reconnection occurs. + server-push is received. It is also responsible for triggering writes of pending + requests. The example above is also available in other programming styles for comparison +* cpp20_intro_awaitable_ops.cpp: The version shown above. * cpp20_intro.cpp: Does not use awaitable operators. -* cpp20_intro_awaitable_ops.cpp: The version from above. -* cpp17_intro.cpp: Uses callbacks and requires C++17. * cpp20_intro_tls.cpp: Communicates over TLS. +* cpp17_intro.cpp: Uses callbacks and requires C++17. For performance reasons we will usually want to perform multiple -requests in the same connection. We can do this in the example above +requests with the same connection. We can do this in the example above by letting `async_run` run detached in a separate coroutine, for example (see cpp20_intro.cpp) @@ -123,7 +124,7 @@ auto quit(std::shared_ptr conn) -> net::awaitable co_await conn->async_exec(req); } -auto async_main() -> net::awaitable +auto co_main() -> net::awaitable { auto ex = co_await net::this_coro::executor; auto conn = std::make_shared(ex); @@ -132,18 +133,21 @@ auto async_main() -> net::awaitable co_await ping(conn); co_await quit(conn); - // Pass conn around to other coroutines that need to communicate with Redis. + // conn can be passed around to other coroutines that need to + // communicate with Redis. For example, sessions in a HTTP and + // Websocket server. } ``` With this separation, it is now easy to incorporate other long-running operations in our application, for example, the run coroutine below -adds signal handling and a healthy checker +adds signal handling and a healthy checker (see cpp20_echo_server.cpp +for example) ```cpp auto run(std::shared_ptr conn) -> net::awaitable { - signal_set sig{ex, SIGINT, SIGTERM}; + signal_set sig{co_await net::this_coro::executor, SIGINT, SIGTERM}; co_await connect(conn, "127.0.0.1", "6379"); co_await (conn->async_run() || sig.async_wait() || healthy_checker(conn)); } @@ -161,7 +165,8 @@ them are * [Client-side caching](https://redis.io/docs/manual/client-side-caching/) The connection class supports server pushes by means of the -`aedis::connection::async_receive` function, for example +`aedis::connection::async_receive` function, the coroutine shows how +to used it ```cpp auto receiver(std::shared_ptr conn) -> net::awaitable @@ -181,7 +186,7 @@ in the `run` function as we did for the `healthy_checker` ```cpp auto run(std::shared_ptr conn) -> net::awaitable { - signal_set sig{ex, SIGINT, SIGTERM}; + signal_set sig{co_await net::this_coro::executor, SIGINT, SIGTERM}; co_await connect(conn, "127.0.0.1", "6379"); co_await (conn->async_run() || sig.async_wait() || healthy_checker(conn) || receiver(conn)); } @@ -196,12 +201,11 @@ for example, to reconnect to the same address (see cpp20_subscriber.cpp) ```cpp auto run(std::shared_ptr conn) -> net::awaitable { - auto ex = co_await net::this_coro::executor; - steady_timer timer{ex}; + steady_timer timer{co_await net::this_coro::executor}; for (;;) { co_await connect(conn, "127.0.0.1", "6379"); - co_await (conn->async_run() || healthy_checker(conn) || receiver(conn); + co_await (conn->async_run() || healthy_checker(conn) || receiver(conn)); // Prepare the stream for a new connection. conn->reset_stream(); @@ -242,7 +246,7 @@ co_await (conn.async_exec(...) || time.async_wait(...)) * Provides a way to limit how long the execution of a single request should last. -* NOTE: It is usually a better idea to have a healthy checker than adding +* WARNING: It is usually a better idea to have a healthy checker than adding per request timeout, see cpp20_subscriber.cpp for an example. ```cpp @@ -266,6 +270,7 @@ Redis documentation they are called std::list list {...}; std::map map { ...}; +// The request can contains multiple commands. request req; // Command with variable length of arguments. @@ -292,10 +297,10 @@ with integer data types e.g. `int` and `std::string` out of the box. To send your own data type define a `to_bulk` function like this ```cpp -// Example struct. +// User defined type. struct mystruct {...}; -// Serialize your data structure here. +// Serialize it in to_bulk. void to_bulk(std::pmr::string& to, mystruct const& obj) { std::string dummy = "Dummy serializaiton string."; @@ -438,13 +443,13 @@ subset of the RESP3 specification. ### Pushes -Commands that have push response like +Commands that have no response like * `"SUBSCRIBE"` * `"PSUBSCRIBE"` * `"UNSUBSCRIBE"` -must be **NOT** be included in the tuple. For example, the request below +must be **NOT** be included in the response tuple. For example, the request below ```cpp request req; @@ -513,7 +518,7 @@ std::tuple< co_await conn->async_exec(req, adapt(resp)); ``` -For a complete example see containers.cpp. +For a complete example see cpp20_containers.cpp. ### Deserialization @@ -589,7 +594,7 @@ from Redis with `HGETALL`, some of the options are * `std::map`: Efficient if you are storing serialized data. Avoids temporaries and requires `from_bulk` for `U` and `V`. In addition to the above users can also use unordered versions of the -containers. The same reasoning also applies to sets e.g. `SMEMBERS` +containers. The same reasoning applies to sets e.g. `SMEMBERS` and other data structures in general. ## Examples @@ -826,18 +831,11 @@ library, so you can starting using it right away by adding the ``` in no more than one source file in your applications. To build the -examples and test cmake is supported, for example +examples and tests cmake is supported, for example ```cpp BOOST_ROOT=/opt/boost_1_80_0 cmake --preset dev ``` - -The following compilers are supported - -- Gcc: 10, 11, 12. -- Clang: 11, 13, 14. -- Visual Studio 17 2022, Visual Studio 16 2019. - ## Acknowledgement Acknowledgement to people that helped shape Aedis diff --git a/examples/common/main.cpp b/examples/common/main.cpp index ef064874..33b56d78 100644 --- a/examples/common/main.cpp +++ b/examples/common/main.cpp @@ -10,11 +10,19 @@ #include "common.hpp" -extern boost::asio::awaitable async_main(); +extern boost::asio::awaitable co_main(std::string, std::string); -auto main() -> int +auto main(int argc, char * argv[]) -> int { - return run(async_main()); + std::string host = "127.0.0.1"; + std::string port = "6379"; + + if (argc == 3) { + host = argv[1]; + port = argv[2]; + } + + return run(co_main(host, port)); } #else // defined(BOOST_ASIO_HAS_CO_AWAIT) diff --git a/examples/cpp17_intro.cpp b/examples/cpp17_intro.cpp index 05728e39..08f41a3f 100644 --- a/examples/cpp17_intro.cpp +++ b/examples/cpp17_intro.cpp @@ -20,9 +20,17 @@ void log(boost::system::error_code const& ec, char const* prefix) std::clog << prefix << ec.message() << std::endl; } -auto main() -> int +auto main(int argc, char * argv[]) -> int { try { + std::string host = "127.0.0.1"; + std::string port = "6379"; + + if (argc == 3) { + host = argv[1]; + port = argv[2]; + } + // The request resp3::request req; req.push("HELLO", 3); @@ -79,7 +87,7 @@ auto main() -> int net::async_connect(conn.next_layer(), endpoints, on_connect); }; - resv.async_resolve("127.0.0.1", "6379", on_resolve); + resv.async_resolve(host, port, on_resolve); ioc.run(); return 0; diff --git a/examples/cpp17_intro_sync.cpp b/examples/cpp17_intro_sync.cpp index dbba1286..b4c0e827 100644 --- a/examples/cpp17_intro_sync.cpp +++ b/examples/cpp17_intro_sync.cpp @@ -31,14 +31,22 @@ auto exec(std::shared_ptr conn, resp3::request const& req, Adapter a auto logger = [](auto const& ec) { std::clog << "Run: " << ec.message() << std::endl; }; -int main() +auto main(int argc, char * argv[]) -> int { try { + std::string host = "127.0.0.1"; + std::string port = "6379"; + + if (argc == 3) { + host = argv[1]; + port = argv[2]; + } + net::io_context ioc{1}; auto conn = std::make_shared(ioc); net::ip::tcp::resolver resv{ioc}; - auto const res = resv.resolve("127.0.0.1", "6379"); + auto const res = resv.resolve(host, port); net::connect(conn->next_layer(), res); std::thread t{[conn, &ioc]() { conn->async_run(logger); diff --git a/examples/cpp17_low_level_sync.cpp b/examples/cpp17_low_level_sync.cpp index cb6dfeb6..87d93a76 100644 --- a/examples/cpp17_low_level_sync.cpp +++ b/examples/cpp17_low_level_sync.cpp @@ -16,12 +16,20 @@ namespace net = boost::asio; namespace resp3 = aedis::resp3; using aedis::adapter::adapt2; -int main() +auto main(int argc, char * argv[]) -> int { try { + std::string host = "127.0.0.1"; + std::string port = "6379"; + + if (argc == 3) { + host = argv[1]; + port = argv[2]; + } + net::io_context ioc; net::ip::tcp::resolver resv{ioc}; - auto const res = resv.resolve("127.0.0.1", "6379"); + auto const res = resv.resolve(host, port); net::ip::tcp::socket socket{ioc}; net::connect(socket, res); diff --git a/examples/cpp20_chat_room.cpp b/examples/cpp20_chat_room.cpp index 5e88c1d4..7a59cc33 100644 --- a/examples/cpp20_chat_room.cpp +++ b/examples/cpp20_chat_room.cpp @@ -47,7 +47,7 @@ auto publisher(std::shared_ptr in, std::shared_ptr net::awaitable +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); @@ -58,13 +58,13 @@ auto async_main() -> net::awaitable req.push("HELLO", 3); req.push("SUBSCRIBE", "chat-channel"); - co_await connect(conn, "127.0.0.1", "6379"); + co_await connect(conn, host, port); co_await ((conn->async_run() || publisher(stream, conn) || receiver(conn) || healthy_checker(conn) || sig.async_wait()) && conn->async_exec(req)); } #else // defined(BOOST_ASIO_HAS_POSIX_STREAM_DESCRIPTOR) -auto async_main() -> net::awaitable +auto co_main(std::string host, std::string port) -> 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 e583fa20..bf94ac93 100644 --- a/examples/cpp20_containers.cpp +++ b/examples/cpp20_containers.cpp @@ -30,14 +30,15 @@ void print(std::vector const& cont) std::cout << "\n"; } -// Stores the content of some STL containers in Redis. -auto store() -> net::awaitable +auto run(std::shared_ptr conn, std::string host, std::string port) -> net::awaitable { - auto conn = std::make_shared(co_await net::this_coro::executor); - - // Resolves and connects (from examples/common.hpp to avoid vebosity) - co_await connect(conn, "127.0.0.1", "6379"); + co_await connect(conn, host, port); + co_await conn->async_run(); +} +// Stores the content of some STL containers in Redis. +auto store(std::shared_ptr conn) -> net::awaitable +{ std::vector vec {1, 2, 3, 4, 5, 6}; @@ -48,70 +49,68 @@ auto store() -> net::awaitable req.push("HELLO", 3); req.push_range("RPUSH", "rpush-key", vec); req.push_range("HSET", "hset-key", map); - req.push("QUIT"); - co_await (conn->async_run() || conn->async_exec(req)); + co_await conn->async_exec(req); } -auto hgetall() -> net::awaitable> +auto hgetall(std::shared_ptr conn) -> net::awaitable { - auto conn = std::make_shared(co_await net::this_coro::executor); - - // From examples/common.hpp to avoid vebosity - co_await connect(conn, "127.0.0.1", "6379"); - // A request contains multiple commands. resp3::request req; req.push("HELLO", 3); req.push("HGETALL", "hset-key"); - req.push("QUIT"); // Responses as tuple elements. - std::tuple, aedis::ignore> resp; + std::tuple> resp; // Executes the request and reads the response. - co_await (conn->async_run() || conn->async_exec(req, adapt(resp))); - co_return std::get<1>(resp); + co_await conn->async_exec(req, adapt(resp)); + + print(std::get<1>(resp)); } // Retrieves in a transaction. -auto transaction() -> net::awaitable +auto transaction(std::shared_ptr conn) -> net::awaitable { - auto conn = std::make_shared(co_await net::this_coro::executor); - - // Resolves and connects (from examples/common.hpp to avoid vebosity) - co_await connect(conn, "127.0.0.1", "6379"); - resp3::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"); - req.push("QUIT"); std::tuple< aedis::ignore, // hello aedis::ignore, // multi aedis::ignore, // lrange aedis::ignore, // hgetall - std::tuple>, std::optional>>, // exec - aedis::ignore // quit + std::tuple>, std::optional>> // exec > resp; - co_await (conn->async_run() || conn->async_exec(req, adapt(resp))); + co_await conn->async_exec(req, adapt(resp)); print(std::get<0>(std::get<4>(resp)).value()); print(std::get<1>(std::get<4>(resp)).value()); } -// Called from the main function (see main.cpp) -net::awaitable async_main() +auto quit(std::shared_ptr conn) -> net::awaitable { - co_await store(); - co_await transaction(); - auto const map = co_await hgetall(); - print(map); + resp3::request req; + req.push("QUIT"); + + co_await conn->async_exec(req); +} + +// Called from the main function (see main.cpp) +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); + co_await store(conn); + co_await transaction(conn); + co_await hgetall(conn); + co_await quit(conn); } #endif // defined(BOOST_ASIO_HAS_CO_AWAIT) diff --git a/examples/cpp20_echo_server.cpp b/examples/cpp20_echo_server.cpp index 59a96a43..900b30ec 100644 --- a/examples/cpp20_echo_server.cpp +++ b/examples/cpp20_echo_server.cpp @@ -45,7 +45,7 @@ auto listener(std::shared_ptr conn) -> net::awaitable } // Called from the main function (see main.cpp) -auto async_main() -> net::awaitable +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); @@ -54,7 +54,7 @@ auto async_main() -> net::awaitable resp3::request req; req.push("HELLO", 3); - co_await connect(conn, "127.0.0.1", "6379"); + co_await connect(conn, host, port); co_await ((conn->async_run() || listener(conn) || healthy_checker(conn) || sig.async_wait()) && conn->async_exec(req)); } diff --git a/examples/cpp20_intro.cpp b/examples/cpp20_intro.cpp index 78f81ae7..96ab53fb 100644 --- a/examples/cpp20_intro.cpp +++ b/examples/cpp20_intro.cpp @@ -14,9 +14,9 @@ namespace resp3 = aedis::resp3; using aedis::adapt; using aedis::operation; -auto run(std::shared_ptr conn) -> net::awaitable +auto run(std::shared_ptr conn, std::string host, std::string port) -> net::awaitable { - co_await connect(conn, "127.0.0.1", "6379"); + co_await connect(conn, host, port); co_await conn->async_run(); } @@ -48,11 +48,11 @@ auto quit(std::shared_ptr conn) -> net::awaitable } // Called from the main function (see main.cpp) -auto async_main() -> net::awaitable +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), net::detached); + net::co_spawn(ex, run(conn, host, port), net::detached); co_await hello(conn); co_await ping(conn); co_await quit(conn); diff --git a/examples/cpp20_intro_awaitable_ops.cpp b/examples/cpp20_intro_awaitable_ops.cpp index b825f22f..eabded77 100644 --- a/examples/cpp20_intro_awaitable_ops.cpp +++ b/examples/cpp20_intro_awaitable_ops.cpp @@ -16,7 +16,7 @@ using namespace net::experimental::awaitable_operators; using aedis::adapt; // Called from the main function (see main.cpp) -auto async_main() -> net::awaitable +auto co_main(std::string host, std::string port) -> net::awaitable { resp3::request req; req.push("HELLO", 3); @@ -26,7 +26,7 @@ auto async_main() -> net::awaitable std::tuple resp; auto conn = std::make_shared(co_await net::this_coro::executor); - co_await connect(conn, "127.0.0.1", "6379"); + co_await connect(conn, host, port); co_await (conn->async_run() || conn->async_exec(req, adapt(resp))); std::cout << "PING: " << std::get<1>(resp) << std::endl; diff --git a/examples/cpp20_intro_tls.cpp b/examples/cpp20_intro_tls.cpp index f62951b9..69525c31 100644 --- a/examples/cpp20_intro_tls.cpp +++ b/examples/cpp20_intro_tls.cpp @@ -29,7 +29,7 @@ auto verify_certificate(bool, net::ssl::verify_context&) -> bool return true; } -net::awaitable async_main() +net::awaitable co_main(std::string, std::string) { resp3::request req; req.push("HELLO", 3, "AUTH", "aedis", "aedis"); diff --git a/examples/cpp20_low_level_async.cpp b/examples/cpp20_low_level_async.cpp index 19135d72..1d7ca686 100644 --- a/examples/cpp20_low_level_async.cpp +++ b/examples/cpp20_low_level_async.cpp @@ -17,12 +17,12 @@ using tcp_socket = net::use_awaitable_t<>::as_default_on_t using aedis::adapter::adapt2; using net::ip::tcp; -auto async_main() -> net::awaitable +auto co_main(std::string host, std::string port) -> net::awaitable { auto ex = co_await net::this_coro::executor; resolver resv{ex}; - auto const addrs = co_await resv.async_resolve("127.0.0.1", "6379"); + auto const addrs = co_await resv.async_resolve(host, port); tcp_socket socket{ex}; co_await net::async_connect(socket, addrs); diff --git a/examples/cpp20_resolve_with_sentinel.cpp b/examples/cpp20_resolve_with_sentinel.cpp index 1770a688..8d3beb78 100644 --- a/examples/cpp20_resolve_with_sentinel.cpp +++ b/examples/cpp20_resolve_with_sentinel.cpp @@ -49,14 +49,14 @@ auto resolve_master_address(std::vector
const& endpoints) -> net::await co_return address{}; } -auto async_main() -> net::awaitable +auto co_main(std::string host, std::string port) -> net::awaitable { - // A list of sentinel addresses from which only one is responsive - // to simulate sentinels that are down. + // 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"} - , {"127.0.0.1", "26379"} + , {host, port} }; auto const ep = co_await resolve_master_address(endpoints); diff --git a/examples/cpp20_serialization.cpp b/examples/cpp20_serialization.cpp index 339be0c7..260b99a6 100644 --- a/examples/cpp20_serialization.cpp +++ b/examples/cpp20_serialization.cpp @@ -86,7 +86,7 @@ void from_bulk(user& u, std::string_view sv, boost::system::error_code&) u = value_to(jv); } -net::awaitable async_main() +net::awaitable co_main(std::string host, std::string port) { std::set users {{"Joao", "58", "Brazil"} , {"Serge", "60", "France"}}; @@ -101,7 +101,7 @@ net::awaitable async_main() auto conn = std::make_shared(co_await net::this_coro::executor); - co_await connect(conn, "127.0.0.1", "6379"); + co_await connect(conn, host, port); co_await (conn->async_run() || conn->async_exec(req, adapt(resp))); for (auto const& e: std::get<2>(resp)) diff --git a/examples/cpp20_subscriber.cpp b/examples/cpp20_subscriber.cpp index 181ca712..2e90de10 100644 --- a/examples/cpp20_subscriber.cpp +++ b/examples/cpp20_subscriber.cpp @@ -44,7 +44,7 @@ auto receiver(std::shared_ptr conn) -> net::awaitable } } -auto async_main() -> net::awaitable +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); @@ -56,7 +56,7 @@ auto async_main() -> net::awaitable // The loop will reconnect on connection lost. To exit type Ctrl-C twice. for (;;) { - co_await connect(conn, "127.0.0.1", "6379"); + co_await connect(conn, host, port); co_await ((conn->async_run() || healthy_checker(conn) || receiver(conn)) && conn->async_exec(req)); conn->reset_stream();