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

Implements a function that checks Redis health.

This commit is contained in:
Marcelo Zimbres
2023-03-04 09:17:15 +01:00
parent 16b5c8d1ba
commit 64820bd25b
23 changed files with 365 additions and 54 deletions

View File

@@ -302,6 +302,15 @@ if (MSVC)
target_compile_definitions(test_issue_50 PRIVATE _WIN32_WINNT=0x0601)
endif()
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()
# Install
#=======================================================================

View File

@@ -56,6 +56,23 @@
"DOXYGEN_OUTPUT_DIRECTORY": "${sourceDir}/build/g++-11-cpp17/doc/"
}
},
{
"name": "g++-11-cpp20",
"generator": "Unix Makefiles",
"hidden": false,
"inherits": ["cmake-pedantic"],
"binaryDir": "${sourceDir}/build/g++-11-cpp20",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"CMAKE_CXX_EXTENSIONS": "OFF",
"CMAKE_CXX_FLAGS": "-Wall -Wextra -fsanitize=address",
"CMAKE_CXX_COMPILER": "g++-11",
"CMAKE_SHARED_LINKER_FLAGS": "-fsanitize=address",
"CMAKE_CXX_STANDARD_REQUIRED": "ON",
"PROJECT_BINARY_DIR": "${sourceDir}/build/g++-11-cpp20",
"DOXYGEN_OUTPUT_DIRECTORY": "${sourceDir}/build/g++-11-cpp20/doc/"
}
},
{
"name": "libc++-14-cpp17",
"generator": "Unix Makefiles",
@@ -107,6 +124,7 @@
"buildPresets": [
{ "name": "coverage", "configurePreset": "coverage" },
{ "name": "g++-11-cpp17", "configurePreset": "g++-11-cpp17" },
{ "name": "g++-11-cpp20", "configurePreset": "g++-11-cpp20" },
{ "name": "libc++-14-cpp17", "configurePreset": "libc++-14-cpp17" },
{ "name": "libc++-14-cpp20", "configurePreset": "libc++-14-cpp20" },
{ "name": "clang-tidy", "configurePreset": "clang-tidy" }

View File

@@ -798,23 +798,70 @@ Acknowledgement to people that helped shape Boost.Redis
* Klemens Morgenstern ([klemens-morgenstern](https://github.com/klemens-morgenstern)): For useful discussion about timeouts, cancellation, synchronous interfaces and general help with Asio.
* Vinnie Falco ([vinniefalco](https://github.com/vinniefalco)): For general suggestions about how to improve the code and the documentation.
Also many thanks to all individuals that participated in the Boost
review
* Zach Laine: https://lists.boost.org/Archives/boost/2023/01/253883.php
* Vinnie Falco: https://lists.boost.org/Archives/boost/2023/01/253886.php
* Christian Mazakas: https://lists.boost.org/Archives/boost/2023/01/253900.php
* Ruben Perez: https://lists.boost.org/Archives/boost/2023/01/253915.php
* Dmitry Arkhipov: https://lists.boost.org/Archives/boost/2023/01/253925.php
* Alan de Freitas: https://lists.boost.org/Archives/boost/2023/01/253927.php
* Mohammad Nejati: https://lists.boost.org/Archives/boost/2023/01/253929.php
* Sam Hartsfield: https://lists.boost.org/Archives/boost/2023/01/253931.php
* Miguel Portilla: https://lists.boost.org/Archives/boost/2023/01/253935.php
* Robert A.H. Leahy: https://lists.boost.org/Archives/boost/2023/01/253928.php
The Reviews can be found at:
https://lists.boost.org/Archives/boost/2023/01/date.php. The thread
with the ACCEPT from the review manager can be found here:
https://lists.boost.org/Archives/boost/2023/01/253944.php.
## Changelog
### master (incorporates many suggestions from the boost review)
### master (incorporates changes to conform the boost review and more)
* 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
generic for ADL customization points. They gained the prefix `boost_redis_`.
* 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 generic for ADL customization points. They gained the prefix `boost_redis_`.
* Moves `boost::redis::resp3::request` to `boost::redis::request`.
* Adds new typedef `boost::redis::response` that should be used instead of `std::tuple`.
* Adds new typedef `boost::redis::generic_response` that should be used instead of `std::vector<resp3::node<std::string>>`.
* Adds new typedef `boost::redis::response` that should be used instead of
`std::tuple`.
* Adds new typedef `boost::redis::generic_response` that should be used instead
of `std::vector<resp3::node<std::string>>`.
* Renames `redis::ignore` to `redis::ignore_t`.
* Changes the signature from `async_exec` to receive a `redis::response` instead of an adapter.
* Adds `boost::redis::adapter::result` to store responses to commands including possible resp3 errors without losing the error diagnostic part. Basicaly instead of accessing values as `std::get<N>(resp)` users have to type `std::get<N>(resp).value()`
* Implements full-duplex communication. Before these changes the connection would wait for a response to arrive before sending the next one. Now requests are continuously coalesced and written to the socket. This made the request::coalesce unnecessary and threfore it was removed.
* Adds native json support for Boost.Describe'd classes, see cpp20_json_serialization.cpp for how to use it.
* Changes `async_exec` to receive a `redis::response` instead of an adapter,
namely, instead of passing `adapt(resp)` users should pass `resp` directly.
* Introduces `boost::redis::adapter::result` to store responses to commands
including possible resp3 errors without losing the error diagnostic part. To
access values now use `std::get<N>(resp).value()` instead of
`std::get<N>(resp)`.
* Implements full-duplex communication. Before these changes the connection
would wait for a response to arrive before sending the next one. Now requests
are continuously coalesced and written to the socket. `request::coalesce`
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.
* Upgrades to Boost 1.81.0.
* Fixes build with libc++.
* Adds a function that performs health checks, see
`boost::redis::experimental::async_check_health`.
### v1.4.0-1
* Renames `retry_on_connection_lost` to `cancel_if_unresponded`. (v1.4.1)

View File

@@ -24,30 +24,6 @@ auto redir(boost::system::error_code& ec)
{ return net::redirect_error(net::use_awaitable, ec); }
}
auto health_check(std::shared_ptr<connection> conn) -> net::awaitable<void>
{
try {
request req;
req.push("PING");
timer_type timer{co_await net::this_coro::executor};
for (boost::system::error_code ec;;) {
timer.expires_after(std::chrono::seconds{1});
co_await (conn->async_exec(req) || timer.async_wait(redir(ec)));
if (!ec) {
co_return;
}
// Waits some time before trying the next ping.
timer.expires_after(std::chrono::seconds{1});
co_await timer.async_wait();
}
} catch (...) {
}
}
auto
connect(
std::shared_ptr<connection> conn,

View File

@@ -4,9 +4,10 @@
* accompanying file LICENSE.txt)
*/
#ifndef AEDIS_EXAMPLES_COMMON_HPP
#define AEDIS_EXAMPLES_COMMON_HPP
#ifndef BOOST_REDIS_EXAMPLES_COMMON_HPP
#define BOOST_REDIS_EXAMPLES_COMMON_HPP
#include <iostream>
#include <boost/asio.hpp>
#include <boost/redis.hpp>
#include <memory>
@@ -26,9 +27,7 @@ connect(
std::string const& host,
std::string const& port) -> boost::asio::awaitable<void>;
auto health_check(std::shared_ptr<connection> conn) -> boost::asio::awaitable<void>;
auto run(boost::asio::awaitable<void> op) -> int;
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
#endif // AEDIS_EXAMPLES_COMMON_HPP
#endif // BOOST_REDIS_EXAMPLES_COMMON_HPP

View File

@@ -11,6 +11,7 @@ namespace net = boost::asio;
#if defined(BOOST_ASIO_HAS_POSIX_STREAM_DESCRIPTOR)
#include <boost/asio/experimental/awaitable_operators.hpp>
#include <boost/redis.hpp>
#include <boost/redis/experimental/run.hpp>
#include <unistd.h>
#include "common/common.hpp"
@@ -20,6 +21,7 @@ using stream_descriptor = net::use_awaitable_t<>::as_default_on_t<net::posix::st
using signal_set = net::use_awaitable_t<>::as_default_on_t<net::signal_set>;
using boost::redis::request;
using boost::redis::generic_response;
using boost::redis::experimental::async_check_health;
// Chat over Redis pubsub. To test, run this program from multiple
// terminals and type messages to stdin.
@@ -60,7 +62,8 @@ auto co_main(std::string host, std::string port) -> net::awaitable<void>
co_await connect(conn, host, port);
co_await ((conn->async_run() || publisher(stream, conn) || receiver(conn) ||
health_check(conn) || sig.async_wait()) && conn->async_exec(req));
async_check_health(*conn) || sig.async_wait()) &&
conn->async_exec(req));
}
#else // defined(BOOST_ASIO_HAS_POSIX_STREAM_DESCRIPTOR)

View File

@@ -8,6 +8,7 @@
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
#include <boost/asio/experimental/awaitable_operators.hpp>
#include <boost/redis.hpp>
#include <boost/redis/experimental/run.hpp>
#include "common/common.hpp"
namespace net = boost::asio;
@@ -17,6 +18,7 @@ using tcp_acceptor = net::use_awaitable_t<>::as_default_on_t<net::ip::tcp::accep
using signal_set = net::use_awaitable_t<>::as_default_on_t<net::signal_set>;
using boost::redis::request;
using boost::redis::response;
using boost::redis::experimental::async_check_health;
auto echo_server_session(tcp_socket socket, std::shared_ptr<connection> conn) -> net::awaitable<void>
{
@@ -54,7 +56,7 @@ auto co_main(std::string host, std::string port) -> net::awaitable<void>
req.push("HELLO", 3);
co_await connect(conn, host, port);
co_await ((conn->async_run() || listener(conn) || health_check(conn) ||
co_await ((conn->async_run() || listener(conn) || async_check_health(*conn) ||
sig.async_wait()) && conn->async_exec(req));
}

View File

@@ -8,6 +8,7 @@
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
#include <boost/asio/experimental/awaitable_operators.hpp>
#include <boost/redis.hpp>
#include <boost/redis/experimental/run.hpp>
#include "common/common.hpp"
@@ -16,6 +17,7 @@ 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::generic_response;
using boost::redis::experimental::async_check_health;
/* This example will subscribe and read pushes indefinitely.
*
@@ -56,7 +58,7 @@ auto co_main(std::string host, std::string port) -> net::awaitable<void>
// The loop will reconnect on connection lost. To exit type Ctrl-C twice.
for (;;) {
co_await connect(conn, host, port);
co_await ((conn->async_run() || health_check(conn) || receiver(conn)) && conn->async_exec(req));
co_await ((conn->async_run() || async_check_health(*conn) || receiver(conn)) && conn->async_exec(req));
conn->reset_stream();
timer.expires_after(std::chrono::seconds{1});

View File

@@ -15,7 +15,6 @@
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/asio/bind_executor.hpp>
#include <boost/asio/deferred.hpp>
#include <boost/asio/experimental/channel.hpp>
#include <vector>

View File

@@ -0,0 +1,130 @@
/* 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 <tuple>
#include <boost/asio/experimental/promise.hpp>
#include <boost/asio/experimental/use_promise.hpp>
#include <boost/asio/steady_timer.hpp>
#include <memory>
#include <chrono>
namespace boost::redis::experimental {
namespace detail {
template <class Connection>
class check_health_op {
private:
using executor_type = typename Connection::executor_type;
struct state {
using clock_type = std::chrono::steady_clock;
using clock_traits_type = asio::wait_traits<clock_type>;
using timer_type = asio::basic_waitable_timer<clock_type, clock_traits_type, executor_type>;
using promise_type = asio::experimental::promise<void(system::error_code, std::size_t), executor_type>;
timer_type timer_;
request req_;
generic_response resp_;
std::optional<promise_type> prom_;
std::chrono::steady_clock::duration interval_;
state(
executor_type ex,
std::string const& msg,
std::chrono::steady_clock::duration interval)
: timer_{ex}
, interval_{interval}
{
req_.push("PING", msg);
}
void reset()
{
resp_.value().clear();
prom_.reset();
}
};
Connection* conn_ = nullptr;
std::shared_ptr<state> state_ = nullptr;
asio::coroutine coro_{};
public:
check_health_op(
Connection& conn,
std::string const& msg,
std::chrono::steady_clock::duration interval)
: conn_{&conn}
, state_{std::make_shared<state>(conn.get_executor(), msg, interval)}
{ }
template <class Self>
void operator()(Self& self, system::error_code ec = {}, std::size_t = 0)
{
BOOST_ASIO_CORO_REENTER (coro_) for (;;)
{
state_->prom_.emplace(conn_->async_exec(state_->req_, state_->resp_, asio::experimental::use_promise));
state_->timer_.expires_after(state_->interval_);
BOOST_ASIO_CORO_YIELD
state_->timer_.async_wait(std::move(self));
if (ec || is_cancelled(self) || state_->resp_.value().empty()) {
conn_->cancel(operation::run);
BOOST_ASIO_CORO_YIELD
std::move(*state_->prom_)(std::move(self));
self.complete({});
return;
}
state_->reset();
}
}
};
} // 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{})
{
return asio::async_compose
< CompletionToken
, void(system::error_code)
>(detail::check_health_op<Connection>{conn, msg, interval}, token, conn);
}
} // boost::redis::experimental
#endif // BOOST_REDIS_RUN_HPP

124
tests/conn_check_health.cpp Normal file
View File

@@ -0,0 +1,124 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#include <iostream>
#include <boost/asio.hpp>
#include <boost/system/errc.hpp>
#define BOOST_TEST_MODULE check-health
#include <boost/test/included/unit_test.hpp>
#include <boost/redis.hpp>
#include <boost/redis/experimental/run.hpp>
#include <boost/redis/src.hpp>
#include "common.hpp"
namespace net = boost::asio;
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::experimental::async_check_health;
std::chrono::seconds const interval{1};
struct push_callback {
connection* conn;
connection* conn2;
generic_response* resp;
request* req;
int i = 0;
void operator()(error_code ec = {}, std::size_t = 0)
{
++i;
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;
// 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);
}
};
};
BOOST_AUTO_TEST_CASE(check_health)
{
net::io_context ioc;
auto const endpoints = resolve();
connection conn{ioc};
net::connect(conn.next_layer(), endpoints);
// It looks like client pause does not work for clients that are
// sending MONITOR. I will therefore open a second connection.
connection conn2{ioc};
net::connect(conn2.next_layer(), endpoints);
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);
});
request req2;
req2.push("HELLO", "3");
req2.push("CLIENT", "PAUSE", "3000", "ALL");
generic_response resp;
push_callback{&conn, &conn2, &resp, &req2}(); // Starts reading pushes.
conn.async_run([](auto ec){
std::cout << "B" << std::endl;
BOOST_TEST(!!ec);
});
conn2.async_run([](auto ec){
std::cout << "C" << std::endl;
BOOST_TEST(!!ec);
});
ioc.run();
BOOST_TEST(seen);
}

View File

@@ -8,7 +8,7 @@
#include <boost/asio.hpp>
#ifdef BOOST_ASIO_HAS_CO_AWAIT
#include <boost/system/errc.hpp>
#define BOOST_TEST_MODULE low level
#define BOOST_TEST_MODULE echo-stress
#include <boost/test/included/unit_test.hpp>
#include <boost/redis.hpp>
#include <boost/redis/src.hpp>

View File

@@ -8,7 +8,7 @@
#include <boost/asio.hpp>
#include <boost/system/errc.hpp>
#define BOOST_TEST_MODULE low level
#define BOOST_TEST_MODULE conn-exec
#include <boost/test/included/unit_test.hpp>
#include <boost/redis.hpp>

View File

@@ -9,7 +9,7 @@
#ifdef BOOST_ASIO_HAS_CO_AWAIT
#include <boost/system/errc.hpp>
#include <boost/asio/experimental/awaitable_operators.hpp>
#define BOOST_TEST_MODULE low level
#define BOOST_TEST_MODULE conn-exec-cancel
#include <boost/test/included/unit_test.hpp>
#include <boost/redis.hpp>
#include <boost/redis/src.hpp>

View File

@@ -8,7 +8,7 @@
#include <boost/asio.hpp>
#include <boost/system/errc.hpp>
#define BOOST_TEST_MODULE low level
#define BOOST_TEST_MODULE conn-exec-error
#include <boost/test/included/unit_test.hpp>
#include <boost/redis.hpp>

View File

@@ -8,7 +8,7 @@
#include <boost/asio.hpp>
#include <boost/system/errc.hpp>
#define BOOST_TEST_MODULE low level
#define BOOST_TEST_MODULE conn-exec-retry
#include <boost/test/included/unit_test.hpp>
#include <boost/redis.hpp>

View File

@@ -10,7 +10,7 @@
#include <boost/system/errc.hpp>
#include <boost/asio/experimental/as_tuple.hpp>
#define BOOST_TEST_MODULE low level
#define BOOST_TEST_MODULE conn-push
#include <boost/test/included/unit_test.hpp>
#include <boost/redis.hpp>

View File

@@ -8,7 +8,7 @@
#include <boost/asio.hpp>
#include <boost/system/errc.hpp>
#define BOOST_TEST_MODULE low level
#define BOOST_TEST_MODULE conn-quit
#include <boost/test/included/unit_test.hpp>
#include <boost/redis.hpp>

View File

@@ -8,7 +8,7 @@
#include <boost/asio.hpp>
#ifdef BOOST_ASIO_HAS_CO_AWAIT
#define BOOST_TEST_MODULE low level
#define BOOST_TEST_MODULE conn-reconnect
#include <boost/test/included/unit_test.hpp>
#include <boost/redis.hpp>

View File

@@ -10,7 +10,7 @@
#include <boost/system/errc.hpp>
#include <boost/asio/experimental/as_tuple.hpp>
#define BOOST_TEST_MODULE low level
#define BOOST_TEST_MODULE conn-run-cancel
#include <boost/test/included/unit_test.hpp>
#include <boost/redis.hpp>

View File

@@ -8,7 +8,7 @@
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#define BOOST_TEST_MODULE low level
#define BOOST_TEST_MODULE conn-tls
#include <boost/test/included/unit_test.hpp>
#include <boost/redis.hpp>

View File

@@ -7,6 +7,7 @@
#include <boost/asio.hpp>
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
#include <boost/redis.hpp>
#include <boost/redis/experimental/run.hpp>
#include <boost/asio/experimental/awaitable_operators.hpp>
#include "../examples/common/common.hpp"
@@ -17,6 +18,7 @@ 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::experimental::async_check_health;
// Push consumer
auto receiver(std::shared_ptr<connection> conn) -> net::awaitable<void>
@@ -61,7 +63,7 @@ auto co_main(std::string host, std::string port) -> net::awaitable<void>
// The loop will reconnect on connection lost. To exit type Ctrl-C twice.
for (int i = 0; i < 10; ++i) {
co_await connect(conn, host, port);
co_await ((conn->async_run() || receiver(conn) || health_check(conn) || periodic_task(conn)) &&
co_await ((conn->async_run() || receiver(conn) || async_check_health(*conn) || periodic_task(conn)) &&
conn->async_exec(req));
conn->reset_stream();

View File

@@ -6,7 +6,7 @@
#include <iostream>
#define BOOST_TEST_MODULE low level
#define BOOST_TEST_MODULE request
#include <boost/test/included/unit_test.hpp>
#include <boost/redis/request.hpp>