mirror of
https://github.com/boostorg/redis.git
synced 2026-01-19 04:42:09 +00:00
Progresses with TLS support and improvements in the docs.
This commit is contained in:
@@ -13,6 +13,8 @@
|
||||
* Adds example on how to resolve addresses over sentinels, see
|
||||
subscriber_sentinel.cpp.
|
||||
|
||||
* Adds a `connection::config::resp3_handshake_timeout`.
|
||||
|
||||
* Adds `endpoint` where in addition to host and port, users can also
|
||||
optionally provide username and password that are needed to connect
|
||||
to the Redis server and the expected server role (see
|
||||
|
||||
@@ -71,6 +71,7 @@ add_executable(test_connection_hello tests/connection_hello.cpp)
|
||||
add_executable(test_connection_push tests/connection_push.cpp)
|
||||
add_executable(test_connection_quit tests/connection_quit.cpp)
|
||||
add_executable(test_connection_reconnect tests/connection_reconnect.cpp)
|
||||
add_executable(test_connection_tls tests/connection_tls.cpp)
|
||||
|
||||
target_compile_features(chat_room PUBLIC cxx_std_20)
|
||||
target_compile_features(echo_server PUBLIC cxx_std_20)
|
||||
@@ -98,6 +99,7 @@ add_test(test_connection_hello test_connection_hello)
|
||||
add_test(test_connection_push test_connection_push)
|
||||
add_test(test_connection_quit test_connection_quit)
|
||||
add_test(test_connection_reconnect test_connection_reconnect)
|
||||
add_test(test_connection_tls test_connection_tls)
|
||||
|
||||
# Install
|
||||
#=======================================================================
|
||||
|
||||
621
README.md
621
README.md
@@ -1,20 +1,615 @@
|
||||
|
||||
# Documentation
|
||||
|
||||
##### Table of Contents
|
||||
|
||||
* [Overview](#overview)
|
||||
* [Tutorial](#tutorial)
|
||||
* [Async](#async)
|
||||
* [Reconnection](#reconnection)
|
||||
* [Sync](#sync)
|
||||
* [Request](#requests)
|
||||
* [Serialization](#serialization)
|
||||
* [Responses](#responses)
|
||||
* [Null](#responses)
|
||||
* [Transactions](#transactions)
|
||||
* [Deserialization](#deserialization)
|
||||
* [General responses](#general-resps)
|
||||
* [Installation](#installation)
|
||||
* [Why Aedis](#why-aedis)
|
||||
* [Benchmarks](benchmarks/benchmarks.md)
|
||||
* [Redis-plus-plus](#why-aedis)
|
||||
* [Build status](#build-status)
|
||||
* [Changelog](CHANGELOG.md)
|
||||
* [Reference](#any)
|
||||
* [Acknowledgement](#acknowledgement)
|
||||
|
||||
<a name="overview"/>
|
||||
## Overview
|
||||
|
||||
Aedis is a high-level [Redis](https://redis.io/) client library
|
||||
built on top of
|
||||
[Asio](https://www.boost.org/doc/libs/release/doc/html/boost_asio.html).
|
||||
Some of its distinctive features are
|
||||
|
||||
* Support for the latest version of the Redis communication protocol [RESP3](https://github.com/redis/redis-specifications/blob/master/protocol/RESP3.md).
|
||||
* First class support for STL containers and C++ built-in types.
|
||||
* Serialization and deserialization of your own data types.
|
||||
* Healthy checks, back pressure and low latency.
|
||||
* Support for TLS and Redis sentinel.
|
||||
|
||||
If you are unfamiliar with Redis, the best place to start is https://redis.io, in short
|
||||
|
||||
* In-memory data structure server with support for strings, hashes, lists, sets, sorted sets, streams, and more.
|
||||
* Keeps the dataset in memory for fast access, but can also persist all writes to permanent storage to survive reboots and system failures.
|
||||
* Replication with automatic failover for both standalone and clustered deployments.
|
||||
* More than 56k github stars.
|
||||
* Five times voted as the most loved database on stackoverflow.
|
||||
|
||||
Inpacient users can skim over the examples before proceeding to the next sections
|
||||
|
||||
* intro.cpp: This is the Aedis hello-world program. It sends one command to Redis and quits the connection.
|
||||
* intro_sync.cpp: Synchronous version of intro.cpp.
|
||||
* containers.cpp: Shows how to send and receive stl containers. Also shows 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.
|
||||
* subscriber_sentinel.cpp: Subscriber that resolves the master address with sentinels.
|
||||
* subscriber_sync.cpp: Synchronous version of subscriber.cpp.
|
||||
* echo_server.cpp: A simple TCP echo server that uses coroutines.
|
||||
* chat_room.cpp: A simple chat room that uses coroutines.
|
||||
|
||||
<a name="tutorial"/>
|
||||
## Tutorial
|
||||
|
||||
The support for synchronous communication with Redis is based on
|
||||
the asynchronous interface, therefore we will start with the later
|
||||
here.
|
||||
|
||||
<a name="async"/>
|
||||
### Async
|
||||
|
||||
The basic Aedis functionality resolves around calling these three
|
||||
functions
|
||||
|
||||
* `connection::async_run`: Stablishes a connection the Redis
|
||||
server and performs healthy checks.
|
||||
* `connection::async_exec`: Send commands and receive responses.
|
||||
* `connection::async_receive_push`: Receives server side pushes.
|
||||
|
||||
For example, in the simplest cases when no reconnection is required,
|
||||
calling `connection::async_run` can be as simple as
|
||||
|
||||
```cpp
|
||||
auto main() -> int
|
||||
{
|
||||
using connection = aedis::connection<net::ip::tcp::socket>;
|
||||
|
||||
try {
|
||||
net::io_context ioc;
|
||||
connection conn{ioc};
|
||||
endpoint ep{"127.0.0.1", "6379"};
|
||||
|
||||
conn.async_run([](auto const& ec) {
|
||||
std::clog << ec.message() << std::endl;
|
||||
});
|
||||
|
||||
// Spawn other operations that use the conn object.
|
||||
net::co_spawn(ioc, receive_pushes(conn), net::detached);
|
||||
net::co_spawn(ioc, send_commands(conn), net::detached);
|
||||
...
|
||||
|
||||
ioc.run();
|
||||
} catch (...) {
|
||||
std::cerr << "Error" << std::endl;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`async_run` completes only when the connection is lost and does
|
||||
not affect any other operation that is happening on the stream,
|
||||
like `async_exec` or `async_receive_push`. An example implementation of
|
||||
`receive_pushes(conn)` could looks like this
|
||||
|
||||
```cpp
|
||||
net::awaitable<void> receive_pushes(connection& db)
|
||||
{
|
||||
for (std::vector<node<std::string>> resp;;) {
|
||||
co_await db->async_receive_push(adapt(resp));
|
||||
// Process the push in resp.
|
||||
resp.clear();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`send_commands` on the other hand could be
|
||||
|
||||
```cpp
|
||||
net::awaitable<void> send_commands(connection& db)
|
||||
{
|
||||
request req;
|
||||
std::tuple<std::string> resp;
|
||||
req.push("PING", "some message");
|
||||
co_await db->async_exec(req, adapt(resp));
|
||||
}
|
||||
```
|
||||
|
||||
<a name="reconnection"/>
|
||||
#### Reconnection
|
||||
|
||||
In the majority of cases users will want to reconnect after a
|
||||
connection is lost, since the `run`, `exec` and `receive_push`
|
||||
operations are independent from one another, reconnection can be
|
||||
implemented as a simple loop arount `async_run`
|
||||
|
||||
```cpp
|
||||
net::awaitable<void> reconnect(std::shared_ptr<connection> db)
|
||||
{
|
||||
net::steady_timer timer{co_await net::this_coro::executor};
|
||||
endpoint ep{"127.0.0.1", "6379"};
|
||||
for (;;) {
|
||||
boost::system::error_code ec;
|
||||
co_await db->async_run(ep, req, adapt(), net::redirect_error(net::use_awaitable, ec));
|
||||
std::cout << ec.message() << std::endl;
|
||||
db->reset_stream();
|
||||
timer.expires_after(std::chrono::seconds{1});
|
||||
co_await timer.async_wait(net::use_awaitable);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
More complex scenarios like performing a failover with sentinels
|
||||
can be seen in the example subscriber_sentinel.cpp.
|
||||
|
||||
<a name="sync"/>
|
||||
### Sync
|
||||
|
||||
Synchronous communication is supported in Aedis by the wrapper class `aedis::sync`.
|
||||
|
||||
<a name="requests"/>
|
||||
### Requests
|
||||
|
||||
Redis requests are composed of one of more Redis commands (in
|
||||
Redis documentation they are called
|
||||
[pipelines](https://redis.io/topics/pipelining)). For example
|
||||
|
||||
```cpp
|
||||
request req;
|
||||
|
||||
// Command with variable length of arguments.
|
||||
req.push("SET", "key", "some value", 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_range2("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);
|
||||
```
|
||||
|
||||
Sending a request to Redis is then peformed with the following function
|
||||
|
||||
```cpp
|
||||
co_await db->async_exec(req, adapt(resp));
|
||||
```
|
||||
|
||||
<a name="serialization"/>
|
||||
#### 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 defined a `to_bulk` function like this
|
||||
|
||||
```cpp
|
||||
struct mystruct {
|
||||
// Example struct.
|
||||
};
|
||||
|
||||
void to_bulk(std::string& to, mystruct const& obj)
|
||||
{
|
||||
std::string dummy = "Dummy serializaiton string.";
|
||||
aedis::resp3::to_bulk(to, dummy);
|
||||
}
|
||||
```
|
||||
|
||||
Once `to_bulk` is defined and accessible over ADL `mystruct` can
|
||||
be passed to the `request`
|
||||
|
||||
```cpp
|
||||
request req;
|
||||
|
||||
std::map<std::string, mystruct> map {...};
|
||||
|
||||
req.push_range("HSET", "key", map);
|
||||
```
|
||||
|
||||
Example serialization.cpp shows how store json string in Redis.
|
||||
|
||||
<a name="responses"/>
|
||||
### Responses
|
||||
|
||||
To read responses effectively, users must know their RESP3 type,
|
||||
this can be found in the Redis documentation for each command
|
||||
(https://redis.io/commands). For example
|
||||
|
||||
Command | RESP3 type | Documentation
|
||||
---------|-------------------------------------|--------------
|
||||
lpush | Number | https://redis.io/commands/lpush
|
||||
lrange | Array | https://redis.io/commands/lrange
|
||||
set | Simple-string, null or blob-string | https://redis.io/commands/set
|
||||
get | Blob-string | https://redis.io/commands/get
|
||||
smembers | Set | https://redis.io/commands/smembers
|
||||
hgetall | Map | https://redis.io/commands/hgetall
|
||||
|
||||
Once the RESP3 type of a given response is known we can choose a
|
||||
proper C++ data structure to receive it in. Fortunately, this is a
|
||||
simple task for most types. The table below summarises the options
|
||||
|
||||
RESP3 type | Possible C++ type | Type
|
||||
---------------|--------------------------------------------------------------|------------------
|
||||
Simple-string | `std::string` | Simple
|
||||
Simple-error | `std::string` | Simple
|
||||
Blob-string | `std::string`, `std::vector` | Simple
|
||||
Blob-error | `std::string`, `std::vector` | Simple
|
||||
Number | `long long`, `int`, `std::size_t`, `std::string` | Simple
|
||||
Double | `double`, `std::string` | Simple
|
||||
Null | `std::optional<T>` | Simple
|
||||
Array | `std::vector`, `std::list`, `std::array`, `std::deque` | Aggregate
|
||||
Map | `std::vector`, `std::map`, `std::unordered_map` | Aggregate
|
||||
Set | `std::vector`, `std::set`, `std::unordered_set` | Aggregate
|
||||
Push | `std::vector`, `std::map`, `std::unordered_map` | Aggregate
|
||||
|
||||
For example
|
||||
|
||||
```cpp
|
||||
request req;
|
||||
req.push("HELLO", 3);
|
||||
req.push_range("RPUSH", "key1", vec);
|
||||
req.push_range("HSET", "key2", map);
|
||||
req.push("LRANGE", "key3", 0, -1);
|
||||
req.push("HGETALL", "key4");
|
||||
req.push("QUIT");
|
||||
|
||||
std::tuple<
|
||||
aedis::ignore, // hello
|
||||
int, // rpush
|
||||
int, // hset
|
||||
std::vector<T>, // lrange
|
||||
std::map<U, V>, // hgetall
|
||||
std::string // quit
|
||||
> resp;
|
||||
|
||||
co_await db->async_exec(req, adapt(resp));
|
||||
```
|
||||
|
||||
The tag @c aedis::ignore can be used to ignore individual
|
||||
elements in the responses. If the intention is to ignore the
|
||||
response to all commands in the request use @c adapt()
|
||||
|
||||
```cpp
|
||||
co_await db->async_exec(req, adapt());
|
||||
```
|
||||
|
||||
Responses that contain nested aggregates or heterogeneous data
|
||||
types will be given special treatment later in \ref gen-case. As
|
||||
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.
|
||||
|
||||
<a name="null"/>
|
||||
#### Null
|
||||
|
||||
It is not uncommon for apps to access keys that do not exist or
|
||||
that have already expired in the Redis server, to deal with these
|
||||
cases Aedis provides support for `std::optional`. To use it,
|
||||
wrap your type around `std::optional` like this
|
||||
|
||||
```cpp
|
||||
std::optional<std::unordered_map<T, U>> resp;
|
||||
co_await db->async_exec(req, adapt(resp));
|
||||
```
|
||||
|
||||
Everything else stays the same.
|
||||
|
||||
<a name="transactions"/>
|
||||
#### Transactions
|
||||
|
||||
To read the response to transactions we have to observe that Redis
|
||||
queues the commands as they arrive and sends the responses back to
|
||||
the user as an array, in the response to the @c exec command.
|
||||
For example, to read the response to the this request
|
||||
|
||||
```cpp
|
||||
db.send("MULTI");
|
||||
db.send("GET", "key1");
|
||||
db.send("LRANGE", "key2", 0, -1);
|
||||
db.send("HGETALL", "key3");
|
||||
db.send("EXEC");
|
||||
```
|
||||
|
||||
use the following response type
|
||||
|
||||
```cpp
|
||||
using aedis::ignore;
|
||||
|
||||
using exec_resp_type =
|
||||
std::tuple<
|
||||
std::optional<std::string>, // get
|
||||
std::optional<std::vector<std::string>>, // lrange
|
||||
std::optional<std::map<std::string, std::string>> // hgetall
|
||||
>;
|
||||
|
||||
std::tuple<
|
||||
ignore, // multi
|
||||
ignore, // get
|
||||
ignore, // lrange
|
||||
ignore, // hgetall
|
||||
exec_resp_type, // exec
|
||||
> resp;
|
||||
|
||||
co_await db->async_exec(req, adapt(resp));
|
||||
```
|
||||
|
||||
Note that above we are not ignoring the response to the commands
|
||||
themselves but whether they have been successfully queued. For a
|
||||
complete example see containers.cpp.
|
||||
|
||||
<a name="deserialization"/>
|
||||
#### Deserialization
|
||||
|
||||
As mentioned in \ref requests-serialization, it is common to
|
||||
serialize data before sending it to Redis e.g. to json strings.
|
||||
For performance and convenience reasons, we may also want to
|
||||
deserialize it directly in its 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)
|
||||
{
|
||||
// Deserializes p into obj.
|
||||
}
|
||||
```
|
||||
|
||||
After that, you can start receiving data efficiently in the desired
|
||||
types e.g. `mystruct`, `std::map<std::string, mystruct>` etc.
|
||||
|
||||
<a name="general-resps"/>
|
||||
#### The general case
|
||||
|
||||
There are cases where responses to Redis
|
||||
commands won't fit in the model presented above, some examples are
|
||||
|
||||
* Commands (like `set`) whose responses don't have a fixed
|
||||
RESP3 type. Expecting an `int` and receiving a blob-string
|
||||
will result in error.
|
||||
* RESP3 aggregates that contain nested aggregates can't be read in STL containers.
|
||||
* Transactions with a dynamic number of commands can't be read in a `std::tuple`.
|
||||
|
||||
To deal with these cases Aedis provides the `resp3::node`
|
||||
type, that is the most general form of an element in a response,
|
||||
be it a simple RESP3 type or an aggregate. It is defined like this
|
||||
|
||||
```cpp
|
||||
template <class String>
|
||||
struct node {
|
||||
// The RESP3 type of the data in this node.
|
||||
type data_type;
|
||||
|
||||
// The number of elements of an aggregate (or 1 for simple data).
|
||||
std::size_t aggregate_size;
|
||||
|
||||
// The depth of this node in the response tree.
|
||||
std::size_t depth;
|
||||
|
||||
// The actual data. For aggregate types this is always empty.
|
||||
String value;
|
||||
};
|
||||
```
|
||||
|
||||
Any response to a Redis command can be received in a
|
||||
`std::vector<node<std::string>>`. The vector can be seen as a
|
||||
pre-order view of the response tree. Using it is no different than
|
||||
using other types
|
||||
|
||||
```cpp
|
||||
// Receives any RESP3 simple data type.
|
||||
node<std::string> resp;
|
||||
co_await db->async_exec(req, adapt(resp));
|
||||
|
||||
// Receives any RESP3 simple or aggregate data type.
|
||||
std::vector<node<std::string>> resp;
|
||||
co_await db->async_exec(req, adapt(resp));
|
||||
```
|
||||
|
||||
For example, suppose we want to retrieve a hash data structure
|
||||
from Redis with `HGETALL`, some of the options are
|
||||
|
||||
* `std::vector<node<std::string>`: Works always.
|
||||
* `std::vector<std::string>`: Efficient and flat, all elements as string.
|
||||
* `std::map<std::string, std::string>`: Efficient if you need the data as a `std::map`.
|
||||
* `std::map<U, V>`: Efficient if you are storing serialized data. Avoids temporaries and requires `from_bulk` for `U` and `V`.
|
||||
|
||||
In addition to the above users can also use unordered versions of the containers. The same reasoning also applies to sets e.g. `SMEMBERS`.
|
||||
|
||||
<a name="installation"/>
|
||||
## Installation
|
||||
|
||||
Download the latest Aedis release from github
|
||||
|
||||
```cpp
|
||||
$ wget https://github.com/mzimbres/aedis/releases/download/v1.0.0/aedis-1.0.0.tar.gz
|
||||
```
|
||||
|
||||
and unpack in your prefered location. Aedis is a header only
|
||||
library, so you can starting using it. For that include the
|
||||
following header
|
||||
|
||||
```cpp
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
```
|
||||
in no more than one source file in your applications (see
|
||||
intro.cpp for example). To build the examples, run the tests etc.
|
||||
cmake is also supported
|
||||
|
||||
```cpp
|
||||
$ BOOST_ROOT=/opt/boost_1_79_0/ cmake
|
||||
$ make
|
||||
$ make test
|
||||
```
|
||||
|
||||
Notice you have to specify the compiler flags manually.
|
||||
|
||||
These are the requirements for using Aedis
|
||||
|
||||
- Boost 1.78 or greater.
|
||||
- C++17. Some examples require C++20 with coroutine support.
|
||||
- Redis 6 or higher. Optionally also redis-cli and Redis Sentinel.
|
||||
|
||||
The following compilers are supported
|
||||
|
||||
- Tested with gcc: 10, 11, 12.
|
||||
- Tested with clang: 11, 13, 14.
|
||||
|
||||
|
||||
<a name="why-aedis"/>
|
||||
## Why Aedis
|
||||
|
||||
At the time of this writing there are seventeen Redis clients
|
||||
listed in the [official](https://redis.io/docs/clients/#cpp) list.
|
||||
With so many clients available it is not unlikely that users are
|
||||
asking themselves why yet another one. In this section I will try
|
||||
to compare Aedis with the most popular clients and why we need
|
||||
Aedis. Notice however that this is ongoing work as comparing
|
||||
client objectively is difficult and time consuming.
|
||||
|
||||
The most popular client at the moment of this writing ranked by
|
||||
github stars is
|
||||
|
||||
* https://github.com/sewenew/redis-plus-plus
|
||||
|
||||
Before we start it is worth mentioning some of the things it does
|
||||
not support
|
||||
|
||||
* RESP3. Without RESP3 is impossible to support some important Redis features like client side caching, among other things.
|
||||
* Coroutines.
|
||||
* Reading responses directly in user data structures avoiding temporaries.
|
||||
* Error handling with error-code and exception overloads.
|
||||
* Healthy checks.
|
||||
|
||||
The remaining points will be addressed individually.
|
||||
|
||||
<a name="redis-plus-plus"/>
|
||||
### redis-plus-plus
|
||||
|
||||
Let us first have a look at what sending a command a pipeline and a
|
||||
transaction look like
|
||||
|
||||
```cpp
|
||||
auto redis = Redis("tcp://127.0.0.1:6379");
|
||||
|
||||
// Send commands
|
||||
redis.set("key", "val");
|
||||
auto val = redis.get("key"); // val is of type OptionalString.
|
||||
if (val)
|
||||
std::cout << *val << std::endl;
|
||||
|
||||
// Sending pipelines
|
||||
auto pipe = redis.pipeline();
|
||||
auto pipe_replies = pipe.set("key", "value")
|
||||
.get("key")
|
||||
.rename("key", "new-key")
|
||||
.rpush("list", {"a", "b", "c"})
|
||||
.lrange("list", 0, -1)
|
||||
.exec();
|
||||
|
||||
// Parse reply with reply type and index.
|
||||
auto set_cmd_result = pipe_replies.get<bool>(0);
|
||||
// ...
|
||||
|
||||
// Sending a transaction
|
||||
auto tx = redis.transaction();
|
||||
auto tx_replies = tx.incr("num0")
|
||||
.incr("num1")
|
||||
.mget({"num0", "num1"})
|
||||
.exec();
|
||||
|
||||
auto incr_result0 = tx_replies.get<long long>(0);
|
||||
// ...
|
||||
```
|
||||
|
||||
Some of the problems with this API are
|
||||
|
||||
* Heterogeneous treatment of commands, pipelines and transaction. This makes auto-pipelining impossible.
|
||||
* Any Api that sends individual commands has a very restricted scope of usability and should be avoided for performance reasons.
|
||||
* The API imposes exceptions on users, no error-code overload is provided.
|
||||
* No way to reuse the buffer for new calls to e.g. redis.get in order to avoid further dynamic memory allocations.
|
||||
* Error handling of resolve and connection not clear.
|
||||
|
||||
According to the documentation, pipelines in redis-plus-plus have
|
||||
the following characteristics
|
||||
|
||||
> NOTE: By default, creating a Pipeline object is NOT cheap, since
|
||||
> it creates a new connection.
|
||||
|
||||
This is clearly a downside of the API as pipelines should be the
|
||||
default way of communicating and not an exception, paying such a
|
||||
high price for each pipeline imposes a severe cost in performance.
|
||||
Transactions also suffer from the very same problem.
|
||||
|
||||
> NOTE: Creating a Transaction object is NOT cheap, since it
|
||||
> creates a new connection.
|
||||
|
||||
In Aedis there is no difference between sending one command, a
|
||||
pipeline or a transaction because requests are decoupled
|
||||
from the IO objects.
|
||||
|
||||
> redis-plus-plus also supports async interface, however, async
|
||||
> support for Transaction and Subscriber is still on the way.
|
||||
>
|
||||
> The async interface depends on third-party event library, and so
|
||||
> far, only libuv is supported.
|
||||
|
||||
Async code in redis-plus-plus looks like the following
|
||||
|
||||
```cpp
|
||||
auto async_redis = AsyncRedis(opts, pool_opts);
|
||||
|
||||
Future<string> ping_res = async_redis.ping();
|
||||
|
||||
cout << ping_res.get() << endl;
|
||||
```
|
||||
|
||||
As the reader can see, the async interface is based on futures
|
||||
which is also known to have a bad performance. The biggest
|
||||
problem however with this async design is that it makes it
|
||||
impossible to write asynchronous programs correctly since it
|
||||
starts an async operation on every command sent instead of
|
||||
enqueueing a message and triggering a write when it can be sent.
|
||||
It is also not clear how are pipelines realised with the design
|
||||
(if at all).
|
||||
|
||||
<a name="build-status"/>
|
||||
## Build status
|
||||
|
||||
Branch | GH Actions | codecov.io |
|
||||
:-------------: | ---------- | ---------- |
|
||||
[`master`](https://github.com/mzimbres/aedis/tree/master) | [](https://github.com/mzimbres/aedis/actions/workflows/ci.yml) | [](https://codecov.io/gh/mzimbres/aedis/branch/master)
|
||||
|
||||
## Aedis
|
||||
<a name="acknowledgement"/>
|
||||
## Acknowledgement
|
||||
|
||||
An async redis client designed for performance and scalability
|
||||
Some people that were helpful in the development of Aedis
|
||||
|
||||
### License
|
||||
|
||||
Distributed under the [Boost Software License, Version 1.0](http://www.boost.org/LICENSE_1_0.txt).
|
||||
|
||||
### More information
|
||||
|
||||
* See the official github-pages for documentation: https://mzimbres.github.io/aedis
|
||||
|
||||
### Installation
|
||||
|
||||
See https://mzimbres.github.io/aedis/#using-aedis
|
||||
* Richard Hodges ([madmongo1](https://github.com/madmongo1)): For helping me with Asio and the design of asynchronous programs in general.
|
||||
* Vinícius dos Santos Oliveira ([vinipsmaker](https://github.com/vinipsmaker)): For useful discussion about how Aedis consumes buffers in the read operation (among other things).
|
||||
* Petr Dannhofer ([Eddie-cz](https://github.com/Eddie-cz)): For helping me understand how the `AUTH` and `HELLO` command can influence each other.
|
||||
|
||||
|
||||
537
doc/Doxyfile.in
537
doc/Doxyfile.in
@@ -1,4 +1,4 @@
|
||||
# Doxyfile 1.8.17
|
||||
# Doxyfile 1.9.4
|
||||
|
||||
# This file describes the settings to be used by the documentation system
|
||||
# doxygen (www.doxygen.org) for a project.
|
||||
@@ -12,6 +12,15 @@
|
||||
# For lists, items can also be appended using:
|
||||
# TAG += value [value, ...]
|
||||
# Values that contain spaces should be placed between quotes (\" \").
|
||||
#
|
||||
# Note:
|
||||
#
|
||||
# Use doxygen to compare the used configuration file with the template
|
||||
# configuration file:
|
||||
# doxygen -x [configFile]
|
||||
# Use doxygen to compare the used configuration file with the template
|
||||
# configuration file without replacing the environment variables:
|
||||
# doxygen -x_noenv [configFile]
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Project related configuration options
|
||||
@@ -32,19 +41,19 @@ DOXYFILE_ENCODING = UTF-8
|
||||
# title of most generated pages and in a few other places.
|
||||
# The default value is: My Project.
|
||||
|
||||
PROJECT_NAME = "@PROJECT_NAME@"
|
||||
PROJECT_NAME = @PROJECT_NAME@
|
||||
|
||||
# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
|
||||
# could be handy for archiving the generated documentation or if some version
|
||||
# control system is used.
|
||||
|
||||
PROJECT_NUMBER = "@PROJECT_VERSION@"
|
||||
PROJECT_NUMBER = @PROJECT_VERSION@
|
||||
|
||||
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
||||
# for a project that appears at the top of each page and should give viewer a
|
||||
# quick idea about the purpose of the project. Keep the description short.
|
||||
|
||||
PROJECT_BRIEF = "@PROJECT_DESCRIPTION@"
|
||||
PROJECT_BRIEF = @PROJECT_DESCRIPTION@
|
||||
|
||||
# With the PROJECT_LOGO tag one can specify a logo or an icon that is included
|
||||
# in the documentation. The maximum height of the logo should not exceed 55
|
||||
@@ -58,18 +67,30 @@ PROJECT_LOGO =
|
||||
# entered, it will be relative to the location where doxygen was started. If
|
||||
# left blank the current directory will be used.
|
||||
|
||||
OUTPUT_DIRECTORY = "@DOXYGEN_OUTPUT_DIRECTORY@"
|
||||
OUTPUT_DIRECTORY = @DOXYGEN_OUTPUT_DIRECTORY@
|
||||
|
||||
# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub-
|
||||
# directories (in 2 levels) under the output directory of each output format and
|
||||
# will distribute the generated files over these directories. Enabling this
|
||||
# If the CREATE_SUBDIRS tag is set to YES then doxygen will create up to 4096
|
||||
# sub-directories (in 2 levels) under the output directory of each output format
|
||||
# and will distribute the generated files over these directories. Enabling this
|
||||
# option can be useful when feeding doxygen a huge amount of source files, where
|
||||
# putting all generated files in the same directory would otherwise causes
|
||||
# performance problems for the file system.
|
||||
# performance problems for the file system. Adapt CREATE_SUBDIRS_LEVEL to
|
||||
# control the number of sub-directories.
|
||||
# The default value is: NO.
|
||||
|
||||
CREATE_SUBDIRS = NO
|
||||
|
||||
# Controls the number of sub-directories that will be created when
|
||||
# CREATE_SUBDIRS tag is set to YES. Level 0 represents 16 directories, and every
|
||||
# level increment doubles the number of directories, resulting in 4096
|
||||
# directories at level 8 which is the default and also the maximum value. The
|
||||
# sub-directories are organized in 2 levels, the first level always has a fixed
|
||||
# numer of 16 directories.
|
||||
# Minimum value: 0, maximum value: 8, default value: 8.
|
||||
# This tag requires that the tag CREATE_SUBDIRS is set to YES.
|
||||
|
||||
CREATE_SUBDIRS_LEVEL = 8
|
||||
|
||||
# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII
|
||||
# characters to appear in the names of generated files. If set to NO, non-ASCII
|
||||
# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode
|
||||
@@ -81,26 +102,18 @@ ALLOW_UNICODE_NAMES = NO
|
||||
# The OUTPUT_LANGUAGE tag is used to specify the language in which all
|
||||
# documentation generated by doxygen is written. Doxygen will use this
|
||||
# information to generate all constant output in the proper language.
|
||||
# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese,
|
||||
# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States),
|
||||
# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian,
|
||||
# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages),
|
||||
# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian,
|
||||
# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian,
|
||||
# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish,
|
||||
# Ukrainian and Vietnamese.
|
||||
# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Bulgarian,
|
||||
# Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, English
|
||||
# (United States), Esperanto, Farsi (Persian), Finnish, French, German, Greek,
|
||||
# Hindi, Hungarian, Indonesian, Italian, Japanese, Japanese-en (Japanese with
|
||||
# English messages), Korean, Korean-en (Korean with English messages), Latvian,
|
||||
# Lithuanian, Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese,
|
||||
# Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish,
|
||||
# Swedish, Turkish, Ukrainian and Vietnamese.
|
||||
# The default value is: English.
|
||||
|
||||
OUTPUT_LANGUAGE = English
|
||||
|
||||
# The OUTPUT_TEXT_DIRECTION tag is used to specify the direction in which all
|
||||
# documentation generated by doxygen is written. Doxygen will use this
|
||||
# information to generate all generated output in the proper direction.
|
||||
# Possible values are: None, LTR, RTL and Context.
|
||||
# The default value is: None.
|
||||
|
||||
OUTPUT_TEXT_DIRECTION = None
|
||||
|
||||
# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member
|
||||
# descriptions after the members that are listed in the file and class
|
||||
# documentation (similar to Javadoc). Set to NO to disable this.
|
||||
@@ -170,7 +183,8 @@ FULL_PATH_NAMES = YES
|
||||
# will be relative from the directory where doxygen is started.
|
||||
# This tag requires that the tag FULL_PATH_NAMES is set to YES.
|
||||
|
||||
STRIP_FROM_PATH = "@PROJECT_SOURCE_DIR@/include" "@PROJECT_SOURCE_DIR@"
|
||||
STRIP_FROM_PATH = @PROJECT_SOURCE_DIR@/include \
|
||||
@PROJECT_SOURCE_DIR@
|
||||
|
||||
# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the
|
||||
# path mentioned in the documentation of a class, which tells the reader which
|
||||
@@ -227,6 +241,14 @@ QT_AUTOBRIEF = NO
|
||||
|
||||
MULTILINE_CPP_IS_BRIEF = NO
|
||||
|
||||
# By default Python docstrings are displayed as preformatted text and doxygen's
|
||||
# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the
|
||||
# doxygen's special commands can be used and the contents of the docstring
|
||||
# documentation blocks is shown as doxygen documentation.
|
||||
# The default value is: YES.
|
||||
|
||||
PYTHON_DOCSTRING = YES
|
||||
|
||||
# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the
|
||||
# documentation from any documented member that it re-implements.
|
||||
# The default value is: YES.
|
||||
@@ -250,16 +272,16 @@ TAB_SIZE = 4
|
||||
# the documentation. An alias has the form:
|
||||
# name=value
|
||||
# For example adding
|
||||
# "sideeffect=@par Side Effects:\n"
|
||||
# "sideeffect=@par Side Effects:^^"
|
||||
# will allow you to put the command \sideeffect (or @sideeffect) in the
|
||||
# documentation, which will result in a user-defined paragraph with heading
|
||||
# "Side Effects:". You can put \n's in the value part of an alias to insert
|
||||
# newlines (in the resulting output). You can put ^^ in the value part of an
|
||||
# alias to insert a newline as if a physical newline was in the original file.
|
||||
# When you need a literal { or } or , in the value part of an alias you have to
|
||||
# escape them by means of a backslash (\), this can lead to conflicts with the
|
||||
# commands \{ and \} for these it is advised to use the version @{ and @} or use
|
||||
# a double escape (\\{ and \\})
|
||||
# "Side Effects:". Note that you cannot put \n's in the value part of an alias
|
||||
# to insert newlines (in the resulting output). You can put ^^ in the value part
|
||||
# of an alias to insert a newline as if a physical newline was in the original
|
||||
# file. When you need a literal { or } or , in the value part of an alias you
|
||||
# have to escape them by means of a backslash (\), this can lead to conflicts
|
||||
# with the commands \{ and \} for these it is advised to use the version @{ and
|
||||
# @} or use a double escape (\\{ and \\})
|
||||
|
||||
ALIASES =
|
||||
|
||||
@@ -304,18 +326,21 @@ OPTIMIZE_OUTPUT_SLICE = NO
|
||||
# extension. Doxygen has a built-in mapping, but you can override or extend it
|
||||
# using this tag. The format is ext=language, where ext is a file extension, and
|
||||
# language is one of the parsers supported by doxygen: IDL, Java, JavaScript,
|
||||
# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice,
|
||||
# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran:
|
||||
# Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice,
|
||||
# VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran:
|
||||
# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser
|
||||
# tries to guess whether the code is fixed or free formatted code, this is the
|
||||
# default for Fortran type files), VHDL, tcl. For instance to make doxygen treat
|
||||
# .inc files as Fortran files (default is PHP), and .f files as C (default is
|
||||
# Fortran), use: inc=Fortran f=C.
|
||||
# default for Fortran type files). For instance to make doxygen treat .inc files
|
||||
# as Fortran files (default is PHP), and .f files as C (default is Fortran),
|
||||
# use: inc=Fortran f=C.
|
||||
#
|
||||
# Note: For files without extension you can use no_extension as a placeholder.
|
||||
#
|
||||
# Note that for custom extensions you also need to set FILE_PATTERNS otherwise
|
||||
# the files are not read by doxygen.
|
||||
# the files are not read by doxygen. When specifying no_extension you should add
|
||||
# * to the FILE_PATTERNS.
|
||||
#
|
||||
# Note see also the list of default file extension mappings.
|
||||
|
||||
EXTENSION_MAPPING =
|
||||
|
||||
@@ -449,6 +474,19 @@ TYPEDEF_HIDES_STRUCT = NO
|
||||
|
||||
LOOKUP_CACHE_SIZE = 0
|
||||
|
||||
# The NUM_PROC_THREADS specifies the number of threads doxygen is allowed to use
|
||||
# during processing. When set to 0 doxygen will based this on the number of
|
||||
# cores available in the system. You can set it explicitly to a value larger
|
||||
# than 0 to get more control over the balance between CPU load and processing
|
||||
# speed. At this moment only the input processing can be done using multiple
|
||||
# threads. Since this is still an experimental feature the default is set to 1,
|
||||
# which effectively disables parallel processing. Please report any issues you
|
||||
# encounter. Generating dot graphs in parallel is controlled by the
|
||||
# DOT_NUM_THREADS setting.
|
||||
# Minimum value: 0, maximum value: 32, default value: 1.
|
||||
|
||||
NUM_PROC_THREADS = 1
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Build related configuration options
|
||||
#---------------------------------------------------------------------------
|
||||
@@ -512,6 +550,13 @@ EXTRACT_LOCAL_METHODS = NO
|
||||
|
||||
EXTRACT_ANON_NSPACES = NO
|
||||
|
||||
# If this flag is set to YES, the name of an unnamed parameter in a declaration
|
||||
# will be determined by the corresponding definition. By default unnamed
|
||||
# parameters remain unnamed in the output.
|
||||
# The default value is: YES.
|
||||
|
||||
RESOLVE_UNNAMED_PARAMS = YES
|
||||
|
||||
# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all
|
||||
# undocumented members inside documented classes or files. If set to NO these
|
||||
# members will be included in the various overviews, but no documentation
|
||||
@@ -549,11 +594,18 @@ HIDE_IN_BODY_DOCS = NO
|
||||
|
||||
INTERNAL_DOCS = NO
|
||||
|
||||
# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file
|
||||
# names in lower-case letters. If set to YES, upper-case letters are also
|
||||
# allowed. This is useful if you have classes or files whose names only differ
|
||||
# in case and if your file system supports case sensitive file names. Windows
|
||||
# (including Cygwin) ands Mac users are advised to set this option to NO.
|
||||
# With the correct setting of option CASE_SENSE_NAMES doxygen will better be
|
||||
# able to match the capabilities of the underlying filesystem. In case the
|
||||
# filesystem is case sensitive (i.e. it supports files in the same directory
|
||||
# whose names only differ in casing), the option must be set to YES to properly
|
||||
# deal with such files in case they appear in the input. For filesystems that
|
||||
# are not case sensitive the option should be set to NO to properly deal with
|
||||
# output files written for symbols that only differ in casing, such as for two
|
||||
# classes, one named CLASS and the other named Class, and to also support
|
||||
# references to files without having to specify the exact matching casing. On
|
||||
# Windows (including Cygwin) and MacOS, users should typically set this option
|
||||
# to NO, whereas on Linux or other Unix flavors it should typically be set to
|
||||
# YES.
|
||||
# The default value is: system dependent.
|
||||
|
||||
CASE_SENSE_NAMES = YES
|
||||
@@ -572,6 +624,12 @@ HIDE_SCOPE_NAMES = NO
|
||||
|
||||
HIDE_COMPOUND_REFERENCE= NO
|
||||
|
||||
# If the SHOW_HEADERFILE tag is set to YES then the documentation for a class
|
||||
# will show which file needs to be included to use the class.
|
||||
# The default value is: YES.
|
||||
|
||||
SHOW_HEADERFILE = YES
|
||||
|
||||
# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of
|
||||
# the files that are included by a file in the documentation of that file.
|
||||
# The default value is: YES.
|
||||
@@ -729,7 +787,8 @@ FILE_VERSION_FILTER =
|
||||
# output files in an output format independent way. To create the layout file
|
||||
# that represents doxygen's defaults, run doxygen with the -l option. You can
|
||||
# optionally specify a file name after the option, if omitted DoxygenLayout.xml
|
||||
# will be used as the name of the layout file.
|
||||
# will be used as the name of the layout file. See also section "Changing the
|
||||
# layout of pages" for information.
|
||||
#
|
||||
# Note that if you run doxygen from a directory containing a file called
|
||||
# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE
|
||||
@@ -775,24 +834,35 @@ WARNINGS = YES
|
||||
WARN_IF_UNDOCUMENTED = YES
|
||||
|
||||
# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for
|
||||
# potential errors in the documentation, such as not documenting some parameters
|
||||
# in a documented function, or documenting parameters that don't exist or using
|
||||
# markup commands wrongly.
|
||||
# potential errors in the documentation, such as documenting some parameters in
|
||||
# a documented function twice, or documenting parameters that don't exist or
|
||||
# using markup commands wrongly.
|
||||
# The default value is: YES.
|
||||
|
||||
WARN_IF_DOC_ERROR = YES
|
||||
|
||||
# If WARN_IF_INCOMPLETE_DOC is set to YES, doxygen will warn about incomplete
|
||||
# function parameter documentation. If set to NO, doxygen will accept that some
|
||||
# parameters have no documentation without warning.
|
||||
# The default value is: YES.
|
||||
|
||||
WARN_IF_INCOMPLETE_DOC = YES
|
||||
|
||||
# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
|
||||
# are documented, but have no documentation for their parameters or return
|
||||
# value. If set to NO, doxygen will only warn about wrong or incomplete
|
||||
# parameter documentation, but not about the absence of documentation. If
|
||||
# EXTRACT_ALL is set to YES then this flag will automatically be disabled.
|
||||
# value. If set to NO, doxygen will only warn about wrong parameter
|
||||
# documentation, but not about the absence of documentation. If EXTRACT_ALL is
|
||||
# set to YES then this flag will automatically be disabled. See also
|
||||
# WARN_IF_INCOMPLETE_DOC
|
||||
# The default value is: NO.
|
||||
|
||||
WARN_NO_PARAMDOC = NO
|
||||
|
||||
# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when
|
||||
# a warning is encountered.
|
||||
# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS
|
||||
# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but
|
||||
# at the end of the doxygen process doxygen will return with a non-zero status.
|
||||
# Possible values are: NO, YES and FAIL_ON_WARNINGS.
|
||||
# The default value is: NO.
|
||||
|
||||
WARN_AS_ERROR = NO
|
||||
@@ -803,13 +873,27 @@ WARN_AS_ERROR = NO
|
||||
# and the warning text. Optionally the format may contain $version, which will
|
||||
# be replaced by the version of the file (if it could be obtained via
|
||||
# FILE_VERSION_FILTER)
|
||||
# See also: WARN_LINE_FORMAT
|
||||
# The default value is: $file:$line: $text.
|
||||
|
||||
WARN_FORMAT = "$file:$line: $text"
|
||||
|
||||
# In the $text part of the WARN_FORMAT command it is possible that a reference
|
||||
# to a more specific place is given. To make it easier to jump to this place
|
||||
# (outside of doxygen) the user can define a custom "cut" / "paste" string.
|
||||
# Example:
|
||||
# WARN_LINE_FORMAT = "'vi $file +$line'"
|
||||
# See also: WARN_FORMAT
|
||||
# The default value is: at line $line of file $file.
|
||||
|
||||
WARN_LINE_FORMAT = "at line $line of file $file"
|
||||
|
||||
# The WARN_LOGFILE tag can be used to specify a file to which warning and error
|
||||
# messages should be written. If left blank the output is written to standard
|
||||
# error (stderr).
|
||||
# error (stderr). In case the file specified cannot be opened for writing the
|
||||
# warning and error messages are written to standard error. When as file - is
|
||||
# specified the warning and error messages are written to standard output
|
||||
# (stdout).
|
||||
|
||||
WARN_LOGFILE =
|
||||
|
||||
@@ -823,13 +907,16 @@ WARN_LOGFILE =
|
||||
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
|
||||
# Note: If this tag is empty the current directory is searched.
|
||||
|
||||
INPUT = include benchmarks/benchmarks.md CHANGELOG.md examples
|
||||
INPUT = include \
|
||||
benchmarks/benchmarks.md \
|
||||
CHANGELOG.md \
|
||||
examples README.md
|
||||
|
||||
# This tag can be used to specify the character encoding of the source files
|
||||
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
|
||||
# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
|
||||
# documentation (see: https://www.gnu.org/software/libiconv/) for the list of
|
||||
# possible encodings.
|
||||
# documentation (see:
|
||||
# https://www.gnu.org/software/libiconv/) for the list of possible encodings.
|
||||
# The default value is: UTF-8.
|
||||
|
||||
INPUT_ENCODING = UTF-8
|
||||
@@ -842,15 +929,18 @@ INPUT_ENCODING = UTF-8
|
||||
# need to set EXTENSION_MAPPING for the extension otherwise the files are not
|
||||
# read by doxygen.
|
||||
#
|
||||
# Note the list of default checked file patterns might differ from the list of
|
||||
# default file extension mappings.
|
||||
#
|
||||
# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,
|
||||
# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,
|
||||
# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc,
|
||||
# *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment),
|
||||
# *.doc (to be provided as doxygen C comment), *.txt (to be provided as doxygen
|
||||
# C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f, *.for, *.tcl, *.vhd,
|
||||
# *.hh, *.hxx, *.hpp, *.h++, *.l, *.cs, *.d, *.php, *.php4, *.php5, *.phtml,
|
||||
# *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C
|
||||
# comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd,
|
||||
# *.vhdl, *.ucf, *.qsf and *.ice.
|
||||
|
||||
FILE_PATTERNS = *.hpp *.cpp
|
||||
FILE_PATTERNS = *.hpp \
|
||||
*.cpp
|
||||
|
||||
# The RECURSIVE tag can be used to specify whether or not subdirectories should
|
||||
# be searched for input files as well.
|
||||
@@ -887,7 +977,7 @@ EXCLUDE_PATTERNS =
|
||||
# (namespaces, classes, functions, etc.) that should be excluded from the
|
||||
# output. The symbol name can be a fully qualified name, a word, or if the
|
||||
# wildcard * is used, a substring. Examples: ANamespace, AClass,
|
||||
# AClass::ANamespace, ANamespace::*Test
|
||||
# ANamespace::AClass, ANamespace::*Test
|
||||
#
|
||||
# Note that the wildcards are matched against the file with absolute path, so to
|
||||
# exclude all test directories use the pattern */test/*
|
||||
@@ -974,7 +1064,7 @@ FILTER_SOURCE_PATTERNS =
|
||||
# (index.html). This can be useful if you have a project on for instance GitHub
|
||||
# and want to reuse the introduction page also for the doxygen output.
|
||||
|
||||
USE_MDFILE_AS_MAINPAGE =
|
||||
USE_MDFILE_AS_MAINPAGE = README.md
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Configuration options related to source browsing
|
||||
@@ -1063,16 +1153,24 @@ USE_HTAGS = NO
|
||||
VERBATIM_HEADERS = YES
|
||||
|
||||
# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the
|
||||
# clang parser (see: http://clang.llvm.org/) for more accurate parsing at the
|
||||
# cost of reduced performance. This can be particularly helpful with template
|
||||
# rich C++ code for which doxygen's built-in parser lacks the necessary type
|
||||
# information.
|
||||
# clang parser (see:
|
||||
# http://clang.llvm.org/) for more accurate parsing at the cost of reduced
|
||||
# performance. This can be particularly helpful with template rich C++ code for
|
||||
# which doxygen's built-in parser lacks the necessary type information.
|
||||
# Note: The availability of this option depends on whether or not doxygen was
|
||||
# generated with the -Duse_libclang=ON option for CMake.
|
||||
# The default value is: NO.
|
||||
|
||||
CLANG_ASSISTED_PARSING = NO
|
||||
|
||||
# If the CLANG_ASSISTED_PARSING tag is set to YES and the CLANG_ADD_INC_PATHS
|
||||
# tag is set to YES then doxygen will add the directory of each input to the
|
||||
# include path.
|
||||
# The default value is: YES.
|
||||
# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES.
|
||||
|
||||
CLANG_ADD_INC_PATHS = YES
|
||||
|
||||
# If clang assisted parsing is enabled you can provide the compiler with command
|
||||
# line options that you would normally use when invoking the compiler. Note that
|
||||
# the include paths will already be set by doxygen for the files and directories
|
||||
@@ -1082,10 +1180,13 @@ CLANG_ASSISTED_PARSING = NO
|
||||
CLANG_OPTIONS =
|
||||
|
||||
# If clang assisted parsing is enabled you can provide the clang parser with the
|
||||
# path to the compilation database (see:
|
||||
# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) used when the files
|
||||
# were built. This is equivalent to specifying the "-p" option to a clang tool,
|
||||
# such as clang-check. These options will then be passed to the parser.
|
||||
# path to the directory containing a file called compile_commands.json. This
|
||||
# file is the compilation database (see:
|
||||
# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) containing the
|
||||
# options used when the source files were built. This is equivalent to
|
||||
# specifying the -p option to a clang tool, such as clang-check. These options
|
||||
# will then be passed to the parser. Any options specified with CLANG_OPTIONS
|
||||
# will be added as well.
|
||||
# Note: The availability of this option depends on whether or not doxygen was
|
||||
# generated with the -Duse_libclang=ON option for CMake.
|
||||
|
||||
@@ -1201,7 +1302,7 @@ HTML_EXTRA_FILES =
|
||||
|
||||
# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
|
||||
# will adjust the colors in the style sheet and background images according to
|
||||
# this color. Hue is specified as an angle on a colorwheel, see
|
||||
# this color. Hue is specified as an angle on a color-wheel, see
|
||||
# https://en.wikipedia.org/wiki/Hue for more information. For instance the value
|
||||
# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300
|
||||
# purple, and 360 is red again.
|
||||
@@ -1211,7 +1312,7 @@ HTML_EXTRA_FILES =
|
||||
HTML_COLORSTYLE_HUE = 220
|
||||
|
||||
# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors
|
||||
# in the HTML output. For a value of 0 the output will use grayscales only. A
|
||||
# in the HTML output. For a value of 0 the output will use gray-scales only. A
|
||||
# value of 255 will produce the most vivid colors.
|
||||
# Minimum value: 0, maximum value: 255, default value: 100.
|
||||
# This tag requires that the tag GENERATE_HTML is set to YES.
|
||||
@@ -1272,10 +1373,11 @@ HTML_INDEX_NUM_ENTRIES = 100
|
||||
|
||||
# If the GENERATE_DOCSET tag is set to YES, additional index files will be
|
||||
# generated that can be used as input for Apple's Xcode 3 integrated development
|
||||
# environment (see: https://developer.apple.com/xcode/), introduced with OSX
|
||||
# 10.5 (Leopard). To create a documentation set, doxygen will generate a
|
||||
# Makefile in the HTML output directory. Running make will produce the docset in
|
||||
# that directory and running make install will install the docset in
|
||||
# environment (see:
|
||||
# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To
|
||||
# create a documentation set, doxygen will generate a Makefile in the HTML
|
||||
# output directory. Running make will produce the docset in that directory and
|
||||
# running make install will install the docset in
|
||||
# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at
|
||||
# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy
|
||||
# genXcode/_index.html for more information.
|
||||
@@ -1292,6 +1394,13 @@ GENERATE_DOCSET = NO
|
||||
|
||||
DOCSET_FEEDNAME = "Doxygen generated docs"
|
||||
|
||||
# This tag determines the URL of the docset feed. A documentation feed provides
|
||||
# an umbrella under which multiple documentation sets from a single provider
|
||||
# (such as a company or product suite) can be grouped.
|
||||
# This tag requires that the tag GENERATE_DOCSET is set to YES.
|
||||
|
||||
DOCSET_FEEDURL =
|
||||
|
||||
# This tag specifies a string that should uniquely identify the documentation
|
||||
# set bundle. This should be a reverse domain-name style string, e.g.
|
||||
# com.mycompany.MyDocSet. Doxygen will append .docset to the name.
|
||||
@@ -1317,8 +1426,12 @@ DOCSET_PUBLISHER_NAME = Publisher
|
||||
# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three
|
||||
# additional HTML index files: index.hhp, index.hhc, and index.hhk. The
|
||||
# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop
|
||||
# (see: https://www.microsoft.com/en-us/download/details.aspx?id=21138) on
|
||||
# Windows.
|
||||
# on Windows. In the beginning of 2021 Microsoft took the original page, with
|
||||
# a.o. the download links, offline the HTML help workshop was already many years
|
||||
# in maintenance mode). You can download the HTML help workshop from the web
|
||||
# archives at Installation executable (see:
|
||||
# http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo
|
||||
# ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe).
|
||||
#
|
||||
# The HTML Help Workshop contains a compiler that can convert all HTML output
|
||||
# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML
|
||||
@@ -1348,7 +1461,7 @@ CHM_FILE =
|
||||
HHC_LOCATION =
|
||||
|
||||
# The GENERATE_CHI flag controls if a separate .chi index file is generated
|
||||
# (YES) or that it should be included in the master .chm file (NO).
|
||||
# (YES) or that it should be included in the main .chm file (NO).
|
||||
# The default value is: NO.
|
||||
# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
|
||||
|
||||
@@ -1393,7 +1506,8 @@ QCH_FILE =
|
||||
|
||||
# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help
|
||||
# Project output. For more information please see Qt Help Project / Namespace
|
||||
# (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace).
|
||||
# (see:
|
||||
# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace).
|
||||
# The default value is: org.doxygen.Project.
|
||||
# This tag requires that the tag GENERATE_QHP is set to YES.
|
||||
|
||||
@@ -1401,8 +1515,8 @@ QHP_NAMESPACE = org.doxygen.Project
|
||||
|
||||
# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt
|
||||
# Help Project output. For more information please see Qt Help Project / Virtual
|
||||
# Folders (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-
|
||||
# folders).
|
||||
# Folders (see:
|
||||
# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders).
|
||||
# The default value is: doc.
|
||||
# This tag requires that the tag GENERATE_QHP is set to YES.
|
||||
|
||||
@@ -1410,16 +1524,16 @@ QHP_VIRTUAL_FOLDER = doc
|
||||
|
||||
# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom
|
||||
# filter to add. For more information please see Qt Help Project / Custom
|
||||
# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-
|
||||
# filters).
|
||||
# Filters (see:
|
||||
# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters).
|
||||
# This tag requires that the tag GENERATE_QHP is set to YES.
|
||||
|
||||
QHP_CUST_FILTER_NAME =
|
||||
|
||||
# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the
|
||||
# custom filter to add. For more information please see Qt Help Project / Custom
|
||||
# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-
|
||||
# filters).
|
||||
# Filters (see:
|
||||
# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters).
|
||||
# This tag requires that the tag GENERATE_QHP is set to YES.
|
||||
|
||||
QHP_CUST_FILTER_ATTRS =
|
||||
@@ -1431,9 +1545,9 @@ QHP_CUST_FILTER_ATTRS =
|
||||
|
||||
QHP_SECT_FILTER_ATTRS =
|
||||
|
||||
# The QHG_LOCATION tag can be used to specify the location of Qt's
|
||||
# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the
|
||||
# generated .qhp file.
|
||||
# The QHG_LOCATION tag can be used to specify the location (absolute path
|
||||
# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to
|
||||
# run qhelpgenerator on the generated .qhp file.
|
||||
# This tag requires that the tag GENERATE_QHP is set to YES.
|
||||
|
||||
QHG_LOCATION =
|
||||
@@ -1476,16 +1590,28 @@ DISABLE_INDEX = YES
|
||||
# to work a browser that supports JavaScript, DHTML, CSS and frames is required
|
||||
# (i.e. any modern browser). Windows users are probably better off using the
|
||||
# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can
|
||||
# further fine-tune the look of the index. As an example, the default style
|
||||
# sheet generated by doxygen has an example that shows how to put an image at
|
||||
# the root of the tree instead of the PROJECT_NAME. Since the tree basically has
|
||||
# the same information as the tab index, you could consider setting
|
||||
# DISABLE_INDEX to YES when enabling this option.
|
||||
# further fine tune the look of the index (see "Fine-tuning the output"). As an
|
||||
# example, the default style sheet generated by doxygen has an example that
|
||||
# shows how to put an image at the root of the tree instead of the PROJECT_NAME.
|
||||
# Since the tree basically has the same information as the tab index, you could
|
||||
# consider setting DISABLE_INDEX to YES when enabling this option.
|
||||
# The default value is: NO.
|
||||
# This tag requires that the tag GENERATE_HTML is set to YES.
|
||||
|
||||
GENERATE_TREEVIEW = NO
|
||||
|
||||
# When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the
|
||||
# FULL_SIDEBAR option determines if the side bar is limited to only the treeview
|
||||
# area (value NO) or if it should extend to the full height of the window (value
|
||||
# YES). Setting this to YES gives a layout similar to
|
||||
# https://docs.readthedocs.io with more room for contents, but less room for the
|
||||
# project logo, title, and description. If either GENERATE_TREEVIEW or
|
||||
# DISABLE_INDEX is set to NO, this option has no effect.
|
||||
# The default value is: NO.
|
||||
# This tag requires that the tag GENERATE_HTML is set to YES.
|
||||
|
||||
FULL_SIDEBAR = NO
|
||||
|
||||
# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that
|
||||
# doxygen will group on one line in the generated HTML documentation.
|
||||
#
|
||||
@@ -1510,6 +1636,24 @@ TREEVIEW_WIDTH = 250
|
||||
|
||||
EXT_LINKS_IN_WINDOW = NO
|
||||
|
||||
# If the OBFUSCATE_EMAILS tag is set to YES, doxygen will obfuscate email
|
||||
# addresses.
|
||||
# The default value is: YES.
|
||||
# This tag requires that the tag GENERATE_HTML is set to YES.
|
||||
|
||||
OBFUSCATE_EMAILS = YES
|
||||
|
||||
# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg
|
||||
# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see
|
||||
# https://inkscape.org) to generate formulas as SVG images instead of PNGs for
|
||||
# the HTML output. These images will generally look nicer at scaled resolutions.
|
||||
# Possible values are: png (the default) and svg (looks nicer but requires the
|
||||
# pdf2svg or inkscape tool).
|
||||
# The default value is: png.
|
||||
# This tag requires that the tag GENERATE_HTML is set to YES.
|
||||
|
||||
HTML_FORMULA_FORMAT = png
|
||||
|
||||
# Use this tag to change the font size of LaTeX formulas included as images in
|
||||
# the HTML documentation. When you change the font size after a successful
|
||||
# doxygen run you need to manually remove any form_*.png images from the HTML
|
||||
@@ -1547,11 +1691,29 @@ FORMULA_MACROFILE =
|
||||
|
||||
USE_MATHJAX = YES
|
||||
|
||||
# With MATHJAX_VERSION it is possible to specify the MathJax version to be used.
|
||||
# Note that the different versions of MathJax have different requirements with
|
||||
# regards to the different settings, so it is possible that also other MathJax
|
||||
# settings have to be changed when switching between the different MathJax
|
||||
# versions.
|
||||
# Possible values are: MathJax_2 and MathJax_3.
|
||||
# The default value is: MathJax_2.
|
||||
# This tag requires that the tag USE_MATHJAX is set to YES.
|
||||
|
||||
MATHJAX_VERSION = MathJax_2
|
||||
|
||||
# When MathJax is enabled you can set the default output format to be used for
|
||||
# the MathJax output. See the MathJax site (see:
|
||||
# http://docs.mathjax.org/en/latest/output.html) for more details.
|
||||
# the MathJax output. For more details about the output format see MathJax
|
||||
# version 2 (see:
|
||||
# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3
|
||||
# (see:
|
||||
# http://docs.mathjax.org/en/latest/web/components/output.html).
|
||||
# Possible values are: HTML-CSS (which is slower, but has the best
|
||||
# compatibility), NativeMML (i.e. MathML) and SVG.
|
||||
# compatibility. This is the name for Mathjax version 2, for MathJax version 3
|
||||
# this will be translated into chtml), NativeMML (i.e. MathML. Only supported
|
||||
# for NathJax 2. For MathJax version 3 chtml will be used instead.), chtml (This
|
||||
# is the name for Mathjax version 3, for MathJax version 2 this will be
|
||||
# translated into HTML-CSS) and SVG.
|
||||
# The default value is: HTML-CSS.
|
||||
# This tag requires that the tag USE_MATHJAX is set to YES.
|
||||
|
||||
@@ -1564,22 +1726,29 @@ MATHJAX_FORMAT = HTML-CSS
|
||||
# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax
|
||||
# Content Delivery Network so you can quickly see the result without installing
|
||||
# MathJax. However, it is strongly recommended to install a local copy of
|
||||
# MathJax from https://www.mathjax.org before deployment.
|
||||
# The default value is: https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/.
|
||||
# MathJax from https://www.mathjax.org before deployment. The default value is:
|
||||
# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2
|
||||
# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3
|
||||
# This tag requires that the tag USE_MATHJAX is set to YES.
|
||||
|
||||
MATHJAX_RELPATH = https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/
|
||||
|
||||
# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
|
||||
# extension names that should be enabled during MathJax rendering. For example
|
||||
# for MathJax version 2 (see
|
||||
# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions):
|
||||
# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
|
||||
# For example for MathJax version 3 (see
|
||||
# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html):
|
||||
# MATHJAX_EXTENSIONS = ams
|
||||
# This tag requires that the tag USE_MATHJAX is set to YES.
|
||||
|
||||
MATHJAX_EXTENSIONS =
|
||||
|
||||
# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces
|
||||
# of code that will be used on startup of the MathJax code. See the MathJax site
|
||||
# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an
|
||||
# (see:
|
||||
# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an
|
||||
# example see the documentation.
|
||||
# This tag requires that the tag USE_MATHJAX is set to YES.
|
||||
|
||||
@@ -1626,7 +1795,8 @@ SERVER_BASED_SEARCH = NO
|
||||
#
|
||||
# Doxygen ships with an example indexer (doxyindexer) and search engine
|
||||
# (doxysearch.cgi) which are based on the open source search engine library
|
||||
# Xapian (see: https://xapian.org/).
|
||||
# Xapian (see:
|
||||
# https://xapian.org/).
|
||||
#
|
||||
# See the section "External Indexing and Searching" for details.
|
||||
# The default value is: NO.
|
||||
@@ -1639,8 +1809,9 @@ EXTERNAL_SEARCH = NO
|
||||
#
|
||||
# Doxygen ships with an example indexer (doxyindexer) and search engine
|
||||
# (doxysearch.cgi) which are based on the open source search engine library
|
||||
# Xapian (see: https://xapian.org/). See the section "External Indexing and
|
||||
# Searching" for details.
|
||||
# Xapian (see:
|
||||
# https://xapian.org/). See the section "External Indexing and Searching" for
|
||||
# details.
|
||||
# This tag requires that the tag SEARCHENGINE is set to YES.
|
||||
|
||||
SEARCHENGINE_URL =
|
||||
@@ -1749,29 +1920,31 @@ PAPER_TYPE = a4
|
||||
|
||||
EXTRA_PACKAGES =
|
||||
|
||||
# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the
|
||||
# generated LaTeX document. The header should contain everything until the first
|
||||
# chapter. If it is left blank doxygen will generate a standard header. See
|
||||
# section "Doxygen usage" for information on how to let doxygen write the
|
||||
# default header to a separate file.
|
||||
# The LATEX_HEADER tag can be used to specify a user-defined LaTeX header for
|
||||
# the generated LaTeX document. The header should contain everything until the
|
||||
# first chapter. If it is left blank doxygen will generate a standard header. It
|
||||
# is highly recommended to start with a default header using
|
||||
# doxygen -w latex new_header.tex new_footer.tex new_stylesheet.sty
|
||||
# and then modify the file new_header.tex. See also section "Doxygen usage" for
|
||||
# information on how to generate the default header that doxygen normally uses.
|
||||
#
|
||||
# Note: Only use a user-defined header if you know what you are doing! The
|
||||
# following commands have a special meaning inside the header: $title,
|
||||
# $datetime, $date, $doxygenversion, $projectname, $projectnumber,
|
||||
# $projectbrief, $projectlogo. Doxygen will replace $title with the empty
|
||||
# string, for the replacement values of the other commands the user is referred
|
||||
# to HTML_HEADER.
|
||||
# Note: Only use a user-defined header if you know what you are doing!
|
||||
# Note: The header is subject to change so you typically have to regenerate the
|
||||
# default header when upgrading to a newer version of doxygen. The following
|
||||
# commands have a special meaning inside the header (and footer): For a
|
||||
# description of the possible markers and block names see the documentation.
|
||||
# This tag requires that the tag GENERATE_LATEX is set to YES.
|
||||
|
||||
LATEX_HEADER =
|
||||
|
||||
# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the
|
||||
# generated LaTeX document. The footer should contain everything after the last
|
||||
# chapter. If it is left blank doxygen will generate a standard footer. See
|
||||
# The LATEX_FOOTER tag can be used to specify a user-defined LaTeX footer for
|
||||
# the generated LaTeX document. The footer should contain everything after the
|
||||
# last chapter. If it is left blank doxygen will generate a standard footer. See
|
||||
# LATEX_HEADER for more information on how to generate a default footer and what
|
||||
# special commands can be used inside the footer.
|
||||
#
|
||||
# Note: Only use a user-defined footer if you know what you are doing!
|
||||
# special commands can be used inside the footer. See also section "Doxygen
|
||||
# usage" for information on how to generate the default footer that doxygen
|
||||
# normally uses. Note: Only use a user-defined footer if you know what you are
|
||||
# doing!
|
||||
# This tag requires that the tag GENERATE_LATEX is set to YES.
|
||||
|
||||
LATEX_FOOTER =
|
||||
@@ -1804,9 +1977,11 @@ LATEX_EXTRA_FILES =
|
||||
|
||||
PDF_HYPERLINKS = YES
|
||||
|
||||
# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate
|
||||
# the PDF file directly from the LaTeX files. Set this option to YES, to get a
|
||||
# higher quality PDF documentation.
|
||||
# If the USE_PDFLATEX tag is set to YES, doxygen will use the engine as
|
||||
# specified with LATEX_CMD_NAME to generate the PDF file directly from the LaTeX
|
||||
# files. Set this option to YES, to get a higher quality PDF documentation.
|
||||
#
|
||||
# See also section LATEX_CMD_NAME for selecting the engine.
|
||||
# The default value is: YES.
|
||||
# This tag requires that the tag GENERATE_LATEX is set to YES.
|
||||
|
||||
@@ -1814,8 +1989,7 @@ USE_PDFLATEX = YES
|
||||
|
||||
# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode
|
||||
# command to the generated LaTeX files. This will instruct LaTeX to keep running
|
||||
# if errors occur, instead of asking the user for help. This option is also used
|
||||
# when generating formulas in HTML.
|
||||
# if errors occur, instead of asking the user for help.
|
||||
# The default value is: NO.
|
||||
# This tag requires that the tag GENERATE_LATEX is set to YES.
|
||||
|
||||
@@ -1828,16 +2002,6 @@ LATEX_BATCHMODE = NO
|
||||
|
||||
LATEX_HIDE_INDICES = NO
|
||||
|
||||
# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source
|
||||
# code with syntax highlighting in the LaTeX output.
|
||||
#
|
||||
# Note that which sources are shown also depends on other settings such as
|
||||
# SOURCE_BROWSER.
|
||||
# The default value is: NO.
|
||||
# This tag requires that the tag GENERATE_LATEX is set to YES.
|
||||
|
||||
LATEX_SOURCE_CODE = NO
|
||||
|
||||
# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
|
||||
# bibliography, e.g. plainnat, or ieeetr. See
|
||||
# https://en.wikipedia.org/wiki/BibTeX and \cite for more info.
|
||||
@@ -1918,16 +2082,6 @@ RTF_STYLESHEET_FILE =
|
||||
|
||||
RTF_EXTENSIONS_FILE =
|
||||
|
||||
# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code
|
||||
# with syntax highlighting in the RTF output.
|
||||
#
|
||||
# Note that which sources are shown also depends on other settings such as
|
||||
# SOURCE_BROWSER.
|
||||
# The default value is: NO.
|
||||
# This tag requires that the tag GENERATE_RTF is set to YES.
|
||||
|
||||
RTF_SOURCE_CODE = NO
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Configuration options related to the man page output
|
||||
#---------------------------------------------------------------------------
|
||||
@@ -2024,15 +2178,6 @@ GENERATE_DOCBOOK = NO
|
||||
|
||||
DOCBOOK_OUTPUT = docbook
|
||||
|
||||
# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the
|
||||
# program listings (including syntax highlighting and cross-referencing
|
||||
# information) to the DOCBOOK output. Note that enabling this will significantly
|
||||
# increase the size of the DOCBOOK output.
|
||||
# The default value is: NO.
|
||||
# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
|
||||
|
||||
DOCBOOK_PROGRAMLISTING = NO
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Configuration options for the AutoGen Definitions output
|
||||
#---------------------------------------------------------------------------
|
||||
@@ -2119,7 +2264,8 @@ SEARCH_INCLUDES = YES
|
||||
|
||||
# The INCLUDE_PATH tag can be used to specify one or more directories that
|
||||
# contain include files that are not input files but should be processed by the
|
||||
# preprocessor.
|
||||
# preprocessor. Note that the INCLUDE_PATH is not recursive, so the setting of
|
||||
# RECURSIVE has no effect here.
|
||||
# This tag requires that the tag SEARCH_INCLUDES is set to YES.
|
||||
|
||||
INCLUDE_PATH =
|
||||
@@ -2211,15 +2357,6 @@ EXTERNAL_PAGES = YES
|
||||
# Configuration options related to the dot tool
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram
|
||||
# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to
|
||||
# NO turns the diagrams off. Note that this option also works with HAVE_DOT
|
||||
# disabled, but it is recommended to install and use dot, since it yields more
|
||||
# powerful graphs.
|
||||
# The default value is: YES.
|
||||
|
||||
CLASS_DIAGRAMS = NO
|
||||
|
||||
# You can include diagrams made with dia in doxygen documentation. Doxygen will
|
||||
# then run dia to produce the diagram and insert it in the documentation. The
|
||||
# DIA_PATH tag allows you to specify the directory where the dia binary resides.
|
||||
@@ -2276,11 +2413,14 @@ DOT_FONTSIZE = 10
|
||||
|
||||
DOT_FONTPATH =
|
||||
|
||||
# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for
|
||||
# each documented class showing the direct and indirect inheritance relations.
|
||||
# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO.
|
||||
# If the CLASS_GRAPH tag is set to YES (or GRAPH) then doxygen will generate a
|
||||
# graph for each documented class showing the direct and indirect inheritance
|
||||
# relations. In case HAVE_DOT is set as well dot will be used to draw the graph,
|
||||
# otherwise the built-in generator will be used. If the CLASS_GRAPH tag is set
|
||||
# to TEXT the direct and indirect inheritance relations will be shown as texts /
|
||||
# links.
|
||||
# Possible values are: NO, YES, TEXT and GRAPH.
|
||||
# The default value is: YES.
|
||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||
|
||||
CLASS_GRAPH = NO
|
||||
|
||||
@@ -2294,7 +2434,8 @@ CLASS_GRAPH = NO
|
||||
COLLABORATION_GRAPH = YES
|
||||
|
||||
# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for
|
||||
# groups, showing the direct groups dependencies.
|
||||
# groups, showing the direct groups dependencies. See also the chapter Grouping
|
||||
# in the manual.
|
||||
# The default value is: YES.
|
||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||
|
||||
@@ -2317,10 +2458,32 @@ UML_LOOK = NO
|
||||
# but if the number exceeds 15, the total amount of fields shown is limited to
|
||||
# 10.
|
||||
# Minimum value: 0, maximum value: 100, default value: 10.
|
||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||
# This tag requires that the tag UML_LOOK is set to YES.
|
||||
|
||||
UML_LIMIT_NUM_FIELDS = 10
|
||||
|
||||
# If the DOT_UML_DETAILS tag is set to NO, doxygen will show attributes and
|
||||
# methods without types and arguments in the UML graphs. If the DOT_UML_DETAILS
|
||||
# tag is set to YES, doxygen will add type and arguments for attributes and
|
||||
# methods in the UML graphs. If the DOT_UML_DETAILS tag is set to NONE, doxygen
|
||||
# will not generate fields with class member information in the UML graphs. The
|
||||
# class diagrams will look similar to the default class diagrams but using UML
|
||||
# notation for the relationships.
|
||||
# Possible values are: NO, YES and NONE.
|
||||
# The default value is: NO.
|
||||
# This tag requires that the tag UML_LOOK is set to YES.
|
||||
|
||||
DOT_UML_DETAILS = NO
|
||||
|
||||
# The DOT_WRAP_THRESHOLD tag can be used to set the maximum number of characters
|
||||
# to display on a single line. If the actual line length exceeds this threshold
|
||||
# significantly it will wrapped across multiple lines. Some heuristics are apply
|
||||
# to avoid ugly line breaks.
|
||||
# Minimum value: 0, maximum value: 1000, default value: 17.
|
||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||
|
||||
DOT_WRAP_THRESHOLD = 17
|
||||
|
||||
# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and
|
||||
# collaboration graphs will show the relations between templates and their
|
||||
# instances.
|
||||
@@ -2387,6 +2550,13 @@ GRAPHICAL_HIERARCHY = YES
|
||||
|
||||
DIRECTORY_GRAPH = NO
|
||||
|
||||
# The DIR_GRAPH_MAX_DEPTH tag can be used to limit the maximum number of levels
|
||||
# of child directories generated in directory dependency graphs by dot.
|
||||
# Minimum value: 1, maximum value: 25, default value: 1.
|
||||
# This tag requires that the tag DIRECTORY_GRAPH is set to YES.
|
||||
|
||||
DIR_GRAPH_MAX_DEPTH = 1
|
||||
|
||||
# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
|
||||
# generated by dot. For an explanation of the image formats see the section
|
||||
# output formats in the documentation of the dot tool (Graphviz (see:
|
||||
@@ -2394,10 +2564,9 @@ DIRECTORY_GRAPH = NO
|
||||
# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order
|
||||
# to make the SVG files visible in IE 9+ (other browsers do not have this
|
||||
# requirement).
|
||||
# Possible values are: png, png:cairo, png:cairo:cairo, png:cairo:gd, png:gd,
|
||||
# png:gd:gd, jpg, jpg:cairo, jpg:cairo:gd, jpg:gd, jpg:gd:gd, gif, gif:cairo,
|
||||
# gif:cairo:gd, gif:gd, gif:gd:gd, svg, png:gd, png:gd:gd, png:cairo,
|
||||
# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and
|
||||
# Possible values are: png, jpg, jpg:cairo, jpg:cairo:gd, jpg:gd, jpg:gd:gd,
|
||||
# gif, gif:cairo, gif:cairo:gd, gif:gd, gif:gd:gd, svg, png:gd, png:gd:gd,
|
||||
# png:cairo, png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and
|
||||
# png:gdiplus:gdiplus.
|
||||
# The default value is: png.
|
||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||
@@ -2442,10 +2611,10 @@ MSCFILE_DIRS =
|
||||
DIAFILE_DIRS =
|
||||
|
||||
# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the
|
||||
# path where java can find the plantuml.jar file. If left blank, it is assumed
|
||||
# PlantUML is not used or called during a preprocessing step. Doxygen will
|
||||
# generate a warning when it encounters a \startuml command in this case and
|
||||
# will not generate output for the diagram.
|
||||
# path where java can find the plantuml.jar file or to the filename of jar file
|
||||
# to be used. If left blank, it is assumed PlantUML is not used or called during
|
||||
# a preprocessing step. Doxygen will generate a warning when it encounters a
|
||||
# \startuml command in this case and will not generate output for the diagram.
|
||||
|
||||
PLANTUML_JAR_PATH =
|
||||
|
||||
@@ -2507,14 +2676,18 @@ DOT_MULTI_TARGETS = NO
|
||||
# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page
|
||||
# explaining the meaning of the various boxes and arrows in the dot generated
|
||||
# graphs.
|
||||
# Note: This tag requires that UML_LOOK isn't set, i.e. the doxygen internal
|
||||
# graphical representation for inheritance and collaboration diagrams is used.
|
||||
# The default value is: YES.
|
||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||
|
||||
GENERATE_LEGEND = YES
|
||||
|
||||
# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate dot
|
||||
# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate
|
||||
# files that are used to generate the various graphs.
|
||||
#
|
||||
# Note: This setting is not only used for dot files but also for msc temporary
|
||||
# files.
|
||||
# The default value is: YES.
|
||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||
|
||||
DOT_CLEANUP = YES
|
||||
|
||||
@@ -52,6 +52,9 @@ net::awaitable<void> push_receiver(std::shared_ptr<connection> db)
|
||||
}
|
||||
}
|
||||
|
||||
// See
|
||||
// - https://redis.io/docs/manual/sentinel.
|
||||
// - https://redis.io/docs/reference/sentinel-clients.
|
||||
net::awaitable<void> reconnect(std::shared_ptr<connection> db)
|
||||
{
|
||||
request req;
|
||||
|
||||
@@ -31,7 +31,7 @@ using connection = aedis::connection<tcp_socket>;
|
||||
// https://redis.io/docs/reference/sentinel-clients. This example
|
||||
// assumes a sentinel and a redis server running on localhost.
|
||||
|
||||
net::awaitable<void> push_receiver(std::shared_ptr<connection> db)
|
||||
net::awaitable<void> receive_pushes(std::shared_ptr<connection> db)
|
||||
{
|
||||
for (std::vector<node<std::string>> resp;;) {
|
||||
co_await db->async_receive_push(adapt(resp));
|
||||
@@ -104,7 +104,7 @@ int main()
|
||||
try {
|
||||
net::io_context ioc;
|
||||
auto db = std::make_shared<connection>(ioc);
|
||||
net::co_spawn(ioc, push_receiver(db), net::detached);
|
||||
net::co_spawn(ioc, receive_pushes(db), net::detached);
|
||||
net::co_spawn(ioc, reconnect(db), net::detached);
|
||||
net::signal_set signals(ioc, SIGINT, SIGTERM);
|
||||
signals.async_wait([&](auto, auto){ ioc.stop(); });
|
||||
|
||||
@@ -33,6 +33,7 @@ void reconnect(connection& conn)
|
||||
for (;;) {
|
||||
boost::system::error_code ec;
|
||||
conn.run(ep, req, adapt(), ec);
|
||||
// TODO: Call conn.reset.
|
||||
std::cout << ec.message() << std::endl;
|
||||
std::this_thread::sleep_for(std::chrono::seconds{1});
|
||||
}
|
||||
|
||||
@@ -13,513 +13,9 @@
|
||||
#include <aedis/sync.hpp>
|
||||
#include <aedis/resp3/request.hpp>
|
||||
|
||||
/** \mainpage Documentation
|
||||
\tableofcontents
|
||||
|
||||
Useful links: \subpage any, [Changelog](CHANGELOG.md) and [Benchmarks](benchmarks/benchmarks.md).
|
||||
|
||||
\section Overview
|
||||
|
||||
Aedis is a high-level [Redis](https://redis.io/) client library
|
||||
built on top of
|
||||
[Asio](https://www.boost.org/doc/libs/release/doc/html/boost_asio.html).
|
||||
Some of its distinctive features are
|
||||
|
||||
\li Support for the latest version of the Redis communication protocol [RESP3](https://github.com/redis/redis-specifications/blob/master/protocol/RESP3.md).
|
||||
\li First class support for STL containers and C++ built-in types.
|
||||
\li Serialization and deserialization of your own data types.
|
||||
\li Healthy checks, back pressure and low latency.
|
||||
\li Hides most of the low level asynchronous operations away from the user.
|
||||
|
||||
If you are unfamiliar with Redis, the best place to start is https://redis.io. Inpacient users can skim over the examples before proceeding to the next sections
|
||||
|
||||
@li intro.cpp: This is the Aedis hello-world program. It sends one command to Redis and quits the connection.
|
||||
@li intro_sync.cpp: Synchronous version of intro.cpp.
|
||||
@li containers.cpp: Shows how to send and receive stl containers. In addition to that it also shows how to deal with transactions.
|
||||
@li serialization.cpp: Shows how to serialize types using Boost.Json.
|
||||
@li subscriber.cpp: Shows how to use pubsub.
|
||||
@li subscriber_sentinel.cpp: Subscriber that resolves the master address with sentinels.
|
||||
@li subscriber_sync.cpp: Synchronous version of subscriber.cpp.
|
||||
@li echo_server.cpp: A simple TCP echo server that uses coroutines.
|
||||
@li chat_room.cpp: A simple chat room that uses coroutines.
|
||||
|
||||
@section Tutorial
|
||||
|
||||
The basic way of using Aedis in an app is to call
|
||||
|
||||
@li `connection::async_run`: Stablishes a connection the Redis
|
||||
server and perform it healthy checks.
|
||||
@li `connection::async_exec`: Send commands and receive responses.
|
||||
@li `connection::async_receive_push`: Receives server side pushes
|
||||
(optional).
|
||||
|
||||
@subsection Async
|
||||
|
||||
The calls to `connection::async_run` look like this most of the time
|
||||
|
||||
@code
|
||||
int main()
|
||||
{
|
||||
net::io_context ioc;
|
||||
auto conn = std::make_shared<connection>(ioc);
|
||||
conn->get_config().enable_reconnect = true;
|
||||
|
||||
// Use the connection is other operations.
|
||||
net::co_spawn(ioc, some_operation(conn), net::detached);
|
||||
|
||||
conn->async_run(net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
@endcode
|
||||
|
||||
@subsection Sync
|
||||
|
||||
@subsection requests Requests
|
||||
|
||||
Redis requests are composed of one of more Redis commands (in
|
||||
Redis documentation they are called
|
||||
[pipelines](https://redis.io/topics/pipelining)). For example
|
||||
|
||||
@code
|
||||
request req;
|
||||
|
||||
// Command with variable length of arguments.
|
||||
req.push("SET", "key", "some value", 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_range2("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);
|
||||
@endcode
|
||||
|
||||
Sending a request to Redis is then peformed with the following function
|
||||
|
||||
@code
|
||||
co_await db->async_exec(req, adapt(resp));
|
||||
@endcode
|
||||
|
||||
\subsubsection requests-serialization Serialization
|
||||
|
||||
The \c push and \c push_range functions above work with integers
|
||||
e.g. \c int and \c std::string out of the box. To send your own
|
||||
data type defined a \c to_bulk function like this
|
||||
|
||||
@code
|
||||
struct mystruct {
|
||||
// Example struct.
|
||||
};
|
||||
|
||||
void to_bulk(std::string& to, mystruct const& obj)
|
||||
{
|
||||
std::string dummy = "Dummy serializaiton string.";
|
||||
aedis::resp3::to_bulk(to, dummy);
|
||||
}
|
||||
@endcode
|
||||
|
||||
Once \c to_bulk is defined and accessible over ADL \c mystruct can
|
||||
be passed to the \c request
|
||||
|
||||
@code
|
||||
request req;
|
||||
|
||||
std::map<std::string, mystruct> map {...};
|
||||
|
||||
req.push_range("HSET", "key", map);
|
||||
@endcode
|
||||
|
||||
Example serialization.cpp shows how store json string in Redis.
|
||||
|
||||
\subsection low-level-responses Responses
|
||||
|
||||
To read responses effectively, users must know their RESP3 type,
|
||||
this can be found in the Redis documentation for each command
|
||||
(https://redis.io/commands). For example
|
||||
|
||||
Command | RESP3 type | Documentation
|
||||
---------|-------------------------------------|--------------
|
||||
lpush | Number | https://redis.io/commands/lpush
|
||||
lrange | Array | https://redis.io/commands/lrange
|
||||
set | Simple-string, null or blob-string | https://redis.io/commands/set
|
||||
get | Blob-string | https://redis.io/commands/get
|
||||
smembers | Set | https://redis.io/commands/smembers
|
||||
hgetall | Map | https://redis.io/commands/hgetall
|
||||
|
||||
Once the RESP3 type of a given response is known we can choose a
|
||||
proper C++ data structure to receive it in. Fortunately, this is a
|
||||
simple task for most types. The table below summarises the options
|
||||
|
||||
RESP3 type | Possible C++ type | Type
|
||||
---------------|--------------------------------------------------------------|------------------
|
||||
Simple-string | \c std::string | Simple
|
||||
Simple-error | \c std::string | Simple
|
||||
Blob-string | \c std::string, \c std::vector | Simple
|
||||
Blob-error | \c std::string, \c std::vector | Simple
|
||||
Number | `long long`, `int`, `std::size_t`, \c std::string | Simple
|
||||
Double | `double`, \c std::string | Simple
|
||||
Null | `std::optional<T>` | Simple
|
||||
Array | \c std::vector, \c std::list, \c std::array, \c std::deque | Aggregate
|
||||
Map | \c std::vector, \c std::map, \c std::unordered_map | Aggregate
|
||||
Set | \c std::vector, \c std::set, \c std::unordered_set | Aggregate
|
||||
Push | \c std::vector, \c std::map, \c std::unordered_map | Aggregate
|
||||
|
||||
For example
|
||||
|
||||
@code
|
||||
request req;
|
||||
req.push("HELLO", 3);
|
||||
req.push_range("RPUSH", "key1", vec);
|
||||
req.push_range("HSET", "key2", map);
|
||||
req.push("LRANGE", "key3", 0, -1);
|
||||
req.push("HGETALL", "key4");
|
||||
req.push("QUIT");
|
||||
|
||||
std::tuple<
|
||||
aedis::ignore, // hello
|
||||
int, // rpush
|
||||
int, // hset
|
||||
std::vector<T>, // lrange
|
||||
std::map<U, V>, // hgetall
|
||||
std::string // quit
|
||||
> resp;
|
||||
|
||||
co_await db->async_exec(req, adapt(resp));
|
||||
@endcode
|
||||
|
||||
The tag @c aedis::ignore can be used to ignore individual
|
||||
elements in the responses. If the intention is to ignore the
|
||||
response to all commands in the request use @c adapt()
|
||||
|
||||
@code
|
||||
co_await db->async_exec(req, adapt());
|
||||
@endcode
|
||||
|
||||
Responses that contain nested aggregates or heterogeneous data
|
||||
types will be given special treatment later in \ref gen-case. As
|
||||
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.
|
||||
|
||||
\subsubsection Optional
|
||||
|
||||
It is not uncommon for apps to access keys that do not exist or
|
||||
that have already expired in the Redis server, to deal with these
|
||||
cases Aedis provides support for \c std::optional. To use it,
|
||||
wrap your type around \c std::optional like this
|
||||
|
||||
@code
|
||||
std::optional<std::unordered_map<T, U>> resp;
|
||||
co_await db->async_exec(req, adapt(resp));
|
||||
@endcode
|
||||
|
||||
Everything else stays the same.
|
||||
|
||||
\subsubsection transactions Transactions
|
||||
|
||||
To read the response to transactions we have to observe that Redis
|
||||
queues the commands as they arrive and sends the responses back to
|
||||
the user as an array, in the response to the @c exec command.
|
||||
For example, to read the response to the this request
|
||||
|
||||
@code
|
||||
db.send("MULTI");
|
||||
db.send("GET", "key1");
|
||||
db.send("LRANGE", "key2", 0, -1);
|
||||
db.send("HGETALL", "key3");
|
||||
db.send("EXEC");
|
||||
@endcode
|
||||
|
||||
use the following response type
|
||||
|
||||
@code
|
||||
using aedis::ignore;
|
||||
|
||||
using exec_resp_type =
|
||||
std::tuple<
|
||||
std::optional<std::string>, // get
|
||||
std::optional<std::vector<std::string>>, // lrange
|
||||
std::optional<std::map<std::string, std::string>> // hgetall
|
||||
>;
|
||||
|
||||
std::tuple<
|
||||
ignore, // multi
|
||||
ignore, // get
|
||||
ignore, // lrange
|
||||
ignore, // hgetall
|
||||
exec_resp_type, // exec
|
||||
> resp;
|
||||
|
||||
co_await db->async_exec(req, adapt(resp));
|
||||
@endcode
|
||||
|
||||
Note that above we are not ignoring the response to the commands
|
||||
themselves but whether they have been successfully queued. For a
|
||||
complete example see containers.cpp.
|
||||
|
||||
\subsubsection Deserialization
|
||||
|
||||
As mentioned in \ref requests-serialization, it is common to
|
||||
serialize data before sending it to Redis e.g. to json strings.
|
||||
For performance and convenience reasons, we may also want to
|
||||
deserialize it directly in its final data structure. Aedis
|
||||
supports this use case by calling a user provided \c from_bulk
|
||||
function while parsing the response. For example
|
||||
|
||||
@code
|
||||
void from_bulk(mystruct& obj, char const* p, std::size_t size, boost::system::error_code& ec)
|
||||
{
|
||||
// Deserializes p into obj.
|
||||
}
|
||||
@endcode
|
||||
|
||||
After that, you can start receiving data efficiently in the desired
|
||||
types e.g. \c mystruct, \c std::map<std::string, mystruct> etc.
|
||||
|
||||
\subsubsection gen-case The general case
|
||||
|
||||
There are cases where responses to Redis
|
||||
commands won't fit in the model presented above, some examples are
|
||||
|
||||
@li Commands (like \c set) whose responses don't have a fixed
|
||||
RESP3 type. Expecting an \c int and receiving a blob-string
|
||||
will result in error.
|
||||
@li RESP3 aggregates that contain nested aggregates can't be read in STL containers.
|
||||
@li Transactions with a dynamic number of commands can't be read in a \c std::tuple.
|
||||
|
||||
To deal with these cases Aedis provides the \c resp3::node
|
||||
type, that is the most general form of an element in a response,
|
||||
be it a simple RESP3 type or an aggregate. It is defined like this
|
||||
|
||||
@code
|
||||
template <class String>
|
||||
struct node {
|
||||
// The RESP3 type of the data in this node.
|
||||
type data_type;
|
||||
|
||||
// The number of elements of an aggregate (or 1 for simple data).
|
||||
std::size_t aggregate_size;
|
||||
|
||||
// The depth of this node in the response tree.
|
||||
std::size_t depth;
|
||||
|
||||
// The actual data. For aggregate types this is always empty.
|
||||
String value;
|
||||
};
|
||||
@endcode
|
||||
|
||||
Any response to a Redis command can be received in a \c
|
||||
std::vector<node<std::string>>. The vector can be seen as a
|
||||
pre-order view of the response tree
|
||||
(https://en.wikipedia.org/wiki/Tree_traversal#Pre-order,_NLR).
|
||||
Using it is no different than using other types
|
||||
|
||||
@code
|
||||
// Receives any RESP3 simple data type.
|
||||
node<std::string> resp;
|
||||
co_await db->async_exec(req, adapt(resp));
|
||||
|
||||
// Receives any RESP3 simple or aggregate data type.
|
||||
std::vector<node<std::string>> resp;
|
||||
co_await db->async_exec(req, adapt(resp));
|
||||
@endcode
|
||||
|
||||
For example, suppose we want to retrieve a hash data structure
|
||||
from Redis with `HGETALL`, some of the options are
|
||||
|
||||
@li \c std::vector<node<std::string>: Works always.
|
||||
@li \c std::vector<std::string>: Efficient and flat, all elements as string.
|
||||
@li \c std::map<std::string, std::string>: Efficient if you need the data as a \c std::map
|
||||
@li \c std::map<U, V>: Efficient if you are storing serialized data. Avoids temporaries and requires \c from_bulk for \c U and \c V.
|
||||
|
||||
In addition to the above users can also use unordered versions of the containers. The same reasoning also applies to sets e.g. `SMEMBERS`.
|
||||
|
||||
\section using-aedis Installation
|
||||
|
||||
Download the latest Aedis release from github
|
||||
|
||||
```
|
||||
$ wget https://github.com/mzimbres/aedis/releases/download/v1.0.0/aedis-1.0.0.tar.gz
|
||||
```
|
||||
|
||||
and unpack in your prefered location. Aedis is a header only
|
||||
library, so you can starting using it. For that include the
|
||||
following header
|
||||
|
||||
```cpp
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
```
|
||||
|
||||
in no more than one source file in your applications (see
|
||||
intro.cpp for example). To build the examples, run the tests etc.
|
||||
cmake is also supported
|
||||
|
||||
```
|
||||
$ BOOST_ROOT=/opt/boost_1_79_0/ cmake
|
||||
$ make
|
||||
$ make test
|
||||
```
|
||||
|
||||
Notice you have to specify the compiler flags manually.
|
||||
|
||||
These are the requirements for using Aedis
|
||||
|
||||
- Boost 1.78 or greater.
|
||||
- C++17. Some examples require C++20 with coroutine support.
|
||||
- Redis 6 or higher. Optionally also redis-cli and Redis Sentinel.
|
||||
|
||||
The following compilers are supported
|
||||
|
||||
- Tested with gcc: 10, 11, 12.
|
||||
- Tested with clang: 11, 13, 14.
|
||||
|
||||
|
||||
\section why-aedis Why Aedis
|
||||
|
||||
At the time of this writing there are seventeen Redis clients
|
||||
listed in the [official](https://redis.io/docs/clients/#cpp) list.
|
||||
With so many clients available it is not unlikely that users are
|
||||
asking themselves why yet another one. In this section I will try
|
||||
to compare Aedis with the most popular clients and why we need
|
||||
Aedis. Notice however that this is ongoing work as comparing
|
||||
client objectively is difficult and time consuming.
|
||||
|
||||
The most popular client at the moment of this writing ranked by
|
||||
github stars is
|
||||
|
||||
@li https://github.com/sewenew/redis-plus-plus
|
||||
|
||||
Before we start it is worth mentioning some of the things it does
|
||||
not support
|
||||
|
||||
@li RESP3. Without RESP3 is impossible to support some important Redis features like client side caching, among other things.
|
||||
@li Coroutines.
|
||||
@li Reading responses directly in user data structures avoiding temporaries.
|
||||
@li Error handling with error-code and exception overloads.
|
||||
@li Healthy checks.
|
||||
|
||||
The remaining points will be addressed individually.
|
||||
|
||||
@subsection redis-plus-plus
|
||||
|
||||
Let us first have a look at what sending a command a pipeline and a
|
||||
transaction look like
|
||||
|
||||
@code
|
||||
auto redis = Redis("tcp://127.0.0.1:6379");
|
||||
|
||||
// Send commands
|
||||
redis.set("key", "val");
|
||||
auto val = redis.get("key"); // val is of type OptionalString.
|
||||
if (val)
|
||||
std::cout << *val << std::endl;
|
||||
|
||||
// Sending pipelines
|
||||
auto pipe = redis.pipeline();
|
||||
auto pipe_replies = pipe.set("key", "value")
|
||||
.get("key")
|
||||
.rename("key", "new-key")
|
||||
.rpush("list", {"a", "b", "c"})
|
||||
.lrange("list", 0, -1)
|
||||
.exec();
|
||||
|
||||
// Parse reply with reply type and index.
|
||||
auto set_cmd_result = pipe_replies.get<bool>(0);
|
||||
// ...
|
||||
|
||||
// Sending a transaction
|
||||
auto tx = redis.transaction();
|
||||
auto tx_replies = tx.incr("num0")
|
||||
.incr("num1")
|
||||
.mget({"num0", "num1"})
|
||||
.exec();
|
||||
|
||||
auto incr_result0 = tx_replies.get<long long>(0);
|
||||
// ...
|
||||
@endcode
|
||||
|
||||
Some of the problems with this API are
|
||||
|
||||
@li Heterogeneous treatment of commands, pipelines and transaction. This makes auto-pipelining impossible.
|
||||
@li Any Api that sends individual commands has a very restricted scope of usability and should be avoided for performance reasons.
|
||||
@li The API imposes exceptions on users, no error-code overload is provided.
|
||||
@li No way to reuse the buffer for new calls to e.g. \c redis.get in order to avoid further dynamic memory allocations.
|
||||
@li Error handling of resolve and connection not clear.
|
||||
|
||||
According to the documentation, pipelines in redis-plus-plus have
|
||||
the following characteristics
|
||||
|
||||
> NOTE: By default, creating a Pipeline object is NOT cheap, since
|
||||
> it creates a new connection.
|
||||
|
||||
This is clearly a downside of the API as pipelines should be the
|
||||
default way of communicating and not an exception, paying such a
|
||||
high price for each pipeline imposes a severe cost in performance.
|
||||
Transactions also suffer from the very same problem.
|
||||
|
||||
> NOTE: Creating a Transaction object is NOT cheap, since it
|
||||
> creates a new connection.
|
||||
|
||||
In Aedis there is no difference between sending one command, a
|
||||
pipeline or a transaction because requests are decoupled
|
||||
from the IO objects.
|
||||
|
||||
> redis-plus-plus also supports async interface, however, async
|
||||
> support for Transaction and Subscriber is still on the way.
|
||||
>
|
||||
> The async interface depends on third-party event library, and so
|
||||
> far, only libuv is supported.
|
||||
|
||||
Async code in redis-plus-plus looks like the following
|
||||
|
||||
@code
|
||||
auto async_redis = AsyncRedis(opts, pool_opts);
|
||||
|
||||
Future<string> ping_res = async_redis.ping();
|
||||
|
||||
cout << ping_res.get() << endl;
|
||||
@endcode
|
||||
|
||||
As the reader can see, the async interface is based on futures
|
||||
which is also known to have a bad performance. The biggest
|
||||
problem however with this async design is that it makes it
|
||||
impossible to write asynchronous programs correctly since it
|
||||
starts an async operation on every command sent instead of
|
||||
enqueueing a message and triggering a write when it can be sent.
|
||||
It is also not clear how are pipelines realised with the design
|
||||
(if at all).
|
||||
|
||||
\section Acknowledgement
|
||||
|
||||
Some people that were helpful in the development of Aedis
|
||||
|
||||
@li Richard Hodges ([madmongo1](https://github.com/madmongo1)): For helping me with Asio and the design of asynchronous programs in general.
|
||||
@li Vinícius dos Santos Oliveira ([vinipsmaker](https://github.com/vinipsmaker)): For useful discussion about how Aedis consumes buffers in the read operation (among other things).
|
||||
@li Petr Dannhofer ([Eddie-cz](https://github.com/Eddie-cz)): For helping me understand how the `AUTH` and `HELLO` command can influence each other.
|
||||
*/
|
||||
|
||||
/** \defgroup any Reference
|
||||
/** @defgroup any Reference
|
||||
*
|
||||
* This page contains the documentation of all user facing code.
|
||||
*/
|
||||
|
||||
// Support sentinel support as described in
|
||||
//
|
||||
// - https://redis.io/docs/manual/sentinel.
|
||||
// - https://redis.io/docs/reference/sentinel-clients.
|
||||
//
|
||||
// Avoid conflicts between
|
||||
//
|
||||
// - aedis::adapt
|
||||
// - aedis::resp3::adapt.
|
||||
|
||||
#endif // AEDIS_HPP
|
||||
|
||||
@@ -43,6 +43,9 @@ public:
|
||||
/// Timeout of the connect operation.
|
||||
std::chrono::milliseconds connect_timeout = std::chrono::seconds{10};
|
||||
|
||||
/// Timeout of the resp3 handshake operation.
|
||||
std::chrono::milliseconds resp3_handshake_timeout = std::chrono::seconds{2};
|
||||
|
||||
/// Time interval of ping operations.
|
||||
std::chrono::milliseconds ping_interval = std::chrono::seconds{1};
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
namespace aedis {
|
||||
|
||||
/** @brief A high level connection to Redis.
|
||||
/** @brief Base class for high level Redis asynchronous connections.
|
||||
* @ingroup any
|
||||
*
|
||||
* This class keeps a healthy connection to the Redis instance where
|
||||
@@ -61,10 +61,9 @@ public:
|
||||
receive_push,
|
||||
};
|
||||
|
||||
/** \brief Contructor
|
||||
/** @brief Contructor
|
||||
*
|
||||
* \param ex The executor.
|
||||
* \param cfg Configuration parameters.
|
||||
* @param ex The executor.
|
||||
*/
|
||||
explicit connection_base(executor_type ex)
|
||||
: resv_{ex}
|
||||
@@ -84,18 +83,18 @@ public:
|
||||
|
||||
/** @brief Cancel operations.
|
||||
*
|
||||
* @li `operation::exec`: Cancels operations started with `async_exec`.
|
||||
* @li `operation::exec`: Cancels operations started with `async_exec`.
|
||||
*
|
||||
* @li operation::run: Cancels `async_run`. Notice that the
|
||||
* preferred way to close a connection is send `QUIT`
|
||||
* to the server. An unresponsive Redis server will also cause
|
||||
* the idle-checks to kick in 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::run: Cancels `async_run`. Notice that the
|
||||
* preferred way to close a connection is to send a `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.
|
||||
*
|
||||
* @param op: The operation to be cancelled.
|
||||
* @returns The number of operations that have been canceled.
|
||||
* @param op: The operation to be cancelled.
|
||||
* @returns The number of operations that have been canceled.
|
||||
*/
|
||||
auto cancel(operation op) -> std::size_t
|
||||
{
|
||||
@@ -145,7 +144,8 @@ public:
|
||||
|
||||
/** @name Asynchronous functions
|
||||
*
|
||||
* Each of these operations a individually cancellable.
|
||||
* Each of these operations are cancellable via the cancel member
|
||||
* function.
|
||||
**/
|
||||
|
||||
/// @{
|
||||
@@ -154,14 +154,29 @@ public:
|
||||
* This function performs the following steps
|
||||
*
|
||||
* @li Resolves the Redis host as of `async_resolve` with the
|
||||
* timeout passed in `config::resolve_timeout`.
|
||||
* timeout passed in the base class `config::resolve_timeout`.
|
||||
*
|
||||
* @li Connects to one of the endpoints returned by the resolve
|
||||
* operation with the timeout passed in `config::connect_timeout`.
|
||||
* operation with the timeout passed in the base class
|
||||
* `config::connect_timeout`.
|
||||
*
|
||||
* @li Starts healthy checks with a timeout twice
|
||||
* the value of `config::ping_interval`. If no data is
|
||||
* received during that time interval `connection::async_run` completes with
|
||||
* @li Performs a RESP3 handshake by sending a `HELLO` command
|
||||
* with protocol version 3 and optionally credentials necessary
|
||||
* for authentication, the last are read from the `endpoint`
|
||||
* object. TODO: specify a timeout.
|
||||
*
|
||||
* @li Erase any password that are contained in
|
||||
* `endpoint::password`.
|
||||
*
|
||||
* @li Check whether the server role corresponds to the one
|
||||
* specifed in the `endpoint`. If `endpoint::role` is left empty,
|
||||
* no check is performed. If the role role is different than the
|
||||
* expect `async_run` will complete with
|
||||
* `error::unexpected_server_role`.
|
||||
*
|
||||
* @li Starts healthy checks with a timeout twice the value of
|
||||
* `config::ping_interval`. If no data is received during that
|
||||
* time interval `connection::async_run` completes with
|
||||
* `error::idle_timeout`.
|
||||
*
|
||||
* @li Starts the healthy check operation that sends `PING`s to
|
||||
@@ -170,10 +185,7 @@ public:
|
||||
* @li Starts reading from the socket and executes all requests
|
||||
* that have been started prior to this function call.
|
||||
*
|
||||
* For an example see echo_server.cpp.
|
||||
*
|
||||
* @param ep Redis endpoint. The password will be erased after the
|
||||
* connection has been stablished.
|
||||
* @param ep Redis endpoint.
|
||||
* @param token Completion token.
|
||||
*
|
||||
* The completion token must have the following signature
|
||||
@@ -197,14 +209,12 @@ public:
|
||||
* single function. This function is useful for users that want to
|
||||
* send a single request to the server and close it.
|
||||
*
|
||||
* @param ep Redis endpoint. The password will be erased after the
|
||||
* connection has been stablished.
|
||||
* @param ep Redis endpoint.
|
||||
* @param req Request object.
|
||||
* @param adapter Response adapter.
|
||||
* @param token Asio completion token.
|
||||
*
|
||||
* For an example see intro.cpp. The completion token must have
|
||||
* the following signature
|
||||
* The completion token must have the following signature
|
||||
*
|
||||
* @code
|
||||
* void f(boost::system::error_code, std::size_t);
|
||||
@@ -230,12 +240,11 @@ public:
|
||||
|
||||
/** @brief Executes a command on the redis server asynchronously.
|
||||
*
|
||||
* There is no need to synchronize multiple calls to this
|
||||
* function as it keeps an internal queue.
|
||||
* The implementation will queue multiple calls to this function.
|
||||
*
|
||||
* \param req Request object.
|
||||
* \param adapter Response adapter.
|
||||
* \param token Asio completion token.
|
||||
* @param req Request object.
|
||||
* @param adapter Response adapter.
|
||||
* @param token Asio completion token.
|
||||
*
|
||||
* For an example see echo_server.cpp. The completion token must
|
||||
* have the following signature
|
||||
@@ -266,11 +275,11 @@ public:
|
||||
/** @brief Receives server side pushes asynchronously.
|
||||
*
|
||||
* Users that expect server pushes have to call this function in a
|
||||
* loop. If a push arrives and there is no reader,
|
||||
* the connection will hang and eventually timeout.
|
||||
* loop. If a push arrives and there is no reader, the connection
|
||||
* will hang and eventually timeout.
|
||||
*
|
||||
* \param adapter The response adapter.
|
||||
* \param token The Asio completion token.
|
||||
* @param adapter The response adapter.
|
||||
* @param token The Asio completion token.
|
||||
*
|
||||
* For an example see subscriber.cpp. The completion token must
|
||||
* have the following signature
|
||||
|
||||
@@ -409,7 +409,7 @@ struct run_op {
|
||||
}
|
||||
|
||||
conn->prepare_hello(*ep);
|
||||
conn->ping_timer_.expires_after(conn->get_config().ping_interval);
|
||||
conn->ping_timer_.expires_after(conn->get_config().resp3_handshake_timeout);
|
||||
|
||||
yield
|
||||
resp3::detail::async_exec(
|
||||
@@ -427,6 +427,8 @@ struct run_op {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Erase the password.
|
||||
|
||||
if (!conn->expect_role(ep->role)) {
|
||||
conn->cancel(Conn::operation::run);
|
||||
self.complete(error::unexpected_server_role);
|
||||
|
||||
@@ -144,6 +144,9 @@ public:
|
||||
/// Timeout of the ssl handshake operation.
|
||||
std::chrono::milliseconds handshake_timeout = std::chrono::seconds{10};
|
||||
|
||||
/// Timeout of the resp3 handshake operation.
|
||||
std::chrono::milliseconds resp3_handshake_timeout = std::chrono::seconds{2};
|
||||
|
||||
/// Time interval of ping operations.
|
||||
std::chrono::milliseconds ping_interval = std::chrono::seconds{1};
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ namespace aedis {
|
||||
template <class Connection>
|
||||
class sync {
|
||||
public:
|
||||
// TODO: Add cancel and reset functions.
|
||||
using config = typename Connection::config;
|
||||
|
||||
/** @brief Constructor
|
||||
@@ -33,10 +34,10 @@ public:
|
||||
template <class Executor>
|
||||
explicit sync(Executor ex, config cfg = config{}) : conn_{ex, cfg} { }
|
||||
|
||||
/** @brief Executes a request synchronously.
|
||||
/** @brief Calls `async_exec` from the underlying connection object.
|
||||
*
|
||||
* The functions calls `connection::async_exec` and waits
|
||||
* for its completion.
|
||||
* Calls `async_exec` from the underlying connection object and
|
||||
* waits for its completion.
|
||||
*
|
||||
* @param req The request.
|
||||
* @param adapter The response adapter.
|
||||
@@ -68,10 +69,10 @@ public:
|
||||
return res;
|
||||
}
|
||||
|
||||
/** @brief Executes a command synchronously
|
||||
/** @brief Calls `async_exec` from the underlying connection object.
|
||||
*
|
||||
* The functions calls `connection::async_exec` and waits for its
|
||||
* completion.
|
||||
* Calls `async_exec` from the underlying connection object and
|
||||
* waits for its completion.
|
||||
*
|
||||
* @param req The request.
|
||||
* @param adapter The response adapter.
|
||||
@@ -88,10 +89,10 @@ public:
|
||||
return res;
|
||||
}
|
||||
|
||||
/** @brief Receives server pushes synchronusly.
|
||||
/** @brief Calls `async_receive_push` from the underlying connection object.
|
||||
*
|
||||
* The functions calls `connection::async_receive_push` and
|
||||
* waits for its completion.
|
||||
* Calls `async_receive_push` from the underlying connection
|
||||
* object and waits for its completion.
|
||||
*
|
||||
* @param adapter The response adapter.
|
||||
* @param ec Error code in case of error.
|
||||
@@ -121,10 +122,10 @@ public:
|
||||
return res;
|
||||
}
|
||||
|
||||
/** @brief Receives server pushes synchronusly.
|
||||
/** @brief Calls `async_receive_push` from the underlying connection object.
|
||||
*
|
||||
* The functions calls `connection::async_receive_push` and
|
||||
* waits for its completion.
|
||||
* Calls `async_receive_push` from the underlying connection
|
||||
* object and waits for its completion.
|
||||
*
|
||||
* @param adapter The response adapter.
|
||||
* @throws std::system_error in case of error.
|
||||
@@ -142,9 +143,10 @@ public:
|
||||
|
||||
/** @brief Calls \c async_run from the underlying connection.
|
||||
*
|
||||
* The functions calls `connection::async_run` and waits for its
|
||||
* completion.
|
||||
* Calls `async_run` from the underlying connection objects and
|
||||
* waits for its completion.
|
||||
*
|
||||
* @param ep The Redis server endpoint.
|
||||
* @param ec Error code.
|
||||
*/
|
||||
void run(endpoint& ep, boost::system::error_code& ec)
|
||||
@@ -168,9 +170,10 @@ public:
|
||||
|
||||
/** @brief Calls \c async_run from the underlying connection.
|
||||
*
|
||||
* The functions calls `connection::async_run` and waits for its
|
||||
* completion.
|
||||
* Calls `async_run` from the underlying connection objects and
|
||||
* waits for its completion.
|
||||
*
|
||||
* @param ep The Redis server endpoint.
|
||||
* @throws std::system_error.
|
||||
*/
|
||||
void run(endpoint& ep)
|
||||
@@ -181,7 +184,16 @@ public:
|
||||
throw std::system_error(ec);
|
||||
}
|
||||
|
||||
/// Calls `connection_base::async_run` and blocks until its competion.
|
||||
/** @brief Calls \c async_run from the underlying connection.
|
||||
*
|
||||
* Calls `async_run` from the underlying connection objects and
|
||||
* waits for its completion.
|
||||
*
|
||||
* @param ep The Redis server endpoint.
|
||||
* @param req The request.
|
||||
* @param adapter The response adapter.
|
||||
* @param ec Error code in case of error.
|
||||
*/
|
||||
template <class ResponseAdapter>
|
||||
auto run(endpoint& ep, resp3::request const& req, ResponseAdapter adapter, boost::system::error_code& ec)
|
||||
{
|
||||
@@ -206,7 +218,16 @@ public:
|
||||
return res;
|
||||
}
|
||||
|
||||
/// Calls `connection_base::async_run` and blocks until its competion.
|
||||
/** @brief Calls \c async_run from the underlying connection.
|
||||
*
|
||||
* Calls `async_run` from the underlying connection objects and
|
||||
* waits for its completion.
|
||||
*
|
||||
* @param ep The Redis server endpoint.
|
||||
* @param req The request.
|
||||
* @param adapter The response adapter.
|
||||
* @throws std::system_error.
|
||||
*/
|
||||
template <class ResponseAdapter = detail::response_traits<void>::adapter_type>
|
||||
auto run(endpoint& ep, resp3::request const& req, ResponseAdapter adapter = aedis::adapt())
|
||||
{
|
||||
|
||||
@@ -82,7 +82,7 @@ BOOST_AUTO_TEST_CASE(test_idle)
|
||||
|
||||
endpoint ep{"127.0.0.1", "6379"};
|
||||
db->async_run(ep, req, adapt(), [](auto ec, auto){
|
||||
BOOST_CHECK_EQUAL(ec, net::error::misc_errors::eof);
|
||||
BOOST_CHECK_EQUAL(ec, aedis::error::exec_timeout);
|
||||
});
|
||||
|
||||
ioc.run();
|
||||
|
||||
40
tests/connection_tls.cpp
Normal file
40
tests/connection_tls.cpp
Normal file
@@ -0,0 +1,40 @@
|
||||
/* 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>
|
||||
|
||||
#define BOOST_TEST_MODULE low level
|
||||
#include <boost/test/included/unit_test.hpp>
|
||||
|
||||
#include <aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
namespace net = boost::asio;
|
||||
|
||||
using aedis::adapt;
|
||||
using connection = aedis::connection<>;
|
||||
using endpoint = aedis::endpoint;
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_hello_fail)
|
||||
{
|
||||
std::cout << boost::unit_test::framework::current_test_case().p_name << std::endl;
|
||||
|
||||
net::io_context ioc;
|
||||
auto db = std::make_shared<connection>(ioc.get_executor());
|
||||
|
||||
// Succeeds with the tcp connection but fails the hello.
|
||||
endpoint ep;
|
||||
ep.host = "google.com";
|
||||
ep.port = "443";
|
||||
|
||||
db->async_run(ep, [](auto ec) {
|
||||
BOOST_CHECK_EQUAL(ec, net::error::misc_errors::eof);
|
||||
});
|
||||
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user