2
0
mirror of https://github.com/boostorg/redis.git synced 2026-01-19 04:42:09 +00:00

Improves documentations of the connection class.

This commit is contained in:
Marcelo Zimbres
2022-08-21 11:28:20 +02:00
parent 24a215d78b
commit 11807c82b7
10 changed files with 160 additions and 158 deletions

View File

@@ -1,15 +1,16 @@
# Changelog
## master
## v1.0.0
* Adds experimental cmake support for windows users.
* Adds new class `sync` that wraps a `connection` and offers a
thread-safe synchronous API. All free functions from the `sync.hpp`
are now member functions of the `sync` class.
* Adds new class `aedis::sync` that wraps an `aedis::connection` in
a thread-safe and synchronous API. All free functions from the
`sync.hpp` are now member functions of `aedis::sync`.
* Split `connection::async_receive_event` in two functions, one to
receive events and another for server side pushes.
* Split `aedis::connection::async_receive_event` in two functions, one
to receive events and another for server side pushes, see
`aedis::connection::async_receive_push`.
* Removes collision between `aedis::adapter::adapt` and
`aedis::adapt`.

View File

@@ -5,7 +5,7 @@ cmake_minimum_required(VERSION 3.14)
project(
Aedis
VERSION 0.3.0
VERSION 1.0.0
DESCRIPTION "An async redis client designed for performance and scalability"
HOMEPAGE_URL "https://mzimbres.github.io/aedis"
LANGUAGES CXX

View File

@@ -1 +0,0 @@
See https://mzimbres.github.io/aedis/#using-aedis

View File

@@ -14,3 +14,7 @@ Distributed under the [Boost Software License, Version 1.0](http://www.boost.org
* See the official github-pages for documentation: https://mzimbres.github.io/aedis
### Installation
See https://mzimbres.github.io/aedis/#using-aedis

View File

@@ -85,9 +85,3 @@ The code used in the benchmarks can be found at
## Running the benchmarks
Run one of the echo-server programs in one terminal and the [echo-server-client](https://github.com/mzimbres/aedis/blob/42880e788bec6020dd018194075a211ad9f339e8/benchmarks/cpp/asio/echo_server_client.cpp) in another.
## Contributing
If your spot any performance improvement in any of the example or
would like to include other clients, please open a PR and I will
gladly merge it.

View File

@@ -1,5 +1,5 @@
AC_PREREQ([2.69])
AC_INIT([Aedis], [0.3.0], [mzimbres@gmail.com])
AC_INIT([Aedis], [1.0.0], [mzimbres@gmail.com])
AC_CONFIG_MACRO_DIR([m4])
AC_CONFIG_HEADERS([config.h])
AC_CONFIG_SRCDIR(include/aedis.hpp)

View File

@@ -22,8 +22,8 @@
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
[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.
@@ -31,11 +31,11 @@
\li Healthy checks, back pressure and low latency.
\li Hides most of the low level asynchronous operations away from the user.
Let us start with an overview of asynchronous code.
Let us have a look a some code snippets
@subsection Async
The code below sends a ping command to Redis (see intro.cpp)
The code below sends a ping command to Redis and quits (see intro.cpp)
@code
int main()
@@ -56,9 +56,9 @@
}
@endcode
The connection class maintains a healthy connection with
Redis over which users can execute their commands, without any
need of queuing. For example, to execute more than one command
The connection class maintains a healthy connection with Redis
over which users can execute their commands, without any need of
queuing. For example, to execute more than one request
@code
int main()
@@ -78,7 +78,7 @@
}
@endcode
The `async_exec` functions above can be called from different
The `connection::async_exec` functions above can be called from different
places in the code without knowing about each other, see for
example echo_server.cpp. Server-side pushes are supported on the
same connection where commands are executed, a typical subscriber
@@ -88,55 +88,42 @@
@code
net::awaitable<void> reader(std::shared_ptr<connection> db)
{
request req;
req.push("SUBSCRIBE", "channel");
for (std::vector<node_type> resp;;) {
auto ev = co_await db->async_receive_event(aedis::adapt(resp));
switch (ev) {
case connection::event::push:
// Use resp and clear it.
resp.clear();
break;
default:;
}
co_await db->async_receive_event(adapt(resp));
// Use resp and clear it.
resp.clear();
}
}
@endcode
@subsection Sync
The `connection` class is async-only, many users however need to
interact with it synchronously, this is also supported by Aedis as long
as this interaction occurs across threads, for example (see
intro_sync.cpp)
The `connection` class offers only an asynchronous API.
Synchronous communications with redis is provided by the `aedis::sync`
wrapper class. (see intro_sync.cpp)
@code
int main()
{
try {
net::io_context ioc{1};
connection conn{ioc};
std::thread thread{[&]() {
conn.async_run(net::detached);
ioc.run();
}};
request req;
req.push("PING");
req.push("QUIT");
std::tuple<std::string, aedis::ignore> resp;
exec(conn, req, adapt(resp));
thread.join();
std::cout << "Response: " << std::get<0>(resp) << std::endl;
} catch (std::exception const& e) {
std::cerr << e.what() << std::endl;
}
net::io_context ioc{1};
auto work = net::make_work_guard(ioc);
std::thread t1{[&]() { ioc.run(); }};
sync<connection> conn{work.get_executor()};
std::thread t2{[&]() { boost::system::error_code ec; conn.run(ec); }};
request req;
req.push("PING");
req.push("QUIT");
std::tuple<std::string, aedis::ignore> resp;
conn.exec(req, adapt(resp));
std::cout << "Response: " << std::get<0>(resp) << std::endl;
work.reset();
t1.join();
t2.join();
}
@endcode
@@ -151,23 +138,27 @@
For a simple installation run
```
# Clone the repository and checkout the lastest release tag.
$ git clone --branch v0.3.0 https://github.com/mzimbres/aedis.git
$ git clone --branch v1.0.0 https://github.com/mzimbres/aedis.git
$ cd aedis
# Build an example
# Option 1: Direct compilation.
$ g++ -std=c++17 -pthread examples/intro.cpp -I./include -I/path/boost_1_79_0/include/
# Option 2: Use cmake.
$ BOOST_ROOT=/opt/boost_1_79_0/ cmake -DCMAKE_CXX_FLAGS=-std=c++20 .
```
@note CMake support is still experimental.
For a proper full installation on the system run
```
# Download and unpack the latest release
$ wget https://github.com/mzimbres/aedis/releases/download/v0.3.0/aedis-0.3.0.tar.gz
$ tar -xzvf aedis-0.3.0.tar.gz
$ wget https://github.com/mzimbres/aedis/releases/download/v1.0.0/aedis-1.0.0.tar.gz
$ tar -xzvf aedis-1.0.0.tar.gz
# Configure, build and install
$ CXXFLAGS="-std=c++17" ./configure --prefix=/opt/aedis-0.3.0 --with-boost=/opt/boost_1_78_0
$ CXXFLAGS="-std=c++17" ./configure --prefix=/opt/aedis-1.0.0 --with-boost=/opt/boost_1_78_0
$ sudo make install
```
@@ -177,12 +168,6 @@
$ make
```
There is also experimental support cmake, for example
@code
$ BOOST_ROOT=/opt/boost_1_79_0/ cmake -DCMAKE_CXX_FLAGS=-std=c++20 .
@endcode
@subsubsection using_aedis Using Aedis
When writing you own applications include the following header
@@ -380,7 +365,7 @@
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 in a single array, in the response to the @c exec command.
the user as an array, in the response to the @c exec command.
For example, to read the response to the this request
@code
@@ -397,7 +382,7 @@
using aedis::ignore;
using boost::optional;
using tresp_type =
using exec_resp_type =
std::tuple<
optional<std::string>, // get
optional<std::vector<std::string>>, // lrange
@@ -409,7 +394,7 @@
ignore, // get
ignore, // lrange
ignore, // hgetall
tresp_type, // exec
exec_resp_type, // exec
> resp;
co_await db->async_exec(req, adapt(resp));
@@ -443,7 +428,7 @@
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 response don't have a fixed
@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.
@@ -487,14 +472,14 @@
@endcode
For example, suppose we want to retrieve a hash data structure
from Redis with \c hgetall, some of the options are
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. \c smembers.
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 examples Examples
@@ -503,8 +488,8 @@
@li intro.cpp: Basic steps with Aedis.
@li intro_sync.cpp: Synchronous version of intro.cpp.
@li containers.cpp: Shows how to send and receive stl containers.
@li serialization.cpp: Shows the \c request support to serialization of user types.
@li subscriber.cpp: Shows how to subscribe to a channel and how to reconnect when connection is lost.
@li serialization.cpp: Shows how to serialize your own types.
@li subscriber.cpp: Shows how to use pubsub.
@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.

View File

@@ -20,7 +20,7 @@
namespace aedis {
/** @brief Tag used tp ignore responses.
/** @brief Tag used to ignore responses.
* @ingroup any
*
* For example

View File

@@ -26,13 +26,20 @@
namespace aedis {
/** @brief A high level Redis asynchronous connection to Redis.
/** @brief A high level connection to Redis.
* @ingroup any
*
* This class keeps a healthy connection to the Redis instance where
* commands can be sent at any time. For more details, please see the
* documentation of each individual function.
*
* @remarks This class exposes only asynchronous member functions,
* synchronous communications with the Redis server is provided by
* the sync class.
*
* @tparam AsyncReadWriteStream A stream that supports
* `async_read_some` and `async_write_some`.
*
*/
template <class AsyncReadWriteStream = boost::asio::ip::tcp::socket>
class connection {
@@ -43,20 +50,13 @@ public:
/// Type of the next layer
using next_layer_type = AsyncReadWriteStream;
using default_completion_token_type = boost::asio::default_completion_token_t<executor_type>;
using push_channel_type = boost::asio::experimental::channel<executor_type, void(boost::system::error_code, std::size_t)>;
using clock_type = std::chrono::steady_clock;
using clock_traits_type = boost::asio::wait_traits<clock_type>;
using timer_type = boost::asio::basic_waitable_timer<clock_type, clock_traits_type, executor_type>;
using resolver_type = boost::asio::ip::basic_resolver<boost::asio::ip::tcp, executor_type>;
/** @brief Connection configuration parameters.
*/
struct config {
/// The Redis server address.
/// Redis server address.
std::string host = "127.0.0.1";
/// The Redis server port.
/// Redis server port.
std::string port = "6379";
/// Username if authentication is required.
@@ -71,30 +71,30 @@ public:
/// Timeout of the connect operation.
std::chrono::milliseconds connect_timeout = std::chrono::seconds{10};
/// Time interval ping operations.
/// Time interval of ping operations.
std::chrono::milliseconds ping_interval = std::chrono::seconds{1};
/// Time waited before trying a reconnection (see enable reconnect).
/// Time waited before trying a reconnection (see config::enable_reconnect).
std::chrono::milliseconds reconnect_interval = std::chrono::seconds{1};
/// The maximum size allowed on read operations.
/// The maximum size of read operations.
std::size_t max_read_size = (std::numeric_limits<std::size_t>::max)();
/// Whether to coalesce requests (see [pipelines](https://redis.io/topics/pipelining)).
bool coalesce_requests = true;
/// Enable events
/// Enable internal events, see connection::async_receive_event.
bool enable_events = false;
/// Enable automatic reconnection (see also reconnect_interval).
/// Enable automatic reconnection (see also config::reconnect_interval).
bool enable_reconnect = false;
};
/// Events communicated through \c async_receive_event.
/// Events that are communicated by `connection::async_receive_event`.
enum class event {
/// The address has been successfully resolved.
/// Resolve operation was successful.
resolve,
/// Connected to the Redis server.
/// Connect operation was successful.
connect,
/// Success sending AUTH and HELLO.
hello,
@@ -102,24 +102,23 @@ public:
invalid
};
using event_channel_type = boost::asio::experimental::channel<executor_type, void(boost::system::error_code, event)>;
/** @brief Async operations that can be cancelled.
/** @brief Async operations exposed by this class.
*
* See the \c cancel member function for more information.
* The operations listed below can be cancelled with the `cancel`
* member function.
*/
enum class operation {
/// Operations started with \c async_exec.
/// `connection::async_exec` operations.
exec,
/// Operation started with \c async_run.
/// `connection::async_run` operations.
run,
/// Operation started with async_receive_event.
/// `connection::async_receive_event` operations.
receive_event,
/// Operation started with async_receive_push.
/// `connection::async_receive_push` operations.
receive_push,
};
/** \brief Construct a connection from an executor.
/** \brief Contructor
*
* \param ex The executor.
* \param cfg Configuration parameters.
@@ -139,7 +138,7 @@ public:
read_timer_.expires_at(std::chrono::steady_clock::time_point::max());
}
/** \brief Construct a connection from an io_context.
/** \brief Constructor
*
* \param ioc The executor.
* \param cfg Configuration parameters.
@@ -153,15 +152,18 @@ public:
/** @brief Cancel operations.
*
* @li operation::exec: Cancels all operations started with \c async_exec.
* @li operation::run: Cancels @c async_run. The prefered way to
* close a connection is to set config::enable_reconnect to
* false and send a \c quit command. Otherwise an unresponsive Redis server
* will cause the idle-checks to kick in and lead to \c
* async_run returning with idle_timeout. Calling \c
* cancel(operation::run) directly should be seen as the last
* option.
* @li operation::receive_event: Cancels @c async_receive_event.
* @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 to ensure
* `config::enable_reconnect` is set to `false` and 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::receive_event: Cancels `connection::async_receive_event`.
*
* @param op: The operation to be cancelled.
* @returns The number of operations that have been canceled.
@@ -190,11 +192,11 @@ public:
writer_timer_.cancel();
ping_timer_.cancel();
// Cancel own pings if there are any waiting.
auto point = std::stable_partition(std::begin(reqs_), std::end(reqs_), [](auto const& ptr) {
return !ptr->req->close_on_run_completion;
});
// Cancel own pings if there are any waiting.
std::for_each(point, std::end(reqs_), [](auto const& ptr) {
ptr->stop = true;
ptr->timer.cancel();
@@ -225,6 +227,8 @@ public:
config const& get_config() const noexcept { return cfg_;}
/** @name Asynchronous functions
*
* Each of these operations a individually cancellable.
**/
/// @{
@@ -232,25 +236,28 @@ public:
*
* This function performs the following steps
*
* \li Resolves the Redis host as of \c async_resolve with the
* timeout passed in connection::config::resolve_timeout.
* @li Resolves the Redis host as of `async_resolve` with the
* timeout passed in `config::resolve_timeout`.
*
* \li Connects to one of the endpoints returned by the resolve
* operation with the timeout passed in connection::config::connect_timeout.
* @li Connects to one of the endpoints returned by the resolve
* operation with the timeout passed in `config::connect_timeout`.
*
* \li Starts the idle check operation with the timeout of twice
* the value of connection::config::ping_interval. If no data is
* received during that time interval \c async_run completes with
* error::idle_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
* `error::idle_timeout`.
*
* \li Starts the healthy check operation that sends command::ping
* to Redis with a frequency equal to
* connection::config::ping_interval.
* @li Starts the healthy check operation that sends `PING`s to
* Redis with a frequency equal to `config::ping_interval`.
*
* \li Starts reading from the socket and delivering events to the
* request started with \c async_exec and \c async_receive_event.
* @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.
* @remark When a timeout occur and config::enable_reconnect is
* set, this function will automatically try a reconnection
* without returning control to the user.
*
* For an example see echo_server.cpp.
*
* \param token Completion token.
*
@@ -259,10 +266,8 @@ public:
* @code
* void f(boost::system::error_code);
* @endcode
*
* \return This function returns only when there is an error.
*/
template <class CompletionToken = default_completion_token_type>
template <class CompletionToken = boost::asio::default_completion_token_t<executor_type>>
auto async_run(CompletionToken token = CompletionToken{})
{
return boost::asio::async_compose
@@ -273,7 +278,7 @@ public:
/** @brief Connects and executes a request asynchronously.
*
* Combines \c async_run and the other \c async_exec overload in a
* Combines the other `async_run` overload with `async_exec` in a
* single function. This function is useful for users that want to
* send a single request to the server and close it.
*
@@ -281,7 +286,8 @@ public:
* \param adapter Response adapter.
* \param token Asio completion token.
*
* For an example see intro.cpp. The completion token must have the following signature
* For an example see intro.cpp. The completion token must have
* the following signature
*
* @code
* void f(boost::system::error_code, std::size_t);
@@ -291,7 +297,7 @@ public:
*/
template <
class Adapter = detail::response_traits<void>::adapter_type,
class CompletionToken = default_completion_token_type>
class CompletionToken = boost::asio::default_completion_token_t<executor_type>>
auto async_run(
resp3::request const& req,
Adapter adapter = adapt(),
@@ -305,6 +311,9 @@ 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.
*
* \param req Request object.
* \param adapter Response adapter.
@@ -322,7 +331,7 @@ public:
*/
template <
class Adapter = detail::response_traits<void>::adapter_type,
class CompletionToken = default_completion_token_type>
class CompletionToken = boost::asio::default_completion_token_t<executor_type>>
auto async_exec(
resp3::request const& req,
Adapter adapter = adapt(),
@@ -336,11 +345,11 @@ public:
>(detail::exec_op<connection, Adapter>{this, &req, adapter}, token, resv_);
}
/** @brief Receives unsolicited events asynchronously.
/** @brief Receives server side pushes asynchronously.
*
* Users that expect unsolicited events should call this function
* in a loop. If an unsolicited events comes in and there is no
* reader, the connection will hang and eventually timeout.
* Users that expect server pushes have to call this function in a
* loop. If an unsolicited event comes in and there is no reader,
* the connection will hang and eventually timeout.
*
* \param adapter The response adapter.
* \param token The Asio completion token.
@@ -351,10 +360,13 @@ public:
* @code
* void f(boost::system::error_code, std::size_t);
* @endcode
*
* Where the second parameter is the size of the push in
* bytes.
*/
template <
class Adapter = detail::response_traits<void>::adapter_type,
class CompletionToken = default_completion_token_type>
class CompletionToken = boost::asio::default_completion_token_t<executor_type>>
auto async_receive_push(
Adapter adapter = adapt(),
CompletionToken token = CompletionToken{})
@@ -374,7 +386,7 @@ public:
/** @brief Receives internal events.
*
* See enum \c events for a list of events.
* See enum \c events for the list of events.
*
* \param token The Asio completion token.
*
@@ -384,7 +396,7 @@ public:
* void f(boost::system::error_code, event);
* @endcode
*/
template <class CompletionToken = default_completion_token_type>
template <class CompletionToken = boost::asio::default_completion_token_t<executor_type>>
auto async_receive_event(CompletionToken token = CompletionToken{})
{
return event_channel_.async_receive(token);
@@ -392,6 +404,14 @@ public:
/// @}
private:
using clock_type = std::chrono::steady_clock;
using clock_traits_type = boost::asio::wait_traits<clock_type>;
using timer_type = boost::asio::basic_waitable_timer<clock_type, clock_traits_type, executor_type>;
using resolver_type = boost::asio::ip::basic_resolver<boost::asio::ip::tcp, executor_type>;
using push_channel_type = boost::asio::experimental::channel<executor_type, void(boost::system::error_code, std::size_t)>;
using time_point_type = std::chrono::time_point<std::chrono::steady_clock>;
using event_channel_type = boost::asio::experimental::channel<executor_type, void(boost::system::error_code, event)>;
struct req_info {
req_info(executor_type ex) : timer{ex} {}
timer_type timer;
@@ -401,7 +421,6 @@ private:
bool written = false;
};
using time_point_type = std::chrono::time_point<std::chrono::steady_clock>;
using reqs_type = std::deque<std::shared_ptr<req_info>>;
template <class T, class U> friend struct detail::receive_push_op;
@@ -419,7 +438,7 @@ private:
template <class T> friend struct detail::start_op;
template <class T> friend struct detail::send_receive_op;
template <class CompletionToken = default_completion_token_type>
template <class CompletionToken>
auto async_run_one(CompletionToken token = CompletionToken{})
{
return boost::asio::async_compose

View File

@@ -36,7 +36,7 @@ public:
/** @brief Executes a request synchronously.
*
* The functions calls `connections::async_receive_exec` and waits
* The functions calls `connection::async_exec` and waits
* for its completion.
*
* @param req The request.
@@ -71,7 +71,7 @@ public:
/** @brief Executes a command synchronously
*
* The functions calls `connections::async_exec` and waits for its
* The functions calls `connection::async_exec` and waits for its
* completion.
*
* @param req The request.
@@ -91,7 +91,7 @@ public:
/** @brief Receives server pushes synchronusly.
*
* The functions calls `connections::async_receive_push` and
* The functions calls `connection::async_receive_push` and
* waits for its completion.
*
* @param adapter The response adapter.
@@ -124,7 +124,7 @@ public:
/** @brief Receives server pushes synchronusly.
*
* The functions calls `connections::async_receive_push` and
* The functions calls `connection::async_receive_push` and
* waits for its completion.
*
* @param adapter The response adapter.
@@ -143,7 +143,7 @@ public:
/** @brief Receives events synchronously.
*
* The functions calls `connections::async_receive_event` and
* The functions calls `connection::async_receive_event` and
* waits for its completion.
*
* @param ec Error code in case of error.
@@ -174,7 +174,7 @@ public:
/** @brief Receives events synchronously
*
* The functions calls `connections::async_receive_event` and
* The functions calls `connection::async_receive_event` and
* waits for its completion.
*
* @throws std::system_error in case of error.
@@ -191,7 +191,7 @@ public:
/** @brief Calls \c async_run from the underlying connection.
*
* The functions calls `connections::async_run` and waits for its
* The functions calls `connection::async_run` and waits for its
* completion.
*
* @param ec Error code.
@@ -217,7 +217,7 @@ public:
/** @brief Calls \c async_run from the underlying connection.
*
* The functions calls `connections::async_run` and waits for its
* The functions calls `connection::async_run` and waits for its
* completion.
*
* @throws std::system_error.