From 8cc142d55b785046cdecbbcfc2dfce7d24571da6 Mon Sep 17 00:00:00 2001 From: Marcelo Zimbres Date: Sun, 6 Mar 2022 11:34:59 +0100 Subject: [PATCH] Many improvements in the docs. --- aedis/aedis.hpp | 130 +++++++++++++++--------------- aedis/redis/client.hpp | 12 ++- aedis/redis/detail/client_ops.hpp | 2 +- aedis/redis/impl/command.ipp | 3 +- aedis/redis/receiver.hpp | 3 + examples/aggregates.cpp | 2 +- examples/chat_room.cpp | 9 +-- examples/echo_server.cpp | 5 +- 8 files changed, 86 insertions(+), 80 deletions(-) diff --git a/aedis/aedis.hpp b/aedis/aedis.hpp index ead49013..8d17ff4f 100644 --- a/aedis/aedis.hpp +++ b/aedis/aedis.hpp @@ -24,58 +24,84 @@ \section Overview - Aedis is low-level redis client library built on top of Boost.Asio - that implements communication with a Redis server over the latests - version of its protocol RESP3. Some of its most important features - are + Aedis is a low-level redis client library that provides simple and + efficient communication with a Redis server built on top of + Boost.Asio. Some of its distinctive features are - 1. First class support for asynchronous communication. - 2. Support for STL containers. - 3. Serialization and deserialization of your own data types built directly in the parser to avoid unnecessary copies. - 4. Client class that encapsulates handling of requests for the user. - 5. etc. + 1. Support for the latest version of the Redis communication protocol RESP3. + 2. Asynchronous interface that handles servers pushs optimally. + 3. Firt class support for STL containers. + 4. Serialization and deserialization of your own data types built directly into the parser that avoids temporaries. + 5. Client class that abstracts the management of output messages away from the user. + 6. Asymptotic zero allocations by means of memory reuse. - For more information about Redis see https://redis.io/ - - \section tutorial Tutorial - - The general structure of a program involves writing a receiver like this - - @code - class myreceiver : receiver>> { - public: - void on_read(command cmd) override - { - switch (cmd) { - case command::hello: on_hello(); break; - case command::set: on_set(); break; - case command::get: on_get(); break; - ... - default: - } - } - }; - @endcode - - and to start communication with Redis + The general form of a program looks like @code int main() { net::io_context ioc; - client db(ioc.get_executor()); - myreceiver recv; + client db{ioc.get_executor()}; + receiver recv; db.async_run( recv, {net::ip::make_address("127.0.0.1"), 6379}, - [](auto ec){ std::cout << ec.message() << std::endl;}); + [](auto ec){ ... }); ioc.run(); } @endcode + + Most of the time users will be concerned only with the + implementation of the \c receiver class, to make that simpler, + Aedis provides a base receiver class that abstracts all the + complexity away from the user. A typical implementation will look + like the following + + @code + class myreceiver : receiver { + public: + void on_read_impl(command cmd) override + { + // Handle commands here. + } + }; + @endcode + + Sending commands to Redis is also simple, for example + + @code + db.>send(command::ping, "O rato roeu a roupa do rei de Roma"); + db.>send(command::incr, "counter"); + db.>send(command::set, "key", "Três pratos de trigo para três tigres"); + db.>send(command::get, "key"); + db.>send(command::quit); + @endcode + + See Tutorial for more details on how to use the library. + For more information about Redis see https://redis.io/ + + \section tutorial Tutorial + + In the last secion we have seem the general structure of an Aedis + program. Here we will give more detail. + + \subsection Requests + \subsection Responses + \subsubsection Serializaiton + \subsubsection Transactions + + Redis commands can fail only if called with a wrong syntax (and + the problem is not detectable during the command queueing), + or against keys holding the wrong data type: this means that in + practical terms a failing command is the result of a programming + errors, and a kind of error that is very likely to be detected + during development, and not in production. \section examples Examples + https://redis.io/topics/data-types. + See also https://redis.io/topics/transactions. \b Basics: Focuses on small examples that show basic usage of the library. @@ -83,11 +109,12 @@ - intro.cpp: A good starting point. Some commands are sent to the Redis server and the responses are printed to screen. - - aggregates.cpp: + - aggregates.cpp: Shows how receive RESP3 aggregate data types. - - transaction.cpp: Shows how to read the responses to a trasaction - efficiently. See also https://redis.io/topics/transactions. + - stl_containers.cpp: + - serialization.cpp: Shows how to de/serialize your own + non-aggregate data-structures. - subscriber.cpp: Shows how channel subscription works at a low level. See also https://redis.io/topics/pubsub. @@ -100,34 +127,9 @@ - chat_room.cpp: Shows how to build a scalable chat room that scales to millions of users. - \b STL \b Containers: Many of the Redis data structures can be - directly translated in to STL containers, below you will find some - example code. For a list of Redis data types see - https://redis.io/topics/data-types. - - - hashes.cpp: Shows how to read Redis hashes in a \c std::map, \c - std::unordered_map and \c std::vector. - - - lists.cpp: Shows how to read Redis lists in \c std::list, - \c std::deque, \c std::vector. It also illustrates basic serialization. - - - sets.cpp: Shows how to read Redis sets in a \c std::set, \c - std::unordered_set and \c std::vector. - - \b Customization \b points: Shows how de/serialize user types - avoiding copies. This is particularly useful for low latency - applications that want to avoid unneeded copies, for examples when - storing json strings in Redis keys. - - - serialization.cpp: Shows how to de/serialize your own - non-aggregate data-structures. - - - response_adapter.cpp: Customization point for users that want to + - receiver.cpp: Customization point for users that want to de/serialize their own data-structures like containers for example. - - key_expiration.cpp: Shows how to use \c std::optional to deal - with keys that may have expired or do not exist. - \section using-aedis Using Aedis To install and use Aedis you will need diff --git a/aedis/redis/client.hpp b/aedis/redis/client.hpp index ee03c08b..dc01de38 100644 --- a/aedis/redis/client.hpp +++ b/aedis/redis/client.hpp @@ -12,6 +12,10 @@ #include #include +// TODO: What to do if a users send a discard not contained in a +// transaction. The client object will try to pop the queue until a +// multi is found. + namespace aedis { namespace redis { @@ -20,7 +24,8 @@ namespace redis { * * This Redis client keeps a connection to the database open and * uses it for all communication with Redis. For examples on how to - * use see the examples chat_room.cpp, echo_server.cpp and redis_client.cpp. + * use see the examples chat_room.cpp, echo_server.cpp and + * redis_client.cpp. * * \remarks This class reuses its internal buffers for requests and * for reading Redis responses. With time it will allocate less and @@ -95,8 +100,11 @@ private: } // Returns true when the next request can be writen. - bool on_cmd() + bool on_cmd(command) { + // TODO: If the response to a discard is received we have to + // remove all commands up until multi. + assert(!std::empty(req_info_)); assert(!std::empty(commands_)); diff --git a/aedis/redis/detail/client_ops.hpp b/aedis/redis/detail/client_ops.hpp index d062728f..9c582e6f 100644 --- a/aedis/redis/detail/client_ops.hpp +++ b/aedis/redis/detail/client_ops.hpp @@ -182,7 +182,7 @@ struct read_op { return; } - if (t != resp3::type::push && cli->on_cmd()) + if (t != resp3::type::push && cli->on_cmd(cmd)) cli->timer_.cancel_one(); recv->on_read(cmd); diff --git a/aedis/redis/impl/command.ipp b/aedis/redis/impl/command.ipp index 28c873f6..acd8f983 100644 --- a/aedis/redis/impl/command.ipp +++ b/aedis/redis/impl/command.ipp @@ -13,8 +13,6 @@ namespace redis { char const* to_string(command c) { - assert(c != command::invalid); - static char const* table[] = { "ACL", "APPEND", @@ -220,6 +218,7 @@ char const* to_string(command c) "ZSCAN", "ZSCORE", "ZUNIONSTORE", + "INVALID", }; return table[static_cast(c)]; diff --git a/aedis/redis/receiver.hpp b/aedis/redis/receiver.hpp index 1440e7af..12e223c6 100644 --- a/aedis/redis/receiver.hpp +++ b/aedis/redis/receiver.hpp @@ -69,6 +69,9 @@ public: void on_read(command cmd) { + if (cmd == command::discard) + on_transaction_ = false; + if (on_transaction_) return; diff --git a/examples/aggregates.cpp b/examples/aggregates.cpp index 0da54d72..0c76a565 100644 --- a/examples/aggregates.cpp +++ b/examples/aggregates.cpp @@ -20,7 +20,7 @@ using aedis::resp3::node; using client_type = aedis::redis::client; using response_type = std::vector>; -// Prints aggregates that don't contain nested aggregates. +// Prints aggregates that don't contain any nested aggregates. void print_aggregate(response_type const& v) { auto const m = element_multiplicity(v.front().data_type); diff --git a/examples/chat_room.cpp b/examples/chat_room.cpp index 42936740..2bf6a107 100644 --- a/examples/chat_room.cpp +++ b/examples/chat_room.cpp @@ -31,7 +31,7 @@ private: public: myreceiver(std::shared_ptr db) : db_{db} {} - void on_message(command cmd) + void on_read(command cmd) { switch (cmd) { case command::hello: @@ -72,8 +72,8 @@ listener( for (;;) { auto socket = co_await acc->async_accept(net::use_awaitable); auto session = std::make_shared(std::move(socket)); - recv->add(session); session->start(on_user_msg); + recv->add(session); } } @@ -95,10 +95,7 @@ int main() co_spawn(ioc, listener(acc, db, recv), net::detached); net::signal_set signals(ioc.get_executor(), SIGINT, SIGTERM); - signals.async_wait([=] (auto, int) { - db->send(command::quit); - acc->cancel(); - }); + signals.async_wait([&] (auto, int) { ioc.stop(); }); ioc.run(); } catch (std::exception const& e) { diff --git a/examples/echo_server.cpp b/examples/echo_server.cpp index 26359a06..c01aa142 100644 --- a/examples/echo_server.cpp +++ b/examples/echo_server.cpp @@ -91,10 +91,7 @@ int main() co_spawn(ioc, listener(acc, db, recv), net::detached); net::signal_set signals(ioc.get_executor(), SIGINT, SIGTERM); - signals.async_wait([=] (auto, int) { - db->send(command::quit); - acc->cancel(); - }); + signals.async_wait([&] (auto, int) { ioc.stop(); }); ioc.run(); } catch (std::exception const& e) {