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

Merge pull request #92 from boostorg/90-add-support-for-reconnection

90 add support for reconnection
This commit is contained in:
Marcelo
2023-05-06 15:39:29 +02:00
committed by GitHub
60 changed files with 2876 additions and 1879 deletions

View File

@@ -58,6 +58,17 @@ include_directories(include)
# Main function for the examples.
#=======================================================================
add_library(test_common STATIC
tests/common.cpp
)
target_compile_features(test_common PUBLIC cxx_std_17)
if (MSVC)
target_compile_options(test_common PRIVATE /bigobj)
target_compile_definitions(test_common PRIVATE _WIN32_WINNT=0x0601)
endif()
#=======================================================================
add_library(common STATIC
examples/start.cpp
examples/main.cpp
@@ -81,15 +92,6 @@ if (MSVC)
target_compile_definitions(cpp20_intro PRIVATE _WIN32_WINNT=0x0601)
endif()
add_executable(cpp20_intro_awaitable_ops examples/cpp20_intro_awaitable_ops.cpp)
target_link_libraries(cpp20_intro_awaitable_ops common)
target_compile_features(cpp20_intro_awaitable_ops PUBLIC cxx_std_20)
add_test(cpp20_intro_awaitable_ops cpp20_intro_awaitable_ops)
if (MSVC)
target_compile_options(cpp20_intro_awaitable_ops PRIVATE /bigobj)
target_compile_definitions(cpp20_intro_awaitable_ops PRIVATE _WIN32_WINNT=0x0601)
endif()
add_executable(cpp17_intro examples/cpp17_intro.cpp)
target_compile_features(cpp17_intro PUBLIC cxx_std_17)
add_test(cpp17_intro cpp17_intro)
@@ -156,10 +158,12 @@ if (Protobuf_FOUND)
endif()
endif()
if (NOT MSVC)
add_executable(cpp20_subscriber examples/cpp20_subscriber.cpp)
target_compile_features(cpp20_subscriber PUBLIC cxx_std_20)
target_link_libraries(cpp20_subscriber common)
if (MSVC)
target_compile_options(cpp20_subscriber PRIVATE /bigobj)
target_compile_definitions(cpp20_subscriber PRIVATE _WIN32_WINNT=0x0601)
endif()
add_executable(cpp20_intro_tls examples/cpp20_intro_tls.cpp)
@@ -205,6 +209,7 @@ endif()
add_executable(test_conn_exec tests/conn_exec.cpp)
target_compile_features(test_conn_exec PUBLIC cxx_std_20)
target_link_libraries(test_conn_exec test_common)
add_test(test_conn_exec test_conn_exec)
if (MSVC)
target_compile_options(test_conn_exec PRIVATE /bigobj)
@@ -213,6 +218,7 @@ endif()
add_executable(test_conn_exec_retry tests/conn_exec_retry.cpp)
target_compile_features(test_conn_exec_retry PUBLIC cxx_std_20)
target_link_libraries(test_conn_exec_retry test_common)
add_test(test_conn_exec_retry test_conn_exec_retry)
if (MSVC)
target_compile_options(test_conn_exec_retry PRIVATE /bigobj)
@@ -221,6 +227,7 @@ endif()
add_executable(test_conn_push tests/conn_push.cpp)
target_compile_features(test_conn_push PUBLIC cxx_std_20)
target_link_libraries(test_conn_push test_common)
add_test(test_conn_push test_conn_push)
if (MSVC)
target_compile_options(test_conn_push PRIVATE /bigobj)
@@ -237,7 +244,7 @@ endif()
add_executable(test_conn_reconnect tests/conn_reconnect.cpp)
target_compile_features(test_conn_reconnect PUBLIC cxx_std_20)
target_link_libraries(test_conn_reconnect common)
target_link_libraries(test_conn_reconnect common test_common)
add_test(test_conn_reconnect test_conn_reconnect)
if (MSVC)
target_compile_options(test_conn_reconnect PRIVATE /bigobj)
@@ -271,16 +278,25 @@ endif()
add_executable(test_conn_exec_cancel tests/conn_exec_cancel.cpp)
target_compile_features(test_conn_exec_cancel PUBLIC cxx_std_20)
target_link_libraries(test_conn_exec_cancel common)
target_link_libraries(test_conn_exec_cancel common test_common)
add_test(test_conn_exec_cancel test_conn_exec_cancel)
if (MSVC)
target_compile_options(test_conn_exec_cancel PRIVATE /bigobj)
target_compile_definitions(test_conn_exec_cancel PRIVATE _WIN32_WINNT=0x0601)
endif()
add_executable(test_conn_exec_cancel2 tests/conn_exec_cancel2.cpp)
target_compile_features(test_conn_exec_cancel2 PUBLIC cxx_std_20)
target_link_libraries(test_conn_exec_cancel2 common test_common)
add_test(test_conn_exec_cancel2 test_conn_exec_cancel2)
if (MSVC)
target_compile_options(test_conn_exec_cancel2 PRIVATE /bigobj)
target_compile_definitions(test_conn_exec_cancel2 PRIVATE _WIN32_WINNT=0x0601)
endif()
add_executable(test_conn_exec_error tests/conn_exec_error.cpp)
target_compile_features(test_conn_exec_error PUBLIC cxx_std_17)
target_link_libraries(test_conn_exec_error common)
target_link_libraries(test_conn_exec_error common test_common)
add_test(test_conn_exec_error test_conn_exec_error)
if (MSVC)
target_compile_options(test_conn_exec_error PRIVATE /bigobj)
@@ -289,7 +305,7 @@ endif()
add_executable(test_conn_echo_stress tests/conn_echo_stress.cpp)
target_compile_features(test_conn_echo_stress PUBLIC cxx_std_20)
target_link_libraries(test_conn_echo_stress common)
target_link_libraries(test_conn_echo_stress common test_common)
add_test(test_conn_echo_stress test_conn_echo_stress)
if (MSVC)
target_compile_options(test_conn_echo_stress PRIVATE /bigobj)
@@ -304,22 +320,27 @@ if (MSVC)
target_compile_definitions(test_request PRIVATE _WIN32_WINNT=0x0601)
endif()
if (NOT MSVC)
add_executable(test_issue_50 tests/issue_50.cpp)
target_compile_features(test_issue_50 PUBLIC cxx_std_20)
target_link_libraries(test_issue_50 common)
add_test(test_issue_50 test_issue_50)
if (MSVC)
target_compile_options(test_issue_50 PRIVATE /bigobj)
target_compile_definitions(test_issue_50 PRIVATE _WIN32_WINNT=0x0601)
endif()
if (NOT MSVC)
add_executable(test_conn_check_health tests/conn_check_health.cpp)
target_compile_features(test_conn_check_health PUBLIC cxx_std_17)
target_link_libraries(test_conn_check_health common)
add_test(test_conn_check_health test_conn_check_health)
if (MSVC)
target_compile_options(test_conn_check_health PRIVATE /bigobj)
target_compile_definitions(test_conn_check_health PRIVATE _WIN32_WINNT=0x0601)
endif()
add_executable(test_run tests/run.cpp)
target_compile_features(test_run PUBLIC cxx_std_17)
target_link_libraries(test_run test_common)
add_test(test_run test_run)
if (MSVC)
target_compile_options(test_run PRIVATE /bigobj)

185
README.md
View File

@@ -2,9 +2,9 @@
Boost.Redis is a [Redis](https://redis.io/) client library built on top of
[Boost.Asio](https://www.boost.org/doc/libs/release/doc/html/boost_asio.html)
that implements
[RESP3](https://github.com/redis/redis-specifications/blob/master/protocol/RESP3.md),
a plain text protocol which can multiplex any number of client
that implements Redis plain text protocol
[RESP3](https://github.com/redis/redis-specifications/blob/master/protocol/RESP3.md).
It can multiplex any number of client
requests, responses, and server pushes onto a single active socket
connection to the Redis server. The library hides low-level code away
from the user, which, in the majority of the cases will be concerned
@@ -17,7 +17,7 @@ with only three library entities
STL containers and user defined data types.
* `boost::redis::response`: Container of Redis responses.
In the next sections we will cover all those points in detail with
In the next sections we will cover all these points in detail with
examples. The requirements for using Boost.Redis are
* Boost 1.81 or greater.
@@ -40,7 +40,7 @@ examples and tests cmake is supported, for example
```cpp
# Linux
$ BOOST_ROOT=/opt/boost_1_81_0 cmake --preset dev
$ BOOST_ROOT=/opt/boost_1_81_0 cmake --preset g++-11
# Windows
$ cmake -G "Visual Studio 17 2022" -A x64 -B bin64 -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake
@@ -65,107 +65,34 @@ connection to send a [ping](https://redis.io/commands/ping/) command
to Redis
```cpp
auto run(std::shared_ptr<connection> conn, std::string host, std::string port) -> net::awaitable<void>
auto co_main(config const& cfg) -> net::awaitable<void>
{
// From examples/common.hpp to avoid vebosity
co_await connect(conn, host, port);
auto conn = std::make_shared<connection>(co_await net::this_coro::executor);
conn->async_run(cfg, {}, net::consign(net::detached, conn));
// async_run coordinates read and write operations.
co_await conn->async_run();
// Cancel pending operations, if any.
conn->cancel(operation::exec);
conn->cancel(operation::receive);
}
auto co_main(std::string host, std::string port) -> net::awaitable<void>
{
auto ex = co_await net::this_coro::executor;
auto conn = std::make_shared<connection>(ex);
net::co_spawn(ex, run(conn, host, port), net::detached);
// A request can contain multiple commands.
// A request containing only a ping command.
request req;
req.push("HELLO", 3);
req.push("PING", "Hello world");
req.push("QUIT");
// Stores responses of each individual command. The responses to
// HELLO and QUIT are being ignored for simplicity.
response<ignore_t, std::string, ignore_t> resp;
// Response where the PONG response will be stored.
response<std::string> resp;
// Executes the request.
co_await conn->async_exec(req, resp);
conn->cancel();
std::cout << "PING: " << std::get<1>(resp).value() << std::endl;
std::cout << "PING: " << std::get<0>(resp).value() << std::endl;
}
```
The roles played by the `async_run` and `async_exec` functions are
* `connection::async_exec`: Execute the commands contained in the
request and store the individual responses in the `resp` object. Can
be called from multiple places in your code concurrently.
* `connection::async_run`: Coordinate low-level read and write
operations. More specifically, it will hand IO control to
`async_exec` when a response arrives and to `async_receive` when a
server-push is received. It is also responsible for triggering
writes of pending requests.
Depending on the user's requirements, there are different styles of
calling `async_run`. For example, in a short-lived connection where
there is only one active client communicating with the server, the
easiest way to call `async_run` is to only run it simultaneously with
the `async_exec` call, this is exemplified in
cpp20_intro_awaitable_ops.cpp. If there are many in-process clients
performing simultaneous requests, an alternative is to launch a
long-running coroutine which calls `async_run` detached from other
operations as shown in the example above, cpp20_intro.cpp and
cpp20_echo_server.cpp. The list of examples below will help users
comparing different ways of implementing the ping example shown above
* cpp20_intro_awaitable_ops.cpp: Uses awaitable operators.
* cpp20_intro.cpp: Calls `async_run` detached from other operations.
* cpp20_intro_tls.cpp: Communicates over TLS.
* cpp17_intro.cpp: Uses callbacks and requires C++17.
* cpp17_intro_sync.cpp: Runs `async_run` in a separate thread and
performs synchronous calls to `async_exec`.
While calling `async_run` is a sufficient condition for maintaining
active two-way communication with the Redis server, most production
deployments will want to do more. For example, they may want to
reconnect if the connection goes down, either to the same server or a
failover server. They may want to perform health checks and more. The
example below shows for example how to use a loop to keep reconnecting
to the same address when a disconnection occurs (see
cpp20_subscriber.cpp)
```cpp
auto run(std::shared_ptr<connection> conn) -> net::awaitable<void>
{
steady_timer timer{co_await net::this_coro::executor};
for (;;) {
co_await connect(conn, "127.0.0.1", "6379");
co_await (conn->async_run() || health_check(conn) || receiver(conn));
// Prepare the stream for a new connection.
conn->reset_stream();
// Waits one second before trying to reconnect.
timer.expires_after(std::chrono::seconds{1});
co_await timer.async_wait();
}
}
```
The ability to reconnect the same connection object results in
considerable simplification of backend code and makes it easier to
write failover-safe applications. For example, a Websocket server
might have a 10k sessions communicating with Redis at the time the
connection is lost (or maybe killed by the server admin to force a
failover). It would be concerning if each individual section were to
throw exceptions and handle error. With the pattern shown above the
only place that has to manage the error is the run function.
* `connection::async_run`: Resolve, connect, ssl-handshake,
resp3-handshake, health-checks reconnection and coordinate low-level
read and write operations..
### Server pushes
@@ -181,49 +108,35 @@ The connection class supports server pushes by means of the
to used it
```cpp
auto receiver(std::shared_ptr<connection> conn) -> net::awaitable<void>
auto
receiver(std::shared_ptr<connection> conn) -> net::awaitable<void>
{
for (generic_response resp;;) {
co_await conn->async_receive(resp);
// Use resp and clear the response for a new push.
resp.clear();
request req;
req.push("SUBSCRIBE", "channel");
while (!conn->is_cancelled()) {
// Reconnect to channels.
co_await conn->async_exec(req);
// Loop reading Redis pushs messages.
for (generic_response resp;;) {
error_code ec;
co_await conn->async_receive(resp, net::redirect_error(net::use_awaitable, ec));
if (ec)
break; // Connection lost, break so we can reconnect to channels.
std::cout
<< resp.value().at(1).value
<< " " << resp.value().at(2).value
<< " " << resp.value().at(3).value
<< std::endl;
resp.value().clear();
}
}
}
```
### Cancellation
Boost.Redis supports both implicit and explicit cancellation of connection
operations. Explicit cancellation is supported by means of the
`boost::redis::connection::cancel` member function. Implicit
terminal-cancellation, like those that happen when using Asio
awaitable `operator ||` will be discussed with more detail below.
```cpp
co_await (conn.async_run(...) || conn.async_exec(...))
```
* Useful for short-lived connections that are meant to be closed after
a command has been executed.
```cpp
co_await (conn.async_exec(...) || time.async_wait(...))
```
* Provides a way to limit how long the execution of a single request
should last.
* WARNING: If the timer fires after the request has been sent but before the
response has been received, the connection will be closed.
* It is usually a better idea to have a healthy checker than adding
per request timeout, see cpp20_subscriber.cpp for an example.
```cpp
co_await (conn.async_exec(...) || conn.async_exec(...) || ... || conn.async_exec(...))
```
* This works but is unnecessary, the connection will automatically
merge the individual requests into a single payload.
<a name="requests"></a>
## Requests
@@ -535,7 +448,6 @@ to serialize using json and [protobuf](https://protobuf.dev/).
The examples below show how to use the features discussed so far
* cpp20_intro_awaitable_ops.cpp: The version shown above.
* cpp20_intro.cpp: Does not use awaitable operators.
* cpp20_intro_tls.cpp: Communicates over TLS.
* cpp20_containers.cpp: Shows how to send and receive STL containers and how to use transactions.
@@ -793,7 +705,7 @@ https://lists.boost.org/Archives/boost/2023/01/253944.php.
* Renames the project to Boost.Redis and moves the code into namespace
`boost::redis`.
* As pointed out in the reviews the `to_buld` and `from_buld` names were too
* As pointed out in the reviews the `to_bulk` and `from_bulk` names were too
generic for ADL customization points. They gained the prefix `boost_redis_`.
* Moves `boost::redis::resp3::request` to `boost::redis::request`.
@@ -820,19 +732,16 @@ https://lists.boost.org/Archives/boost/2023/01/253944.php.
became unnecessary and was removed. I could measure significative performance
gains with theses changes.
* Adds native json support for Boost.Describe'd classes. To use it include
`<boost/redis/json.hpp>` and decribe you class as of Boost.Describe, see
cpp20_json_serialization.cpp for more details.
* Improves serialization examples using Boost.Describe to serialize to JSON and protobuf. See
cpp20_json.cpp and cpp20_protobuf.cpp for more details.
* Upgrades to Boost 1.81.0.
* Fixes build with libc++.
* Adds a function that performs health checks, see
`boost::redis::experimental::async_check_health`.
* Adds non-member `async_run` function that resolves, connects and
calls member `async_run` on a connection object.
* Adds high-level functionality to the connection classes. For
example, `boost::redis::connection::async_run` will automatically
resolve, connect, reconnect and perform health checks.
### v1.4.0-1

View File

@@ -4,57 +4,49 @@
* accompanying file LICENSE.txt)
*/
#include <boost/redis/connection.hpp>
#include <boost/asio/detached.hpp>
#include <iostream>
#include <boost/redis/run.hpp>
#include <boost/redis/address.hpp>
#include <boost/redis/src.hpp>
namespace net = boost::asio;
using boost::redis::connection;
using boost::redis::request;
using boost::redis::response;
using boost::redis::ignore_t;
using boost::redis::async_run;
using boost::redis::address;
using namespace std::chrono_literals;
using boost::redis::config;
auto main(int argc, char * argv[]) -> int
{
try {
address addr;
config cfg;
if (argc == 3) {
addr.host = argv[1];
addr.port = argv[2];
cfg.addr.host = argv[1];
cfg.addr.port = argv[2];
}
// The request
request req;
req.push("HELLO", 3);
req.push("PING", "Hello world");
// The response.
response<ignore_t, std::string> resp;
response<std::string> resp;
net::io_context ioc;
connection conn{ioc};
async_run(conn, addr, 10s, 10s, [&](auto){
conn.cancel();
});
conn.async_run(cfg, {}, net::detached);
conn.async_exec(req, resp, [&](auto ec, auto){
conn.async_exec(req, resp, [&](auto ec, auto) {
if (!ec)
std::cout << "PING: " << std::get<1>(resp).value() << std::endl;
std::cout << "PING: " << std::get<0>(resp).value() << std::endl;
conn.cancel();
});
ioc.run();
return 0;
} catch (std::exception const& e) {
std::cerr << "Error: " << e.what() << std::endl;
return 1;
}
return 1;
}

View File

@@ -4,82 +4,42 @@
* accompanying file LICENSE.txt)
*/
#include <boost/redis/operation.hpp>
#include <boost/redis/request.hpp>
#include <boost/redis/check_health.hpp>
#include <boost/redis/run.hpp>
#include <boost/asio/deferred.hpp>
#include <boost/asio/use_future.hpp>
#include <tuple>
#include "sync_connection.hpp"
#include <string>
#include <chrono>
#include <thread>
#include <iostream>
// Include this in no more than one .cpp file.
#include <boost/redis/src.hpp>
namespace net = boost::asio;
using connection = boost::redis::connection;
using boost::redis::operation;
using boost::redis::sync_connection;
using boost::redis::request;
using boost::redis::response;
using boost::redis::ignore_t;
using boost::redis::async_run;
using boost::redis::address;
using boost::redis::async_check_health;
using namespace std::chrono_literals;
template <class Response>
auto exec(std::shared_ptr<connection> conn, request const& req, Response& resp)
{
net::dispatch(
conn->get_executor(),
net::deferred([&]() { return conn->async_exec(req, resp, net::deferred); }))
(net::use_future).get();
}
using boost::redis::config;
auto main(int argc, char * argv[]) -> int
{
try {
address addr;
config cfg;
if (argc == 3) {
addr.host = argv[1];
addr.port = argv[2];
cfg.addr.host = argv[1];
cfg.addr.port = argv[2];
}
net::io_context ioc{1};
auto conn = std::make_shared<connection>(ioc);
// Starts a thread that will can io_context::run on which the
// connection will run.
std::thread t{[&ioc, conn, addr]() {
async_run(*conn, addr, 10s, 10s, [conn](auto){
conn->cancel();
});
async_check_health(*conn, "Boost.Redis", 2s, [conn](auto) {
conn->cancel();
});
ioc.run();
}};
sync_connection conn;
conn.run(cfg);
request req;
req.push("HELLO", 3);
req.push("PING");
req.push("QUIT");
response<ignore_t, std::string, ignore_t> resp;
response<std::string> resp;
// Executes commands synchronously.
exec(conn, req, resp);
conn.exec(req, resp);
conn.stop();
std::cout << "Response: " << std::get<1>(resp).value() << std::endl;
std::cout << "Response: " << std::get<0>(resp).value() << std::endl;
t.join();
} catch (std::exception const& e) {
std::cerr << e.what() << std::endl;
}

View File

@@ -4,39 +4,58 @@
* accompanying file LICENSE.txt)
*/
#include <boost/redis/run.hpp>
#include <boost/redis/check_health.hpp>
#include <boost/asio/use_awaitable.hpp>
#include <boost/redis/connection.hpp>
#include <boost/asio/deferred.hpp>
#include <boost/asio/signal_set.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/redirect_error.hpp>
#include <boost/asio/posix/stream_descriptor.hpp>
#include <unistd.h>
#include <iostream>
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
#include <boost/asio/experimental/awaitable_operators.hpp>
#if defined(BOOST_ASIO_HAS_POSIX_STREAM_DESCRIPTOR)
namespace net = boost::asio;
using namespace net::experimental::awaitable_operators;
using stream_descriptor = net::use_awaitable_t<>::as_default_on_t<net::posix::stream_descriptor>;
using signal_set = net::use_awaitable_t<>::as_default_on_t<net::signal_set>;
using stream_descriptor = net::deferred_t::as_default_on_t<net::posix::stream_descriptor>;
using connection = net::deferred_t::as_default_on_t<boost::redis::connection>;
using signal_set = net::deferred_t::as_default_on_t<net::signal_set>;
using boost::redis::request;
using boost::redis::generic_response;
using boost::redis::async_check_health;
using boost::redis::async_run;
using boost::redis::address;
using connection = net::use_awaitable_t<>::as_default_on_t<boost::redis::connection>;
using boost::redis::config;
using net::redirect_error;
using net::use_awaitable;
using boost::system::error_code;
using namespace std::chrono_literals;
// Chat over Redis pubsub. To test, run this program from multiple
// terminals and type messages to stdin.
// Receives Redis pushes.
auto receiver(std::shared_ptr<connection> conn) -> net::awaitable<void>
auto
receiver(std::shared_ptr<connection> conn) -> net::awaitable<void>
{
for (generic_response resp;;) {
co_await conn->async_receive(resp);
std::cout << resp.value().at(1).value << " " << resp.value().at(2).value << " " << resp.value().at(3).value << std::endl;
resp.value().clear();
request req;
req.push("SUBSCRIBE", "channel");
while (!conn->is_cancelled()) {
// Subscribe to channels.
co_await conn->async_exec(req);
// Loop reading Redis push messages.
for (generic_response resp;;) {
error_code ec;
co_await conn->async_receive(resp, redirect_error(use_awaitable, ec));
if (ec)
break; // Connection lost, break so we can reconnect to channels.
std::cout
<< resp.value().at(1).value
<< " " << resp.value().at(2).value
<< " " << resp.value().at(3).value
<< std::endl;
resp.value().clear();
}
}
}
@@ -46,31 +65,31 @@ auto publisher(std::shared_ptr<stream_descriptor> in, std::shared_ptr<connection
for (std::string msg;;) {
auto n = co_await net::async_read_until(*in, net::dynamic_buffer(msg, 1024), "\n");
request req;
req.push("PUBLISH", "chat-channel", msg);
req.push("PUBLISH", "channel", msg);
co_await conn->async_exec(req);
msg.erase(0, n);
}
}
// Called from the main function (see main.cpp)
auto co_main(address const& addr) -> net::awaitable<void>
auto co_main(config const& cfg) -> net::awaitable<void>
{
auto ex = co_await net::this_coro::executor;
auto conn = std::make_shared<connection>(ex);
auto stream = std::make_shared<stream_descriptor>(ex, ::dup(STDIN_FILENO));
signal_set sig{ex, SIGINT, SIGTERM};
request req;
req.push("HELLO", 3);
req.push("SUBSCRIBE", "chat-channel");
net::co_spawn(ex, receiver(conn), net::detached);
net::co_spawn(ex, publisher(stream, conn), net::detached);
conn->async_run(cfg, {}, net::consign(net::detached, conn));
co_await ((async_run(*conn, addr) || publisher(stream, conn) || receiver(conn) ||
async_check_health(*conn) || sig.async_wait()) &&
conn->async_exec(req));
signal_set sig_set{ex, SIGINT, SIGTERM};
co_await sig_set.async_wait();
conn->cancel();
stream->cancel();
}
#else // defined(BOOST_ASIO_HAS_POSIX_STREAM_DESCRIPTOR)
auto co_main(address const&) -> net::awaitable<void>
auto co_main(config const&) -> net::awaitable<void>
{
std::cout << "Requires support for posix streams." << std::endl;
co_return;

View File

@@ -4,8 +4,8 @@
* accompanying file LICENSE.txt)
*/
#include <boost/redis/run.hpp>
#include <boost/asio/use_awaitable.hpp>
#include <boost/redis/connection.hpp>
#include <boost/asio/deferred.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/co_spawn.hpp>
#include <map>
@@ -16,12 +16,12 @@
namespace net = boost::asio;
namespace redis = boost::redis;
using boost::redis::request;
using boost::redis::response;
using boost::redis::ignore_t;
using boost::redis::async_run;
using boost::redis::address;
using connection = boost::asio::use_awaitable_t<>::as_default_on_t<boost::redis::connection>;
using redis::request;
using redis::response;
using redis::ignore_t;
using redis::ignore;
using redis::config;
using connection = net::deferred_t::as_default_on_t<boost::redis::connection>;
void print(std::map<std::string, std::string> const& cont)
{
@@ -35,11 +35,6 @@ void print(std::vector<int> const& cont)
std::cout << "\n";
}
auto run(std::shared_ptr<connection> conn, address const& addr) -> net::awaitable<void>
{
co_await async_run(*conn, addr);
}
// Stores the content of some STL containers in Redis.
auto store(std::shared_ptr<connection> conn) -> net::awaitable<void>
{
@@ -50,7 +45,6 @@ auto store(std::shared_ptr<connection> conn) -> net::awaitable<void>
{{"key1", "value1"}, {"key2", "value2"}, {"key3", "value3"}};
request req;
req.push("HELLO", 3);
req.push_range("RPUSH", "rpush-key", vec);
req.push_range("HSET", "hset-key", map);
@@ -61,30 +55,27 @@ auto hgetall(std::shared_ptr<connection> conn) -> net::awaitable<void>
{
// A request contains multiple commands.
request req;
req.push("HELLO", 3);
req.push("HGETALL", "hset-key");
// Responses as tuple elements.
response<ignore_t, std::map<std::string, std::string>> resp;
response<std::map<std::string, std::string>> resp;
// Executes the request and reads the response.
co_await conn->async_exec(req, resp);
print(std::get<1>(resp).value());
print(std::get<0>(resp).value());
}
// Retrieves in a transaction.
auto transaction(std::shared_ptr<connection> conn) -> net::awaitable<void>
{
request req;
req.push("HELLO", 3);
req.push("MULTI");
req.push("LRANGE", "rpush-key", 0, -1); // Retrieves
req.push("HGETALL", "hset-key"); // Retrieves
req.push("EXEC");
response<
ignore_t, // hello
ignore_t, // multi
ignore_t, // lrange
ignore_t, // hgetall
@@ -93,28 +84,20 @@ auto transaction(std::shared_ptr<connection> conn) -> net::awaitable<void>
co_await conn->async_exec(req, resp);
print(std::get<0>(std::get<4>(resp).value()).value().value());
print(std::get<1>(std::get<4>(resp).value()).value().value());
}
auto quit(std::shared_ptr<connection> conn) -> net::awaitable<void>
{
request req;
req.push("QUIT");
co_await conn->async_exec(req);
print(std::get<0>(std::get<3>(resp).value()).value().value());
print(std::get<1>(std::get<3>(resp).value()).value().value());
}
// Called from the main function (see main.cpp)
net::awaitable<void> co_main(address const& addr)
net::awaitable<void> co_main(config const& cfg)
{
auto ex = co_await net::this_coro::executor;
auto conn = std::make_shared<connection>(ex);
net::co_spawn(ex, run(conn, addr), net::detached);
auto conn = std::make_shared<connection>(co_await net::this_coro::executor);
conn->async_run(cfg, {}, net::consign(net::detached, conn));
co_await store(conn);
co_await transaction(conn);
co_await hgetall(conn);
co_await quit(conn);
conn->cancel();
}
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)

View File

@@ -4,26 +4,26 @@
* accompanying file LICENSE.txt)
*/
#include <boost/redis/run.hpp>
#include <boost/redis/check_health.hpp>
#include <boost/asio/use_awaitable.hpp>
#include <boost/redis/connection.hpp>
#include <boost/asio/deferred.hpp>
#include <boost/asio/signal_set.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/redirect_error.hpp>
#include <boost/asio/co_spawn.hpp>
#include <iostream>
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
#include <boost/asio/experimental/awaitable_operators.hpp>
namespace net = boost::asio;
using namespace net::experimental::awaitable_operators;
using tcp_socket = net::use_awaitable_t<>::as_default_on_t<net::ip::tcp::socket>;
using tcp_acceptor = net::use_awaitable_t<>::as_default_on_t<net::ip::tcp::acceptor>;
using signal_set = net::use_awaitable_t<>::as_default_on_t<net::signal_set>;
using tcp_socket = net::deferred_t::as_default_on_t<net::ip::tcp::socket>;
using tcp_acceptor = net::deferred_t::as_default_on_t<net::ip::tcp::acceptor>;
using signal_set = net::deferred_t::as_default_on_t<net::signal_set>;
using connection = net::deferred_t::as_default_on_t<boost::redis::connection>;
using boost::redis::request;
using boost::redis::response;
using boost::redis::async_check_health;
using boost::redis::async_run;
using boost::redis::address;
using connection = net::use_awaitable_t<>::as_default_on_t<boost::redis::connection>;
using boost::redis::config;
using boost::system::error_code;
using namespace std::chrono_literals;
auto echo_server_session(tcp_socket socket, std::shared_ptr<connection> conn) -> net::awaitable<void>
{
@@ -44,24 +44,27 @@ auto echo_server_session(tcp_socket socket, std::shared_ptr<connection> conn) ->
// Listens for tcp connections.
auto listener(std::shared_ptr<connection> conn) -> net::awaitable<void>
{
auto ex = co_await net::this_coro::executor;
tcp_acceptor acc(ex, {net::ip::tcp::v4(), 55555});
for (;;)
net::co_spawn(ex, echo_server_session(co_await acc.async_accept(), conn), net::detached);
try {
auto ex = co_await net::this_coro::executor;
tcp_acceptor acc(ex, {net::ip::tcp::v4(), 55555});
for (;;)
net::co_spawn(ex, echo_server_session(co_await acc.async_accept(), conn), net::detached);
} catch (std::exception const& e) {
std::clog << "Listener: " << e.what() << std::endl;
}
}
// Called from the main function (see main.cpp)
auto co_main(address const& addr) -> net::awaitable<void>
auto co_main(config const& cfg) -> net::awaitable<void>
{
auto ex = co_await net::this_coro::executor;
auto conn = std::make_shared<connection>(ex);
signal_set sig{ex, SIGINT, SIGTERM};
net::co_spawn(ex, listener(conn), net::detached);
conn->async_run(cfg, {}, net::consign(net::detached, conn));
request req;
req.push("HELLO", 3);
co_await ((async_run(*conn, addr) || listener(conn) || async_check_health(*conn) ||
sig.async_wait()) && conn->async_exec(req));
signal_set sig_set(ex, SIGINT, SIGTERM);
co_await sig_set.async_wait();
conn->cancel();
}
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)

View File

@@ -4,10 +4,11 @@
* accompanying file LICENSE.txt)
*/
#include <boost/redis/run.hpp>
#include <boost/asio/use_awaitable.hpp>
#include <boost/redis/connection.hpp>
#include <boost/asio/deferred.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/consign.hpp>
#include <iostream>
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
@@ -15,41 +16,28 @@
namespace net = boost::asio;
using boost::redis::request;
using boost::redis::response;
using boost::redis::ignore_t;
using boost::redis::async_run;
using boost::redis::address;
using connection = net::use_awaitable_t<>::as_default_on_t<boost::redis::connection>;
auto run(std::shared_ptr<connection> conn, address addr) -> net::awaitable<void>
{
// async_run coordinate read and write operations.
co_await async_run(*conn, addr);
// Cancel pending operations, if any.
conn->cancel();
}
using boost::redis::config;
using boost::redis::logger;
using connection = net::deferred_t::as_default_on_t<boost::redis::connection>;
// Called from the main function (see main.cpp)
auto co_main(address const& addr) -> net::awaitable<void>
auto co_main(config const& cfg) -> net::awaitable<void>
{
auto ex = co_await net::this_coro::executor;
auto conn = std::make_shared<connection>(ex);
net::co_spawn(ex, run(conn, addr), net::detached);
auto conn = std::make_shared<connection>(co_await net::this_coro::executor);
conn->async_run(cfg, {}, net::consign(net::detached, conn));
// A request can contain multiple commands.
// A request containing only a ping command.
request req;
req.push("HELLO", 3);
req.push("PING", "Hello world");
req.push("QUIT");
// Stores responses of each individual command. The responses to
// HELLO and QUIT are being ignored for simplicity.
response<ignore_t, std::string, ignore_t> resp;
// Response where the PONG response will be stored.
response<std::string> resp;
// Executtes the request.
// Executes the request.
co_await conn->async_exec(req, resp);
conn->cancel();
std::cout << "PING: " << std::get<1>(resp).value() << std::endl;
std::cout << "PING: " << std::get<0>(resp).value() << std::endl;
}
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)

View File

@@ -1,43 +0,0 @@
/* 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 <boost/redis/run.hpp>
#include <boost/asio/use_awaitable.hpp>
#include <iostream>
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
#include <boost/asio/experimental/awaitable_operators.hpp>
namespace net = boost::asio;
using namespace net::experimental::awaitable_operators;
using boost::redis::request;
using boost::redis::response;
using boost::redis::ignore_t;
using boost::redis::async_run;
using boost::redis::address;
using connection = boost::asio::use_awaitable_t<>::as_default_on_t<boost::redis::connection>;
// Called from the main function (see main.cpp)
auto co_main(address const& addr) -> net::awaitable<void>
{
try {
request req;
req.push("HELLO", 3);
req.push("PING", "Hello world");
req.push("QUIT");
response<ignore_t, std::string, ignore_t> resp;
connection conn{co_await net::this_coro::executor};
co_await (async_run(conn, addr) || conn.async_exec(req, resp));
std::cout << "PING: " << std::get<1>(resp).value() << std::endl;
} catch (std::exception const& e) {
std::cout << e.what() << std::endl;
}
}
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)

View File

@@ -5,27 +5,20 @@
*/
#include <boost/redis/ssl/connection.hpp>
#include <boost/redis/ssl/connection.hpp>
#include <boost/redis/address.hpp>
#include <boost/asio/deferred.hpp>
#include <boost/asio/use_awaitable.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/asio/connect.hpp>
#include <tuple>
#include <string>
#include <boost/asio/detached.hpp>
#include <boost/asio/consign.hpp>
#include <iostream>
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
#include <boost/asio/experimental/awaitable_operators.hpp>
namespace net = boost::asio;
namespace redis = boost::redis;
using namespace net::experimental::awaitable_operators;
using resolver = net::use_awaitable_t<>::as_default_on_t<net::ip::tcp::resolver>;
using connection = net::use_awaitable_t<>::as_default_on_t<redis::ssl::connection>;
using boost::redis::request;
using boost::redis::response;
using boost::redis::ignore_t;
using boost::redis::address;
using boost::redis::config;
using boost::redis::logger;
using connection = net::deferred_t::as_default_on_t<boost::redis::ssl::connection>;
auto verify_certificate(bool, net::ssl::verify_context&) -> bool
{
@@ -33,30 +26,30 @@ auto verify_certificate(bool, net::ssl::verify_context&) -> bool
return true;
}
net::awaitable<void> co_main(address const&)
auto co_main(config const&) -> net::awaitable<void>
{
request req;
req.push("HELLO", 3, "AUTH", "aedis", "aedis");
req.push("PING");
req.push("QUIT");
response<ignore_t, std::string, ignore_t> resp;
// Resolve
auto ex = co_await net::this_coro::executor;
resolver resv{ex};
auto const endpoints = co_await resv.async_resolve("db.occase.de", "6380");
config cfg;
cfg.username = "aedis";
cfg.password = "aedis";
cfg.addr.host = "db.occase.de";
cfg.addr.port = "6380";
net::ssl::context ctx{net::ssl::context::sslv23};
connection conn{ex, ctx};
conn.next_layer().set_verify_mode(net::ssl::verify_peer);
conn.next_layer().set_verify_callback(verify_certificate);
auto conn = std::make_shared<connection>(co_await net::this_coro::executor, ctx);
conn->async_run(cfg, {}, net::consign(net::detached, conn));
co_await net::async_connect(conn.lowest_layer(), endpoints);
co_await conn.next_layer().async_handshake(net::ssl::stream_base::client);
co_await (conn.async_run() || conn.async_exec(req, resp));
request req;
req.push("PING");
std::cout << "Response: " << std::get<1>(resp).value() << std::endl;
response<std::string> resp;
conn->next_layer().set_verify_mode(net::ssl::verify_peer);
conn->next_layer().set_verify_callback(verify_certificate);
co_await conn->async_exec(req, resp);
conn->cancel();
std::cout << "Response: " << std::get<0>(resp).value() << std::endl;
}
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)

View File

@@ -4,31 +4,29 @@
* accompanying file LICENSE.txt)
*/
#include <boost/redis/run.hpp>
#include <boost/asio/use_awaitable.hpp>
#include <boost/redis/connection.hpp>
#include <boost/asio/deferred.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/describe.hpp>
#include <boost/asio/consign.hpp>
#include <string>
#include <iostream>
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
// Include this in no more than one .cpp file.
#define BOOST_JSON_NO_LIB
#define BOOST_CONTAINER_NO_LIB
#include "json.hpp"
#include <boost/json/src.hpp>
namespace net = boost::asio;
namespace redis = boost::redis;
using namespace boost::describe;
using connection = net::deferred_t::as_default_on_t<boost::redis::connection>;
using boost::redis::request;
using boost::redis::response;
using boost::redis::operation;
using boost::redis::ignore_t;
using boost::redis::async_run;
using boost::redis::address;
using connection = boost::asio::use_awaitable_t<>::as_default_on_t<boost::redis::connection>;
using boost::redis::config;
// Struct that will be stored in Redis using json serialization.
struct user {
@@ -47,37 +45,30 @@ void boost_redis_to_bulk(std::string& to, user const& u)
void boost_redis_from_bulk(user& u, std::string_view sv, boost::system::error_code& ec)
{ boost::redis::json::from_bulk(u, sv, ec); }
auto run(std::shared_ptr<connection> conn, address addr) -> net::awaitable<void>
{
co_await async_run(*conn, addr);
}
net::awaitable<void> co_main(address const& addr)
net::awaitable<void> co_main(config const& cfg)
{
auto ex = co_await net::this_coro::executor;
auto conn = std::make_shared<connection>(ex);
net::co_spawn(ex, run(conn, addr), net::detached);
conn->async_run(cfg, {}, net::consign(net::detached, conn));
// user object that will be stored in Redis in json format.
user const u{"Joao", "58", "Brazil"};
// Stores and retrieves in the same request.
request req;
req.push("HELLO", 3);
req.push("SET", "json-key", u); // Stores in Redis.
req.push("GET", "json-key"); // Retrieves from Redis.
response<ignore_t, ignore_t, user> resp;
response<ignore_t, user> resp;
co_await conn->async_exec(req, resp);
conn->cancel();
// Prints the first ping
std::cout
<< "Name: " << std::get<2>(resp).value().name << "\n"
<< "Age: " << std::get<2>(resp).value().age << "\n"
<< "Country: " << std::get<2>(resp).value().country << "\n";
conn->cancel(operation::run);
<< "Name: " << std::get<1>(resp).value().name << "\n"
<< "Age: " << std::get<1>(resp).value().age << "\n"
<< "Country: " << std::get<1>(resp).value().country << "\n";
}
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)

View File

@@ -4,10 +4,11 @@
* accompanying file LICENSE.txt)
*/
#include <boost/redis/run.hpp>
#include <boost/asio/use_awaitable.hpp>
#include <boost/redis/connection.hpp>
#include <boost/asio/deferred.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/consign.hpp>
#include <iostream>
#include "protobuf.hpp"
@@ -18,14 +19,12 @@
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
namespace net = boost::asio;
namespace redis = boost::redis;
using boost::redis::request;
using boost::redis::response;
using boost::redis::operation;
using boost::redis::ignore_t;
using boost::redis::async_run;
using boost::redis::address;
using connection = net::use_awaitable_t<>::as_default_on_t<boost::redis::connection>;
using boost::redis::config;
using connection = net::deferred_t::as_default_on_t<boost::redis::connection>;
// The protobuf type described in examples/person.proto
using tutorial::person;
@@ -45,16 +44,11 @@ void boost_redis_from_bulk(person& u, std::string_view sv, boost::system::error_
using tutorial::boost_redis_to_bulk;
using tutorial::boost_redis_from_bulk;
auto run(std::shared_ptr<connection> conn, address const& addr) -> net::awaitable<void>
{
co_await async_run(*conn, addr);
}
net::awaitable<void> co_main(address const& addr)
net::awaitable<void> co_main(config const& cfg)
{
auto ex = co_await net::this_coro::executor;
auto conn = std::make_shared<connection>(ex);
net::co_spawn(ex, run(conn, addr), net::detached);
conn->async_run(cfg, {}, net::consign(net::detached, conn));
person p;
p.set_name("Louis");
@@ -62,21 +56,19 @@ net::awaitable<void> co_main(address const& addr)
p.set_email("No email yet.");
request req;
req.push("HELLO", 3);
req.push("SET", "protobuf-key", p);
req.push("GET", "protobuf-key");
response<ignore_t, ignore_t, person> resp;
response<ignore_t, person> resp;
// Sends the request and receives the response.
co_await conn->async_exec(req, resp);
conn->cancel();
std::cout
<< "Name: " << std::get<2>(resp).value().name() << "\n"
<< "Age: " << std::get<2>(resp).value().id() << "\n"
<< "Email: " << std::get<2>(resp).value().email() << "\n";
conn->cancel(operation::run);
<< "Name: " << std::get<1>(resp).value().name() << "\n"
<< "Age: " << std::get<1>(resp).value().id() << "\n"
<< "Email: " << std::get<1>(resp).value().email() << "\n";
}
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)

View File

@@ -4,22 +4,22 @@
* accompanying file LICENSE.txt)
*/
#include <boost/redis/run.hpp>
#include <boost/redis/connection.hpp>
#include <boost/asio/use_awaitable.hpp>
#include <boost/asio/redirect_error.hpp>
#include <boost/asio/detached.hpp>
#include <iostream>
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
#include <boost/asio/experimental/awaitable_operators.hpp>
namespace net = boost::asio;
using namespace net::experimental::awaitable_operators;
namespace redis = boost::redis;
using endpoints = net::ip::tcp::resolver::results_type;
using boost::redis::request;
using boost::redis::response;
using boost::redis::ignore_t;
using boost::redis::async_run;
using boost::redis::address;
using redis::request;
using redis::response;
using redis::ignore_t;
using redis::config;
using redis::address;
using connection = boost::asio::use_awaitable_t<>::as_default_on_t<boost::redis::connection>;
auto redir(boost::system::error_code& ec)
@@ -39,23 +39,30 @@ auto resolve_master_address(std::vector<address> const& addresses) -> net::await
response<std::optional<std::array<std::string, 2>>, ignore_t> resp;
for (auto addr : addresses) {
boost::system::error_code ec;
co_await (async_run(*conn, addr) && conn->async_exec(req, resp, redir(ec)));
config cfg;
cfg.addr = addr;
// TODO: async_run and async_exec should be lauched in
// parallel here so we can wait for async_run completion
// before eventually calling it again.
conn->async_run(cfg, {}, net::consign(net::detached, conn));
co_await conn->async_exec(req, resp, redir(ec));
conn->cancel();
conn->reset_stream();
if (std::get<0>(resp))
if (!ec && std::get<0>(resp))
co_return address{std::get<0>(resp).value().value().at(0), std::get<0>(resp).value().value().at(1)};
}
co_return address{};
}
auto co_main(address const& addr) -> net::awaitable<void>
auto co_main(config const& cfg) -> net::awaitable<void>
{
// A list of sentinel addresses from which only one is responsive.
// This simulates sentinels that are down.
std::vector<address> const addresses
{ address{"foo", "26379"}
, address{"bar", "26379"}
, addr
, cfg.addr
};
auto const ep = co_await resolve_master_address(addresses);

View File

@@ -4,24 +4,29 @@
* accompanying file LICENSE.txt)
*/
#include <boost/redis/run.hpp>
#include <boost/redis/check_health.hpp>
#include <boost/redis/connection.hpp>
#include <boost/redis/logger.hpp>
#include <boost/asio/awaitable.hpp>
#include <boost/asio/use_awaitable.hpp>
#include <boost/asio/deferred.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/consign.hpp>
#include <boost/asio/redirect_error.hpp>
#include <boost/asio/signal_set.hpp>
#include <iostream>
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
#include <boost/asio/experimental/awaitable_operators.hpp>
namespace net = boost::asio;
using namespace net::experimental::awaitable_operators;
using steady_timer = net::use_awaitable_t<>::as_default_on_t<net::steady_timer>;
using boost::redis::request;
using boost::redis::async_run;
using boost::redis::generic_response;
using boost::redis::async_check_health;
using boost::redis::address;
using connection = net::use_awaitable_t<>::as_default_on_t<boost::redis::connection>;
using boost::redis::logger;
using boost::redis::config;
using boost::system::error_code;
using connection = net::deferred_t::as_default_on_t<boost::redis::connection>;
using signal_set = net::deferred_t::as_default_on_t<net::signal_set>;
using namespace std::chrono_literals;
/* This example will subscribe and read pushes indefinitely.
*
@@ -39,33 +44,45 @@ using connection = net::use_awaitable_t<>::as_default_on_t<boost::redis::connect
* > CLIENT kill TYPE pubsub
*/
// Receives pushes.
auto receiver(std::shared_ptr<connection> conn) -> net::awaitable<void>
// Receives server pushes.
auto
receiver(std::shared_ptr<connection> conn) -> net::awaitable<void>
{
for (generic_response resp;;) {
co_await conn->async_receive(resp);
std::cout << resp.value().at(1).value << " " << resp.value().at(2).value << " " << resp.value().at(3).value << std::endl;
resp.value().clear();
request req;
req.push("SUBSCRIBE", "channel");
while (!conn->is_cancelled()) {
// Reconnect to channels.
co_await conn->async_exec(req);
// Loop reading Redis pushs messages.
for (generic_response resp;;) {
error_code ec;
co_await conn->async_receive(resp, net::redirect_error(net::use_awaitable, ec));
if (ec)
break; // Connection lost, break so we can reconnect to channels.
std::cout
<< resp.value().at(1).value
<< " " << resp.value().at(2).value
<< " " << resp.value().at(3).value
<< std::endl;
resp.value().clear();
}
}
}
auto co_main(address const& addr) -> net::awaitable<void>
auto co_main(config const& cfg) -> net::awaitable<void>
{
auto ex = co_await net::this_coro::executor;
auto conn = std::make_shared<connection>(ex);
steady_timer timer{ex};
net::co_spawn(ex, receiver(conn), net::detached);
conn->async_run(cfg, {}, net::consign(net::detached, conn));
request req;
req.push("HELLO", 3);
req.push("SUBSCRIBE", "channel");
signal_set sig_set(ex, SIGINT, SIGTERM);
co_await sig_set.async_wait();
// The loop will reconnect on connection lost. To exit type Ctrl-C twice.
for (;;) {
co_await ((async_run(*conn, addr) || async_check_health(*conn) || receiver(conn)) && conn->async_exec(req));
conn->reset_stream();
timer.expires_after(std::chrono::seconds{1});
co_await timer.async_wait();
}
conn->cancel();
}
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)

View File

@@ -5,25 +5,27 @@
*/
#include "start.hpp"
#include <boost/redis/address.hpp>
#include <boost/redis/config.hpp>
#include <boost/asio/awaitable.hpp>
#include <string>
#include <iostream>
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
extern boost::asio::awaitable<void> co_main(boost::redis::address const&);
using boost::redis::config;
extern boost::asio::awaitable<void> co_main(config const&);
auto main(int argc, char * argv[]) -> int
{
boost::redis::address addr;
config cfg;
if (argc == 3) {
addr.host = argv[1];
addr.port = argv[2];
cfg.addr.host = argv[1];
cfg.addr.port = argv[2];
}
return start(co_main(addr));
return start(co_main(cfg));
}
#else // defined(BOOST_ASIO_HAS_CO_AWAIT)

View File

@@ -27,7 +27,7 @@ auto start(net::awaitable<void> op) -> int
return 0;
} catch (std::exception const& e) {
std::cerr << "Error: " << e.what() << std::endl;
std::cerr << "start> " << e.what() << std::endl;
}
return 1;

View File

@@ -0,0 +1,63 @@
/* 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 <boost/redis/connection.hpp>
#include <boost/redis/request.hpp>
#include <boost/asio/deferred.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/use_future.hpp>
#include <thread>
#include <chrono>
using namespace std::chrono_literals;
namespace boost::redis
{
class sync_connection {
public:
sync_connection()
: ioc_{1}
, conn_{std::make_shared<connection>(ioc_)}
{ }
~sync_connection()
{
thread_.join();
}
void run(config cfg)
{
// Starts a thread that will can io_context::run on which the
// connection will run.
thread_ = std::thread{[this, cfg]() {
conn_->async_run(cfg, {}, asio::detached);
ioc_.run();
}};
}
void stop()
{
asio::dispatch(ioc_, [this]() { conn_->cancel(); });
}
template <class Response>
auto exec(request const& req, Response& resp)
{
asio::dispatch(
conn_->get_executor(),
asio::deferred([this, &req, &resp]() { return conn_->async_exec(req, resp, asio::deferred); }))
(asio::use_future).get();
}
private:
asio::io_context ioc_{1};
std::shared_ptr<connection> conn_;
std::thread thread_;
};
}

View File

@@ -7,12 +7,15 @@
#ifndef BOOST_REDIS_HPP
#define BOOST_REDIS_HPP
#include <boost/redis/config.hpp>
#include <boost/redis/error.hpp>
#include <boost/redis/connection.hpp>
#include <boost/redis/request.hpp>
#include <boost/redis/response.hpp>
#include <boost/redis/run.hpp>
#include <boost/redis/address.hpp>
#include <boost/redis/ignore.hpp>
#include <boost/redis/logger.hpp>
/** @defgroup high-level-api Reference
*

View File

@@ -12,7 +12,6 @@
#include <boost/redis/resp3/serialization.hpp>
#include <boost/redis/resp3/node.hpp>
#include <boost/redis/adapter/result.hpp>
#include <boost/redis/adapter/ignore.hpp>
#include <boost/assert.hpp>
#include <set>

View File

@@ -12,6 +12,7 @@
#include <boost/redis/ignore.hpp>
#include <boost/redis/adapter/detail/adapters.hpp>
#include <boost/redis/adapter/result.hpp>
#include <boost/redis/adapter/ignore.hpp>
#include <boost/mp11.hpp>
#include <vector>

View File

@@ -1,27 +0,0 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#ifndef BOOST_REDIS_ADDRESS_HPP
#define BOOST_REDIS_ADDRESS_HPP
#include <string>
namespace boost::redis
{
/** @brief Address of a Redis server
* @ingroup high-level-api
*/
struct address {
/// Redis host.
std::string host = "127.0.0.1";
/// Redis port.
std::string port = "6379";
};
} // boost::redis
#endif // BOOST_REDIS_ADDRESS_HPP

View File

@@ -1,144 +0,0 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#ifndef BOOST_REDIS_CHECK_HEALTH_HPP
#define BOOST_REDIS_CHECK_HEALTH_HPP
// Has to included before promise.hpp to build on msvc.
#include <boost/redis/request.hpp>
#include <boost/redis/response.hpp>
#include <boost/redis/operation.hpp>
#include <boost/redis/detail/read_ops.hpp>
#include <boost/asio/experimental/promise.hpp>
#include <boost/asio/experimental/use_promise.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/asio/compose.hpp>
#include <boost/asio/consign.hpp>
#include <memory>
#include <chrono>
#include <optional>
namespace boost::redis {
namespace detail {
template <class HealthChecker, class Connection>
class check_health_op {
public:
HealthChecker* checker = nullptr;
Connection* conn = nullptr;
asio::coroutine coro_{};
template <class Self>
void operator()(Self& self, system::error_code ec = {}, std::size_t = 0)
{
BOOST_ASIO_CORO_REENTER (coro_) for (;;)
{
checker->prom_.emplace(conn->async_exec(checker->req_, checker->resp_, asio::experimental::use_promise));
checker->timer_.expires_after(checker->interval_);
BOOST_ASIO_CORO_YIELD
checker->timer_.async_wait(std::move(self));
if (ec || is_cancelled(self) || checker->resp_.value().empty()) {
conn->cancel(operation::run);
BOOST_ASIO_CORO_YIELD
std::move(*checker->prom_)(std::move(self));
self.complete({});
return;
}
checker->reset();
}
}
};
template <class Executor>
class health_checker {
private:
using promise_type = asio::experimental::promise<void(system::error_code, std::size_t), Executor>;
using timer_type =
asio::basic_waitable_timer<
std::chrono::steady_clock,
asio::wait_traits<std::chrono::steady_clock>,
Executor>;
public:
health_checker(
Executor ex,
std::string const& msg,
std::chrono::steady_clock::duration interval)
: timer_{ex}
, interval_{interval}
{
req_.push("PING", msg);
}
template <
class Connection,
class CompletionToken = asio::default_completion_token_t<Executor>
>
auto async_check_health(Connection& conn, CompletionToken token = CompletionToken{})
{
return asio::async_compose
< CompletionToken
, void(system::error_code)
>(check_health_op<health_checker, Connection>{this, &conn}, token, conn);
}
void reset()
{
resp_.value().clear();
prom_.reset();
}
private:
template <class, class> friend class check_health_op;
timer_type timer_;
std::optional<promise_type> prom_;
redis::request req_;
redis::generic_response resp_;
std::chrono::steady_clock::duration interval_;
};
} // detail
/** @brief Checks Redis health asynchronously
* @ingroup high-level-api
*
* This function will ping the Redis server periodically until a ping
* timesout or an error occurs. On timeout this function will
* complete with success.
*
* @param conn A connection to the Redis server.
* @param msg The message to be sent with the [PING](https://redis.io/commands/ping/) command. Seting a proper and unique id will help users identify which connections are active.
* @param interval Ping interval.
* @param token The completion token
*
* The completion token must have the following signature
*
* @code
* void f(system::error_code);
* @endcode
*/
template <
class Connection,
class CompletionToken = asio::default_completion_token_t<typename Connection::executor_type>
>
auto
async_check_health(
Connection& conn,
std::string const& msg = "Boost.Redis",
std::chrono::steady_clock::duration interval = std::chrono::seconds{2},
CompletionToken token = CompletionToken{})
{
using executor_type = typename Connection::executor_type;
using health_checker_type = detail::health_checker<executor_type>;
auto checker = std::make_shared<health_checker_type>(conn.get_executor(), msg, interval);
return checker->async_check_health(conn, asio::consign(std::move(token), checker));
}
} // boost::redis
#endif // BOOST_REDIS_CHECK_HEALTH_HPP

View File

@@ -0,0 +1,72 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#ifndef BOOST_REDIS_CONFIG_HPP
#define BOOST_REDIS_CONFIG_HPP
#include <string>
#include <chrono>
namespace boost::redis
{
/** @brief Address of a Redis server
* @ingroup high-level-api
*/
struct address {
/// Redis host.
std::string host = "127.0.0.1";
/// Redis port.
std::string port = "6379";
};
/** @brief Configure parameters used by the connection classes
* @ingroup high-level-api
*/
struct config {
/// Address of the Redis server.
address addr = address{"127.0.0.1", "6379"};
/** @brief Username passed to the
* [HELLO](https://redis.io/commands/hello/) command. If left
* empty `HELLO` will be sent without authentication parameters.
*/
std::string username;
/** @brief Password passed to the
* [HELLO](https://redis.io/commands/hello/) command. If left
* empty `HELLO` will be sent without authentication parameters.
*/
std::string password;
/// Client name parameter of the [HELLO](https://redis.io/commands/hello/) command.
std::string clientname = "Boost.Redis";
/// Message used by the health-checker in `boost::redis::connection::async_run`.
std::string health_check_id = "Boost.Redis";
/// Logger prefix, see `boost::redis::logger`.
std::string log_prefix = "(Boost.Redis) ";
/// Time the resolve operation is allowed to last.
std::chrono::steady_clock::duration resolve_timeout = std::chrono::seconds{10};
/// Time the connect operation is allowed to last.
std::chrono::steady_clock::duration connect_timeout = std::chrono::seconds{10};
/// Time the SSL handshake operation is allowed to last.
std::chrono::steady_clock::duration ssl_handshake_timeout = std::chrono::seconds{10};
/// @brief Health checks interval.
std::chrono::steady_clock::duration health_check_interval = std::chrono::seconds{2};
/// Time waited before trying a reconnection.
std::chrono::steady_clock::duration reconnect_wait_interval = std::chrono::seconds{1};
};
} // boost::redis
#endif // BOOST_REDIS_CONFIG_HPP

View File

@@ -8,6 +8,10 @@
#define BOOST_REDIS_CONNECTION_HPP
#include <boost/redis/detail/connection_base.hpp>
#include <boost/redis/detail/runner.hpp>
#include <boost/redis/detail/reconnection.hpp>
#include <boost/redis/logger.hpp>
#include <boost/redis/config.hpp>
#include <boost/redis/response.hpp>
#include <boost/asio/io_context.hpp>
@@ -16,6 +20,27 @@
namespace boost::redis {
namespace detail
{
template <class Executor>
class dummy_handshaker {
public:
dummy_handshaker(Executor) {}
template <class Stream, class CompletionToken>
auto async_handshake(Stream&, CompletionToken&& token)
{ return asio::post(std::move(token)); }
void set_config(config const&) {}
std::size_t cancel(operation) { return 0;}
constexpr bool is_dummy() const noexcept {return true;}
};
}
/** @brief A connection to the Redis server.
* @ingroup high-level-api
*
@@ -50,6 +75,8 @@ public:
explicit
basic_connection(executor_type ex)
: base_type{ex}
, reconn_{ex}
, runner_{ex, {}}
, stream_{ex}
{}
@@ -66,9 +93,9 @@ public:
void reset_stream()
{
if (stream_.is_open()) {
system::error_code ignore;
stream_.shutdown(asio::ip::tcp::socket::shutdown_both, ignore);
stream_.close(ignore);
system::error_code ec;
stream_.shutdown(asio::ip::tcp::socket::shutdown_both, ec);
stream_.close(ec);
}
}
@@ -78,13 +105,31 @@ public:
/// Returns a const reference to the next layer.
auto next_layer() const noexcept -> auto const& { return stream_; }
/** @brief Starts read and write operations
/** @brief Starts underlying connection operations.
*
* This function starts read and write operations with the Redis
* In more detail, this function will
*
* 1. Resolve the address passed on `boost::redis::config::addr`.
* 2. Connect to one of the results obtained in the resolve operation.
* 3. Send a [HELLO](https://redis.io/commands/hello/) command where each of its parameters are read from `cfg`.
* 4. Start a health-check operation where ping commands are sent
* at intervals specified in
* `boost::redis::config::health_check_interval`. The message passed to
* `PING` will be `boost::redis::config::health_check_id`. Passing a
* timeout with value zero will disable health-checks. If the Redis
* server does not respond to a health-check within two times the value
* specified here, it will be considered unresponsive and the connection
* will be closed and a new connection will be stablished.
* 5. Starts read and write operations with the Redis
* server. More specifically it will trigger the write of all
* requests i.e. calls to `async_exec` that happened prior to this
* call.
*
* When a connection is lost for any reason, a new one is stablished automatically. To disable
* reconnection call `boost::redis::connection::cancel(operation::reconnection)`.
*
* @param cfg Configuration paramters.
* @param l Logger object. The interface expected is specified in the class `boost::redis::logger`.
* @param token Completion token.
*
* The completion token must have the following signature
@@ -95,51 +140,37 @@ public:
*
* @remarks
*
* * This function will complete only when the connection is lost.
* If the error is asio::error::eof this function will complete
* without error.
* * It can can be called multiple times on the same connection
* object. This makes it simple to implement reconnection in a way
* that does not require cancelling any pending connections.
* * This function will complete only if reconnection was disabled and the connection is lost.
*
* For examples of how to call this function see the examples. For
* example, if reconnection is not necessary, the coroutine below
* is enough
*
* ```cpp
* auto run(std::shared_ptr<connection> conn, std::string host, std::string port) -> net::awaitable<void>
* {
* // From examples/common.hpp to avoid vebosity
* co_await connect(conn, host, port);
*
* // async_run coordinate read and write operations.
* co_await conn->async_run();
*
* // Cancel pending operations, if any.
* conn->cancel(operation::exec);
* conn->cancel(operation::receive);
* }
* ```
*
* For a reconnection example see cpp20_subscriber.cpp.
* For example on how to call this function refer to cpp20_intro.cpp or any other example.
*/
template <class CompletionToken = asio::default_completion_token_t<executor_type>>
auto async_run(CompletionToken token = CompletionToken{})
template <
class Logger = logger,
class CompletionToken = asio::default_completion_token_t<executor_type>>
auto
async_run(
config const& cfg = {},
Logger l = Logger{},
CompletionToken token = CompletionToken{})
{
return base_type::async_run(std::move(token));
reconn_.set_wait_interval(cfg.reconnect_wait_interval);
runner_.set_config(cfg);
l.set_prefix(runner_.get_config().log_prefix);
return reconn_.async_run(*this, l, std::move(token));
}
/** @brief Executes a command on the Redis server asynchronously.
/** @brief Executes commands on the Redis server asynchronously.
*
* This function sends a request to the Redis server and
* complete after the response has been processed. If the request
* waits for the responses to each individual command in the
* request to arrive. If the request
* contains only commands that don't expect a response, the
* completion occurs after it has been written to the underlying
* stream. Multiple concurrent calls to this function will be
* automatically queued by the implementation.
*
* @param req Request object.
* @param response Response object.
* @param resp Response object.
* @param token Asio completion token.
*
* For an example see cpp20_echo_server.cpp. The completion token must
@@ -157,17 +188,17 @@ public:
class CompletionToken = asio::default_completion_token_t<executor_type>>
auto async_exec(
request const& req,
Response& response = ignore,
Response& resp = ignore,
CompletionToken token = CompletionToken{})
{
return base_type::async_exec(req, response, std::move(token));
return base_type::async_exec(req, resp, std::move(token));
}
/** @brief Receives server side pushes asynchronously.
*
* When pushes arrive and there is no async_receive operation in
* When pushes arrive and there is no `async_receive` operation in
* progress, pushed data, requests, and responses will be paused
* until async_receive is called again. Apps will usually want to
* until `async_receive` is called again. Apps will usually want to
* call `async_receive` in a loop.
*
* To cancel an ongoing receive operation apps should call
@@ -202,15 +233,18 @@ public:
* `async_exec`. Affects only requests that haven't been written
* yet.
* @li operation::run: Cancels the `async_run` operation.
* @li operation::receive: Cancels any ongoing calls to * `async_receive`.
* @li operation::all: Cancels all operations listed above. This
* is the default argument.
* @li operation::receive: Cancels any ongoing calls to `async_receive`.
* @li operation::all: Cancels all operations listed above.
*
* @param op: The operation to be cancelled.
* @returns The number of operations that have been canceled.
*/
auto cancel(operation op = operation::all) -> std::size_t
{ return base_type::cancel(op); }
{
reconn_.cancel(op);
runner_.cancel(op);
return base_type::cancel(op);
}
/// Sets the maximum size of the read buffer.
void set_max_buffer_read_size(std::size_t max_read_size) noexcept
@@ -227,22 +261,42 @@ public:
void reserve(std::size_t read, std::size_t write)
{ base_type::reserve(read, write); }
/// Returns true if the connection was canceled.
bool is_cancelled() const noexcept
{ return reconn_.is_cancelled();}
private:
using runner_type = detail::runner<executor_type, detail::dummy_handshaker>;
using reconnection_type = detail::basic_reconnection<executor_type>;
using this_type = basic_connection<next_layer_type>;
template <class, class> friend class detail::connection_base;
template <class, class> friend struct detail::exec_read_op;
template <class, class> friend class detail::read_next_op;
template <class, class> friend struct detail::exec_op;
template <class, class> friend struct detail::receive_op;
template <class> friend struct detail::reader_op;
template <class> friend struct detail::writer_op;
template <class> friend struct detail::run_op;
template <class, class> friend struct detail::writer_op;
template <class, class> friend struct detail::run_op;
template <class> friend struct detail::wait_receive_op;
template <class, class, class> friend struct detail::run_all_op;
template <class, class, class> friend struct detail::reconnection_op;
template <class Logger, class CompletionToken>
auto async_run_one(Logger l, CompletionToken token)
{ return runner_.async_run(*this, l, std::move(token)); }
template <class Logger, class CompletionToken>
auto async_run_impl(Logger l, CompletionToken token)
{ return base_type::async_run_impl(l, std::move(token)); }
void close()
{ reset_stream(); }
void close() { stream_.close(); }
auto is_open() const noexcept { return stream_.is_open(); }
auto lowest_layer() noexcept -> auto& { return stream_.lowest_layer(); }
reconnection_type reconn_;
runner_type runner_;
Socket stream_;
};

View File

@@ -8,24 +8,450 @@
#define BOOST_REDIS_CONNECTION_BASE_HPP
#include <boost/redis/adapter/adapt.hpp>
#include <boost/redis/detail/helper.hpp>
#include <boost/redis/detail/read.hpp>
#include <boost/redis/error.hpp>
#include <boost/redis/operation.hpp>
#include <boost/redis/request.hpp>
#include <boost/redis/detail/connection_ops.hpp>
#include <boost/assert.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/redis/resp3/type.hpp>
#include <boost/system.hpp>
#include <boost/asio/bind_executor.hpp>
#include <boost/asio/experimental/channel.hpp>
#include <boost/asio/experimental/parallel_group.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/asio/write.hpp>
#include <boost/assert.hpp>
#include <boost/core/ignore_unused.hpp>
#include <vector>
#include <queue>
#include <limits>
#include <algorithm>
#include <array>
#include <chrono>
#include <deque>
#include <limits>
#include <memory>
#include <string_view>
#include <type_traits>
namespace boost::redis::detail {
template <class Conn>
struct wait_receive_op {
Conn* conn;
asio::coroutine coro{};
template <class Self>
void
operator()(Self& self , system::error_code ec = {})
{
BOOST_ASIO_CORO_REENTER (coro)
{
BOOST_ASIO_CORO_YIELD
conn->channel_.async_send(system::error_code{}, 0, std::move(self));
BOOST_REDIS_CHECK_OP0(;);
BOOST_ASIO_CORO_YIELD
conn->channel_.async_send(system::error_code{}, 0, std::move(self));
BOOST_REDIS_CHECK_OP0(;);
self.complete({});
}
}
};
template <class Conn, class Adapter>
class read_next_op {
public:
using req_info_type = typename Conn::req_info;
using req_info_ptr = typename std::shared_ptr<req_info_type>;
private:
Conn* conn_;
req_info_ptr info_;
Adapter adapter_;
std::size_t cmds_ = 0;
std::size_t read_size_ = 0;
std::size_t index_ = 0;
asio::coroutine coro_{};
public:
read_next_op(Conn& conn, Adapter adapter, req_info_ptr info)
: conn_{&conn}
, info_{info}
, adapter_{adapter}
, cmds_{info->get_number_of_commands()}
{}
template <class Self>
void
operator()( Self& self
, system::error_code ec = {}
, std::size_t n = 0)
{
BOOST_ASIO_CORO_REENTER (coro_)
{
// Loop reading the responses to this request.
while (cmds_ != 0) {
if (info_->stop_requested()) {
self.complete(asio::error::operation_aborted, 0);
return;
}
//-----------------------------------
// If we detect a push in the middle of a request we have
// to hand it to the push consumer. To do that we need
// some data in the read bufer.
if (conn_->read_buffer_.empty()) {
BOOST_ASIO_CORO_YIELD
asio::async_read_until(
conn_->next_layer(),
conn_->make_dynamic_buffer(),
"\r\n", std::move(self));
BOOST_REDIS_CHECK_OP1(conn_->cancel(operation::run););
if (info_->stop_requested()) {
self.complete(asio::error::operation_aborted, 0);
return;
}
}
// If the next request is a push we have to handle it to
// the receive_op wait for it to be done and continue.
if (resp3::to_type(conn_->read_buffer_.front()) == resp3::type::push) {
BOOST_ASIO_CORO_YIELD
conn_->async_wait_receive(std::move(self));
BOOST_REDIS_CHECK_OP1(conn_->cancel(operation::run););
continue;
}
//-----------------------------------
BOOST_ASIO_CORO_YIELD
redis::detail::async_read(
conn_->next_layer(),
conn_->make_dynamic_buffer(),
[i = index_, adpt = adapter_] (resp3::basic_node<std::string_view> const& nd, system::error_code& ec) mutable { adpt(i, nd, ec); },
std::move(self));
++index_;
BOOST_REDIS_CHECK_OP1(conn_->cancel(operation::run););
read_size_ += n;
BOOST_ASSERT(cmds_ != 0);
--cmds_;
}
self.complete({}, read_size_);
}
}
};
template <class Conn, class Adapter>
struct receive_op {
Conn* conn;
Adapter adapter;
std::size_t read_size = 0;
asio::coroutine coro{};
template <class Self>
void
operator()( Self& self
, system::error_code ec = {}
, std::size_t n = 0)
{
BOOST_ASIO_CORO_REENTER (coro)
{
BOOST_ASIO_CORO_YIELD
conn->channel_.async_receive(std::move(self));
BOOST_REDIS_CHECK_OP1(;);
BOOST_ASIO_CORO_YIELD
redis::detail::async_read(conn->next_layer(), conn->make_dynamic_buffer(), adapter, std::move(self));
if (ec || is_cancelled(self)) {
conn->cancel(operation::run);
conn->cancel(operation::receive);
self.complete(!!ec ? ec : asio::error::operation_aborted, {});
return;
}
read_size = n;
BOOST_ASIO_CORO_YIELD
conn->channel_.async_receive(std::move(self));
BOOST_REDIS_CHECK_OP1(;);
self.complete({}, read_size);
return;
}
}
};
template <class Conn, class Adapter>
struct exec_op {
using req_info_type = typename Conn::req_info;
Conn* conn = nullptr;
request const* req = nullptr;
Adapter adapter{};
std::shared_ptr<req_info_type> info = nullptr;
std::size_t read_size = 0;
asio::coroutine coro{};
template <class Self>
void
operator()( Self& self
, system::error_code ec = {}
, std::size_t n = 0)
{
BOOST_ASIO_CORO_REENTER (coro)
{
// Check whether the user wants to wait for the connection to
// be stablished.
if (req->get_config().cancel_if_not_connected && !conn->is_open()) {
BOOST_ASIO_CORO_YIELD
asio::post(std::move(self));
return self.complete(error::not_connected, 0);
}
info = std::allocate_shared<req_info_type>(asio::get_associated_allocator(self), *req, conn->get_executor());
conn->add_request_info(info);
EXEC_OP_WAIT:
BOOST_ASIO_CORO_YIELD
info->async_wait(std::move(self));
BOOST_ASSERT(ec == asio::error::operation_aborted);
if (info->stop_requested()) {
// Don't have to call remove_request as it has already
// been by cancel(exec).
return self.complete(ec, 0);
}
if (is_cancelled(self)) {
if (info->is_written()) {
using c_t = asio::cancellation_type;
auto const c = self.get_cancellation_state().cancelled();
if ((c & c_t::terminal) != c_t::none) {
// Cancellation requires closing the connection
// otherwise it stays in inconsistent state.
conn->cancel(operation::run);
return self.complete(ec, 0);
} else {
// Can't implement other cancelation types, ignoring.
self.get_cancellation_state().clear();
goto EXEC_OP_WAIT;
}
} else {
// Cancelation can be honored.
conn->remove_request(info);
self.complete(ec, 0);
return;
}
}
BOOST_ASSERT(conn->is_open());
if (req->size() == 0) {
// Don't have to call remove_request as it has already
// been removed.
return self.complete({}, 0);
}
BOOST_ASSERT(!conn->reqs_.empty());
BOOST_ASSERT(conn->reqs_.front() != nullptr);
BOOST_ASIO_CORO_YIELD
conn->async_read_next(adapter, std::move(self));
BOOST_REDIS_CHECK_OP1(;);
read_size = n;
if (info->stop_requested()) {
// Don't have to call remove_request as it has already
// been by cancel(exec).
return self.complete(ec, 0);
}
BOOST_ASSERT(!conn->reqs_.empty());
conn->reqs_.pop_front();
if (conn->is_waiting_response()) {
BOOST_ASSERT(!conn->reqs_.empty());
conn->reqs_.front()->proceed();
} else {
conn->read_timer_.cancel_one();
}
self.complete({}, read_size);
}
}
};
template <class Conn, class Logger>
struct run_op {
Conn* conn = nullptr;
Logger logger_;
asio::coroutine coro{};
template <class Self>
void operator()( Self& self
, std::array<std::size_t, 2> order = {}
, system::error_code ec0 = {}
, system::error_code ec1 = {})
{
BOOST_ASIO_CORO_REENTER (coro)
{
conn->write_buffer_.clear();
conn->read_buffer_.clear();
BOOST_ASIO_CORO_YIELD
asio::experimental::make_parallel_group(
[this](auto token) { return conn->reader(token);},
[this](auto token) { return conn->writer(logger_, token);}
).async_wait(
asio::experimental::wait_for_one(),
std::move(self));
if (is_cancelled(self)) {
self.complete(asio::error::operation_aborted);
return;
}
switch (order[0]) {
case 0: self.complete(ec0); break;
case 1: self.complete(ec1); break;
default: BOOST_ASSERT(false);
}
}
}
};
template <class Conn, class Logger>
struct writer_op {
Conn* conn_;
Logger logger_;
asio::coroutine coro{};
template <class Self>
void operator()( Self& self
, system::error_code ec = {}
, std::size_t n = 0)
{
ignore_unused(n);
BOOST_ASIO_CORO_REENTER (coro) for (;;)
{
while (conn_->coalesce_requests()) {
BOOST_ASIO_CORO_YIELD
asio::async_write(conn_->next_layer(), asio::buffer(conn_->write_buffer_), std::move(self));
logger_.on_write(ec, conn_->write_buffer_);
BOOST_REDIS_CHECK_OP0(conn_->cancel(operation::run););
conn_->on_write();
// A socket.close() may have been called while a
// successful write might had already been queued, so we
// have to check here before proceeding.
if (!conn_->is_open()) {
self.complete({});
return;
}
}
BOOST_ASIO_CORO_YIELD
conn_->writer_timer_.async_wait(std::move(self));
if (!conn_->is_open() || is_cancelled(self)) {
// Notice this is not an error of the op, stoping was
// requested from the outside, so we complete with
// success.
self.complete({});
return;
}
}
}
};
template <class Conn>
struct reader_op {
Conn* conn;
asio::coroutine coro{};
bool as_push() const
{
return
(resp3::to_type(conn->read_buffer_.front()) == resp3::type::push)
|| conn->reqs_.empty()
|| (!conn->reqs_.empty() && conn->reqs_.front()->get_number_of_commands() == 0)
|| !conn->is_waiting_response(); // Added to deal with MONITOR.
}
template <class Self>
void operator()( Self& self
, system::error_code ec = {}
, std::size_t n = 0)
{
ignore_unused(n);
BOOST_ASIO_CORO_REENTER (coro) for (;;)
{
BOOST_ASIO_CORO_YIELD
asio::async_read_until(
conn->next_layer(),
conn->make_dynamic_buffer(),
"\r\n", std::move(self));
if (ec == asio::error::eof) {
conn->cancel(operation::run);
return self.complete({}); // EOFINAE: EOF is not an error.
}
BOOST_REDIS_CHECK_OP0(conn->cancel(operation::run););
// We handle unsolicited events in the following way
//
// 1. Its resp3 type is a push.
//
// 2. A non-push type is received with an empty requests
// queue. I have noticed this is possible (e.g. -MISCONF).
// I expect them to have type push so we can distinguish
// them from responses to commands, but it is a
// simple-error. If we are lucky enough to receive them
// when the command queue is empty we can treat them as
// server pushes, otherwise it is impossible to handle
// them properly
//
// 3. The request does not expect any response but we got
// one. This may happen if for example, subscribe with
// wrong syntax.
//
// Useful links:
//
// - https://github.com/redis/redis/issues/11784
// - https://github.com/redis/redis/issues/6426
//
BOOST_ASSERT(!conn->read_buffer_.empty());
if (as_push()) {
BOOST_ASIO_CORO_YIELD
conn->async_wait_receive(std::move(self));
} else {
BOOST_ASSERT_MSG(conn->is_waiting_response(), "Not waiting for a response (using MONITOR command perhaps?)");
BOOST_ASSERT(!conn->reqs_.empty());
BOOST_ASSERT(conn->reqs_.front()->get_number_of_commands() != 0);
conn->reqs_.front()->proceed();
BOOST_ASIO_CORO_YIELD
conn->read_timer_.async_wait(std::move(self));
ec = {};
}
if (!conn->is_open() || ec || is_cancelled(self)) {
conn->cancel(operation::run);
self.complete(asio::error::basic_errors::operation_aborted);
return;
}
}
}
};
/** Base class for high level Redis asynchronous connections.
*
* This class is not meant to be instantiated directly but as base
@@ -64,16 +490,14 @@ public:
derived().close();
read_timer_.cancel();
writer_timer_.cancel();
cancel_on_conn_lost();
return 1U;
return cancel_on_conn_lost();
}
case operation::receive:
{
channel_.cancel();
return 1U;
}
default: BOOST_ASSERT(false); return 0;
default: /* ignore */; return 0;
}
}
@@ -150,7 +574,7 @@ public:
return asio::async_compose
< CompletionToken
, void(system::error_code, std::size_t)
>(redis::detail::exec_op<Derived, decltype(f)>{&derived(), &req, f}, token, writer_timer_);
>(exec_op<Derived, decltype(f)>{&derived(), &req, f}, token, writer_timer_);
}
template <class Response, class CompletionToken>
@@ -163,16 +587,16 @@ public:
return asio::async_compose
< CompletionToken
, void(system::error_code, std::size_t)
>(redis::detail::receive_op<Derived, decltype(f)>{&derived(), f}, token, channel_);
>(receive_op<Derived, decltype(f)>{&derived(), f}, token, channel_);
}
template <class CompletionToken>
auto async_run(CompletionToken token)
template <class Logger, class CompletionToken>
auto async_run_impl(Logger l, CompletionToken token)
{
return asio::async_compose
< CompletionToken
, void(system::error_code)
>(detail::run_op<Derived>{&derived()}, token, writer_timer_);
>(run_op<Derived, Logger>{&derived(), l}, token, writer_timer_);
}
void set_max_buffer_read_size(std::size_t max_read_size) noexcept
@@ -296,13 +720,13 @@ private:
using reqs_type = std::deque<std::shared_ptr<req_info>>;
template <class> friend struct detail::reader_op;
template <class> friend struct detail::writer_op;
template <class> friend struct detail::run_op;
template <class, class> friend struct detail::exec_op;
template <class, class> friend struct detail::exec_read_op;
template <class, class> friend struct detail::receive_op;
template <class> friend struct detail::wait_receive_op;
template <class> friend struct reader_op;
template <class, class> friend struct writer_op;
template <class, class> friend struct run_op;
template <class, class> friend struct exec_op;
template <class, class> friend class read_next_op;
template <class, class> friend struct receive_op;
template <class> friend struct wait_receive_op;
template <class CompletionToken>
auto async_wait_receive(CompletionToken token)
@@ -356,25 +780,25 @@ private:
return asio::async_compose
< CompletionToken
, void(system::error_code)
>(detail::reader_op<Derived>{&derived()}, token, writer_timer_);
>(reader_op<Derived>{&derived()}, token, writer_timer_);
}
template <class CompletionToken>
auto writer(CompletionToken&& token)
template <class CompletionToken, class Logger>
auto writer(Logger l, CompletionToken&& token)
{
return asio::async_compose
< CompletionToken
, void(system::error_code)
>(detail::writer_op<Derived>{&derived()}, token, writer_timer_);
>(writer_op<Derived, Logger>{&derived(), l}, token, writer_timer_);
}
template <class Adapter, class CompletionToken>
auto async_exec_read(Adapter adapter, std::size_t cmds, CompletionToken token)
auto async_read_next(Adapter adapter, CompletionToken token)
{
return asio::async_compose
< CompletionToken
, void(system::error_code, std::size_t)
>(detail::exec_read_op<Derived, Adapter>{&derived(), adapter, cmds}, token, writer_timer_);
>(read_next_op<Derived, Adapter>{derived(), adapter, reqs_.front()}, token, writer_timer_);
}
[[nodiscard]] bool coalesce_requests()

View File

@@ -1,403 +0,0 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#ifndef BOOST_REDIS_CONNECTION_OPS_HPP
#define BOOST_REDIS_CONNECTION_OPS_HPP
#include <boost/redis/adapter/adapt.hpp>
#include <boost/redis/detail/helper.hpp>
#include <boost/redis/error.hpp>
#include <boost/redis/resp3/type.hpp>
#include <boost/redis/detail/read.hpp>
#include <boost/redis/request.hpp>
#include <boost/assert.hpp>
#include <boost/system.hpp>
#include <boost/asio/write.hpp>
#include <boost/core/ignore_unused.hpp>
#include <boost/asio/experimental/parallel_group.hpp>
#include <array>
#include <algorithm>
#include <string_view>
namespace boost::redis::detail {
template <class Conn>
struct wait_receive_op {
Conn* conn;
asio::coroutine coro{};
template <class Self>
void
operator()(Self& self , system::error_code ec = {})
{
BOOST_ASIO_CORO_REENTER (coro)
{
BOOST_ASIO_CORO_YIELD
conn->channel_.async_send(system::error_code{}, 0, std::move(self));
BOOST_REDIS_CHECK_OP0(;);
BOOST_ASIO_CORO_YIELD
conn->channel_.async_send(system::error_code{}, 0, std::move(self));
BOOST_REDIS_CHECK_OP0(;);
self.complete({});
}
}
};
template <class Conn, class Adapter>
struct exec_read_op {
Conn* conn;
Adapter adapter;
std::size_t cmds = 0;
std::size_t read_size = 0;
std::size_t index = 0;
asio::coroutine coro{};
template <class Self>
void
operator()( Self& self
, system::error_code ec = {}
, std::size_t n = 0)
{
BOOST_ASIO_CORO_REENTER (coro)
{
// Loop reading the responses to this request.
BOOST_ASSERT(!conn->reqs_.empty());
while (cmds != 0) {
BOOST_ASSERT(conn->is_waiting_response());
//-----------------------------------
// If we detect a push in the middle of a request we have
// to hand it to the push consumer. To do that we need
// some data in the read bufer.
if (conn->read_buffer_.empty()) {
BOOST_ASIO_CORO_YIELD
asio::async_read_until(
conn->next_layer(),
conn->make_dynamic_buffer(),
"\r\n", std::move(self));
BOOST_REDIS_CHECK_OP1(conn->cancel(operation::run););
}
// If the next request is a push we have to handle it to
// the receive_op wait for it to be done and continue.
if (resp3::to_type(conn->read_buffer_.front()) == resp3::type::push) {
BOOST_ASIO_CORO_YIELD
conn->async_wait_receive(std::move(self));
BOOST_REDIS_CHECK_OP1(conn->cancel(operation::run););
continue;
}
//-----------------------------------
BOOST_ASIO_CORO_YIELD
redis::detail::async_read(
conn->next_layer(),
conn->make_dynamic_buffer(),
[i = index, adpt = adapter] (resp3::basic_node<std::string_view> const& nd, system::error_code& ec) mutable { adpt(i, nd, ec); },
std::move(self));
++index;
BOOST_REDIS_CHECK_OP1(conn->cancel(operation::run););
read_size += n;
BOOST_ASSERT(cmds != 0);
--cmds;
}
self.complete({}, read_size);
}
}
};
template <class Conn, class Adapter>
struct receive_op {
Conn* conn;
Adapter adapter;
std::size_t read_size = 0;
asio::coroutine coro{};
template <class Self>
void
operator()( Self& self
, system::error_code ec = {}
, std::size_t n = 0)
{
BOOST_ASIO_CORO_REENTER (coro)
{
BOOST_ASIO_CORO_YIELD
conn->channel_.async_receive(std::move(self));
BOOST_REDIS_CHECK_OP1(;);
BOOST_ASIO_CORO_YIELD
redis::detail::async_read(conn->next_layer(), conn->make_dynamic_buffer(), adapter, std::move(self));
if (ec || is_cancelled(self)) {
conn->cancel(operation::run);
conn->cancel(operation::receive);
self.complete(!!ec ? ec : asio::error::operation_aborted, {});
return;
}
read_size = n;
BOOST_ASIO_CORO_YIELD
conn->channel_.async_receive(std::move(self));
BOOST_REDIS_CHECK_OP1(;);
self.complete({}, read_size);
return;
}
}
};
template <class Conn, class Adapter>
struct exec_op {
using req_info_type = typename Conn::req_info;
Conn* conn = nullptr;
request const* req = nullptr;
Adapter adapter{};
std::shared_ptr<req_info_type> info = nullptr;
std::size_t read_size = 0;
asio::coroutine coro{};
template <class Self>
void
operator()( Self& self
, system::error_code ec = {}
, std::size_t n = 0)
{
BOOST_ASIO_CORO_REENTER (coro)
{
// Check whether the user wants to wait for the connection to
// be stablished.
if (req->get_config().cancel_if_not_connected && !conn->is_open()) {
return self.complete(error::not_connected, 0);
}
info = std::allocate_shared<req_info_type>(asio::get_associated_allocator(self), *req, conn->get_executor());
conn->add_request_info(info);
EXEC_OP_WAIT:
BOOST_ASIO_CORO_YIELD
info->async_wait(std::move(self));
BOOST_ASSERT(ec == asio::error::operation_aborted);
if (info->stop_requested()) {
// Don't have to call remove_request as it has already
// been by cancel(exec).
return self.complete(ec, 0);
}
if (is_cancelled(self)) {
if (info->is_written()) {
using c_t = asio::cancellation_type;
auto const c = self.get_cancellation_state().cancelled();
if ((c & c_t::terminal) != c_t::none) {
// Cancellation requires closing the connection
// otherwise it stays in inconsistent state.
conn->cancel(operation::run);
return self.complete(ec, 0);
} else {
// Can't implement other cancelation types, ignoring.
self.get_cancellation_state().clear();
goto EXEC_OP_WAIT;
}
} else {
// Cancelation can be honored.
conn->remove_request(info);
self.complete(ec, 0);
return;
}
}
BOOST_ASSERT(conn->is_open());
if (req->size() == 0) {
// Don't have to call remove_request as it has already
// been removed.
return self.complete({}, 0);
}
BOOST_ASSERT(!conn->reqs_.empty());
BOOST_ASSERT(conn->reqs_.front() != nullptr);
BOOST_ASIO_CORO_YIELD
conn->async_exec_read(adapter, conn->reqs_.front()->get_number_of_commands(), std::move(self));
BOOST_REDIS_CHECK_OP1(;);
read_size = n;
BOOST_ASSERT(!conn->reqs_.empty());
conn->reqs_.pop_front();
if (conn->is_waiting_response()) {
BOOST_ASSERT(!conn->reqs_.empty());
conn->reqs_.front()->proceed();
} else {
conn->read_timer_.cancel_one();
}
self.complete({}, read_size);
}
}
};
template <class Conn>
struct run_op {
Conn* conn = nullptr;
asio::coroutine coro{};
template <class Self>
void operator()( Self& self
, std::array<std::size_t, 2> order = {}
, system::error_code ec0 = {}
, system::error_code ec1 = {})
{
BOOST_ASIO_CORO_REENTER (coro)
{
conn->write_buffer_.clear();
conn->read_buffer_.clear();
BOOST_ASIO_CORO_YIELD
asio::experimental::make_parallel_group(
[this](auto token) { return conn->reader(token);},
[this](auto token) { return conn->writer(token);}
).async_wait(
asio::experimental::wait_for_one(),
std::move(self));
if (is_cancelled(self)) {
self.complete(asio::error::operation_aborted);
return;
}
switch (order[0]) {
case 0: self.complete(ec0); break;
case 1: self.complete(ec1); break;
default: BOOST_ASSERT(false);
}
}
}
};
template <class Conn>
struct writer_op {
Conn* conn;
asio::coroutine coro{};
template <class Self>
void operator()( Self& self
, system::error_code ec = {}
, std::size_t n = 0)
{
ignore_unused(n);
BOOST_ASIO_CORO_REENTER (coro) for (;;)
{
while (conn->coalesce_requests()) {
BOOST_ASIO_CORO_YIELD
asio::async_write(conn->next_layer(), asio::buffer(conn->write_buffer_), std::move(self));
BOOST_REDIS_CHECK_OP0(conn->cancel(operation::run););
conn->on_write();
// A socket.close() may have been called while a
// successful write might had already been queued, so we
// have to check here before proceeding.
if (!conn->is_open()) {
self.complete({});
return;
}
}
BOOST_ASIO_CORO_YIELD
conn->writer_timer_.async_wait(std::move(self));
if (!conn->is_open() || is_cancelled(self)) {
// Notice this is not an error of the op, stoping was
// requested from the outside, so we complete with
// success.
self.complete({});
return;
}
}
}
};
template <class Conn>
struct reader_op {
Conn* conn;
asio::coroutine coro{};
template <class Self>
void operator()( Self& self
, system::error_code ec = {}
, std::size_t n = 0)
{
ignore_unused(n);
BOOST_ASIO_CORO_REENTER (coro) for (;;)
{
BOOST_ASIO_CORO_YIELD
asio::async_read_until(
conn->next_layer(),
conn->make_dynamic_buffer(),
"\r\n", std::move(self));
if (ec == asio::error::eof) {
conn->cancel(operation::run);
return self.complete({}); // EOFINAE: EOF is not an error.
}
BOOST_REDIS_CHECK_OP0(conn->cancel(operation::run););
// We handle unsolicited events in the following way
//
// 1. Its resp3 type is a push.
//
// 2. A non-push type is received with an empty requests
// queue. I have noticed this is possible (e.g. -MISCONF).
// I expect them to have type push so we can distinguish
// them from responses to commands, but it is a
// simple-error. If we are lucky enough to receive them
// when the command queue is empty we can treat them as
// server pushes, otherwise it is impossible to handle
// them properly
//
// 3. The request does not expect any response but we got
// one. This may happen if for example, subscribe with
// wrong syntax.
//
BOOST_ASSERT(!conn->read_buffer_.empty());
if (resp3::to_type(conn->read_buffer_.front()) == resp3::type::push
|| conn->reqs_.empty()
|| (!conn->reqs_.empty() && conn->reqs_.front()->get_number_of_commands() == 0)) {
BOOST_ASIO_CORO_YIELD
conn->async_wait_receive(std::move(self));
} else {
BOOST_ASSERT(conn->is_waiting_response());
BOOST_ASSERT(!conn->reqs_.empty());
BOOST_ASSERT(conn->reqs_.front()->get_number_of_commands() != 0);
conn->reqs_.front()->proceed();
BOOST_ASIO_CORO_YIELD
conn->read_timer_.async_wait(std::move(self));
ec = {};
}
if (!conn->is_open() || ec || is_cancelled(self)) {
conn->cancel(operation::run);
self.complete(asio::error::basic_errors::operation_aborted);
return;
}
}
}
};
} // boost::redis::detail
#endif // BOOST_REDIS_CONNECTION_OPS_HPP

View File

@@ -0,0 +1,133 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#ifndef BOOST_REDIS_CONNECTOR_HPP
#define BOOST_REDIS_CONNECTOR_HPP
#include <boost/redis/detail/helper.hpp>
#include <boost/redis/error.hpp>
#include <boost/asio/compose.hpp>
#include <boost/asio/connect.hpp>
#include <boost/asio/coroutine.hpp>
#include <boost/asio/experimental/parallel_group.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/steady_timer.hpp>
#include <string>
#include <chrono>
namespace boost::redis::detail
{
template <class Connector, class Stream>
struct connect_op {
Connector* ctor_ = nullptr;
Stream* stream = nullptr;
asio::ip::tcp::resolver::results_type const* res_ = nullptr;
asio::coroutine coro{};
template <class Self>
void operator()( Self& self
, std::array<std::size_t, 2> const& order = {}
, system::error_code const& ec1 = {}
, asio::ip::tcp::endpoint const& ep= {}
, system::error_code const& ec2 = {})
{
BOOST_ASIO_CORO_REENTER (coro)
{
ctor_->timer_.expires_after(ctor_->timeout_);
BOOST_ASIO_CORO_YIELD
asio::experimental::make_parallel_group(
[this](auto token)
{
auto f = [](system::error_code const&, auto const&) { return true; };
return asio::async_connect(*stream, *res_, f, token);
},
[this](auto token) { return ctor_->timer_.async_wait(token);}
).async_wait(
asio::experimental::wait_for_one(),
std::move(self));
if (is_cancelled(self)) {
self.complete(asio::error::operation_aborted);
return;
}
switch (order[0]) {
case 0: {
ctor_->endpoint_ = ep;
self.complete(ec1);
} break;
case 1:
{
if (ec2) {
self.complete(ec2);
} else {
self.complete(error::connect_timeout);
}
} break;
default: BOOST_ASSERT(false);
}
}
}
};
template <class Executor>
class connector {
public:
using timer_type =
asio::basic_waitable_timer<
std::chrono::steady_clock,
asio::wait_traits<std::chrono::steady_clock>,
Executor>;
connector(Executor ex)
: timer_{ex}
{}
void set_config(config const& cfg)
{ timeout_ = cfg.connect_timeout; }
template <class Stream, class CompletionToken>
auto
async_connect(
Stream& stream,
asio::ip::tcp::resolver::results_type const& res,
CompletionToken&& token)
{
return asio::async_compose
< CompletionToken
, void(system::error_code)
>(connect_op<connector, Stream>{this, &stream, &res}, token, timer_);
}
std::size_t cancel(operation op)
{
switch (op) {
case operation::connect:
case operation::all:
timer_.cancel();
break;
default: /* ignore */;
}
return 0;
}
auto const& endpoint() const noexcept { return endpoint_;}
private:
template <class, class> friend struct connect_op;
timer_type timer_;
std::chrono::steady_clock::duration timeout_ = std::chrono::seconds{2};
asio::ip::tcp::endpoint endpoint_;
};
} // boost::redis::detail
#endif // BOOST_REDIS_CONNECTOR_HPP

View File

@@ -0,0 +1,222 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#ifndef BOOST_REDIS_HEALTH_CHECKER_HPP
#define BOOST_REDIS_HEALTH_CHECKER_HPP
// Has to included before promise.hpp to build on msvc.
#include <boost/redis/request.hpp>
#include <boost/redis/response.hpp>
#include <boost/redis/operation.hpp>
#include <boost/redis/detail/helper.hpp>
#include <boost/redis/config.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/asio/compose.hpp>
#include <boost/asio/consign.hpp>
#include <boost/asio/coroutine.hpp>
#include <boost/asio/experimental/parallel_group.hpp>
#include <memory>
#include <chrono>
namespace boost::redis::detail {
template <class HealthChecker, class Connection>
class ping_op {
public:
HealthChecker* checker_ = nullptr;
Connection* conn_ = nullptr;
asio::coroutine coro_{};
template <class Self>
void operator()(Self& self, system::error_code ec = {}, std::size_t = 0)
{
BOOST_ASIO_CORO_REENTER (coro_) for (;;)
{
if (checker_->checker_has_exited_) {
self.complete({});
return;
}
BOOST_ASIO_CORO_YIELD
conn_->async_exec(checker_->req_, checker_->resp_, std::move(self));
BOOST_REDIS_CHECK_OP0(checker_->wait_timer_.cancel();)
// Wait before pinging again.
checker_->ping_timer_.expires_after(checker_->ping_interval_);
BOOST_ASIO_CORO_YIELD
checker_->ping_timer_.async_wait(std::move(self));
BOOST_REDIS_CHECK_OP0(;)
}
}
};
template <class HealthChecker, class Connection>
class check_timeout_op {
public:
HealthChecker* checker_ = nullptr;
Connection* conn_ = nullptr;
asio::coroutine coro_{};
template <class Self>
void operator()(Self& self, system::error_code ec = {})
{
BOOST_ASIO_CORO_REENTER (coro_) for (;;)
{
checker_->wait_timer_.expires_after(2 * checker_->ping_interval_);
BOOST_ASIO_CORO_YIELD
checker_->wait_timer_.async_wait(std::move(self));
BOOST_REDIS_CHECK_OP0(;)
if (checker_->resp_.has_error()) {
self.complete({});
return;
}
if (checker_->resp_.value().empty()) {
checker_->ping_timer_.cancel();
conn_->cancel(operation::run);
checker_->checker_has_exited_ = true;
self.complete(error::pong_timeout);
return;
}
if (checker_->resp_.has_value()) {
checker_->resp_.value().clear();
}
}
}
};
template <class HealthChecker, class Connection>
class check_health_op {
public:
HealthChecker* checker_ = nullptr;
Connection* conn_ = nullptr;
asio::coroutine coro_{};
template <class Self>
void
operator()(
Self& self,
std::array<std::size_t, 2> order = {},
system::error_code ec1 = {},
system::error_code ec2 = {})
{
BOOST_ASIO_CORO_REENTER (coro_)
{
if (checker_->ping_interval_.count() == 0) {
BOOST_ASIO_CORO_YIELD
asio::post(std::move(self));
self.complete({});
return;
}
BOOST_ASIO_CORO_YIELD
asio::experimental::make_parallel_group(
[this](auto token) { return checker_->async_ping(*conn_, token); },
[this](auto token) { return checker_->async_check_timeout(*conn_, token);}
).async_wait(
asio::experimental::wait_for_one(),
std::move(self));
if (is_cancelled(self)) {
self.complete(asio::error::operation_aborted);
return;
}
switch (order[0]) {
case 0: self.complete(ec1); return;
case 1: self.complete(ec2); return;
default: BOOST_ASSERT(false);
}
}
}
};
template <class Executor>
class health_checker {
private:
using timer_type =
asio::basic_waitable_timer<
std::chrono::steady_clock,
asio::wait_traits<std::chrono::steady_clock>,
Executor>;
public:
health_checker(Executor ex)
: ping_timer_{ex}
, wait_timer_{ex}
{
req_.push("PING", "Boost.Redis");
}
void set_config(config const& cfg)
{
req_.clear();
req_.push("PING", cfg.health_check_id);
ping_interval_ = cfg.health_check_interval;
}
template <
class Connection,
class CompletionToken = asio::default_completion_token_t<Executor>
>
auto async_check_health(Connection& conn, CompletionToken token = CompletionToken{})
{
return asio::async_compose
< CompletionToken
, void(system::error_code)
>(check_health_op<health_checker, Connection>{this, &conn}, token, conn);
}
std::size_t cancel(operation op)
{
switch (op) {
case operation::health_check:
case operation::all:
ping_timer_.cancel();
wait_timer_.cancel();
break;
default: /* ignore */;
}
return 0;
}
private:
template <class Connection, class CompletionToken>
auto async_ping(Connection& conn, CompletionToken token)
{
return asio::async_compose
< CompletionToken
, void(system::error_code)
>(ping_op<health_checker, Connection>{this, &conn}, token, conn, ping_timer_);
}
template <class Connection, class CompletionToken>
auto async_check_timeout(Connection& conn, CompletionToken token)
{
return asio::async_compose
< CompletionToken
, void(system::error_code)
>(check_timeout_op<health_checker, Connection>{this, &conn}, token, conn, wait_timer_);
}
template <class, class> friend class ping_op;
template <class, class> friend class check_timeout_op;
template <class, class> friend class check_health_op;
timer_type ping_timer_;
timer_type wait_timer_;
redis::request req_;
redis::generic_response resp_;
std::chrono::steady_clock::duration ping_interval_ = std::chrono::seconds{5};
bool checker_has_exited_ = false;
};
} // boost::redis::detail
#endif // BOOST_REDIS_HEALTH_CHECKER_HPP

View File

@@ -0,0 +1,135 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#ifndef BOOST_REDIS_RECONNECTION_HPP
#define BOOST_REDIS_RECONNECTION_HPP
#include <boost/redis/config.hpp>
#include <boost/redis/logger.hpp>
#include <boost/asio/coroutine.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/asio/any_io_executor.hpp>
namespace boost::redis::detail
{
template <class Reconnector, class Connection, class Logger>
struct reconnection_op {
Reconnector* reconn_ = nullptr;
Connection* conn_ = nullptr;
Logger logger_;
asio::coroutine coro_{};
template <class Self>
void operator()(Self& self, system::error_code ec = {})
{
BOOST_ASIO_CORO_REENTER (coro_) for (;;)
{
BOOST_ASIO_CORO_YIELD
conn_->async_run_one(logger_, std::move(self));
conn_->reset_stream();
conn_->cancel(operation::receive);
logger_.on_connection_lost(ec);
if (reconn_->is_cancelled() || is_cancelled(self)) {
reconn_->cancel(operation::reconnection);
self.complete(!!ec ? ec : asio::error::operation_aborted);
return;
}
reconn_->timer_.expires_after(reconn_->wait_interval_);
BOOST_ASIO_CORO_YIELD
reconn_->timer_.async_wait(std::move(self));
BOOST_REDIS_CHECK_OP0(;)
if (reconn_->is_cancelled()) {
self.complete(asio::error::operation_aborted);
return;
}
}
}
};
// NOTE: wait_interval could be an async_run parameter.
template <class Executor>
class basic_reconnection {
public:
/// Executor type.
using executor_type = Executor;
basic_reconnection(Executor ex)
: timer_{ex}
, is_cancelled_{false}
{}
basic_reconnection(asio::io_context& ioc, std::chrono::steady_clock::duration wait_interval)
: basic_reconnection{ioc.get_executor(), wait_interval}
{}
/// Rebinds to a new executor type.
template <class Executor1>
struct rebind_executor
{
using other = basic_reconnection<Executor1>;
};
template <
class Connection,
class Logger = logger,
class CompletionToken = asio::default_completion_token_t<executor_type>
>
auto
async_run(
Connection& conn,
Logger l = Logger{},
CompletionToken token = CompletionToken{})
{
return asio::async_compose
< CompletionToken
, void(system::error_code)
>(detail::reconnection_op<basic_reconnection, Connection, Logger>{this, &conn, l}, token, conn);
}
void set_wait_interval(std::chrono::steady_clock::duration wait_interval)
{
wait_interval_ = wait_interval;
}
std::size_t cancel(operation op)
{
switch (op) {
case operation::reconnection:
case operation::all:
is_cancelled_ = true;
timer_.cancel();
break;
default: /* ignore */;
}
return 0U;
}
bool is_cancelled() const noexcept {return is_cancelled_;}
void reset() noexcept {is_cancelled_ = false;}
private:
using timer_type =
asio::basic_waitable_timer<
std::chrono::steady_clock,
asio::wait_traits<std::chrono::steady_clock>,
Executor>;
template <class, class, class> friend struct detail::reconnection_op;
timer_type timer_;
std::chrono::steady_clock::duration wait_interval_ = std::chrono::seconds{1};
bool is_cancelled_;
};
using reconnection = basic_reconnection<asio::any_io_executor>;
} // boost::redis
#endif // BOOST_REDIS_RECONNECTION_HPP

View File

@@ -0,0 +1,137 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#ifndef BOOST_REDIS_RESOLVER_HPP
#define BOOST_REDIS_RESOLVER_HPP
#include <boost/redis/config.hpp>
#include <boost/redis/detail/helper.hpp>
#include <boost/redis/error.hpp>
#include <boost/asio/compose.hpp>
#include <boost/asio/coroutine.hpp>
#include <boost/asio/experimental/parallel_group.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/steady_timer.hpp>
#include <string>
#include <chrono>
namespace boost::redis::detail
{
template <class Resolver>
struct resolve_op {
Resolver* resv_ = nullptr;
asio::coroutine coro{};
template <class Self>
void operator()( Self& self
, std::array<std::size_t, 2> order = {}
, system::error_code ec1 = {}
, asio::ip::tcp::resolver::results_type res = {}
, system::error_code ec2 = {})
{
BOOST_ASIO_CORO_REENTER (coro)
{
resv_->timer_.expires_after(resv_->timeout_);
BOOST_ASIO_CORO_YIELD
asio::experimental::make_parallel_group(
[this](auto token)
{
return resv_->resv_.async_resolve(resv_->addr_.host, resv_->addr_.port, token);
},
[this](auto token) { return resv_->timer_.async_wait(token);}
).async_wait(
asio::experimental::wait_for_one(),
std::move(self));
if (is_cancelled(self)) {
self.complete(asio::error::operation_aborted);
return;
}
switch (order[0]) {
case 0: {
// Resolver completed first.
resv_->results_ = res;
self.complete(ec1);
} break;
case 1: {
if (ec2) {
// Timer completed first with error, perhaps a
// cancellation going on.
self.complete(ec2);
} else {
// Timer completed first without an error, this is a
// resolve timeout.
self.complete(error::resolve_timeout);
}
} break;
default: BOOST_ASSERT(false);
}
}
}
};
template <class Executor>
class resolver {
public:
using timer_type =
asio::basic_waitable_timer<
std::chrono::steady_clock,
asio::wait_traits<std::chrono::steady_clock>,
Executor>;
resolver(Executor ex) : resv_{ex} , timer_{ex} {}
template <class CompletionToken>
auto async_resolve(CompletionToken&& token)
{
return asio::async_compose
< CompletionToken
, void(system::error_code)
>(resolve_op<resolver>{this}, token, resv_);
}
std::size_t cancel(operation op)
{
switch (op) {
case operation::resolve:
case operation::all:
resv_.cancel();
timer_.cancel();
break;
default: /* ignore */;
}
return 0;
}
auto const& results() const noexcept
{ return results_;}
void set_config(config const& cfg)
{
addr_ = cfg.addr;
timeout_ = cfg.resolve_timeout;
}
private:
using resolver_type = asio::ip::basic_resolver<asio::ip::tcp, Executor>;
template <class> friend struct resolve_op;
resolver_type resv_;
timer_type timer_;
address addr_;
std::chrono::steady_clock::duration timeout_;
asio::ip::tcp::resolver::results_type results_;
};
} // boost::redis::detail
#endif // BOOST_REDIS_RESOLVER_HPP

View File

@@ -7,45 +7,86 @@
#ifndef BOOST_REDIS_RUNNER_HPP
#define BOOST_REDIS_RUNNER_HPP
// Has to included before promise.hpp to build on msvc.
#include <boost/redis/detail/health_checker.hpp>
#include <boost/redis/config.hpp>
#include <boost/redis/response.hpp>
#include <boost/redis/detail/helper.hpp>
#include <boost/redis/error.hpp>
#include <boost/redis/address.hpp>
#include <boost/redis/logger.hpp>
#include <boost/redis/operation.hpp>
#include <boost/redis/detail/connector.hpp>
#include <boost/redis/detail/resolver.hpp>
#include <boost/asio/compose.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/asio/consign.hpp>
#include <boost/asio/connect.hpp>
#include <boost/asio/coroutine.hpp>
#include <boost/asio/experimental/parallel_group.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/steady_timer.hpp>
#include <string>
#include <memory>
#include <chrono>
namespace boost::redis::detail {
namespace boost::redis::detail
{
template <class Runner>
struct resolve_op {
Runner* runner = nullptr;
asio::coroutine coro{};
template <class Runner, class Connection, class Logger>
struct hello_op {
Runner* runner_ = nullptr;
Connection* conn_ = nullptr;
Logger logger_;
asio::coroutine coro_{};
template <class Self>
void operator()(Self& self, system::error_code ec = {}, std::size_t = 0)
{
BOOST_ASIO_CORO_REENTER (coro_)
{
runner_->hello_req_.clear();
if (runner_->hello_resp_.has_value())
runner_->hello_resp_.value().clear();
runner_->add_hello();
BOOST_ASIO_CORO_YIELD
conn_->async_exec(runner_->hello_req_, runner_->hello_resp_, std::move(self));
logger_.on_hello(ec, runner_->hello_resp_);
BOOST_REDIS_CHECK_OP0(conn_->cancel(operation::run);)
self.complete(ec);
}
}
};
template <class Runner, class Connection, class Logger>
class runner_op {
private:
Runner* runner_ = nullptr;
Connection* conn_ = nullptr;
Logger logger_;
asio::coroutine coro_{};
public:
runner_op(Runner* runner, Connection* conn, Logger l)
: runner_{runner}
, conn_{conn}
, logger_{l}
{}
template <class Self>
void operator()( Self& self
, std::array<std::size_t, 2> order = {}
, std::array<std::size_t, 3> order = {}
, system::error_code ec0 = {}
, system::error_code ec1 = {}
, asio::ip::tcp::resolver::results_type res = {}
, system::error_code ec2 = {})
, system::error_code ec2 = {}
, std::size_t = 0)
{
BOOST_ASIO_CORO_REENTER (coro)
BOOST_ASIO_CORO_REENTER (coro_)
{
BOOST_ASIO_CORO_YIELD
asio::experimental::make_parallel_group(
[this](auto token)
{
return runner->resv_.async_resolve(runner->addr_.host, runner->addr_.port, token);
},
[this](auto token) { return runner->timer_.async_wait(token);}
[this](auto token) { return runner_->async_run_all(*conn_, logger_, token); },
[this](auto token) { return runner_->health_checker_.async_check_health(*conn_, token); },
[this](auto token) { return runner_->async_hello(*conn_, logger_, token); }
).async_wait(
asio::experimental::wait_for_one(),
asio::experimental::wait_for_all(),
std::move(self));
if (is_cancelled(self)) {
@@ -53,167 +94,151 @@ struct resolve_op {
return;
}
switch (order[0]) {
case 0: {
// Resolver completed first.
runner->endpoints_ = res;
self.complete(ec1);
} break;
case 1: {
if (ec2) {
// Timer completed first with error, perhaps a
// cancellation going on.
self.complete(ec2);
} else {
// Timer completed first without an error, this is a
// resolve timeout.
self.complete(error::resolve_timeout);
}
} break;
default: BOOST_ASSERT(false);
}
}
}
};
template <class Runner, class Stream>
struct connect_op {
Runner* runner = nullptr;
Stream* stream = nullptr;
asio::coroutine coro{};
template <class Self>
void operator()( Self& self
, std::array<std::size_t, 2> order = {}
, system::error_code ec1 = {}
, asio::ip::tcp::endpoint const& = {}
, system::error_code ec2 = {})
{
BOOST_ASIO_CORO_REENTER (coro)
{
BOOST_ASIO_CORO_YIELD
asio::experimental::make_parallel_group(
[this](auto token)
{
auto f = [](system::error_code const&, auto const&) { return true; };
return asio::async_connect(*stream, runner->endpoints_, f, token);
},
[this](auto token) { return runner->timer_.async_wait(token);}
).async_wait(
asio::experimental::wait_for_one(),
std::move(self));
if (is_cancelled(self)) {
self.complete(asio::error::operation_aborted);
if (ec0 == error::connect_timeout || ec0 == error::resolve_timeout) {
self.complete(ec0);
return;
}
switch (order[0]) {
case 0: {
self.complete(ec1);
} break;
case 1:
{
if (ec2) {
self.complete(ec2);
} else {
self.complete(error::connect_timeout);
}
} break;
default: BOOST_ASSERT(false);
if (order[0] == 2 && !!ec2) {
self.complete(ec2);
return;
}
if (order[0] == 1 && ec1 == error::pong_timeout) {
self.complete(ec1);
return;
}
self.complete(ec0);
}
}
};
template <class Runner, class Connection>
struct runner_op {
Runner* runner = nullptr;
Connection* conn = nullptr;
std::chrono::steady_clock::duration resolve_timeout;
std::chrono::steady_clock::duration connect_timeout;
asio::coroutine coro{};
template <class Runner, class Connection, class Logger>
struct run_all_op {
Runner* runner_ = nullptr;
Connection* conn_ = nullptr;
Logger logger_;
asio::coroutine coro_{};
template <class Self>
void operator()(Self& self, system::error_code ec = {})
void operator()(Self& self, system::error_code ec = {}, std::size_t = 0)
{
BOOST_ASIO_CORO_REENTER (coro)
BOOST_ASIO_CORO_REENTER (coro_)
{
runner->timer_.expires_after(resolve_timeout);
BOOST_ASIO_CORO_YIELD
runner->async_resolve(std::move(self));
BOOST_REDIS_CHECK_OP0(;)
runner->timer_.expires_after(connect_timeout);
BOOST_ASIO_CORO_YIELD
runner->async_connect(conn->next_layer(), std::move(self));
BOOST_REDIS_CHECK_OP0(;)
runner_->resv_.async_resolve(std::move(self));
logger_.on_resolve(ec, runner_->resv_.results());
BOOST_REDIS_CHECK_OP0(conn_->cancel(operation::run);)
BOOST_ASIO_CORO_YIELD
conn->async_run(std::move(self));
runner_->ctor_.async_connect(conn_->lowest_layer(), runner_->resv_.results(), std::move(self));
logger_.on_connect(ec, runner_->ctor_.endpoint());
BOOST_REDIS_CHECK_OP0(conn_->cancel(operation::run);)
if (!runner_->hsher_.is_dummy()) {
BOOST_ASIO_CORO_YIELD
runner_->hsher_.async_handshake(conn_->next_layer(), std::move(self));
logger_.on_ssl_handshake(ec);
BOOST_REDIS_CHECK_OP0(conn_->cancel(operation::run);)
}
BOOST_ASIO_CORO_YIELD
conn_->async_run_impl(logger_, std::move(self));
BOOST_REDIS_CHECK_OP0(;)
self.complete({});
self.complete(ec);
}
}
};
template <class Executor>
template <class Executor, template<class> class Handshaker>
class runner {
public:
runner(Executor ex, address addr): resv_{ex}, timer_{ex}, addr_{addr} {}
runner(Executor ex, config cfg)
: resv_{ex}
, ctor_{ex}
, hsher_{ex}
, health_checker_{ex}
, cfg_{cfg}
{ }
template <class CompletionToken>
auto async_resolve(CompletionToken&& token)
std::size_t cancel(operation op)
{
resv_.cancel(op);
ctor_.cancel(op);
hsher_.cancel(op);
health_checker_.cancel(op);
return 0U;
}
void set_config(config const& cfg)
{
cfg_ = cfg;
resv_.set_config(cfg);
ctor_.set_config(cfg);
hsher_.set_config(cfg);
health_checker_.set_config(cfg);
}
template <class Connection, class Logger, class CompletionToken>
auto async_run(Connection& conn, Logger l, CompletionToken token)
{
return asio::async_compose
< CompletionToken
, void(system::error_code)
>(resolve_op<runner>{this}, token, resv_);
>(runner_op<runner, Connection, Logger>{this, &conn, l}, token, conn);
}
template <class Stream, class CompletionToken>
auto async_connect(Stream& stream, CompletionToken&& token)
{
return asio::async_compose
< CompletionToken
, void(system::error_code)
>(connect_op<runner, Stream>{this, &stream}, token, resv_);
}
template <class Connection, class CompletionToken>
auto
async_run(
Connection& conn,
std::chrono::steady_clock::duration resolve_timeout,
std::chrono::steady_clock::duration connect_timeout,
CompletionToken&& token)
{
return asio::async_compose
< CompletionToken
, void(system::error_code)
>(runner_op<runner, Connection>{this, &conn, resolve_timeout, connect_timeout}, token, resv_);
}
config const& get_config() const noexcept {return cfg_;}
private:
using resolver_type = asio::ip::basic_resolver<asio::ip::tcp, Executor>;
using timer_type =
asio::basic_waitable_timer<
std::chrono::steady_clock,
asio::wait_traits<std::chrono::steady_clock>,
Executor>;
using resolver_type = resolver<Executor>;
using connector_type = connector<Executor>;
using handshaker_type = Handshaker<Executor>;
using health_checker_type = health_checker<Executor>;
using timer_type = typename connector_type::timer_type;
template <class, class, class> friend struct run_all_op;
template <class, class, class> friend class runner_op;
template <class, class, class> friend struct hello_op;
template <class, class> friend struct runner_op;
template <class, class> friend struct connect_op;
template <class> friend struct resolve_op;
template <class Connection, class Logger, class CompletionToken>
auto async_run_all(Connection& conn, Logger l, CompletionToken token)
{
return asio::async_compose
< CompletionToken
, void(system::error_code)
>(run_all_op<runner, Connection, Logger>{this, &conn, l}, token, conn);
}
template <class Connection, class Logger, class CompletionToken>
auto async_hello(Connection& conn, Logger l, CompletionToken token)
{
return asio::async_compose
< CompletionToken
, void(system::error_code)
>(hello_op<runner, Connection, Logger>{this, &conn, l}, token, conn);
}
void add_hello()
{
if (!cfg_.username.empty() && !cfg_.password.empty() && !cfg_.clientname.empty())
hello_req_.push("HELLO", "3", "AUTH", cfg_.username, cfg_.password, "SETNAME", cfg_.clientname);
else if (cfg_.username.empty() && cfg_.password.empty() && cfg_.clientname.empty())
hello_req_.push("HELLO", "3");
else if (cfg_.clientname.empty())
hello_req_.push("HELLO", "3", "AUTH", cfg_.username, cfg_.password);
else
hello_req_.push("HELLO", "3", "SETNAME", cfg_.clientname);
}
resolver_type resv_;
timer_type timer_;
address addr_;
asio::ip::tcp::resolver::results_type endpoints_;
connector_type ctor_;
handshaker_type hsher_;
health_checker_type health_checker_;
request hello_req_;
generic_response hello_resp_;
config cfg_;
};
} // boost::redis::detail

View File

@@ -69,6 +69,12 @@ enum class error
/// Connect timeout
connect_timeout,
/// Connect timeout
pong_timeout,
/// SSL handshake timeout
ssl_handshake_timeout,
};
/** \internal

View File

@@ -40,6 +40,7 @@ struct error_category_impl : system::error_category {
case error::not_connected: return "Not connected.";
case error::resolve_timeout: return "Resolve timeout.";
case error::connect_timeout: return "Connect timeout.";
case error::pong_timeout: return "Pong timeout.";
default: BOOST_ASSERT(false); return "Boost.Redis error.";
}
}

View File

@@ -0,0 +1,128 @@
/* 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 <boost/redis/logger.hpp>
#include <boost/system/error_code.hpp>
#include <iostream>
#include <iterator>
namespace boost::redis
{
void logger::write_prefix()
{
if (!std::empty(prefix_))
std::clog << prefix_;
}
void logger::on_resolve(system::error_code const& ec, asio::ip::tcp::resolver::results_type const& res)
{
if (level_ < level::info)
return;
write_prefix();
std::clog << "Resolve results: ";
if (ec) {
std::clog << ec.message() << std::endl;
} else {
auto begin = std::cbegin(res);
auto end = std::cend(res);
if (begin == end)
return;
std::clog << begin->endpoint();
for (auto iter = std::next(begin); iter != end; ++iter)
std::clog << ", " << iter->endpoint();
}
std::clog << std::endl;
}
void logger::on_connect(system::error_code const& ec, asio::ip::tcp::endpoint const& ep)
{
if (level_ < level::info)
return;
write_prefix();
std::clog << "Connected to endpoint: ";
if (ec)
std::clog << ec.message() << std::endl;
else
std::clog << ep;
std::clog << std::endl;
}
void logger::on_ssl_handshake(system::error_code const& ec)
{
if (level_ < level::info)
return;
write_prefix();
std::clog << "SSL handshake: " << ec.message() << std::endl;
}
void logger::on_connection_lost(system::error_code const& ec)
{
if (level_ < level::info)
return;
write_prefix();
if (ec)
std::clog << "Connection lost: " << ec.message();
else
std::clog << "Connection lost.";
std::clog << std::endl;
}
void
logger::on_write(
system::error_code const& ec,
std::string const& payload)
{
if (level_ < level::info)
return;
write_prefix();
if (ec)
std::clog << "Write: " << ec.message();
else
std::clog << "Bytes written: " << std::size(payload);
std::clog << std::endl;
}
void
logger::on_hello(
system::error_code const& ec,
generic_response const& resp)
{
if (level_ < level::info)
return;
write_prefix();
if (ec) {
std::clog << "Hello: " << ec.message();
if (resp.has_error())
std::clog << " (" << resp.error().diagnostic << ")";
} else {
std::clog << "Hello: Success";
}
std::clog << std::endl;
}
} // boost::redis

View File

@@ -0,0 +1,127 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#ifndef BOOST_REDIS_LOGGER_HPP
#define BOOST_REDIS_LOGGER_HPP
#include <boost/redis/response.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <string>
namespace boost::system {class error_code;}
namespace boost::redis {
/** @brief Logger class
* @ingroup high-level-api
*
* The class can be passed to the connection objects to log to `std::clog`
*/
class logger {
public:
/** @brief Syslog-like log levels
* @ingroup high-level-api
*/
enum class level
{ /// Emergency
emerg,
/// Alert
alert,
/// Critical
crit,
/// Error
err,
/// Warning
warning,
/// Notice
notice,
/// Info
info,
/// Debug
debug
};
/** @brief Constructor
* @ingroup high-level-api
*
* @param l Log level.
*/
logger(level l = level::info)
: level_{l}
{}
/** @brief Called when the resolve operation completes.
* @ingroup high-level-api
*
* @param ec Error returned by the resolve operation.
* @param res Resolve results.
*/
void on_resolve(system::error_code const& ec, asio::ip::tcp::resolver::results_type const& res);
/** @brief Called when the connect operation completes.
* @ingroup high-level-api
*
* @param ec Error returned by the connect operation.
* @param ep Endpoint to which the connection connected.
*/
void on_connect(system::error_code const& ec, asio::ip::tcp::endpoint const& ep);
/** @brief Called when the ssl handshake operation completes.
* @ingroup high-level-api
*
* @param ec Error returned by the handshake operation.
*/
void on_ssl_handshake(system::error_code const& ec);
/** @brief Called when the connection is lost.
* @ingroup high-level-api
*
* @param ec Error returned when the connection is lost.
*/
void on_connection_lost(system::error_code const& ec);
/** @brief Called when the write operation completes.
* @ingroup high-level-api
*
* @param ec Error code returned by the write operation.
* @param payload The payload written to the socket.
*/
void on_write(system::error_code const& ec, std::string const& payload);
/** @brief Called when the `HELLO` request completes.
* @ingroup high-level-api
*
* @param ec Error code returned by the async_exec operation.
* @param resp Response sent by the Redis server.
*/
void on_hello(system::error_code const& ec, generic_response const& resp);
/** @brief Sets a prefix to every log message
* @ingroup high-level-api
*
* @param prefix The prefix.
*/
void set_prefix(std::string_view prefix)
{
prefix_ = prefix;
}
private:
void write_prefix();
level level_;
std::string_view prefix_;
};
} // boost::redis
#endif // BOOST_REDIS_LOGGER_HPP

View File

@@ -9,19 +9,29 @@
namespace boost::redis {
/** \brief Connection operations that can be cancelled.
* \ingroup high-level-api
/** @brief Connection operations that can be cancelled.
* @ingroup high-level-api
*
* The operations listed below can be passed to the
* `boost::redis::connection::cancel` member function.
*/
enum class operation {
/// Resolve operation.
resolve,
/// Connect operation.
connect,
/// SSL handshake operation.
ssl_handshake,
/// Refers to `connection::async_exec` operations.
exec,
/// Refers to `connection::async_run` operations.
run,
/// Refers to `connection::async_receive` operations.
receive,
/// Cancels reconnection.
reconnection,
/// Health check operation.
health_check,
/// Refers to all operations.
all,
};

View File

@@ -12,6 +12,7 @@
#include <string>
#include <tuple>
#include <algorithm>
// NOTE: For some commands like hset it would be a good idea to assert
// the value type is a pair.
@@ -138,12 +139,14 @@ public:
template <class... Ts>
void push(std::string_view cmd, Ts const&... args)
{
auto const size_before = std::size(payload_);
auto constexpr pack_size = sizeof...(Ts);
resp3::add_header(payload_, resp3::type::array, 1 + pack_size);
resp3::add_bulk(payload_, cmd);
resp3::add_bulk(payload_, std::tie(std::forward<Ts const&>(args)...));
auto const size_after = std::size(payload_);
check_cmd(cmd);
check_cmd(cmd, size_after - size_before);
}
/** @brief Appends a new command to the end of the request.
@@ -191,6 +194,7 @@ public:
if (begin == end)
return;
auto const size_before = std::size(payload_);
auto constexpr size = resp3::bulk_counter<value_type>::size;
auto const distance = std::distance(begin, end);
resp3::add_header(payload_, resp3::type::array, 2 + size * distance);
@@ -200,7 +204,9 @@ public:
for (; begin != end; ++begin)
resp3::add_bulk(payload_, *begin);
check_cmd(cmd);
auto const size_after = std::size(payload_);
check_cmd(cmd, size_after - size_before);
}
/** @brief Appends a new command to the end of the request.
@@ -243,6 +249,7 @@ public:
if (begin == end)
return;
auto const size_before = std::size(payload_);
auto constexpr size = resp3::bulk_counter<value_type>::size;
auto const distance = std::distance(begin, end);
resp3::add_header(payload_, resp3::type::array, 1 + size * distance);
@@ -251,7 +258,9 @@ public:
for (; begin != end; ++begin)
resp3::add_bulk(payload_, *begin);
check_cmd(cmd);
auto const size_after = std::size(payload_);
check_cmd(cmd, size_after - size_before);
}
/** @brief Appends a new command to the end of the request.
@@ -299,13 +308,18 @@ public:
}
private:
void check_cmd(std::string_view cmd)
void check_cmd(std::string_view cmd, std::size_t n)
{
if (!detail::has_response(cmd))
++commands_;
if (cmd == "HELLO")
if (cmd == "HELLO") {
has_hello_priority_ = cfg_.hello_with_priority;
if (has_hello_priority_) {
auto const shift = std::size(payload_) - n;
std::rotate(std::begin(payload_), std::begin(payload_) + shift, std::end(payload_));
}
}
}
config cfg_;

View File

@@ -21,7 +21,6 @@ void boost_redis_to_bulk(std::string& payload, std::string_view data)
void add_header(std::string& payload, type t, std::size_t size)
{
// TODO: Call reserve.
auto const str = std::to_string(size);
payload += to_code(t);

View File

@@ -84,7 +84,6 @@ void add_header(std::string& payload, type t, std::size_t size);
template <class T>
void add_bulk(std::string& payload, T const& data)
{
// TODO: Call reserve.
add_bulk_impl<T>::add(payload, data);
}

View File

@@ -1,62 +0,0 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#ifndef BOOST_REDIS_RUN_HPP
#define BOOST_REDIS_RUN_HPP
// Has to included before promise.hpp to build on msvc.
#include <boost/redis/detail/runner.hpp>
#include <boost/redis/connection.hpp>
#include <boost/redis/address.hpp>
#include <boost/asio/compose.hpp>
#include <boost/asio/consign.hpp>
#include <memory>
#include <chrono>
namespace boost::redis {
/** @brief Call async_run on the connection.
* @ingroup high-level-api
*
* This is a facility function that
* 1. Resoves the endpoint.
* 2. Connects to one of the endpoints from 1.
* 3. Calls async_run on the underlying connection.
*
* @param conn A connection to Redis.
* @param host Redis host to connect to.
* @param port Redis port to connect to.
* @param resolve_timeout Time the resolve operation is allowed to take.
* @param connect_timeout Time the connect operation is allowed to take.
* @param token Completion token.
*/
template <
class Socket,
class CompletionToken = asio::default_completion_token_t<typename Socket::executor_type>
>
auto
async_run(
basic_connection<Socket>& conn,
address addr = address{"127.0.0.1", "6379"},
std::chrono::steady_clock::duration resolve_timeout = std::chrono::seconds{10},
std::chrono::steady_clock::duration connect_timeout = std::chrono::seconds{10},
CompletionToken token = CompletionToken{})
{
using executor_type = typename Socket::executor_type;
using runner_type = detail::runner<executor_type>;
auto runner = std::make_shared<runner_type>(conn.get_executor(), addr);
return
runner->async_run(
conn,
resolve_timeout,
connect_timeout,
asio::consign(std::move(token), runner));
}
} // boost::redis
#endif // BOOST_REDIS_RUN_HPP

View File

@@ -5,6 +5,7 @@
*/
#include <boost/redis/impl/error.ipp>
#include <boost/redis/impl/logger.ipp>
#include <boost/redis/impl/request.ipp>
#include <boost/redis/impl/ignore.ipp>
#include <boost/redis/resp3/impl/type.ipp>

View File

@@ -8,7 +8,11 @@
#define BOOST_REDIS_SSL_CONNECTION_HPP
#include <boost/redis/detail/connection_base.hpp>
#include <boost/redis/detail/runner.hpp>
#include <boost/redis/ssl/detail/handshaker.hpp>
#include <boost/redis/detail/reconnection.hpp>
#include <boost/redis/response.hpp>
#include <boost/redis/logger.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ssl/stream.hpp>
@@ -56,7 +60,10 @@ public:
explicit
basic_connection(executor_type ex, asio::ssl::context& ctx)
: base_type{ex}
, stream_{ex, ctx}
, ctx_{&ctx}
, reconn_{ex}
, runner_{ex, {}}
, stream_{std::make_unique<next_layer_type>(ex, ctx)}
{ }
/// Constructor
@@ -66,28 +73,40 @@ public:
{ }
/// Returns the associated executor.
auto get_executor() {return stream_.get_executor();}
auto get_executor() {return stream_->get_executor();}
/// Reset the underlying stream.
void reset_stream(asio::ssl::context& ctx)
void reset_stream()
{
stream_ = next_layer_type{stream_.get_executor(), ctx};
if (stream_->next_layer().is_open()) {
stream_->next_layer().close();
stream_ = std::make_unique<next_layer_type>(stream_->get_executor(), *ctx_);
}
}
/// Returns a reference to the next layer.
auto& next_layer() noexcept { return stream_; }
auto& next_layer() noexcept { return *stream_; }
/// Returns a const reference to the next layer.
auto const& next_layer() const noexcept { return stream_; }
auto const& next_layer() const noexcept { return *stream_; }
/** @brief Establishes a connection with the Redis server asynchronously.
*
* See redis::connection::async_run for more information.
*/
template <class CompletionToken = asio::default_completion_token_t<executor_type>>
auto async_run(CompletionToken token = CompletionToken{})
template <
class Logger = logger,
class CompletionToken = asio::default_completion_token_t<executor_type>>
auto
async_run(
config const& cfg = {},
Logger l = Logger{},
CompletionToken token = CompletionToken{})
{
return base_type::async_run(std::move(token));
reconn_.set_wait_interval(cfg.reconnect_wait_interval);
runner_.set_config(cfg);
l.set_prefix(runner_.get_config().log_prefix);
return reconn_.async_run(*this, l, std::move(token));
}
/** @brief Executes a command on the Redis server asynchronously.
@@ -124,9 +143,13 @@ public:
* See redis::connection::cancel for more information.
*/
auto cancel(operation op = operation::all) -> std::size_t
{ return base_type::cancel(op); }
{
reconn_.cancel(op);
runner_.cancel(op);
return base_type::cancel(op);
}
auto& lowest_layer() noexcept { return stream_.lowest_layer(); }
auto& lowest_layer() noexcept { return stream_->lowest_layer(); }
/// Sets the maximum size of the read buffer.
void set_max_buffer_read_size(std::size_t max_read_size) noexcept
@@ -143,22 +166,43 @@ public:
void reserve(std::size_t read, std::size_t write)
{ base_type::reserve(read, write); }
/// Returns true if the connection was canceled.
bool is_cancelled() const noexcept
{ return reconn_.is_cancelled();}
private:
using runner_type = redis::detail::runner<executor_type, detail::handshaker>;
using reconnection_type = redis::detail::basic_reconnection<executor_type>;
using this_type = basic_connection<next_layer_type>;
template <class Logger, class CompletionToken>
auto async_run_one(Logger l, CompletionToken token)
{ return runner_.async_run(*this, l, std::move(token)); }
template <class Logger, class CompletionToken>
auto async_run_impl(Logger l, CompletionToken token)
{ return base_type::async_run_impl(l, std::move(token)); }
template <class, class> friend class redis::detail::connection_base;
template <class, class> friend class redis::detail::read_next_op;
template <class, class> friend struct redis::detail::exec_op;
template <class, class> friend struct redis::detail::exec_read_op;
template <class, class> friend struct detail::receive_op;
template <class> friend struct redis::detail::run_op;
template <class> friend struct redis::detail::writer_op;
template <class, class> friend struct redis::detail::receive_op;
template <class, class> friend struct redis::detail::run_op;
template <class, class> friend struct redis::detail::writer_op;
template <class> friend struct redis::detail::reader_op;
template <class> friend struct detail::wait_receive_op;
template <class> friend struct redis::detail::wait_receive_op;
template <class, class, class> friend struct redis::detail::run_all_op;
template <class, class, class> friend struct redis::detail::reconnection_op;
auto is_open() const noexcept { return stream_.next_layer().is_open(); }
void close() { stream_.next_layer().close(); }
auto is_open() const noexcept { return stream_->next_layer().is_open(); }
next_layer_type stream_;
void close()
{ reset_stream(); }
asio::ssl::context* ctx_;
reconnection_type reconn_;
runner_type runner_;
std::unique_ptr<next_layer_type> stream_;
};
/** \brief A connection that uses a boost::asio::ssl::stream<boost::asio::ip::tcp::socket>.

View File

@@ -0,0 +1,124 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#ifndef BOOST_REDIS_SSL_CONNECTOR_HPP
#define BOOST_REDIS_SSL_CONNECTOR_HPP
#include <boost/redis/detail/helper.hpp>
#include <boost/redis/error.hpp>
#include <boost/asio/compose.hpp>
#include <boost/asio/connect.hpp>
#include <boost/asio/coroutine.hpp>
#include <boost/asio/experimental/parallel_group.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/asio/ssl.hpp>
#include <string>
#include <chrono>
namespace boost::redis::ssl::detail
{
template <class Handshaker, class Stream>
struct handshake_op {
Handshaker* hsher_ = nullptr;
Stream* stream_ = nullptr;
asio::coroutine coro{};
template <class Self>
void operator()( Self& self
, std::array<std::size_t, 2> const& order = {}
, system::error_code const& ec1 = {}
, system::error_code const& ec2 = {})
{
BOOST_ASIO_CORO_REENTER (coro)
{
hsher_->timer_.expires_after(hsher_->timeout_);
BOOST_ASIO_CORO_YIELD
asio::experimental::make_parallel_group(
[this](auto token) { return stream_->async_handshake(asio::ssl::stream_base::client, token); },
[this](auto token) { return hsher_->timer_.async_wait(token);}
).async_wait(
asio::experimental::wait_for_one(),
std::move(self));
if (is_cancelled(self)) {
self.complete(asio::error::operation_aborted);
return;
}
switch (order[0]) {
case 0: {
self.complete(ec1);
} break;
case 1:
{
if (ec2) {
self.complete(ec2);
} else {
self.complete(error::ssl_handshake_timeout);
}
} break;
default: BOOST_ASSERT(false);
}
}
}
};
template <class Executor>
class handshaker {
public:
using timer_type =
asio::basic_waitable_timer<
std::chrono::steady_clock,
asio::wait_traits<std::chrono::steady_clock>,
Executor>;
handshaker(Executor ex)
: timer_{ex}
{}
template <class Stream, class CompletionToken>
auto
async_handshake(Stream& stream, CompletionToken&& token)
{
return asio::async_compose
< CompletionToken
, void(system::error_code)
>(handshake_op<handshaker, Stream>{this, &stream}, token, timer_);
}
std::size_t cancel(operation op)
{
switch (op) {
case operation::ssl_handshake:
case operation::all:
timer_.cancel();
break;
default: /* ignore */;
}
return 0;
}
constexpr bool is_dummy() const noexcept
{return false;}
void set_config(config const& cfg)
{ timeout_ = cfg.ssl_handshake_timeout; }
private:
template <class, class> friend struct handshake_op;
timer_type timer_;
std::chrono::steady_clock::duration timeout_;
};
} // boost::redis::ssl::detail
#endif // BOOST_REDIS_SSL_CONNECTOR_HPP

29
tests/common.cpp Normal file
View File

@@ -0,0 +1,29 @@
#include "common.hpp"
#include <iostream>
#include <boost/asio/consign.hpp>
#include <boost/test/unit_test.hpp>
struct run_callback {
std::shared_ptr<boost::redis::connection> conn;
boost::redis::operation op;
boost::system::error_code expected;
void operator()(boost::system::error_code const& ec) const
{
std::cout << "async_run: " << ec.message() << std::endl;
//BOOST_CHECK_EQUAL(ec, expected);
conn->cancel(op);
}
};
void
run(
std::shared_ptr<boost::redis::connection> conn,
boost::redis::config cfg,
boost::system::error_code ec,
boost::redis::operation op)
{
conn->async_run(cfg, {}, run_callback{conn, op, ec});
}

View File

@@ -3,10 +3,23 @@
#include <boost/system/error_code.hpp>
#include <boost/asio/redirect_error.hpp>
#include <boost/asio/use_awaitable.hpp>
#include <boost/redis/connection.hpp>
#include <boost/redis/operation.hpp>
#include <memory>
namespace net = boost::asio;
#ifdef BOOST_ASIO_HAS_CO_AWAIT
namespace net = boost::asio;
inline
auto redir(boost::system::error_code& ec)
{ return net::redirect_error(net::use_awaitable, ec); }
{ return net::redirect_error(boost::asio::use_awaitable, ec); }
#endif // BOOST_ASIO_HAS_CO_AWAIT
void
run(
std::shared_ptr<boost::redis::connection> conn,
boost::redis::config cfg = {},
boost::system::error_code ec = boost::asio::error::operation_aborted,
boost::redis::operation op = boost::redis::operation::receive);

View File

@@ -4,8 +4,7 @@
* accompanying file LICENSE.txt)
*/
#include <boost/redis/run.hpp>
#include <boost/redis/check_health.hpp>
#include <boost/redis/connection.hpp>
#include <boost/system/errc.hpp>
#define BOOST_TEST_MODULE check-health
#include <boost/test/included/unit_test.hpp>
@@ -14,57 +13,59 @@
#include <boost/redis/src.hpp>
namespace net = boost::asio;
namespace redis = boost::redis;
using error_code = boost::system::error_code;
using connection = boost::redis::connection;
using boost::redis::request;
using boost::redis::ignore;
using boost::redis::operation;
using boost::redis::generic_response;
using boost::redis::async_check_health;
using boost::redis::async_run;
using boost::redis::address;
using namespace std::chrono_literals;
using boost::redis::logger;
using redis::config;
// TODO: Test cancel(health_check)
std::chrono::seconds const interval{1};
struct push_callback {
connection* conn;
connection* conn1;
connection* conn2;
generic_response* resp;
request* req;
generic_response* resp2;
request* req1;
int i = 0;
boost::asio::coroutine coro{};
void operator()(error_code ec = {}, std::size_t = 0)
{
++i;
if (ec) {
std::clog << "Exiting." << std::endl;
return;
}
BOOST_ASIO_CORO_REENTER (coro) for (;;)
{
resp2->value().clear();
BOOST_ASIO_CORO_YIELD
conn2->async_receive(*resp2, *this);
if (ec) {
std::clog << "Exiting." << std::endl;
return;
}
if (resp->value().empty()) {
// First call
BOOST_TEST(!ec);
conn2->async_receive(*resp, *this);
} else if (i == 5) {
std::clog << "Pausing the server" << std::endl;
// Pause the redis server to test if the health-check exits.
conn->async_exec(*req, ignore, [](auto ec, auto) {
std::clog << "Pausing callback> " << ec.message() << std::endl;
BOOST_TEST(resp2->has_value());
BOOST_TEST(!resp2->value().empty());
std::clog << "Event> " << resp2->value().front().value << std::endl;
++i;
if (i == 5) {
std::clog << "Pausing the server" << std::endl;
// Pause the redis server to test if the health-check exits.
BOOST_ASIO_CORO_YIELD
conn1->async_exec(*req1, ignore, *this);
std::clog << "After pausing> " << ec.message() << std::endl;
// Don't know in CI we are getting: Got RESP3 simple-error.
//BOOST_TEST(!ec);
});
conn2->cancel(operation::run);
conn2->cancel(operation::receive);
} else {
BOOST_TEST(!ec);
// Expect 3 pongs and pause the clients so check-health exists
// without error.
BOOST_TEST(resp->has_value());
BOOST_TEST(!resp->value().empty());
std::clog << "Event> " << resp->value().front().value << std::endl;
resp->value().clear();
conn2->async_receive(*resp, *this);
conn2->cancel(operation::run);
conn2->cancel(operation::receive);
conn2->cancel(operation::reconnection);
return;
}
}
};
};
@@ -73,48 +74,51 @@ BOOST_AUTO_TEST_CASE(check_health)
{
net::io_context ioc;
connection conn{ioc};
connection conn1{ioc};
conn1.cancel(operation::reconnection);
request req1;
req1.push("CLIENT", "PAUSE", "10000", "ALL");
config cfg1;
cfg1.health_check_id = "conn1";
error_code res1;
conn1.async_run(cfg1, {}, [&](auto ec) {
std::cout << "async_run 1 completed: " << ec.message() << std::endl;
res1 = ec;
});
//--------------------------------
// It looks like client pause does not work for clients that are
// sending MONITOR. I will therefore open a second connection.
connection conn2{ioc};
std::string const msg = "test-check-health";
bool seen = false;
async_check_health(conn, msg, interval, [&](auto ec) {
BOOST_TEST(!ec);
std::cout << "async_check_health: completed." << std::endl;
seen = true;
});
request req;
req.push("HELLO", 3);
req.push("MONITOR");
conn2.async_exec(req, ignore, [](auto ec, auto) {
std::cout << "A" << std::endl;
BOOST_TEST(!ec);
config cfg2;
cfg2.health_check_id = "conn2";
error_code res2;
conn2.async_run(cfg2, {}, [&](auto ec){
std::cout << "async_run 2 completed: " << ec.message() << std::endl;
res2 = ec;
});
request req2;
req2.push("HELLO", "3");
req2.push("CLIENT", "PAUSE", "3000", "ALL");
req2.push("MONITOR");
generic_response resp2;
generic_response resp;
push_callback{&conn, &conn2, &resp, &req2}(); // Starts reading pushes.
async_run(conn, address{}, 10s, 10s, [](auto ec){
std::cout << "B" << std::endl;
BOOST_TEST(!!ec);
conn2.async_exec(req2, ignore, [](auto ec, auto) {
std::cout << "async_exec: " << std::endl;
BOOST_TEST(!ec);
});
async_run(conn2, address{}, 10s, 10s, [](auto ec){
std::cout << "C" << std::endl;
BOOST_TEST(!!ec);
});
//--------------------------------
push_callback{&conn1, &conn2, &resp2, &req1}(); // Starts reading pushes.
ioc.run();
BOOST_TEST(seen);
BOOST_TEST(!!res1);
BOOST_TEST(!!res2);
}

View File

@@ -4,7 +4,7 @@
* accompanying file LICENSE.txt)
*/
#include <boost/redis/run.hpp>
#include <boost/redis/connection.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/detached.hpp>
#include <boost/system/errc.hpp>
@@ -24,10 +24,9 @@ using boost::redis::request;
using boost::redis::response;
using boost::redis::ignore;
using boost::redis::ignore_t;
using boost::redis::async_run;
using boost::redis::address;
using connection = boost::asio::use_awaitable_t<>::as_default_on_t<boost::redis::connection>;
using namespace std::chrono_literals;
using boost::redis::logger;
using boost::redis::config;
using boost::redis::connection;
auto push_consumer(std::shared_ptr<connection> conn, int expected) -> net::awaitable<void>
{
@@ -38,10 +37,7 @@ auto push_consumer(std::shared_ptr<connection> conn, int expected) -> net::await
break;
}
request req;
req.push("HELLO", 3);
req.push("QUIT");
co_await conn->async_exec(req, ignore);
conn->cancel();
}
auto echo_session(std::shared_ptr<connection> conn, std::string id, int n) -> net::awaitable<void>
@@ -81,8 +77,7 @@ auto async_echo_stress() -> net::awaitable<void>
net::co_spawn(ex, echo_session(conn, std::to_string(i), msgs), net::detached);
address addr;
co_await async_run(*conn, addr);
run(conn);
}
BOOST_AUTO_TEST_CASE(echo_stress)

View File

@@ -4,8 +4,7 @@
* accompanying file LICENSE.txt)
*/
#include <boost/redis/run.hpp>
#include <boost/redis/address.hpp>
#include <boost/redis/connection.hpp>
#include <boost/system/errc.hpp>
#define BOOST_TEST_MODULE conn-exec
#include <boost/test/included/unit_test.hpp>
@@ -19,16 +18,14 @@
// container.
namespace net = boost::asio;
using error_code = boost::system::error_code;
using connection = boost::redis::connection;
using boost::redis::connection;
using boost::redis::request;
using boost::redis::response;
using boost::redis::ignore;
using boost::redis::ignore_t;
using boost::redis::async_run;
using boost::redis::address;
using namespace std::chrono_literals;
using boost::redis::operation;
// Sends three requests where one of them has a hello with a priority
// set, which means it should be executed first.
BOOST_AUTO_TEST_CASE(hello_priority)
{
request req1;
@@ -38,7 +35,6 @@ BOOST_AUTO_TEST_CASE(hello_priority)
req2.get_config().hello_with_priority = false;
req2.push("HELLO", 3);
req2.push("PING", "req2");
req2.push("QUIT");
request req3;
req3.get_config().hello_with_priority = true;
@@ -47,60 +43,63 @@ BOOST_AUTO_TEST_CASE(hello_priority)
net::io_context ioc;
connection conn{ioc};
auto conn = std::make_shared<connection>(ioc);
bool seen1 = false;
bool seen2 = false;
bool seen3 = false;
conn.async_exec(req1, ignore, [&](auto ec, auto){
std::cout << "bbb" << std::endl;
conn->async_exec(req1, ignore, [&](auto ec, auto){
// Second callback to the called.
std::cout << "req1" << std::endl;
BOOST_TEST(!ec);
BOOST_TEST(!seen2);
BOOST_TEST(seen3);
seen1 = true;
});
conn.async_exec(req2, ignore, [&](auto ec, auto){
std::cout << "ccc" << std::endl;
conn->async_exec(req2, ignore, [&](auto ec, auto){
// Last callback to the called.
std::cout << "req2" << std::endl;
BOOST_TEST(!ec);
BOOST_TEST(seen1);
BOOST_TEST(seen3);
seen2 = true;
conn->cancel(operation::run);
conn->cancel(operation::reconnection);
});
conn.async_exec(req3, ignore, [&](auto ec, auto){
std::cout << "ddd" << std::endl;
conn->async_exec(req3, ignore, [&](auto ec, auto){
// Callback that will be called first.
std::cout << "req3" << std::endl;
BOOST_TEST(!ec);
BOOST_TEST(!seen1);
BOOST_TEST(!seen2);
seen3 = true;
});
async_run(conn, address{}, 10s, 10s, [](auto ec){
BOOST_TEST(!ec);
});
run(conn);
ioc.run();
}
// Tries to receive a string in an int and gets an error.
BOOST_AUTO_TEST_CASE(wrong_response_data_type)
{
request req;
req.push("HELLO", 3);
req.push("QUIT");
req.push("PING");
// Wrong data type.
response<ignore_t, int> resp;
response<int> resp;
net::io_context ioc;
connection conn{ioc};
auto conn = std::make_shared<connection>(ioc);
conn.async_exec(req, resp, [](auto ec, auto){
conn->async_exec(req, resp, [conn](auto ec, auto){
BOOST_CHECK_EQUAL(ec, boost::redis::error::not_a_number);
});
async_run(conn, address{}, 10s, 10s, [](auto ec){
BOOST_CHECK_EQUAL(ec, boost::asio::error::basic_errors::operation_aborted);
conn->cancel(operation::reconnection);
});
run(conn);
ioc.run();
}
@@ -108,13 +107,13 @@ BOOST_AUTO_TEST_CASE(cancel_request_if_not_connected)
{
request req;
req.get_config().cancel_if_not_connected = true;
req.push("HELLO", 3);
req.push("PING");
net::io_context ioc;
auto conn = std::make_shared<connection>(ioc);
conn->async_exec(req, ignore, [](auto ec, auto){
conn->async_exec(req, ignore, [conn](auto ec, auto){
BOOST_CHECK_EQUAL(ec, boost::redis::error::not_connected);
conn->cancel();
});
ioc.run();

View File

@@ -4,7 +4,7 @@
* accompanying file LICENSE.txt)
*/
#include <boost/redis/run.hpp>
#include <boost/redis/connection.hpp>
#include <boost/system/errc.hpp>
#define BOOST_TEST_MODULE conn-exec-cancel
#include <boost/test/included/unit_test.hpp>
@@ -16,88 +16,37 @@
#ifdef BOOST_ASIO_HAS_CO_AWAIT
#include <boost/asio/experimental/awaitable_operators.hpp>
// NOTE1: Sends hello separately. I have observed that if hello and
// NOTE1: I have observed that if hello and
// blpop are sent toguether, Redis will send the response of hello
// right away, not waiting for blpop. That is why we have to send it
// separately here.
// right away, not waiting for blpop.
namespace net = boost::asio;
using error_code = boost::system::error_code;
using namespace net::experimental::awaitable_operators;
using boost::redis::operation;
using boost::redis::error;
using boost::redis::request;
using boost::redis::response;
using boost::redis::generic_response;
using boost::redis::ignore;
using boost::redis::ignore_t;
using boost::redis::async_run;
using boost::redis::address;
using connection = boost::asio::use_awaitable_t<>::as_default_on_t<boost::redis::connection>;
using boost::redis::config;
using boost::redis::logger;
using boost::redis::connection;
using namespace std::chrono_literals;
auto async_ignore_explicit_cancel_of_req_written() -> net::awaitable<void>
{
auto ex = co_await net::this_coro::executor;
generic_response gresp;
auto conn = std::make_shared<connection>(ex);
async_run(*conn, address{}, 10s, 10s, [conn](auto ec) {
std::cout << "async_run: " << ec.message() << std::endl;
BOOST_TEST(!ec);
});
net::steady_timer st{ex};
st.expires_after(std::chrono::seconds{1});
// See NOTE1.
request req0;
req0.push("HELLO", 3);
co_await conn->async_exec(req0, gresp, net::use_awaitable);
request req1;
req1.push("BLPOP", "any", 3);
// Should not be canceled.
bool seen = false;
conn->async_exec(req1, gresp, [&](auto ec, auto) mutable{
std::cout << "async_exec (1): " << ec.message() << std::endl;
BOOST_TEST(!ec);
seen = true;
});
// Will complete while BLPOP is pending.
boost::system::error_code ec1;
co_await st.async_wait(net::redirect_error(net::use_awaitable, ec1));
conn->cancel(operation::exec);
BOOST_TEST(!ec1);
request req3;
req3.push("QUIT");
// Test whether the connection remains usable after a call to
// cancel(exec).
co_await conn->async_exec(req3, gresp, net::redirect_error(net::use_awaitable, ec1));
BOOST_TEST(!ec1);
BOOST_TEST(seen);
}
auto ignore_implicit_cancel_of_req_written() -> net::awaitable<void>
auto implicit_cancel_of_req_written() -> net::awaitable<void>
{
auto ex = co_await net::this_coro::executor;
auto conn = std::make_shared<connection>(ex);
// Calls async_run separately from the group of ops below to avoid
// having it canceled when the timer fires.
async_run(*conn, address{}, 10s, 10s, [conn](auto ec) {
BOOST_CHECK_EQUAL(ec, net::error::basic_errors::operation_aborted);
});
config cfg;
cfg.health_check_interval = std::chrono::seconds{0};
run(conn, cfg);
// See NOTE1.
request req0;
req0.push("HELLO", 3);
req0.push("PING");
co_await conn->async_exec(req0, ignore, net::use_awaitable);
// Will be cancelled after it has been written but before the
@@ -108,33 +57,33 @@ auto ignore_implicit_cancel_of_req_written() -> net::awaitable<void>
net::steady_timer st{ex};
st.expires_after(std::chrono::seconds{1});
// Achieves implicit cancellation when the timer fires.
boost::system::error_code ec1, ec2;
co_await (
conn->async_exec(req1, ignore, redir(ec1)) ||
st.async_wait(redir(ec2))
);
BOOST_CHECK_EQUAL(ec1, net::error::basic_errors::operation_aborted);
BOOST_TEST(!ec2);
}
conn->cancel();
BOOST_AUTO_TEST_CASE(test_ignore_explicit_cancel_of_req_written)
{
start(async_ignore_explicit_cancel_of_req_written());
// I have observed this produces terminal cancellation so it can't
// be ignored, an error is expected.
BOOST_CHECK_EQUAL(ec1, net::error::operation_aborted);
BOOST_TEST(!ec2);
}
BOOST_AUTO_TEST_CASE(test_ignore_implicit_cancel_of_req_written)
{
start(ignore_implicit_cancel_of_req_written());
start(implicit_cancel_of_req_written());
}
BOOST_AUTO_TEST_CASE(test_cancel_of_req_written_on_run_canceled)
{
net::io_context ioc;
connection conn{ioc};
auto conn = std::make_shared<connection>(ioc);
request req0;
req0.push("HELLO", 3);
req0.push("PING");
// Sends a request that will be blocked forever, so we can test
// canceling it while waiting for a response.
@@ -145,26 +94,27 @@ BOOST_AUTO_TEST_CASE(test_cancel_of_req_written_on_run_canceled)
auto c1 = [&](auto ec, auto)
{
BOOST_CHECK_EQUAL(ec, net::error::basic_errors::operation_aborted);
BOOST_CHECK_EQUAL(ec, net::error::operation_aborted);
};
auto c0 = [&](auto ec, auto)
{
BOOST_TEST(!ec);
conn.async_exec(req1, ignore, c1);
conn->async_exec(req1, ignore, c1);
};
conn.async_exec(req0, ignore, c0);
conn->async_exec(req0, ignore, c0);
async_run(conn, address{}, 10s, 10s, [](auto ec){
BOOST_CHECK_EQUAL(ec, net::error::basic_errors::operation_aborted);
});
config cfg;
cfg.health_check_interval = std::chrono::seconds{5};
run(conn);
net::steady_timer st{ioc};
st.expires_after(std::chrono::seconds{1});
st.async_wait([&](auto ec){
BOOST_TEST(!ec);
conn.cancel(operation::run);
conn->cancel(operation::run);
conn->cancel(operation::reconnection);
});
ioc.run();

View File

@@ -0,0 +1,96 @@
/* 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 <boost/redis/connection.hpp>
#include <boost/system/errc.hpp>
#define BOOST_TEST_MODULE conn-exec-cancel
#include <boost/test/included/unit_test.hpp>
#include "common.hpp"
#include "../examples/start.hpp"
#include <iostream>
#include <boost/redis/src.hpp>
#ifdef BOOST_ASIO_HAS_CO_AWAIT
#include <boost/asio/experimental/awaitable_operators.hpp>
// NOTE1: Sends hello separately. I have observed that if hello and
// blpop are sent toguether, Redis will send the response of hello
// right away, not waiting for blpop. That is why we have to send it
// separately.
namespace net = boost::asio;
using error_code = boost::system::error_code;
using namespace net::experimental::awaitable_operators;
using boost::redis::operation;
using boost::redis::request;
using boost::redis::response;
using boost::redis::generic_response;
using boost::redis::ignore;
using boost::redis::ignore_t;
using boost::redis::config;
using boost::redis::logger;
using boost::redis::connection;
using namespace std::chrono_literals;
auto async_ignore_explicit_cancel_of_req_written() -> net::awaitable<void>
{
auto ex = co_await net::this_coro::executor;
generic_response gresp;
auto conn = std::make_shared<connection>(ex);
run(conn);
net::steady_timer st{ex};
st.expires_after(std::chrono::seconds{1});
// See NOTE1.
request req0;
req0.push("PING", "async_ignore_explicit_cancel_of_req_written");
co_await conn->async_exec(req0, gresp, net::use_awaitable);
request req1;
req1.push("BLPOP", "any", 3);
bool seen = false;
conn->async_exec(req1, gresp, [&](auto ec, auto) mutable{
// No error should occur since the cancelation should be
// ignored.
std::cout << "async_exec (1): " << ec.message() << std::endl;
BOOST_TEST(!ec);
seen = true;
});
// Will complete while BLPOP is pending.
boost::system::error_code ec1;
co_await st.async_wait(net::redirect_error(net::use_awaitable, ec1));
conn->cancel(operation::exec);
BOOST_TEST(!ec1);
request req3;
req3.push("PING");
// Test whether the connection remains usable after a call to
// cancel(exec).
co_await conn->async_exec(req3, gresp, net::redirect_error(net::use_awaitable, ec1));
conn->cancel();
BOOST_TEST(!ec1);
BOOST_TEST(seen);
}
BOOST_AUTO_TEST_CASE(test_ignore_explicit_cancel_of_req_written)
{
start(async_ignore_explicit_cancel_of_req_written());
}
#else
BOOST_AUTO_TEST_CASE(dummy)
{
BOOST_TEST(true);
}
#endif

View File

@@ -4,7 +4,8 @@
* accompanying file LICENSE.txt)
*/
#include <boost/redis/run.hpp>
#include <boost/redis/connection.hpp>
#include <boost/redis/logger.hpp>
#include <boost/system/errc.hpp>
#define BOOST_TEST_MODULE conn-exec-error
#include <boost/test/included/unit_test.hpp>
@@ -23,8 +24,9 @@ using boost::redis::generic_response;
using boost::redis::ignore;
using boost::redis::ignore_t;
using boost::redis::error;
using boost::redis::async_run;
using boost::redis::address;
using boost::redis::logger;
using boost::redis::operation;
using redis::config;
using namespace std::chrono_literals;
BOOST_AUTO_TEST_CASE(no_ignore_error)
@@ -36,16 +38,16 @@ BOOST_AUTO_TEST_CASE(no_ignore_error)
net::io_context ioc;
connection conn{ioc};
auto conn = std::make_shared<connection>(ioc);
conn.async_exec(req, ignore, [&](auto ec, auto){
conn->async_exec(req, ignore, [&](auto ec, auto){
BOOST_CHECK_EQUAL(ec, error::resp3_simple_error);
conn.cancel(redis::operation::run);
});
async_run(conn, address{}, 10s, 10s, [](auto ec){
BOOST_CHECK_EQUAL(ec, boost::asio::error::basic_errors::operation_aborted);
conn->cancel(operation::run);
conn->cancel(operation::reconnection);
});
run(conn);
ioc.run();
}
@@ -62,10 +64,10 @@ BOOST_AUTO_TEST_CASE(has_diagnostic)
net::io_context ioc;
connection conn{ioc};
auto conn = std::make_shared<connection>(ioc);
response<std::string, std::string> resp;
conn.async_exec(req, resp, [&](auto ec, auto){
conn->async_exec(req, resp, [&](auto ec, auto){
BOOST_TEST(!ec);
// HELLO
@@ -79,12 +81,12 @@ BOOST_AUTO_TEST_CASE(has_diagnostic)
BOOST_TEST(std::get<1>(resp).has_value());
BOOST_CHECK_EQUAL(std::get<1>(resp).value(), "Barra do Una");
conn.cancel(redis::operation::run);
});
async_run(conn, address{}, 10s, 10s, [](auto ec){
BOOST_CHECK_EQUAL(ec, boost::asio::error::basic_errors::operation_aborted);
conn->cancel(operation::run);
conn->cancel(operation::reconnection);
});
run(conn);
ioc.run();
}
@@ -104,14 +106,15 @@ BOOST_AUTO_TEST_CASE(resp3_error_in_cmd_pipeline)
response<std::string> resp2;
net::io_context ioc;
connection conn{ioc};
auto conn = std::make_shared<connection>(ioc);
auto c2 = [&](auto ec, auto)
{
BOOST_TEST(!ec);
BOOST_TEST(std::get<0>(resp2).has_value());
BOOST_CHECK_EQUAL(std::get<0>(resp2).value(), "req2-msg1");
conn.cancel(redis::operation::run);
conn->cancel(operation::run);
conn->cancel(operation::reconnection);
};
auto c1 = [&](auto ec, auto)
@@ -126,13 +129,11 @@ BOOST_AUTO_TEST_CASE(resp3_error_in_cmd_pipeline)
BOOST_TEST(std::get<3>(resp1).has_value());
BOOST_CHECK_EQUAL(std::get<3>(resp1).value(), "req1-msg3");
conn.async_exec(req2, resp2, c2);
conn->async_exec(req2, resp2, c2);
};
conn.async_exec(req1, resp1, c1);
async_run(conn, address{}, 10s, 10s, [](auto ec){
BOOST_CHECK_EQUAL(ec, boost::asio::error::basic_errors::operation_aborted);
});
conn->async_exec(req1, resp1, c1);
run(conn);
ioc.run();
}
@@ -161,9 +162,9 @@ BOOST_AUTO_TEST_CASE(error_in_transaction)
net::io_context ioc;
connection conn{ioc};
auto conn = std::make_shared<connection>(ioc);
conn.async_exec(req, resp, [&](auto ec, auto){
conn->async_exec(req, resp, [&](auto ec, auto){
BOOST_TEST(!ec);
BOOST_TEST(std::get<0>(resp).has_value());
@@ -191,12 +192,12 @@ BOOST_AUTO_TEST_CASE(error_in_transaction)
BOOST_TEST(std::get<6>(resp).has_value());
BOOST_CHECK_EQUAL(std::get<6>(resp).value(), "PONG");
conn.cancel(redis::operation::run);
});
async_run(conn, address{}, 10s, 10s, [](auto ec){
BOOST_CHECK_EQUAL(ec, boost::asio::error::basic_errors::operation_aborted);
conn->cancel(operation::run);
conn->cancel(operation::reconnection);
});
run(conn);
ioc.run();
}
@@ -213,7 +214,7 @@ BOOST_AUTO_TEST_CASE(subscriber_wrong_syntax)
req2.push("SUBSCRIBE"); // Wrong command synthax.
net::io_context ioc;
connection conn{ioc};
auto conn = std::make_shared<connection>(ioc);
auto c2 = [&](auto ec, auto)
{
@@ -225,10 +226,10 @@ BOOST_AUTO_TEST_CASE(subscriber_wrong_syntax)
{
std::cout << "async_exec: hello" << std::endl;
BOOST_TEST(!ec);
conn.async_exec(req2, ignore, c2);
conn->async_exec(req2, ignore, c2);
};
conn.async_exec(req1, ignore, c1);
conn->async_exec(req1, ignore, c1);
generic_response gresp;
auto c3 = [&](auto ec, auto)
@@ -239,14 +240,13 @@ BOOST_AUTO_TEST_CASE(subscriber_wrong_syntax)
BOOST_CHECK_EQUAL(gresp.error().data_type, resp3::type::simple_error);
BOOST_TEST(!std::empty(gresp.error().diagnostic));
std::cout << gresp.error().diagnostic << std::endl;
conn.cancel(redis::operation::run);
conn->cancel(operation::run);
conn->cancel(operation::reconnection);
};
conn.async_receive(gresp, c3);
async_run(conn, address{}, 10s, 10s, [](auto ec){
std::cout << "async_run" << std::endl;
BOOST_CHECK_EQUAL(ec, boost::asio::error::basic_errors::operation_aborted);
});
conn->async_receive(gresp, c3);
run(conn);
ioc.run();
}

View File

@@ -4,19 +4,17 @@
* accompanying file LICENSE.txt)
*/
#include <iostream>
#include <boost/redis/connection.hpp>
#include <boost/asio/awaitable.hpp>
#include <boost/system/errc.hpp>
#define BOOST_TEST_MODULE conn-exec-retry
#include <boost/test/included/unit_test.hpp>
#include <boost/redis/run.hpp>
#include <boost/redis/address.hpp>
#include <boost/redis/src.hpp>
#include <iostream>
#include "common.hpp"
#include <boost/redis/src.hpp>
namespace net = boost::asio;
using error_code = boost::system::error_code;
using connection = boost::redis::connection;
@@ -24,8 +22,8 @@ using boost::redis::operation;
using boost::redis::request;
using boost::redis::response;
using boost::redis::ignore;
using boost::redis::async_run;
using boost::redis::address;
using boost::redis::logger;
using boost::redis::config;
using namespace std::chrono_literals;
BOOST_AUTO_TEST_CASE(request_retry_false)
@@ -44,7 +42,7 @@ BOOST_AUTO_TEST_CASE(request_retry_false)
req2.push("PING");
net::io_context ioc;
connection conn{ioc};
auto conn = std::make_shared<connection>(ioc);
net::steady_timer st{ioc};
st.expires_after(std::chrono::seconds{1});
@@ -54,28 +52,33 @@ BOOST_AUTO_TEST_CASE(request_retry_false)
// although it has cancel_on_connection_lost = false. The reason
// being it has already been written so
// cancel_on_connection_lost does not apply.
conn.cancel(operation::run);
conn->cancel(operation::run);
conn->cancel(operation::reconnection);
std::cout << "async_wait" << std::endl;
});
auto c2 = [&](auto ec, auto){
std::cout << "c2" << std::endl;
BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled);
};
auto c1 = [&](auto ec, auto){
std::cout << "c1" << std::endl;
BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled);
};
auto c0 = [&](auto ec, auto){
std::cout << "c0" << std::endl;
BOOST_TEST(!ec);
conn.async_exec(req1, ignore, c1);
conn.async_exec(req2, ignore, c2);
conn->async_exec(req1, ignore, c1);
conn->async_exec(req2, ignore, c2);
};
conn.async_exec(req0, ignore, c0);
conn->async_exec(req0, ignore, c0);
async_run(conn, address{}, 10s, 10s, [](auto ec){
BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled);
});
config cfg;
cfg.health_check_interval = 5s;
run(conn);
ioc.run();
}
@@ -101,25 +104,27 @@ BOOST_AUTO_TEST_CASE(request_retry_true)
req3.push("QUIT");
net::io_context ioc;
connection conn{ioc};
auto conn = std::make_shared<connection>(ioc);
net::steady_timer st{ioc};
st.expires_after(std::chrono::seconds{1});
st.async_wait([&](auto){
// Cancels the request before receiving the response. This
// should cause the thrid request to not complete with error
// since it has cancel_if_unresponded = true and cancellation commes
// after it was written.
conn.cancel(boost::redis::operation::run);
// since it has cancel_if_unresponded = true and cancellation
// comes after it was written.
conn->cancel(operation::run);
});
auto c3 = [&](auto ec, auto){
std::cout << "c3: " << ec.message() << std::endl;
BOOST_TEST(!ec);
conn->cancel();
};
auto c2 = [&](auto ec, auto){
BOOST_TEST(!ec);
conn.async_exec(req3, ignore, c3);
conn->async_exec(req3, ignore, c3);
};
auto c1 = [](auto ec, auto){
@@ -128,22 +133,17 @@ BOOST_AUTO_TEST_CASE(request_retry_true)
auto c0 = [&](auto ec, auto){
BOOST_TEST(!ec);
conn.async_exec(req1, ignore, c1);
conn.async_exec(req2, ignore, c2);
conn->async_exec(req1, ignore, c1);
conn->async_exec(req2, ignore, c2);
};
conn.async_exec(req0, ignore, c0);
conn->async_exec(req0, ignore, c0);
async_run(conn, address{}, 10s, 10s, [&](auto ec){
// The first cacellation.
BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled);
conn.reset_stream();
// Reconnects and runs again to test req3.
async_run(conn, address{}, 10s, 10s, [](auto ec){
std::cout << ec.message() << std::endl;
BOOST_TEST(!ec);
});
config cfg;
cfg.health_check_interval = 5s;
conn->async_run(cfg, {}, [&](auto ec){
std::cout << ec.message() << std::endl;
BOOST_TEST(!!ec);
});
ioc.run();

View File

@@ -4,7 +4,8 @@
* accompanying file LICENSE.txt)
*/
#include <boost/redis/run.hpp>
#include <boost/redis/connection.hpp>
#include <boost/redis/logger.hpp>
#include <boost/system/errc.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/co_spawn.hpp>
@@ -16,6 +17,7 @@
#include <boost/redis/src.hpp>
namespace net = boost::asio;
namespace redis = boost::redis;
using boost::redis::operation;
using connection = boost::redis::connection;
@@ -25,8 +27,8 @@ using boost::redis::request;
using boost::redis::response;
using boost::redis::ignore;
using boost::redis::ignore_t;
using boost::redis::async_run;
using boost::redis::address;
using redis::config;
using boost::redis::logger;
using namespace std::chrono_literals;
BOOST_AUTO_TEST_CASE(receives_push_waiting_resps)
@@ -44,37 +46,35 @@ BOOST_AUTO_TEST_CASE(receives_push_waiting_resps)
net::io_context ioc;
connection conn{ioc};
auto conn = std::make_shared<connection>(ioc);
auto c3 =[](auto ec, auto...)
{
BOOST_TEST(!!ec);
};
auto c2 =[&](auto ec, auto...)
auto c2 =[&, conn](auto ec, auto...)
{
BOOST_TEST(!ec);
conn.async_exec(req3, ignore, c3);
conn->async_exec(req3, ignore, c3);
};
auto c1 =[&](auto ec, auto...)
auto c1 =[&, conn](auto ec, auto...)
{
BOOST_TEST(!ec);
conn.async_exec(req2, ignore, c2);
conn->async_exec(req2, ignore, c2);
};
conn.async_exec(req1, ignore, c1);
conn->async_exec(req1, ignore, c1);
async_run(conn, address{}, 10s, 10s, [&](auto ec){
BOOST_TEST(!ec);
conn.cancel(operation::receive);
});
run(conn, {}, {});
bool push_received = false;
conn.async_receive(ignore, [&](auto ec, auto){
conn->async_receive(ignore, [&, conn](auto ec, auto){
std::cout << "async_receive" << std::endl;
BOOST_TEST(!ec);
conn.cancel(operation::run);
conn->cancel(operation::run);
conn->cancel(operation::reconnection);
push_received = true;
});
@@ -86,27 +86,25 @@ BOOST_AUTO_TEST_CASE(receives_push_waiting_resps)
BOOST_AUTO_TEST_CASE(push_received1)
{
net::io_context ioc;
connection conn{ioc};
auto conn = std::make_shared<connection>(ioc);
request req;
req.push("HELLO", 3);
//req.push("HELLO", 3);
req.push("SUBSCRIBE", "channel");
conn.async_exec(req, ignore, [](auto ec, auto){
conn->async_exec(req, ignore, [conn](auto ec, auto){
std::cout << "async_exec" << std::endl;
BOOST_TEST(!ec);
});
async_run(conn, address{}, 10s, 10s, [&](auto ec){
std::cout << "async_run: " << ec.message() << std::endl;
conn.cancel(operation::receive);
});
run(conn);
bool push_received = false;
conn.async_receive(ignore, [&](auto ec, auto){
conn->async_receive(ignore, [&, conn](auto ec, auto){
std::cout << "async_receive" << std::endl;
BOOST_TEST(!ec);
conn.cancel(operation::run);
conn->cancel(operation::run);
conn->cancel(operation::reconnection);
push_received = true;
});
@@ -118,7 +116,7 @@ BOOST_AUTO_TEST_CASE(push_received1)
BOOST_AUTO_TEST_CASE(push_filtered_out)
{
net::io_context ioc;
connection conn{ioc};
auto conn = std::make_shared<connection>(ioc);
request req;
req.push("HELLO", 3);
@@ -127,17 +125,16 @@ BOOST_AUTO_TEST_CASE(push_filtered_out)
req.push("QUIT");
response<ignore_t, std::string, std::string> resp;
conn.async_exec(req, resp, [](auto ec, auto){
conn->async_exec(req, resp, [conn](auto ec, auto){
BOOST_TEST(!ec);
});
conn.async_receive(ignore, [](auto ec, auto){
conn->async_receive(ignore, [conn](auto ec, auto){
BOOST_TEST(!ec);
conn->cancel(operation::reconnection);
});
async_run(conn, address{}, 10s, 10s, [&](auto ec){
BOOST_TEST(!ec);
});
run(conn);
ioc.run();
@@ -146,15 +143,16 @@ BOOST_AUTO_TEST_CASE(push_filtered_out)
}
#ifdef BOOST_ASIO_HAS_CO_AWAIT
net::awaitable<void> push_consumer1(connection& conn, bool& push_received)
net::awaitable<void>
push_consumer1(std::shared_ptr<connection> conn, bool& push_received)
{
{
auto [ec, ev] = co_await conn.async_receive(ignore, as_tuple(net::use_awaitable));
auto [ec, ev] = co_await conn->async_receive(ignore, as_tuple(net::use_awaitable));
BOOST_TEST(!ec);
}
{
auto [ec, ev] = co_await conn.async_receive(ignore, as_tuple(net::use_awaitable));
auto [ec, ev] = co_await conn->async_receive(ignore, as_tuple(net::use_awaitable));
BOOST_CHECK_EQUAL(ec, net::experimental::channel_errc::channel_cancelled);
}
@@ -185,7 +183,7 @@ auto boost_redis_adapt(response_error_tag&)
BOOST_AUTO_TEST_CASE(test_push_adapter)
{
net::io_context ioc;
connection conn{ioc};
auto conn = std::make_shared<connection>(ioc);
request req;
req.push("HELLO", 3);
@@ -193,17 +191,16 @@ BOOST_AUTO_TEST_CASE(test_push_adapter)
req.push("SUBSCRIBE", "channel");
req.push("PING");
conn.async_receive(error_tag_obj, [](auto ec, auto) {
conn->async_receive(error_tag_obj, [conn](auto ec, auto) {
BOOST_CHECK_EQUAL(ec, boost::redis::error::incompatible_size);
conn->cancel(operation::reconnection);
});
conn.async_exec(req, ignore, [](auto ec, auto){
conn->async_exec(req, ignore, [](auto ec, auto){
BOOST_CHECK_EQUAL(ec, net::experimental::error::channel_errors::channel_cancelled);
});
async_run(conn, address{}, 10s, 10s, [](auto ec){
BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled);
});
run(conn);
ioc.run();
@@ -211,10 +208,10 @@ BOOST_AUTO_TEST_CASE(test_push_adapter)
// reconnection is possible after an error.
}
net::awaitable<void> push_consumer3(connection& conn)
net::awaitable<void> push_consumer3(std::shared_ptr<connection> conn)
{
for (;;) {
co_await conn.async_receive(ignore, net::use_awaitable);
co_await conn->async_receive(ignore, net::use_awaitable);
}
}
@@ -237,75 +234,73 @@ BOOST_AUTO_TEST_CASE(many_subscribers)
req3.push("QUIT");
net::io_context ioc;
connection conn{ioc};
auto conn = std::make_shared<connection>(ioc);
auto c11 =[&](auto ec, auto...)
{
std::cout << "quit sent" << std::endl;
conn->cancel(operation::reconnection);
BOOST_TEST(!ec);
};
auto c10 =[&](auto ec, auto...)
{
BOOST_TEST(!ec);
conn.async_exec(req3, ignore, c11);
conn->async_exec(req3, ignore, c11);
};
auto c9 =[&](auto ec, auto...)
{
BOOST_TEST(!ec);
conn.async_exec(req2, ignore, c10);
conn->async_exec(req2, ignore, c10);
};
auto c8 =[&](auto ec, auto...)
{
BOOST_TEST(!ec);
conn.async_exec(req1, ignore, c9);
conn->async_exec(req1, ignore, c9);
};
auto c7 =[&](auto ec, auto...)
{
BOOST_TEST(!ec);
conn.async_exec(req2, ignore, c8);
conn->async_exec(req2, ignore, c8);
};
auto c6 =[&](auto ec, auto...)
{
BOOST_TEST(!ec);
conn.async_exec(req2, ignore, c7);
conn->async_exec(req2, ignore, c7);
};
auto c5 =[&](auto ec, auto...)
{
BOOST_TEST(!ec);
conn.async_exec(req1, ignore, c6);
conn->async_exec(req1, ignore, c6);
};
auto c4 =[&](auto ec, auto...)
{
BOOST_TEST(!ec);
conn.async_exec(req2, ignore, c5);
conn->async_exec(req2, ignore, c5);
};
auto c3 =[&](auto ec, auto...)
{
BOOST_TEST(!ec);
conn.async_exec(req1, ignore, c4);
conn->async_exec(req1, ignore, c4);
};
auto c2 =[&](auto ec, auto...)
{
BOOST_TEST(!ec);
conn.async_exec(req2, ignore, c3);
conn->async_exec(req2, ignore, c3);
};
auto c1 =[&](auto ec, auto...)
{
BOOST_TEST(!ec);
conn.async_exec(req2, ignore, c2);
conn->async_exec(req2, ignore, c2);
};
auto c0 =[&](auto ec, auto...)
{
BOOST_TEST(!ec);
conn.async_exec(req1, ignore, c1);
conn->async_exec(req1, ignore, c1);
};
conn.async_exec(req0, ignore, c0);
conn->async_exec(req0, ignore, c0);
async_run(conn, address{}, 10s, 10s, [&](auto ec){
BOOST_TEST(!ec);
conn.cancel(operation::receive);
});
run(conn, {}, {});
net::co_spawn(ioc.get_executor(), push_consumer3(conn), net::detached);
ioc.run();

View File

@@ -4,53 +4,53 @@
* accompanying file LICENSE.txt)
*/
#include <boost/redis/run.hpp>
#include <boost/redis/connection.hpp>
#include <boost/system/errc.hpp>
#define BOOST_TEST_MODULE conn-quit
#include <boost/test/included/unit_test.hpp>
#include "common.hpp"
#include <iostream>
// TODO: Move this to a lib.
#include <boost/redis/src.hpp>
namespace net = boost::asio;
using connection = boost::redis::connection;
using error_code = boost::system::error_code;
using operation = boost::redis::operation;
using boost::redis::connection;
using boost::system::error_code;
using boost::redis::operation;
using boost::redis::request;
using boost::redis::response;
using boost::redis::ignore;
using boost::redis::async_run;
using boost::redis::address;
using boost::redis::config;
using namespace std::chrono_literals;
BOOST_AUTO_TEST_CASE(test_quit1)
BOOST_AUTO_TEST_CASE(test_eof_no_error)
{
request req;
req.get_config().cancel_on_connection_lost = false;
req.push("HELLO", 3);
req.push("QUIT");
net::io_context ioc;
connection conn{ioc};
conn.async_exec(req, ignore, [](auto ec, auto) {
conn.async_exec(req, ignore, [&](auto ec, auto) {
BOOST_TEST(!ec);
conn.cancel(operation::reconnection);
});
async_run(conn, address{}, 10s, 10s, [&](auto ec){
BOOST_TEST(!ec);
conn.async_run({}, {}, [](auto ec){
BOOST_TEST(!!ec);
});
ioc.run();
}
// Test if quit causes async_run to exit.
BOOST_AUTO_TEST_CASE(test_quit2)
BOOST_AUTO_TEST_CASE(test_async_run_exits)
{
net::io_context ioc;
connection conn{ioc};
conn.cancel(operation::reconnection);
request req1;
req1.get_config().cancel_on_connection_lost = false;
@@ -60,37 +60,39 @@ BOOST_AUTO_TEST_CASE(test_quit2)
req2.get_config().cancel_on_connection_lost = false;
req2.push("QUIT");
// Should fail since this request will be sent after quit.
request req3;
// Should cause the request to fail since this request will be sent
// after quit.
req3.get_config().cancel_if_not_connected = true;
req3.push("PING");
auto c3 = [](auto ec, auto)
{
std::cout << "3--> " << ec.message() << std::endl;
std::clog << "c3: " << ec.message() << std::endl;
BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled);
};
auto c2 = [&](auto ec, auto)
{
std::cout << "2--> " << ec.message() << std::endl;
std::clog << "c2: " << ec.message() << std::endl;
BOOST_TEST(!ec);
conn.async_exec(req3, ignore, c3);
};
auto c1 = [&](auto ec, auto)
{
std::cout << "1--> " << ec.message() << std::endl;
std::cout << "c3: " << ec.message() << std::endl;
BOOST_TEST(!ec);
conn.async_exec(req2, ignore, c2);
};
conn.async_exec(req1, ignore, c1);
async_run(conn, address{}, 10s, 10s, [&](auto ec){
BOOST_TEST(!ec);
// The healthy checker should not be the cause of async_run
// completing, so we set a long timeout.
config cfg;
cfg.health_check_interval = 10000s;
conn.async_run({}, {}, [&](auto ec){
BOOST_TEST(!!ec);
});
ioc.run();

View File

@@ -4,7 +4,7 @@
* accompanying file LICENSE.txt)
*/
#include <boost/redis/run.hpp>
#include <boost/redis/connection.hpp>
#include <boost/asio/detached.hpp>
#define BOOST_TEST_MODULE conn-reconnect
#include <boost/test/included/unit_test.hpp>
@@ -17,13 +17,14 @@
#include <boost/asio/experimental/awaitable_operators.hpp>
namespace net = boost::asio;
using error_code = boost::system::error_code;
using boost::system::error_code;
using boost::redis::request;
using boost::redis::response;
using boost::redis::ignore;
using boost::redis::async_run;
using boost::redis::address;
using connection = boost::asio::use_awaitable_t<>::as_default_on_t<boost::redis::connection>;
using boost::redis::config;
using boost::redis::logger;
using boost::redis::operation;
using boost::redis::connection;
using namespace std::chrono_literals;
using namespace boost::asio::experimental::awaitable_operators;
@@ -35,22 +36,20 @@ net::awaitable<void> test_reconnect_impl()
request req;
req.push("QUIT");
connection conn{ex};
auto conn = std::make_shared<connection>(ex);
run(conn);
int i = 0;
address addr;
for (; i < 5; ++i) {
boost::system::error_code ec1, ec2;
co_await (
conn.async_exec(req, ignore, net::redirect_error(net::use_awaitable, ec1)) &&
async_run(conn, addr, 10s, 10s, net::redirect_error(net::use_awaitable, ec2))
);
BOOST_TEST(!ec1);
BOOST_TEST(!ec2);
conn.reset_stream();
error_code ec1, ec2;
config cfg;
logger l;
co_await conn->async_exec(req, ignore, net::redirect_error(net::use_awaitable, ec1));
//BOOST_TEST(!ec);
std::cout << "test_reconnect: " << i << " " << ec2.message() << " " << ec1.message() << std::endl;
}
conn->cancel();
BOOST_CHECK_EQUAL(i, 5);
co_return;
}
@@ -70,44 +69,40 @@ auto async_test_reconnect_timeout() -> net::awaitable<void>
net::steady_timer st{ex};
auto conn = std::make_shared<connection>(ex);
boost::system::error_code ec1, ec2, ec3;
error_code ec1, ec3;
request req1;
req1.get_config().cancel_if_not_connected = false;
req1.get_config().cancel_on_connection_lost = true;
req1.get_config().cancel_if_unresponded = true;
req1.push("HELLO", 3);
req1.push("BLPOP", "any", 0);
st.expires_after(std::chrono::seconds{1});
address addr;
config cfg;
co_await (
conn->async_exec(req1, ignore, redir(ec1)) ||
async_run(*conn, addr, 10s, 10s, redir(ec2)) ||
st.async_wait(redir(ec3))
);
//BOOST_TEST(!ec1);
BOOST_CHECK_EQUAL(ec2, boost::system::errc::errc_t::operation_canceled);
//BOOST_TEST(!ec3);
request req2;
req2.get_config().cancel_if_not_connected = false;
req2.get_config().cancel_on_connection_lost = true;
req2.get_config().cancel_if_unresponded= true;
req2.push("HELLO", 3);
req2.push("QUIT");
st.expires_after(std::chrono::seconds{1});
co_await (
conn->async_exec(req1, ignore, net::redirect_error(net::use_awaitable, ec1)) ||
async_run(*conn, addr, 10s, 10s, net::redirect_error(net::use_awaitable, ec2)) ||
st.async_wait(net::redirect_error(net::use_awaitable, ec3))
);
conn->cancel();
std::cout << "ccc" << std::endl;
BOOST_CHECK_EQUAL(ec1, boost::system::errc::errc_t::operation_canceled);
BOOST_CHECK_EQUAL(ec2, boost::asio::error::basic_errors::operation_aborted);
}
BOOST_AUTO_TEST_CASE(test_reconnect_and_idle)

View File

@@ -4,7 +4,8 @@
* accompanying file LICENSE.txt)
*/
#include <boost/redis/run.hpp>
#include <boost/redis/connection.hpp>
#include <boost/redis/logger.hpp>
#include <boost/asio/detached.hpp>
#include <boost/system/errc.hpp>
#define BOOST_TEST_MODULE conn-run-cancel
@@ -20,14 +21,14 @@
namespace net = boost::asio;
using boost::redis::operation;
using connection = boost::redis::connection;
using error_code = boost::system::error_code;
using boost::redis::config;
using boost::redis::connection;
using boost::system::error_code;
using net::experimental::as_tuple;
using boost::redis::request;
using boost::redis::response;
using boost::redis::ignore;
using boost::redis::async_run;
using boost::redis::address;
using boost::redis::logger;
using namespace std::chrono_literals;
using namespace net::experimental::awaitable_operators;
@@ -38,13 +39,14 @@ auto async_cancel_run_with_timer() -> net::awaitable<void>
connection conn{ex};
net::steady_timer st{ex};
st.expires_after(std::chrono::seconds{1});
st.expires_after(1s);
boost::system::error_code ec1, ec2;
address addr;
co_await (async_run(conn, addr, 10s, 10s, redir(ec1)) || st.async_wait(redir(ec2)));
error_code ec1, ec2;
config cfg;
logger l;
co_await (conn.async_run(cfg, l, redir(ec1)) || st.async_wait(redir(ec2)));
BOOST_CHECK_EQUAL(ec1, boost::asio::error::basic_errors::operation_aborted);
BOOST_CHECK_EQUAL(ec1, boost::asio::error::operation_aborted);
BOOST_TEST(!ec2);
}
@@ -65,10 +67,11 @@ async_check_cancellation_not_missed(int n, std::chrono::milliseconds ms) -> net:
for (auto i = 0; i < n; ++i) {
timer.expires_after(ms);
boost::system::error_code ec1, ec2;
address addr;
co_await (async_run(conn, addr, 10s, 10s, redir(ec1)) || timer.async_wait(redir(ec2)));
BOOST_CHECK_EQUAL(ec1, boost::asio::error::basic_errors::operation_aborted);
error_code ec1, ec2;
config cfg;
logger l;
co_await (conn.async_run(cfg, l, redir(ec1)) || timer.async_wait(redir(ec2)));
BOOST_CHECK_EQUAL(ec1, boost::asio::error::operation_aborted);
std::cout << "Counter: " << i << std::endl;
}
}
@@ -144,28 +147,6 @@ BOOST_AUTO_TEST_CASE(check_implicit_cancel_not_missed_1024)
ioc.run();
}
BOOST_AUTO_TEST_CASE(reset_before_run_completes)
{
net::io_context ioc;
connection conn{ioc};
// Sends a ping just as a means of waiting until we are connected.
request req;
req.push("HELLO", 3);
req.push("PING");
conn.async_exec(req, ignore, [&](auto ec, auto){
BOOST_TEST(!ec);
conn.reset_stream();
});
address addr;
async_run(conn, addr, 10s, 10s, [&](auto ec){
BOOST_CHECK_EQUAL(ec, net::error::operation_aborted);
});
ioc.run();
}
#else
BOOST_AUTO_TEST_CASE(dummy)
{

View File

@@ -4,13 +4,12 @@
* accompanying file LICENSE.txt)
*/
#include <boost/redis/run.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/redis/ssl/connection.hpp>
#define BOOST_TEST_MODULE conn-tls
#include <boost/test/included/unit_test.hpp>
#include <boost/redis/ssl/connection.hpp>
#include <iostream>
#include "common.hpp"
#include <boost/redis/src.hpp>
namespace net = boost::asio;
@@ -18,24 +17,8 @@ namespace net = boost::asio;
using connection = boost::redis::ssl::connection;
using boost::redis::request;
using boost::redis::response;
using boost::redis::ignore_t;
using endpoints = net::ip::tcp::resolver::results_type;
auto
resolve(
std::string const& host = "127.0.0.1",
std::string const& port = "6379") -> endpoints
{
net::io_context ioc;
net::ip::tcp::resolver resv{ioc};
return resv.resolve(host, port);
}
struct endpoint {
std::string host;
std::string port;
};
using boost::redis::config;
using boost::redis::operation;
bool verify_certificate(bool, net::ssl::verify_context&)
{
@@ -45,17 +28,18 @@ bool verify_certificate(bool, net::ssl::verify_context&)
BOOST_AUTO_TEST_CASE(ping)
{
config cfg;
cfg.username = "aedis";
cfg.password = "aedis";
cfg.addr.host = "db.occase.de";
cfg.addr.port = "6380";
std::string const in = "Kabuf";
request req;
req.get_config().cancel_on_connection_lost = true;
req.push("HELLO", 3, "AUTH", "aedis", "aedis");
req.push("PING", in);
req.push("QUIT");
response<ignore_t, std::string, ignore_t> resp;
auto const endpoints = resolve("db.occase.de", "6380");
response<std::string> resp;
net::io_context ioc;
net::ssl::context ctx{net::ssl::context::sslv23};
@@ -63,19 +47,16 @@ BOOST_AUTO_TEST_CASE(ping)
conn.next_layer().set_verify_mode(net::ssl::verify_peer);
conn.next_layer().set_verify_callback(verify_certificate);
net::connect(conn.lowest_layer(), endpoints);
conn.next_layer().handshake(net::ssl::stream_base::client);
conn.async_exec(req, resp, [](auto ec, auto) {
conn.async_exec(req, resp, [&](auto ec, auto) {
BOOST_TEST(!ec);
conn.cancel();
});
conn.async_run([](auto ec) {
BOOST_TEST(!ec);
});
conn.async_run(cfg, {}, [](auto) { });
ioc.run();
BOOST_CHECK_EQUAL(in, std::get<1>(resp).value());
BOOST_CHECK_EQUAL(in, std::get<0>(resp).value());
std::cout << "===============================" << std::endl;
}

View File

@@ -4,8 +4,8 @@
* accompanying file LICENSE.txt)
*/
#include <boost/redis/connection.hpp>
#include <boost/redis/adapter/adapt.hpp>
#include <boost/redis/address.hpp>
#include <boost/redis/detail/read.hpp>
#include <boost/redis/detail/write.hpp>
#include <boost/asio/use_awaitable.hpp>
@@ -22,15 +22,15 @@ using tcp_socket = net::use_awaitable_t<>::as_default_on_t<net::ip::tcp::socket>
using boost::redis::adapter::adapt2;
using net::ip::tcp;
using boost::redis::request;
using boost::redis::address;
using boost::redis::adapter::result;
using redis::config;
auto co_main(address const& addr) -> net::awaitable<void>
auto co_main(config const& cfg) -> net::awaitable<void>
{
auto ex = co_await net::this_coro::executor;
resolver resv{ex};
auto const addrs = co_await resv.async_resolve(addr.host, addr.port);
auto const addrs = co_await resv.async_resolve(cfg.addr.host, cfg.addr.port);
tcp_socket socket{ex};
co_await net::async_connect(socket, addrs);

View File

@@ -6,40 +6,60 @@
// Must come before any asio header, otherwise build fails on msvc.
#include <boost/redis/run.hpp>
#include <boost/redis/check_health.hpp>
#include <boost/redis/connection.hpp>
#include <boost/redis/logger.hpp>
#include <boost/asio/as_tuple.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/consign.hpp>
#include <boost/asio/redirect_error.hpp>
#include <boost/asio/awaitable.hpp>
#include <boost/asio/use_awaitable.hpp>
#include <boost/asio/co_spawn.hpp>
#include <tuple>
#include <iostream>
#include "../examples/start.hpp"
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
#include <boost/asio/experimental/awaitable_operators.hpp>
namespace net = boost::asio;
using namespace net::experimental::awaitable_operators;
using steady_timer = net::use_awaitable_t<>::as_default_on_t<net::steady_timer>;
using boost::redis::request;
using boost::redis::response;
using boost::redis::ignore;
using boost::redis::async_check_health;
using boost::redis::async_run;
using boost::redis::address;
using boost::redis::logger;
using boost::redis::config;
using boost::redis::operation;
using boost::system::error_code;
using boost::asio::use_awaitable;
using boost::asio::redirect_error;
using connection = boost::asio::use_awaitable_t<>::as_default_on_t<boost::redis::connection>;
using namespace std::chrono_literals;
// Push consumer
auto receiver(std::shared_ptr<connection> conn) -> net::awaitable<void>
auto
receiver(std::shared_ptr<connection> conn) -> net::awaitable<void>
{
for (;;)
co_await conn->async_receive();
std::cout << "uuu" << std::endl;
while (!conn->is_cancelled()) {
std::cout << "dddd" << std::endl;
// Loop reading Redis pushs messages.
for (;;) {
std::cout << "aaaa" << std::endl;
error_code ec;
co_await conn->async_receive(ignore, redirect_error(use_awaitable, ec));
if (ec)
break;
}
}
}
auto periodic_task(std::shared_ptr<connection> conn) -> net::awaitable<void>
auto
periodic_task(std::shared_ptr<connection> conn) -> net::awaitable<void>
{
net::steady_timer timer{co_await net::this_coro::executor};
for (int i = 0; i < 10; ++i) {
timer.expires_after(std::chrono::seconds(2));
std::cout << "In the loop: " << i << std::endl;
timer.expires_after(std::chrono::milliseconds(50));
co_await timer.async_wait(net::use_awaitable);
// Key is not set so it will cause an error since we are passing
@@ -49,34 +69,26 @@ auto periodic_task(std::shared_ptr<connection> conn) -> net::awaitable<void>
req.push("GET", "mykey");
auto [ec, u] = co_await conn->async_exec(req, ignore, net::as_tuple(net::use_awaitable));
if (ec) {
std::cout << "Error: " << ec << std::endl;
std::cout << "(1)Error: " << ec << std::endl;
} else {
std::cout << "no error: " << std::endl;
}
}
std::cout << "Periodic task done!" << std::endl;
conn->cancel(operation::run);
conn->cancel(operation::receive);
conn->cancel(operation::reconnection);
}
auto co_main(address const& addr) -> net::awaitable<void>
auto co_main(config const& cfg) -> net::awaitable<void>
{
auto ex = co_await net::this_coro::executor;
auto conn = std::make_shared<connection>(ex);
steady_timer timer{ex};
auto ex = co_await net::this_coro::executor;
auto conn = std::make_shared<connection>(ex);
request req;
req.push("HELLO", 3);
req.push("SUBSCRIBE", "channel");
// The loop will reconnect on connection lost. To exit type Ctrl-C twice.
for (int i = 0; i < 10; ++i) {
co_await ((async_run(*conn, addr) || receiver(conn) || async_check_health(*conn) || periodic_task(conn)) &&
conn->async_exec(req));
conn->reset_stream();
timer.expires_after(std::chrono::seconds{1});
co_await timer.async_wait();
}
net::co_spawn(ex, receiver(conn), net::detached);
net::co_spawn(ex, periodic_task(conn), net::detached);
conn->async_run(cfg, {}, net::consign(net::detached, conn));
}
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)

View File

@@ -4,17 +4,20 @@
* accompanying file LICENSE.txt)
*/
#include <boost/redis/run.hpp>
#include <boost/redis/connection.hpp>
#define BOOST_TEST_MODULE run
#include <boost/test/included/unit_test.hpp>
#include <iostream>
#include <boost/redis/src.hpp>
#include "common.hpp"
namespace net = boost::asio;
namespace redis = boost::redis;
using connection = boost::redis::connection;
using boost::redis::async_run;
using boost::redis::address;
using connection = redis::connection;
using redis::config;
using redis::logger;
using redis::operation;
using boost::system::error_code;
using namespace std::chrono_literals;
@@ -29,8 +32,16 @@ BOOST_AUTO_TEST_CASE(resolve_bad_host)
{
net::io_context ioc;
config cfg;
cfg.addr.host = "Atibaia";
cfg.addr.port = "6379";
cfg.resolve_timeout = 10h;
cfg.connect_timeout = 10h;
cfg.health_check_interval = 10h;
connection conn{ioc};
async_run(conn, address{{"Atibaia"}, {"6379"}}, 1000s, 1000s, [](auto ec){
conn.cancel(operation::reconnection);
conn.async_run(cfg, {}, [](auto ec){
BOOST_TEST(is_host_not_found(ec));
});
@@ -41,11 +52,16 @@ BOOST_AUTO_TEST_CASE(resolve_with_timeout)
{
net::io_context ioc;
connection conn{ioc};
async_run(conn, address{{"Atibaia"}, {"6379"}}, 1ms, 1ms, [](auto ec){
BOOST_CHECK_EQUAL(ec, boost::redis::error::resolve_timeout);
});
config cfg;
cfg.addr.host = "occase.de";
cfg.addr.port = "6379";
cfg.resolve_timeout = 1ms;
cfg.connect_timeout = 1ms;
cfg.health_check_interval = 10h;
auto conn = std::make_shared<connection>(ioc);
conn->cancel(operation::reconnection);
run(conn, cfg);
ioc.run();
}
@@ -53,23 +69,33 @@ BOOST_AUTO_TEST_CASE(connect_bad_port)
{
net::io_context ioc;
connection conn{ioc};
async_run(conn, address{{"127.0.0.1"}, {"1"}}, 1000s, 10s, [](auto ec){
BOOST_CHECK_EQUAL(ec, net::error::basic_errors::connection_refused);
});
config cfg;
cfg.addr.host = "127.0.0.1";
cfg.addr.port = "1";
cfg.resolve_timeout = 10h;
cfg.connect_timeout = 10s;
cfg.health_check_interval = 10h;
auto conn = std::make_shared<connection>(ioc);
conn->cancel(operation::reconnection);
run(conn, cfg, net::error::connection_refused);
ioc.run();
}
BOOST_AUTO_TEST_CASE(connect_with_timeout)
{
net::io_context ioc;
connection conn{ioc};
async_run(conn, address{{"example.com"}, {"1"}}, 10s, 1ms, [](auto ec){
BOOST_CHECK_EQUAL(ec, boost::redis::error::connect_timeout);
});
ioc.run();
}
// Hard to test.
//BOOST_AUTO_TEST_CASE(connect_with_timeout)
//{
// net::io_context ioc;
//
// config cfg;
// cfg.addr.host = "example.com";
// cfg.addr.port = "80";
// cfg.resolve_timeout = 10s;
// cfg.connect_timeout = 1ns;
// cfg.health_check_interval = 10h;
//
// auto conn = std::make_shared<connection>(ioc);
// run(conn, cfg, boost::redis::error::connect_timeout);
// ioc.run();
//}