From 75627cfadd5eeeba0b03eee54758d08065046290 Mon Sep 17 00:00:00 2001 From: Marcelo Zimbres Date: Sat, 13 Feb 2021 11:34:07 +0100 Subject: [PATCH] More progresses with response types. --- Makefile | 1 - README.md | 112 +++++++++++++++-------------- examples/async_basic.cpp | 32 +++------ examples/async_low_level.cpp | 4 +- examples/async_minimum.cpp | 38 ---------- include/aedis/connection.hpp | 8 +-- include/aedis/read.hpp | 4 +- include/aedis/receiver_base.hpp | 4 +- include/aedis/resp_types.hpp | 82 +++++++++++++++++++++ include/aedis/response.hpp | 51 +++---------- include/aedis/response_buffers.hpp | 2 +- include/aedis/response_types.hpp | 5 -- tests/general.cpp | 3 +- 13 files changed, 170 insertions(+), 176 deletions(-) delete mode 100644 examples/async_minimum.cpp create mode 100644 include/aedis/resp_types.hpp diff --git a/Makefile b/Makefile index 63443e90..1c1e53cb 100644 --- a/Makefile +++ b/Makefile @@ -23,7 +23,6 @@ LDFLAGS += -pthread examples = examples += sync_basic -examples += async_minimum examples += async_basic examples += async_all_hashes examples += async_low_level diff --git a/README.md b/README.md index 50c93af2..5b0370d4 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,71 @@ # Aedis -Aedis is a low level redis client designed for scalability and -performance while providing an easy and intuitive interface. Some of -the supported features are +Aedis is a redis client designed for scalability and performance while +providing an easy and intuitive interface. Some of the supported +features are * TLS, RESP3 and STL containers. * Pipelines (essential for performance). * Coroutines, futures and callbacks. -At the moment the biggest missing part is the Attribute data type as -its specification seems to be incomplete and I found no way to test -them. - ## Tutorial +All you have to do is to define an `enum` that defines the events you +want to handle, if any, and a receiver. For example + +```cpp + enum class events {one, two, three, ignore}; + + void f(request& req) + { + req.ping(events::one); + req.quit(); + } + + class receiver : public receiver_base { + private: + std::shared_ptr> conn_; + + public: + using event_type = events; + receiver(std::shared_ptr> conn) : conn_{conn} { } + + void on_hello(events ev, resp::array_type& v) noexcept override + { conn_->send(f); } + + void on_ping(events ev, resp::simple_string_type& s) noexcept override + { std::cout << "PING: " << s << std::endl; } + + void on_quit(events ev, resp::simple_string_type& s) noexcept override + { std::cout << "QUIT: " << s << std::endl; } + }; + +``` + +Inheriting from the `receiver_base` class is not needed but convenient +to avoid writing the complete receiver interface. In general for each +redis command you have to override (or offer) a member function in the +receiver. + +The main function looks like + +```cpp + int main() + { + net::io_context ioc {1}; + net::ip::tcp::resolver resolver{ioc}; + auto const results = resolver.resolve("127.0.0.1", "6379"); + auto conn = std::make_shared>(ioc); + receiver recv{conn}; + conn->start(recv, results); + ioc.run(); + } +``` + +## Low level API + +A low level api is also offered, though I don't recommend using it as +is offers no real advantage over the high level as shown above. Sending a command to a redis server is done as follows ```cpp @@ -81,6 +133,7 @@ net::awaitable example() } } ``` + The important things to notice above are * After connecting RESP3 requires the `hello` command to be sent. @@ -161,48 +214,3 @@ net::awaitable example() } } ``` - -## Reconnecting and Sentinel support - -In production we usually need a way to reconnect to the redis server -after a disconnect, some of the reasons are - -1. The server has crashed and has been restarted by systemd. -1. All connection have been killed by the admin. -1. A failover operation has started. - -It is easy to implement such a mechnism in scalable way using -coroutines, for example - -```cpp -net::awaitable example1() -{ - auto ex = co_await this_coro::executor; - for (;;) { - try { - resp::request req; - req.quit(); - - tcp::resolver resv(ex); - auto const r = resv.resolve("127.0.0.1", "6379"); - tcp_socket socket {ex}; - co_await async_connect(socket, r); - co_await async_write(socket, req); - - std::string buffer; - for (;;) { - resp::response_ignore res; - co_await resp::async_read(socket, buffer, res); - } - } catch (std::exception const& e) { - std::cerr << "Trying to reconnect ..." << std::endl; - stimer timer(ex, std::chrono::seconds{2}); - co_await timer.async_wait(); - } - } -} -``` - -More sophisticated reconnect strategies using sentinel are also easy -to implement using coroutines. - diff --git a/examples/async_basic.cpp b/examples/async_basic.cpp index 9a0bf3bb..589a7125 100644 --- a/examples/async_basic.cpp +++ b/examples/async_basic.cpp @@ -9,20 +9,16 @@ using namespace aedis; -/* This example shows how to receive and send events. - * - * 1. Create a connection obeject. - * - * 2. Start sending commands after the hello command has been - * received. - * - * As a rule, every redis command is received in a function named - * on_command. The user has to override the base class version to - * start receiving events. - */ +// This example shows how to receive and send events. enum class events {one, two, three, ignore}; +void f(request& req) +{ + req.ping(events::one); + req.quit(); +} + class receiver : public receiver_base { private: std::shared_ptr> conn_; @@ -32,16 +28,7 @@ public: receiver(std::shared_ptr> conn) : conn_{conn} { } void on_hello(events ev, resp::array_type& v) noexcept override - { - auto f = [](auto& req) - { - req.ping(events::one); - req.quit(); - }; - - conn_->disable_reconnect(); - conn_->send(f); - } + { conn_->send(f); } void on_ping(events ev, resp::simple_string_type& s) noexcept override { std::cout << "PING: " << s << std::endl; } @@ -60,6 +47,3 @@ int main() conn->start(recv, results); ioc.run(); } - - - diff --git a/examples/async_low_level.cpp b/examples/async_low_level.cpp index 83536ddd..a3329d6a 100644 --- a/examples/async_low_level.cpp +++ b/examples/async_low_level.cpp @@ -37,13 +37,13 @@ net::awaitable example() switch (req.events.front().second) { case events::one: { - resp::response_basic_array res; + resp::response_array res; co_await async_read(socket, buffer, res, net::use_awaitable); print(res.result, "one"); } break; case events::two: { - resp::response_basic_array res; + resp::response_array res; co_await async_read(socket, buffer, res, net::use_awaitable); print(res.result, "two"); } break; diff --git a/examples/async_minimum.cpp b/examples/async_minimum.cpp deleted file mode 100644 index 38f9bdb6..00000000 --- a/examples/async_minimum.cpp +++ /dev/null @@ -1,38 +0,0 @@ -/* Copyright (c) 2019 - 2021 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 - -using namespace aedis; - -/* This example shows the absolute minimum you need to stablish a - * connection with redis. - * - * 1. Write an enum class that defines your events. - * - * 2. Write a receiver. The receiver_base class below is not - * required if your receiver class supports the receiver - * concept. - * - * In the next examples we will see how to receive and write commands. - */ - -enum class events {one, two, three, ignore}; -struct receiver : public receiver_base { }; - -int main() -{ - net::io_context ioc {1}; - net::ip::tcp::resolver resolver{ioc}; - auto const results = resolver.resolve("127.0.0.1", "6379"); - auto conn = std::make_shared>(ioc); - receiver recv; - conn->start(recv, results); - ioc.run(); -} - diff --git a/include/aedis/connection.hpp b/include/aedis/connection.hpp index bf62c320..cc69764b 100644 --- a/include/aedis/connection.hpp +++ b/include/aedis/connection.hpp @@ -26,7 +26,7 @@ private: std::string buffer_; resp::response_buffers resps_; std::queue> reqs_; - bool reconnect_ = true; + bool reconnect_ = false; void reset() { @@ -48,7 +48,7 @@ private: std::chrono::seconds wait_interval {1}; boost::system::error_code ec; - while (reconnect_) { + do { co_await async_connect( socket_, results, @@ -75,7 +75,7 @@ private: co_await timer.async_wait(net::use_awaitable); continue; } - } + } while (reconnect_); } public: @@ -106,7 +106,7 @@ public: void send(Filler filler) { queue_writer(reqs_, filler, timer_); } - void disable_reconnect() + void enable_reconnect() { reconnect_ = false; } diff --git a/include/aedis/read.hpp b/include/aedis/read.hpp index 63abfc50..a3220374 100644 --- a/include/aedis/read.hpp +++ b/include/aedis/read.hpp @@ -318,7 +318,7 @@ async_reader( id.t = t; id.event = req.events.front().second; - auto* tmp = resps.get(id); + auto* tmp = resps.select(id); co_await async_read( socket, buffer, @@ -349,7 +349,7 @@ async_reader( } response_id_type id{cmd, t, req.events.front().second}; - auto* tmp = resps.get(id); + auto* tmp = resps.select(id); co_await async_read( socket, diff --git a/include/aedis/receiver_base.hpp b/include/aedis/receiver_base.hpp index f69e3edb..42a5634e 100644 --- a/include/aedis/receiver_base.hpp +++ b/include/aedis/receiver_base.hpp @@ -58,8 +58,8 @@ public: // TODO: Introduce a push type. virtual void on_push(Event ev, resp::array_type& v) noexcept { } - virtual void on_simple_error(command cmd, Event ev, resp::response_simple_error::data_type& v) noexcept { } - virtual void on_blob_error(command cmd, Event ev, resp::response_blob_error::data_type& v) noexcept { } + virtual void on_simple_error(command cmd, Event ev, resp::simple_error_type& v) noexcept { } + virtual void on_blob_error(command cmd, Event ev, resp::blob_error_type& v) noexcept { } virtual void on_null(command cmd, Event ev) noexcept { } }; diff --git a/include/aedis/resp_types.hpp b/include/aedis/resp_types.hpp new file mode 100644 index 00000000..fa237273 --- /dev/null +++ b/include/aedis/resp_types.hpp @@ -0,0 +1,82 @@ +/* Copyright (c) 2019 - 2021 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/. + */ + +#pragma once + +#include +#include + +namespace aedis { namespace resp { + +template > +using basic_array_type = std::vector; + +template > +using basic_map_type = std::vector; + +template > +using basic_set_type = std::vector; + +template< + class CharT, + class Traits = std::char_traits, + class Allocator = std::allocator> +using basic_streamed_string_part = std::basic_string; + +template< + class CharT, + class Traits = std::char_traits, + class Allocator = std::allocator> +using basic_verbatim_string = std::basic_string; + +template< + class CharT, + class Traits = std::char_traits, + class Allocator = std::allocator> +using basic_big_number = std::basic_string; + +template< + class CharT, + class Traits = std::char_traits, + class Allocator = std::allocator> +using basic_blob_string = std::basic_string; + +template< + class CharT, + class Traits = std::char_traits, + class Allocator = std::allocator> +using basic_blob_error = std::basic_string; + +template< + class CharT, + class Traits = std::char_traits, + class Allocator = std::allocator> +using basic_simple_string = std::basic_string; + +template< + class CharT, + class Traits = std::char_traits, + class Allocator = std::allocator> +using basic_simple_error = std::basic_string; + +using array_type = basic_array_type; +using map_type = basic_map_type; +using set_type = basic_set_type; + +using number_type = long long int; +using bool_type = bool; +using double_type = double; +using blob_string_type = basic_blob_string; +using blob_error_type = basic_blob_error; +using simple_string_type = basic_simple_string; +using simple_error_type = basic_simple_error; +using big_number_type = basic_big_number; +using verbatim_string_type = basic_verbatim_string; +using streamed_string_part_type = basic_streamed_string_part; + +} // resp +} // aedis diff --git a/include/aedis/response.hpp b/include/aedis/response.hpp index 2f3ad5e8..67bc02be 100644 --- a/include/aedis/response.hpp +++ b/include/aedis/response.hpp @@ -20,42 +20,12 @@ #include "config.hpp" #include "type.hpp" #include "command.hpp" +#include "resp_types.hpp" #include namespace aedis { namespace resp { -template > -using basic_array_type = std::vector; - -template > -using basic_map_type = std::vector; - -template > -using basic_set_type = std::vector; - -template< - class CharT, - class Traits = std::char_traits, - class Allocator = std::allocator> -using basic_blob_string_type = std::basic_string; - -template< - class CharT, - class Traits = std::char_traits, - class Allocator = std::allocator> -using basic_simple_string_type = std::basic_string; - -using array_type = basic_array_type; -using map_type = basic_map_type; -using set_type = basic_set_type; - -using number_type = long long int; -using bool_type = bool; -using double_type = double; -using blob_string_type = basic_blob_string_type; -using simple_string_type = basic_simple_string_type; - template std::enable_if::value, void>::type from_string_view(std::string_view s, T& n) @@ -211,7 +181,7 @@ private: void on_blob_string_impl(std::string_view s) override { from_string_view(s, result); } public: - basic_blob_string_type result; + basic_blob_string result; }; template< @@ -223,8 +193,7 @@ private: void on_blob_error_impl(std::string_view s) override { from_string_view(s, result); } public: - using data_type = std::basic_string; - data_type result; + basic_blob_error result; }; template< @@ -237,7 +206,7 @@ private: void on_simple_string_impl(std::string_view s) override { from_string_view(s, result); } public: - basic_simple_string_type result; + basic_simple_string result; }; template< @@ -251,8 +220,7 @@ private: { from_string_view(s, result); } public: - using data_type = std::basic_string; - data_type result; + basic_simple_error result; }; // Big number uses strings at the moment as the underlying storage. @@ -267,8 +235,7 @@ private: { from_string_view(s, result); } public: - using data_type = std::basic_string; - data_type result; + basic_big_number result; }; class response_double : public response_base { @@ -293,8 +260,7 @@ private: void on_verbatim_string_impl(std::string_view s) override { from_string_view(s, result); } public: - using data_type = std::basic_string; - data_type result; + basic_verbatim_string result; }; template< @@ -307,8 +273,7 @@ private: void on_streamed_string_part_impl(std::string_view s) override { result += s; } public: - using data_type = std::basic_string; - data_type result; + basic_streamed_string_part result; }; class response_bool : public response_base { diff --git a/include/aedis/response_buffers.hpp b/include/aedis/response_buffers.hpp index f63c1351..01922fe2 100644 --- a/include/aedis/response_buffers.hpp +++ b/include/aedis/response_buffers.hpp @@ -45,7 +45,7 @@ public: // When the id is from a transaction the type of the message is not // specified. template - response_base* get(response_id const& id) + response_base* select(response_id const& id) { if (id.cmd == command::exec) return &tree_; diff --git a/include/aedis/response_types.hpp b/include/aedis/response_types.hpp index c4302bf9..5852cc3f 100644 --- a/include/aedis/response_types.hpp +++ b/include/aedis/response_types.hpp @@ -18,16 +18,11 @@ using response_set = response_basic_set; using response_blob_string = response_basic_blob_string; using response_blob_error = response_basic_blob_error; using response_simple_string = response_basic_simple_string; - using response_simple_error = response_basic_simple_error; using response_big_number = response_basic_big_number; using response_verbatim_string = response_basic_verbatim_string; using response_streamed_string_part = response_basic_streamed_string_part; -using big_number_type = response_big_number::data_type; -using verbatim_string_type = response_verbatim_string::data_type; -using streamed_string_part_type = response_streamed_string_part::data_type; - } // resp } // aedis diff --git a/tests/general.cpp b/tests/general.cpp index df58c3d5..7848d3a0 100644 --- a/tests/general.cpp +++ b/tests/general.cpp @@ -80,11 +80,10 @@ public: req.quit(); }; - conn_->disable_reconnect(); conn_->send(f); } - void on_simple_error(command cmd, events ev, resp::response_simple_error::data_type& v) noexcept override + void on_simple_error(command cmd, events ev, resp::simple_error_type& v) noexcept override { std::cout << v << std::endl; }