diff --git a/CHANGELOG.md b/CHANGELOG.md index f2bacb29..e02fe3a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,37 +2,39 @@ ## v0.3.0 -* Adds `experimental::exec` and `receive_event` - functions to offer a thread safe and synchronous way of executing - requests. See `intro_sync.cpp` and `subscriber_sync.cpp` for +* Adds `experimental::exec` and `receive_event` functions to offer a + thread safe and synchronous way of executing requests across + threads. See `intro_sync.cpp` and `subscriber_sync.cpp` for examples. * `connection::async_read_push` was renamed to `async_receive_event`. -* Uses `async_receive_event` to communicate internal events to the - user, see subscriber.cpp and `connection::event`. +* `connection::async_receive_event` is now being used to communicate + internal events to the user, such as resolve, connect, push etc. For + examples see subscriber.cpp and `connection::event`. * The `aedis` directory has been moved to `include` to look more similar to Boost libraries. Users should now replace `-I/aedis-path` with `-I/aedis-path/include` in the compiler flags. -* AUTH and HELLO commands are sent automatically. This change was - necessary to implement reconnection. +* The `AUTH` and `HELLO` commands are now sent automatically. This change was + necessary to implement reconnection. The username and password + used in `AUTH` should be provided by the user on + `connection::config`. -* Adds support for reconnection. See connection::enable reconnect. +* Adds support for reconnection. See `connection::enable_reconnect`. -* Fixes a bug in the `connection::async_exec(host, port)` overload - that was causing crashes reconnection. +* Fixes a bug in the `connection::async_run(host, port)` overload + that was causing crashes on reconnection. * Fixes the executor usage in the connection class. Before theses changes it was imposing `any_io_executor` on users. * `connection::async_receiver_event` is not cancelled anymore when - `connection::async_run` exits. This change simplifies the - implementation failover operations. + `connection::async_run` exits. This change makes user code simpler. * `connection::async_exec` with host and port overload has been - removed. Use the net `connection::async_run` overload. + removed. Use the other `connection::async_run` overload. * The host and port parameters from `connection::async_run` have been move to `connection::config` to better support authentication and @@ -43,6 +45,32 @@ * Fixes build in clang the compilers and makes some improvements in the documentation. -##v0.2.1 +## v0.2.1 -* Bugfixes and improvements in the documentation. +* Fixes a bug that happens on very high load. + +## v0.2.0 + +* Major rewrite of the high-level API. There is no more need to use the low-level API anymore. +* No more callbacks: Sending requests follows the ASIO asynchrnous model. +* Support for reconnection: Pending requests are not canceled when a connection is lost and are re-sent when a new one is established. +* The library is not sending HELLO-3 on user behalf anymore. This is important to support AUTH properly. + +## v0.1.2 + +* Adds reconnect coroutine in the `echo_server` example. +* Corrects `client::async_wait_for_data` with `make_parallel_group` to launch operation. +* Improvements in the documentation. +* Avoids dynamic memory allocation in the client class after reconnection. + +## v0.1.1 + +* Improves the documentation and adds some features to the high-level client. + +## v0.1.0 + +* Improvements in the design and documentation. + +## v0.0.1 + +* First release to collect design feedback. diff --git a/doc/aedis.css b/doc/aedis.css index 235c3b62..22a48758 100644 --- a/doc/aedis.css +++ b/doc/aedis.css @@ -26,5 +26,5 @@ div.contents { code { - background-color:#f0e9ce; + background-color:#fffbeb; } diff --git a/examples/subscriber.cpp b/examples/subscriber.cpp index 062069f9..21eb1090 100644 --- a/examples/subscriber.cpp +++ b/examples/subscriber.cpp @@ -9,7 +9,6 @@ #include #include #include -#include #include #include "print.hpp" @@ -23,7 +22,6 @@ using aedis::resp3::request; using node_type = aedis::resp3::node; using tcp_socket = net::use_awaitable_t<>::as_default_on_t; using connection = aedis::connection; -using net::experimental::as_tuple; /* This example will subscribe and read pushes indefinitely. * @@ -47,7 +45,7 @@ net::awaitable receiver(std::shared_ptr db) req.push("SUBSCRIBE", "channel"); for (std::vector resp;;) { - auto [ec, ev] = co_await db->async_receive_event(aedis::adapt(resp), as_tuple(net::use_awaitable)); + auto const ev = co_await db->async_receive_event(aedis::adapt(resp)); std::cout << "Event: " << aedis::to_string(ev) << std::endl; diff --git a/include/aedis.hpp b/include/aedis.hpp index 5743ebdd..b40c1357 100644 --- a/include/aedis.hpp +++ b/include/aedis.hpp @@ -28,37 +28,36 @@ \li First class support for STL containers and C++ built-in types. \li Serialization and deserialization of your own data types. \li Healthy checks, back pressure and low latency. + \li Hides most of the low level asynchronous operations away from the user. - Aedis API hides most of the low level asynchronous operations away - from the user, for example, the code below sends a ping command to - Redis (see intro.cpp) + Let us start with an overview of asynchronous code. + + @subsection Async + + The code below sends a ping command to Redis (see intro.cpp) @code int main() { + net::io_context ioc; + connection db{ioc}; + request req; - req.push("HELLO", 3); req.push("PING"); req.push("QUIT"); - std::tuple resp; - - net::io_context ioc; - - connection db{ioc}; - - db.async_exec("127.0.0.1", "6379", req, adapt(resp), handler); + std::tuple resp; + db.async_run(req, adapt(resp), net::detached); ioc.run(); - // Print the ping message. - std::cout << std::get<1>(resp) << std::endl; + std::cout << std::get<0>(resp) << std::endl; } @endcode - The connection class keeps a long lasting connection to the Redis - server over which users can execute commands, without any need of - queuing, for example, to execute more than one command + The connection class maintains a healthy connection with + Redis over which users can execute their commands, without any + need of queuing. For example, to execute more than one command @code int main() @@ -71,32 +70,80 @@ db.async_exec(req2, adapt(resp2), handler2); db.async_exec(req3, adapt(resp3), handler3); - db.async_run("127.0.0.1", "6379", handler4); + db.async_run(net::detached); ioc.run(); ... } @endcode - See echo_server.cpp for a more complex example. Server-side - pushes are supported on the same connection where commands are - executed, a typical subscriber will look like + The `async_exec` functions above can be called from different + places in the code without knowing about each other, see for + example echo_server.cpp. Server-side pushes are supported on the + same connection where commands are executed, a typical subscriber + will look like + (see subscriber.cpp) @code net::awaitable reader(std::shared_ptr db) { - ... - for (std::vector> resp;;) { - co_await db->async_receive(adapt(resp)); + request req; + req.push("SUBSCRIBE", "channel"); + + for (std::vector resp;;) { + auto ev = co_await db->async_receive_event(aedis::adapt(resp)); - // Handle message + switch (ev) { + case connection::event::push: + // Use resp. + resp.clear(); + break; - resp.clear(); + case connection::event::hello: + // Subscribes to channels when a new connection is + // stablished. + co_await db->async_exec(req); + break; + + default:; + } } } @endcode - See subscriber.cpp for a complete example. + @subsection Sync + + The `connection` class is async-only, many users however need to + interact with it synchronously, this is also supported by Aedis as long + as this interaction occurs across threads, for example (see + intro_sync.cpp) + + @code + int main() + { + try { + net::io_context ioc{1}; + connection conn{ioc}; + + std::thread thread{[&]() { + conn.async_run(net::detached); + ioc.run(); + }}; + + request req; + req.push("PING"); + req.push("QUIT"); + + std::tuple resp; + exec(conn, req, adapt(resp)); + thread.join(); + + std::cout << "Response: " << std::get<0>(resp) << std::endl; + } catch (std::exception const& e) { + std::cerr << e.what() << std::endl; + } + } + @endcode \subsection using-aedis Installation @@ -110,7 +157,7 @@ ``` # Clone the repository and checkout the lastest release tag. - $ git clone --branch v0.2.1 https://github.com/mzimbres/aedis.git + $ git clone --branch v0.3.0 https://github.com/mzimbres/aedis.git $ cd aedis # Build an example @@ -121,7 +168,7 @@ ``` # Download and unpack the latest release - $ wget https://github.com/mzimbres/aedis/releases/download/v0.2.1/aedis-0.2.1.tar.gz + $ wget https://github.com/mzimbres/aedis/releases/download/v0.3.0/aedis-0.2.1.tar.gz $ tar -xzvf aedis-0.2.1.tar.gz # Configure, build and install @@ -451,10 +498,12 @@ The examples listed below cover most use cases presented in the documentation above. @li intro.cpp: Basic steps with Aedis. + @li intro_sync.cpp: Synchronous version of intro.cpp. @li containers.cpp: Shows how to send and receive stl containers. @li serialization.cpp: Shows the \c request support to serialization of user types. @li subscriber.cpp: Shows how to subscribe to a channel and how to reconnect when connection is lost. - @li echo_server.cpp: A simple TCP echo server that users coroutines. + @li subscriber_sync.cpp: Synchronous version of subscriber.cpp. + @li echo_server.cpp: A simple TCP echo server that uses coroutines. @li chat_room.cpp: A simple chat room that uses coroutines. \section why-aedis Why Aedis @@ -476,8 +525,8 @@ not support @li RESP3. Without RESP3 is impossible to support some important Redis features like client side caching, among other things. - @li The Asio asynchronous model. - @li Reading response diretly in user data structures avoiding temporaries. + @li Coroutines. + @li Reading responses directly in user data structures avoiding temporaries. @li Error handling with error-code and exception overloads. @li Healthy checks. @@ -523,11 +572,11 @@ Some of the problems with this API are - @li Heterogeneous treatment of commands, pipelines and transaction. + @li Heterogeneous treatment of commands, pipelines and transaction. This makes auto-pipelining impossible. @li Any Api that sends individual commands has a very restricted scope of usability and should be avoided for performance reasons. @li The API imposes exceptions on users, no error-code overload is provided. @li No way to reuse the buffer for new calls to e.g. \c redis.get in order to avoid further dynamic memory allocations. - @li Error handling of resolve and connection no clear. + @li Error handling of resolve and connection not clear. According to the documentation, pipelines in redis-plus-plus have the following characteristics @@ -538,7 +587,7 @@ This is clearly a downside of the API as pipelines should be the default way of communicating and not an exception, paying such a high price for each pipeline imposes a severe cost in performance. - Transactions also suffer from the very same problem + Transactions also suffer from the very same problem. > NOTE: Creating a Transaction object is NOT cheap, since it > creates a new connection.