Skip to content

Commit 69d1242

Browse files
authored
Merge pull request #116 from boostorg/113-create-an-experimental-connection-class-that-has-fast-compilation-times
113 create an experimental connection class that has fast compilation times
2 parents 607946f + a715c25 commit 69d1242

17 files changed

+331
-217
lines changed

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
cmake_minimum_required(VERSION 3.14)
22

3-
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CMAKE_COMMAND} -E time")
3+
#set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CMAKE_COMMAND} -E time")
44

55
project(
66
boost_redis

README.md

Lines changed: 47 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,30 @@
11
# boost_redis
22

3-
Boost.Redis is a [Redis](https://redis.io/) client library built on top of
3+
Boost.Redis is a high-level [Redis](https://redis.io/) client library built on top of
44
[Boost.Asio](https://www.boost.org/doc/libs/release/doc/html/boost_asio.html)
55
that implements Redis plain text protocol
66
[RESP3](https://github.com/redis/redis-specifications/blob/master/protocol/RESP3.md).
77
It can multiplex any number of client
88
requests, responses, and server pushes onto a single active socket
9-
connection to the Redis server. The library hides low-level code away
10-
from the user, which, in the majority of the cases will be concerned
11-
with only three library entities
12-
13-
* `boost::redis::connection`: A full-duplex connection to the Redis
14-
server with high-level functions to execute Redis commands, receive
15-
server pushes and automatic command [pipelines](https://redis.io/docs/manual/pipelining/).
16-
* `boost::redis::request`: A container of Redis commands that supports
17-
STL containers and user defined data types.
18-
* `boost::redis::response`: Container of Redis responses.
19-
20-
In the next sections we will cover all these points in detail with
21-
examples. The requirements for using Boost.Redis are
9+
connection to the Redis server. The requirements for using Boost.Redis are
2210

2311
* Boost 1.81 or greater.
2412
* C++17 minimum.
2513
* Redis 6 or higher (must support RESP3).
2614
* Gcc (10, 11, 12), Clang (11, 13, 14) and Visual Studio (16 2019, 17 2022).
27-
* Have basic-level knowledge about Redis and understand Asio and its asynchronous model.
15+
* Have basic-level knowledge about [Redis](https://redis.io/docs/)
16+
and [Boost.Asio](https://www.boost.org/doc/libs/1_82_0/doc/html/boost_asio/overview.html).
2817

29-
To install Boost.Redis download the latest release on
30-
https://github.com/boostorg/redis/releases. Boost.Redis is a header only
31-
library, so you can starting using it right away by adding the
32-
`include` subdirectory to your project and including
18+
The latest release can be downloaded on
19+
https://github.com/boostorg/redis/releases. The library headers can be
20+
found in the `include` subdirectory and a compilation of the source
3321

3422
```cpp
3523
#include <boost/redis/src.hpp>
3624
```
3725

38-
in no more than one source file in your applications. To build the
26+
is required. The simplest way to do it is to included this header in
27+
no more than one source file in your applications. To build the
3928
examples and tests cmake is supported, for example
4029

4130
```cpp
@@ -45,21 +34,10 @@ $ BOOST_ROOT=/opt/boost_1_81_0 cmake --preset g++-11
4534
# Windows
4635
$ cmake -G "Visual Studio 17 2022" -A x64 -B bin64 -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake
4736
```
37+
4838
<a name="connection"></a>
4939
## Connection
5040

51-
Readers that are not familiar with Redis are advised to learn more about
52-
it on https://redis.io/docs/ before we start, in essence
53-
54-
> Redis is an open source (BSD licensed), in-memory data structure
55-
> store used as a database, cache, message broker, and streaming
56-
> engine. Redis provides data structures such as strings, hashes,
57-
> lists, sets, sorted sets with range queries, bitmaps, hyperloglogs,
58-
> geospatial indexes, and streams. Redis has built-in replication, Lua
59-
> scripting, LRU eviction, transactions, and different levels of
60-
> on-disk persistence, and provides high availability via Redis
61-
> Sentinel and automatic partitioning with Redis Cluster.
62-
6341
Let us start with a simple application that uses a short-lived
6442
connection to send a [ping](https://redis.io/commands/ping/) command
6543
to Redis
@@ -78,7 +56,7 @@ auto co_main(config const& cfg) -> net::awaitable<void>
7856
response<std::string> resp;
7957

8058
// Executes the request.
81-
co_await conn->async_exec(req, resp);
59+
co_await conn->async_exec(req, resp, net::deferred);
8260
conn->cancel();
8361

8462
std::cout << "PING: " << std::get<0>(resp).value() << std::endl;
@@ -87,12 +65,12 @@ auto co_main(config const& cfg) -> net::awaitable<void>
8765
8866
The roles played by the `async_run` and `async_exec` functions are
8967
90-
* `connection::async_exec`: Execute the commands contained in the
68+
* `async_exec`: Execute the commands contained in the
9169
request and store the individual responses in the `resp` object. Can
9270
be called from multiple places in your code concurrently.
93-
* `connection::async_run`: Resolve, connect, ssl-handshake,
94-
resp3-handshake, health-checks reconnection and coordinate low-level
95-
read and write operations..
71+
* `async_run`: Resolve, connect, ssl-handshake,
72+
resp3-handshake, health-checks, reconnection and coordinate low-level
73+
read and write operations (among other things).
9674
9775
### Server pushes
9876
@@ -114,22 +92,22 @@ receiver(std::shared_ptr<connection> conn) -> net::awaitable<void>
11492
request req;
11593
req.push("SUBSCRIBE", "channel");
11694
117-
while (!conn->is_cancelled()) {
95+
// Loop while reconnection is enabled
96+
while (conn->will_reconnect()) {
11897
11998
// Reconnect to channels.
120-
co_await conn->async_exec(req);
99+
co_await conn->async_exec(req, ignore, net::deferred);
121100
122-
// Loop reading Redis pushs messages.
101+
// Loop reading Redis pushes.
123102
for (generic_response resp;;) {
124103
error_code ec;
125104
co_await conn->async_receive(resp, net::redirect_error(net::use_awaitable, ec));
126105
if (ec)
127106
break; // Connection lost, break so we can reconnect to channels.
128-
std::cout
129-
<< resp.value().at(1).value
130-
<< " " << resp.value().at(2).value
131-
<< " " << resp.value().at(3).value
132-
<< std::endl;
107+
108+
// Use the response resp in some way and then clear it.
109+
...
110+
133111
resp.value().clear();
134112
}
135113
}
@@ -167,22 +145,21 @@ req.push_range("HSET", "key", map);
167145
168146
Sending a request to Redis is performed with `boost::redis::connection::async_exec` as already stated.
169147
170-
<a name="responses"></a>
171-
172148
### Config flags
173149
174150
The `boost::redis::request::config` object inside the request dictates how the
175151
`boost::redis::connection` should handle the request in some important situations. The
176152
reader is advised to read it carefully.
177153
154+
<a name="responses"></a>
178155
## Responses
179156
180157
Boost.Redis uses the following strategy to support Redis responses
181158
182-
* **Static**: For `boost::redis::request` whose sizes are known at compile time use the `response` type.
159+
* `boost::redis::request` is used for requests whose number of commands are not dynamic.
183160
* **Dynamic**: Otherwise use `boost::redis::generic_response`.
184161
185-
For example, below is a request with a compile time size
162+
For example, the request below has three commands
186163
187164
```cpp
188165
request req;
@@ -191,18 +168,19 @@ req.push("INCR", "key");
191168
req.push("QUIT");
192169
```
193170

194-
To read the response to this request users can use the following tuple
171+
and its response also has three comamnds and can be read in the
172+
following response object
195173

196174
```cpp
197175
response<std::string, int, std::string>
198176
```
199177

200-
The pattern might have become apparent to the reader: the tuple must
178+
The response behaves as a tuple and must
201179
have as many elements as the request has commands (exceptions below).
202180
It is also necessary that each tuple element is capable of storing the
203181
response to the command it refers to, otherwise an error will occur.
204182
To ignore responses to individual commands in the request use the tag
205-
`boost::redis::ignore_t`
183+
`boost::redis::ignore_t`, for example
206184

207185
```cpp
208186
// Ignore the second and last responses.
@@ -266,18 +244,14 @@ response<
266244
Where both are passed to `async_exec` as showed elsewhere
267245

268246
```cpp
269-
co_await conn->async_exec(req, resp);
247+
co_await conn->async_exec(req, resp, net::deferred);
270248
```
271249
272-
If the intention is to ignore the response to all commands altogether
273-
use `ignore`
250+
If the intention is to ignore responses altogether use `ignore`
274251
275252
```cpp
276253
// Ignores the response
277-
co_await conn->async_exec(req, ignore);
278-
279-
// The default response argument will also ignore responses.
280-
co_await conn->async_exec(req);
254+
co_await conn->async_exec(req, ignore, net::deferred);
281255
```
282256

283257
Responses that contain nested aggregates or heterogeneous data
@@ -294,7 +268,7 @@ Commands that have no response like
294268
* `"PSUBSCRIBE"`
295269
* `"UNSUBSCRIBE"`
296270

297-
must be **NOT** be included in the response tuple. For example, the request below
271+
must **NOT** be included in the response tuple. For example, the request below
298272

299273
```cpp
300274
request req;
@@ -304,7 +278,7 @@ req.push("QUIT");
304278
```
305279

306280
must be read in this tuple `response<std::string, std::string>`,
307-
that has size two.
281+
that has static size two.
308282

309283
### Null
310284

@@ -320,17 +294,17 @@ response<
320294
...
321295
> resp;
322296

323-
co_await conn->async_exec(req, resp);
297+
co_await conn->async_exec(req, resp, net::deferred);
324298
```
325299
326300
Everything else stays pretty much the same.
327301
328302
### Transactions
329303
330-
To read responses to transactions we must first observe that Redis will
331-
queue the transaction commands and send their individual responses as elements
332-
of an array, the array is itself the response to the `EXEC` command.
333-
For example, to read the response to this request
304+
To read responses to transactions we must first observe that Redis
305+
will queue the transaction commands and send their individual
306+
responses as elements of an array, the array is itself the response to
307+
the `EXEC` command. For example, to read the response to this request
334308
335309
```cpp
336310
req.push("MULTI");
@@ -360,7 +334,7 @@ response<
360334
exec_resp_type, // exec
361335
> resp;
362336

363-
co_await conn->async_exec(req, resp);
337+
co_await conn->async_exec(req, resp, net::deferred);
364338
```
365339
366340
For a complete example see cpp20_containers.cpp.
@@ -373,8 +347,8 @@ There are cases where responses to Redis
373347
commands won't fit in the model presented above, some examples are
374348
375349
* Commands (like `set`) whose responses don't have a fixed
376-
RESP3 type. Expecting an `int` and receiving a blob-string
377-
will result in error.
350+
RESP3 type. Expecting an `int` and receiving a blob-string
351+
will result in error.
378352
* RESP3 aggregates that contain nested aggregates can't be read in STL containers.
379353
* Transactions with a dynamic number of commands can't be read in a `response`.
380354
@@ -408,7 +382,7 @@ using other types
408382
```cpp
409383
// Receives any RESP3 simple or aggregate data type.
410384
boost::redis::generic_response resp;
411-
co_await conn->async_exec(req, resp);
385+
co_await conn->async_exec(req, resp, net::deferred);
412386
```
413387
414388
For example, suppose we want to retrieve a hash data structure
@@ -460,9 +434,8 @@ The examples below show how to use the features discussed so far
460434
* cpp17_intro.cpp: Uses callbacks and requires C++17.
461435
* cpp17_intro_sync.cpp: Runs `async_run` in a separate thread and performs synchronous calls to `async_exec`.
462436

463-
To avoid repetition code that is common to some examples has been
464-
grouped in common.hpp. The main function used in some async examples
465-
has been factored out in the main.cpp file.
437+
The main function used in some async examples has been factored out in
438+
the main.cpp file.
466439

467440
## Echo server benchmark
468441

@@ -701,7 +674,7 @@ https://lists.boost.org/Archives/boost/2023/01/253944.php.
701674

702675
## Changelog
703676

704-
### master (incorporates changes to conform the boost review and more)
677+
### develop (incorporates changes to conform the boost review and more)
705678

706679
* Adds Redis stream example.
707680

examples/cpp20_chat_room.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,12 @@
1919

2020
namespace net = boost::asio;
2121
using stream_descriptor = net::deferred_t::as_default_on_t<net::posix::stream_descriptor>;
22-
using connection = net::deferred_t::as_default_on_t<boost::redis::connection>;
2322
using signal_set = net::deferred_t::as_default_on_t<net::signal_set>;
2423
using boost::redis::request;
2524
using boost::redis::generic_response;
2625
using boost::redis::config;
26+
using boost::redis::connection;
27+
using boost::redis::ignore;
2728
using net::redirect_error;
2829
using net::use_awaitable;
2930
using boost::system::error_code;
@@ -41,7 +42,7 @@ receiver(std::shared_ptr<connection> conn) -> net::awaitable<void>
4142
while (conn->will_reconnect()) {
4243

4344
// Subscribe to channels.
44-
co_await conn->async_exec(req);
45+
co_await conn->async_exec(req, ignore, net::deferred);
4546

4647
// Loop reading Redis push messages.
4748
for (generic_response resp;;) {
@@ -66,7 +67,7 @@ auto publisher(std::shared_ptr<stream_descriptor> in, std::shared_ptr<connection
6667
auto n = co_await net::async_read_until(*in, net::dynamic_buffer(msg, 1024), "\n");
6768
request req;
6869
req.push("PUBLISH", "channel", msg);
69-
co_await conn->async_exec(req);
70+
co_await conn->async_exec(req, ignore, net::deferred);
7071
msg.erase(0, n);
7172
}
7273
}

examples/cpp20_containers.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ using boost::redis::response;
2020
using boost::redis::ignore_t;
2121
using boost::redis::ignore;
2222
using boost::redis::config;
23-
using connection = net::deferred_t::as_default_on_t<boost::redis::connection>;
23+
using boost::redis::connection;
2424

2525
void print(std::map<std::string, std::string> const& cont)
2626
{
@@ -47,7 +47,7 @@ auto store(std::shared_ptr<connection> conn) -> net::awaitable<void>
4747
req.push_range("RPUSH", "rpush-key", vec);
4848
req.push_range("HSET", "hset-key", map);
4949

50-
co_await conn->async_exec(req);
50+
co_await conn->async_exec(req, ignore, net::deferred);
5151
}
5252

5353
auto hgetall(std::shared_ptr<connection> conn) -> net::awaitable<void>
@@ -60,7 +60,7 @@ auto hgetall(std::shared_ptr<connection> conn) -> net::awaitable<void>
6060
response<std::map<std::string, std::string>> resp;
6161

6262
// Executes the request and reads the response.
63-
co_await conn->async_exec(req, resp);
63+
co_await conn->async_exec(req, resp, net::deferred);
6464

6565
print(std::get<0>(resp).value());
6666
}
@@ -81,7 +81,7 @@ auto transaction(std::shared_ptr<connection> conn) -> net::awaitable<void>
8181
response<std::optional<std::vector<int>>, std::optional<std::map<std::string, std::string>>> // exec
8282
> resp;
8383

84-
co_await conn->async_exec(req, resp);
84+
co_await conn->async_exec(req, resp, net::deferred);
8585

8686
print(std::get<0>(std::get<3>(resp).value()).value().value());
8787
print(std::get<1>(std::get<3>(resp).value()).value().value());

examples/cpp20_echo_server.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@ namespace net = boost::asio;
1818
using tcp_socket = net::deferred_t::as_default_on_t<net::ip::tcp::socket>;
1919
using tcp_acceptor = net::deferred_t::as_default_on_t<net::ip::tcp::acceptor>;
2020
using signal_set = net::deferred_t::as_default_on_t<net::signal_set>;
21-
using connection = net::deferred_t::as_default_on_t<boost::redis::connection>;
2221
using boost::redis::request;
2322
using boost::redis::response;
2423
using boost::redis::config;
2524
using boost::system::error_code;
25+
using boost::redis::connection;
2626
using namespace std::chrono_literals;
2727

2828
auto echo_server_session(tcp_socket socket, std::shared_ptr<connection> conn) -> net::awaitable<void>
@@ -33,7 +33,7 @@ auto echo_server_session(tcp_socket socket, std::shared_ptr<connection> conn) ->
3333
for (std::string buffer;;) {
3434
auto n = co_await net::async_read_until(socket, net::dynamic_buffer(buffer, 1024), "\n");
3535
req.push("PING", buffer);
36-
co_await conn->async_exec(req, resp);
36+
co_await conn->async_exec(req, resp, net::deferred);
3737
co_await net::async_write(socket, net::buffer(std::get<0>(resp).value()));
3838
std::get<0>(resp).value().clear();
3939
req.clear();

0 commit comments

Comments
 (0)