From 1b15c2d1fe355b480025e0ec6292fbde41fd9116 Mon Sep 17 00:00:00 2001 From: Marcelo Zimbres Date: Sat, 5 Feb 2022 21:38:18 +0100 Subject: [PATCH] Some refactoring. --- Makefile.am | 11 +- aedis/aedis.hpp | 341 ++++++++++------------- aedis/config.hpp | 1 + aedis/redis/command.hpp | 8 +- aedis/redis/experimental/client.hpp | 56 ++-- aedis/redis/experimental/impl/client.ipp | 156 +++++++---- aedis/resp3/adapt.hpp | 46 ++- aedis/resp3/adapter/error.hpp | 2 +- aedis/resp3/error.hpp | 4 +- aedis/resp3/node.hpp | 12 +- aedis/resp3/response_traits.hpp | 2 +- aedis/resp3/serializer.hpp | 2 +- aedis/resp3/type.hpp | 8 +- aedis/sentinel/command.hpp | 6 +- configure.ac | 4 +- doc/Doxyfile | 6 +- doc/htmlheader.html | 2 +- examples/chat_room.cpp | 17 +- examples/echo_server.cpp | 19 +- examples/intro.cpp | 4 +- examples/lib/net_utils.hpp | 25 ++ examples/nested_response.cpp | 79 ------ examples/redis_client.cpp | 55 ---- 23 files changed, 403 insertions(+), 463 deletions(-) delete mode 100644 examples/nested_response.cpp delete mode 100644 examples/redis_client.cpp diff --git a/Makefile.am b/Makefile.am index 03ca5b9b..918ccd10 100644 --- a/Makefile.am +++ b/Makefile.am @@ -15,12 +15,12 @@ check_PROGRAMS += intro check_PROGRAMS += sets check_PROGRAMS += hashes check_PROGRAMS += serialization -check_PROGRAMS += nested_response +check_PROGRAMS += multipurpose_response check_PROGRAMS += lists check_PROGRAMS += key_expiration check_PROGRAMS += response_adapter check_PROGRAMS += sync -check_PROGRAMS += redis_client +check_PROGRAMS += multipurpose_client check_PROGRAMS += test_offline check_PROGRAMS += test_online check_PROGRAMS += transaction @@ -41,12 +41,12 @@ intro_SOURCES = $(top_srcdir)/examples/intro.cpp sets_SOURCES = $(top_srcdir)/examples/sets.cpp hashes_SOURCES = $(top_srcdir)/examples/hashes.cpp serialization_SOURCES = $(top_srcdir)/examples/serialization.cpp -nested_response_SOURCES = $(top_srcdir)/examples/nested_response.cpp +multipurpose_response_SOURCES = $(top_srcdir)/examples/multipurpose_response.cpp lists_SOURCES = $(top_srcdir)/examples/lists.cpp key_expiration_SOURCES = $(top_srcdir)/examples/key_expiration.cpp response_adapter_SOURCES = $(top_srcdir)/examples/response_adapter.cpp sync_SOURCES = $(top_srcdir)/examples/sync.cpp -redis_client_SOURCES = $(top_srcdir)/examples/redis_client.cpp +multipurpose_client_SOURCES = $(top_srcdir)/examples/multipurpose_client.cpp commands_SOURCES = $(top_srcdir)/tools/commands.cpp test_offline_SOURCES = $(top_srcdir)/tests/offline.cpp test_online_SOURCES = $(top_srcdir)/tests/online.cpp @@ -91,7 +91,6 @@ TESTS = $(check_PROGRAMS) EXTRA_DIST = EXTRA_DIST += $(top_srcdir)/README.md -EXTRA_DIST += $(top_srcdir)/doc/Doxyfile EXTRA_DIST += $(top_srcdir)/doc/DoxygenLayout.xml EXTRA_DIST += $(top_srcdir)/doc/aedis.css EXTRA_DIST += $(top_srcdir)/doc/htmlfooter.html @@ -99,6 +98,6 @@ EXTRA_DIST += $(top_srcdir)/doc/htmlheader.html .PHONY: doc doc: - rm -rf /tmp/aedis + rm -rf ../aedis-gh-pages/* doxygen doc/Doxyfile diff --git a/aedis/aedis.hpp b/aedis/aedis.hpp index b6e656b0..ce789e72 100644 --- a/aedis/aedis.hpp +++ b/aedis/aedis.hpp @@ -18,240 +18,187 @@ #include #include -/** \mainpage - * - * \b Overview - * - * Aedis is low-level redis client library built on top of Boost.Asio - * that implements communication with a Redis server over its native - * protocol RESP3. It has first-class support for STL containers and - * C++ built in types among other things. You will be able to - * implement your own redis client or use a general purpose provided - * by the library. For more information about Redis see - * https://redis.io/ - * - * \b Using \b Aedis - * - * - \subpage installation - * - \subpage examples - * - * \b Reference - * - * - \subpage enums - * - \subpage classes - * - \subpage functions - * - \subpage operators - */ +/** \mainpage Documentation + \tableofcontents + + \section Overview + + Aedis is low-level redis client library built on top of Boost.Asio + that implements communication with a Redis server over its native + protocol RESP3. It has first-class support for STL containers and + C++ built in types among other things. You will be able to + implement your own redis client or use a general purpose provided + by the library. For more information about Redis see + https://redis.io/ + + \section examples Examples -//--------------------------------------------------------- -// Pages + In general every feature offered by the library will be + accompained by an example showing how to use it. We also focus in + a more modern asynchronous programming with coroutines. -/** \page examples Examples - * - \b Basics: Focuses on small code snippets that show basic usage of - the library, for example: how to make a request and read the - response, how to deal with keys that may not exist, etc. + \subsection Basics - - intro.cpp + Focuses on small examples that show basic usage of + the library, for example, how to make a request and read the + response, how to deal with keys that may not exist, pubsub, etc. - Some commands are sent to the Redis server and the responses are - printed to screen. A good starting point. + - intro.cpp: A good starting point. Some commands are sent to the + Redis server and the responses are printed to screen. - - transaction.cpp + - transaction.cpp: Shows how to read the responses to a trasaction + efficiently. See also https://redis.io/topics/transactions. - Shows how to read the responses to all commands inside a - trasaction efficiently. At the moment this feature supports only - transactions that contain simple types or aggregates that don't - contain aggregates themselves (as in most cases). + - multipurpose_response.cpp: Shows how to read any responses to + Redis commands, including nested aggegagtes. - - nested_response.cpp - - Shows how to read responses to commands that cannot be - translated in a C++ built-in type like std::string or STL - containers, for example all commands contained in a transaction - will be nested by Redis in a single response. Users may have to - convert into their own desired format. + - subscriber.cpp: Shows how channel subscription works at a low + level. See also https://redis.io/topics/pubsub. - - subscriber.cpp + - sync.cpp: Shows hot to use the Aedis synchronous api. - Shows how channel subscription works at a low level. + - key_expiration.cpp: Shows how to use \c std::optional to deal + with keys that may have expired or do not exist. - - sync.cpp - - Shows hot to use the Aedis synchronous api. + \subsection stl-containers STL Containers - - key_expiration.cpp - - Shows how to use \c std::optional to deal with keys that may - have expired or do not exist. - - \b STL \b Containers: Many of the Redis data structures can be + 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 + - 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, + - 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 + - sets.cpp: Shows how to read Redis sets in a \c std::set, \c + std::unordered_set and \c std::vector. - Shows how to read Redis sets in a \c std::set, \c std::unordered_set - and \c std::vector. + \subsection customization-points Customization points - \b Customization \b points: Shows how de/serialize user types + 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 + - serialization.cpp: Shows how to de/serialize your own + non-aggregate data-structures. - Shows how to de/serialize your own non-aggregate data-structures. + - response_adapter.cpp: Customization point for users that want to + de/serialize their own data-structures like containers for example. - - response_adapter.cpp + \subsection async-servers Asynchronous servers - Customization point for users that want to de/serialize their - own data-structures like containers for example. - - \b Asynchronous \b servers: Contains some non-trivial examples + Contains some non-trivial examples servers that interact with users and Redis asynchronously over long lasting connections using a higher level API. - - redis_client.cpp + - multipurpose_client.cpp: Shows how to use and experimental high + level redis client that keeps a long lasting connections to a + redis server. This is the starting point for the next examples. - Shows how to use and experimental high level redis client that - keeps a long lasting connections to a redis server. This is the - starting point for the next examples. + - echo_server.cpp: Shows the basic principles behind asynchronous + communication with a database in an asynchronous server. In this + case, the server is a proxy between the user and Redis. - - echo_server.cpp + - chat_room.cpp: Shows how to build a scalable chat room that + scales to millions of users. - Shows the basic principles behind asynchronous communication - with a database in an asynchronous server. In this case, the - server is a proxy between the user and Redis. + \section using-aedis Using Aedis - - chat_room.cpp + This section contains instructions on how to install aedis on your machine among other things. - Shows how to build a scalable chat room that scales to - millions of users. + \subsection Requirements + + Aedis installation requires + + - Boost 1.78 or greater + - Unix Shell + - Make + + To use Aedis and build its examples and tests you will need + + - C++20 with coroutine support. + - Redis server. + + Some examples will also require interaction with + + - Redis-client: used in on example. + - Redis Sentinel server: used in some examples. + + \subsection Installation + + Start by downloading and configuring the library + + ``` + # Download the libray on github. + $ wget github-link + + # Uncompress the tarball and cd into the dir + $ tar -xzvf aedis-1.0.0.tar.gz && cd aedis-1.0.0 + + # Run configure with appropriate C++ flags and your boost installation, for example + $ CXXFLAGS="-std=c++20 -fcoroutines -g -Wall -Wno-subobject-linkage"\ + ./configure --prefix=/opt/aedis-1.0.0 --with-boost=/opt/boost_1_78_0 --with-boost-libdir=/opt/boost_1_78_0/lib + + ``` + + To install the library run + + ``` + # Install Aedis in the path specified in --prefix + $ sudo make install + + ``` + + At this point you can start using Aedis. To build the examples and + test you can also run + + ``` + # Build aedis examples. + $ make examples + + # Test aedis in your machine. + $ make check + ``` + + Finally you will have to include the following header + + ```cpp + #include + ``` + in exactly one source file in your applications. + + \subsubsection Windows + + Windows users can use aedis by either adding the project root + directory to their include path or manually copying to another + location. + + \subsection Developers + + To generate the build system run + + ``` + $ autoreconf -i + ``` + + After that you will have a config in the project dir that you can + run as explained above, for example, to use a compiler other that + the system compiler use + + ``` + CC=/opt/gcc-10.2.0/bin/gcc-10.2.0\ + CXX=/opt/gcc-10.2.0/bin/g++-10.2.0\ + CXXFLAGS="-std=c++20 -fcoroutines -g -Wall -Wno-subobject-linkage -Werror" ./configure ... + ``` + \section Referece + + See \subpage any. */ -/** \page installation Installation - * \tableofcontents - * - * \section Requirements - * - * Aedis installation requires - * - * - \b Boost \b 1.78 or greater - * - \b Unix \b Shell - * - \b Make - * - * To use Aedis and build its examples and tests you will need - * - * - \b C++20 with \b coroutine support. - * - \b Redis \b server. - * - * Some examples will also require interaction with - * - * - \b Redis-client: used in on example. - * - \b Redis \b Sentinel \b server: used in some examples. - * - * \section Installation - * - * Get the latest release and follow the steps - * - * ``` - * # Download the libray on github. - * $ wget github-link - * - * # Uncompress the tarball and cd into the dir - * $ tar -xzvf aedis-1.0.0.tar.gz && cd aedis-1.0.0 - * - * # Run configure with appropriate C++ flags and your boost installation, for example - * $ CXXFLAGS="-std=c++20 -fcoroutines -g -Wall -Wno-subobject-linkage"\ - * ./configure --prefix=/opt/aedis-1.0.0 --with-boost=/opt/boost_1_78_0 --with-boost-libdir=/opt/boost_1_78_0/lib - * - * ``` - * - * To install the library run - * - * ``` - * # Install Aedis in the path specified in --prefix - * $ sudo make install - * - * ``` - * - * To build the examples and test the library in your machine - * - * ``` - * # Build aedis examples. - * $ make examples - * - * # Test aedis in your machine. - * $ make check - * ``` - * - * \subsection Windows - * - * Windows users can use aedis by either adding the project root - * directory to their include path or manually copying to another - * location. - * - * \section using Using Aedis - * - * This library in not header-only. You have to include the following - * header - * - * ```cpp - * #include - * ``` - * - * in exactly one source file in your applications. - * - * \section Developers - * - * Aedis uses Autotools for its build system. To generate the build - * system run - * - * ``` - * $ autoreconf -i - * ``` - * - * After that you will have a config in the project dir that you can - * run as explained above, for example, to use a compiler other that - * the system compiler use - * - * ``` - * CC=/opt/gcc-10.2.0/bin/gcc-10.2.0\ - * CXX=/opt/gcc-10.2.0/bin/g++-10.2.0\ - * CXXFLAGS="-std=c++20 -fcoroutines -g -Wall -Wno-subobject-linkage -Werror" ./configure ... - * ``` +/** \defgroup any Reference */ -//--------------------------------------------------------- -// Groups - -/** \defgroup enums Enumerations - * \brief Enumerations defined by this library. - */ - - -/** \defgroup classes Classes - * \brief Classes defined by this library. - */ - - -/** \defgroup functions Free functions - * \brief All functions defined by this library. - */ - - -/** \defgroup operators Operators - * \brief Operators defined in Aedis - */ diff --git a/aedis/config.hpp b/aedis/config.hpp index 1b7be285..3a4425f3 100644 --- a/aedis/config.hpp +++ b/aedis/config.hpp @@ -7,6 +7,7 @@ #pragma once +// TODO: Remove this. #include namespace aedis { diff --git a/aedis/redis/command.hpp b/aedis/redis/command.hpp index 5cdcd2ee..18e1b4ce 100644 --- a/aedis/redis/command.hpp +++ b/aedis/redis/command.hpp @@ -16,7 +16,7 @@ namespace aedis { namespace redis { /** \brief Redis commands. - * \ingroup enums + * \ingroup any * * For a full list of commands see https://redis.io/commands. * @@ -437,7 +437,7 @@ enum class command { }; /** \brief Converts a command to a string - * \ingroup functions + * \ingroup any * * \param c The command to convert. */ @@ -452,12 +452,12 @@ char const* to_string(command c); std::ostream& operator<<(std::ostream& os, command c); /** \brief Returns true for commands with push response. - * \ingroup functions + * \ingroup any */ bool has_push_response(command cmd); /** \brief Creates a serializer for a \c std::string. - * \ingroup functions + * \ingroup any * \param storage The string. */ template diff --git a/aedis/redis/experimental/client.hpp b/aedis/redis/experimental/client.hpp index c15aa7f6..0fb565fc 100644 --- a/aedis/redis/experimental/client.hpp +++ b/aedis/redis/experimental/client.hpp @@ -18,22 +18,33 @@ namespace resp3 { namespace experimental { /** \brief A high level redis client. - * \ingroup classes + * \ingroup any * * This Redis client keeps a connection to the database open and - * manage reconnections. + * 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. + * + * \remarks This class reuses its internal buffers for requests and + * for reading Redis responses. With time it will allocate less and + * less. */ class client : public std::enable_shared_from_this { public: - /// The response adapter type. - using adapter_type = std::function; + /** \brief The extended response adapter type. + * + * The difference between the adapter and extended_adapter + * concepts is that the extended get a command redis::parameter. + */ + using extented_adapter_type = std::function; /// The type of the message callback. using on_message_type = std::function; -private: - using tcp_socket = net::use_awaitable_t<>::as_default_on_t; + /// The type of the socket used by the client. + //using socket_type = net::use_awaitable_t<>::as_default_on_t; + using socket_type = net::ip::tcp::socket; +private: struct request_info { // Request size in bytes. std::size_t size = 0; @@ -53,18 +64,21 @@ private: std::queue req_info_; // The stream. - tcp_socket socket_; + socket_type socket_; // Timer used to inform the write coroutine that it can write the // next message in the output queue. net::steady_timer timer_; // Response adapter. - adapter_type adapter_ = [](redis::command, type, std::size_t, std::size_t, char const*, std::size_t, std::error_code&) {}; + extented_adapter_type extended_adapter_ = [](redis::command, type, std::size_t, std::size_t, char const*, std::size_t, std::error_code&) {}; // Message callback. on_message_type on_msg_ = [](std::error_code ec, redis::command) {}; + // Set when the writer coroutine should stop. + bool stop_writer_ = false; + // A coroutine that keeps reading the socket. When a message // arrives it calls on_message. net::awaitable reader(); @@ -73,12 +87,6 @@ private: // to be sent. net::awaitable writer(); - net::awaitable say_hello(); - - // The connection manager. It keeps trying the reconnect to the - // server when the connection is lost. - net::awaitable connection_manager(); - /* Prepares the back of the queue to receive further commands. * * If true is returned the request in the front of the queue can be @@ -95,9 +103,21 @@ public: */ client(net::any_io_executor ex); - /** \brief Prepares the client for execution. + /// Returns the executor used for I/O with Redis. + auto get_executor() {return socket_.get_executor();} + + /** \brief Starts communication with Redis. + * + * This functions will send the hello command to Redis and spawn + * the read and write coroutines. + * + * \param socket A socket that is connected to redis. + * + * \returns This function returns an awaitable on which users should \c + * co_await. When the communication with Redis is lost the + * coroutine will finally co_return. */ - void prepare(); + net::awaitable engage(socket_type socket); /** \brief Adds a command to the command queue. * @@ -106,8 +126,8 @@ public: template void send(redis::command cmd, Ts const&... args); - /// Sets the response adapter. - void set_adapter(adapter_type adapter); + /// Sets an extended response adapter. + void set_extended_adapter(extented_adapter_type adapter); /// Sets the message callback; void set_msg_callback(on_message_type on_msg); diff --git a/aedis/redis/experimental/impl/client.ipp b/aedis/redis/experimental/impl/client.ipp index e0c67411..5ed2b5b1 100644 --- a/aedis/redis/experimental/impl/client.ipp +++ b/aedis/redis/experimental/impl/client.ipp @@ -18,16 +18,27 @@ namespace experimental { client::client(net::any_io_executor ex) : socket_{ex} , timer_{ex} -{ } +{ + timer_.expires_at(std::chrono::steady_clock::time_point::max()); +} net::awaitable client::reader() { // Writes and reads continuosly from the socket. for (std::string buffer;;) { - while (!std::empty(req_info_)) { - co_await net::async_write(socket_, net::buffer(requests_.data(), req_info_.front().size)); + // Notice this coro can get scheduled while the write operation + // in the writer is ongoing. so we have to check. + while (!std::empty(req_info_) && req_info_.front().size != 0) { + assert(!std::empty(requests_)); + boost::system::error_code ec; + co_await + net::async_write( + socket_, + net::buffer(requests_.data(), req_info_.front().size), + net::redirect_error(net::use_awaitable, ec)); requests_.erase(0, req_info_.front().size); + req_info_.front().size = 0; if (req_info_.front().cmds != 0) break; // We must await the responses. @@ -37,21 +48,41 @@ net::awaitable client::reader() do { // Keeps reading while there are no messages queued waiting to be sent. do { // Consumes the responses to all commands in the request. - auto const t = co_await async_read_type(socket_, net::dynamic_buffer(buffer)); + boost::system::error_code ec; + auto const t = + co_await async_read_type(socket_, net::dynamic_buffer(buffer), + net::redirect_error(net::use_awaitable, ec)); + if (ec) { + stop_writer_ = true; + timer_.cancel(); + co_return; + } + if (t == type::push) { auto adapter = [this](type t, std::size_t aggregate_size, std::size_t depth, char const* data, std::size_t size, std::error_code& ec) - {adapter_(redis::command::unknown, t, aggregate_size, depth, data, size, ec);}; + {extended_adapter_(redis::command::unknown, t, aggregate_size, depth, data, size, ec);}; - boost::system::error_code ec; co_await resp3::async_read(socket_, net::dynamic_buffer(buffer), adapter, net::redirect_error(net::use_awaitable, ec)); on_msg_(ec, redis::command::unknown); + if (ec) { // TODO: Return only on non aedis errors. + stop_writer_ = true; + timer_.cancel(); + co_return; + } + } else { auto adapter = [this](type t, std::size_t aggregate_size, std::size_t depth, char const* data, std::size_t size, std::error_code& ec) - {adapter_(commands_.front(), t, aggregate_size, depth, data, size, ec);}; + {extended_adapter_(commands_.front(), t, aggregate_size, depth, data, size, ec);}; boost::system::error_code ec; co_await resp3::async_read(socket_, net::dynamic_buffer(buffer), adapter, net::redirect_error(net::use_awaitable, ec)); on_msg_(ec, commands_.front()); + if (ec) { // TODO: Return only on non aedis errors. + stop_writer_ = true; + timer_.cancel(); + co_return; + } + commands_.pop(); --req_info_.front().cmds; } @@ -71,14 +102,33 @@ net::awaitable client::reader() net::awaitable client::writer() { + boost::system::error_code ec; while (socket_.is_open()) { - boost::system::error_code ec; + ec = {}; co_await timer_.async_wait(net::redirect_error(net::use_awaitable, ec)); - while (!std::empty(req_info_)) { - co_await net::async_write(socket_, net::buffer(requests_.data(), req_info_.front().size)); + if (stop_writer_) + co_return; + + // Notice this coro can get scheduled while the write operation + // in the reader is ongoing. so we have to check. + while (!std::empty(req_info_) && req_info_.front().size != 0) { + assert(!std::empty(requests_)); + ec = {}; + co_await net::async_write( + socket_, net::buffer(requests_.data(), req_info_.front().size), + net::redirect_error(net::use_awaitable, ec)); + if (ec) { + // What should we do here exactly? Closing the socket will + // cause the reader coroutine to return so that the engage + // coroutine returns to the user. + socket_.close(); + co_return; + } + requests_.erase(0, req_info_.front().size); + req_info_.front().size = 0; - if (req_info_.front().cmds != 0) + if (req_info_.front().cmds != 0) break; req_info_.pop(); @@ -86,44 +136,6 @@ net::awaitable client::writer() } } -net::awaitable client::say_hello() -{ - std::string request; - auto sr = redis::make_serializer(request); - sr.push(redis::command::hello, 3); - co_await net::async_write(socket_, net::buffer(request)); - - std::string buffer; - auto adapter = [this](type t, std::size_t aggregate_size, std::size_t depth, char const* data, std::size_t size, std::error_code& ec) - {adapter_(redis::command::hello, t, aggregate_size, depth, data, size, ec);}; - co_await resp3::async_read(socket_, net::dynamic_buffer(buffer), adapter); -} - -net::awaitable -client::connection_manager() -{ - using namespace aedis::net::experimental::awaitable_operators; - using tcp_resolver = aedis::net::use_awaitable_t<>::as_default_on_t; - - for (;;) { - tcp_resolver resolver{socket_.get_executor()}; - auto const res = co_await resolver.async_resolve("127.0.0.1", "6379"); - co_await net::async_connect(socket_, res); - - co_await say_hello(); - - timer_.expires_at(std::chrono::steady_clock::time_point::max()); - co_await (reader() && writer()); - - socket_.close(); - timer_.cancel(); - - timer_.expires_after(std::chrono::seconds{1}); - boost::system::error_code ec; - co_await timer_.async_wait(net::redirect_error(net::use_awaitable, ec)); - } -} - bool client::prepare_next() { if (std::empty(req_info_)) { @@ -131,7 +143,9 @@ bool client::prepare_next() return true; } - if (std::size(req_info_) == 1) { + if (req_info_.front().size == 0) { + // It has already been written and we are waiting for the + // responses. req_info_.push({}); return false; } @@ -139,16 +153,9 @@ bool client::prepare_next() return false; } -void client::prepare() +void client::set_extended_adapter(extented_adapter_type adapter) { - net::co_spawn(socket_.get_executor(), - [self = this->shared_from_this()]{ return self->connection_manager(); }, - net::detached); -} - -void client::set_adapter(adapter_type adapter) -{ - adapter_ = adapter; + extended_adapter_ = adapter; } void client::set_msg_callback(on_message_type on_msg) @@ -156,6 +163,37 @@ void client::set_msg_callback(on_message_type on_msg) on_msg_ = on_msg; } +net::awaitable client::engage(socket_type socket) +{ + using namespace aedis::net::experimental::awaitable_operators; + + socket_ = std::move(socket); + + std::string request; + auto sr = redis::make_serializer(request); + sr.push(redis::command::hello, 3); + + boost::system::error_code ec; + co_await net::async_write(socket_, net::buffer(request), net::redirect_error(net::use_awaitable, ec)); + if (ec) + co_return; + + std::string buffer; + auto adapter = [this](type t, std::size_t aggregate_size, std::size_t depth, char const* data, std::size_t size, std::error_code& ec) + {extended_adapter_(redis::command::hello, t, aggregate_size, depth, data, size, ec);}; + + co_await + resp3::async_read( + socket_, net::dynamic_buffer(buffer), adapter, net::redirect_error(net::use_awaitable, ec)); + + on_msg_(ec, redis::command::hello); + + if (ec) + co_return; + + co_await (reader() && writer()); +} + } // experimental } // resp3 } // aedis diff --git a/aedis/resp3/adapt.hpp b/aedis/resp3/adapt.hpp index 94861745..0da56e4f 100644 --- a/aedis/resp3/adapt.hpp +++ b/aedis/resp3/adapt.hpp @@ -13,7 +13,7 @@ namespace aedis { namespace resp3 { /** \brief Creates a void response adapter. - \ingroup functions + \ingroup any The adapter returned by this function ignores responses and is useful to avoid wasting time with responses which the user is @@ -30,32 +30,36 @@ auto adapt() noexcept { return response_traits::adapt(); } /** \brief Adapts user data to read operations. - * \ingroup functions + * \ingroup any * * For example * The following types are supported. * - * 1. Integer data types e.g. `int`, `unsigned`, etc. + * - Integer data types e.g. `int`, `unsigned`, etc. * - * 1. `std::string` + * - `std::string` * * We also support the following C++ containers * - * 1. `std::vector`. Can be used with any RESP3 aggregate type. + * - `std::vector`. Can be used with any RESP3 aggregate type. * - * 1. `std::deque`. Can be used with any RESP3 aggregate type. + * - `std::deque`. Can be used with any RESP3 aggregate type. * - * 1. `std::list`. Can be used with any RESP3 aggregate type. + * - `std::list`. Can be used with any RESP3 aggregate type. * - * 1. `std::set`. Can be used with RESP3 set type. + * - `std::set`. Can be used with RESP3 set type. * - * 1. `std::unordered_set`. Can be used with RESP3 set type. + * - `std::unordered_set`. Can be used with RESP3 set type. * - * 1. `std::map`. Can be used with RESP3 hash type. + * - `std::map`. Can be used with RESP3 hash type. * - * 1. `std::unordered_map`. Can be used with RESP3 hash type. + * - `std::unordered_map`. Can be used with RESP3 hash type. * - * All these types can be wrapped in an `std::optional`. + * All these types can be wrapped in an `std::optional`. This + * function also support \c std::tuple to read the response to + * tuples. At the moment this feature supports only transactions that + * contain simple types or aggregates that don't contain aggregates + * themselves (as in most cases). * * Example usage: * @@ -63,6 +67,24 @@ auto adapt() noexcept * std::unordered_map cont; * co_await async_read(socket, buffer, adapt(cont)); * @endcode + * + * For a transaction + * + * @code + sr.push(command::multi); + sr.push(command::ping, ...); + sr.push(command::incr, ...); + sr.push_range(command::rpush, ...); + sr.push(command::lrange, ...); + sr.push(command::incr, ...); + sr.push(command::exec); + + co_await async_write(socket, buffer(request)); + + // Reads the response to a transaction + std::tuple, int> execs; + co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(execs)); + * @endcode */ template auto adapt(T& t) noexcept diff --git a/aedis/resp3/adapter/error.hpp b/aedis/resp3/adapter/error.hpp index e80d5972..6cd6cbbc 100644 --- a/aedis/resp3/adapter/error.hpp +++ b/aedis/resp3/adapter/error.hpp @@ -7,7 +7,7 @@ namespace resp3 { namespace adapter { /** \brief Errors that may occurr when reading a response. - * \ingroup enums + * \ingroup any */ enum class error { diff --git a/aedis/resp3/error.hpp b/aedis/resp3/error.hpp index bd6c7c5d..3f3342e7 100644 --- a/aedis/resp3/error.hpp +++ b/aedis/resp3/error.hpp @@ -8,7 +8,7 @@ namespace aedis { namespace resp3 { /** \brief RESP3 parsing errors. - * \ingroup enums + * \ingroup any */ enum class error { @@ -54,7 +54,7 @@ std::error_category const& category() } // detail /** \brief Converts an error in an std::error_code object. - * \ingroup functions + * \ingroup any */ inline std::error_code make_error_code(error e) diff --git a/aedis/resp3/node.hpp b/aedis/resp3/node.hpp index 752aac95..37d85e94 100644 --- a/aedis/resp3/node.hpp +++ b/aedis/resp3/node.hpp @@ -16,7 +16,7 @@ namespace aedis { namespace resp3 { /** \brief A node in the response tree. - * \ingroup classes + * \ingroup any * * Redis responses are the pre-order view of the response tree (see * https://en.wikipedia.org/wiki/Tree_traversal#Pre-order,_NLR). @@ -36,31 +36,31 @@ struct node { }; /** \brief Converts the node to a string. - * \ingroup functions + * \ingroup any * * \param obj The node object. */ std::string to_string(node const& obj); /** \brief Compares a node for equality. - * \ingroup operators + * \ingroup any */ bool operator==(node const& a, node const& b); /** \brief Writes the node to the stream. - * \ingroup operators + * \ingroup any * * NOTE: Binary data is not converted to text. */ std::ostream& operator<<(std::ostream& os, node const& o); /** \brief Writes the response to the output stream - * \ingroup functions + * \ingroup any */ std::string to_string(std::vector const& vec); /** \brief Writes the response to the output stream - * \ingroup operators + * \ingroup any */ std::ostream& operator<<(std::ostream& os, std::vector const& r); diff --git a/aedis/resp3/response_traits.hpp b/aedis/resp3/response_traits.hpp index 9d78bcb0..94390f3a 100644 --- a/aedis/resp3/response_traits.hpp +++ b/aedis/resp3/response_traits.hpp @@ -27,7 +27,7 @@ namespace aedis { namespace resp3 { /** \brief Traits class for response objects. - * \ingroup classes + * \ingroup any */ template struct response_traits diff --git a/aedis/resp3/serializer.hpp b/aedis/resp3/serializer.hpp index 506c5d1d..e8175318 100644 --- a/aedis/resp3/serializer.hpp +++ b/aedis/resp3/serializer.hpp @@ -13,7 +13,7 @@ namespace aedis { namespace resp3 { /** @brief Creates a Redis request from user data. - * \ingroup classes + * \ingroup any * * A request is composed of one or more redis commands and is * referred to in the redis documentation as a pipeline, see diff --git a/aedis/resp3/type.hpp b/aedis/resp3/type.hpp index 55b3276b..36dba79d 100644 --- a/aedis/resp3/type.hpp +++ b/aedis/resp3/type.hpp @@ -15,7 +15,7 @@ namespace aedis { namespace resp3 { /** \brief RESP3 types - \ingroup enums + \ingroup any The RESP3 full specification can be found at https://github.com/antirez/RESP3/blob/74adea588783e463c7e84793b325b088fe6edd1c/spec.md */ @@ -57,7 +57,7 @@ enum class type }; /** \brief Returns the string representation of the type. - * \ingroup functions + * \ingroup any * \param t RESP3 type. */ char const* to_string(type t); @@ -70,13 +70,13 @@ char const* to_string(type t); std::ostream& operator<<(std::ostream& os, type t); /** \brief Returns true if the data type is an aggregate. - * \ingroup functions + * \ingroup any * \param t RESP3 type. */ bool is_aggregate(type t); /** @brief Returns the element multilicity. - * \ingroup functions + * \ingroup any * \param t RESP3 type. * * For type map and attribute this value is 2, all other types have diff --git a/aedis/sentinel/command.hpp b/aedis/sentinel/command.hpp index 442108c5..21e7c720 100644 --- a/aedis/sentinel/command.hpp +++ b/aedis/sentinel/command.hpp @@ -13,7 +13,7 @@ namespace aedis { namespace sentinel { /** \brief Sentinel commands. - * \ingroup enums + * \ingroup any * * For a full list of commands see https://redis.io/topics/sentinel * @@ -56,7 +56,7 @@ enum class command { }; /** \brief Converts a sentinel command to a string - * \ingroup functions + * \ingroup any * * \param c The command to convert. */ @@ -71,7 +71,7 @@ char const* to_string(command c); std::ostream& operator<<(std::ostream& os, command c); /** \brief Returns true for sentinel commands with push response. - * \ingroup functions + * \ingroup any */ bool has_push_response(command cmd); diff --git a/configure.ac b/configure.ac index c6518f2e..2f1428aa 100644 --- a/configure.ac +++ b/configure.ac @@ -1,5 +1,5 @@ AC_PREREQ([2.69]) -AC_INIT([aedis], [1.0.0], [mzimbres@gmail.com]) +AC_INIT([Aedis], [0.0.1], [mzimbres@gmail.com]) AC_CONFIG_MACRO_DIR([m4]) #AC_CONFIG_SRCDIR([src/aedis.cpp]) AC_CONFIG_HEADERS([config.h]) @@ -18,5 +18,5 @@ AC_CHECK_HEADER_STDBOOL AC_TYPE_UINT64_T AC_CHECK_TYPES([ptrdiff_t]) -AC_CONFIG_FILES([Makefile]) +AC_CONFIG_FILES([Makefile doc/Doxyfile]) AC_OUTPUT diff --git a/doc/Doxyfile b/doc/Doxyfile index 920e33a9..3459d496 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -38,13 +38,13 @@ PROJECT_NAME = "Aedis" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 1.0.0 +PROJECT_NUMBER = "0.0.1" # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a # quick idea about the purpose of the project. Keep the description short. -PROJECT_BRIEF = "A low level Redis client library" +PROJECT_BRIEF = "Low level Redis client library" # With the PROJECT_LOGO tag one can specify a logo or an icon that is included # in the documentation. The maximum height of the logo should not exceed 55 @@ -58,7 +58,7 @@ PROJECT_LOGO = # entered, it will be relative to the location where doxygen was started. If # left blank the current directory will be used. -OUTPUT_DIRECTORY = /home/marcelo/my/aedis-gh-pages +OUTPUT_DIRECTORY = ../aedis-gh-pages # If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- # directories (in 2 levels) under the output directory of each output format and diff --git a/doc/htmlheader.html b/doc/htmlheader.html index f0cc368d..513a191b 100644 --- a/doc/htmlheader.html +++ b/doc/htmlheader.html @@ -24,7 +24,7 @@ $extrastylesheet - + diff --git a/examples/chat_room.cpp b/examples/chat_room.cpp index f2161979..f0b95f8d 100644 --- a/examples/chat_room.cpp +++ b/examples/chat_room.cpp @@ -12,6 +12,7 @@ #include #include "lib/user_session.hpp" +#include "lib/net_utils.hpp" namespace net = aedis::net; using aedis::redis::command; @@ -59,7 +60,7 @@ public: resps_.clear(); } - auto get_adapter() + auto get_extended_adapter() { return [adapter = adapt(resps_)](command, type t, std::size_t aggregate_size, std::size_t depth, char const* data, std::size_t size, std::error_code& ec) mutable { return adapter(t, aggregate_size, depth, data, size, ec); }; @@ -69,6 +70,16 @@ public: { sessions_.push_back(session); } }; +net::awaitable connection_manager(std::shared_ptr db) +{ + try { + auto socket = co_await connect(); + co_await db->engage(std::move(socket)); + } catch (std::exception const& e) { + std::cerr << "Error: " << e.what() << std::endl; + } +} + net::awaitable listener() { auto ex = co_await net::this_coro::executor; @@ -79,10 +90,10 @@ net::awaitable listener() { recv->on_message(ec, cmd); }; auto db = std::make_shared(ex); - db->set_adapter(recv->get_adapter()); + db->set_extended_adapter(recv->get_extended_adapter()); db->set_msg_callback(on_db_msg); + net::co_spawn(ex, connection_manager(db), net::detached); db->send(command::subscribe, "channel"); - db->prepare(); auto on_user_msg = [db](std::string const& msg) { diff --git a/examples/echo_server.cpp b/examples/echo_server.cpp index 2cd8ccdd..3a3b9f1f 100644 --- a/examples/echo_server.cpp +++ b/examples/echo_server.cpp @@ -13,6 +13,7 @@ #include #include "lib/user_session.hpp" +#include "lib/net_utils.hpp" namespace net = aedis::net; using aedis::redis::command; @@ -51,13 +52,13 @@ public: { std::cout << "Echos so far: " << resps_.front().data << std::endl; } break; - default: { assert(false); } + default: { /* Ignore */; } } resps_.clear(); } - auto get_adapter() + auto get_extended_adapter() { return [adapter = adapt(resps_)](command, type t, std::size_t aggregate_size, std::size_t depth, char const* data, std::size_t size, std::error_code& ec) mutable { return adapter(t, aggregate_size, depth, data, size, ec); }; @@ -67,6 +68,16 @@ public: { sessions_.push(session); } }; +net::awaitable connection_manager(std::shared_ptr db) +{ + try { + auto socket = co_await connect(); + co_await db->engage(std::move(socket)); + } catch (std::exception const& e) { + std::cerr << "Error: " << e.what() << std::endl; + } +} + net::awaitable listener() { auto ex = co_await net::this_coro::executor; @@ -77,9 +88,9 @@ net::awaitable listener() { recv->on_message(ec, cmd); }; auto db = std::make_shared(ex); - db->set_adapter(recv->get_adapter()); + db->set_extended_adapter(recv->get_extended_adapter()); db->set_msg_callback(on_db_msg); - db->prepare(); + net::co_spawn(ex, connection_manager(db), net::detached); for (;;) { auto socket = co_await acceptor.async_accept(net::use_awaitable); diff --git a/examples/intro.cpp b/examples/intro.cpp index dc0a3efe..8685e318 100644 --- a/examples/intro.cpp +++ b/examples/intro.cpp @@ -51,8 +51,8 @@ net::awaitable ping() // Print the responses. std::cout - << "ping: " << ping << "\n" - << "incr: " << incr << "\n"; + << "ping: " << ping << "\n" + << "incr: " << incr << "\n"; } catch (std::exception const& e) { std::cerr << e.what() << std::endl; diff --git a/examples/lib/net_utils.hpp b/examples/lib/net_utils.hpp index 9919391d..6de0ad31 100644 --- a/examples/lib/net_utils.hpp +++ b/examples/lib/net_utils.hpp @@ -24,3 +24,28 @@ connect( co_return std::move(socket); } +//net::awaitable +//client::connection_manager() +//{ +// using namespace aedis::net::experimental::awaitable_operators; +// using tcp_resolver = aedis::net::use_awaitable_t<>::as_default_on_t; +// +// for (;;) { +// tcp_resolver resolver{socket_.get_executor()}; +// auto const res = co_await resolver.async_resolve("127.0.0.1", "6379"); +// co_await net::async_connect(socket_, res); +// +// co_await say_hello(); +// +// timer_.expires_at(std::chrono::steady_clock::time_point::max()); +// co_await (reader() && writer()); +// +// socket_.close(); +// timer_.cancel(); +// +// timer_.expires_after(std::chrono::seconds{1}); +// boost::system::error_code ec; +// co_await timer_.async_wait(net::redirect_error(net::use_awaitable, ec)); +// } +//} + diff --git a/examples/nested_response.cpp b/examples/nested_response.cpp deleted file mode 100644 index b6c71b4b..00000000 --- a/examples/nested_response.cpp +++ /dev/null @@ -1,79 +0,0 @@ -/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres at gmail dot com) - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - */ - -#include -#include -#include -#include - -#include -#include - -#include "lib/net_utils.hpp" - -namespace resp3 = aedis::resp3; -using aedis::redis::command; -using aedis::redis::make_serializer; -using resp3::adapt; -using resp3::node; - -namespace net = aedis::net; -using net::async_write; -using net::buffer; -using net::dynamic_buffer; - -net::awaitable nested_response() -{ - try { - auto socket = co_await connect(); - - auto list = {"one", "two", "three"}; - - std::string request; - auto sr = make_serializer(request); - sr.push(command::hello, 3); - sr.push(command::flushall); - sr.push(command::multi); - sr.push(command::ping, "Some message"); - sr.push(command::incr, "incr-key"); - sr.push_range(command::rpush, "list-key", std::cbegin(list), std::cend(list)); - sr.push(command::lrange, "list-key", 0, -1); - sr.push(command::exec); - sr.push(command::quit); - co_await async_write(socket, buffer(request)); - - // Expected responses. - std::vector exec; - - // Reads the response. - std::string buffer; - co_await resp3::async_read(socket, dynamic_buffer(buffer)); // hello - co_await resp3::async_read(socket, dynamic_buffer(buffer)); // flushall - co_await resp3::async_read(socket, dynamic_buffer(buffer)); // multi - co_await resp3::async_read(socket, dynamic_buffer(buffer)); // ping - co_await resp3::async_read(socket, dynamic_buffer(buffer)); // incr - co_await resp3::async_read(socket, dynamic_buffer(buffer)); // rpush - co_await resp3::async_read(socket, dynamic_buffer(buffer)); // lrange - co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(exec)); - co_await resp3::async_read(socket, dynamic_buffer(buffer)); // quit - - // Prints the response. - std::cout << "General format:\n"; - for (auto const& e: exec) std::cout << e << "\n"; - - } catch (std::exception const& e) { - std::cerr << e.what() << std::endl; - exit(EXIT_FAILURE); - } -} - -int main() -{ - net::io_context ioc; - co_spawn(ioc, nested_response(), net::detached); - ioc.run(); -} diff --git a/examples/redis_client.cpp b/examples/redis_client.cpp deleted file mode 100644 index 54ca5326..00000000 --- a/examples/redis_client.cpp +++ /dev/null @@ -1,55 +0,0 @@ -/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com) - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - */ - -#include - -#include -#include - -namespace net = aedis::net; -using aedis::redis::command; -using aedis::resp3::experimental::client; -using aedis::resp3::node; -using aedis::resp3::type; - -int main() -{ - try { - std::vector resps; - - auto on_msg = [&resps](std::error_code ec, command cmd) - { - if (ec) { - std::cerr << "Error: " << ec.message() << std::endl; - return; - } - - std::cout << cmd << ": " << resps.front().data << std::endl; - resps.clear(); - }; - - net::io_context ioc{1}; - - // This adapter uses the general response that is suitable for - // all commands, so the command parameter will be ignored. - auto adapter = [adapter = adapt(resps)](command, type t, std::size_t aggregate_size, std::size_t depth, char const* data, std::size_t size, std::error_code& ec) mutable - { return adapter(t, aggregate_size, depth, data, size, ec); }; - - auto db = std::make_shared(ioc.get_executor()); - db->set_adapter(adapter); - db->set_msg_callback(on_msg); - db->send(command::ping, "O rato roeu a roupa do rei de Roma"); - db->send(command::incr, "redis-client-counter"); - db->send(command::quit); - db->prepare(); - - ioc.run(); - } catch (std::exception const& e) { - std::cerr << e.what() << std::endl; - exit(EXIT_FAILURE); - } -}
$projectname  
$projectbrief
$projectname $projectnumber  
$projectbrief
$searchbox