mirror of
https://github.com/boostorg/redis.git
synced 2026-01-26 06:52:09 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
75f91f3b11 | ||
|
|
b9a23568e3 | ||
|
|
4ac2509afa |
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -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: |
|
||||
|
||||
2
.github/workflows/coverage.yml
vendored
2
.github/workflows/coverage.yml
vendored
@@ -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: |
|
||||
|
||||
@@ -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
128
README.md
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
8
examples/common/aedis.cpp
Normal file
8
examples/common/aedis.cpp
Normal 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>
|
||||
@@ -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
29
examples/common/main.cpp
Normal 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)
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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"}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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); }
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user