Skip to content

Commit 1645881

Browse files
committed
Doc improvements and add guarded_op class.
1 parent 730e06c commit 1645881

File tree

8 files changed

+180
-148
lines changed

8 files changed

+180
-148
lines changed

README.md

Lines changed: 42 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,21 @@ with three library entities
2323
* `aedis::adapt()`: A function that adapts data structures to receive Redis responses.
2424
* `aedis::connection`: A connection to the Redis server.
2525

26-
For example, the coroutine below reads Redis [hashes](https://redis.io/docs/data-types/hashes/)
27-
in a `std::map` and quits the connection (see containers.cpp)
26+
For example, the coroutine below uses a short-lived connection to read Redis
27+
[hashes](https://redis.io/docs/data-types/hashes/)
28+
in a `std::map` (see containers.cpp)
2829

2930
```cpp
30-
auto hgetall(std::shared_ptr<connection> conn) -> net::awaitable<void>
31+
auto hgetall() -> net::awaitable<void>
3132
{
33+
auto conn = std::make_shared<connection>(co_await net::this_coro::executor);
34+
35+
// Resolves and connects (from examples/common.hpp to avoid vebosity)
36+
co_await connect(conn, "127.0.0.1", "6379");
37+
3238
// A request contains multiple Redis commands.
3339
request req;
40+
req.get_config().cancel_on_connection_lost = true;
3441
req.push("HELLO", 3);
3542
req.push("HGETALL", "hset-key");
3643
req.push("QUIT");
@@ -39,46 +46,29 @@ auto hgetall(std::shared_ptr<connection> conn) -> net::awaitable<void>
3946
std::tuple<aedis::ignore, std::map<std::string, std::string>, aedis::ignore> resp;
4047

4148
// Executes the request and reads the response.
42-
co_await conn->async_exec(req, adapt(resp));
43-
44-
// Uses the map ...
45-
}
46-
```
49+
co_await (conn->async_run() || conn->async_exec(req, adapt(resp)));
4750

48-
The execution of `connection::async_exec` as shown above must
49-
still be triggered by the `connection::async_run` member function. For
50-
example, the code below uses a short-lived connection to execute the
51-
coroutine above
52-
53-
```cpp
54-
net::awaitable<void> async_main()
55-
{
56-
auto conn = std::make_shared<connection>(co_await net::this_coro::executor);
57-
58-
// Resolves and connects (from examples/common.hpp to avoid vebosity)
59-
co_await connect(conn, "127.0.0.1", "6379");
60-
61-
// Runs hgetall (previous example).
62-
co_await (conn->async_run() || hgetall(conn));
51+
// Use the map ...
6352
}
6453
```
6554

66-
Long-lived connections follow the same principle (see the examples
67-
below) and will be discussed in more detail later. The role of the
68-
`async_run` is to coordinate IO and ensure the connection is always
69-
reading from the socket. The reationale behind this design is
55+
The execution of `connection::async_exec` as shown above is triggered
56+
by the `connection::async_run` member function, whose role is to
57+
coordinate IO and ensure the connection is always reading from the
58+
socket. The rationale behind this design is
7059

7160
* Provide quick reaction to disconnections and hence faster failovers.
7261
* Support server pushes and requests in the same connection object,
7362
concurrently.
7463

75-
Before we see with more detail how connections, requests and responses
76-
work, users might find it useful to skim over the examples in order to
77-
gain a better feeling about the library capabilities.
64+
Long-lived connections follow the same principle and will be discussed
65+
more later. Before we go to the next sections, users might find it
66+
useful to skim over the examples in order to gain a better feeling
67+
about the library capabilities.
7868

7969
* intro.cpp: The Aedis hello-world program. Sends one command and quits the connection.
8070
* intro_tls.cpp: Same as intro.cpp but over TLS.
81-
* intro_sync.cpp: Shows how to use the conneciton class synchronously.
71+
* intro_sync.cpp: Shows how to use the connection class synchronously.
8272
* containers.cpp: Shows how to send and receive STL containers and how to use transactions.
8373
* serialization.cpp: Shows how to serialize types using Boost.Json.
8474
* resolve_with_sentinel.cpp: Shows how to resolve a master address using sentinels.
@@ -88,14 +78,14 @@ gain a better feeling about the library capabilities.
8878
* low_level_sync.cpp: Sends a ping synchronously using the low-level API.
8979
* low_level_async.cpp: Sends a ping asynchronously using the low-level API.
9080

91-
To avoid repetition code that is common to all examples have been
81+
To avoid repetition code that is common to all examples has been
9282
grouped in common.hpp. The main function used in some async examples
9383
has been factored out in the main.cpp file.
9484

9585
<a name="requests"></a>
96-
### Requests
86+
## Requests
9787

98-
Redis requests are composed of one or more Redis commands (in
88+
Redis requests are composed of one or more commands (in the
9989
Redis documentation they are called
10090
[pipelines](https://redis.io/topics/pipelining)). For example
10191

@@ -152,11 +142,11 @@ std::map<std::string, mystruct> map {...};
152142
req.push_range("HSET", "key", map);
153143
```
154144
155-
Example serialization.cpp shows how store json string in Redis.
145+
Example serialization.cpp shows how store json strings in Redis.
156146
157147
<a name="responses"></a>
158148
159-
### Responses
149+
## Responses
160150
161151
Aedis uses the following strategy to support Redis responses
162152
@@ -182,7 +172,7 @@ std::tuple<std::string, int, std::string>
182172
The pattern might have become apparent to the reader: the tuple must
183173
have as many elements as the request has commands (exceptions below).
184174
It is also necessary that each tuple element is capable of storing the
185-
response to the command it refers to, otherwise an error will ocurr.
175+
response to the command it refers to, otherwise an error will occur.
186176
To ignore responses to individual commands in the request use the tag
187177
`aedis::ignore`
188178

@@ -425,7 +415,7 @@ containers. The same reasoning also applies to sets e.g. `SMEMBERS`
425415
and other data structures in general.
426416

427417
<a name="connection"></a>
428-
### Connection
418+
## Connection
429419

430420
The `aedis::connection` is a class that provides async-only
431421
communication with a Redis server by means of three member
@@ -452,7 +442,7 @@ the points above with more detail.
452442

453443
#### Run
454444

455-
The code snipet in the overview section has shown us an example that
445+
The code snippet in the overview section has shown us an example that
456446
used `connection::async_run` in short-lived connection, in the general
457447
case however, applications will connect to a Redis server and hang
458448
around for as long as possible, until the connection is lost for some
@@ -495,7 +485,7 @@ auto async_main() -> net::awaitable<void>
495485

496486
It is important to emphasize that Redis servers use the old
497487
communication protocol RESP2 by default, therefore it is necessary to
498-
send a `HELLO 3` command everytime a connection is established.
488+
send a `HELLO 3` command every time a connection is established.
499489
Another common scenario for reconnection is, for example, a failover
500490
with sentinels, covered in `resolve_with_sentinel.cpp` example.
501491

@@ -624,12 +614,12 @@ co_await (conn.async_run(...) || time.async_wait(...))
624614
co_await (conn.async_exec(...) || conn.async_exec(...) || ... || conn.async_exec(...))
625615
```
626616
627-
* This works but is considered an antipattern. Unless
617+
* This works but is considered an anti-pattern. Unless
628618
the user has set `aedis::resp3::request::config::coalesce` to
629619
`false`, and he shouldn't, the connection will automatically merge
630620
the individual requests into a single payload anyway.
631621
632-
## Why Aedis
622+
## Comparison
633623
634624
The main reason for why I started writing Aedis was to have a client
635625
compatible with the Asio asynchronous model. As I made progresses I could
@@ -642,6 +632,8 @@ stars, namely
642632
643633
* https://github.com/sewenew/redis-plus-plus
644634
635+
### Aedis vs Redis-plus-plus
636+
645637
Before we start it is important to mentioning some of the things
646638
redis-plus-plus does not support
647639
@@ -738,7 +730,7 @@ enqueueing a message and triggering a write when it can be sent.
738730
It is also not clear how are pipelines realised with this design
739731
(if at all).
740732

741-
### Echo server benchmark
733+
## Echo server benchmark
742734

743735
This document benchmarks the performance of TCP echo servers I
744736
implemented in different languages using different Redis clients. The
@@ -860,8 +852,9 @@ The requirements for using Aedis are
860852

861853
The following compilers are supported
862854

863-
- Tested with gcc: 10, 11, 12.
864-
- Tested with clang: 11, 13, 14.
855+
- Gcc: 10, 11, 12.
856+
- Clang: 11, 13, 14.
857+
- Visual Studio 17 2022, Visual Studio 16 2019.
865858

866859
## Acknowledgement
867860

@@ -875,18 +868,19 @@ Acknowledgement to people that helped shape Aedis
875868

876869
## Changelog
877870

878-
### master
871+
### 1.4.0
879872

880873
* Removes dependency on Boost.Hana.
881874
* Removes dependency on `boost::string_view`, now using `std::string_view`.
875+
* Fixes build and setup CI on windows.
882876

883877
### v1.3.0-1
884878

885879
* Upgrades to Boost 1.80.0
886880

887881
* Removes automatic sending of the `HELLO` command. This can't be
888882
implemented properly without bloating the connection class. It is
889-
now a user responsability to send HELLO. Requests that contain it have
883+
now a user responsibility to send HELLO. Requests that contain it have
890884
priority over other requests and will be moved to the front of the
891885
queue, see `aedis::resp3::request::config`
892886

@@ -999,7 +993,7 @@ Acknowledgement to people that helped shape Aedis
999993
is possible in simple reconnection strategies but bloats the class
1000994
in more complex scenarios, for example, with sentinel,
1001995
authentication and TLS. This is trivial to implement in a separate
1002-
coroutine. As a result the enum `event` and `async_receive_event`
996+
coroutine. As a result the `enum event` and `async_receive_event`
1003997
have been removed from the class too.
1004998

1005999
* Fixes a bug in `connection::async_receive_push` that prevented

examples/containers.cpp

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,18 +50,25 @@ auto store(std::shared_ptr<connection> conn) -> net::awaitable<void>
5050
}
5151

5252
// Retrieves a Redis hash as an std::map.
53-
auto hgetall(std::shared_ptr<connection> conn) -> net::awaitable<void>
53+
auto hgetall() -> net::awaitable<void>
5454
{
55+
auto conn = std::make_shared<connection>(co_await net::this_coro::executor);
56+
57+
// Resolves and connects (from examples/common.hpp to avoid vebosity)
58+
co_await connect(conn, "127.0.0.1", "6379");
59+
60+
// A request contains multiple Redis commands.
5561
request req;
5662
req.get_config().cancel_on_connection_lost = true;
5763
req.push("HELLO", 3);
5864
req.push("HGETALL", "hset-key");
5965
req.push("QUIT");
6066

67+
// Tuple elements will hold the response to each command in the request.
6168
std::tuple<aedis::ignore, std::map<std::string, std::string>, aedis::ignore> resp;
6269

63-
co_await conn->async_exec(req, adapt(resp));
64-
70+
// Executes the request and reads the response.
71+
co_await (conn->async_run() || conn->async_exec(req, adapt(resp)));
6572
print(std::get<1>(resp));
6673
}
6774

@@ -102,9 +109,9 @@ net::awaitable<void> async_main()
102109
co_await connect(conn, "127.0.0.1", "6379");
103110
co_await (conn->async_run() || store(conn));
104111

105-
co_await connect(conn, "127.0.0.1", "6379");
106-
co_await (conn->async_run() || hgetall(conn));
112+
co_await hgetall();
107113

114+
// Resused the connection object.
108115
co_await connect(conn, "127.0.0.1", "6379");
109116
co_await (conn->async_run() || transaction(conn));
110117
}

include/aedis/connection.hpp

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,8 @@ namespace aedis {
1818
/** @brief A connection to the Redis server.
1919
* @ingroup high-level-api
2020
*
21-
* This class keeps a healthy connection to the Redis instance where
22-
* commands can be sent at any time. For more details, please see the
23-
* documentation of each individual function.
21+
* For more details, please see the documentation of each individual
22+
* function.
2423
*
2524
* @tparam AsyncReadWriteStream A stream that supports reading and
2625
* writing.
@@ -98,9 +97,9 @@ class basic_connection :
9897
* void f(boost::system::error_code);
9998
* @endcode
10099
*
101-
* This function will complete when the connection is lost as
102-
* follows. If the error is boost::asio::error::eof this function
103-
* will complete without error.
100+
* This function will complete when the connection is lost. If the
101+
* error is boost::asio::error::eof this function will complete
102+
* without error.
104103
*/
105104
template <class CompletionToken = boost::asio::default_completion_token_t<executor_type>>
106105
auto async_run(CompletionToken token = CompletionToken{})
@@ -110,7 +109,7 @@ class basic_connection :
110109

111110
/** @brief Executes a command on the Redis server asynchronously.
112111
*
113-
* This function will send a request to the Redis server and
112+
* This function sends a request to the Redis server and
114113
* complete after the response has been processed. If the request
115114
* contains only commands that don't expect a response, the
116115
* completion occurs after it has been written to the underlying
@@ -146,7 +145,7 @@ class basic_connection :
146145
*
147146
* Users that expect server pushes should call this function in a
148147
* loop. If a push arrives and there is no reader, the connection
149-
* will hang and eventually timeout.
148+
* will hang.
150149
*
151150
* @param adapter The response adapter.
152151
* @param token The Asio completion token.
@@ -193,7 +192,6 @@ class basic_connection :
193192
template <class, class> friend class detail::connection_base;
194193
template <class, class> friend struct detail::exec_read_op;
195194
template <class, class> friend struct detail::exec_op;
196-
template <class, class> friend struct detail::receive_op;
197195
template <class> friend struct detail::reader_op;
198196
template <class> friend struct detail::writer_op;
199197
template <class> friend struct detail::run_op;

include/aedis/detail/connection_base.hpp

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include <boost/asio/ip/tcp.hpp>
2020
#include <boost/asio/steady_timer.hpp>
2121
#include <boost/asio/bind_executor.hpp>
22+
#include <boost/asio/deferred.hpp>
2223
#include <boost/asio/experimental/channel.hpp>
2324

2425
#include <aedis/adapt.hpp>
@@ -47,7 +48,7 @@ class connection_base {
4748
connection_base(executor_type ex, std::pmr::memory_resource* resource)
4849
: writer_timer_{ex}
4950
, read_timer_{ex}
50-
, push_channel_{ex}
51+
, guarded_op_{ex}
5152
, read_buffer_{resource}
5253
, write_buffer_{resource}
5354
, reqs_{resource}
@@ -76,7 +77,7 @@ class connection_base {
7677
}
7778
case operation::receive:
7879
{
79-
push_channel_.cancel();
80+
guarded_op_.cancel();
8081
return 1U;
8182
}
8283
default: BOOST_ASSERT(false); return 0;
@@ -154,10 +155,10 @@ class connection_base {
154155
CompletionToken token = CompletionToken{})
155156
{
156157
auto f = detail::make_adapter_wrapper(adapter);
157-
return boost::asio::async_compose
158-
< CompletionToken
159-
, void(boost::system::error_code, std::size_t)
160-
>(detail::receive_op<Derived, decltype(f)>{&derived(), f}, token, writer_timer_);
158+
159+
return guarded_op_.async_wait(
160+
resp3::async_read(derived().next_layer(), make_dynamic_buffer(adapter.get_max_read_size(0)), f, boost::asio::deferred),
161+
std::move(token));
161162
}
162163

163164
template <class CompletionToken>
@@ -276,7 +277,6 @@ class connection_base {
276277

277278
using reqs_type = std::pmr::deque<std::shared_ptr<req_info>>;
278279

279-
template <class, class> friend struct detail::receive_op;
280280
template <class> friend struct detail::reader_op;
281281
template <class> friend struct detail::writer_op;
282282
template <class> friend struct detail::run_op;
@@ -371,7 +371,7 @@ class connection_base {
371371
// IO objects
372372
timer_type writer_timer_;
373373
timer_type read_timer_;
374-
push_channel_type push_channel_;
374+
detail::guarded_operation<executor_type> guarded_op_;
375375

376376
std::pmr::string read_buffer_;
377377
std::pmr::string write_buffer_;

0 commit comments

Comments
 (0)