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:
@@ -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
185
README.md
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
63
examples/sync_connection.hpp
Normal file
63
examples/sync_connection.hpp
Normal 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_;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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
|
||||
*
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
72
include/boost/redis/config.hpp
Normal file
72
include/boost/redis/config.hpp
Normal 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
|
||||
@@ -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_;
|
||||
};
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
133
include/boost/redis/detail/connector.hpp
Normal file
133
include/boost/redis/detail/connector.hpp
Normal 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
|
||||
222
include/boost/redis/detail/health_checker.hpp
Normal file
222
include/boost/redis/detail/health_checker.hpp
Normal 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
|
||||
135
include/boost/redis/detail/reconnection.hpp
Normal file
135
include/boost/redis/detail/reconnection.hpp
Normal 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
|
||||
137
include/boost/redis/detail/resolver.hpp
Normal file
137
include/boost/redis/detail/resolver.hpp
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -69,6 +69,12 @@ enum class error
|
||||
|
||||
/// Connect timeout
|
||||
connect_timeout,
|
||||
|
||||
/// Connect timeout
|
||||
pong_timeout,
|
||||
|
||||
/// SSL handshake timeout
|
||||
ssl_handshake_timeout,
|
||||
};
|
||||
|
||||
/** \internal
|
||||
|
||||
@@ -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.";
|
||||
}
|
||||
}
|
||||
|
||||
128
include/boost/redis/impl/logger.ipp
Normal file
128
include/boost/redis/impl/logger.ipp
Normal 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
|
||||
127
include/boost/redis/logger.hpp
Normal file
127
include/boost/redis/logger.hpp
Normal 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
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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>
|
||||
|
||||
@@ -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>.
|
||||
|
||||
124
include/boost/redis/ssl/detail/handshaker.hpp
Normal file
124
include/boost/redis/ssl/detail/handshaker.hpp
Normal 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
29
tests/common.cpp
Normal 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});
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
96
tests/conn_exec_cancel2.cpp
Normal file
96
tests/conn_exec_cancel2.cpp
Normal 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
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
//}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user