Skip to content

Commit

Permalink
Improvements in the docs and examples.
Browse files Browse the repository at this point in the history
  • Loading branch information
mzimbres committed Nov 27, 2022
1 parent e9dab97 commit 4ac2509
Show file tree
Hide file tree
Showing 14 changed files with 122 additions and 99 deletions.
8 changes: 5 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,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)
Expand All @@ -119,7 +123,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)
Expand All @@ -139,7 +142,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)
Expand Down
83 changes: 48 additions & 35 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ auto hgetall(std::shared_ptr<connection> conn) -> net::awaitable<void>
}
```
The execution of calls to `connection::async_exec` like above are
The execution of `connection::async_exec` as shown above is
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
Expand Down Expand Up @@ -68,22 +68,31 @@ reading from the socket. The reationale behind this design is
concurrently.

In the following sections we will discuss with more details the main
entities Aedis users are concerned with, namely
code entities Aedis users are concerned with, namely

* `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
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 to Redis 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.
* 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.

The next two examples uses the Aedis low-level API

* low_level_sync.cpp: Sends a ping synchronously.
* low_level_async.cpp: Sends a ping asynchronously

To avoid repetition code that is common to all examples have been
grouped in common.hpp.

<a name="requests"></a>
### Requests
Expand All @@ -93,25 +102,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);
```
Expand All @@ -121,9 +127,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.
Expand Down Expand Up @@ -175,18 +181,20 @@ 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
// Ignore the second and last responses.
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
---------|-------------------------------------|--------------
Expand Down Expand Up @@ -249,7 +257,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
Expand All @@ -258,15 +270,15 @@ 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

* `"SUBSCRIBE"`
* `"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;
Expand All @@ -290,19 +302,19 @@ 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.
#### 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");
Expand Down Expand Up @@ -342,9 +354,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)
Expand Down Expand Up @@ -421,15 +433,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
Expand Down
3 changes: 2 additions & 1 deletion examples/chat_room.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;;) {
Expand Down Expand Up @@ -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 common.cpp)
auto async_main() -> net::awaitable<void>
{
auto ex = co_await net::this_coro::executor;
Expand Down
1 change: 1 addition & 0 deletions examples/containers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 common.cpp)
net::awaitable<void> async_main()
{
auto conn = std::make_shared<connection>(co_await net::this_coro::executor);
Expand Down
2 changes: 2 additions & 0 deletions examples/echo_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 common.cpp)
auto async_main() -> net::awaitable<void>
{
auto ex = co_await net::this_coro::executor;
Expand Down
1 change: 1 addition & 0 deletions examples/intro.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ using namespace net::experimental::awaitable_operators;
using aedis::adapt;
using aedis::resp3::request;

// Called from the main function (see common.cpp)
net::awaitable<void> async_main()
{
request req;
Expand Down
36 changes: 11 additions & 25 deletions examples/low_level_async.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,33 @@
* accompanying file LICENSE.txt)
*/

#include <string>
#include <iostream>

#include <boost/asio.hpp>
#if defined(BOOST_ASIO_HAS_CO_AWAIT)

#include <aedis.hpp>
#include <aedis/src.hpp>
#include <string>
#include <iostream>

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);

Expand All @@ -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)
2 changes: 1 addition & 1 deletion examples/low_level_sync.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
Empty file removed examples/main.cpp
Empty file.
10 changes: 5 additions & 5 deletions examples/resolve_with_sentinel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@ 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;
};

// 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;
Expand All @@ -43,17 +43,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"}
Expand Down
Loading

0 comments on commit 4ac2509

Please sign in to comment.