2
0
mirror of https://github.com/boostorg/redis.git synced 2026-01-26 06:52:09 +00:00

Compare commits

...

3 Commits

Author SHA1 Message Date
Marcelo Zimbres
75f91f3b11 v1.3.1 and build fixes. 2022-12-03 14:34:15 +01:00
Marcelo Zimbres
b9a23568e3 Many improvements in the examples. 2022-12-02 22:58:39 +01:00
Marcelo Zimbres
4ac2509afa Improvements in the docs and examples. 2022-11-27 21:59:02 +01:00
24 changed files with 244 additions and 207 deletions

View File

@@ -35,7 +35,7 @@ jobs:
uses: MarkusJx/install-boost@v2.3.0
id: install-boost
with:
boost_version: 1.79.0
boost_version: 1.80.0
platform_version: 22.04
- name: Run CMake
run: |

View File

@@ -30,7 +30,7 @@ jobs:
uses: MarkusJx/install-boost@v2.3.0
id: install-boost
with:
boost_version: 1.79.0
boost_version: 1.80.0
platform_version: 22.04
- name: Run CMake
run: |

View File

@@ -10,7 +10,7 @@ cmake_minimum_required(VERSION 3.14)
project(
Aedis
VERSION 1.3.0
VERSION 1.3.1
DESCRIPTION "A redis client designed for performance and scalability"
HOMEPAGE_URL "https://mzimbres.github.io/aedis"
LANGUAGES CXX
@@ -43,7 +43,7 @@ write_basic_package_version_file(
COMPATIBILITY AnyNewerVersion
)
find_package(Boost 1.79 REQUIRED)
find_package(Boost 1.80 REQUIRED)
include_directories(${Boost_INCLUDE_DIRS})
find_package(OpenSSL REQUIRED)
@@ -54,19 +54,25 @@ include_directories(include)
# Main function for the examples.
#=======================================================================
add_library(common STATIC examples/common.cpp)
add_library(common STATIC
examples/common/common.cpp
examples/common/main.cpp
examples/common/aedis.cpp
)
target_compile_features(common PUBLIC cxx_std_20)
# Executables
#=======================================================================
#add_executable(intro_sync examples/intro_sync.cpp) // Uncomment after update to Boost 1.80
add_executable(intro examples/intro.cpp)
target_link_libraries(intro common)
target_compile_features(intro PUBLIC cxx_std_20)
add_test(intro intro)
add_executable(intro_sync examples/intro_sync.cpp)
target_compile_features(intro_sync PUBLIC cxx_std_20)
add_test(intro_sync intro_sync)
add_executable(chat_room examples/chat_room.cpp)
target_compile_features(chat_room PUBLIC cxx_std_20)
target_link_libraries(chat_room common)
@@ -100,10 +106,14 @@ add_test(intro_tls intro_tls)
target_link_libraries(intro_tls OpenSSL::Crypto OpenSSL::SSL)
target_link_libraries(intro_tls common)
add_executable(low_level_async examples/low_level_async.cpp)
target_compile_features(low_level_async PUBLIC cxx_std_20)
add_test(low_level_async low_level_async)
target_link_libraries(low_level_async common)
add_executable(echo_server_client benchmarks/cpp/asio/echo_server_client.cpp)
add_executable(echo_server_direct benchmarks/cpp/asio/echo_server_direct.cpp)
add_executable(low_level_sync examples/low_level_sync.cpp)
add_executable(low_level_async examples/low_level_async.cpp)
add_executable(test_conn_exec tests/conn_exec.cpp)
add_executable(test_conn_push tests/conn_push.cpp)
add_executable(test_conn_quit tests/conn_quit.cpp)
@@ -119,7 +129,6 @@ add_executable(test_request tests/request.cpp)
target_compile_features(echo_server_client PUBLIC cxx_std_20)
target_compile_features(echo_server_direct PUBLIC cxx_std_20)
target_compile_features(low_level_sync PUBLIC cxx_std_17)
target_compile_features(low_level_async PUBLIC cxx_std_20)
target_compile_features(test_conn_exec PUBLIC cxx_std_20)
target_compile_features(test_conn_push PUBLIC cxx_std_20)
target_compile_features(test_conn_quit PUBLIC cxx_std_17)
@@ -139,7 +148,6 @@ target_link_libraries(test_conn_tls OpenSSL::Crypto OpenSSL::SSL)
#add_test(intro_sync intro_sync)
add_test(low_level_sync low_level_sync)
add_test(low_level_async low_level_async)
add_test(test_low_level test_low_level)
add_test(test_conn_exec test_conn_exec)
add_test(test_conn_push test_conn_push)

128
README.md
View File

@@ -16,7 +16,14 @@ Some of its distinctive features are
* Back pressure, cancellation and low latency.
In addition to that, Aedis hides most of the low-level Asio code away
from the user. For example, the coroutine below retrieves Redis hashes
from the user, which in the majority of the cases will be concerned
with three library entities
* `aedis::resp3::request`: A container of Redis commands.
* `aedis::adapt()`: A function that adapts data structures to receive Redis responses.
* `aedis::connection`: A connection to the Redis server.
For example, the coroutine below reads Redis [hashes](https://redis.io/docs/data-types/hashes/)
in a `std::map` and quits the connection (see containers.cpp)
```cpp
@@ -24,7 +31,6 @@ auto hgetall(std::shared_ptr<connection> conn) -> net::awaitable<void>
{
// A request contains multiple Redis commands.
request req;
req.get_config().cancel_on_connection_lost = true;
req.push("HELLO", 3);
req.push("HGETALL", "hset-key");
req.push("QUIT");
@@ -39,11 +45,10 @@ auto hgetall(std::shared_ptr<connection> conn) -> net::awaitable<void>
}
```
The execution of calls to `connection::async_exec` like above are
triggered by the `connection::async_run` member function, which is
required to be running concurrently for as long as the connection
stands. For example, the code below uses a short-lived connection to
execute the coroutine above
The execution of `connection::async_exec` as shown above must
still be triggered by the `connection::async_run` member function. For
example, the code below uses a short-lived connection to execute the
coroutine above
```cpp
net::awaitable<void> async_main()
@@ -53,7 +58,7 @@ net::awaitable<void> async_main()
// Resolves and connects (from examples/common.hpp to avoid vebosity)
co_await connect(conn, "127.0.0.1", "6379");
// Runs and executes the request.
// Runs hgetall (previous example).
co_await (conn->async_run() || hgetall(conn));
}
```
@@ -67,23 +72,25 @@ reading from the socket. The reationale behind this design is
* Support server pushes and requests in the same connection object,
concurrently.
In the following sections we will discuss with more details the main
entities Aedis users are concerned with, namely
Before we see with more detail how connections, requests and responses
work, users might find it useful to skim over the examples in order to
gain a better feeling about the library capabilities.
* `aedis::resp3::request`: A container of Redis commands.
* `aedis::adapt()`: A function that adapts data structures to receive Redis responses.
* `aedis::connection`: A connection to the Redis server.
before that however, users might find it helpful to skim over the
examples, to gain a better feeling about the library capabilities
* intro.cpp: The Aedis hello-world program. It sends one command to Redis and quits the connection.
* intro.cpp: The Aedis hello-world program. Sends one command and quits the connection.
* intro_tls.cpp: Same as intro.cpp but over TLS.
* containers.cpp: Shows how to send and receive stl containers and how to use transactions.
* intro_sync.cpp: Shows how to use the conneciton class synchronously.
* containers.cpp: Shows how to send and receive STL containers and how to use transactions.
* serialization.cpp: Shows how to serialize types using Boost.Json.
* subscriber.cpp: Shows how to implement pubsub that reconnects and resubscribes when the connection is lost.
* resolve_with_sentinel.cpp: Shows how to resolve a master address using sentinels.
* subscriber.cpp: Shows how to implement pubsub with reconnection re-subscription.
* echo_server.cpp: A simple TCP echo server.
* chat_room.cpp: A command line chat room built on Redis pubsub.
* chat_room.cpp: A command line chat built on Redis pubsub.
* low_level_sync.cpp: Sends a ping synchronously using the low-level API.
* low_level_async.cpp: Sends a ping asynchronously using the low-level API.
To avoid repetition code that is common to all examples have been
grouped in common.hpp. The main function used in some async examples
has been factored out in the main.cpp file.
<a name="requests"></a>
### Requests
@@ -93,25 +100,22 @@ Redis documentation they are called
[pipelines](https://redis.io/topics/pipelining)). For example
```cpp
// Some example containers.
std::list<std::string> list {...};
std::map<std::string, mystruct> map { ...};
request req;
// Command with variable length of arguments.
req.push("SET", "key", "some value", "EX", "2");
// Pushes a list.
std::list<std::string> list
{"channel1", "channel2", "channel3"};
req.push_range("SUBSCRIBE", list);
// Same as above but as an iterator range.
req.push_range("SUBSCRIBE", std::cbegin(list), std::cend(list));
// Pushes a map.
std::map<std::string, mystruct> map
{ {"key1", "value1"}
, {"key2", "value2"}
, {"key3", "value3"}};
req.push_range("HSET", "key", map);
```
@@ -121,9 +125,9 @@ Sending a request to Redis is performed with `aedis::connection::async_exec` as
#### Serialization
The `push` and `push_range` functions above work with integers
e.g. `int` and `std::string` out of the box. To send your own
data type define a `to_bulk` function like this
The `resp3::request::push` and `resp3::request::push_range` member functions work
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.
@@ -175,10 +179,11 @@ To read the response to this request users can use the following tuple
std::tuple<std::string, int, std::string>
```
The pattern may have become apparent to the user, the tuple must have
the same size as the request (exceptions below) and each element must
be able to store the response to the command it refers to. To ignore
responses to individual commands in the request use the tag
The pattern might have become apparent to the reader: the tuple must
have as many elements as the request has commands (exceptions below).
It is also necessary that each tuple element is capable of storing the
response to the command it refers to, otherwise an error will ocurr.
To ignore responses to individual commands in the request use the tag
`aedis::ignore`
```cpp
@@ -186,7 +191,8 @@ responses to individual commands in the request use the tag
std::tuple<std::string, aedis::ignore, std::string, aedis::ignore>
```
The following table provides the response types of some commands
The following table provides the resp3-types returned by some Redis
commands
Command | RESP3 type | Documentation
---------|-------------------------------------|--------------
@@ -249,7 +255,11 @@ If the intention is to ignore the response to all commands altogether
use `adapt()` without arguments instead
```cpp
// Uses the ignore adapter explicitly.
co_await conn->async_exec(req, adapt());
// Ignore adapter is also the default argument.
co_await conn->async_exec(req);
```
Responses that contain nested aggregates or heterogeneous data
@@ -258,7 +268,7 @@ of this writing, not all RESP3 types are used by the Redis server,
which means in practice users will be concerned with a reduced
subset of the RESP3 specification.
#### Push
#### Pushes
Commands that have push response like
@@ -266,7 +276,7 @@ Commands that have push response like
* `"PSUBSCRIBE"`
* `"UNSUBSCRIBE"`
must be not be included in the tuple. For example, the request below
must be **NOT** be included in the tuple. For example, the request below
```cpp
request req;
@@ -290,9 +300,9 @@ std::tuple<
std::optional<A>,
std::optional<B>,
...
> response;
> resp;
co_await conn->async_exec(req, adapt(response));
co_await conn->async_exec(req, adapt(resp));
```
Everything else stays pretty much the same.
@@ -300,9 +310,9 @@ Everything else stays pretty much the same.
#### Transactions
To read responses to transactions we must first observe that Redis will
queue its commands and send their responses to the user as elements
of an array, after the `EXEC` command comes. For example, to read
the response to this request
queue the transaction commands and send their individual responses as elements
of an array, the array is itself the response to the `EXEC` command.
For example, to read the response to this request
```cpp
req.push("MULTI");
@@ -342,9 +352,9 @@ For a complete example see containers.cpp.
As mentioned in \ref serialization, it is common practice to
serialize data before sending it to Redis e.g. as json strings.
For performance and convenience reasons, we may also want to
deserialize it directly in its final data structure when reading them
back from Redis. Aedis supports this use case by calling a user
provided `from_bulk` function while parsing the response. For example
deserialize responses directly in their final data structure. Aedis
supports this use case by calling a user provided `from_bulk` function
while parsing the response. For example
```cpp
void from_bulk(mystruct& obj, char const* p, std::size_t size, boost::system::error_code& ec)
@@ -421,15 +431,16 @@ The `aedis::connection` is a class that provides async-only
communication with a Redis server by means of three member
functions
* `connection::async_run`: Starts read and write operations and remains suspended until the connection it is lost.
* `connection::async_run`: Starts read and write operations and remains suspended until the connection is lost.
* `connection::async_exec`: Executes commands.
* `connection::async_receive`: Receives server-side pushes.
In general, these operations will be running concurrently in user
application, where, for example
1. **Run**: One coroutine will call `async_run`, perhaps in a loop and
with healthy checks.
1. **Run**: One coroutine will call `async_run`, perhaps with other
operations like healthy checks and in a loop to implement
reconnection.
2. **Execute**: Multiple coroutines will call `async_exec` independently
and without coordination (e.g. queuing).
3. **Receive**: One coroutine will loop on `async_receive` to receive
@@ -485,7 +496,7 @@ auto async_main() -> net::awaitable<void>
It is important to emphasize that Redis servers use the old
communication protocol RESP2 by default, therefore it is necessary to
send a `HELLO 3` command everytime a connection is established.
Another common scenarios for reconnection is, for example, a failover
Another common scenario for reconnection is, for example, a failover
with sentinels, covered in `resolve_with_sentinel.cpp` example.
#### Execute
@@ -600,7 +611,7 @@ co_await (conn.async_exec(...) || time.async_wait(...))
should last.
* The cancellation will be ignored if the request has already
been written to the socket.
* It is usually a better idea to have a healthy checker that adding
* It is usually a better idea to have a healthy checker than adding
per request timeout, see subscriber.cpp for an example.
```cpp
@@ -831,18 +842,18 @@ library, so you can starting using it right away by adding the
```cpp
#include <aedis/src.hpp>
```
in no more than one source file in your applications. For example, to
compile one of the examples manually
in no more than one source file in your applications. To build the
examples and test cmake is supported, for example
```cpp
g++ -std=c++20 -pthread -I/opt/boost_1_79_0/include/ -Iinclude -Iexamples examples/intro.cpp examples/common.cpp
BOOST_ROOT=/opt/boost_1_80_0 cmake --preset dev
```
The requirements for using Aedis are
- Boost 1.79 or greater.
- Boost 1.80 or greater.
- C++17 minimum.
- Redis 6 or higher (must support RESP3).
- Optionally also redis-cli and Redis Sentinel.
@@ -854,8 +865,7 @@ The following compilers are supported
## Acknowledgement
Acknowledgement to people that helped shape Aedis in one way or
another.
Acknowledgement to people that helped shape Aedis
* Richard Hodges ([madmongo1](https://github.com/madmongo1)): For very helpful support with Asio, the design of asynchronous programs, etc.
* Vinícius dos Santos Oliveira ([vinipsmaker](https://github.com/vinipsmaker)): For useful discussion about how Aedis consumes buffers in the read operation.
@@ -865,7 +875,7 @@ another.
## Changelog
### v1.3.0
### v1.3.0-1
* Removes automatic sending of the `HELLO` command. This can't be
implemented properly without bloating the connection class. It is

View File

@@ -10,7 +10,7 @@
#include <aedis.hpp>
#include <unistd.h>
#include "common.hpp"
#include "common/common.hpp"
namespace net = boost::asio;
using namespace net::experimental::awaitable_operators;
@@ -23,7 +23,7 @@ using aedis::resp3::node;
// Chat over Redis pubsub. To test, run this program from different
// terminals and type messages to stdin.
// Receives Redis server-side pushes.
// Receives Redis pushes.
auto receiver(std::shared_ptr<connection> conn) -> net::awaitable<void>
{
for (std::vector<node<std::string>> resp;;) {
@@ -55,6 +55,7 @@ auto subscriber(std::shared_ptr<connection> conn) -> net::awaitable<void>
co_await conn->async_exec(req);
}
// Called from the main function (see main.cpp)
auto async_main() -> net::awaitable<void>
{
auto ex = co_await net::this_coro::executor;

View File

@@ -0,0 +1,8 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#include <aedis.hpp>
#include <aedis/src.hpp>

View File

@@ -6,6 +6,7 @@
#include "common.hpp"
#include <boost/asio.hpp>
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
#include <boost/asio/experimental/awaitable_operators.hpp>
#include <iostream>
@@ -18,9 +19,6 @@ using aedis::resp3::request;
using aedis::adapt;
using aedis::operation;
// Include this in no more than one .cpp file.
#include <aedis/src.hpp>
namespace
{
auto redir(boost::system::error_code& ec)
@@ -74,21 +72,4 @@ connect(
throw std::runtime_error("Connect timeout");
}
extern net::awaitable<void> async_main();
// Main function used in our examples.
auto main() -> int
{
try {
net::io_context ioc;
net::co_spawn(ioc, async_main(), net::detached);
ioc.run();
} catch (std::exception const& e) {
std::cerr << "Error: " << e.what() << std::endl;
return 1;
}
}
#else // defined(BOOST_ASIO_HAS_CO_AWAIT)
auto main() -> int {std::cout << "Requires coroutine support." << std::endl; return 0;}
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)

29
examples/common/main.cpp Normal file
View File

@@ -0,0 +1,29 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#include <iostream>
#include <boost/asio.hpp>
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
namespace net = boost::asio;
extern net::awaitable<void> async_main();
// The main function used in our examples.
auto main() -> int
{
try {
net::io_context ioc;
net::co_spawn(ioc, async_main(), net::detached);
ioc.run();
} catch (std::exception const& e) {
std::cerr << "Error: " << e.what() << std::endl;
return 1;
}
}
#else // defined(BOOST_ASIO_HAS_CO_AWAIT)
auto main() -> int {std::cout << "Requires coroutine support." << std::endl; return 0;}
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)

View File

@@ -11,7 +11,7 @@
#include <map>
#include <vector>
#include "common.hpp"
#include "common/common.hpp"
namespace net = boost::asio;
using namespace net::experimental::awaitable_operators;
@@ -92,6 +92,7 @@ auto transaction(std::shared_ptr<connection> conn) -> net::awaitable<void>
print(std::get<1>(std::get<4>(resp)).value());
}
// Called from the main function (see main.cpp)
net::awaitable<void> async_main()
{
auto conn = std::make_shared<connection>(co_await net::this_coro::executor);

View File

@@ -8,7 +8,7 @@
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
#include <boost/asio/experimental/awaitable_operators.hpp>
#include <aedis.hpp>
#include "common.hpp"
#include "common/common.hpp"
namespace net = boost::asio;
using namespace net::experimental::awaitable_operators;
@@ -35,6 +35,7 @@ auto echo_server_session(tcp_socket socket, std::shared_ptr<connection> conn) ->
}
}
// Listens for tcp connections.
auto listener(std::shared_ptr<connection> conn) -> net::awaitable<void>
{
auto ex = co_await net::this_coro::executor;
@@ -43,6 +44,7 @@ auto listener(std::shared_ptr<connection> conn) -> net::awaitable<void>
net::co_spawn(ex, echo_server_session(co_await acc.async_accept(), conn), net::detached);
}
// Called from the main function (see main.cpp)
auto async_main() -> net::awaitable<void>
{
auto ex = co_await net::this_coro::executor;

View File

@@ -8,14 +8,15 @@
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
#include <boost/asio/experimental/awaitable_operators.hpp>
#include <aedis.hpp>
#include "common.hpp"
#include "common/common.hpp"
namespace net = boost::asio;
using namespace net::experimental::awaitable_operators;
using aedis::adapt;
using aedis::resp3::request;
net::awaitable<void> async_main()
// Called from the main function (see main.cpp)
auto async_main() -> net::awaitable<void>
{
request req;
req.get_config().cancel_on_connection_lost = true;

View File

@@ -10,23 +10,21 @@
#include <boost/asio.hpp>
#include <aedis.hpp>
// TODO: Fix this after updating to 1.80.
// Include this in no more than one .cpp file.
#include <aedis/src.hpp>
namespace net = boost::asio;
using aedis::adapt;
using aedis::resp3::request;
using connection = aedis::connection<>;
using connection = aedis::connection;
template <class Adapter>
auto exec(connection& conn, request const& req, Adapter adapter, boost::system::error_code& ec)
auto exec(std::shared_ptr<connection> conn, request const& req, Adapter adapter)
{
net::dispatch(
conn.get_executor(),
net::deferred([&]() { return conn.async_exec(req, adapter, net::deferred); }))
(net::redirect_error(net::use_future, ec)).get();
conn->get_executor(),
net::deferred([&]() { return conn->async_exec(req, adapter, net::deferred); }))
(net::use_future).get();
}
auto logger = [](auto const& ec)
@@ -37,9 +35,12 @@ int main()
try {
net::io_context ioc{1};
connection conn{ioc};
std::thread t{[&]() {
conn.async_run(logger);
auto conn = std::make_shared<connection>(ioc);
net::ip::tcp::resolver resv{ioc};
auto const res = resv.resolve("127.0.0.1", "6379");
net::connect(conn->next_layer(), res);
std::thread t{[conn, &ioc]() {
conn->async_run(logger);
ioc.run();
}};
@@ -49,13 +50,10 @@ int main()
req.push("PING");
req.push("QUIT");
boost::system::error_code ec;
std::tuple<std::string, aedis::ignore> resp;
exec(conn, req, adapt(resp), ec);
std::tuple<aedis::ignore, std::string, aedis::ignore> resp;
exec(conn, req, adapt(resp));
std::cout
<< "Exec: " << ec.message() << "\n"
<< "Response: " << std::get<0>(resp) << std::endl;
std::cout << "Response: " << std::get<1>(resp) << std::endl;
t.join();
} catch (std::exception const& e) {

View File

@@ -4,32 +4,33 @@
* accompanying file LICENSE.txt)
*/
#include <boost/asio.hpp>
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
#include <aedis.hpp>
#include <string>
#include <iostream>
#include <boost/asio.hpp>
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
#include <aedis.hpp>
#include <aedis/src.hpp>
namespace net = boost::asio;
namespace resp3 = aedis::resp3;
using endpoints = net::ip::tcp::resolver::results_type;
using resolver = net::use_awaitable_t<>::as_default_on_t<net::ip::tcp::resolver>;
using tcp_socket = net::use_awaitable_t<>::as_default_on_t<net::ip::tcp::socket>;
using aedis::resp3::request;
using aedis::adapter::adapt2;
using net::ip::tcp;
net::awaitable<void> ping(endpoints const& addrs)
auto async_main() -> net::awaitable<void>
{
tcp_socket socket{co_await net::this_coro::executor};
net::connect(socket, addrs);
auto ex = co_await net::this_coro::executor;
resolver resv{ex};
auto const addrs = co_await resv.async_resolve("127.0.0.1", "6379");
tcp_socket socket{ex};
co_await net::async_connect(socket, addrs);
// Creates the request and writes to the socket.
request req;
req.push("HELLO", 3);
req.push("PING");
req.push("PING", "Hello world");
req.push("QUIT");
co_await resp3::async_write(socket, req);
@@ -45,19 +46,4 @@ net::awaitable<void> ping(endpoints const& addrs)
std::cout << "Ping: " << resp << std::endl;
}
int main()
{
try {
net::io_context ioc;
net::ip::tcp::resolver resv{ioc};
auto const addrs = resv.resolve("127.0.0.1", "6379");
net::co_spawn(ioc, ping(addrs), net::detached);
ioc.run();
} catch (std::exception const& e) {
std::cerr << "Error: " << e.what() << std::endl;
}
}
#else // defined(BOOST_ASIO_HAS_CO_AWAIT)
auto main() -> int {std::cout << "Requires coroutine support." << std::endl; return 0;}
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)

View File

@@ -31,7 +31,7 @@ int main()
// Creates the request and writes to the socket.
request req;
req.push("HELLO", 3);
req.push("PING");
req.push("PING", "Hello world");
req.push("QUIT");
resp3::write(socket, req);

View File

View File

@@ -8,7 +8,8 @@
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
#include <boost/asio/experimental/awaitable_operators.hpp>
#include <aedis.hpp>
#include "common.hpp"
#include "common/common.hpp"
namespace net = boost::asio;
using namespace net::experimental::awaitable_operators;
@@ -19,7 +20,7 @@ using aedis::resp3::request;
auto redir(boost::system::error_code& ec)
{ return net::redirect_error(net::use_awaitable, ec); }
struct endpoint {
struct address {
std::string host;
std::string port;
};
@@ -27,7 +28,7 @@ struct endpoint {
// For more info see
// - https://redis.io/docs/manual/sentinel.
// - https://redis.io/docs/reference/sentinel-clients.
auto resolve_master_address(std::vector<endpoint> const& endpoints) -> net::awaitable<endpoint>
auto resolve_master_address(std::vector<address> const& endpoints) -> net::awaitable<address>
{
request req;
req.get_config().cancel_on_connection_lost = true;
@@ -43,17 +44,17 @@ auto resolve_master_address(std::vector<endpoint> const& endpoints) -> net::awai
co_await (conn->async_run() && conn->async_exec(req, adapt(addr), redir(ec)));
conn->reset_stream();
if (std::get<0>(addr))
co_return endpoint{std::get<0>(addr).value().at(0), std::get<0>(addr).value().at(1)};
co_return address{std::get<0>(addr).value().at(0), std::get<0>(addr).value().at(1)};
}
co_return endpoint{};
co_return address{};
}
auto async_main() -> net::awaitable<void>
{
// A list of sentinel addresses from which only one is responsive
// to simulate sentinels that are down.
std::vector<endpoint> const endpoints
std::vector<address> const endpoints
{ {"foo", "26379"}
, {"bar", "26379"}
, {"127.0.0.1", "26379"}

View File

@@ -9,13 +9,13 @@
#include <boost/asio/experimental/awaitable_operators.hpp>
#include <boost/json.hpp>
#include <aedis.hpp>
#include "common.hpp"
#include <algorithm>
#include <cstdint>
#include <iostream>
#include <set>
#include <iterator>
#include <string>
#include "common/common.hpp"
// Include this in no more than one .cpp file.
#include <boost/json/src.hpp>
@@ -30,8 +30,23 @@ struct user {
std::string name;
std::string age;
std::string country;
friend auto operator<(user const& a, user const& b)
{
return std::tie(a.name, a.age, a.country) < std::tie(b.name, b.age, b.country);
}
friend auto operator<<(std::ostream& os, user const& u) -> std::ostream&
{
os << "Name: " << u.name << "\n"
<< "Age: " << u.age << "\n"
<< "Country: " << u.country;
return os;
}
};
// Boost.Json serialization.
void tag_invoke(value_from_tag, value& jv, user const& u)
{
jv =
@@ -57,33 +72,18 @@ auto tag_invoke(value_to_tag<user>, value const& jv)
return u;
}
// Serializes
// Aedis serialization
void to_bulk(std::pmr::string& to, user const& u)
{
aedis::resp3::to_bulk(to, serialize(value_from(u)));
}
// Deserializes
void from_bulk(user& u, boost::string_view sv, boost::system::error_code&)
{
value jv = parse(sv);
u = value_to<user>(jv);
}
auto operator<<(std::ostream& os, user const& u) -> std::ostream&
{
os << "Name: " << u.name << "\n"
<< "Age: " << u.age << "\n"
<< "Country: " << u.country;
return os;
}
auto operator<(user const& a, user const& b)
{
return std::tie(a.name, a.age, a.country) < std::tie(b.name, b.age, b.country);
}
net::awaitable<void> async_main()
{
std::set<user> users

View File

@@ -9,11 +9,10 @@
#include <boost/asio/experimental/awaitable_operators.hpp>
#include <aedis.hpp>
#include "common.hpp"
#include "common/common.hpp"
namespace net = boost::asio;
using namespace net::experimental::awaitable_operators;
using resolver = net::use_awaitable_t<>::as_default_on_t<net::ip::tcp::resolver>;
using signal_set_type = net::use_awaitable_t<>::as_default_on_t<net::signal_set>;
using timer_type = net::use_awaitable_t<>::as_default_on_t<net::steady_timer>;
using aedis::adapt;
@@ -46,6 +45,16 @@ auto receiver(std::shared_ptr<connection> conn) -> net::awaitable<void>
}
}
auto subscriber(std::shared_ptr<connection> conn) -> net::awaitable<void>
{
request req;
req.get_config().cancel_on_connection_lost = true;
req.push("HELLO", 3);
req.push("SUBSCRIBE", "channel");
co_await conn->async_exec(req);
}
auto async_main() -> net::awaitable<void>
{
auto ex = co_await net::this_coro::executor;
@@ -53,16 +62,12 @@ auto async_main() -> net::awaitable<void>
signal_set_type sig{ex, SIGINT, SIGTERM};
timer_type timer{ex};
request req;
req.get_config().cancel_on_connection_lost = true;
req.push("HELLO", 3);
req.push("SUBSCRIBE", "channel");
// 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 ((conn->async_run() || healthy_checker(conn) || sig.async_wait() ||
receiver(conn)) && conn->async_exec(req));
receiver(conn)) && subscriber(conn));
conn->reset_stream();
timer.expires_after(std::chrono::seconds{1});
co_await timer.async_wait();

View File

@@ -210,7 +210,7 @@ inline auto adapt(std::size_t max_read_size = (std::numeric_limits<std::size_t>:
* 2. std::vector<node<String>>
*
* The types T1, T2, etc can be any STL container, any integer type
* and \c std::string
* and `std::string`.
*
* @param t Tuple containing the responses.
* @param max_read_size Specifies the maximum size of the read

View File

@@ -47,7 +47,7 @@ public:
using base_type = detail::connection_base<executor_type, basic_connection<AsyncReadWriteStream>>;
/// Constructor
/// Contructs from an executor.
explicit
basic_connection(
executor_type ex,
@@ -56,6 +56,7 @@ public:
, stream_{ex}
{}
/// Contructs from a context.
explicit
basic_connection(
boost::asio::io_context& ioc,
@@ -82,10 +83,11 @@ public:
/// Returns a const reference to the next layer.
auto next_layer() const noexcept -> auto const& { return stream_; }
/** @brief Establishes a connection with the Redis server asynchronously.
/** @brief Starts read and write operations
*
* This function will start reading from the socket and executes
* all requests that have been started prior to this function
* This function starts read and write operations with the Redis
* server. More specifically it will trigger the write of all
* requests i.e. calls to `async_exec` that happened prior to this
* call.
*
* @param token Completion token.
@@ -109,10 +111,10 @@ public:
/** @brief Executes a command on the Redis server asynchronously.
*
* This function will send a request to the Redis server and
* complete when the response arrives. If the request contains
* only commands that don't expect a response, the completion
* occurs after it has been written to the underlying stream.
* Multiple concurrent calls to this function will be
* complete after the response has been processed. If the request
* contains only commands that don't expect a response, the
* completion occurs after it has been written to the underlying
* stream. Multiple concurrent calls to this function will be
* automatically queued by the implementation.
*
* @param req Request object.
@@ -177,12 +179,7 @@ public:
* @li operation::run: Cancels the `async_run` operation. Notice
* that the preferred way to close a connection is to send a
* [QUIT](https://redis.io/commands/quit/) command to the server.
* An unresponsive Redis server will also cause the idle-checks to
* timeout and lead to `connection::async_run` completing with
* `error::idle_timeout`. Calling `cancel(operation::run)`
* directly should be seen as the last option.
* @li operation::receive: Cancels any ongoing callto
* `async_receive`.
* @li operation::receive: Cancels any ongoing calls to * `async_receive`.
*
* @param op: The operation to be cancelled.
* @returns The number of operations that have been canceled.

View File

@@ -38,10 +38,10 @@ struct node {
String value{};
};
/** @brief Converts the node to a string.
* @relates node
/** \brief Converts the node to a string.
* \relates node
*
* @param in The node object.
* \param in The node object.
*/
template <class String>
auto to_string(node<String> const& in)

View File

@@ -171,43 +171,40 @@ void add_separator(Request& to)
*
* \li Non-string types will be converted to string by using \c
* to_bulk, which must be made available over ADL.
* \li Uses std::string as internal storage.
* \li Uses a std::pmr::string for internal storage.
*/
class request {
public:
/// Request configuration options.
struct config {
/** \brief If set to true, requests started with
* `aedis::connection::async_exec` will fail if the connection is
* lost while the request is pending. The default
/** \brief If true the request will complete with error if the
* connection is lost while the request is pending. The default
* behaviour is not to close requests.
*/
bool cancel_on_connection_lost = false;
/** \brief If true this request will be coalesced with other requests,
* see https://redis.io/topics/pipelining. If false, this
* request will be sent individually.
/** \brief If true the request will be coalesced with other requests,
* see https://redis.io/topics/pipelining. Otherwise the
* request is sent individually.
*/
bool coalesce = true;
/** \brief If set to true, requests started with
* `aedis::connection::async_exec` will fail if the call happens
* before the connection with Redis was stablished.
/** \brief If true, the request will complete with error if the
* call happens before the connection with Redis was stablished.
*/
bool cancel_if_not_connected = false;
/** \brief If true, the implementation will resend this
* request if it remained unresponded when
* `aedis::connection::async_run` completed. Has effect only if
* request if it remains unresponded when
* `aedis::connection::async_run` completes. Has effect only if
* cancel_on_connection_lost is true.
*/
bool retry = true;
/** \brief If this request has a HELLO command and this flag is
* set to true, the `aedis::connection` will move it to the
* front of the queue of awaiting requests. This makes it
* possible to send HELLO and authenticate before other
* commands are sent.
* true, the `aedis::connection` will move it to the front of
* the queue of awaiting requests. This makes it possible to
* send HELLO and authenticate before other commands are sent.
*/
bool hello_with_priority = true;
};
@@ -239,7 +236,7 @@ public:
commands_ = 0;
}
/// Calls std::string::reserve on the internal storage.
/// Calls std::pmr::string::reserve on the internal storage.
void reserve(std::size_t new_cap = 0)
{ payload_.reserve(new_cap); }

View File

@@ -893,10 +893,22 @@ BOOST_AUTO_TEST_CASE(type_convert)
BOOST_AUTO_TEST_CASE(adapter)
{
using aedis::adapt;
using resp3::type;
boost::system::error_code ec;
std::string a;
int b;
auto resp = std::tie(a, b, std::ignore);
std::string s;
auto resp = std::tie(s, std::ignore);
auto f = adapt(resp);
(void)f;
f(0, resp3::node<boost::string_view>{type::simple_string, 1, 0, "Hello"}, ec);
f(1, resp3::node<boost::string_view>{type::number, 1, 0, "42"}, ec);
BOOST_CHECK_EQUAL(a, "Hello");
BOOST_TEST(!ec);
BOOST_CHECK_EQUAL(b, 42);
BOOST_TEST(!ec);
}