mirror of
https://github.com/boostorg/redis.git
synced 2026-01-26 19:02:08 +00:00
Compare commits
42 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e9dab97992 | ||
|
|
2e8cad858d | ||
|
|
5a6e426028 | ||
|
|
c55978a379 | ||
|
|
6f51397e49 | ||
|
|
6b9ba6b2d9 | ||
|
|
d29c03cb38 | ||
|
|
34cfbaa22f | ||
|
|
c9354fe320 | ||
|
|
bb555cb509 | ||
|
|
5b209afa1d | ||
|
|
3f5491654d | ||
|
|
2bdc25752f | ||
|
|
faafce1c64 | ||
|
|
562075230f | ||
|
|
5dc677c6d8 | ||
|
|
395a167d48 | ||
|
|
f93f3cab58 | ||
|
|
df68fb0235 | ||
|
|
15e6883bc1 | ||
|
|
3816d1d358 | ||
|
|
bb15c70723 | ||
|
|
297b7f15eb | ||
|
|
ec6e99d99a | ||
|
|
8dc6db069b | ||
|
|
bac27c1770 | ||
|
|
feaaedc6c0 | ||
|
|
000ebddf44 | ||
|
|
268ea2c10f | ||
|
|
d8b67f6e23 | ||
|
|
ce1fa6a683 | ||
|
|
b8ede6ccb7 | ||
|
|
6dce1a9226 | ||
|
|
8566745d83 | ||
|
|
0b4906fcba | ||
|
|
2c8bb92071 | ||
|
|
770e224917 | ||
|
|
4fb2b20954 | ||
|
|
c01a57b6cb | ||
|
|
ea0b333c4d | ||
|
|
ba82c6cd84 | ||
|
|
4c298ddc6b |
@@ -9,6 +9,7 @@ Checks: "*,\
|
||||
-google-readability-braces-around-statements,\
|
||||
-hicpp-braces-around-statements,\
|
||||
-hicpp-named-parameter,\
|
||||
-hicpp-avoid-goto,\
|
||||
-google-build-using-namespace,\
|
||||
-altera-*,\
|
||||
-fuchsia-*,\
|
||||
@@ -21,6 +22,12 @@ Checks: "*,\
|
||||
-bugprone-use-after-move,\
|
||||
-hicpp-invalid-access-moved,\
|
||||
-misc-no-recursion,\
|
||||
-cppcoreguidelines-pro-bounds-pointer-arithmetic,\
|
||||
-cppcoreguidelines-avoid-magic-numbers,\
|
||||
-cppcoreguidelines-pro-bounds-constant-array-index,\
|
||||
-cppcoreguidelines-interfaces-global-init,\
|
||||
-cppcoreguidelines-macro-usage,\
|
||||
-cppcoreguidelines-avoid-goto,\
|
||||
-cppcoreguidelines-non-private-member-variables-in-classes"
|
||||
WarningsAsErrors: ''
|
||||
CheckOptions:
|
||||
|
||||
134
CMakeLists.txt
134
CMakeLists.txt
@@ -10,7 +10,7 @@ cmake_minimum_required(VERSION 3.14)
|
||||
|
||||
project(
|
||||
Aedis
|
||||
VERSION 1.1.0
|
||||
VERSION 1.3.0
|
||||
DESCRIPTION "A redis client designed for performance and scalability"
|
||||
HOMEPAGE_URL "https://mzimbres.github.io/aedis"
|
||||
LANGUAGES CXX
|
||||
@@ -21,6 +21,7 @@ target_include_directories(aedis INTERFACE
|
||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
|
||||
$<INSTALL_INTERFACE:include>
|
||||
)
|
||||
|
||||
target_link_libraries(aedis
|
||||
INTERFACE
|
||||
Boost::asio
|
||||
@@ -50,66 +51,106 @@ find_package(OpenSSL REQUIRED)
|
||||
enable_testing()
|
||||
include_directories(include)
|
||||
|
||||
# Main function for the examples.
|
||||
#=======================================================================
|
||||
|
||||
add_library(common STATIC examples/common.cpp)
|
||||
target_compile_features(common PUBLIC cxx_std_20)
|
||||
|
||||
# Executables
|
||||
#=======================================================================
|
||||
|
||||
add_executable(chat_room examples/chat_room.cpp)
|
||||
add_executable(containers examples/containers.cpp)
|
||||
add_executable(echo_server examples/echo_server.cpp)
|
||||
add_executable(intro examples/intro.cpp)
|
||||
add_executable(intro_tls examples/intro_tls.cpp)
|
||||
#add_executable(intro_sync examples/intro_sync.cpp) // Uncomment after update to Boost 1.80
|
||||
add_executable(serialization examples/serialization.cpp)
|
||||
add_executable(subscriber examples/subscriber.cpp)
|
||||
add_executable(subscriber_sentinel examples/subscriber_sentinel.cpp)
|
||||
add_executable(test_low_level tests/low_level.cpp)
|
||||
add_executable(low_level_sync examples/low_level_sync.cpp)
|
||||
add_executable(test_connection_other tests/connection_other.cpp)
|
||||
add_executable(test_connection_connect tests/connection_connect.cpp)
|
||||
add_executable(test_connection_push tests/connection_push.cpp)
|
||||
add_executable(test_connection_quit tests/connection_quit.cpp)
|
||||
add_executable(test_connection_quit_coalesce tests/connection_quit_coalesce.cpp)
|
||||
add_executable(test_connection_reconnect tests/connection_reconnect.cpp)
|
||||
add_executable(test_connection_tls tests/connection_tls.cpp)
|
||||
|
||||
add_executable(intro examples/intro.cpp)
|
||||
target_link_libraries(intro common)
|
||||
target_compile_features(intro PUBLIC cxx_std_20)
|
||||
add_test(intro intro)
|
||||
|
||||
add_executable(chat_room examples/chat_room.cpp)
|
||||
target_compile_features(chat_room PUBLIC cxx_std_20)
|
||||
target_compile_features(intro PUBLIC cxx_std_17)
|
||||
target_compile_features(intro_tls PUBLIC cxx_std_17)
|
||||
target_compile_features(serialization PUBLIC cxx_std_17)
|
||||
target_compile_features(containers PUBLIC cxx_std_17)
|
||||
target_compile_features(test_low_level PUBLIC cxx_std_17)
|
||||
target_compile_features(low_level_sync PUBLIC cxx_std_17)
|
||||
target_compile_features(echo_server PUBLIC cxx_std_20)
|
||||
target_compile_features(subscriber PUBLIC cxx_std_20)
|
||||
target_compile_features(subscriber_sentinel PUBLIC cxx_std_20)
|
||||
target_compile_features(test_connection_other PUBLIC cxx_std_20)
|
||||
target_compile_features(test_connection_push PUBLIC cxx_std_20)
|
||||
target_compile_features(test_connection_connect PUBLIC cxx_std_17)
|
||||
target_compile_features(test_connection_quit PUBLIC cxx_std_17)
|
||||
target_compile_features(test_connection_quit_coalesce PUBLIC cxx_std_17)
|
||||
target_compile_features(test_connection_reconnect PUBLIC cxx_std_20)
|
||||
target_compile_features(test_connection_tls PUBLIC cxx_std_17)
|
||||
target_link_libraries(chat_room common)
|
||||
|
||||
add_executable(containers examples/containers.cpp)
|
||||
target_compile_features(containers PUBLIC cxx_std_20)
|
||||
target_link_libraries(containers common)
|
||||
add_test(containers containers)
|
||||
|
||||
add_executable(echo_server examples/echo_server.cpp)
|
||||
target_compile_features(echo_server PUBLIC cxx_std_20)
|
||||
target_link_libraries(echo_server common)
|
||||
|
||||
add_executable(resolve_with_sentinel examples/resolve_with_sentinel.cpp)
|
||||
target_compile_features(resolve_with_sentinel PUBLIC cxx_std_20)
|
||||
target_link_libraries(resolve_with_sentinel common)
|
||||
add_test(resolve_with_sentinel resolve_with_sentinel)
|
||||
|
||||
add_executable(serialization examples/serialization.cpp)
|
||||
target_compile_features(serialization PUBLIC cxx_std_20)
|
||||
target_link_libraries(serialization common)
|
||||
add_test(serialization serialization)
|
||||
|
||||
add_executable(subscriber examples/subscriber.cpp)
|
||||
target_compile_features(subscriber PUBLIC cxx_std_20)
|
||||
target_link_libraries(subscriber common)
|
||||
|
||||
add_executable(intro_tls examples/intro_tls.cpp)
|
||||
target_compile_features(intro_tls PUBLIC cxx_std_20)
|
||||
add_test(intro_tls intro_tls)
|
||||
target_link_libraries(intro_tls OpenSSL::Crypto OpenSSL::SSL)
|
||||
target_link_libraries(test_connection_tls OpenSSL::Crypto OpenSSL::SSL)
|
||||
target_link_libraries(intro_tls common)
|
||||
|
||||
add_executable(echo_server_client benchmarks/cpp/asio/echo_server_client.cpp)
|
||||
add_executable(echo_server_direct benchmarks/cpp/asio/echo_server_direct.cpp)
|
||||
add_executable(low_level_sync examples/low_level_sync.cpp)
|
||||
add_executable(low_level_async examples/low_level_async.cpp)
|
||||
add_executable(test_conn_exec tests/conn_exec.cpp)
|
||||
add_executable(test_conn_push tests/conn_push.cpp)
|
||||
add_executable(test_conn_quit tests/conn_quit.cpp)
|
||||
add_executable(test_conn_quit_coalesce tests/conn_quit_coalesce.cpp)
|
||||
add_executable(test_conn_reconnect tests/conn_reconnect.cpp)
|
||||
add_executable(test_conn_tls tests/conn_tls.cpp)
|
||||
add_executable(test_low_level tests/low_level.cpp)
|
||||
add_executable(test_conn_run_cancel tests/conn_run_cancel.cpp)
|
||||
add_executable(test_conn_exec_cancel tests/conn_exec_cancel.cpp)
|
||||
add_executable(test_conn_echo_stress tests/conn_echo_stress.cpp)
|
||||
add_executable(test_request tests/request.cpp)
|
||||
|
||||
target_compile_features(echo_server_client PUBLIC cxx_std_20)
|
||||
target_compile_features(echo_server_direct PUBLIC cxx_std_20)
|
||||
target_compile_features(low_level_sync PUBLIC cxx_std_17)
|
||||
target_compile_features(low_level_async PUBLIC cxx_std_20)
|
||||
target_compile_features(test_conn_exec PUBLIC cxx_std_20)
|
||||
target_compile_features(test_conn_push PUBLIC cxx_std_20)
|
||||
target_compile_features(test_conn_quit PUBLIC cxx_std_17)
|
||||
target_compile_features(test_conn_quit_coalesce PUBLIC cxx_std_17)
|
||||
target_compile_features(test_conn_reconnect PUBLIC cxx_std_20)
|
||||
target_compile_features(test_conn_tls PUBLIC cxx_std_17)
|
||||
target_compile_features(test_low_level PUBLIC cxx_std_17)
|
||||
target_compile_features(test_conn_run_cancel PUBLIC cxx_std_20)
|
||||
target_compile_features(test_conn_exec_cancel PUBLIC cxx_std_20)
|
||||
target_compile_features(test_conn_echo_stress PUBLIC cxx_std_20)
|
||||
target_compile_features(test_request PUBLIC cxx_std_17)
|
||||
|
||||
target_link_libraries(test_conn_tls OpenSSL::Crypto OpenSSL::SSL)
|
||||
|
||||
# Tests
|
||||
#=======================================================================
|
||||
|
||||
add_test(containers containers)
|
||||
add_test(intro intro)
|
||||
add_test(intro_tls intro_tls)
|
||||
#add_test(intro_sync intro_sync)
|
||||
add_test(serialization serialization)
|
||||
add_test(low_level_sync low_level_sync)
|
||||
add_test(low_level_async low_level_async)
|
||||
add_test(test_low_level test_low_level)
|
||||
add_test(test_connection_other test_connection_other)
|
||||
add_test(test_connection_connect test_connection_connect)
|
||||
add_test(test_connection_push test_connection_push)
|
||||
add_test(test_connection_quit test_connection_quit)
|
||||
add_test(test_connection_quit_coalesce test_connection_quit_coalesce)
|
||||
add_test(test_connection_reconnect test_connection_reconnect)
|
||||
add_test(test_connection_tls test_connection_tls)
|
||||
add_test(test_conn_exec test_conn_exec)
|
||||
add_test(test_conn_push test_conn_push)
|
||||
add_test(test_conn_quit test_conn_quit)
|
||||
add_test(test_conn_quit_coalesce test_conn_quit_coalesce)
|
||||
add_test(test_conn_reconnect test_conn_reconnect)
|
||||
add_test(test_conn_tls test_conn_tls)
|
||||
add_test(test_conn_run_cancel test_conn_run_cancel)
|
||||
add_test(test_conn_exec_cancel test_conn_exec_cancel)
|
||||
add_test(test_conn_echo_stress test_conn_echo_stress)
|
||||
add_test(test_request test_request)
|
||||
|
||||
# Install
|
||||
#=======================================================================
|
||||
@@ -138,7 +179,6 @@ install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/ DESTINATION include)
|
||||
#=======================================================================
|
||||
|
||||
set(DOXYGEN_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/doc")
|
||||
|
||||
configure_file(doc/Doxyfile.in doc/Doxyfile @ONLY)
|
||||
|
||||
add_custom_target(
|
||||
|
||||
@@ -51,7 +51,8 @@
|
||||
"CMAKE_CXX_FLAGS": "-Wall -Wextra -fsanitize=address",
|
||||
"CMAKE_SHARED_LINKER_FLAGS": "-fsanitize=address",
|
||||
"CMAKE_CXX_STANDARD_REQUIRED": "ON",
|
||||
"PROJECT_BINARY_DIR": "${sourceDir}/build/dev"
|
||||
"PROJECT_BINARY_DIR": "${sourceDir}/build/dev",
|
||||
"DOXYGEN_OUTPUT_DIRECTORY": "${sourceDir}/build/dev/doc/"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
nodes near coords align={horizontal},
|
||||
]
|
||||
\addplot coordinates {
|
||||
(31.1,Asio)
|
||||
(29.5,Asio)
|
||||
(30.7,Tokio)
|
||||
(35.6,Go)
|
||||
(43.6,Libuv)
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <iostream>
|
||||
#include <boost/asio.hpp>
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
|
||||
namespace net = boost::asio;
|
||||
|
||||
@@ -62,3 +63,6 @@ int main(int argc, char* argv[])
|
||||
std::cerr << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
#else // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
auto main() -> int {std::cout << "Requires coroutine support." << std::endl; return 1;}
|
||||
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
|
||||
@@ -9,7 +9,9 @@
|
||||
//
|
||||
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
#include <boost/asio.hpp>
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
|
||||
namespace net = boost::asio;
|
||||
namespace this_coro = net::this_coro;
|
||||
@@ -56,3 +58,6 @@ int main()
|
||||
std::printf("Exception: %s\n", e.what());
|
||||
}
|
||||
}
|
||||
#else // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
auto main() -> int {std::cout << "Requires coroutine support." << std::endl; return 1;}
|
||||
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
|
||||
@@ -907,10 +907,7 @@ WARN_LOGFILE =
|
||||
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
|
||||
# Note: If this tag is empty the current directory is searched.
|
||||
|
||||
INPUT = include \
|
||||
benchmarks/benchmarks.md \
|
||||
CHANGELOG.md \
|
||||
examples README.md
|
||||
INPUT = include examples README.md
|
||||
|
||||
# This tag can be used to specify the character encoding of the source files
|
||||
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
|
||||
|
||||
@@ -4,96 +4,67 @@
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include "unistd.h"
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT) && defined(BOOST_ASIO_HAS_POSIX_STREAM_DESCRIPTOR)
|
||||
#include <boost/asio/experimental/awaitable_operators.hpp>
|
||||
#include <aedis.hpp>
|
||||
#include "print.hpp"
|
||||
#include <unistd.h>
|
||||
|
||||
// Include this in no more than one .cpp file.
|
||||
#include <aedis/src.hpp>
|
||||
#include "common.hpp"
|
||||
|
||||
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_type = net::use_awaitable_t<>::as_default_on_t<net::signal_set>;
|
||||
using aedis::adapt;
|
||||
using aedis::resp3::request;
|
||||
using aedis::resp3::node;
|
||||
using aedis::endpoint;
|
||||
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 stream_descriptor = net::use_awaitable_t<>::as_default_on_t<net::posix::stream_descriptor>;
|
||||
using connection = aedis::connection<tcp_socket>;
|
||||
using stimer = net::use_awaitable_t<>::as_default_on_t<net::steady_timer>;
|
||||
|
||||
// Chat over redis pubsub. To test, run this program from different
|
||||
// terminals and type messages to stdin. Use
|
||||
//
|
||||
// $ redis-cli monitor
|
||||
//
|
||||
// to monitor the message traffic.
|
||||
// Chat over Redis pubsub. To test, run this program from different
|
||||
// terminals and type messages to stdin.
|
||||
|
||||
// Receives messages from other users.
|
||||
net::awaitable<void> push_receiver(std::shared_ptr<connection> db)
|
||||
// Receives Redis server-side pushes.
|
||||
auto receiver(std::shared_ptr<connection> conn) -> net::awaitable<void>
|
||||
{
|
||||
for (std::vector<node<std::string>> resp;;) {
|
||||
co_await db->async_receive_push(adapt(resp));
|
||||
print_push(resp);
|
||||
co_await conn->async_receive(adapt(resp));
|
||||
std::cout << resp.at(1).value << " " << resp.at(2).value << " " << resp.at(3).value << std::endl;
|
||||
resp.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// Subscribes to the channels when a new connection is stablished.
|
||||
net::awaitable<void> reconnect(std::shared_ptr<connection> db)
|
||||
{
|
||||
request req;
|
||||
req.push("SUBSCRIBE", "chat-channel");
|
||||
|
||||
stimer timer{co_await net::this_coro::executor};
|
||||
endpoint ep{"127.0.0.1", "6379"};
|
||||
for (;;) {
|
||||
boost::system::error_code ec;
|
||||
co_await db->async_run(ep, req, adapt(), {}, net::redirect_error(net::use_awaitable, ec));
|
||||
db->reset_stream();
|
||||
std::cout << ec.message() << std::endl;
|
||||
timer.expires_after(std::chrono::seconds{1});
|
||||
co_await timer.async_wait();
|
||||
}
|
||||
}
|
||||
|
||||
// Publishes messages to other users.
|
||||
net::awaitable<void> publisher(stream_descriptor& in, std::shared_ptr<connection> db)
|
||||
// Publishes stdin messages to a Redis channel.
|
||||
auto publisher(std::shared_ptr<stream_descriptor> in, std::shared_ptr<connection> conn) -> net::awaitable<void>
|
||||
{
|
||||
for (std::string msg;;) {
|
||||
auto n = co_await net::async_read_until(in, net::dynamic_buffer(msg, 1024), "\n");
|
||||
auto n = co_await net::async_read_until(*in, net::dynamic_buffer(msg, 1024), "\n");
|
||||
request req;
|
||||
req.push("PUBLISH", "chat-channel", msg);
|
||||
co_await db->async_exec(req);
|
||||
co_await conn->async_exec(req);
|
||||
msg.erase(0, n);
|
||||
}
|
||||
}
|
||||
|
||||
auto main() -> int
|
||||
auto subscriber(std::shared_ptr<connection> conn) -> net::awaitable<void>
|
||||
{
|
||||
try {
|
||||
net::io_context ioc{1};
|
||||
stream_descriptor in{ioc, ::dup(STDIN_FILENO)};
|
||||
request req;
|
||||
req.get_config().cancel_on_connection_lost = true;
|
||||
req.push("HELLO", 3);
|
||||
req.push("SUBSCRIBE", "chat-channel");
|
||||
|
||||
auto db = std::make_shared<connection>(ioc);
|
||||
co_spawn(ioc, publisher(in, db), net::detached);
|
||||
co_spawn(ioc, push_receiver(db), net::detached);
|
||||
co_spawn(ioc, reconnect(db), net::detached);
|
||||
|
||||
net::signal_set signals(ioc, SIGINT, SIGTERM);
|
||||
signals.async_wait([&](auto, auto){ ioc.stop(); });
|
||||
|
||||
ioc.run();
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
}
|
||||
co_await conn->async_exec(req);
|
||||
}
|
||||
|
||||
auto async_main() -> 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_type sig{ex, SIGINT, SIGTERM};
|
||||
|
||||
co_await connect(conn, "127.0.0.1", "6379");
|
||||
co_await ((conn->async_run() || publisher(stream, conn) || receiver(conn) ||
|
||||
healthy_checker(conn) || sig.async_wait()) && subscriber(conn));
|
||||
}
|
||||
|
||||
#else // defined(BOOST_ASIO_HAS_CO_AWAIT) && defined(BOOST_ASIO_HAS_POSIX_STREAM_DESCRIPTOR)
|
||||
auto main() -> int {std::cout << "Requires coroutine support." << std::endl; return 1;}
|
||||
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT) && defined(BOOST_ASIO_HAS_POSIX_STREAM_DESCRIPTOR)
|
||||
|
||||
94
examples/common.cpp
Normal file
94
examples/common.cpp
Normal file
@@ -0,0 +1,94 @@
|
||||
/* 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 "common.hpp"
|
||||
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
#include <boost/asio/experimental/awaitable_operators.hpp>
|
||||
#include <iostream>
|
||||
|
||||
namespace net = boost::asio;
|
||||
using namespace net::experimental::awaitable_operators;
|
||||
using resolver = net::use_awaitable_t<>::as_default_on_t<net::ip::tcp::resolver>;
|
||||
using timer_type = net::use_awaitable_t<>::as_default_on_t<net::steady_timer>;
|
||||
using aedis::resp3::request;
|
||||
using aedis::adapt;
|
||||
using aedis::operation;
|
||||
|
||||
// Include this in no more than one .cpp file.
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
namespace
|
||||
{
|
||||
auto redir(boost::system::error_code& ec)
|
||||
{ return net::redirect_error(net::use_awaitable, ec); }
|
||||
}
|
||||
|
||||
auto healthy_checker(std::shared_ptr<connection> conn) -> net::awaitable<void>
|
||||
{
|
||||
try {
|
||||
request req;
|
||||
req.get_config().cancel_on_connection_lost = true;
|
||||
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, adapt()) || 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,
|
||||
std::string const& host,
|
||||
std::string const& port) -> net::awaitable<void>
|
||||
{
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
resolver resv{ex};
|
||||
timer_type timer{ex};
|
||||
|
||||
boost::system::error_code ec;
|
||||
timer.expires_after(std::chrono::seconds{5});
|
||||
auto const addrs = co_await (resv.async_resolve(host, port) || timer.async_wait(redir(ec)));
|
||||
if (!ec)
|
||||
throw std::runtime_error("Resolve timeout");
|
||||
|
||||
timer.expires_after(std::chrono::seconds{5});
|
||||
co_await (net::async_connect(conn->next_layer(), std::get<0>(addrs)) || timer.async_wait(redir(ec)));
|
||||
if (!ec)
|
||||
throw std::runtime_error("Connect timeout");
|
||||
}
|
||||
|
||||
extern net::awaitable<void> async_main();
|
||||
|
||||
// Main function used in our examples.
|
||||
auto main() -> int
|
||||
{
|
||||
try {
|
||||
net::io_context ioc;
|
||||
net::co_spawn(ioc, async_main(), net::detached);
|
||||
ioc.run();
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << "Error: " << e.what() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
#else // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
auto main() -> int {std::cout << "Requires coroutine support." << std::endl; return 0;}
|
||||
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
32
examples/common.hpp
Normal file
32
examples/common.hpp
Normal file
@@ -0,0 +1,32 @@
|
||||
/* 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 AEDIS_EXAMPLES_COMMON_HPP
|
||||
#define AEDIS_EXAMPLES_COMMON_HPP
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#include <aedis.hpp>
|
||||
#include <memory>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
|
||||
using connection = boost::asio::use_awaitable_t<>::as_default_on_t<aedis::connection>;
|
||||
|
||||
auto
|
||||
connect(
|
||||
std::shared_ptr<connection> conn,
|
||||
std::string const& host,
|
||||
std::string const& port) -> boost::asio::awaitable<void>;
|
||||
|
||||
auto healthy_checker(std::shared_ptr<connection> conn) -> boost::asio::awaitable<void>;
|
||||
|
||||
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
#endif // AEDIS_EXAMPLES_COMMON_HPP
|
||||
@@ -4,60 +4,108 @@
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
#include <boost/asio/experimental/awaitable_operators.hpp>
|
||||
#include <aedis.hpp>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
#include <aedis.hpp>
|
||||
#include "print.hpp"
|
||||
|
||||
// Include this in no more than one .cpp file.
|
||||
#include <aedis/src.hpp>
|
||||
#include "common.hpp"
|
||||
|
||||
namespace net = boost::asio;
|
||||
using namespace net::experimental::awaitable_operators;
|
||||
using aedis::adapt;
|
||||
using aedis::resp3::request;
|
||||
using aedis::endpoint;
|
||||
using connection = aedis::connection<>;
|
||||
|
||||
auto main() -> int
|
||||
void print(std::map<std::string, std::string> const& cont)
|
||||
{
|
||||
try {
|
||||
std::vector<int> vec{1, 2, 3, 4, 5, 6};
|
||||
|
||||
std::map<std::string, int> map{{"key1", 10}, {"key2", 20}, {"key3", 30}};
|
||||
|
||||
// Sends and retrieves containers in the same request for
|
||||
// simplification.
|
||||
request req;
|
||||
req.push_range("RPUSH", "rpush-key", vec); // Sends
|
||||
req.push_range("HSET", "hset-key", map); // Sends
|
||||
req.push("MULTI");
|
||||
req.push("LRANGE", "rpush-key", 0, -1); // Retrieves
|
||||
req.push("HGETALL", "hset-key"); // Retrieves
|
||||
req.push("EXEC");
|
||||
req.push("QUIT");
|
||||
|
||||
std::tuple<
|
||||
aedis::ignore, // rpush
|
||||
aedis::ignore, // hset
|
||||
aedis::ignore, // multi
|
||||
aedis::ignore, // lrange
|
||||
aedis::ignore, // hgetall
|
||||
std::tuple<std::optional<std::vector<int>>, std::optional<std::map<std::string, int>>>, // exec
|
||||
aedis::ignore // quit
|
||||
> resp;
|
||||
|
||||
net::io_context ioc;
|
||||
connection db{ioc};
|
||||
endpoint ep{"127.0.0.1", "6379"};
|
||||
db.async_run(ep, req, adapt(resp), {}, [](auto ec, auto) {
|
||||
std::cout << ec.message() << std::endl;
|
||||
});
|
||||
ioc.run();
|
||||
|
||||
print(std::get<0>(std::get<5>(resp)).value());
|
||||
print(std::get<1>(std::get<5>(resp)).value());
|
||||
} catch (...) {
|
||||
std::cerr << "Error." << std::endl;
|
||||
}
|
||||
for (auto const& e: cont)
|
||||
std::cout << e.first << ": " << e.second << "\n";
|
||||
}
|
||||
|
||||
void print(std::vector<int> const& cont)
|
||||
{
|
||||
for (auto const& e: cont) std::cout << e << " ";
|
||||
std::cout << "\n";
|
||||
}
|
||||
|
||||
// Stores the content of some STL containers in Redis.
|
||||
auto store(std::shared_ptr<connection> conn) -> net::awaitable<void>
|
||||
{
|
||||
std::vector<int> vec
|
||||
{1, 2, 3, 4, 5, 6};
|
||||
|
||||
std::map<std::string, std::string> map
|
||||
{{"key1", "value1"}, {"key2", "value2"}, {"key3", "value3"}};
|
||||
|
||||
request req;
|
||||
req.get_config().cancel_on_connection_lost = true;
|
||||
req.push("HELLO", 3);
|
||||
req.push_range("RPUSH", "rpush-key", vec);
|
||||
req.push_range("HSET", "hset-key", map);
|
||||
req.push("QUIT");
|
||||
|
||||
co_await conn->async_exec(req);
|
||||
}
|
||||
|
||||
// Retrieves a Redis hash as an std::map.
|
||||
auto hgetall(std::shared_ptr<connection> conn) -> net::awaitable<void>
|
||||
{
|
||||
request req;
|
||||
req.get_config().cancel_on_connection_lost = true;
|
||||
req.push("HELLO", 3);
|
||||
req.push("HGETALL", "hset-key");
|
||||
req.push("QUIT");
|
||||
|
||||
std::tuple<aedis::ignore, std::map<std::string, std::string>, aedis::ignore> resp;
|
||||
|
||||
co_await conn->async_exec(req, adapt(resp));
|
||||
|
||||
print(std::get<1>(resp));
|
||||
}
|
||||
|
||||
// Retrieves in a transaction.
|
||||
auto transaction(std::shared_ptr<connection> conn) -> net::awaitable<void>
|
||||
{
|
||||
request req;
|
||||
req.get_config().cancel_on_connection_lost = true;
|
||||
req.push("HELLO", 3);
|
||||
req.push("MULTI");
|
||||
req.push("LRANGE", "rpush-key", 0, -1); // Retrieves
|
||||
req.push("HGETALL", "hset-key"); // Retrieves
|
||||
req.push("EXEC");
|
||||
req.push("QUIT");
|
||||
|
||||
std::tuple<
|
||||
aedis::ignore, // hello
|
||||
aedis::ignore, // multi
|
||||
aedis::ignore, // lrange
|
||||
aedis::ignore, // hgetall
|
||||
std::tuple<std::optional<std::vector<int>>, std::optional<std::map<std::string, std::string>>>, // exec
|
||||
aedis::ignore // quit
|
||||
> resp;
|
||||
|
||||
co_await conn->async_exec(req, adapt(resp));
|
||||
|
||||
print(std::get<0>(std::get<4>(resp)).value());
|
||||
print(std::get<1>(std::get<4>(resp)).value());
|
||||
}
|
||||
|
||||
net::awaitable<void> async_main()
|
||||
{
|
||||
auto conn = std::make_shared<connection>(co_await net::this_coro::executor);
|
||||
|
||||
// Uses short-lived connections to store and retrieve the
|
||||
// containers.
|
||||
co_await connect(conn, "127.0.0.1", "6379");
|
||||
co_await (conn->async_run() || store(conn));
|
||||
|
||||
co_await connect(conn, "127.0.0.1", "6379");
|
||||
co_await (conn->async_run() || hgetall(conn));
|
||||
|
||||
co_await connect(conn, "127.0.0.1", "6379");
|
||||
co_await (conn->async_run() || transaction(conn));
|
||||
}
|
||||
|
||||
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
|
||||
@@ -4,73 +4,58 @@
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
#include <boost/asio/experimental/awaitable_operators.hpp>
|
||||
#include <aedis.hpp>
|
||||
|
||||
// Include this in no more than one .cpp file.
|
||||
#include <aedis/src.hpp>
|
||||
#include "common.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_type = net::use_awaitable_t<>::as_default_on_t<net::signal_set>;
|
||||
using aedis::adapt;
|
||||
using aedis::resp3::request;
|
||||
using aedis::endpoint;
|
||||
using executor_type = net::io_context::executor_type;
|
||||
using socket_type = net::basic_stream_socket<net::ip::tcp, executor_type>;
|
||||
using tcp_socket = net::use_awaitable_t<executor_type>::as_default_on_t<socket_type>;
|
||||
using acceptor_type = net::basic_socket_acceptor<net::ip::tcp, executor_type>;
|
||||
using tcp_acceptor = net::use_awaitable_t<executor_type>::as_default_on_t<acceptor_type>;
|
||||
using awaitable_type = net::awaitable<void, executor_type>;
|
||||
using connection = aedis::connection<tcp_socket>;
|
||||
|
||||
awaitable_type echo_loop(tcp_socket socket, std::shared_ptr<connection> db)
|
||||
auto echo_server_session(tcp_socket socket, std::shared_ptr<connection> conn) -> net::awaitable<void>
|
||||
{
|
||||
request req;
|
||||
std::tuple<std::string> resp;
|
||||
std::string resp;
|
||||
|
||||
for (std::string buffer;;) {
|
||||
auto n = co_await net::async_read_until(socket, net::dynamic_buffer(buffer, 1024), "\n");
|
||||
req.push("PING", buffer);
|
||||
co_await db->async_exec(req, adapt(resp));
|
||||
co_await net::async_write(socket, net::buffer(std::get<0>(resp)));
|
||||
std::get<0>(resp).clear();
|
||||
auto tmp = std::tie(resp);
|
||||
co_await conn->async_exec(req, adapt(tmp));
|
||||
co_await net::async_write(socket, net::buffer(resp));
|
||||
resp.clear();
|
||||
req.clear();
|
||||
buffer.erase(0, n);
|
||||
}
|
||||
}
|
||||
|
||||
awaitable_type listener(std::shared_ptr<connection> db)
|
||||
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_loop(co_await acc.async_accept(), db), net::detached);
|
||||
net::co_spawn(ex, echo_server_session(co_await acc.async_accept(), conn), net::detached);
|
||||
}
|
||||
|
||||
auto main() -> int
|
||||
auto async_main() -> net::awaitable<void>
|
||||
{
|
||||
try {
|
||||
net::io_context ioc{BOOST_ASIO_CONCURRENCY_HINT_UNSAFE_IO};
|
||||
auto db = std::make_shared<connection>(ioc);
|
||||
endpoint ep{"127.0.0.1", "6379"};
|
||||
db->async_run(ep, {}, [&](auto const& ec) {
|
||||
std::clog << ec.message() << std::endl;
|
||||
ioc.stop();
|
||||
});
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
auto conn = std::make_shared<connection>(ex);
|
||||
signal_set_type sig{ex, SIGINT, SIGTERM};
|
||||
|
||||
net::signal_set signals(ioc, SIGINT, SIGTERM);
|
||||
signals.async_wait([&](auto, auto){ ioc.stop(); });
|
||||
request req;
|
||||
req.get_config().cancel_on_connection_lost = true;
|
||||
req.push("HELLO", 3);
|
||||
|
||||
co_spawn(ioc, listener(db), net::detached);
|
||||
ioc.run();
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
}
|
||||
co_await connect(conn, "127.0.0.1", "6379");
|
||||
co_await ((conn->async_run() || listener(conn) || healthy_checker(conn) ||
|
||||
sig.async_wait()) && conn->async_exec(req));
|
||||
}
|
||||
|
||||
#else // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
auto main() -> int {std::cout << "Requires coroutine support." << std::endl; return 1;}
|
||||
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
|
||||
@@ -4,40 +4,32 @@
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <tuple>
|
||||
#include <string>
|
||||
#include <boost/asio.hpp>
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
#include <boost/asio/experimental/awaitable_operators.hpp>
|
||||
#include <aedis.hpp>
|
||||
|
||||
// Include this in no more than one .cpp file.
|
||||
#include <aedis/src.hpp>
|
||||
#include "common.hpp"
|
||||
|
||||
namespace net = boost::asio;
|
||||
|
||||
using namespace net::experimental::awaitable_operators;
|
||||
using aedis::adapt;
|
||||
using aedis::resp3::request;
|
||||
using aedis::endpoint;
|
||||
using connection = aedis::connection<>;
|
||||
|
||||
auto main() -> int
|
||||
net::awaitable<void> async_main()
|
||||
{
|
||||
try {
|
||||
net::io_context ioc;
|
||||
connection db{ioc};
|
||||
request req;
|
||||
req.get_config().cancel_on_connection_lost = true;
|
||||
req.push("HELLO", 3);
|
||||
req.push("PING", "Hello world");
|
||||
req.push("QUIT");
|
||||
|
||||
request req;
|
||||
req.push("PING");
|
||||
req.push("QUIT");
|
||||
std::tuple<aedis::ignore, std::string, aedis::ignore> resp;
|
||||
|
||||
std::tuple<std::string, aedis::ignore> resp;
|
||||
db.async_run({"127.0.0.1", "6379"}, req, adapt(resp), {}, [](auto ec, auto) {
|
||||
std::cout << ec.message() << std::endl;
|
||||
});
|
||||
auto conn = std::make_shared<connection>(co_await net::this_coro::executor);
|
||||
co_await connect(conn, "127.0.0.1", "6379");
|
||||
co_await (conn->async_run() || conn->async_exec(req, adapt(resp)));
|
||||
|
||||
ioc.run();
|
||||
|
||||
std::cout << std::get<0>(resp) << std::endl;
|
||||
} catch (...) {
|
||||
std::cerr << "Error" << std::endl;
|
||||
}
|
||||
std::cout << "PING: " << std::get<1>(resp) << std::endl;
|
||||
}
|
||||
|
||||
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
|
||||
@@ -10,13 +10,14 @@
|
||||
#include <boost/asio.hpp>
|
||||
#include <aedis.hpp>
|
||||
|
||||
// TODO: Fix this after updating to 1.80.
|
||||
|
||||
// Include this in no more than one .cpp file.
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
namespace net = boost::asio;
|
||||
using aedis::adapt;
|
||||
using aedis::resp3::request;
|
||||
using aedis::endpoint;
|
||||
using connection = aedis::connection<>;
|
||||
|
||||
template <class Adapter>
|
||||
@@ -38,11 +39,13 @@ int main()
|
||||
|
||||
connection conn{ioc};
|
||||
std::thread t{[&]() {
|
||||
conn.async_run({"127.0.0.1", "6379"}, {}, logger);
|
||||
conn.async_run(logger);
|
||||
ioc.run();
|
||||
}};
|
||||
|
||||
request req;
|
||||
req.get_config().cancel_on_connection_lost = true;
|
||||
req.push("HELLO", 3);
|
||||
req.push("PING");
|
||||
req.push("QUIT");
|
||||
|
||||
|
||||
@@ -6,50 +6,54 @@
|
||||
|
||||
#include <tuple>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
#include <boost/asio/experimental/awaitable_operators.hpp>
|
||||
#include <boost/asio/ssl.hpp>
|
||||
|
||||
#include <aedis.hpp>
|
||||
#include <aedis/ssl/connection.hpp>
|
||||
|
||||
// Include this in no more than one .cpp file.
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
namespace net = boost::asio;
|
||||
using namespace net::experimental::awaitable_operators;
|
||||
using resolver = net::use_awaitable_t<>::as_default_on_t<net::ip::tcp::resolver>;
|
||||
using aedis::adapt;
|
||||
using aedis::resp3::request;
|
||||
using aedis::endpoint;
|
||||
using connection = aedis::ssl::connection<net::ssl::stream<net::ip::tcp::socket>>;
|
||||
using connection = net::use_awaitable_t<>::as_default_on_t<aedis::ssl::connection>;
|
||||
|
||||
bool verify_certificate(bool, net::ssl::verify_context&)
|
||||
auto verify_certificate(bool, net::ssl::verify_context&) -> bool
|
||||
{
|
||||
std::cout << "set_verify_callback" << std::endl;
|
||||
return true;
|
||||
}
|
||||
|
||||
auto main() -> int
|
||||
net::awaitable<void> async_main()
|
||||
{
|
||||
try {
|
||||
net::io_context ioc;
|
||||
request req;
|
||||
req.get_config().cancel_on_connection_lost = true;
|
||||
req.push("HELLO", 3, "AUTH", "aedis", "aedis");
|
||||
req.push("PING");
|
||||
req.push("QUIT");
|
||||
|
||||
net::ssl::context ctx{net::ssl::context::sslv23};
|
||||
std::tuple<aedis::ignore, std::string, aedis::ignore> resp;
|
||||
|
||||
connection conn{ioc, ctx};
|
||||
conn.next_layer().set_verify_mode(net::ssl::verify_peer);
|
||||
conn.next_layer().set_verify_callback(verify_certificate);
|
||||
// Resolve
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
resolver resv{ex};
|
||||
auto const endpoints = co_await resv.async_resolve("db.occase.de", "6380");
|
||||
|
||||
request req;
|
||||
req.push("PING");
|
||||
req.push("QUIT");
|
||||
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);
|
||||
|
||||
std::tuple<std::string, aedis::ignore> resp;
|
||||
conn.async_run({"127.0.0.1", "6379"}, req, adapt(resp), {}, [&](auto ec, auto) {
|
||||
std::cout << ec.message() << std::endl;
|
||||
});
|
||||
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, adapt(resp)));
|
||||
|
||||
ioc.run();
|
||||
|
||||
std::cout << "Response: " << std::get<0>(resp) << std::endl;
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << "Error: " << e.what() << std::endl;
|
||||
}
|
||||
std::cout << "Response: " << std::get<1>(resp) << std::endl;
|
||||
}
|
||||
|
||||
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
|
||||
63
examples/low_level_async.cpp
Normal file
63
examples/low_level_async.cpp
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 <string>
|
||||
#include <iostream>
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
|
||||
#include <aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
namespace net = boost::asio;
|
||||
namespace resp3 = aedis::resp3;
|
||||
using endpoints = net::ip::tcp::resolver::results_type;
|
||||
using tcp_socket = net::use_awaitable_t<>::as_default_on_t<net::ip::tcp::socket>;
|
||||
using aedis::resp3::request;
|
||||
using aedis::adapter::adapt2;
|
||||
using net::ip::tcp;
|
||||
|
||||
net::awaitable<void> ping(endpoints const& addrs)
|
||||
{
|
||||
tcp_socket socket{co_await net::this_coro::executor};
|
||||
net::connect(socket, addrs);
|
||||
|
||||
// Creates the request and writes to the socket.
|
||||
request req;
|
||||
req.push("HELLO", 3);
|
||||
req.push("PING");
|
||||
req.push("QUIT");
|
||||
co_await resp3::async_write(socket, req);
|
||||
|
||||
// Responses
|
||||
std::string buffer, resp;
|
||||
|
||||
// Reads the responses to all commands in the request.
|
||||
auto dbuffer = net::dynamic_buffer(buffer);
|
||||
co_await resp3::async_read(socket, dbuffer);
|
||||
co_await resp3::async_read(socket, dbuffer, adapt2(resp));
|
||||
co_await resp3::async_read(socket, dbuffer);
|
||||
|
||||
std::cout << "Ping: " << resp << std::endl;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
try {
|
||||
net::io_context ioc;
|
||||
net::ip::tcp::resolver resv{ioc};
|
||||
auto const addrs = resv.resolve("127.0.0.1", "6379");
|
||||
net::co_spawn(ioc, ping(addrs), net::detached);
|
||||
ioc.run();
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << "Error: " << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
#else // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
auto main() -> int {std::cout << "Requires coroutine support." << std::endl; return 0;}
|
||||
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
0
examples/main.cpp
Normal file
0
examples/main.cpp
Normal file
@@ -1,65 +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 <map>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
|
||||
#include <aedis/resp3/type.hpp>
|
||||
#include <aedis/resp3/node.hpp>
|
||||
|
||||
// Some functions to make the examples less repetitive.
|
||||
|
||||
namespace net = boost::asio;
|
||||
using aedis::resp3::node;
|
||||
|
||||
void print_aggr(std::vector<aedis::resp3::node<std::string>>& v)
|
||||
{
|
||||
if (std::empty(v))
|
||||
return;
|
||||
|
||||
auto const m = aedis::resp3::element_multiplicity(v.front().data_type);
|
||||
for (auto i = 0lu; i < m * v.front().aggregate_size; ++i)
|
||||
std::cout << v[i + 1].value << " ";
|
||||
std::cout << "\n";
|
||||
v.clear();
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void print(std::vector<T> const& cont)
|
||||
{
|
||||
for (auto const& e: cont) std::cout << e << " ";
|
||||
std::cout << "\n";
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void print(std::set<T> const& cont)
|
||||
{
|
||||
for (auto const& e: cont) std::cout << e << "\n";
|
||||
}
|
||||
|
||||
template <class T, class U>
|
||||
void print(std::map<T, U> const& cont)
|
||||
{
|
||||
for (auto const& e: cont)
|
||||
std::cout << e.first << ": " << e.second << "\n";
|
||||
}
|
||||
|
||||
void print(std::string const& e)
|
||||
{
|
||||
std::cout << e << std::endl;
|
||||
}
|
||||
|
||||
void print_push(std::vector<aedis::resp3::node<std::string>>& resp)
|
||||
{
|
||||
std::cout
|
||||
<< "Push type: " << resp.at(1).value << "\n"
|
||||
<< "Channel: " << resp.at(2).value << "\n"
|
||||
<< "Message: " << resp.at(3).value << "\n"
|
||||
<< std::endl;
|
||||
}
|
||||
70
examples/resolve_with_sentinel.cpp
Normal file
70
examples/resolve_with_sentinel.cpp
Normal file
@@ -0,0 +1,70 @@
|
||||
/* 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/asio.hpp>
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
#include <boost/asio/experimental/awaitable_operators.hpp>
|
||||
#include <aedis.hpp>
|
||||
#include "common.hpp"
|
||||
|
||||
namespace net = boost::asio;
|
||||
using namespace net::experimental::awaitable_operators;
|
||||
using endpoints = net::ip::tcp::resolver::results_type;
|
||||
using aedis::adapt;
|
||||
using aedis::resp3::request;
|
||||
|
||||
auto redir(boost::system::error_code& ec)
|
||||
{ return net::redirect_error(net::use_awaitable, ec); }
|
||||
|
||||
struct endpoint {
|
||||
std::string host;
|
||||
std::string port;
|
||||
};
|
||||
|
||||
// For more info see
|
||||
// - https://redis.io/docs/manual/sentinel.
|
||||
// - https://redis.io/docs/reference/sentinel-clients.
|
||||
auto resolve_master_address(std::vector<endpoint> const& endpoints) -> net::awaitable<endpoint>
|
||||
{
|
||||
request req;
|
||||
req.get_config().cancel_on_connection_lost = true;
|
||||
req.push("SENTINEL", "get-master-addr-by-name", "mymaster");
|
||||
req.push("QUIT");
|
||||
|
||||
auto conn = std::make_shared<connection>(co_await net::this_coro::executor);
|
||||
|
||||
std::tuple<std::optional<std::array<std::string, 2>>, aedis::ignore> addr;
|
||||
for (auto ep : endpoints) {
|
||||
boost::system::error_code ec;
|
||||
co_await connect(conn, ep.host, ep.port);
|
||||
co_await (conn->async_run() && conn->async_exec(req, adapt(addr), redir(ec)));
|
||||
conn->reset_stream();
|
||||
if (std::get<0>(addr))
|
||||
co_return endpoint{std::get<0>(addr).value().at(0), std::get<0>(addr).value().at(1)};
|
||||
}
|
||||
|
||||
co_return endpoint{};
|
||||
}
|
||||
|
||||
auto async_main() -> net::awaitable<void>
|
||||
{
|
||||
// A list of sentinel addresses from which only one is responsive
|
||||
// to simulate sentinels that are down.
|
||||
std::vector<endpoint> const endpoints
|
||||
{ {"foo", "26379"}
|
||||
, {"bar", "26379"}
|
||||
, {"127.0.0.1", "26379"}
|
||||
};
|
||||
|
||||
auto const ep = co_await resolve_master_address(endpoints);
|
||||
|
||||
std::clog
|
||||
<< "Host: " << ep.host << "\n"
|
||||
<< "Port: " << ep.port << "\n"
|
||||
<< std::flush;
|
||||
}
|
||||
|
||||
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
@@ -4,25 +4,26 @@
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
#include <boost/asio/experimental/awaitable_operators.hpp>
|
||||
#include <boost/json.hpp>
|
||||
#include <aedis.hpp>
|
||||
#include "common.hpp"
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <set>
|
||||
#include <iterator>
|
||||
#include <string>
|
||||
#include <boost/json.hpp>
|
||||
#include <boost/json/src.hpp>
|
||||
#include <aedis.hpp>
|
||||
#include "print.hpp"
|
||||
|
||||
// Include this in no more than one .cpp file.
|
||||
#include <aedis/src.hpp>
|
||||
#include <boost/json/src.hpp>
|
||||
|
||||
namespace net = boost::asio;
|
||||
using namespace net::experimental::awaitable_operators;
|
||||
using aedis::resp3::request;
|
||||
using aedis::adapt;
|
||||
using aedis::endpoint;
|
||||
using connection = aedis::connection<>;
|
||||
using namespace boost::json;
|
||||
|
||||
struct user {
|
||||
@@ -43,21 +44,21 @@ void tag_invoke(value_from_tag, value& jv, user const& u)
|
||||
template<class T>
|
||||
void extract(object const& obj, T& t, boost::string_view key)
|
||||
{
|
||||
t = value_to<T>(obj.at(key));
|
||||
t = value_to<T>(obj.at(key));
|
||||
}
|
||||
|
||||
user tag_invoke(value_to_tag<user>, value const& jv)
|
||||
auto tag_invoke(value_to_tag<user>, value const& jv)
|
||||
{
|
||||
user u;
|
||||
object const& obj = jv.as_object();
|
||||
extract(obj, u.name, "name");
|
||||
extract(obj, u.age, "age");
|
||||
extract(obj, u.country, "country");
|
||||
return u;
|
||||
user u;
|
||||
object const& obj = jv.as_object();
|
||||
extract(obj, u.name, "name");
|
||||
extract(obj, u.age, "age");
|
||||
extract(obj, u.country, "country");
|
||||
return u;
|
||||
}
|
||||
|
||||
// Serializes
|
||||
void to_bulk(std::string& to, user const& u)
|
||||
void to_bulk(std::pmr::string& to, user const& u)
|
||||
{
|
||||
aedis::resp3::to_bulk(to, serialize(value_from(u)));
|
||||
}
|
||||
@@ -69,7 +70,7 @@ void from_bulk(user& u, boost::string_view sv, boost::system::error_code&)
|
||||
u = value_to<user>(jv);
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, user const& u)
|
||||
auto operator<<(std::ostream& os, user const& u) -> std::ostream&
|
||||
{
|
||||
os << "Name: " << u.name << "\n"
|
||||
<< "Age: " << u.age << "\n"
|
||||
@@ -78,20 +79,18 @@ std::ostream& operator<<(std::ostream& os, user const& u)
|
||||
return os;
|
||||
}
|
||||
|
||||
bool operator<(user const& a, user const& b)
|
||||
auto operator<(user const& a, user const& b)
|
||||
{
|
||||
return std::tie(a.name, a.age, a.country) < std::tie(b.name, b.age, b.country);
|
||||
}
|
||||
|
||||
int main()
|
||||
net::awaitable<void> async_main()
|
||||
{
|
||||
net::io_context ioc;
|
||||
connection db{ioc};
|
||||
|
||||
std::set<user> users
|
||||
{{"Joao", "58", "Brazil"} , {"Serge", "60", "France"}};
|
||||
|
||||
request req;
|
||||
req.get_config().cancel_on_connection_lost = true;
|
||||
req.push("HELLO", 3);
|
||||
req.push_range("SADD", "sadd-key", users); // Sends
|
||||
req.push("SMEMBERS", "sadd-key"); // Retrieves
|
||||
@@ -99,13 +98,12 @@ int main()
|
||||
|
||||
std::tuple<aedis::ignore, int, std::set<user>, std::string> resp;
|
||||
|
||||
endpoint ep{"127.0.0.1", "6379"};
|
||||
db.async_run(ep, req, adapt(resp), {}, [](auto ec, auto) {
|
||||
std::cout << ec.message() << std::endl;
|
||||
});
|
||||
auto conn = std::make_shared<connection>(co_await net::this_coro::executor);
|
||||
co_await connect(conn, "127.0.0.1", "6379");
|
||||
co_await (conn->async_run() || conn->async_exec(req, adapt(resp)));
|
||||
|
||||
ioc.run();
|
||||
|
||||
// Print
|
||||
print(std::get<2>(resp));
|
||||
for (auto const& e: std::get<2>(resp))
|
||||
std::cout << e << "\n";
|
||||
}
|
||||
|
||||
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
|
||||
@@ -4,27 +4,21 @@
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
#include <tuple>
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
#include <boost/asio/experimental/awaitable_operators.hpp>
|
||||
#include <aedis.hpp>
|
||||
#include "print.hpp"
|
||||
|
||||
// Include this in no more than one .cpp file.
|
||||
#include <aedis/src.hpp>
|
||||
#include "common.hpp"
|
||||
|
||||
namespace net = boost::asio;
|
||||
using namespace net::experimental::awaitable_operators;
|
||||
using resolver = net::use_awaitable_t<>::as_default_on_t<net::ip::tcp::resolver>;
|
||||
using signal_set_type = net::use_awaitable_t<>::as_default_on_t<net::signal_set>;
|
||||
using timer_type = net::use_awaitable_t<>::as_default_on_t<net::steady_timer>;
|
||||
using aedis::adapt;
|
||||
using aedis::resp3::request;
|
||||
using aedis::resp3::node;
|
||||
using aedis::endpoint;
|
||||
using tcp_socket = net::use_awaitable_t<>::as_default_on_t<net::ip::tcp::socket>;
|
||||
using stimer = net::use_awaitable_t<>::as_default_on_t<net::steady_timer>;
|
||||
using connection = aedis::connection<tcp_socket>;
|
||||
|
||||
/* This example will subscribe and read pushes indefinitely.
|
||||
*
|
||||
@@ -43,53 +37,36 @@ using connection = aedis::connection<tcp_socket>;
|
||||
*/
|
||||
|
||||
// Receives pushes.
|
||||
net::awaitable<void> push_receiver(std::shared_ptr<connection> db)
|
||||
auto receiver(std::shared_ptr<connection> conn) -> net::awaitable<void>
|
||||
{
|
||||
for (std::vector<node<std::string>> resp;;) {
|
||||
co_await db->async_receive_push(adapt(resp));
|
||||
print_push(resp);
|
||||
co_await conn->async_receive(adapt(resp));
|
||||
std::cout << resp.at(1).value << " " << resp.at(2).value << " " << resp.at(3).value << std::endl;
|
||||
resp.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// See
|
||||
// - https://redis.io/docs/manual/sentinel.
|
||||
// - https://redis.io/docs/reference/sentinel-clients.
|
||||
net::awaitable<void> reconnect(std::shared_ptr<connection> db)
|
||||
auto async_main() -> net::awaitable<void>
|
||||
{
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
auto conn = std::make_shared<connection>(ex);
|
||||
signal_set_type sig{ex, SIGINT, SIGTERM};
|
||||
timer_type timer{ex};
|
||||
|
||||
request req;
|
||||
req.get_config().cancel_on_connection_lost = true;
|
||||
req.push("HELLO", 3);
|
||||
req.push("SUBSCRIBE", "channel");
|
||||
|
||||
stimer timer{co_await net::this_coro::executor};
|
||||
endpoint ep{"127.0.0.1", "6379"};
|
||||
// The loop will reconnect on connection lost. To exit type Ctrl-C twice.
|
||||
for (;;) {
|
||||
boost::system::error_code ec;
|
||||
co_await db->async_run(ep, req, adapt(), {}, net::redirect_error(net::use_awaitable, ec));
|
||||
db->reset_stream();
|
||||
std::cout << ec.message() << std::endl;
|
||||
co_await connect(conn, "127.0.0.1", "6379");
|
||||
co_await ((conn->async_run() || healthy_checker(conn) || sig.async_wait() ||
|
||||
receiver(conn)) && conn->async_exec(req));
|
||||
conn->reset_stream();
|
||||
timer.expires_after(std::chrono::seconds{1});
|
||||
co_await timer.async_wait();
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
try {
|
||||
net::io_context ioc;
|
||||
auto db = std::make_shared<connection>(ioc);
|
||||
|
||||
net::co_spawn(ioc, push_receiver(db), net::detached);
|
||||
net::co_spawn(ioc, reconnect(db), net::detached);
|
||||
|
||||
net::signal_set signals(ioc, SIGINT, SIGTERM);
|
||||
signals.async_wait([&](auto, auto){ ioc.stop(); });
|
||||
|
||||
ioc.run();
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << "Error: " << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
#else // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
int main() {std::cout << "Requires coroutine support." << std::endl; return 1;}
|
||||
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
|
||||
@@ -1,119 +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 <string>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
#include <tuple>
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
#include <aedis.hpp>
|
||||
#include "print.hpp"
|
||||
|
||||
// Include this in no more than one .cpp file.
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
namespace net = boost::asio;
|
||||
using aedis::adapt;
|
||||
using aedis::resp3::request;
|
||||
using aedis::resp3::node;
|
||||
using aedis::endpoint;
|
||||
using tcp_socket = net::use_awaitable_t<>::as_default_on_t<net::ip::tcp::socket>;
|
||||
using stimer = net::use_awaitable_t<>::as_default_on_t<net::steady_timer>;
|
||||
using connection = aedis::connection<tcp_socket>;
|
||||
|
||||
// Connects to a Redis instance over sentinel and performs failover in
|
||||
// case of disconnection, see
|
||||
// https://redis.io/docs/reference/sentinel-clients. This example
|
||||
// assumes a sentinel and a redis server running on localhost.
|
||||
|
||||
net::awaitable<void> receive_pushes(std::shared_ptr<connection> db)
|
||||
{
|
||||
for (std::vector<node<std::string>> resp;;) {
|
||||
co_await db->async_receive_push(adapt(resp));
|
||||
print_push(resp);
|
||||
resp.clear();
|
||||
}
|
||||
}
|
||||
|
||||
net::awaitable<endpoint> resolve()
|
||||
{
|
||||
// A list of sentinel addresses from which only one is responsive
|
||||
// to simulate sentinels that are down.
|
||||
std::vector<endpoint> const endpoints
|
||||
{ {"foo", "26379"}
|
||||
, {"bar", "26379"}
|
||||
, {"127.0.0.1", "26379"}
|
||||
};
|
||||
|
||||
request req1;
|
||||
req1.push("SENTINEL", "get-master-addr-by-name", "mymaster");
|
||||
req1.push("QUIT");
|
||||
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
connection conn{ex};
|
||||
|
||||
std::tuple<std::optional<std::array<std::string, 2>>, aedis::ignore> addr;
|
||||
for (auto ep : endpoints) {
|
||||
boost::system::error_code ec;
|
||||
co_await conn.async_run(ep, req1, adapt(addr), {}, net::redirect_error(net::use_awaitable, ec));
|
||||
conn.reset_stream();
|
||||
std::cout << ec.message() << std::endl;
|
||||
if (std::get<0>(addr))
|
||||
break;
|
||||
}
|
||||
|
||||
endpoint ep;
|
||||
if (std::get<0>(addr)) {
|
||||
ep.host = std::get<0>(addr).value().at(0);
|
||||
ep.port = std::get<0>(addr).value().at(1);
|
||||
}
|
||||
|
||||
co_return ep;
|
||||
}
|
||||
|
||||
net::awaitable<void> reconnect(std::shared_ptr<connection> db)
|
||||
{
|
||||
request req2;
|
||||
req2.push("SUBSCRIBE", "channel");
|
||||
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
stimer timer{ex};
|
||||
for (;;) {
|
||||
auto ep = co_await net::co_spawn(ex, resolve(), net::use_awaitable);
|
||||
if (!aedis::is_valid(ep)) {
|
||||
std::clog << "Can't resolve master name" << std::endl;
|
||||
co_return;
|
||||
}
|
||||
|
||||
boost::system::error_code ec;
|
||||
co_await db->async_run(ep, req2, adapt(), {}, net::redirect_error(net::use_awaitable, ec));
|
||||
std::cout << ec.message() << std::endl;
|
||||
std::cout << "Starting the failover." << std::endl;
|
||||
timer.expires_after(std::chrono::seconds{1});
|
||||
co_await timer.async_wait();
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
try {
|
||||
net::io_context ioc;
|
||||
auto db = std::make_shared<connection>(ioc);
|
||||
net::co_spawn(ioc, receive_pushes(db), net::detached);
|
||||
net::co_spawn(ioc, reconnect(db), net::detached);
|
||||
net::signal_set signals(ioc, SIGINT, SIGTERM);
|
||||
signals.async_wait([&](auto, auto){ ioc.stop(); });
|
||||
ioc.run();
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << "Error: " << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
#else // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
int main() {std::cout << "Requires coroutine support." << std::endl; return 1;}
|
||||
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
@@ -204,8 +204,13 @@ inline auto adapt(std::size_t max_read_size = (std::numeric_limits<std::size_t>:
|
||||
/** @brief Adapts a type to be used as a response.
|
||||
* @ingroup high-level-api
|
||||
*
|
||||
* The type T can be any STL container, any integer type and
|
||||
* \c std::string
|
||||
* The type T must be either
|
||||
*
|
||||
* 1. a std::tuple<T1, T2, T3, ...> or
|
||||
* 2. std::vector<node<String>>
|
||||
*
|
||||
* The types T1, T2, etc can be any STL container, any integer type
|
||||
* and \c std::string
|
||||
*
|
||||
* @param t Tuple containing the responses.
|
||||
* @param max_read_size Specifies the maximum size of the read
|
||||
|
||||
@@ -49,11 +49,10 @@ auto parse_double(
|
||||
// Serialization.
|
||||
|
||||
template <class T>
|
||||
typename std::enable_if<std::is_integral<T>::value, void>::type
|
||||
from_bulk(
|
||||
auto from_bulk(
|
||||
T& i,
|
||||
boost::string_view sv,
|
||||
boost::system::error_code& ec)
|
||||
boost::system::error_code& ec) -> typename std::enable_if<std::is_integral<T>::value, void>::type
|
||||
{
|
||||
i = resp3::detail::parse_uint(sv.data(), sv.size(), ec);
|
||||
}
|
||||
@@ -106,10 +105,9 @@ private:
|
||||
|
||||
public:
|
||||
explicit general_aggregate(Result* c = nullptr): result_(c) {}
|
||||
void operator()(resp3::node<boost::string_view> const& n, boost::system::error_code& ec)
|
||||
void operator()(resp3::node<boost::string_view> const& n, boost::system::error_code&)
|
||||
{
|
||||
result_->push_back({n.data_type, n.aggregate_size, n.depth, std::string{std::cbegin(n.value), std::cend(n.value)}});
|
||||
set_on_resp3_error(n.data_type, ec);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
namespace aedis::adapter::detail {
|
||||
|
||||
struct ignore {};
|
||||
using ignore = std::decay_t<decltype(std::ignore)>;
|
||||
|
||||
/* Traits class for response objects.
|
||||
*
|
||||
@@ -29,18 +29,15 @@ struct ignore {};
|
||||
*/
|
||||
template <class ResponseType>
|
||||
struct response_traits {
|
||||
using adapter_type = adapter::detail::wrapper<ResponseType>;
|
||||
using adapter_type = adapter::detail::wrapper<typename std::decay<ResponseType>::type>;
|
||||
static auto adapt(ResponseType& r) noexcept { return adapter_type{&r}; }
|
||||
};
|
||||
|
||||
template <class T>
|
||||
using adapter_t = typename response_traits<T>::adapter_type;
|
||||
|
||||
template <>
|
||||
struct response_traits<ignore> {
|
||||
using response_type = ignore;
|
||||
using adapter_type = resp3::detail::ignore_response;
|
||||
static auto adapt(response_type&) noexcept { return adapter_type{}; }
|
||||
static auto adapt(response_type) noexcept { return adapter_type{}; }
|
||||
};
|
||||
|
||||
template <class T>
|
||||
@@ -64,10 +61,13 @@ struct response_traits<void> {
|
||||
static auto adapt() noexcept { return adapter_type{}; }
|
||||
};
|
||||
|
||||
template <class T>
|
||||
using adapter_t = typename response_traits<std::decay_t<T>>::adapter_type;
|
||||
|
||||
// Duplicated here to avoid circular include dependency.
|
||||
template<class T>
|
||||
auto internal_adapt(T& t) noexcept
|
||||
{ return response_traits<T>::adapt(t); }
|
||||
{ return response_traits<std::decay_t<T>>::adapt(t); }
|
||||
|
||||
template <std::size_t N>
|
||||
struct assigner {
|
||||
|
||||
@@ -22,50 +22,45 @@ namespace aedis {
|
||||
* commands can be sent at any time. For more details, please see the
|
||||
* documentation of each individual function.
|
||||
*
|
||||
* @remarks This class exposes only asynchronous member functions,
|
||||
* synchronous communications with the Redis server is provided by
|
||||
* the `aedis::sync` class.
|
||||
*
|
||||
* @tparam Derived class.
|
||||
*
|
||||
* @tparam AsyncReadWriteStream A stream that supports reading and
|
||||
* writing.
|
||||
*/
|
||||
template <class AsyncReadWriteStream = boost::asio::ip::tcp::socket>
|
||||
class connection :
|
||||
template <class AsyncReadWriteStream>
|
||||
class basic_connection :
|
||||
private detail::connection_base<
|
||||
typename AsyncReadWriteStream::executor_type,
|
||||
connection<AsyncReadWriteStream>> {
|
||||
basic_connection<AsyncReadWriteStream>> {
|
||||
public:
|
||||
/// Executor type.
|
||||
using executor_type = typename AsyncReadWriteStream::executor_type;
|
||||
|
||||
/// Type of the next layer
|
||||
using next_layer_type = AsyncReadWriteStream;
|
||||
using base_type = detail::connection_base<executor_type, connection<AsyncReadWriteStream>>;
|
||||
|
||||
/** \brief Connection configuration parameters.
|
||||
*/
|
||||
struct timeouts {
|
||||
/// Timeout of the resolve operation.
|
||||
std::chrono::steady_clock::duration resolve_timeout = std::chrono::seconds{10};
|
||||
|
||||
/// Timeout of the connect operation.
|
||||
std::chrono::steady_clock::duration connect_timeout = std::chrono::seconds{10};
|
||||
|
||||
/// Timeout of the resp3 handshake operation.
|
||||
std::chrono::steady_clock::duration resp3_handshake_timeout = std::chrono::seconds{2};
|
||||
|
||||
/// Time interval of ping operations.
|
||||
std::chrono::steady_clock::duration ping_interval = std::chrono::seconds{1};
|
||||
/// Rebinds the socket type to another executor.
|
||||
template <class Executor1>
|
||||
struct rebind_executor
|
||||
{
|
||||
/// The socket type when rebound to the specified executor.
|
||||
using other = basic_connection<typename next_layer_type::template rebind_executor<Executor1>::other>;
|
||||
};
|
||||
|
||||
using base_type = detail::connection_base<executor_type, basic_connection<AsyncReadWriteStream>>;
|
||||
|
||||
/// Constructor
|
||||
explicit connection(executor_type ex)
|
||||
: base_type{ex}
|
||||
explicit
|
||||
basic_connection(
|
||||
executor_type ex,
|
||||
std::pmr::memory_resource* resource = std::pmr::get_default_resource())
|
||||
: base_type{ex, resource}
|
||||
, stream_{ex}
|
||||
{}
|
||||
|
||||
explicit connection(boost::asio::io_context& ioc)
|
||||
: connection(ioc.get_executor())
|
||||
explicit
|
||||
basic_connection(
|
||||
boost::asio::io_context& ioc,
|
||||
std::pmr::memory_resource* resource = std::pmr::get_default_resource())
|
||||
: basic_connection(ioc.get_executor(), resource)
|
||||
{ }
|
||||
|
||||
/// Returns the associated executor.
|
||||
@@ -87,46 +82,12 @@ public:
|
||||
/// Returns a const reference to the next layer.
|
||||
auto next_layer() const noexcept -> auto const& { return stream_; }
|
||||
|
||||
/** @brief Starts communication with the Redis server asynchronously.
|
||||
/** @brief Establishes a connection with the Redis server asynchronously.
|
||||
*
|
||||
* This function performs the following steps
|
||||
* This function will start reading from the socket and executes
|
||||
* all requests that have been started prior to this function
|
||||
* call.
|
||||
*
|
||||
* @li Resolves the Redis host as of `async_resolve` with the
|
||||
* timeout passed in the base class `connection::timeouts::resolve_timeout`.
|
||||
*
|
||||
* @li Connects to one of the endpoints returned by the resolve
|
||||
* operation with the timeout passed in the base class
|
||||
* `connection::timeouts::connect_timeout`.
|
||||
*
|
||||
* @li Performs a RESP3 handshake by sending a
|
||||
* [HELLO](https://redis.io/commands/hello/) command with protocol
|
||||
* version 3 and the credentials contained in the
|
||||
* `aedis::endpoint` object. The timeout used is the one specified
|
||||
* in `connection::timeouts::resp3_handshake_timeout`.
|
||||
*
|
||||
* @li Erases any password that may be contained in
|
||||
* `endpoint::password`.
|
||||
*
|
||||
* @li Checks whether the server role corresponds to the one
|
||||
* specifed in the `endpoint`. If `endpoint::role` is left empty,
|
||||
* no check is performed. If the role role is different than the
|
||||
* expected `async_run` will complete with
|
||||
* `error::unexpected_server_role`.
|
||||
*
|
||||
* @li Starts healthy checks with a timeout twice the value of
|
||||
* `connection::timeouts::ping_interval`. If no data is received during that
|
||||
* time interval `connection::async_run` completes with
|
||||
* `error::idle_timeout`.
|
||||
*
|
||||
* @li Starts the healthy check operation that sends the
|
||||
* [PING](https://redis.io/commands/ping/) to Redis with a
|
||||
* frequency equal to `connection::timeouts::ping_interval`.
|
||||
*
|
||||
* @li Starts reading from the socket and executes all requests
|
||||
* that have been started prior to this function call.
|
||||
*
|
||||
* @param ep Redis endpoint.
|
||||
* @param ts Timeouts used by the operations.
|
||||
* @param token Completion token.
|
||||
*
|
||||
* The completion token must have the following signature
|
||||
@@ -134,48 +95,15 @@ public:
|
||||
* @code
|
||||
* void f(boost::system::error_code);
|
||||
* @endcode
|
||||
*
|
||||
* This function will complete when the connection is lost as
|
||||
* follows. If the error is boost::asio::error::eof this function
|
||||
* will complete without error.
|
||||
*/
|
||||
template <class CompletionToken = boost::asio::default_completion_token_t<executor_type>>
|
||||
auto
|
||||
async_run(
|
||||
endpoint ep,
|
||||
timeouts ts = timeouts{},
|
||||
CompletionToken token = CompletionToken{})
|
||||
auto async_run(CompletionToken token = CompletionToken{})
|
||||
{
|
||||
return base_type::async_run(ep, ts, std::move(token));
|
||||
}
|
||||
|
||||
/** @brief Connects and executes a request asynchronously.
|
||||
*
|
||||
* Combines the other `async_run` overload with `async_exec` in a
|
||||
* single function. This function is useful for users that want to
|
||||
* send a single request to the server and close it.
|
||||
*
|
||||
* @param ep Redis endpoint.
|
||||
* @param req Request object.
|
||||
* @param adapter Response adapter.
|
||||
* @param ts Timeouts used by the operation.
|
||||
* @param token Asio completion token.
|
||||
*
|
||||
* The completion token must have the following signature
|
||||
*
|
||||
* @code
|
||||
* void f(boost::system::error_code, std::size_t);
|
||||
* @endcode
|
||||
*
|
||||
* Where the second parameter is the size of the response in bytes.
|
||||
*/
|
||||
template <
|
||||
class Adapter = detail::response_traits<void>::adapter_type,
|
||||
class CompletionToken = boost::asio::default_completion_token_t<executor_type>>
|
||||
auto async_run(
|
||||
endpoint ep,
|
||||
resp3::request const& req,
|
||||
Adapter adapter,
|
||||
timeouts ts,
|
||||
CompletionToken token = CompletionToken{})
|
||||
{
|
||||
return base_type::async_run(ep, req, adapter, ts, std::move(token));
|
||||
return base_type::async_run(std::move(token));
|
||||
}
|
||||
|
||||
/** @brief Executes a command on the Redis server asynchronously.
|
||||
@@ -234,18 +162,18 @@ public:
|
||||
template <
|
||||
class Adapter = detail::response_traits<void>::adapter_type,
|
||||
class CompletionToken = boost::asio::default_completion_token_t<executor_type>>
|
||||
auto async_receive_push(
|
||||
auto async_receive(
|
||||
Adapter adapter = adapt(),
|
||||
CompletionToken token = CompletionToken{})
|
||||
{
|
||||
return base_type::async_receive_push(adapter, std::move(token));
|
||||
return base_type::async_receive(adapter, std::move(token));
|
||||
}
|
||||
|
||||
/** @brief Cancel operations.
|
||||
*
|
||||
* @li `operation::exec`: Cancels operations started with
|
||||
* `async_exec`. Has precedence over
|
||||
* `request::config::close_on_connection_lost`
|
||||
* `async_exec`. Affects only requests that haven't been written
|
||||
* yet.
|
||||
* @li operation::run: Cancels the `async_run` operation. Notice
|
||||
* that the preferred way to close a connection is to send a
|
||||
* [QUIT](https://redis.io/commands/quit/) command to the server.
|
||||
@@ -253,8 +181,8 @@ public:
|
||||
* timeout and lead to `connection::async_run` completing with
|
||||
* `error::idle_timeout`. Calling `cancel(operation::run)`
|
||||
* directly should be seen as the last option.
|
||||
* @li operation::receive_push: Cancels any ongoing callto
|
||||
* `async_receive_push`.
|
||||
* @li operation::receive: Cancels any ongoing callto
|
||||
* `async_receive`.
|
||||
*
|
||||
* @param op: The operation to be cancelled.
|
||||
* @returns The number of operations that have been canceled.
|
||||
@@ -263,41 +191,28 @@ public:
|
||||
{ return base_type::cancel(op); }
|
||||
|
||||
private:
|
||||
using this_type = connection<next_layer_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 struct detail::exec_op;
|
||||
template <class, class> friend struct detail::receive_push_op;
|
||||
template <class> friend struct detail::check_idle_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, class> friend struct detail::connect_with_timeout_op;
|
||||
template <class, class> friend struct detail::run_op;
|
||||
template <class> friend struct detail::ping_op;
|
||||
|
||||
template <class Timer, class CompletionToken>
|
||||
auto
|
||||
async_connect(
|
||||
boost::asio::ip::tcp::resolver::results_type const& endpoints,
|
||||
timeouts ts,
|
||||
Timer& timer,
|
||||
CompletionToken&& token)
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code)
|
||||
>(detail::connect_with_timeout_op<this_type, Timer>{this, &endpoints, ts, &timer},
|
||||
token, stream_);
|
||||
}
|
||||
template <class> friend struct detail::run_op;
|
||||
|
||||
void close() { stream_.close(); }
|
||||
auto is_open() const noexcept { return stream_.is_open(); }
|
||||
auto& lowest_layer() noexcept { return stream_.lowest_layer(); }
|
||||
auto lowest_layer() noexcept -> auto& { return stream_.lowest_layer(); }
|
||||
|
||||
AsyncReadWriteStream stream_;
|
||||
};
|
||||
|
||||
/** \brief A connection that uses a boost::asio::ip::tcp::socket.
|
||||
* \ingroup high-level-api
|
||||
*/
|
||||
using connection = basic_connection<boost::asio::ip::tcp::socket>;
|
||||
|
||||
} // aedis
|
||||
|
||||
#endif // AEDIS_CONNECTION_HPP
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <memory_resource>
|
||||
|
||||
#include <boost/assert.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
@@ -22,7 +23,6 @@
|
||||
|
||||
#include <aedis/adapt.hpp>
|
||||
#include <aedis/operation.hpp>
|
||||
#include <aedis/endpoint.hpp>
|
||||
#include <aedis/resp3/request.hpp>
|
||||
#include <aedis/detail/connection_ops.hpp>
|
||||
|
||||
@@ -43,60 +43,38 @@ public:
|
||||
using executor_type = Executor;
|
||||
using this_type = connection_base<Executor, Derived>;
|
||||
|
||||
explicit connection_base(executor_type ex)
|
||||
: resv_{ex}
|
||||
, ping_timer_{ex}
|
||||
, check_idle_timer_{ex}
|
||||
, writer_timer_{ex}
|
||||
explicit
|
||||
connection_base(executor_type ex, std::pmr::memory_resource* resource)
|
||||
: writer_timer_{ex}
|
||||
, read_timer_{ex}
|
||||
, push_channel_{ex}
|
||||
, last_data_{std::chrono::time_point<std::chrono::steady_clock>::min()}
|
||||
, req_{{true}}
|
||||
, read_buffer_{resource}
|
||||
, write_buffer_{resource}
|
||||
, reqs_{resource}
|
||||
{
|
||||
writer_timer_.expires_at(std::chrono::steady_clock::time_point::max());
|
||||
read_timer_.expires_at(std::chrono::steady_clock::time_point::max());
|
||||
}
|
||||
|
||||
auto get_executor() {return resv_.get_executor();}
|
||||
auto get_executor() {return writer_timer_.get_executor();}
|
||||
|
||||
auto cancel(operation op) -> std::size_t
|
||||
{
|
||||
switch (op) {
|
||||
case operation::exec:
|
||||
{
|
||||
for (auto& e: reqs_) {
|
||||
e->stop = true;
|
||||
e->timer.cancel_one();
|
||||
}
|
||||
|
||||
auto const ret = reqs_.size();
|
||||
reqs_ = {};
|
||||
return ret;
|
||||
return cancel_unwritten_requests();
|
||||
}
|
||||
case operation::run:
|
||||
{
|
||||
resv_.cancel();
|
||||
derived().close();
|
||||
|
||||
read_timer_.cancel();
|
||||
check_idle_timer_.cancel();
|
||||
writer_timer_.cancel();
|
||||
ping_timer_.cancel();
|
||||
cancel_on_conn_lost();
|
||||
|
||||
auto point = std::stable_partition(std::begin(reqs_), std::end(reqs_), [](auto const& ptr) {
|
||||
return !ptr->req->get_config().close_on_connection_lost;
|
||||
});
|
||||
|
||||
// Cancel own pings if there are any waiting.
|
||||
std::for_each(point, std::end(reqs_), [](auto const& ptr) {
|
||||
ptr->stop = true;
|
||||
ptr->timer.cancel();
|
||||
});
|
||||
|
||||
reqs_.erase(point, std::end(reqs_));
|
||||
return 1U;
|
||||
}
|
||||
case operation::receive_push:
|
||||
case operation::receive:
|
||||
{
|
||||
push_channel_.cancel();
|
||||
return 1U;
|
||||
@@ -105,6 +83,53 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
auto cancel_unwritten_requests() -> std::size_t
|
||||
{
|
||||
auto f = [](auto const& ptr)
|
||||
{
|
||||
BOOST_ASSERT(ptr != nullptr);
|
||||
return ptr->is_written();
|
||||
};
|
||||
|
||||
auto point = std::stable_partition(std::begin(reqs_), std::end(reqs_), f);
|
||||
|
||||
auto const ret = std::distance(point, std::end(reqs_));
|
||||
|
||||
std::for_each(point, std::end(reqs_), [](auto const& ptr) {
|
||||
ptr->stop();
|
||||
});
|
||||
|
||||
reqs_.erase(point, std::end(reqs_));
|
||||
return ret;
|
||||
}
|
||||
|
||||
auto cancel_on_conn_lost() -> std::size_t
|
||||
{
|
||||
auto cond = [](auto const& ptr)
|
||||
{
|
||||
BOOST_ASSERT(ptr != nullptr);
|
||||
|
||||
if (ptr->get_request().get_config().cancel_on_connection_lost)
|
||||
return false;
|
||||
|
||||
return !(!ptr->get_request().get_config().retry && ptr->is_written());
|
||||
};
|
||||
|
||||
auto point = std::stable_partition(std::begin(reqs_), std::end(reqs_), cond);
|
||||
|
||||
auto const ret = std::distance(point, std::end(reqs_));
|
||||
|
||||
std::for_each(point, std::end(reqs_), [](auto const& ptr) {
|
||||
ptr->stop();
|
||||
});
|
||||
|
||||
reqs_.erase(point, std::end(reqs_));
|
||||
std::for_each(std::begin(reqs_), std::end(reqs_), [](auto const& ptr) {
|
||||
return ptr->reset_status();
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <
|
||||
class Adapter = detail::response_traits<void>::adapter_type,
|
||||
class CompletionToken = boost::asio::default_completion_token_t<executor_type>>
|
||||
@@ -118,13 +143,13 @@ public:
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code, std::size_t)
|
||||
>(detail::exec_op<Derived, Adapter>{&derived(), &req, adapter}, token, resv_);
|
||||
>(detail::exec_op<Derived, Adapter>{&derived(), &req, adapter}, token, writer_timer_);
|
||||
}
|
||||
|
||||
template <
|
||||
class Adapter = detail::response_traits<void>::adapter_type,
|
||||
class CompletionToken = boost::asio::default_completion_token_t<executor_type>>
|
||||
auto async_receive_push(
|
||||
auto async_receive(
|
||||
Adapter adapter = adapt(),
|
||||
CompletionToken token = CompletionToken{})
|
||||
{
|
||||
@@ -132,33 +157,16 @@ public:
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code, std::size_t)
|
||||
>(detail::receive_push_op<Derived, decltype(f)>{&derived(), f}, token, resv_);
|
||||
>(detail::receive_op<Derived, decltype(f)>{&derived(), f}, token, writer_timer_);
|
||||
}
|
||||
|
||||
template <class Timeouts, class CompletionToken>
|
||||
auto
|
||||
async_run(endpoint ep, Timeouts ts, CompletionToken token)
|
||||
template <class CompletionToken>
|
||||
auto async_run(CompletionToken token)
|
||||
{
|
||||
ep_ = std::move(ep);
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code)
|
||||
>(detail::run_op<Derived, Timeouts>{&derived(), ts}, token, resv_);
|
||||
}
|
||||
|
||||
template <class Adapter, class Timeouts, class CompletionToken>
|
||||
auto async_run(
|
||||
endpoint ep,
|
||||
resp3::request const& req,
|
||||
Adapter adapter,
|
||||
Timeouts ts,
|
||||
CompletionToken token)
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code, std::size_t)
|
||||
>(detail::runexec_op<Derived, Adapter, Timeouts>
|
||||
{&derived(), ep, &req, adapter, ts}, token, resv_);
|
||||
>(detail::run_op<Derived>{&derived()}, token, writer_timer_);
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -171,38 +179,119 @@ private:
|
||||
|
||||
auto derived() -> Derived& { return static_cast<Derived&>(*this); }
|
||||
|
||||
void on_write()
|
||||
{
|
||||
// We have to clear the payload right after writing it to use it
|
||||
// as a flag that informs there is no ongoing write.
|
||||
write_buffer_.clear();
|
||||
|
||||
// Notice this must come before the for-each below.
|
||||
cancel_push_requests();
|
||||
|
||||
std::for_each(std::begin(reqs_), std::end(reqs_), [](auto const& ptr) {
|
||||
if (ptr->is_staged())
|
||||
ptr->mark_written();
|
||||
});
|
||||
}
|
||||
|
||||
struct req_info {
|
||||
explicit req_info(executor_type ex) : timer{ex} {}
|
||||
timer_type timer;
|
||||
resp3::request const* req = nullptr;
|
||||
std::size_t cmds = 0;
|
||||
bool stop = false;
|
||||
bool written = false;
|
||||
public:
|
||||
enum class action
|
||||
{
|
||||
stop,
|
||||
proceed,
|
||||
none,
|
||||
};
|
||||
|
||||
explicit req_info(resp3::request const& req, executor_type ex)
|
||||
: timer_{ex}
|
||||
, action_{action::none}
|
||||
, req_{&req}
|
||||
, cmds_{std::size(req)}
|
||||
, status_{status::none}
|
||||
{
|
||||
timer_.expires_at(std::chrono::steady_clock::time_point::max());
|
||||
}
|
||||
|
||||
auto proceed()
|
||||
{
|
||||
timer_.cancel();
|
||||
action_ = action::proceed;
|
||||
}
|
||||
|
||||
void stop()
|
||||
{
|
||||
timer_.cancel();
|
||||
action_ = action::stop;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto is_written() const noexcept
|
||||
{ return status_ == status::written; }
|
||||
|
||||
[[nodiscard]] auto is_staged() const noexcept
|
||||
{ return status_ == status::staged; }
|
||||
|
||||
void mark_written() noexcept
|
||||
{ status_ = status::written; }
|
||||
|
||||
void mark_staged() noexcept
|
||||
{ status_ = status::staged; }
|
||||
|
||||
void reset_status() noexcept
|
||||
{ status_ = status::none; }
|
||||
|
||||
[[nodiscard]] auto get_number_of_commands() const noexcept
|
||||
{ return cmds_; }
|
||||
|
||||
[[nodiscard]] auto get_request() const noexcept -> auto const&
|
||||
{ return *req_; }
|
||||
|
||||
[[nodiscard]] auto get_action() const noexcept
|
||||
{ return action_;}
|
||||
|
||||
template <class CompletionToken>
|
||||
auto async_wait(CompletionToken token)
|
||||
{
|
||||
return timer_.async_wait(std::move(token));
|
||||
}
|
||||
|
||||
private:
|
||||
enum class status
|
||||
{ none
|
||||
, staged
|
||||
, written
|
||||
};
|
||||
|
||||
timer_type timer_;
|
||||
action action_;
|
||||
resp3::request const* req_;
|
||||
std::size_t cmds_;
|
||||
status status_;
|
||||
};
|
||||
|
||||
using reqs_type = std::deque<std::shared_ptr<req_info>>;
|
||||
void remove_request(std::shared_ptr<req_info> const& info)
|
||||
{
|
||||
reqs_.erase(std::remove(std::begin(reqs_), std::end(reqs_), info));
|
||||
}
|
||||
|
||||
template <class, class> friend struct detail::receive_push_op;
|
||||
using reqs_type = std::pmr::deque<std::shared_ptr<req_info>>;
|
||||
|
||||
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::ping_op;
|
||||
template <class, class> friend struct detail::run_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, class> friend struct detail::runexec_op;
|
||||
template <class> friend struct detail::resolve_with_timeout_op;
|
||||
template <class> friend struct detail::check_idle_op;
|
||||
template <class, class> friend struct detail::start_op;
|
||||
template <class> friend struct detail::send_receive_op;
|
||||
|
||||
void cancel_push_requests()
|
||||
{
|
||||
auto point = std::stable_partition(std::begin(reqs_), std::end(reqs_), [](auto const& ptr) {
|
||||
return !(ptr->written && ptr->req->size() == 0);
|
||||
return !(ptr->is_staged() && ptr->get_request().size() == 0);
|
||||
});
|
||||
|
||||
std::for_each(point, std::end(reqs_), [](auto const& ptr) {
|
||||
ptr->timer.cancel();
|
||||
ptr->proceed();
|
||||
});
|
||||
|
||||
reqs_.erase(point, std::end(reqs_));
|
||||
@@ -211,6 +300,15 @@ private:
|
||||
void add_request_info(std::shared_ptr<req_info> const& info)
|
||||
{
|
||||
reqs_.push_back(info);
|
||||
|
||||
if (info->get_request().has_hello_priority()) {
|
||||
auto rend = std::partition_point(std::rbegin(reqs_), std::rend(reqs_), [](auto const& e) {
|
||||
return !e->is_written() && !e->is_staged();
|
||||
});
|
||||
|
||||
std::rotate(std::rbegin(reqs_), std::rbegin(reqs_) + 1, rend);
|
||||
}
|
||||
|
||||
if (derived().is_open() && cmds_ == 0 && write_buffer_.empty())
|
||||
writer_timer_.cancel();
|
||||
}
|
||||
@@ -218,26 +316,13 @@ private:
|
||||
auto make_dynamic_buffer(std::size_t max_read_size = 512)
|
||||
{ return boost::asio::dynamic_buffer(read_buffer_, max_read_size); }
|
||||
|
||||
template <class CompletionToken>
|
||||
auto
|
||||
async_resolve_with_timeout(
|
||||
std::chrono::steady_clock::duration d,
|
||||
CompletionToken&& token)
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code)
|
||||
>(detail::resolve_with_timeout_op<this_type>{this, d},
|
||||
token, resv_);
|
||||
}
|
||||
|
||||
template <class CompletionToken>
|
||||
auto reader(CompletionToken&& token)
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code)
|
||||
>(detail::reader_op<Derived>{&derived()}, token, resv_.get_executor());
|
||||
>(detail::reader_op<Derived>{&derived()}, token, writer_timer_);
|
||||
}
|
||||
|
||||
template <class CompletionToken>
|
||||
@@ -246,42 +331,7 @@ private:
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code)
|
||||
>(detail::writer_op<Derived>{&derived()}, token, resv_.get_executor());
|
||||
}
|
||||
|
||||
template <
|
||||
class Timeouts,
|
||||
class CompletionToken>
|
||||
auto async_start(Timeouts ts, CompletionToken&& token)
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code)
|
||||
>(detail::start_op<this_type, Timeouts>{this, ts}, token, resv_);
|
||||
}
|
||||
|
||||
template <class CompletionToken>
|
||||
auto
|
||||
async_ping(
|
||||
std::chrono::steady_clock::duration d,
|
||||
CompletionToken&& token)
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code)
|
||||
>(detail::ping_op<Derived>{&derived(), d}, token, resv_);
|
||||
}
|
||||
|
||||
template <class CompletionToken>
|
||||
auto
|
||||
async_check_idle(
|
||||
std::chrono::steady_clock::duration d,
|
||||
CompletionToken&& token)
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code)
|
||||
>(detail::check_idle_op<Derived>{&derived(), d}, token, check_idle_timer_);
|
||||
>(detail::writer_op<Derived>{&derived()}, token, writer_timer_);
|
||||
}
|
||||
|
||||
template <class Adapter, class CompletionToken>
|
||||
@@ -290,83 +340,43 @@ private:
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code, std::size_t)
|
||||
>(detail::exec_read_op<Derived, Adapter>{&derived(), adapter, cmds}, token, resv_);
|
||||
>(detail::exec_read_op<Derived, Adapter>{&derived(), adapter, cmds}, token, writer_timer_);
|
||||
}
|
||||
|
||||
void stage_request(req_info& ri)
|
||||
{
|
||||
write_buffer_ += ri.req->payload();
|
||||
cmds_ += ri.req->size();
|
||||
ri.written = true;
|
||||
write_buffer_ += ri.get_request().payload();
|
||||
cmds_ += ri.get_request().size();
|
||||
ri.mark_staged();
|
||||
}
|
||||
|
||||
void coalesce_requests()
|
||||
{
|
||||
// Coalesce the requests and marks them staged. After a
|
||||
// successful write staged requests will be marked as written.
|
||||
BOOST_ASSERT(write_buffer_.empty());
|
||||
BOOST_ASSERT(!reqs_.empty());
|
||||
|
||||
stage_request(*reqs_.at(0));
|
||||
|
||||
for (std::size_t i = 1; i < std::size(reqs_); ++i) {
|
||||
if (!reqs_.at(i - 1)->req->get_config().coalesce ||
|
||||
!reqs_.at(i - 0)->req->get_config().coalesce) {
|
||||
if (!reqs_.at(i - 1)->get_request().get_config().coalesce ||
|
||||
!reqs_.at(i - 0)->get_request().get_config().coalesce) {
|
||||
break;
|
||||
}
|
||||
stage_request(*reqs_.at(i));
|
||||
}
|
||||
}
|
||||
|
||||
void prepare_hello(endpoint const& ep)
|
||||
{
|
||||
req_.clear();
|
||||
if (requires_auth(ep)) {
|
||||
req_.push("HELLO", "3", "AUTH", ep.username, ep.password);
|
||||
} else {
|
||||
req_.push("HELLO", "3");
|
||||
}
|
||||
}
|
||||
|
||||
auto expect_role(std::string const& expected) -> bool
|
||||
{
|
||||
if (std::empty(expected))
|
||||
return true;
|
||||
|
||||
resp3::node<std::string> role_node;
|
||||
role_node.data_type = resp3::type::blob_string;
|
||||
role_node.aggregate_size = 1;
|
||||
role_node.depth = 1;
|
||||
role_node.value = "role";
|
||||
|
||||
auto iter = std::find(std::cbegin(response_), std::cend(response_), role_node);
|
||||
if (iter == std::end(response_))
|
||||
return false;
|
||||
|
||||
++iter;
|
||||
BOOST_ASSERT(iter != std::cend(response_));
|
||||
return iter->value == expected;
|
||||
}
|
||||
|
||||
// IO objects
|
||||
resolver_type resv_;
|
||||
timer_type ping_timer_;
|
||||
timer_type check_idle_timer_;
|
||||
timer_type writer_timer_;
|
||||
timer_type read_timer_;
|
||||
push_channel_type push_channel_;
|
||||
|
||||
std::string read_buffer_;
|
||||
std::string write_buffer_;
|
||||
std::pmr::string read_buffer_;
|
||||
std::pmr::string write_buffer_;
|
||||
std::size_t cmds_ = 0;
|
||||
reqs_type reqs_;
|
||||
|
||||
// Last time we received data.
|
||||
time_point_type last_data_;
|
||||
|
||||
resp3::request req_;
|
||||
std::vector<resp3::node<std::string>> response_;
|
||||
endpoint ep_;
|
||||
// The result of async_resolve.
|
||||
boost::asio::ip::tcp::resolver::results_type endpoints_;
|
||||
};
|
||||
|
||||
} // aedis
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
#include <aedis/error.hpp>
|
||||
#include <aedis/detail/net.hpp>
|
||||
#include <aedis/resp3/type.hpp>
|
||||
#include <aedis/resp3/detail/exec.hpp>
|
||||
#include <aedis/resp3/detail/parser.hpp>
|
||||
#include <aedis/resp3/read.hpp>
|
||||
#include <aedis/resp3/write.hpp>
|
||||
@@ -30,56 +29,8 @@
|
||||
|
||||
namespace aedis::detail {
|
||||
|
||||
template <class Conn, class Timer>
|
||||
struct connect_with_timeout_op {
|
||||
Conn* conn = nullptr;
|
||||
boost::asio::ip::tcp::resolver::results_type const* endpoints = nullptr;
|
||||
typename Conn::timeouts ts;
|
||||
Timer* timer = nullptr;
|
||||
boost::asio::coroutine coro{};
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, boost::system::error_code ec = {}
|
||||
, boost::asio::ip::tcp::endpoint const& = {})
|
||||
{
|
||||
reenter (coro)
|
||||
{
|
||||
timer->expires_after(ts.connect_timeout);
|
||||
yield
|
||||
detail::async_connect(
|
||||
conn->next_layer(), *timer, *endpoints, std::move(self));
|
||||
self.complete(ec);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Conn>
|
||||
struct resolve_with_timeout_op {
|
||||
Conn* conn = nullptr;
|
||||
std::chrono::steady_clock::duration resolve_timeout;
|
||||
boost::asio::coroutine coro{};
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, boost::system::error_code ec = {}
|
||||
, boost::asio::ip::tcp::resolver::results_type const& res = {})
|
||||
{
|
||||
reenter (coro)
|
||||
{
|
||||
conn->ping_timer_.expires_after(resolve_timeout);
|
||||
yield
|
||||
aedis::detail::async_resolve(
|
||||
conn->resv_, conn->ping_timer_,
|
||||
conn->ep_.host, conn->ep_.port, std::move(self));
|
||||
conn->endpoints_ = res;
|
||||
self.complete(ec);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Conn, class Adapter>
|
||||
struct receive_push_op {
|
||||
struct receive_op {
|
||||
Conn* conn = nullptr;
|
||||
Adapter adapter;
|
||||
std::size_t read_size = 0;
|
||||
@@ -93,34 +44,26 @@ struct receive_push_op {
|
||||
{
|
||||
reenter (coro)
|
||||
{
|
||||
yield
|
||||
conn->push_channel_.async_receive(std::move(self));
|
||||
if (ec) {
|
||||
self.complete(ec, 0);
|
||||
return;
|
||||
}
|
||||
yield conn->push_channel_.async_receive(std::move(self));
|
||||
AEDIS_CHECK_OP1();
|
||||
|
||||
yield
|
||||
resp3::async_read(
|
||||
conn->next_layer(),
|
||||
conn->make_dynamic_buffer(adapter.get_max_read_size(0)),
|
||||
adapter, std::move(self));
|
||||
if (ec) {
|
||||
conn->cancel(operation::run);
|
||||
|
||||
// Needed to cancel the channel, otherwise the read
|
||||
// operation will be blocked forever see
|
||||
// test_push_adapter.
|
||||
conn->cancel(operation::receive_push);
|
||||
self.complete(ec, 0);
|
||||
return;
|
||||
}
|
||||
// cancel(receive) is needed to cancel the channel, otherwise
|
||||
// the read operation will be blocked forever see
|
||||
// test_push_adapter.
|
||||
AEDIS_CHECK_OP1(conn->cancel(operation::run); conn->cancel(operation::receive));
|
||||
|
||||
read_size = n;
|
||||
|
||||
yield
|
||||
conn->push_channel_.async_send({}, 0, std::move(self));
|
||||
self.complete(ec, read_size);
|
||||
yield conn->push_channel_.async_send({}, 0, std::move(self));
|
||||
AEDIS_CHECK_OP1();
|
||||
|
||||
self.complete({}, read_size);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -158,25 +101,15 @@ struct exec_read_op {
|
||||
conn->next_layer(),
|
||||
conn->make_dynamic_buffer(),
|
||||
"\r\n", std::move(self));
|
||||
if (ec) {
|
||||
conn->cancel(operation::run);
|
||||
self.complete(ec, 0);
|
||||
return;
|
||||
}
|
||||
AEDIS_CHECK_OP1(conn->cancel(operation::run));
|
||||
}
|
||||
|
||||
// If the next request is a push we have to handle it to
|
||||
// the receive_push_op wait for it to be done and continue.
|
||||
// the receive_op wait for it to be done and continue.
|
||||
if (resp3::to_type(conn->read_buffer_.front()) == resp3::type::push) {
|
||||
yield
|
||||
async_send_receive(conn->push_channel_, std::move(self));
|
||||
if (ec) {
|
||||
// Notice we don't call cancel_run() as that is the
|
||||
// responsability of the receive_push_op.
|
||||
self.complete(ec, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
AEDIS_CHECK_OP1(conn->cancel(operation::run));
|
||||
continue;
|
||||
}
|
||||
//-----------------------------------
|
||||
@@ -190,11 +123,7 @@ struct exec_read_op {
|
||||
|
||||
++index;
|
||||
|
||||
if (ec) {
|
||||
conn->cancel(operation::run);
|
||||
self.complete(ec, 0);
|
||||
return;
|
||||
}
|
||||
AEDIS_CHECK_OP1(conn->cancel(operation::run));
|
||||
|
||||
read_size += n;
|
||||
|
||||
@@ -229,49 +158,56 @@ struct exec_op {
|
||||
{
|
||||
reenter (coro)
|
||||
{
|
||||
if (req->get_config().close_on_connection_lost && !conn->is_open()) {
|
||||
// The user doesn't want to wait for the connection to be
|
||||
// stablished.
|
||||
self.complete(error::not_connected, 0);
|
||||
return;
|
||||
// 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>(boost::asio::get_associated_allocator(self), conn->resv_.get_executor());
|
||||
info->timer.expires_at(std::chrono::steady_clock::time_point::max());
|
||||
info->req = req;
|
||||
info->cmds = req->size();
|
||||
info->stop = false;
|
||||
info = std::allocate_shared<req_info_type>(boost::asio::get_associated_allocator(self), *req, conn->get_executor());
|
||||
|
||||
conn->add_request_info(info);
|
||||
yield
|
||||
info->timer.async_wait(std::move(self));
|
||||
BOOST_ASSERT(!!ec);
|
||||
if (ec != boost::asio::error::operation_aborted) {
|
||||
self.complete(ec, 0);
|
||||
return;
|
||||
EXEC_OP_WAIT:
|
||||
yield info->async_wait(std::move(self));
|
||||
BOOST_ASSERT(ec == boost::asio::error::operation_aborted);
|
||||
|
||||
if (info->get_action() == Conn::req_info::action::stop) {
|
||||
// Don't have to call remove_request as it has already
|
||||
// been by cancel(exec).
|
||||
return self.complete(ec, 0);
|
||||
}
|
||||
|
||||
// null can happen for example when resolve fails.
|
||||
if (!conn->is_open() || info->stop) {
|
||||
self.complete(ec, 0);
|
||||
return;
|
||||
if (is_cancelled(self)) {
|
||||
if (info->is_written()) {
|
||||
self.get_cancellation_state().clear();
|
||||
goto EXEC_OP_WAIT; // Too late, can't cancel.
|
||||
} else {
|
||||
conn->remove_request(info);
|
||||
self.complete(ec, 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_ASSERT(conn->is_open());
|
||||
|
||||
if (req->size() == 0) {
|
||||
self.complete({}, 0);
|
||||
return;
|
||||
// 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_ASSERT(conn->cmds_ != 0);
|
||||
yield
|
||||
conn->async_exec_read(adapter, conn->reqs_.front()->cmds, std::move(self));
|
||||
conn->async_exec_read(adapter, conn->reqs_.front()->get_number_of_commands(), std::move(self));
|
||||
if (is_cancelled(self)) {
|
||||
conn->remove_request(info);
|
||||
return self.complete(boost::asio::error::operation_aborted, {});
|
||||
}
|
||||
|
||||
if (ec) {
|
||||
self.complete(ec, 0);
|
||||
return;
|
||||
return self.complete(ec, {});
|
||||
}
|
||||
|
||||
read_size = n;
|
||||
@@ -285,7 +221,7 @@ struct exec_op {
|
||||
conn->writer_timer_.cancel_one();
|
||||
} else {
|
||||
BOOST_ASSERT(!conn->reqs_.empty());
|
||||
conn->reqs_.front()->timer.cancel_one();
|
||||
conn->reqs_.front()->proceed();
|
||||
}
|
||||
|
||||
self.complete({}, read_size);
|
||||
@@ -294,185 +230,43 @@ struct exec_op {
|
||||
};
|
||||
|
||||
template <class Conn>
|
||||
struct ping_op {
|
||||
Conn* conn;
|
||||
std::chrono::steady_clock::duration ping_interval;
|
||||
boost::asio::coroutine coro{};
|
||||
|
||||
template <class Self>
|
||||
void
|
||||
operator()( Self& self
|
||||
, boost::system::error_code ec = {}
|
||||
, std::size_t = 0)
|
||||
{
|
||||
reenter (coro) for (;;)
|
||||
{
|
||||
conn->ping_timer_.expires_after(ping_interval);
|
||||
yield
|
||||
conn->ping_timer_.async_wait(std::move(self));
|
||||
if (ec || !conn->is_open()) {
|
||||
conn->cancel(operation::run);
|
||||
self.complete(ec);
|
||||
return;
|
||||
}
|
||||
|
||||
conn->req_.clear();
|
||||
conn->req_.push("PING");
|
||||
yield
|
||||
conn->async_exec(conn->req_, adapt(), std::move(self));
|
||||
if (ec) {
|
||||
conn->cancel(operation::run);
|
||||
self.complete({});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Conn>
|
||||
struct check_idle_op {
|
||||
Conn* conn;
|
||||
std::chrono::steady_clock::duration ping_interval;
|
||||
boost::asio::coroutine coro{};
|
||||
|
||||
template <class Self>
|
||||
void operator()(Self& self, boost::system::error_code ec = {})
|
||||
{
|
||||
reenter (coro) for (;;)
|
||||
{
|
||||
conn->check_idle_timer_.expires_after(2 * ping_interval);
|
||||
yield
|
||||
conn->check_idle_timer_.async_wait(std::move(self));
|
||||
if (ec) {
|
||||
conn->cancel(operation::run);
|
||||
self.complete({});
|
||||
return;
|
||||
}
|
||||
if (!conn->is_open()) {
|
||||
// Notice this is not an error, it was requested from an
|
||||
// external op.
|
||||
self.complete({});
|
||||
return;
|
||||
}
|
||||
|
||||
auto const now = std::chrono::steady_clock::now();
|
||||
if (conn->last_data_ + (2 * ping_interval) < now) {
|
||||
conn->cancel(operation::run);
|
||||
self.complete(error::idle_timeout);
|
||||
return;
|
||||
}
|
||||
|
||||
conn->last_data_ = now;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Conn, class Timeouts>
|
||||
struct start_op {
|
||||
Conn* conn;
|
||||
Timeouts ts;
|
||||
struct run_op {
|
||||
Conn* conn = nullptr;
|
||||
boost::asio::coroutine coro{};
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, std::array<std::size_t, 4> order = {}
|
||||
, std::array<std::size_t, 2> order = {}
|
||||
, boost::system::error_code ec0 = {}
|
||||
, boost::system::error_code ec1 = {}
|
||||
, boost::system::error_code ec2 = {}
|
||||
, boost::system::error_code ec3 = {})
|
||||
, boost::system::error_code ec1 = {})
|
||||
{
|
||||
reenter (coro)
|
||||
{
|
||||
conn->write_buffer_.clear();
|
||||
conn->cmds_ = 0;
|
||||
|
||||
yield
|
||||
boost::asio::experimental::make_parallel_group(
|
||||
[this](auto token) { return conn->reader(token);},
|
||||
[this](auto token) { return conn->writer(token);},
|
||||
[this](auto token) { return conn->async_check_idle(ts.ping_interval, token);},
|
||||
[this](auto token) { return conn->async_ping(ts.ping_interval, token);}
|
||||
[this](auto token) { return conn->writer(token);}
|
||||
).async_wait(
|
||||
boost::asio::experimental::wait_for_one(),
|
||||
std::move(self));
|
||||
|
||||
if (is_cancelled(self)) {
|
||||
self.complete(boost::asio::error::operation_aborted);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (order[0]) {
|
||||
case 0: self.complete(ec0); break;
|
||||
case 1: self.complete(ec1); break;
|
||||
case 2: self.complete(ec2); break;
|
||||
case 3: self.complete(ec3); break;
|
||||
default: BOOST_ASSERT(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Conn, class Timeouts>
|
||||
struct run_op {
|
||||
Conn* conn = nullptr;
|
||||
Timeouts ts;
|
||||
boost::asio::coroutine coro{};
|
||||
|
||||
template <class Self>
|
||||
void operator()(
|
||||
Self& self,
|
||||
boost::system::error_code ec = {},
|
||||
std::size_t = 0)
|
||||
{
|
||||
reenter (coro)
|
||||
{
|
||||
yield
|
||||
conn->async_resolve_with_timeout(ts.resolve_timeout, std::move(self));
|
||||
if (ec) {
|
||||
conn->cancel(operation::run);
|
||||
self.complete(ec);
|
||||
return;
|
||||
}
|
||||
|
||||
yield
|
||||
conn->derived().async_connect(conn->endpoints_, ts, conn->ping_timer_, std::move(self));
|
||||
if (ec) {
|
||||
conn->cancel(operation::run);
|
||||
self.complete(ec);
|
||||
return;
|
||||
}
|
||||
|
||||
conn->prepare_hello(conn->ep_);
|
||||
conn->ping_timer_.expires_after(ts.resp3_handshake_timeout);
|
||||
|
||||
yield
|
||||
resp3::detail::async_exec(
|
||||
conn->next_layer(),
|
||||
conn->ping_timer_,
|
||||
conn->req_,
|
||||
adapter::adapt2(conn->response_),
|
||||
conn->make_dynamic_buffer(),
|
||||
std::move(self)
|
||||
);
|
||||
|
||||
if (ec) {
|
||||
conn->cancel(operation::run);
|
||||
self.complete(ec);
|
||||
return;
|
||||
}
|
||||
|
||||
conn->ep_.password.clear();
|
||||
|
||||
if (!conn->expect_role(conn->ep_.role)) {
|
||||
conn->cancel(operation::run);
|
||||
self.complete(error::unexpected_server_role);
|
||||
return;
|
||||
}
|
||||
|
||||
conn->write_buffer_.clear();
|
||||
conn->cmds_ = 0;
|
||||
std::for_each(std::begin(conn->reqs_), std::end(conn->reqs_), [](auto const& ptr) {
|
||||
return ptr->written = false;
|
||||
});
|
||||
|
||||
yield conn->async_start(ts, std::move(self));
|
||||
self.complete(ec);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Conn>
|
||||
struct writer_op {
|
||||
Conn* conn;
|
||||
@@ -491,33 +285,21 @@ struct writer_op {
|
||||
conn->coalesce_requests();
|
||||
yield
|
||||
boost::asio::async_write(conn->next_layer(), boost::asio::buffer(conn->write_buffer_), std::move(self));
|
||||
if (ec) {
|
||||
self.complete(ec);
|
||||
AEDIS_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;
|
||||
}
|
||||
|
||||
// We have to clear the payload right after the read op in
|
||||
// order to to use it as a flag that informs there is no
|
||||
// ongoing write.
|
||||
conn->write_buffer_.clear();
|
||||
conn->cancel_push_requests();
|
||||
}
|
||||
|
||||
if (conn->is_open()) {
|
||||
yield
|
||||
conn->writer_timer_.async_wait(std::move(self));
|
||||
if (ec != boost::asio::error::operation_aborted) {
|
||||
conn->cancel(operation::run);
|
||||
self.complete(ec);
|
||||
return;
|
||||
}
|
||||
// The timer may be canceled either to stop the write op
|
||||
// or to proceed to the next write, the difference between
|
||||
// the two is that for the former the socket will be
|
||||
// closed first. We check for that below.
|
||||
}
|
||||
|
||||
if (!conn->is_open()) {
|
||||
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.
|
||||
@@ -547,13 +329,13 @@ struct reader_op {
|
||||
conn->next_layer(),
|
||||
conn->make_dynamic_buffer(),
|
||||
"\r\n", std::move(self));
|
||||
if (ec) {
|
||||
|
||||
if (ec == boost::asio::error::eof) {
|
||||
conn->cancel(operation::run);
|
||||
self.complete(ec);
|
||||
return;
|
||||
return self.complete({}); // EOFINAE: EOF is not an error.
|
||||
}
|
||||
|
||||
conn->last_data_ = std::chrono::steady_clock::now();
|
||||
AEDIS_CHECK_OP0(conn->cancel(operation::run));
|
||||
|
||||
// We handle unsolicited events in the following way
|
||||
//
|
||||
@@ -575,25 +357,24 @@ struct reader_op {
|
||||
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()->cmds == 0)) {
|
||||
yield
|
||||
async_send_receive(conn->push_channel_, std::move(self));
|
||||
if (ec) {
|
||||
|| (!conn->reqs_.empty() && conn->reqs_.front()->get_number_of_commands() == 0)) {
|
||||
yield async_send_receive(conn->push_channel_, std::move(self));
|
||||
if (!conn->is_open() || ec || is_cancelled(self)) {
|
||||
conn->cancel(operation::run);
|
||||
self.complete(ec);
|
||||
self.complete(boost::asio::error::basic_errors::operation_aborted);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
BOOST_ASSERT(conn->cmds_ != 0);
|
||||
BOOST_ASSERT(!conn->reqs_.empty());
|
||||
BOOST_ASSERT(conn->reqs_.front()->cmds != 0);
|
||||
conn->reqs_.front()->timer.cancel_one();
|
||||
yield
|
||||
conn->read_timer_.async_wait(std::move(self));
|
||||
if (ec != boost::asio::error::operation_aborted ||
|
||||
!conn->is_open()) {
|
||||
BOOST_ASSERT(conn->reqs_.front()->get_number_of_commands() != 0);
|
||||
conn->reqs_.front()->proceed();
|
||||
yield conn->read_timer_.async_wait(std::move(self));
|
||||
if (!conn->is_open() || is_cancelled(self)) {
|
||||
// Added this cancel here to make sure any outstanding
|
||||
// ping is cancelled.
|
||||
conn->cancel(operation::run);
|
||||
self.complete(ec);
|
||||
self.complete(boost::asio::error::basic_errors::operation_aborted);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -601,48 +382,6 @@ struct reader_op {
|
||||
}
|
||||
};
|
||||
|
||||
template <class Conn, class Adapter, class Timeouts>
|
||||
struct runexec_op {
|
||||
Conn* conn = nullptr;
|
||||
endpoint ep;
|
||||
resp3::request const* req = nullptr;
|
||||
Adapter adapter;
|
||||
Timeouts ts;
|
||||
boost::asio::coroutine coro{};
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, std::array<std::size_t, 2> order = {}
|
||||
, boost::system::error_code ec1 = {}
|
||||
, boost::system::error_code ec2 = {}
|
||||
, std::size_t n = 0)
|
||||
{
|
||||
reenter (coro)
|
||||
{
|
||||
yield
|
||||
boost::asio::experimental::make_parallel_group(
|
||||
[this, ep2 = ep](auto token) { return conn->async_run(ep2, ts, token);},
|
||||
[this](auto token) { return conn->async_exec(*req, adapter, token);}
|
||||
).async_wait(
|
||||
boost::asio::experimental::wait_for_one_error(),
|
||||
std::move(self));
|
||||
|
||||
switch (order[0]) {
|
||||
case 0: self.complete(ec1, n); return;
|
||||
case 1: {
|
||||
if (ec2)
|
||||
self.complete(ec2, n);
|
||||
else
|
||||
self.complete(ec1, n);
|
||||
|
||||
return;
|
||||
}
|
||||
default: BOOST_ASSERT(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // aedis::detail
|
||||
|
||||
#include <boost/asio/unyield.hpp>
|
||||
|
||||
@@ -19,102 +19,6 @@
|
||||
|
||||
namespace aedis::detail {
|
||||
|
||||
template <class Executor>
|
||||
using conn_timer_t = boost::asio::basic_waitable_timer<std::chrono::steady_clock, boost::asio::wait_traits<std::chrono::steady_clock>, Executor>;
|
||||
|
||||
template <
|
||||
class Stream,
|
||||
class EndpointSequence
|
||||
>
|
||||
struct connect_op {
|
||||
Stream* socket;
|
||||
conn_timer_t<typename Stream::executor_type>* timer;
|
||||
EndpointSequence* endpoints;
|
||||
boost::asio::coroutine coro{};
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, std::array<std::size_t, 2> order = {}
|
||||
, boost::system::error_code ec1 = {}
|
||||
, typename Stream::protocol_type::endpoint const& ep = {}
|
||||
, boost::system::error_code ec2 = {})
|
||||
{
|
||||
reenter (coro)
|
||||
{
|
||||
yield
|
||||
boost::asio::experimental::make_parallel_group(
|
||||
[this](auto token)
|
||||
{
|
||||
auto f = [](boost::system::error_code const&, auto const&) { return true; };
|
||||
return boost::asio::async_connect(*socket, *endpoints, f, token);
|
||||
},
|
||||
[this](auto token) { return timer->async_wait(token);}
|
||||
).async_wait(
|
||||
boost::asio::experimental::wait_for_one(),
|
||||
std::move(self));
|
||||
|
||||
switch (order[0]) {
|
||||
case 0: self.complete(ec1, ep); return;
|
||||
case 1:
|
||||
{
|
||||
if (ec2) {
|
||||
self.complete(ec2, {});
|
||||
} else {
|
||||
self.complete(error::connect_timeout, ep);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
default: BOOST_ASSERT(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Resolver, class Timer>
|
||||
struct resolve_op {
|
||||
Resolver* resv;
|
||||
Timer* timer;
|
||||
boost::string_view host;
|
||||
boost::string_view port;
|
||||
boost::asio::coroutine coro{};
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, std::array<std::size_t, 2> order = {}
|
||||
, boost::system::error_code ec1 = {}
|
||||
, boost::asio::ip::tcp::resolver::results_type res = {}
|
||||
, boost::system::error_code ec2 = {})
|
||||
{
|
||||
reenter (coro)
|
||||
{
|
||||
yield
|
||||
boost::asio::experimental::make_parallel_group(
|
||||
[this](auto token) { return resv->async_resolve(host.data(), port.data(), token);},
|
||||
[this](auto token) { return timer->async_wait(token);}
|
||||
).async_wait(
|
||||
boost::asio::experimental::wait_for_one(),
|
||||
std::move(self));
|
||||
|
||||
switch (order[0]) {
|
||||
case 0: self.complete(ec1, res); return;
|
||||
|
||||
case 1:
|
||||
{
|
||||
if (ec2) {
|
||||
self.complete(ec2, {});
|
||||
} else {
|
||||
self.complete(error::resolve_timeout, {});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
default: BOOST_ASSERT(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Channel>
|
||||
struct send_receive_op {
|
||||
Channel* channel;
|
||||
@@ -129,55 +33,17 @@ struct send_receive_op {
|
||||
{
|
||||
yield
|
||||
channel->async_send(boost::system::error_code{}, 0, std::move(self));
|
||||
if (ec) {
|
||||
self.complete(ec, 0);
|
||||
return;
|
||||
}
|
||||
AEDIS_CHECK_OP1();
|
||||
|
||||
yield
|
||||
channel->async_receive(std::move(self));
|
||||
self.complete(ec, 0);
|
||||
AEDIS_CHECK_OP1();
|
||||
|
||||
self.complete({}, 0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <
|
||||
class Stream,
|
||||
class EndpointSequence,
|
||||
class CompletionToken
|
||||
>
|
||||
auto async_connect(
|
||||
Stream& socket,
|
||||
conn_timer_t<typename Stream::executor_type>& timer,
|
||||
EndpointSequence ep,
|
||||
CompletionToken&& token)
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code, typename Stream::protocol_type::endpoint const&)
|
||||
>(connect_op<Stream, EndpointSequence>
|
||||
{&socket, &timer, &ep}, token, socket, timer);
|
||||
}
|
||||
|
||||
template <
|
||||
class Resolver,
|
||||
class Timer,
|
||||
class CompletionToken =
|
||||
boost::asio::default_completion_token_t<typename Resolver::executor_type>
|
||||
>
|
||||
auto async_resolve(
|
||||
Resolver& resv,
|
||||
Timer& timer,
|
||||
boost::string_view host,
|
||||
boost::string_view port,
|
||||
CompletionToken&& token = CompletionToken{})
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code, boost::asio::ip::tcp::resolver::results_type)
|
||||
>(resolve_op<Resolver, Timer>{&resv, &timer, host, port}, token, resv, timer);
|
||||
}
|
||||
|
||||
template <
|
||||
class Channel,
|
||||
class CompletionToken =
|
||||
|
||||
@@ -1,40 +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 AEDIS_ENDPOINT_HPP
|
||||
#define AEDIS_ENDPOINT_HPP
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace aedis {
|
||||
|
||||
/** \brief A Redis endpoint.
|
||||
* \ingroup high-level-api
|
||||
*/
|
||||
struct endpoint {
|
||||
/// Redis server address.
|
||||
std::string host;
|
||||
|
||||
/// Redis server port.
|
||||
std::string port;
|
||||
|
||||
/// Expected role if any.
|
||||
std::string role{};
|
||||
|
||||
/// Username if authentication is required.
|
||||
std::string username{};
|
||||
|
||||
/// Password if authentication is required.
|
||||
std::string password{};
|
||||
};
|
||||
|
||||
auto is_valid(endpoint const& ep) noexcept -> bool;
|
||||
auto requires_auth(endpoint const& ep) noexcept -> bool;
|
||||
auto operator<<(std::ostream& os, endpoint const& ep) -> std::ostream&;
|
||||
|
||||
} // aedis
|
||||
|
||||
#endif // AEDIS_ENDPOINT_HPP
|
||||
@@ -16,20 +16,8 @@ namespace aedis {
|
||||
*/
|
||||
enum class error
|
||||
{
|
||||
/// Resolve timeout.
|
||||
resolve_timeout = 1,
|
||||
|
||||
/// Connect timeout.
|
||||
connect_timeout,
|
||||
|
||||
/// Idle timeout.
|
||||
idle_timeout,
|
||||
|
||||
/// Exec timeout.
|
||||
exec_timeout,
|
||||
|
||||
/// Invalid RESP3 type.
|
||||
invalid_data_type,
|
||||
invalid_data_type = 1,
|
||||
|
||||
/// Can't parse the string as a number.
|
||||
not_a_number,
|
||||
@@ -73,12 +61,6 @@ enum class error
|
||||
/// Got RESP3 null.
|
||||
resp3_null,
|
||||
|
||||
/// Unexpected server role.
|
||||
unexpected_server_role,
|
||||
|
||||
/// SSL handshake timeout.
|
||||
ssl_handshake_timeout,
|
||||
|
||||
/// There is no stablished connection.
|
||||
not_connected,
|
||||
};
|
||||
|
||||
@@ -1,29 +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 <aedis/endpoint.hpp>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace aedis {
|
||||
|
||||
auto is_valid(endpoint const& ep) noexcept -> bool
|
||||
{
|
||||
return !std::empty(ep.host) && !std::empty(ep.port);
|
||||
}
|
||||
|
||||
auto requires_auth(endpoint const& ep) noexcept -> bool
|
||||
{
|
||||
return !std::empty(ep.username) && !std::empty(ep.password);
|
||||
}
|
||||
|
||||
auto operator<<(std::ostream& os, endpoint const& ep) -> std::ostream&
|
||||
{
|
||||
os << ep.host << ":" << ep.port << " (" << ep.username << "," << ep.password << ")";
|
||||
return os;
|
||||
}
|
||||
|
||||
} // aedis
|
||||
@@ -21,10 +21,6 @@ struct error_category_impl : boost::system::error_category {
|
||||
auto message(int ev) const -> std::string override
|
||||
{
|
||||
switch(static_cast<error>(ev)) {
|
||||
case error::resolve_timeout: return "Resolve operation timeout.";
|
||||
case error::connect_timeout: return "Connect operation timeout.";
|
||||
case error::idle_timeout: return "Idle timeout.";
|
||||
case error::exec_timeout: return "Exec timeout.";
|
||||
case error::invalid_data_type: return "Invalid resp3 type.";
|
||||
case error::not_a_number: return "Can't convert string to number.";
|
||||
case error::exceeeds_max_nested_depth: return "Exceeds the maximum number of nested responses.";
|
||||
@@ -40,8 +36,6 @@ struct error_category_impl : boost::system::error_category {
|
||||
case error::incompatible_size: return "Aggregate container has incompatible size.";
|
||||
case error::not_a_double: return "Not a double.";
|
||||
case error::resp3_null: return "Got RESP3 null.";
|
||||
case error::unexpected_server_role: return "Unexpected server role.";
|
||||
case error::ssl_handshake_timeout: return "SSL handshake timeout.";
|
||||
case error::not_connected: return "Not connected.";
|
||||
default: BOOST_ASSERT(false); return "Aedis error.";
|
||||
}
|
||||
|
||||
@@ -20,8 +20,8 @@ enum class operation {
|
||||
exec,
|
||||
/// Refers to `connection::async_run` operations.
|
||||
run,
|
||||
/// Refers to `connection::async_receive_push` operations.
|
||||
receive_push,
|
||||
/// Refers to `connection::async_receive` operations.
|
||||
receive,
|
||||
};
|
||||
|
||||
} // aedis
|
||||
|
||||
@@ -1,172 +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 AEDIS_RESP3_EXEC_HPP
|
||||
#define AEDIS_RESP3_EXEC_HPP
|
||||
|
||||
#include <boost/assert.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/read.hpp>
|
||||
#include <boost/asio/write.hpp>
|
||||
#include <boost/asio/coroutine.hpp>
|
||||
#include <boost/asio/compose.hpp>
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
#include <boost/asio/experimental/parallel_group.hpp>
|
||||
|
||||
#include <aedis/error.hpp>
|
||||
#include <aedis/resp3/read.hpp>
|
||||
#include <aedis/resp3/request.hpp>
|
||||
|
||||
#include <boost/asio/yield.hpp>
|
||||
|
||||
namespace aedis::resp3::detail {
|
||||
|
||||
template <
|
||||
class AsyncStream,
|
||||
class Adapter,
|
||||
class DynamicBuffer
|
||||
>
|
||||
struct exec_op {
|
||||
AsyncStream* socket = nullptr;
|
||||
request const* req = nullptr;
|
||||
Adapter adapter;
|
||||
DynamicBuffer dbuf{};
|
||||
std::size_t n_cmds = 0;
|
||||
std::size_t size = 0;
|
||||
boost::asio::coroutine coro{};
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, boost::system::error_code ec = {}
|
||||
, std::size_t n = 0)
|
||||
{
|
||||
reenter (coro) for (;;)
|
||||
{
|
||||
if (req) {
|
||||
yield
|
||||
boost::asio::async_write(
|
||||
*socket,
|
||||
boost::asio::buffer(req->payload()),
|
||||
std::move(self));
|
||||
|
||||
if (ec || n_cmds == 0) {
|
||||
self.complete(ec, n);
|
||||
return;
|
||||
}
|
||||
|
||||
req = nullptr;
|
||||
}
|
||||
|
||||
yield resp3::async_read(*socket, dbuf, adapter, std::move(self));
|
||||
if (ec) {
|
||||
self.complete(ec, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
size += n;
|
||||
if (--n_cmds == 0) {
|
||||
self.complete(ec, size);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <
|
||||
class AsyncStream,
|
||||
class Adapter,
|
||||
class DynamicBuffer,
|
||||
class CompletionToken = boost::asio::default_completion_token_t<typename AsyncStream::executor_type>
|
||||
>
|
||||
auto async_exec(
|
||||
AsyncStream& socket,
|
||||
request const& req,
|
||||
Adapter adapter,
|
||||
DynamicBuffer dbuf,
|
||||
CompletionToken token = CompletionToken{})
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code, std::size_t)
|
||||
>(detail::exec_op<AsyncStream, Adapter, DynamicBuffer>
|
||||
{&socket, &req, adapter, dbuf, req.size()}, token, socket);
|
||||
}
|
||||
|
||||
template <
|
||||
class AsyncStream,
|
||||
class Timer,
|
||||
class Adapter,
|
||||
class DynamicBuffer
|
||||
>
|
||||
struct exec_with_timeout_op {
|
||||
AsyncStream* socket = nullptr;
|
||||
Timer* timer = nullptr;
|
||||
request const* req = nullptr;
|
||||
Adapter adapter;
|
||||
DynamicBuffer dbuf{};
|
||||
boost::asio::coroutine coro{};
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, std::array<std::size_t, 2> order = {}
|
||||
, boost::system::error_code ec1 = {}
|
||||
, std::size_t n = 0
|
||||
, boost::system::error_code ec2 = {})
|
||||
{
|
||||
reenter (coro)
|
||||
{
|
||||
yield
|
||||
boost::asio::experimental::make_parallel_group(
|
||||
[this](auto token) { return detail::async_exec(*socket, *req, adapter, dbuf, token);},
|
||||
[this](auto token) { return timer->async_wait(token);}
|
||||
).async_wait(
|
||||
boost::asio::experimental::wait_for_one(),
|
||||
std::move(self));
|
||||
|
||||
switch (order[0]) {
|
||||
case 0: self.complete(ec1, n); break;
|
||||
case 1:
|
||||
{
|
||||
if (ec2) {
|
||||
self.complete(ec2, 0);
|
||||
} else {
|
||||
self.complete(aedis::error::exec_timeout, 0);
|
||||
}
|
||||
|
||||
} break;
|
||||
|
||||
default: BOOST_ASSERT(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <
|
||||
class AsyncStream,
|
||||
class Timer,
|
||||
class Adapter,
|
||||
class DynamicBuffer,
|
||||
class CompletionToken = boost::asio::default_completion_token_t<typename AsyncStream::executor_type>
|
||||
>
|
||||
auto async_exec(
|
||||
AsyncStream& socket,
|
||||
Timer& timer,
|
||||
request const& req,
|
||||
Adapter adapter,
|
||||
DynamicBuffer dbuf,
|
||||
CompletionToken token = CompletionToken{})
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code, std::size_t)
|
||||
>(detail::exec_with_timeout_op<AsyncStream, Timer, Adapter, DynamicBuffer>
|
||||
{&socket, &timer, &req, adapter, dbuf}, token, socket, timer);
|
||||
}
|
||||
|
||||
} // aedis::resp3::detail
|
||||
|
||||
#include <boost/asio/unyield.hpp>
|
||||
#endif // AEDIS_RESP3_EXEC_HPP
|
||||
@@ -212,7 +212,7 @@ public:
|
||||
|
||||
// The bulk type expected in the next read. If none is expected returns
|
||||
// type::invalid.
|
||||
auto bulk() const noexcept { return bulk_; }
|
||||
[[nodiscard]] auto bulk() const noexcept { return bulk_; }
|
||||
|
||||
// The length expected in the the next bulk.
|
||||
[[nodiscard]] auto bulk_length() const noexcept { return bulk_length_; }
|
||||
|
||||
@@ -17,6 +17,29 @@
|
||||
|
||||
#include <boost/asio/yield.hpp>
|
||||
|
||||
namespace aedis::detail
|
||||
{
|
||||
template <class T>
|
||||
auto is_cancelled(T const& self)
|
||||
{
|
||||
return self.get_cancellation_state().cancelled() != boost::asio::cancellation_type_t::none;
|
||||
}
|
||||
}
|
||||
|
||||
#define AEDIS_CHECK_OP0(X)\
|
||||
if (ec || aedis::detail::is_cancelled(self)) {\
|
||||
X;\
|
||||
self.complete(!!ec ? ec : boost::asio::error::operation_aborted);\
|
||||
return;\
|
||||
}
|
||||
|
||||
#define AEDIS_CHECK_OP1(X)\
|
||||
if (ec || aedis::detail::is_cancelled(self)) {\
|
||||
X;\
|
||||
self.complete(!!ec ? ec : boost::asio::error::operation_aborted, {});\
|
||||
return;\
|
||||
}
|
||||
|
||||
namespace aedis::resp3::detail {
|
||||
|
||||
struct ignore_response {
|
||||
@@ -59,12 +82,7 @@ public:
|
||||
if (parser_.bulk() == type::invalid) {
|
||||
yield
|
||||
boost::asio::async_read_until(stream_, buf_, "\r\n", std::move(self));
|
||||
|
||||
if (ec) {
|
||||
self.complete(ec, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
AEDIS_CHECK_OP1();
|
||||
} else {
|
||||
// On a bulk read we can't read until delimiter since the
|
||||
// payload may contain the delimiter itself so we have to
|
||||
@@ -83,11 +101,7 @@ public:
|
||||
buf_.data(buffer_size_, parser_.bulk_length() + 2 - buffer_size_),
|
||||
boost::asio::transfer_all(),
|
||||
std::move(self));
|
||||
|
||||
if (ec) {
|
||||
self.complete(ec, 0);
|
||||
return;
|
||||
}
|
||||
AEDIS_CHECK_OP1();
|
||||
}
|
||||
|
||||
n = parser_.bulk_length() + 2;
|
||||
|
||||
@@ -16,4 +16,9 @@ auto has_push_response(boost::string_view cmd) -> bool
|
||||
return false;
|
||||
}
|
||||
|
||||
auto is_hello(boost::string_view cmd) -> bool
|
||||
{
|
||||
return cmd == "HELLO";
|
||||
}
|
||||
|
||||
} // aedis::resp3::detail
|
||||
|
||||
@@ -26,16 +26,16 @@ namespace aedis::resp3 {
|
||||
template <class String>
|
||||
struct node {
|
||||
/// The RESP3 type of the data in this node.
|
||||
resp3::type data_type;
|
||||
type data_type = type::invalid;
|
||||
|
||||
/// The number of elements of an aggregate.
|
||||
std::size_t aggregate_size;
|
||||
std::size_t aggregate_size{};
|
||||
|
||||
/// The depth of this node in the response tree.
|
||||
std::size_t depth;
|
||||
std::size_t depth{};
|
||||
|
||||
/// The actual data. For aggregate types this is usually empty.
|
||||
String value;
|
||||
String value{};
|
||||
};
|
||||
|
||||
/** @brief Converts the node to a string.
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <memory_resource>
|
||||
|
||||
#include <boost/hana.hpp>
|
||||
#include <boost/utility/string_view.hpp>
|
||||
@@ -67,6 +68,8 @@ namespace detail {
|
||||
|
||||
auto has_push_response(boost::string_view cmd) -> bool;
|
||||
|
||||
auto is_hello(boost::string_view cmd) -> bool;
|
||||
|
||||
template <class T>
|
||||
struct add_bulk_impl {
|
||||
template <class Request>
|
||||
@@ -148,8 +151,8 @@ void add_separator(Request& to)
|
||||
}
|
||||
} // detail
|
||||
|
||||
/** @brief Creates Redis requests.
|
||||
* @ingroup high-level-api
|
||||
/** \brief Creates Redis requests.
|
||||
* \ingroup high-level-api
|
||||
*
|
||||
* A request is composed of one or more Redis commands and is
|
||||
* referred to in the redis documentation as a pipeline, see
|
||||
@@ -162,49 +165,72 @@ void add_separator(Request& to)
|
||||
* r.push("PING");
|
||||
* r.push("PING", "key");
|
||||
* r.push("QUIT");
|
||||
* co_await async_write(socket, buffer(r));
|
||||
* @endcode
|
||||
*
|
||||
* @remarks
|
||||
* \remarks
|
||||
*
|
||||
* @li Non-string types will be converted to string by using \c
|
||||
* \li Non-string types will be converted to string by using \c
|
||||
* to_bulk, which must be made available over ADL.
|
||||
* @li Uses std::string as internal storage.
|
||||
* \li Uses std::string as internal storage.
|
||||
*/
|
||||
class request {
|
||||
public:
|
||||
/// Request configuration options.
|
||||
struct config {
|
||||
/** @brief If set to true, requests started with
|
||||
* `connection::async_exe` will fail either if the connection is
|
||||
* lost while the request is pending or if `async_exec` is
|
||||
* called while there is no connection with Redis. The default
|
||||
/** \brief If set to true, requests started with
|
||||
* `aedis::connection::async_exec` will fail if the connection is
|
||||
* lost while the request is pending. The default
|
||||
* behaviour is not to close requests.
|
||||
*/
|
||||
bool close_on_connection_lost = false;
|
||||
bool cancel_on_connection_lost = false;
|
||||
|
||||
/** @brief Coalesce this with other requests.
|
||||
*
|
||||
* If true this request will be coalesced with other requests,
|
||||
/** \brief If true this request will be coalesced with other requests,
|
||||
* see https://redis.io/topics/pipelining. If false, this
|
||||
* request will be sent individually.
|
||||
*/
|
||||
bool coalesce = true;
|
||||
|
||||
/** \brief If set to true, requests started with
|
||||
* `aedis::connection::async_exec` will fail if the call happens
|
||||
* before the connection with Redis was stablished.
|
||||
*/
|
||||
bool cancel_if_not_connected = false;
|
||||
|
||||
/** \brief If true, the implementation will resend this
|
||||
* request if it remained unresponded when
|
||||
* `aedis::connection::async_run` completed. Has effect only if
|
||||
* cancel_on_connection_lost is true.
|
||||
*/
|
||||
bool retry = true;
|
||||
|
||||
/** \brief If this request has a HELLO command and this flag is
|
||||
* set to true, the `aedis::connection` will move it to the
|
||||
* front of the queue of awaiting requests. This makes it
|
||||
* possible to send HELLO and authenticate before other
|
||||
* commands are sent.
|
||||
*/
|
||||
bool hello_with_priority = true;
|
||||
};
|
||||
|
||||
/** @brief Constructor
|
||||
/** \brief Constructor
|
||||
*
|
||||
* @param cfg Configuration options.
|
||||
* \param cfg Configuration options.
|
||||
* \param resource Memory resource.
|
||||
*/
|
||||
explicit request(config cfg = config{false, true})
|
||||
: cfg_{cfg}
|
||||
{}
|
||||
explicit
|
||||
request(config cfg = config{false, true, false, true, true},
|
||||
std::pmr::memory_resource* resource = std::pmr::get_default_resource())
|
||||
: cfg_{cfg}, payload_(resource) {}
|
||||
|
||||
//// Returns the number of commands contained in this request.
|
||||
auto size() const noexcept -> std::size_t { return commands_;};
|
||||
//// Returns the number of commands contained in this request.
|
||||
[[nodiscard]] auto size() const noexcept -> std::size_t
|
||||
{ return commands_;};
|
||||
|
||||
// Returns the request payload.
|
||||
auto payload() const noexcept -> auto const& { return payload_;}
|
||||
[[nodiscard]] auto payload() const noexcept -> auto const&
|
||||
{ return payload_;}
|
||||
|
||||
[[nodiscard]] auto has_hello_priority() const noexcept -> auto const&
|
||||
{ return has_hello_priority_;}
|
||||
|
||||
/// Clears the request preserving allocated memory.
|
||||
void clear()
|
||||
@@ -213,6 +239,16 @@ public:
|
||||
commands_ = 0;
|
||||
}
|
||||
|
||||
/// Calls std::string::reserve on the internal storage.
|
||||
void reserve(std::size_t new_cap = 0)
|
||||
{ payload_.reserve(new_cap); }
|
||||
|
||||
/// Returns a const reference to the config object.
|
||||
[[nodiscard]] auto get_config() const noexcept -> auto const& {return cfg_; }
|
||||
|
||||
/// Returns a reference to the config object.
|
||||
[[nodiscard]] auto get_config() noexcept -> auto& {return cfg_; }
|
||||
|
||||
/** @brief Appends a new command to the end of the request.
|
||||
*
|
||||
* For example
|
||||
@@ -240,8 +276,7 @@ public:
|
||||
detail::add_bulk(payload_, cmd);
|
||||
detail::add_bulk(payload_, make_tuple(args...));
|
||||
|
||||
if (!detail::has_push_response(cmd))
|
||||
++commands_;
|
||||
check_cmd(cmd);
|
||||
}
|
||||
|
||||
/** @brief Appends a new command to the end of the request.
|
||||
@@ -257,7 +292,7 @@ public:
|
||||
* };
|
||||
*
|
||||
* request req;
|
||||
* req.push_range2("HSET", "key", std::cbegin(map), std::cend(map));
|
||||
* req.push_range("HSET", "key", std::cbegin(map), std::cend(map));
|
||||
* @endcode
|
||||
*
|
||||
* \param cmd The command e.g. Redis or Sentinel command.
|
||||
@@ -266,7 +301,8 @@ public:
|
||||
* \param end Iterator to the end of the range.
|
||||
*/
|
||||
template <class Key, class ForwardIterator>
|
||||
void push_range2(boost::string_view cmd, Key const& key, ForwardIterator begin, ForwardIterator end)
|
||||
void push_range(boost::string_view cmd, Key const& key, ForwardIterator begin, ForwardIterator end,
|
||||
typename std::iterator_traits<ForwardIterator>::value_type * = nullptr)
|
||||
{
|
||||
using value_type = typename std::iterator_traits<ForwardIterator>::value_type;
|
||||
using resp3::type;
|
||||
@@ -283,8 +319,7 @@ public:
|
||||
for (; begin != end; ++begin)
|
||||
detail::add_bulk(payload_, *begin);
|
||||
|
||||
if (!detail::has_push_response(cmd))
|
||||
++commands_;
|
||||
check_cmd(cmd);
|
||||
}
|
||||
|
||||
/** @brief Appends a new command to the end of the request.
|
||||
@@ -297,7 +332,7 @@ public:
|
||||
* { "channel1" , "channel2" , "channel3" }
|
||||
*
|
||||
* request req;
|
||||
* req.push("SUBSCRIBE", std::cbegin(channels), std::cedn(channels));
|
||||
* req.push("SUBSCRIBE", std::cbegin(channels), std::cend(channels));
|
||||
* \endcode
|
||||
*
|
||||
* \param cmd The Redis command
|
||||
@@ -305,7 +340,8 @@ public:
|
||||
* \param end Iterator to the end of the range.
|
||||
*/
|
||||
template <class ForwardIterator>
|
||||
void push_range2(boost::string_view cmd, ForwardIterator begin, ForwardIterator end)
|
||||
void push_range(boost::string_view cmd, ForwardIterator begin, ForwardIterator end,
|
||||
typename std::iterator_traits<ForwardIterator>::value_type * = nullptr)
|
||||
{
|
||||
using value_type = typename std::iterator_traits<ForwardIterator>::value_type;
|
||||
using resp3::type;
|
||||
@@ -321,8 +357,7 @@ public:
|
||||
for (; begin != end; ++begin)
|
||||
detail::add_bulk(payload_, *begin);
|
||||
|
||||
if (!detail::has_push_response(cmd))
|
||||
++commands_;
|
||||
check_cmd(cmd);
|
||||
}
|
||||
|
||||
/** @brief Appends a new command to the end of the request.
|
||||
@@ -334,11 +369,12 @@ public:
|
||||
* \param range Range to send e.g. and \c std::map.
|
||||
*/
|
||||
template <class Key, class Range>
|
||||
void push_range(boost::string_view cmd, Key const& key, Range const& range)
|
||||
void push_range(boost::string_view cmd, Key const& key, Range const& range,
|
||||
decltype(std::begin(range)) * = nullptr)
|
||||
{
|
||||
using std::begin;
|
||||
using std::end;
|
||||
push_range2(cmd, key, begin(range), end(range));
|
||||
push_range(cmd, key, begin(range), end(range));
|
||||
}
|
||||
|
||||
/** @brief Appends a new command to the end of the request.
|
||||
@@ -349,23 +385,27 @@ public:
|
||||
* \param range Range to send e.g. and \c std::map.
|
||||
*/
|
||||
template <class Range>
|
||||
void push_range(boost::string_view cmd, Range const& range)
|
||||
void push_range(boost::string_view cmd, Range const& range,
|
||||
decltype(std::begin(range)) * = nullptr)
|
||||
{
|
||||
using std::begin;
|
||||
using std::end;
|
||||
push_range2(cmd, begin(range), end(range));
|
||||
push_range(cmd, begin(range), end(range));
|
||||
}
|
||||
|
||||
/// Calls std::string::reserve on the internal storage.
|
||||
void reserve(std::size_t new_cap = 0)
|
||||
{ payload_.reserve(new_cap); }
|
||||
|
||||
auto get_config() const noexcept -> auto const& {return cfg_; }
|
||||
|
||||
private:
|
||||
std::string payload_;
|
||||
std::size_t commands_ = 0;
|
||||
void check_cmd(boost::string_view cmd)
|
||||
{
|
||||
if (!detail::has_push_response(cmd))
|
||||
++commands_;
|
||||
|
||||
has_hello_priority_ = detail::is_hello(cmd) && cfg_.hello_with_priority;
|
||||
}
|
||||
|
||||
config cfg_;
|
||||
std::pmr::string payload_;
|
||||
std::size_t commands_ = 0;
|
||||
bool has_hello_priority_ = false;
|
||||
};
|
||||
|
||||
} // aedis::resp3
|
||||
|
||||
@@ -13,6 +13,9 @@ namespace aedis::resp3 {
|
||||
|
||||
/** \brief Writes a request synchronously.
|
||||
* \ingroup low-level-api
|
||||
*
|
||||
* \param stream Stream to write the request to.
|
||||
* \param req Request to write.
|
||||
*/
|
||||
template<
|
||||
class SyncWriteStream,
|
||||
@@ -37,6 +40,10 @@ auto write(
|
||||
|
||||
/** \brief Writes a request asynchronously.
|
||||
* \ingroup low-level-api
|
||||
*
|
||||
* \param stream Stream to write the request to.
|
||||
* \param req Request to write.
|
||||
* \param token Asio completion token.
|
||||
*/
|
||||
template<
|
||||
class AsyncWriteStream,
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
*/
|
||||
|
||||
#include <aedis/impl/error.ipp>
|
||||
#include <aedis/impl/endpoint.ipp>
|
||||
#include <aedis/resp3/impl/request.ipp>
|
||||
#include <aedis/resp3/impl/type.ipp>
|
||||
#include <aedis/resp3/detail/impl/parser.ipp>
|
||||
|
||||
@@ -12,12 +12,11 @@
|
||||
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <aedis/detail/connection_base.hpp>
|
||||
#include <aedis/ssl/detail/connection_ops.hpp>
|
||||
|
||||
namespace aedis::ssl {
|
||||
|
||||
template <class>
|
||||
class connection;
|
||||
class basic_connection;
|
||||
|
||||
/** \brief A SSL connection to the Redis server.
|
||||
* \ingroup high-level-api
|
||||
@@ -26,55 +25,49 @@ class connection;
|
||||
* commands can be sent at any time. For more details, please see the
|
||||
* documentation of each individual function.
|
||||
*
|
||||
* @remarks This class exposes only asynchronous member functions,
|
||||
* synchronous communications with the Redis server is provided by
|
||||
* the `aedis::sync` class.
|
||||
*
|
||||
* @tparam Derived class.
|
||||
* @tparam AsyncReadWriteStream A stream that supports reading and
|
||||
* writing.
|
||||
*
|
||||
*/
|
||||
template <class AsyncReadWriteStream>
|
||||
class connection<boost::asio::ssl::stream<AsyncReadWriteStream>> :
|
||||
class basic_connection<boost::asio::ssl::stream<AsyncReadWriteStream>> :
|
||||
private aedis::detail::connection_base<
|
||||
typename boost::asio::ssl::stream<AsyncReadWriteStream>::executor_type,
|
||||
connection<boost::asio::ssl::stream<AsyncReadWriteStream>>> {
|
||||
basic_connection<boost::asio::ssl::stream<AsyncReadWriteStream>>> {
|
||||
public:
|
||||
/// Type of the next layer
|
||||
using next_layer_type = boost::asio::ssl::stream<AsyncReadWriteStream>;
|
||||
|
||||
/// Executor type.
|
||||
using executor_type = typename next_layer_type::executor_type;
|
||||
using base_type = aedis::detail::connection_base<executor_type, connection<boost::asio::ssl::stream<AsyncReadWriteStream>>>;
|
||||
|
||||
/** \brief Connection configuration parameters.
|
||||
*/
|
||||
struct timeouts {
|
||||
/// Timeout of the resolve operation.
|
||||
std::chrono::steady_clock::duration resolve_timeout = std::chrono::seconds{10};
|
||||
|
||||
/// Timeout of the connect operation.
|
||||
std::chrono::steady_clock::duration connect_timeout = std::chrono::seconds{10};
|
||||
|
||||
/// Timeout of the ssl handshake operation.
|
||||
std::chrono::steady_clock::duration handshake_timeout = std::chrono::seconds{10};
|
||||
|
||||
/// Timeout of the resp3 handshake operation.
|
||||
std::chrono::steady_clock::duration resp3_handshake_timeout = std::chrono::seconds{2};
|
||||
|
||||
/// Time interval of ping operations.
|
||||
std::chrono::steady_clock::duration ping_interval = std::chrono::seconds{1};
|
||||
/// Rebinds the socket type to another executor.
|
||||
template <class Executor1>
|
||||
struct rebind_executor
|
||||
{
|
||||
/// The socket type when rebound to the specified executor.
|
||||
using other = basic_connection<boost::asio::ssl::stream<typename AsyncReadWriteStream::template rebind_executor<Executor1>::other>>;
|
||||
};
|
||||
|
||||
/// Constructor
|
||||
explicit connection(executor_type ex, boost::asio::ssl::context& ctx)
|
||||
: base_type{ex}
|
||||
, stream_{ex, ctx}
|
||||
{
|
||||
}
|
||||
using base_type = aedis::detail::connection_base<executor_type, basic_connection<boost::asio::ssl::stream<AsyncReadWriteStream>>>;
|
||||
|
||||
/// Constructor
|
||||
explicit connection(boost::asio::io_context& ioc, boost::asio::ssl::context& ctx)
|
||||
: connection(ioc.get_executor(), ctx)
|
||||
explicit
|
||||
basic_connection(
|
||||
executor_type ex,
|
||||
boost::asio::ssl::context& ctx,
|
||||
std::pmr::memory_resource* resource = std::pmr::get_default_resource())
|
||||
: base_type{ex, resource}
|
||||
, stream_{ex, ctx}
|
||||
{ }
|
||||
|
||||
/// Constructor
|
||||
explicit
|
||||
basic_connection(
|
||||
boost::asio::io_context& ioc,
|
||||
boost::asio::ssl::context& ctx,
|
||||
std::pmr::memory_resource* resource = std::pmr::get_default_resource())
|
||||
: basic_connection(ioc.get_executor(), ctx, resource)
|
||||
{ }
|
||||
|
||||
/// Returns the associated executor.
|
||||
@@ -92,40 +85,19 @@ public:
|
||||
/// Returns a const reference to the next layer.
|
||||
auto const& next_layer() const noexcept { return stream_; }
|
||||
|
||||
/** @brief Connects and executes a request asynchronously.
|
||||
/** @brief Establishes a connection with the Redis server asynchronously.
|
||||
*
|
||||
* See aedis::connection::async_run for detailed information.
|
||||
* See aedis::connection::async_run for more information.
|
||||
*/
|
||||
template <class CompletionToken = boost::asio::default_completion_token_t<executor_type>>
|
||||
auto
|
||||
async_run(
|
||||
endpoint ep,
|
||||
timeouts ts = timeouts{},
|
||||
CompletionToken token = CompletionToken{})
|
||||
auto async_run(CompletionToken token = CompletionToken{})
|
||||
{
|
||||
return base_type::async_run(ep, ts, std::move(token));
|
||||
}
|
||||
|
||||
/** @brief Connects and executes a request asynchronously.
|
||||
*
|
||||
* See aedis::connection::async_run for detailed information.
|
||||
*/
|
||||
template <
|
||||
class Adapter = aedis::detail::response_traits<void>::adapter_type,
|
||||
class CompletionToken = boost::asio::default_completion_token_t<executor_type>>
|
||||
auto async_run(
|
||||
endpoint ep,
|
||||
resp3::request const& req,
|
||||
Adapter adapter,
|
||||
timeouts ts,
|
||||
CompletionToken token = CompletionToken{})
|
||||
{
|
||||
return base_type::async_run(ep, req, adapter, ts, std::move(token));
|
||||
return base_type::async_run(std::move(token));
|
||||
}
|
||||
|
||||
/** @brief Executes a command on the Redis server asynchronously.
|
||||
*
|
||||
* See aedis::connection::async_exec for detailed information.
|
||||
* See aedis::connection::async_exec for more information.
|
||||
*/
|
||||
template <
|
||||
class Adapter = aedis::detail::response_traits<void>::adapter_type,
|
||||
@@ -140,59 +112,49 @@ public:
|
||||
|
||||
/** @brief Receives server side pushes asynchronously.
|
||||
*
|
||||
* See aedis::connection::async_receive_push for detailed information.
|
||||
* See aedis::connection::async_receive for detailed information.
|
||||
*/
|
||||
template <
|
||||
class Adapter = aedis::detail::response_traits<void>::adapter_type,
|
||||
class CompletionToken = boost::asio::default_completion_token_t<executor_type>>
|
||||
auto async_receive_push(
|
||||
auto async_receive(
|
||||
Adapter adapter = adapt(),
|
||||
CompletionToken token = CompletionToken{})
|
||||
{
|
||||
return base_type::async_receive_push(adapter, std::move(token));
|
||||
return base_type::async_receive(adapter, std::move(token));
|
||||
}
|
||||
|
||||
/** @brief Cancel operations.
|
||||
*
|
||||
* See aedis::connection::cancel for detailed information.
|
||||
* See aedis::connection::cancel for more information.
|
||||
*/
|
||||
auto cancel(operation op) -> std::size_t
|
||||
{ return base_type::cancel(op); }
|
||||
|
||||
auto& lowest_layer() noexcept { return stream_.lowest_layer(); }
|
||||
|
||||
private:
|
||||
using this_type = connection<next_layer_type>;
|
||||
using this_type = basic_connection<next_layer_type>;
|
||||
|
||||
template <class, class> friend class aedis::detail::connection_base;
|
||||
template <class, class> friend struct aedis::detail::exec_op;
|
||||
template <class, class> friend struct detail::ssl_connect_with_timeout_op;
|
||||
template <class, class> friend struct aedis::detail::run_op;
|
||||
template <class> friend struct aedis::detail::writer_op;
|
||||
template <class> friend struct aedis::detail::check_idle_op;
|
||||
template <class> friend struct aedis::detail::reader_op;
|
||||
template <class, class> friend struct aedis::detail::exec_read_op;
|
||||
template <class> friend struct aedis::detail::ping_op;
|
||||
template <class, class> friend struct detail::receive_op;
|
||||
template <class> friend struct aedis::detail::run_op;
|
||||
template <class> friend struct aedis::detail::writer_op;
|
||||
template <class> friend struct aedis::detail::reader_op;
|
||||
|
||||
auto& lowest_layer() noexcept { return stream_.lowest_layer(); }
|
||||
auto is_open() const noexcept { return stream_.next_layer().is_open(); }
|
||||
void close() { stream_.next_layer().close(); }
|
||||
|
||||
template <class Timer, class CompletionToken>
|
||||
auto
|
||||
async_connect(
|
||||
boost::asio::ip::tcp::resolver::results_type const& endpoints,
|
||||
timeouts ts,
|
||||
Timer& timer,
|
||||
CompletionToken&& token)
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code)
|
||||
>(detail::ssl_connect_with_timeout_op<this_type, Timer>{this, &endpoints, ts, &timer}, token, stream_);
|
||||
}
|
||||
|
||||
next_layer_type stream_;
|
||||
};
|
||||
|
||||
/** \brief A connection that uses a boost::asio::ssl::stream<boost::asio::ip::tcp::socket>.
|
||||
* \ingroup high-level-api
|
||||
*/
|
||||
using connection = basic_connection<boost::asio::ssl::stream<boost::asio::ip::tcp::socket>>;
|
||||
|
||||
} // aedis::ssl
|
||||
|
||||
#endif // AEDIS_SSL_CONNECTION_HPP
|
||||
|
||||
@@ -1,113 +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 AEDIS_SSL_CONNECTION_OPS_HPP
|
||||
#define AEDIS_SSL_CONNECTION_OPS_HPP
|
||||
|
||||
#include <array>
|
||||
|
||||
#include <boost/assert.hpp>
|
||||
#include <boost/system.hpp>
|
||||
#include <boost/asio/experimental/parallel_group.hpp>
|
||||
#include <boost/asio/yield.hpp>
|
||||
|
||||
namespace aedis::ssl::detail
|
||||
{
|
||||
|
||||
template <class Stream>
|
||||
struct handshake_op {
|
||||
Stream* stream;
|
||||
aedis::detail::conn_timer_t<typename Stream::executor_type>* timer;
|
||||
boost::asio::coroutine coro{};
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, std::array<std::size_t, 2> order = {}
|
||||
, boost::system::error_code ec1 = {}
|
||||
, boost::system::error_code ec2 = {})
|
||||
{
|
||||
reenter (coro)
|
||||
{
|
||||
yield
|
||||
boost::asio::experimental::make_parallel_group(
|
||||
[this](auto token)
|
||||
{
|
||||
return stream->async_handshake(boost::asio::ssl::stream_base::client, token);
|
||||
},
|
||||
[this](auto token) { return timer->async_wait(token);}
|
||||
).async_wait(
|
||||
boost::asio::experimental::wait_for_one(),
|
||||
std::move(self));
|
||||
|
||||
switch (order[0]) {
|
||||
case 0: self.complete(ec1); return;
|
||||
case 1:
|
||||
{
|
||||
BOOST_ASSERT_MSG(!ec2, "handshake_op: Incompatible state.");
|
||||
self.complete(error::ssl_handshake_timeout);
|
||||
return;
|
||||
}
|
||||
|
||||
default: BOOST_ASSERT(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <
|
||||
class Stream,
|
||||
class CompletionToken
|
||||
>
|
||||
auto async_handshake(
|
||||
Stream& stream,
|
||||
aedis::detail::conn_timer_t<typename Stream::executor_type>& timer,
|
||||
CompletionToken&& token)
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code)
|
||||
>(handshake_op<Stream>{&stream, &timer}, token, stream, timer);
|
||||
}
|
||||
|
||||
template <class Conn, class Timer>
|
||||
struct ssl_connect_with_timeout_op {
|
||||
Conn* conn = nullptr;
|
||||
boost::asio::ip::tcp::resolver::results_type const* endpoints = nullptr;
|
||||
typename Conn::timeouts ts;
|
||||
Timer* timer = nullptr;
|
||||
boost::asio::coroutine coro{};
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, boost::system::error_code ec = {}
|
||||
, boost::asio::ip::tcp::endpoint const& = {})
|
||||
{
|
||||
reenter (coro)
|
||||
{
|
||||
timer->expires_after(ts.connect_timeout);
|
||||
|
||||
yield
|
||||
aedis::detail::async_connect(
|
||||
conn->lowest_layer(), *timer, *endpoints, std::move(self));
|
||||
|
||||
if (ec) {
|
||||
self.complete(ec);
|
||||
return;
|
||||
}
|
||||
|
||||
timer->expires_after(ts.handshake_timeout);
|
||||
|
||||
yield
|
||||
async_handshake(conn->next_layer(), *timer, std::move(self));
|
||||
self.complete(ec);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // aedis::ssl::detail
|
||||
|
||||
#include <boost/asio/unyield.hpp>
|
||||
#endif // AEDIS_SSL_CONNECTION_OPS_HPP
|
||||
17
tests/common.hpp
Normal file
17
tests/common.hpp
Normal file
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#include <chrono>
|
||||
|
||||
namespace net = boost::asio;
|
||||
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);
|
||||
}
|
||||
96
tests/conn_echo_stress.cpp
Normal file
96
tests/conn_echo_stress.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 <iostream>
|
||||
#include <boost/asio.hpp>
|
||||
#ifdef BOOST_ASIO_HAS_CO_AWAIT
|
||||
#include <boost/system/errc.hpp>
|
||||
|
||||
#define BOOST_TEST_MODULE low level
|
||||
#include <boost/test/included/unit_test.hpp>
|
||||
|
||||
#include <aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
#include "common.hpp"
|
||||
|
||||
namespace net = boost::asio;
|
||||
using error_code = boost::system::error_code;
|
||||
|
||||
using aedis::resp3::request;
|
||||
using aedis::operation;
|
||||
using aedis::adapt;
|
||||
using connection = net::use_awaitable_t<>::as_default_on_t<aedis::connection>;
|
||||
|
||||
#include <boost/asio/experimental/awaitable_operators.hpp>
|
||||
using namespace net::experimental::awaitable_operators;
|
||||
|
||||
net::awaitable<void> push_consumer(std::shared_ptr<connection> conn, int expected)
|
||||
{
|
||||
int c = 0;
|
||||
for (;;) {
|
||||
co_await conn->async_receive(adapt(), net::use_awaitable);
|
||||
if (++c == expected)
|
||||
break;
|
||||
}
|
||||
|
||||
request req;
|
||||
req.push("HELLO", 3);
|
||||
req.push("QUIT");
|
||||
co_await conn->async_exec(req, adapt());
|
||||
}
|
||||
|
||||
auto echo_session(std::shared_ptr<connection> conn, std::string id, int n) -> net::awaitable<void>
|
||||
{
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
|
||||
request req;
|
||||
std::tuple<aedis::ignore, std::string> resp;
|
||||
|
||||
for (auto i = 0; i < n; ++i) {
|
||||
auto const msg = id + "/" + std::to_string(i);
|
||||
//std::cout << msg << std::endl;
|
||||
req.push("HELLO", 3);
|
||||
req.push("PING", msg);
|
||||
req.push("SUBSCRIBE", "channel");
|
||||
boost::system::error_code ec;
|
||||
co_await conn->async_exec(req, adapt(resp), net::redirect_error(net::use_awaitable, ec));
|
||||
BOOST_TEST(!ec);
|
||||
BOOST_CHECK_EQUAL(msg, std::get<1>(resp));
|
||||
req.clear();
|
||||
std::get<1>(resp).clear();
|
||||
}
|
||||
}
|
||||
|
||||
auto async_echo_stress() -> net::awaitable<void>
|
||||
{
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
auto conn = std::make_shared<connection>(ex);
|
||||
|
||||
int const sessions = 1000;
|
||||
int const msgs = 100;
|
||||
int total = sessions * msgs;
|
||||
|
||||
net::co_spawn(ex, push_consumer(conn, total), net::detached);
|
||||
|
||||
for (int i = 0; i < sessions; ++i)
|
||||
net::co_spawn(ex, echo_session(conn, std::to_string(i), msgs), net::detached);
|
||||
|
||||
auto const addrs = resolve();
|
||||
co_await net::async_connect(conn->next_layer(), addrs);
|
||||
co_await conn->async_run();
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(echo_stress)
|
||||
{
|
||||
net::io_context ioc;
|
||||
net::co_spawn(ioc, async_echo_stress(), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
#else
|
||||
int main(){}
|
||||
#endif
|
||||
109
tests/conn_exec.cpp
Normal file
109
tests/conn_exec.cpp
Normal file
@@ -0,0 +1,109 @@
|
||||
/* 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 low level
|
||||
#include <boost/test/included/unit_test.hpp>
|
||||
|
||||
#include <aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
#include "common.hpp"
|
||||
|
||||
// TODO: Test whether HELLO won't be inserted passt commands that have
|
||||
// been already writen.
|
||||
|
||||
namespace net = boost::asio;
|
||||
|
||||
using aedis::resp3::request;
|
||||
using aedis::adapt;
|
||||
using connection = aedis::connection;
|
||||
using error_code = boost::system::error_code;
|
||||
|
||||
BOOST_AUTO_TEST_CASE(wrong_response_data_type)
|
||||
{
|
||||
request req;
|
||||
req.push("HELLO", 3);
|
||||
req.push("QUIT");
|
||||
|
||||
// Wrong data type.
|
||||
std::tuple<aedis::ignore, int> resp;
|
||||
net::io_context ioc;
|
||||
|
||||
auto const endpoints = resolve();
|
||||
connection conn{ioc};
|
||||
net::connect(conn.next_layer(), endpoints);
|
||||
|
||||
conn.async_exec(req, adapt(resp), [](auto ec, auto){
|
||||
BOOST_CHECK_EQUAL(ec, aedis::error::not_a_number);
|
||||
});
|
||||
conn.async_run([](auto ec){
|
||||
BOOST_CHECK_EQUAL(ec, boost::asio::error::basic_errors::operation_aborted);
|
||||
});
|
||||
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
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, adapt(), [](auto ec, auto){
|
||||
BOOST_CHECK_EQUAL(ec, aedis::error::not_connected);
|
||||
});
|
||||
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(request_retry)
|
||||
{
|
||||
request req1;
|
||||
req1.get_config().cancel_on_connection_lost = true;
|
||||
req1.push("HELLO", 3);
|
||||
req1.push("CLIENT", "PAUSE", 7000);
|
||||
|
||||
request req2;
|
||||
req2.get_config().cancel_on_connection_lost = false;
|
||||
req2.get_config().retry = false;
|
||||
req2.push("PING");
|
||||
|
||||
net::io_context ioc;
|
||||
connection conn{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 second request to complete with error
|
||||
// although it has cancel_on_connection_lost = false.
|
||||
conn.cancel(aedis::operation::run);
|
||||
});
|
||||
|
||||
auto const endpoints = resolve();
|
||||
net::connect(conn.next_layer(), endpoints);
|
||||
|
||||
conn.async_exec(req1, adapt(), [](auto ec, auto){
|
||||
BOOST_TEST(!ec);
|
||||
});
|
||||
|
||||
conn.async_exec(req2, adapt(), [](auto ec, auto){
|
||||
BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled);
|
||||
});
|
||||
|
||||
conn.async_run([](auto ec){
|
||||
BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled);
|
||||
});
|
||||
|
||||
ioc.run();
|
||||
}
|
||||
145
tests/conn_exec_cancel.cpp
Normal file
145
tests/conn_exec_cancel.cpp
Normal file
@@ -0,0 +1,145 @@
|
||||
/* 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>
|
||||
#ifdef BOOST_ASIO_HAS_CO_AWAIT
|
||||
#include <boost/system/errc.hpp>
|
||||
|
||||
#define BOOST_TEST_MODULE low level
|
||||
#include <boost/test/included/unit_test.hpp>
|
||||
|
||||
#include <aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
#include "common.hpp"
|
||||
|
||||
namespace net = boost::asio;
|
||||
|
||||
using aedis::resp3::request;
|
||||
using aedis::operation;
|
||||
using aedis::adapt;
|
||||
using connection = aedis::connection;
|
||||
using error_code = boost::system::error_code;
|
||||
|
||||
#include <boost/asio/experimental/awaitable_operators.hpp>
|
||||
using namespace net::experimental::awaitable_operators;
|
||||
|
||||
auto async_run(std::shared_ptr<connection> conn, error_code expected) -> net::awaitable<void>
|
||||
{
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
|
||||
boost::system::error_code ec;
|
||||
co_await conn->async_run(net::redirect_error(net::use_awaitable, ec));
|
||||
std::cout << ec.message() << std::endl;
|
||||
BOOST_CHECK_EQUAL(ec, expected);
|
||||
}
|
||||
|
||||
auto async_cancel_exec(std::shared_ptr<connection> conn) -> net::awaitable<void>
|
||||
{
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
|
||||
net::steady_timer st{ex};
|
||||
st.expires_after(std::chrono::seconds{1});
|
||||
|
||||
boost::system::error_code ec1;
|
||||
|
||||
request req1;
|
||||
req1.get_config().coalesce = false;
|
||||
req1.push("HELLO", 3);
|
||||
req1.push("BLPOP", "any", 3);
|
||||
|
||||
// Should not be canceled.
|
||||
conn->async_exec(req1, adapt(), [](auto ec, auto){
|
||||
BOOST_TEST(!ec);
|
||||
});
|
||||
|
||||
request req2;
|
||||
req2.get_config().coalesce = false;
|
||||
req2.push("PING", "second");
|
||||
|
||||
// Should be canceled.
|
||||
conn->async_exec(req1, adapt(), [](auto ec, auto){
|
||||
BOOST_CHECK_EQUAL(ec, net::error::basic_errors::operation_aborted);
|
||||
});
|
||||
|
||||
// Will complete while BLPOP is pending.
|
||||
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, adapt(), net::redirect_error(net::use_awaitable, ec1));
|
||||
|
||||
BOOST_TEST(!ec1);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(cancel_exec_with_timer)
|
||||
{
|
||||
net::io_context ioc;
|
||||
|
||||
auto const endpoints = resolve();
|
||||
|
||||
auto conn = std::make_shared<connection>(ioc);
|
||||
net::connect(conn->next_layer(), endpoints);
|
||||
|
||||
net::co_spawn(ioc.get_executor(), async_run(conn, {}), net::detached);
|
||||
net::co_spawn(ioc.get_executor(), async_cancel_exec(conn), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
auto async_ignore_cancel_of_written_req(std::shared_ptr<connection> conn) -> net::awaitable<void>
|
||||
{
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
|
||||
net::steady_timer st{ex};
|
||||
st.expires_after(std::chrono::seconds{1});
|
||||
|
||||
net::steady_timer st2{ex};
|
||||
st2.expires_after(std::chrono::seconds{3});
|
||||
|
||||
boost::system::error_code ec1, ec2, ec3;
|
||||
|
||||
request req1; // Will be cancelled after it has been written.
|
||||
req1.get_config().coalesce = false;
|
||||
req1.push("HELLO", 3);
|
||||
req1.push("BLPOP", "any", 3);
|
||||
|
||||
request req2; // Will be cancelled.
|
||||
req2.push("PING");
|
||||
|
||||
co_await (
|
||||
conn->async_exec(req1, adapt(), net::redirect_error(net::use_awaitable, ec1)) ||
|
||||
conn->async_exec(req2, adapt(), net::redirect_error(net::use_awaitable, ec2)) ||
|
||||
st.async_wait(net::redirect_error(net::use_awaitable, ec3))
|
||||
);
|
||||
|
||||
BOOST_CHECK_EQUAL(ec1, net::error::basic_errors::operation_aborted);
|
||||
BOOST_CHECK_EQUAL(ec2, net::error::basic_errors::operation_aborted);
|
||||
BOOST_TEST(!ec3);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(ignore_cancel_of_written_req)
|
||||
{
|
||||
auto const endpoints = resolve();
|
||||
|
||||
net::io_context ioc;
|
||||
auto conn = std::make_shared<connection>(ioc);
|
||||
net::connect(conn->next_layer(), endpoints);
|
||||
|
||||
error_code expected = net::error::operation_aborted;
|
||||
net::co_spawn(ioc.get_executor(), async_run(conn, expected), net::detached);
|
||||
net::co_spawn(ioc.get_executor(), async_ignore_cancel_of_written_req(conn), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
#else
|
||||
int main(){}
|
||||
#endif
|
||||
328
tests/conn_push.cpp
Normal file
328
tests/conn_push.cpp
Normal file
@@ -0,0 +1,328 @@
|
||||
/* 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>
|
||||
#ifdef BOOST_ASIO_HAS_CO_AWAIT
|
||||
#include <boost/system/errc.hpp>
|
||||
#include <boost/asio/experimental/as_tuple.hpp>
|
||||
|
||||
#define BOOST_TEST_MODULE low level
|
||||
#include <boost/test/included/unit_test.hpp>
|
||||
|
||||
#include <aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
#include "common.hpp"
|
||||
|
||||
namespace net = boost::asio;
|
||||
|
||||
using aedis::resp3::request;
|
||||
using aedis::adapt;
|
||||
using aedis::operation;
|
||||
using connection = aedis::connection;
|
||||
using error_code = boost::system::error_code;
|
||||
using net::experimental::as_tuple;
|
||||
|
||||
BOOST_AUTO_TEST_CASE(push_filtered_out)
|
||||
{
|
||||
net::io_context ioc;
|
||||
auto const endpoints = resolve();
|
||||
connection conn{ioc};
|
||||
net::connect(conn.next_layer(), endpoints);
|
||||
|
||||
request req;
|
||||
req.push("HELLO", 3);
|
||||
req.push("PING");
|
||||
req.push("SUBSCRIBE", "channel");
|
||||
req.push("QUIT");
|
||||
|
||||
std::tuple<aedis::ignore, std::string, std::string> resp;
|
||||
conn.async_exec(req, adapt(resp), [](auto ec, auto){
|
||||
BOOST_TEST(!ec);
|
||||
});
|
||||
|
||||
conn.async_receive(adapt(), [](auto ec, auto){
|
||||
BOOST_TEST(!ec);
|
||||
});
|
||||
|
||||
conn.async_run([](auto ec){
|
||||
std::cout << "===> " << ec.message() << std::endl;
|
||||
BOOST_TEST(!ec);
|
||||
});
|
||||
|
||||
ioc.run();
|
||||
|
||||
BOOST_CHECK_EQUAL(std::get<1>(resp), "PONG");
|
||||
BOOST_CHECK_EQUAL(std::get<2>(resp), "OK");
|
||||
}
|
||||
|
||||
void receive_wrong_syntax(request const& req)
|
||||
{
|
||||
net::io_context ioc;
|
||||
auto const endpoints = resolve();
|
||||
connection conn{ioc};
|
||||
net::connect(conn.next_layer(), endpoints);
|
||||
|
||||
conn.async_exec(req, adapt(), [](auto ec, auto){
|
||||
BOOST_TEST(!ec);
|
||||
});
|
||||
|
||||
conn.async_run([](auto ec){
|
||||
BOOST_CHECK_EQUAL(ec, boost::asio::error::basic_errors::operation_aborted);
|
||||
});
|
||||
|
||||
conn.async_receive(adapt(), [&](auto ec, auto){
|
||||
BOOST_TEST(!ec);
|
||||
conn.cancel(aedis::operation::run);
|
||||
});
|
||||
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
#ifdef BOOST_ASIO_HAS_CO_AWAIT
|
||||
net::awaitable<void> push_consumer1(connection& conn, bool& push_received)
|
||||
{
|
||||
{
|
||||
auto [ec, ev] = co_await conn.async_receive(adapt(), as_tuple(net::use_awaitable));
|
||||
BOOST_TEST(!ec);
|
||||
}
|
||||
|
||||
{
|
||||
auto [ec, ev] = co_await conn.async_receive(adapt(), as_tuple(net::use_awaitable));
|
||||
BOOST_CHECK_EQUAL(ec, net::experimental::channel_errc::channel_cancelled);
|
||||
}
|
||||
|
||||
push_received = true;
|
||||
}
|
||||
|
||||
struct adapter_error {
|
||||
void
|
||||
operator()(
|
||||
std::size_t, aedis::resp3::node<boost::string_view> const&, boost::system::error_code& ec)
|
||||
{
|
||||
ec = aedis::error::incompatible_size;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
auto get_supported_response_size() const noexcept
|
||||
{ return static_cast<std::size_t>(-1);}
|
||||
|
||||
[[nodiscard]]
|
||||
auto get_max_read_size(std::size_t) const noexcept
|
||||
{ return static_cast<std::size_t>(-1);}
|
||||
};
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_push_adapter)
|
||||
{
|
||||
net::io_context ioc;
|
||||
auto const endpoints = resolve();
|
||||
connection conn{ioc};
|
||||
net::connect(conn.next_layer(), endpoints);
|
||||
|
||||
request req;
|
||||
req.push("HELLO", 3);
|
||||
req.push("PING");
|
||||
req.push("SUBSCRIBE", "channel");
|
||||
req.push("PING");
|
||||
|
||||
conn.async_receive(adapter_error{}, [](auto ec, auto) {
|
||||
BOOST_CHECK_EQUAL(ec, aedis::error::incompatible_size);
|
||||
});
|
||||
|
||||
conn.async_exec(req, adapt(), [](auto ec, auto){
|
||||
BOOST_CHECK_EQUAL(ec, net::experimental::error::channel_errors::channel_cancelled);
|
||||
});
|
||||
|
||||
conn.async_run([](auto ec){
|
||||
BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled);
|
||||
});
|
||||
|
||||
ioc.run();
|
||||
|
||||
// TODO: Reset the ioc reconnect and send a quit to ensure
|
||||
// reconnection is possible after an error.
|
||||
}
|
||||
|
||||
void test_push_is_received1(bool coalesce)
|
||||
{
|
||||
net::io_context ioc;
|
||||
auto const endpoints = resolve();
|
||||
connection conn{ioc};
|
||||
net::connect(conn.next_layer(), endpoints);
|
||||
|
||||
|
||||
request req{{false, coalesce}};
|
||||
req.push("HELLO", 3);
|
||||
req.push("SUBSCRIBE", "channel");
|
||||
req.push("QUIT");
|
||||
|
||||
conn.async_exec(req, adapt(), [](auto ec, auto){
|
||||
BOOST_TEST(!ec);
|
||||
});
|
||||
|
||||
conn.async_run([&](auto ec){
|
||||
BOOST_TEST(!ec);
|
||||
conn.cancel(operation::receive);
|
||||
});
|
||||
|
||||
bool push_received = false;
|
||||
net::co_spawn(
|
||||
ioc.get_executor(),
|
||||
push_consumer1(conn, push_received),
|
||||
net::detached);
|
||||
|
||||
ioc.run();
|
||||
|
||||
BOOST_TEST(push_received);
|
||||
}
|
||||
|
||||
void test_push_is_received2(bool coalesce)
|
||||
{
|
||||
request req1{{false, coalesce}};
|
||||
req1.push("HELLO", 3);
|
||||
req1.push("PING", "Message1");
|
||||
|
||||
request req2{{false, coalesce}};
|
||||
req2.push("SUBSCRIBE", "channel");
|
||||
|
||||
request req3{{false, coalesce}};
|
||||
req3.push("PING", "Message2");
|
||||
req3.push("QUIT");
|
||||
|
||||
net::io_context ioc;
|
||||
|
||||
auto const endpoints = resolve();
|
||||
connection conn{ioc};
|
||||
net::connect(conn.next_layer(), endpoints);
|
||||
|
||||
|
||||
auto handler =[](auto ec, auto...)
|
||||
{
|
||||
BOOST_TEST(!ec);
|
||||
};
|
||||
|
||||
conn.async_exec(req1, adapt(), handler);
|
||||
conn.async_exec(req2, adapt(), handler);
|
||||
conn.async_exec(req3, adapt(), handler);
|
||||
|
||||
conn.async_run([&](auto ec) {
|
||||
BOOST_TEST(!ec);
|
||||
conn.cancel(operation::receive);
|
||||
});
|
||||
|
||||
bool push_received = false;
|
||||
net::co_spawn(
|
||||
ioc.get_executor(),
|
||||
push_consumer1(conn, push_received),
|
||||
net::detached);
|
||||
|
||||
ioc.run();
|
||||
|
||||
BOOST_TEST(push_received);
|
||||
}
|
||||
|
||||
net::awaitable<void> push_consumer3(connection& conn)
|
||||
{
|
||||
for (;;)
|
||||
co_await conn.async_receive(adapt(), net::use_awaitable);
|
||||
}
|
||||
|
||||
// Test many subscribe requests.
|
||||
void test_push_many_subscribes(bool coalesce)
|
||||
{
|
||||
request req0{{false, coalesce}};
|
||||
req0.push("HELLO", 3);
|
||||
|
||||
request req1{{false, coalesce}};
|
||||
req1.push("PING", "Message1");
|
||||
|
||||
request req2{{false, coalesce}};
|
||||
req2.push("SUBSCRIBE", "channel");
|
||||
|
||||
request req3{{false, coalesce}};
|
||||
req3.push("QUIT");
|
||||
|
||||
auto handler =[](auto ec, auto...)
|
||||
{
|
||||
BOOST_TEST(!ec);
|
||||
};
|
||||
|
||||
net::io_context ioc;
|
||||
auto const endpoints = resolve();
|
||||
connection conn{ioc};
|
||||
net::connect(conn.next_layer(), endpoints);
|
||||
|
||||
conn.async_exec(req0, adapt(), handler);
|
||||
conn.async_exec(req1, adapt(), handler);
|
||||
conn.async_exec(req2, adapt(), handler);
|
||||
conn.async_exec(req2, adapt(), handler);
|
||||
conn.async_exec(req1, adapt(), handler);
|
||||
conn.async_exec(req2, adapt(), handler);
|
||||
conn.async_exec(req1, adapt(), handler);
|
||||
conn.async_exec(req2, adapt(), handler);
|
||||
conn.async_exec(req2, adapt(), handler);
|
||||
conn.async_exec(req1, adapt(), handler);
|
||||
conn.async_exec(req2, adapt(), handler);
|
||||
conn.async_exec(req3, adapt(), handler);
|
||||
|
||||
conn.async_run([&](auto ec) {
|
||||
BOOST_TEST(!ec);
|
||||
conn.cancel(operation::receive);
|
||||
});
|
||||
|
||||
net::co_spawn(ioc.get_executor(), push_consumer3(conn), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(push_received1)
|
||||
{
|
||||
test_push_is_received1(true);
|
||||
test_push_is_received1(false);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(push_received2)
|
||||
{
|
||||
test_push_is_received2(true);
|
||||
test_push_is_received2(false);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(many_subscribers)
|
||||
{
|
||||
test_push_many_subscribes(true);
|
||||
test_push_many_subscribes(false);
|
||||
}
|
||||
#endif
|
||||
|
||||
BOOST_AUTO_TEST_CASE(receive_wrong_syntax1)
|
||||
{
|
||||
request req1{{false}};
|
||||
req1.push("HELLO", 3);
|
||||
req1.push("PING", "Message");
|
||||
req1.push("SUBSCRIBE"); // Wrong command synthax.
|
||||
|
||||
req1.get_config().coalesce = true;
|
||||
receive_wrong_syntax(req1);
|
||||
|
||||
req1.get_config().coalesce = false;
|
||||
receive_wrong_syntax(req1);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(receice_wrong_syntay2)
|
||||
{
|
||||
request req2{{false}};
|
||||
req2.push("HELLO", 3);
|
||||
req2.push("SUBSCRIBE"); // Wrong command syntax.
|
||||
|
||||
req2.get_config().coalesce = true;
|
||||
receive_wrong_syntax(req2);
|
||||
|
||||
req2.get_config().coalesce = false;
|
||||
receive_wrong_syntax(req2);
|
||||
}
|
||||
|
||||
#else
|
||||
int main() {}
|
||||
#endif
|
||||
@@ -13,13 +13,13 @@
|
||||
|
||||
#include <aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
#include "common.hpp"
|
||||
|
||||
namespace net = boost::asio;
|
||||
|
||||
using aedis::adapt;
|
||||
using aedis::endpoint;
|
||||
using aedis::resp3::request;
|
||||
using connection = aedis::connection<>;
|
||||
using connection = aedis::connection;
|
||||
using error_code = boost::system::error_code;
|
||||
using operation = aedis::operation;
|
||||
|
||||
@@ -27,34 +27,37 @@ using operation = aedis::operation;
|
||||
BOOST_AUTO_TEST_CASE(test_quit_no_coalesce)
|
||||
{
|
||||
net::io_context ioc;
|
||||
auto db = std::make_shared<connection>(ioc);
|
||||
|
||||
auto const endpoints = resolve();
|
||||
connection conn{ioc};
|
||||
net::connect(conn.next_layer(), endpoints);
|
||||
|
||||
request req1{{false, false}};
|
||||
req1.push("HELLO", 3);
|
||||
req1.push("PING");
|
||||
|
||||
request req2{{false, false}};
|
||||
req2.push("QUIT");
|
||||
|
||||
db->async_exec(req1, adapt(), [](auto ec, auto){
|
||||
conn.async_exec(req1, adapt(), [](auto ec, auto){
|
||||
BOOST_TEST(!ec);
|
||||
});
|
||||
db->async_exec(req2, adapt(), [](auto ec, auto) {
|
||||
conn.async_exec(req2, adapt(), [](auto ec, auto) {
|
||||
BOOST_TEST(!ec);
|
||||
});
|
||||
db->async_exec(req1, adapt(), [](auto ec, auto){
|
||||
conn.async_exec(req1, adapt(), [](auto ec, auto){
|
||||
BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled);
|
||||
});
|
||||
db->async_exec(req1, adapt(), [](auto ec, auto){
|
||||
conn.async_exec(req1, adapt(), [](auto ec, auto){
|
||||
BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled);
|
||||
});
|
||||
db->async_exec(req1, adapt(), [](auto ec, auto){
|
||||
conn.async_exec(req1, adapt(), [](auto ec, auto){
|
||||
BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled);
|
||||
});
|
||||
|
||||
endpoint ep{"127.0.0.1", "6379"};
|
||||
db->async_run(ep, {}, [db](auto ec){
|
||||
BOOST_CHECK_EQUAL(ec, net::error::misc_errors::eof);
|
||||
db->cancel(operation::exec);
|
||||
conn.async_run([&](auto ec){
|
||||
BOOST_TEST(!ec);
|
||||
conn.cancel(operation::exec);
|
||||
});
|
||||
|
||||
ioc.run();
|
||||
@@ -63,13 +66,21 @@ BOOST_AUTO_TEST_CASE(test_quit_no_coalesce)
|
||||
void test_quit2(bool coalesce)
|
||||
{
|
||||
request req{{false, coalesce}};
|
||||
req.push("HELLO", 3);
|
||||
req.push("QUIT");
|
||||
|
||||
net::io_context ioc;
|
||||
auto db = std::make_shared<connection>(ioc);
|
||||
endpoint ep{"127.0.0.1", "6379"};
|
||||
db->async_run(ep, req, adapt(), {}, [](auto ec, auto) {
|
||||
BOOST_CHECK_EQUAL(ec, net::error::misc_errors::eof);
|
||||
auto const endpoints = resolve();
|
||||
connection conn{ioc};
|
||||
net::connect(conn.next_layer(), endpoints);
|
||||
|
||||
|
||||
conn.async_exec(req, adapt(), [](auto ec, auto) {
|
||||
BOOST_TEST(!ec);
|
||||
});
|
||||
|
||||
conn.async_run([](auto ec) {
|
||||
BOOST_TEST(!ec);
|
||||
});
|
||||
|
||||
ioc.run();
|
||||
@@ -77,7 +88,6 @@ void test_quit2(bool coalesce)
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_quit)
|
||||
{
|
||||
std::cout << boost::unit_test::framework::current_test_case().p_name << std::endl;
|
||||
test_quit2(true);
|
||||
test_quit2(false);
|
||||
}
|
||||
@@ -13,44 +13,46 @@
|
||||
|
||||
#include <aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
#include "common.hpp"
|
||||
|
||||
namespace net = boost::asio;
|
||||
|
||||
using aedis::adapt;
|
||||
using aedis::endpoint;
|
||||
using aedis::resp3::request;
|
||||
using connection = aedis::connection<>;
|
||||
using connection = aedis::connection;
|
||||
using error_code = boost::system::error_code;
|
||||
using operation = aedis::operation;
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_quit_coalesce)
|
||||
{
|
||||
net::io_context ioc;
|
||||
auto db = std::make_shared<connection>(ioc);
|
||||
auto const endpoints = resolve();
|
||||
connection conn{ioc};
|
||||
net::connect(conn.next_layer(), endpoints);
|
||||
|
||||
request req1{{false, true}};
|
||||
req1.push("HELLO", 3);
|
||||
req1.push("PING");
|
||||
|
||||
request req2{{false, true}};
|
||||
req2.push("QUIT");
|
||||
|
||||
db->async_exec(req1, adapt(), [](auto ec, auto){
|
||||
conn.async_exec(req1, adapt(), [](auto ec, auto){
|
||||
BOOST_TEST(!ec);
|
||||
});
|
||||
db->async_exec(req2, adapt(), [](auto ec, auto){
|
||||
conn.async_exec(req2, adapt(), [](auto ec, auto){
|
||||
BOOST_TEST(!ec);
|
||||
});
|
||||
db->async_exec(req1, adapt(), [](auto ec, auto){
|
||||
conn.async_exec(req1, adapt(), [](auto ec, auto){
|
||||
BOOST_CHECK_EQUAL(ec, net::error::misc_errors::eof);
|
||||
});
|
||||
db->async_exec(req1, adapt(), [](auto ec, auto){
|
||||
conn.async_exec(req1, adapt(), [](auto ec, auto){
|
||||
BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled);
|
||||
});
|
||||
|
||||
endpoint ep{"127.0.0.1", "6379"};
|
||||
db->async_run(ep, {}, [db](auto ec){
|
||||
conn.async_run([&](auto ec){
|
||||
BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled);
|
||||
db->cancel(operation::exec);
|
||||
conn.cancel(operation::exec);
|
||||
});
|
||||
|
||||
ioc.run();
|
||||
119
tests/conn_reconnect.cpp
Normal file
119
tests/conn_reconnect.cpp
Normal file
@@ -0,0 +1,119 @@
|
||||
/* 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>
|
||||
#ifdef BOOST_ASIO_HAS_CO_AWAIT
|
||||
|
||||
#define BOOST_TEST_MODULE low level
|
||||
#include <boost/test/included/unit_test.hpp>
|
||||
|
||||
#include <aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
#include "common.hpp"
|
||||
|
||||
namespace net = boost::asio;
|
||||
|
||||
using aedis::resp3::request;
|
||||
using aedis::adapt;
|
||||
using connection = aedis::connection;
|
||||
using error_code = boost::system::error_code;
|
||||
|
||||
#include <boost/asio/experimental/awaitable_operators.hpp>
|
||||
using namespace boost::asio::experimental::awaitable_operators;
|
||||
|
||||
net::awaitable<void> test_reconnect_impl()
|
||||
{
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
|
||||
request req;
|
||||
req.push("QUIT");
|
||||
|
||||
auto const endpoints = resolve();
|
||||
connection conn{ex};
|
||||
|
||||
int i = 0;
|
||||
for (; i < 5; ++i) {
|
||||
boost::system::error_code ec1, ec2;
|
||||
net::connect(conn.next_layer(), endpoints);
|
||||
co_await (
|
||||
conn.async_exec(req, adapt(), net::redirect_error(net::use_awaitable, ec1)) &&
|
||||
conn.async_run(net::redirect_error(net::use_awaitable, ec2))
|
||||
);
|
||||
|
||||
BOOST_TEST(!ec1);
|
||||
BOOST_TEST(!ec2);
|
||||
conn.reset_stream();
|
||||
}
|
||||
|
||||
BOOST_CHECK_EQUAL(i, 5);
|
||||
co_return;
|
||||
}
|
||||
|
||||
// Test whether the client works after a reconnect.
|
||||
BOOST_AUTO_TEST_CASE(test_reconnect)
|
||||
{
|
||||
net::io_context ioc;
|
||||
net::co_spawn(ioc, test_reconnect_impl(), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
auto async_test_reconnect_timeout() -> net::awaitable<void>
|
||||
{
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
|
||||
net::steady_timer st{ex};
|
||||
|
||||
auto conn = std::make_shared<connection>(ex);
|
||||
auto const endpoints = resolve();
|
||||
|
||||
boost::system::error_code ec1, ec2, ec3;
|
||||
|
||||
request req1;
|
||||
req1.get_config().cancel_if_not_connected = false;
|
||||
req1.get_config().cancel_on_connection_lost = true;
|
||||
req1.push("HELLO", 3);
|
||||
req1.push("CLIENT", "PAUSE", 7000);
|
||||
|
||||
net::connect(conn->next_layer(), endpoints);
|
||||
st.expires_after(std::chrono::seconds{1});
|
||||
co_await (
|
||||
conn->async_exec(req1, adapt(), net::redirect_error(net::use_awaitable, ec1)) ||
|
||||
conn->async_run(net::redirect_error(net::use_awaitable, ec2)) ||
|
||||
st.async_wait(net::redirect_error(net::use_awaitable, 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.push("HELLO", 3);
|
||||
req2.push("QUIT");
|
||||
|
||||
net::connect(conn->next_layer(), endpoints);
|
||||
st.expires_after(std::chrono::seconds{1});
|
||||
co_await (
|
||||
conn->async_exec(req1, adapt(), net::redirect_error(net::use_awaitable, ec1)) ||
|
||||
conn->async_run(net::redirect_error(net::use_awaitable, ec2)) ||
|
||||
st.async_wait(net::redirect_error(net::use_awaitable, ec3))
|
||||
);
|
||||
|
||||
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)
|
||||
{
|
||||
net::io_context ioc;
|
||||
net::co_spawn(ioc, async_test_reconnect_timeout(), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
#else
|
||||
int main(){}
|
||||
#endif
|
||||
179
tests/conn_run_cancel.cpp
Normal file
179
tests/conn_run_cancel.cpp
Normal file
@@ -0,0 +1,179 @@
|
||||
/* 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>
|
||||
#ifdef BOOST_ASIO_HAS_CO_AWAIT
|
||||
#include <boost/system/errc.hpp>
|
||||
#include <boost/asio/experimental/as_tuple.hpp>
|
||||
|
||||
#define BOOST_TEST_MODULE low level
|
||||
#include <boost/test/included/unit_test.hpp>
|
||||
|
||||
#include <aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
#include "common.hpp"
|
||||
|
||||
namespace net = boost::asio;
|
||||
|
||||
using aedis::resp3::request;
|
||||
using aedis::operation;
|
||||
using aedis::adapt;
|
||||
using connection = aedis::connection;
|
||||
using error_code = boost::system::error_code;
|
||||
using net::experimental::as_tuple;
|
||||
|
||||
#include <boost/asio/experimental/awaitable_operators.hpp>
|
||||
using namespace net::experimental::awaitable_operators;
|
||||
|
||||
auto async_cancel_run_with_timer() -> net::awaitable<void>
|
||||
{
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
auto const endpoints = resolve();
|
||||
connection conn{ex};
|
||||
net::connect(conn.next_layer(), endpoints);
|
||||
|
||||
net::steady_timer st{ex};
|
||||
st.expires_after(std::chrono::seconds{1});
|
||||
|
||||
boost::system::error_code ec1, ec2;
|
||||
co_await (
|
||||
conn.async_run(net::redirect_error(net::use_awaitable, ec1)) ||
|
||||
st.async_wait(net::redirect_error(net::use_awaitable, ec2))
|
||||
);
|
||||
|
||||
BOOST_CHECK_EQUAL(ec1, boost::asio::error::basic_errors::operation_aborted);
|
||||
BOOST_TEST(!ec2);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(cancel_run_with_timer)
|
||||
{
|
||||
net::io_context ioc;
|
||||
net::co_spawn(ioc.get_executor(), async_cancel_run_with_timer(), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
auto
|
||||
async_check_cancellation_not_missed(int n, std::chrono::milliseconds ms) -> net::awaitable<void>
|
||||
{
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
auto const endpoints = resolve();
|
||||
connection conn{ex};
|
||||
|
||||
net::steady_timer timer{ex};
|
||||
|
||||
for (auto i = 0; i < n; ++i) {
|
||||
timer.expires_after(ms);
|
||||
net::connect(conn.next_layer(), endpoints);
|
||||
boost::system::error_code ec1, ec2;
|
||||
co_await (
|
||||
conn.async_run(net::redirect_error(net::use_awaitable, ec1)) ||
|
||||
timer.async_wait(net::redirect_error(net::use_awaitable, ec2))
|
||||
);
|
||||
BOOST_CHECK_EQUAL(ec1, boost::asio::error::basic_errors::operation_aborted);
|
||||
std::cout << "Counter: " << i << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
// See PR #29
|
||||
BOOST_AUTO_TEST_CASE(check_implicit_cancel_not_missed_0)
|
||||
{
|
||||
net::io_context ioc;
|
||||
net::co_spawn(ioc, async_check_cancellation_not_missed(10, std::chrono::milliseconds{0}), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(check_implicit_cancel_not_missed_2)
|
||||
{
|
||||
net::io_context ioc;
|
||||
net::co_spawn(ioc, async_check_cancellation_not_missed(20, std::chrono::milliseconds{2}), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(check_implicit_cancel_not_missed_8)
|
||||
{
|
||||
net::io_context ioc;
|
||||
net::co_spawn(ioc, async_check_cancellation_not_missed(20, std::chrono::milliseconds{8}), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(check_implicit_cancel_not_missed_16)
|
||||
{
|
||||
net::io_context ioc;
|
||||
net::co_spawn(ioc, async_check_cancellation_not_missed(20, std::chrono::milliseconds{16}), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(check_implicit_cancel_not_missed_32)
|
||||
{
|
||||
net::io_context ioc;
|
||||
net::co_spawn(ioc, async_check_cancellation_not_missed(20, std::chrono::milliseconds{32}), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(check_implicit_cancel_not_missed_64)
|
||||
{
|
||||
net::io_context ioc;
|
||||
net::co_spawn(ioc, async_check_cancellation_not_missed(20, std::chrono::milliseconds{64}), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(check_implicit_cancel_not_missed_128)
|
||||
{
|
||||
net::io_context ioc;
|
||||
net::co_spawn(ioc, async_check_cancellation_not_missed(20, std::chrono::milliseconds{128}), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(check_implicit_cancel_not_missed_256)
|
||||
{
|
||||
net::io_context ioc;
|
||||
net::co_spawn(ioc, async_check_cancellation_not_missed(20, std::chrono::milliseconds{256}), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(check_implicit_cancel_not_missed_512)
|
||||
{
|
||||
net::io_context ioc;
|
||||
net::co_spawn(ioc, async_check_cancellation_not_missed(20, std::chrono::milliseconds{512}), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(check_implicit_cancel_not_missed_1024)
|
||||
{
|
||||
net::io_context ioc;
|
||||
net::co_spawn(ioc, async_check_cancellation_not_missed(20, std::chrono::milliseconds{1024}), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(reset_before_run_completes)
|
||||
{
|
||||
net::io_context ioc;
|
||||
auto const endpoints = resolve();
|
||||
connection conn{ioc};
|
||||
net::connect(conn.next_layer(), endpoints);
|
||||
|
||||
|
||||
// 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, adapt(), [&](auto ec, auto){
|
||||
BOOST_TEST(!ec);
|
||||
conn.reset_stream();
|
||||
});
|
||||
|
||||
conn.async_run([&](auto ec){
|
||||
BOOST_CHECK_EQUAL(ec, net::error::operation_aborted);
|
||||
});
|
||||
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
#else
|
||||
int main(){}
|
||||
#endif
|
||||
72
tests/conn_tls.cpp
Normal file
72
tests/conn_tls.cpp
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)
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/asio/ssl.hpp>
|
||||
|
||||
#define BOOST_TEST_MODULE low level
|
||||
#include <boost/test/included/unit_test.hpp>
|
||||
|
||||
#include <aedis.hpp>
|
||||
#include <aedis/ssl/connection.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
#include "common.hpp"
|
||||
|
||||
namespace net = boost::asio;
|
||||
|
||||
using aedis::adapt;
|
||||
using aedis::resp3::request;
|
||||
using connection = aedis::ssl::connection;
|
||||
|
||||
struct endpoint {
|
||||
std::string host;
|
||||
std::string port;
|
||||
};
|
||||
|
||||
bool verify_certificate(bool, net::ssl::verify_context&)
|
||||
{
|
||||
std::cout << "set_verify_callback" << std::endl;
|
||||
return true;
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(ping)
|
||||
{
|
||||
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");
|
||||
|
||||
std::string out;
|
||||
auto resp = std::tie(std::ignore, out, std::ignore);
|
||||
|
||||
auto const endpoints = resolve("db.occase.de", "6380");
|
||||
|
||||
net::io_context ioc;
|
||||
net::ssl::context ctx{net::ssl::context::sslv23};
|
||||
connection conn{ioc, ctx};
|
||||
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, adapt(resp), [](auto ec, auto) {
|
||||
BOOST_TEST(!ec);
|
||||
});
|
||||
|
||||
conn.async_run([](auto ec) {
|
||||
BOOST_TEST(!ec);
|
||||
});
|
||||
|
||||
ioc.run();
|
||||
|
||||
BOOST_CHECK_EQUAL(in, out);
|
||||
}
|
||||
|
||||
@@ -1,153 +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 <iostream>
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/system/errc.hpp>
|
||||
|
||||
#define BOOST_TEST_MODULE low level
|
||||
#include <boost/test/included/unit_test.hpp>
|
||||
|
||||
#include <aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
namespace net = boost::asio;
|
||||
|
||||
using connection = aedis::connection<>;
|
||||
using endpoint = aedis::endpoint;
|
||||
using error_code = boost::system::error_code;
|
||||
|
||||
bool is_host_not_found(error_code ec)
|
||||
{
|
||||
if (ec == net::error::netdb_errors::host_not_found) return true;
|
||||
if (ec == net::error::netdb_errors::host_not_found_try_again) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
error_code test_async_run(endpoint ep, connection::timeouts cfg = {})
|
||||
{
|
||||
net::io_context ioc;
|
||||
connection db{ioc};
|
||||
error_code ret;
|
||||
db.async_run(ep, cfg, [&](auto ec) { ret = ec; });
|
||||
ioc.run();
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Tests whether resolve fails with the correct error.
|
||||
BOOST_AUTO_TEST_CASE(test_resolve)
|
||||
{
|
||||
std::cout << boost::unit_test::framework::current_test_case().p_name << std::endl;
|
||||
|
||||
endpoint ep;
|
||||
ep.host = "Atibaia";
|
||||
ep.port = "6379";
|
||||
|
||||
connection::timeouts cfg;
|
||||
cfg.resolve_timeout = std::chrono::seconds{100};
|
||||
auto const ec = test_async_run(ep, cfg);
|
||||
|
||||
BOOST_TEST(is_host_not_found(ec));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_resolve_with_timeout)
|
||||
{
|
||||
std::cout << boost::unit_test::framework::current_test_case().p_name << std::endl;
|
||||
|
||||
endpoint ep;
|
||||
ep.host = "Atibaia";
|
||||
ep.port = "6379";
|
||||
|
||||
connection::timeouts cfg;
|
||||
// Low-enough to cause a timeout always.
|
||||
cfg.resolve_timeout = std::chrono::milliseconds{1};
|
||||
auto const ec = test_async_run(ep, cfg);
|
||||
BOOST_CHECK_EQUAL(ec, aedis::error::resolve_timeout);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_connect)
|
||||
{
|
||||
std::cout << boost::unit_test::framework::current_test_case().p_name << std::endl;
|
||||
|
||||
endpoint ep;
|
||||
ep.host = "127.0.0.1";
|
||||
ep.port = "1";
|
||||
|
||||
connection::timeouts cfg;
|
||||
cfg.connect_timeout = std::chrono::seconds{100};
|
||||
auto const ec = test_async_run(ep, cfg);
|
||||
BOOST_CHECK_EQUAL(ec, net::error::basic_errors::connection_refused);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_connect_timeout)
|
||||
{
|
||||
std::cout << boost::unit_test::framework::current_test_case().p_name << std::endl;
|
||||
|
||||
endpoint ep;
|
||||
ep.host = "example.com";
|
||||
ep.port = "1";
|
||||
|
||||
connection::timeouts cfg;
|
||||
cfg.connect_timeout = std::chrono::milliseconds{1};
|
||||
auto const ec = test_async_run(ep, cfg);
|
||||
BOOST_CHECK_EQUAL(ec, aedis::error::connect_timeout);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_hello_fail)
|
||||
{
|
||||
std::cout << boost::unit_test::framework::current_test_case().p_name << std::endl;
|
||||
|
||||
// Succeeds with the tcp connection but fails the hello.
|
||||
endpoint ep;
|
||||
ep.host = "google.com";
|
||||
ep.port = "80";
|
||||
|
||||
auto const ec = test_async_run(ep);
|
||||
BOOST_CHECK_EQUAL(ec, aedis::error::invalid_data_type);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_hello_tls_over_plain_fail)
|
||||
{
|
||||
std::cout << boost::unit_test::framework::current_test_case().p_name << std::endl;
|
||||
|
||||
endpoint ep;
|
||||
ep.host = "google.com";
|
||||
ep.port = "443";
|
||||
|
||||
auto const ec = test_async_run(ep);
|
||||
BOOST_CHECK_EQUAL(ec, net::error::misc_errors::eof);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_auth_fail)
|
||||
{
|
||||
std::cout << boost::unit_test::framework::current_test_case().p_name << std::endl;
|
||||
|
||||
// Should cause an error in the authentication as our redis server
|
||||
// has no authentication configured.
|
||||
endpoint ep;
|
||||
ep.host = "127.0.0.1";
|
||||
ep.port = "6379";
|
||||
ep.username = "caboclo-do-mato";
|
||||
ep.password = "jabuticaba";
|
||||
|
||||
auto const ec = test_async_run(ep);
|
||||
BOOST_CHECK_EQUAL(ec, aedis::error::resp3_simple_error);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_wrong_role)
|
||||
{
|
||||
std::cout << boost::unit_test::framework::current_test_case().p_name << std::endl;
|
||||
|
||||
// Should cause an error in the authentication as our redis server
|
||||
// has no authentication configured.
|
||||
endpoint ep;
|
||||
ep.host = "127.0.0.1";
|
||||
ep.port = "6379";
|
||||
ep.role = "errado";
|
||||
|
||||
auto const ec = test_async_run(ep);
|
||||
BOOST_CHECK_EQUAL(ec, aedis::error::unexpected_server_role);
|
||||
}
|
||||
@@ -1,152 +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 <iostream>
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/system/errc.hpp>
|
||||
#include <boost/asio/experimental/as_tuple.hpp>
|
||||
|
||||
#define BOOST_TEST_MODULE low level
|
||||
#include <boost/test/included/unit_test.hpp>
|
||||
|
||||
#include <aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
namespace net = boost::asio;
|
||||
|
||||
using aedis::resp3::request;
|
||||
using aedis::adapt;
|
||||
using connection = aedis::connection<>;
|
||||
using endpoint = aedis::endpoint;
|
||||
using error_code = boost::system::error_code;
|
||||
using net::experimental::as_tuple;
|
||||
|
||||
#ifdef BOOST_ASIO_HAS_CO_AWAIT
|
||||
#include <boost/asio/experimental/awaitable_operators.hpp>
|
||||
using namespace net::experimental::awaitable_operators;
|
||||
|
||||
net::awaitable<void> send_after(std::shared_ptr<connection> db, std::chrono::milliseconds ms)
|
||||
{
|
||||
net::steady_timer st{co_await net::this_coro::executor};
|
||||
st.expires_after(ms);
|
||||
co_await st.async_wait(net::use_awaitable);
|
||||
|
||||
request req;
|
||||
req.push("CLIENT", "PAUSE", ms.count());
|
||||
|
||||
auto [ec, n] = co_await db->async_exec(req, adapt(), as_tuple(net::use_awaitable));
|
||||
BOOST_TEST(!ec);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_idle)
|
||||
{
|
||||
std::cout << boost::unit_test::framework::current_test_case().p_name << std::endl;
|
||||
std::chrono::milliseconds ms{5000};
|
||||
|
||||
{
|
||||
std::cout << "test_idle" << std::endl;
|
||||
connection::timeouts cfg;
|
||||
cfg.resolve_timeout = std::chrono::seconds{1};
|
||||
cfg.connect_timeout = std::chrono::seconds{1};
|
||||
cfg.ping_interval = std::chrono::seconds{1};
|
||||
|
||||
net::io_context ioc;
|
||||
auto db = std::make_shared<connection>(ioc);
|
||||
|
||||
net::co_spawn(ioc.get_executor(), send_after(db, ms), net::detached);
|
||||
|
||||
endpoint ep{"127.0.0.1", "6379"};
|
||||
db->async_run(ep, cfg, [](auto ec){
|
||||
BOOST_CHECK_EQUAL(ec, aedis::error::idle_timeout);
|
||||
});
|
||||
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------
|
||||
// Since we have paused the server above, we have to wait until the
|
||||
// server is responsive again, so as not to cause other tests to
|
||||
// fail.
|
||||
|
||||
{
|
||||
net::io_context ioc;
|
||||
auto db = std::make_shared<connection>(ioc);
|
||||
connection::timeouts cfg;
|
||||
cfg.ping_interval = 2 * ms;
|
||||
cfg.resolve_timeout = 2 * ms;
|
||||
cfg.connect_timeout = 2 * ms;
|
||||
cfg.ping_interval = 2 * ms;
|
||||
cfg.resp3_handshake_timeout = 2 * ms;
|
||||
|
||||
request req;
|
||||
req.push("QUIT");
|
||||
|
||||
endpoint ep{"127.0.0.1", "6379"};
|
||||
db->async_run(ep, req, adapt(), cfg, [](auto ec, auto){
|
||||
BOOST_CHECK_EQUAL(ec, net::error::misc_errors::eof);
|
||||
});
|
||||
|
||||
ioc.run();
|
||||
}
|
||||
}
|
||||
|
||||
net::awaitable<void> reconnect(std::shared_ptr<connection> db)
|
||||
{
|
||||
net::steady_timer timer{co_await net::this_coro::executor};
|
||||
for (auto i = 0; i < 1000; ++i) {
|
||||
timer.expires_after(std::chrono::milliseconds{10});
|
||||
endpoint ep{"127.0.0.1", "6379"};
|
||||
co_await (
|
||||
db->async_run(ep, {}, net::use_awaitable) ||
|
||||
timer.async_wait(net::use_awaitable)
|
||||
);
|
||||
std::cout << i << ": Retrying" << std::endl;
|
||||
}
|
||||
std::cout << "Finished" << std::endl;
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_cancelation)
|
||||
{
|
||||
std::cout << boost::unit_test::framework::current_test_case().p_name << std::endl;
|
||||
net::io_context ioc;
|
||||
auto db = std::make_shared<connection>(ioc);
|
||||
net::co_spawn(ioc, reconnect(db), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
#endif
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_wrong_data_type)
|
||||
{
|
||||
std::cout << boost::unit_test::framework::current_test_case().p_name << std::endl;
|
||||
request req;
|
||||
req.push("QUIT");
|
||||
|
||||
// Wrong data type.
|
||||
std::tuple<int> resp;
|
||||
net::io_context ioc;
|
||||
auto db = std::make_shared<connection>(ioc);
|
||||
endpoint ep{"127.0.0.1", "6379"};
|
||||
db->async_run(ep, req, adapt(resp), {}, [](auto ec, auto){
|
||||
BOOST_CHECK_EQUAL(ec, aedis::error::not_a_number);
|
||||
});
|
||||
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_not_connected)
|
||||
{
|
||||
std::cout << boost::unit_test::framework::current_test_case().p_name << std::endl;
|
||||
request req{{true}};
|
||||
req.push("PING");
|
||||
|
||||
net::io_context ioc;
|
||||
auto db = std::make_shared<connection>(ioc);
|
||||
db->async_exec(req, adapt(), [](auto ec, auto){
|
||||
BOOST_CHECK_EQUAL(ec, aedis::error::not_connected);
|
||||
});
|
||||
|
||||
ioc.run();
|
||||
}
|
||||
@@ -1,277 +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 <iostream>
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/system/errc.hpp>
|
||||
#include <boost/asio/experimental/as_tuple.hpp>
|
||||
|
||||
#define BOOST_TEST_MODULE low level
|
||||
#include <boost/test/included/unit_test.hpp>
|
||||
|
||||
#include <aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
namespace net = boost::asio;
|
||||
|
||||
using aedis::resp3::request;
|
||||
using aedis::adapt;
|
||||
using aedis::endpoint;
|
||||
using aedis::operation;
|
||||
using connection = aedis::connection<>;
|
||||
using error_code = boost::system::error_code;
|
||||
using net::experimental::as_tuple;
|
||||
|
||||
// Checks whether we get idle timeout when no push reader is set.
|
||||
void test_missing_push_reader1(bool coalesce)
|
||||
{
|
||||
net::io_context ioc;
|
||||
auto db = std::make_shared<connection>(ioc);
|
||||
|
||||
request req{{false, coalesce}};
|
||||
req.push("SUBSCRIBE", "channel");
|
||||
|
||||
endpoint ep{"127.0.0.1", "6379"};
|
||||
db->async_run(ep, req, adapt(), {}, [](auto ec, auto){
|
||||
BOOST_CHECK_EQUAL(ec, aedis::error::idle_timeout);
|
||||
});
|
||||
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
void test_missing_push_reader2(bool coalesce)
|
||||
{
|
||||
net::io_context ioc;
|
||||
auto db = std::make_shared<connection>(ioc);
|
||||
|
||||
request req{{false, coalesce}}; // Wrong command syntax.
|
||||
req.push("SUBSCRIBE");
|
||||
|
||||
endpoint ep{"127.0.0.1", "6379"};
|
||||
db->async_run(ep, req, adapt(), {}, [](auto ec, auto){
|
||||
BOOST_CHECK_EQUAL(ec, aedis::error::idle_timeout);
|
||||
});
|
||||
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
void test_missing_push_reader3(bool coalesce)
|
||||
{
|
||||
net::io_context ioc;
|
||||
auto db = std::make_shared<connection>(ioc);
|
||||
|
||||
request req{{false, coalesce}}; // Wrong command synthax.
|
||||
req.push("PING", "Message");
|
||||
req.push("SUBSCRIBE");
|
||||
|
||||
endpoint ep{"127.0.0.1", "6379"};
|
||||
db->async_run(ep, req, adapt(), {}, [](auto ec, auto){
|
||||
BOOST_CHECK_EQUAL(ec, aedis::error::idle_timeout);
|
||||
});
|
||||
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
#ifdef BOOST_ASIO_HAS_CO_AWAIT
|
||||
net::awaitable<void> push_consumer1(std::shared_ptr<connection> db, bool& push_received)
|
||||
{
|
||||
{
|
||||
auto [ec, ev] = co_await db->async_receive_push(adapt(), as_tuple(net::use_awaitable));
|
||||
BOOST_TEST(!ec);
|
||||
}
|
||||
|
||||
{
|
||||
auto [ec, ev] = co_await db->async_receive_push(adapt(), as_tuple(net::use_awaitable));
|
||||
BOOST_CHECK_EQUAL(ec, boost::asio::experimental::channel_errc::channel_cancelled);
|
||||
}
|
||||
|
||||
push_received = true;
|
||||
}
|
||||
|
||||
struct adapter_error {
|
||||
void
|
||||
operator()(
|
||||
std::size_t, aedis::resp3::node<boost::string_view> const&, boost::system::error_code& ec)
|
||||
{
|
||||
ec = aedis::error::incompatible_size;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
auto get_supported_response_size() const noexcept
|
||||
{ return static_cast<std::size_t>(-1);}
|
||||
|
||||
[[nodiscard]]
|
||||
auto get_max_read_size(std::size_t) const noexcept
|
||||
{ return static_cast<std::size_t>(-1);}
|
||||
};
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_push_adapter)
|
||||
{
|
||||
net::io_context ioc;
|
||||
auto db = std::make_shared<connection>(ioc);
|
||||
|
||||
request req;
|
||||
req.push("PING");
|
||||
req.push("SUBSCRIBE", "channel");
|
||||
req.push("PING");
|
||||
|
||||
db->async_receive_push(adapter_error{}, [](auto ec, auto) {
|
||||
BOOST_CHECK_EQUAL(ec, aedis::error::incompatible_size);
|
||||
});
|
||||
|
||||
endpoint ep{"127.0.0.1", "6379"};
|
||||
db->async_run(ep, req, adapt(), {}, [db](auto, auto){
|
||||
//BOOST_CHECK_EQUAL(ec, net::error::misc_errors::eof);
|
||||
});
|
||||
|
||||
ioc.run();
|
||||
|
||||
// TODO: Reset the ioc reconnect and send a quit to ensure
|
||||
// reconnection is possible after an error.
|
||||
}
|
||||
|
||||
void test_push_is_received1(bool coalesce)
|
||||
{
|
||||
net::io_context ioc;
|
||||
auto db = std::make_shared<connection>(ioc);
|
||||
|
||||
request req{{false, coalesce}};
|
||||
req.push("SUBSCRIBE", "channel");
|
||||
req.push("QUIT");
|
||||
|
||||
endpoint ep{"127.0.0.1", "6379"};
|
||||
db->async_run(ep, req, adapt(), {}, [db](auto ec, auto){
|
||||
BOOST_CHECK_EQUAL(ec, net::error::misc_errors::eof);
|
||||
db->cancel(operation::receive_push);
|
||||
});
|
||||
|
||||
bool push_received = false;
|
||||
net::co_spawn(
|
||||
ioc.get_executor(),
|
||||
push_consumer1(db, push_received),
|
||||
net::detached);
|
||||
|
||||
ioc.run();
|
||||
|
||||
BOOST_TEST(push_received);
|
||||
}
|
||||
|
||||
void test_push_is_received2(bool coalesce)
|
||||
{
|
||||
request req1{{false, coalesce}};
|
||||
req1.push("PING", "Message1");
|
||||
|
||||
request req2{{false, coalesce}};
|
||||
req2.push("SUBSCRIBE", "channel");
|
||||
|
||||
request req3{{false, coalesce}};
|
||||
req3.push("PING", "Message2");
|
||||
req3.push("QUIT");
|
||||
|
||||
net::io_context ioc;
|
||||
|
||||
auto db = std::make_shared<connection>(ioc);
|
||||
|
||||
auto handler =[](auto ec, auto...)
|
||||
{
|
||||
BOOST_TEST(!ec);
|
||||
};
|
||||
|
||||
db->async_exec(req1, adapt(), handler);
|
||||
db->async_exec(req2, adapt(), handler);
|
||||
db->async_exec(req3, adapt(), handler);
|
||||
|
||||
endpoint ep{"127.0.0.1", "6379"};
|
||||
db->async_run(ep, {}, [db](auto ec, auto...) {
|
||||
BOOST_CHECK_EQUAL(ec, net::error::misc_errors::eof);
|
||||
db->cancel(operation::receive_push);
|
||||
});
|
||||
|
||||
bool push_received = false;
|
||||
net::co_spawn(
|
||||
ioc.get_executor(),
|
||||
push_consumer1(db, push_received),
|
||||
net::detached);
|
||||
|
||||
ioc.run();
|
||||
|
||||
BOOST_TEST(push_received);
|
||||
}
|
||||
|
||||
net::awaitable<void> push_consumer3(std::shared_ptr<connection> db)
|
||||
{
|
||||
for (;;)
|
||||
co_await db->async_receive_push(adapt(), net::use_awaitable);
|
||||
}
|
||||
|
||||
// Test many subscribe requests.
|
||||
void test_push_many_subscribes(bool coalesce)
|
||||
{
|
||||
request req0{{false, coalesce}};
|
||||
req0.push("HELLO", 3);
|
||||
|
||||
request req1{{false, coalesce}};
|
||||
req1.push("PING", "Message1");
|
||||
|
||||
request req2{{false, coalesce}};
|
||||
req2.push("SUBSCRIBE", "channel");
|
||||
|
||||
request req3{{false, coalesce}};
|
||||
req3.push("QUIT");
|
||||
|
||||
auto handler =[](auto ec, auto...)
|
||||
{
|
||||
BOOST_TEST(!ec);
|
||||
};
|
||||
|
||||
net::io_context ioc;
|
||||
auto db = std::make_shared<connection>(ioc);
|
||||
db->async_exec(req0, adapt(), handler);
|
||||
db->async_exec(req1, adapt(), handler);
|
||||
db->async_exec(req2, adapt(), handler);
|
||||
db->async_exec(req2, adapt(), handler);
|
||||
db->async_exec(req1, adapt(), handler);
|
||||
db->async_exec(req2, adapt(), handler);
|
||||
db->async_exec(req1, adapt(), handler);
|
||||
db->async_exec(req2, adapt(), handler);
|
||||
db->async_exec(req2, adapt(), handler);
|
||||
db->async_exec(req1, adapt(), handler);
|
||||
db->async_exec(req2, adapt(), handler);
|
||||
db->async_exec(req3, adapt(), handler);
|
||||
|
||||
endpoint ep{"127.0.0.1", "6379"};
|
||||
db->async_run(ep, {}, [db](auto ec, auto...) {
|
||||
BOOST_CHECK_EQUAL(ec, net::error::misc_errors::eof);
|
||||
db->cancel(operation::receive_push);
|
||||
});
|
||||
|
||||
net::co_spawn(ioc.get_executor(), push_consumer3(db), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_push)
|
||||
{
|
||||
#ifdef BOOST_ASIO_HAS_CO_AWAIT
|
||||
test_push_is_received1(true);
|
||||
test_push_is_received2(true);
|
||||
test_push_many_subscribes(true);
|
||||
#endif
|
||||
test_missing_push_reader1(true);
|
||||
test_missing_push_reader2(false);
|
||||
test_missing_push_reader3(true);
|
||||
|
||||
#ifdef BOOST_ASIO_HAS_CO_AWAIT
|
||||
test_push_is_received1(true);
|
||||
test_push_is_received2(false);
|
||||
test_push_many_subscribes(false);
|
||||
#endif
|
||||
test_missing_push_reader1(true);
|
||||
test_missing_push_reader2(false);
|
||||
test_missing_push_reader3(false);
|
||||
}
|
||||
|
||||
@@ -1,77 +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 <iostream>
|
||||
#include <boost/asio.hpp>
|
||||
|
||||
#define BOOST_TEST_MODULE low level
|
||||
#include <boost/test/included/unit_test.hpp>
|
||||
|
||||
#include <aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
namespace net = boost::asio;
|
||||
|
||||
using aedis::resp3::request;
|
||||
using aedis::adapt;
|
||||
using connection = aedis::connection<>;
|
||||
using endpoint = aedis::endpoint;
|
||||
using error_code = boost::system::error_code;
|
||||
|
||||
#ifdef BOOST_ASIO_HAS_CO_AWAIT
|
||||
|
||||
net::awaitable<void> test_reconnect_impl(std::shared_ptr<connection> db)
|
||||
{
|
||||
request req;
|
||||
req.push("QUIT");
|
||||
|
||||
int i = 0;
|
||||
endpoint ep{"127.0.0.1", "6379"};
|
||||
for (; i < 5; ++i) {
|
||||
boost::system::error_code ec;
|
||||
co_await db->async_run(ep, req, adapt(), {}, net::redirect_error(net::use_awaitable, ec));
|
||||
db->reset_stream();
|
||||
BOOST_CHECK_EQUAL(ec, net::error::misc_errors::eof);
|
||||
}
|
||||
|
||||
BOOST_CHECK_EQUAL(i, 5);
|
||||
co_return;
|
||||
}
|
||||
|
||||
// Test whether the client works after a reconnect.
|
||||
BOOST_AUTO_TEST_CASE(test_reconnect)
|
||||
{
|
||||
std::cout << boost::unit_test::framework::current_test_case().p_name << std::endl;
|
||||
net::io_context ioc;
|
||||
auto db = std::make_shared<connection>(ioc);
|
||||
net::co_spawn(ioc, test_reconnect_impl(db), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_reconnect_timeout)
|
||||
{
|
||||
net::io_context ioc;
|
||||
auto db = std::make_shared<connection>(ioc);
|
||||
|
||||
request req1;
|
||||
req1.push("CLIENT", "PAUSE", 7000);
|
||||
|
||||
request req2;
|
||||
req2.push("QUIT");
|
||||
|
||||
endpoint ep{"127.0.0.1", "6379"};
|
||||
db->async_run(ep, req1, adapt(), {}, [db, &req2, &ep](auto ec, auto){
|
||||
BOOST_CHECK_EQUAL(ec, aedis::error::idle_timeout);
|
||||
db->reset_stream();
|
||||
db->async_run(ep, req2, adapt(), {}, [db](auto ec, auto){
|
||||
BOOST_CHECK_EQUAL(ec, aedis::error::exec_timeout);
|
||||
});
|
||||
});
|
||||
|
||||
ioc.run();
|
||||
}
|
||||
@@ -1,80 +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 <iostream>
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/asio/ssl.hpp>
|
||||
|
||||
#define BOOST_TEST_MODULE low level
|
||||
#include <boost/test/included/unit_test.hpp>
|
||||
|
||||
#include <aedis.hpp>
|
||||
#include <aedis/ssl/connection.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
namespace net = boost::asio;
|
||||
|
||||
using aedis::adapt;
|
||||
using connection = aedis::ssl::connection<net::ssl::stream<net::ip::tcp::socket>>;
|
||||
using endpoint = aedis::endpoint;
|
||||
|
||||
bool verify_certificate(bool, net::ssl::verify_context&)
|
||||
{
|
||||
std::cout << "set_verify_callback" << std::endl;
|
||||
return true;
|
||||
}
|
||||
|
||||
boost::system::error_code hello_fail(endpoint ep)
|
||||
{
|
||||
net::io_context ioc;
|
||||
|
||||
net::ssl::context ctx{net::ssl::context::sslv23};
|
||||
auto conn = std::make_shared<connection>(ioc.get_executor(), ctx);
|
||||
conn->next_layer().set_verify_mode(net::ssl::verify_peer);
|
||||
conn->next_layer().set_verify_callback(verify_certificate);
|
||||
boost::system::error_code ret;
|
||||
conn->async_run(ep, {}, [&](auto ec) {
|
||||
ret = ec;
|
||||
});
|
||||
|
||||
ioc.run();
|
||||
return ret;
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_tls_handshake_fail)
|
||||
{
|
||||
std::cout << boost::unit_test::framework::current_test_case().p_name << std::endl;
|
||||
|
||||
endpoint ep;
|
||||
ep.host = "google.com";
|
||||
ep.port = "80";
|
||||
auto const ec = hello_fail(ep);
|
||||
BOOST_TEST(!!ec);
|
||||
std::cout << "-----> " << ec.message() << std::endl;
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_tls_handshake_fail2)
|
||||
{
|
||||
std::cout << boost::unit_test::framework::current_test_case().p_name << std::endl;
|
||||
|
||||
endpoint ep;
|
||||
ep.host = "127.0.0.1";
|
||||
ep.port = "6379";
|
||||
auto const ec = hello_fail(ep);
|
||||
BOOST_CHECK_EQUAL(ec, aedis::error::ssl_handshake_timeout);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_hello_fail)
|
||||
{
|
||||
std::cout << boost::unit_test::framework::current_test_case().p_name << std::endl;
|
||||
|
||||
endpoint ep;
|
||||
ep.host = "google.com";
|
||||
ep.port = "443";
|
||||
auto const ec = hello_fail(ep);
|
||||
BOOST_CHECK_EQUAL(ec, aedis::error::invalid_data_type);
|
||||
}
|
||||
|
||||
@@ -24,6 +24,13 @@
|
||||
|
||||
// TODO: Test with empty strings.
|
||||
|
||||
|
||||
namespace std
|
||||
{
|
||||
auto operator==(aedis::ignore, aedis::ignore) noexcept {return true;}
|
||||
auto operator!=(aedis::ignore, aedis::ignore) noexcept {return false;}
|
||||
}
|
||||
|
||||
namespace net = boost::asio;
|
||||
namespace resp3 = aedis::resp3;
|
||||
|
||||
@@ -112,7 +119,8 @@ void test_async(net::any_io_executor ex, expect<Result> e)
|
||||
std::optional<int> op_int_ok = 11;
|
||||
std::optional<bool> op_bool_ok = true;
|
||||
|
||||
std::string const streamed_string_wire = "$?\r\n;4\r\nHell\r\n;5\r\no wor\r\n;1\r\nd\r\n;0\r\n";
|
||||
// TODO: Test a streamed string that is not finished with a string of
|
||||
// size 0 but other command comes in.
|
||||
std::vector<node_type> streamed_string_e1
|
||||
{ {aedis::resp3::type::streamed_string_part, 1, 1, "Hell"}
|
||||
, {aedis::resp3::type::streamed_string_part, 1, 1, "o wor"}
|
||||
@@ -122,39 +130,68 @@ std::vector<node_type> streamed_string_e1
|
||||
|
||||
std::vector<node_type> streamed_string_e2 { {resp3::type::streamed_string_part, 1UL, 1UL, {}} };
|
||||
|
||||
#define S01 "#11\r\n"
|
||||
#define S02 "#f\r\n"
|
||||
#define S03 "#t\r\n"
|
||||
#define S04 "$?\r\n;0\r\n"
|
||||
#define S05 "%11\r\n"
|
||||
#define S06 "$?\r\n;4\r\nHell\r\n;5\r\no wor\r\n;1\r\nd\r\n;0\r\n"
|
||||
#define S07 "$?\r\n;b\r\nHell\r\n;5\r\no wor\r\n;1\r\nd\r\n;0\r\n"
|
||||
#define S08 "*1\r\n:11\r\n"
|
||||
#define S09 ":-3\r\n"
|
||||
#define S10 ":11\r\n"
|
||||
#define S11 ":3\r\n"
|
||||
#define S12 "_\r\n"
|
||||
#define S13 ">4\r\n+pubsub\r\n+message\r\n+some-channel\r\n+some message\r\n"
|
||||
#define S14 ">0\r\n"
|
||||
#define S15 "*3\r\n$2\r\n11\r\n$2\r\n22\r\n$1\r\n3\r\n"
|
||||
#define S16 "%4\r\n$4\r\nkey1\r\n$6\r\nvalue1\r\n$4\r\nkey2\r\n$6\r\nvalue2\r\n$4\r\nkey3\r\n$6\r\nvalue3\r\n$4\r\nkey3\r\n$6\r\nvalue3\r\n"
|
||||
#define S17 "*1\r\n" S16
|
||||
#define S18 "|1\r\n+key-popularity\r\n%2\r\n$1\r\na\r\n,0.1923\r\n$1\r\nb\r\n,0.0012\r\n"
|
||||
#define S19 "|0\r\n"
|
||||
#define S20 "*3\r\n$2\r\n11\r\n$2\r\n22\r\n$1\r\n3\r\n"
|
||||
#define S21 "*1\r\n*1\r\n$2\r\nab\r\n"
|
||||
#define S22 "*1\r\n*1\r\n*1\r\n*1\r\n*1\r\n*1\r\na\r\n"
|
||||
#define S23 "*0\r\n"
|
||||
#define S24 "*3\r\n$2\r\n11\r\n$2\r\n22\r\n$1\r\n3\r\n"
|
||||
#define S25 "~6\r\n+orange\r\n+apple\r\n+one\r\n+two\r\n+three\r\n+orange\r\n"
|
||||
#define S26 "*1\r\n" S25
|
||||
#define S27 "~0\r\n"
|
||||
#define S28 "-Error\r\n"
|
||||
#define S29 "-\r\n"
|
||||
|
||||
#define NUMBER_TEST_CONDITIONS(test) \
|
||||
test(ex, make_expected("#11\r\n", std::optional<bool>{}, "bool.error", aedis::error::unexpected_bool_value)); \
|
||||
test(ex, make_expected("#f\r\n", bool{false}, "bool.bool (true)")); \
|
||||
test(ex, make_expected("#f\r\n", node_type{resp3::type::boolean, 1UL, 0UL, {"f"}}, "bool.node (false)")); \
|
||||
test(ex, make_expected("#t\r\n", bool{true}, "bool.bool (true)")); \
|
||||
test(ex, make_expected("#t\r\n", node_type{resp3::type::boolean, 1UL, 0UL, {"t"}}, "bool.node (true)")); \
|
||||
test(ex, make_expected("#t\r\n", op_bool_ok, "optional.int")); \
|
||||
test(ex, make_expected("#t\r\n", std::map<int, int>{}, "bool.error", aedis::error::expects_resp3_map)); \
|
||||
test(ex, make_expected("#t\r\n", std::set<int>{}, "bool.error", aedis::error::expects_resp3_set)); \
|
||||
test(ex, make_expected("#t\r\n", std::unordered_map<int, int>{}, "bool.error", aedis::error::expects_resp3_map)); \
|
||||
test(ex, make_expected("#t\r\n", std::unordered_set<int>{}, "bool.error", aedis::error::expects_resp3_set)); \
|
||||
test(ex, make_expected("$?\r\n;0\r\n", streamed_string_e2, "streamed_string.node.empty")); \
|
||||
test(ex, make_expected("%11\r\n", std::optional<int>{}, "number.optional.int.error", aedis::error::expects_resp3_simple_type));; \
|
||||
test(ex, make_expected("*1\r\n:11\r\n", std::tuple<int>{11}, "number.tuple.int")); \
|
||||
test(ex, make_expected(":-3\r\n", node_type{resp3::type::number, 1UL, 0UL, {"-3"}}, "number.node (negative)")); \
|
||||
test(ex, make_expected(":11\r\n", int{11}, "number.int")); \
|
||||
test(ex, make_expected(":11\r\n", op_int_ok, "number.optional.int")); \
|
||||
test(ex, make_expected(":11\r\n", std::list<std::string>{}, "number.optional.int", aedis::error::expects_resp3_aggregate)); \
|
||||
test(ex, make_expected(":11\r\n", std::map<std::string, std::string>{}, "number.optional.int", aedis::error::expects_resp3_map)); \
|
||||
test(ex, make_expected(":11\r\n", std::set<std::string>{}, "number.optional.int", aedis::error::expects_resp3_set)); \
|
||||
test(ex, make_expected(":11\r\n", std::unordered_map<std::string, std::string>{}, "number.optional.int", aedis::error::expects_resp3_map)); \
|
||||
test(ex, make_expected(":11\r\n", std::unordered_set<std::string>{}, "number.optional.int", aedis::error::expects_resp3_set)); \
|
||||
test(ex, make_expected(":3\r\n", node_type{resp3::type::number, 1UL, 0UL, {"3"}}, "number.node (positive)")); \
|
||||
test(ex, make_expected("_\r\n", int{0}, "number.int.error.null", aedis::error::resp3_null)); \
|
||||
test(ex, make_expected(streamed_string_wire, std::string{"Hello word"}, "streamed_string.string")); \
|
||||
test(ex, make_expected(streamed_string_wire, int{}, "streamed_string.string", aedis::error::not_a_number)); \
|
||||
test(ex, make_expected(streamed_string_wire, streamed_string_e1, "streamed_string.node")); \
|
||||
test(ex, make_expected(S01, std::optional<bool>{}, "bool.error", aedis::error::unexpected_bool_value)); \
|
||||
test(ex, make_expected(S02, bool{false}, "bool.bool (true)")); \
|
||||
test(ex, make_expected(S02, node_type{resp3::type::boolean, 1UL, 0UL, {"f"}}, "bool.node (false)")); \
|
||||
test(ex, make_expected(S03, bool{true}, "bool.bool (true)")); \
|
||||
test(ex, make_expected(S03, node_type{resp3::type::boolean, 1UL, 0UL, {"t"}}, "bool.node (true)")); \
|
||||
test(ex, make_expected(S03, op_bool_ok, "optional.int")); \
|
||||
test(ex, make_expected(S03, std::map<int, int>{}, "bool.error", aedis::error::expects_resp3_map)); \
|
||||
test(ex, make_expected(S03, std::set<int>{}, "bool.error", aedis::error::expects_resp3_set)); \
|
||||
test(ex, make_expected(S03, std::unordered_map<int, int>{}, "bool.error", aedis::error::expects_resp3_map)); \
|
||||
test(ex, make_expected(S03, std::unordered_set<int>{}, "bool.error", aedis::error::expects_resp3_set)); \
|
||||
test(ex, make_expected(S04, streamed_string_e2, "streamed_string.node.empty")); \
|
||||
test(ex, make_expected(S05, std::optional<int>{}, "number.optional.int.error", aedis::error::expects_resp3_simple_type));; \
|
||||
test(ex, make_expected(S06, int{}, "streamed_string.string", aedis::error::not_a_number)); \
|
||||
test(ex, make_expected(S06, std::string{"Hello word"}, "streamed_string.string")); \
|
||||
test(ex, make_expected(S06, streamed_string_e1, "streamed_string.node")); \
|
||||
test(ex, make_expected(S07, std::string{}, "streamed_string.error", aedis::error::not_a_number)); \
|
||||
test(ex, make_expected(S08, std::tuple<int>{11}, "number.tuple.int")); \
|
||||
test(ex, make_expected(S09, node_type{resp3::type::number, 1UL, 0UL, {"-3"}}, "number.node (negative)")); \
|
||||
test(ex, make_expected(S10, int{11}, "number.int")); \
|
||||
test(ex, make_expected(S10, op_int_ok, "number.optional.int")); \
|
||||
test(ex, make_expected(S10, std::list<std::string>{}, "number.optional.int", aedis::error::expects_resp3_aggregate)); \
|
||||
test(ex, make_expected(S10, std::map<std::string, std::string>{}, "number.optional.int", aedis::error::expects_resp3_map)); \
|
||||
test(ex, make_expected(S10, std::set<std::string>{}, "number.optional.int", aedis::error::expects_resp3_set)); \
|
||||
test(ex, make_expected(S10, std::unordered_map<std::string, std::string>{}, "number.optional.int", aedis::error::expects_resp3_map)); \
|
||||
test(ex, make_expected(S10, std::unordered_set<std::string>{}, "number.optional.int", aedis::error::expects_resp3_set)); \
|
||||
test(ex, make_expected(S11, node_type{resp3::type::number, 1UL, 0UL, {"3"}}, "number.node (positive)")); \
|
||||
test(ex, make_expected(S12, int{0}, "number.int.error.null", aedis::error::resp3_null)); \
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_push)
|
||||
{
|
||||
net::io_context ioc;
|
||||
std::string const wire = ">4\r\n+pubsub\r\n+message\r\n+some-channel\r\n+some message\r\n";
|
||||
|
||||
std::vector<node_type> e1a
|
||||
{ {resp3::type::push, 4UL, 0UL, {}}
|
||||
@@ -166,8 +203,8 @@ BOOST_AUTO_TEST_CASE(test_push)
|
||||
|
||||
std::vector<node_type> e1b { {resp3::type::push, 0UL, 0UL, {}} };
|
||||
|
||||
auto const in01 = expect<std::vector<node_type>>{wire, e1a, "push.node"};
|
||||
auto const in02 = expect<std::vector<node_type>>{">0\r\n", e1b, "push.node.empty"};
|
||||
auto const in01 = expect<std::vector<node_type>>{S13, e1a, "push.node"};
|
||||
auto const in02 = expect<std::vector<node_type>>{S14, e1b, "push.node.empty"};
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
|
||||
@@ -191,9 +228,6 @@ BOOST_AUTO_TEST_CASE(test_map)
|
||||
using op_vec_type = std::optional<std::vector<std::string>>;
|
||||
using tuple_type = std::tuple<std::string, std::string, std::string, std::string, std::string, std::string, std::string, std::string>;
|
||||
|
||||
std::string const wire2 = "*3\r\n$2\r\n11\r\n$2\r\n22\r\n$1\r\n3\r\n";
|
||||
std::string const wire = "%4\r\n$4\r\nkey1\r\n$6\r\nvalue1\r\n$4\r\nkey2\r\n$6\r\nvalue2\r\n$4\r\nkey3\r\n$6\r\nvalue3\r\n$4\r\nkey3\r\n$6\r\nvalue3\r\n";
|
||||
|
||||
std::vector<node_type> expected_1a
|
||||
{ {resp3::type::map, 4UL, 0UL, {}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"key1"}}
|
||||
@@ -252,20 +286,20 @@ BOOST_AUTO_TEST_CASE(test_map)
|
||||
, std::string{"key3"}, std::string{"value3"}
|
||||
};
|
||||
|
||||
auto const in00 = expect<std::vector<node_type>>{wire, expected_1a, "map.node"};
|
||||
auto const in00 = expect<std::vector<node_type>>{S16, expected_1a, "map.node"};
|
||||
auto const in01 = expect<map_type>{"%0\r\n", map_type{}, "map.map.empty"};
|
||||
auto const in02 = expect<map_type>{wire, expected_1b, "map.map"};
|
||||
auto const in03 = expect<mmap_type>{wire, e1k, "map.multimap"};
|
||||
auto const in04 = expect<umap_type>{wire, e1g, "map.unordered_map"};
|
||||
auto const in05 = expect<mumap_type>{wire, e1l, "map.unordered_multimap"};
|
||||
auto const in06 = expect<vec_type>{wire, expected_1c, "map.vector"};
|
||||
auto const in07 = expect<op_map_type>{wire, expected_1d, "map.optional.map"};
|
||||
auto const in08 = expect<op_vec_type>{wire, expected_1e, "map.optional.vector"};
|
||||
auto const in09 = expect<std::tuple<op_map_type>>{"*1\r\n" + wire, std::tuple<op_map_type>{expected_1d}, "map.transaction.optional.map"};
|
||||
auto const in02 = expect<map_type>{S16, expected_1b, "map.map"};
|
||||
auto const in03 = expect<mmap_type>{S16, e1k, "map.multimap"};
|
||||
auto const in04 = expect<umap_type>{S16, e1g, "map.unordered_map"};
|
||||
auto const in05 = expect<mumap_type>{S16, e1l, "map.unordered_multimap"};
|
||||
auto const in06 = expect<vec_type>{S16, expected_1c, "map.vector"};
|
||||
auto const in07 = expect<op_map_type>{S16, expected_1d, "map.optional.map"};
|
||||
auto const in08 = expect<op_vec_type>{S16, expected_1e, "map.optional.vector"};
|
||||
auto const in09 = expect<std::tuple<op_map_type>>{S17, std::tuple<op_map_type>{expected_1d}, "map.transaction.optional.map"};
|
||||
auto const in10 = expect<int>{"%11\r\n", int{}, "map.invalid.int", aedis::error::expects_resp3_simple_type};
|
||||
auto const in11 = expect<tuple_type>{wire, e1f, "map.tuple"};
|
||||
auto const in12 = expect<map_type>{wire2, map_type{}, "map.error", aedis::error::expects_resp3_map};
|
||||
auto const in13 = expect<map_type>{"_\r\n", map_type{}, "map.null", aedis::error::resp3_null};
|
||||
auto const in11 = expect<tuple_type>{S16, e1f, "map.tuple"};
|
||||
auto const in12 = expect<map_type>{S15, map_type{}, "map.error", aedis::error::expects_resp3_map};
|
||||
auto const in13 = expect<map_type>{S12, map_type{}, "map.null", aedis::error::resp3_null};
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
|
||||
@@ -303,8 +337,6 @@ BOOST_AUTO_TEST_CASE(test_map)
|
||||
|
||||
void test_attribute(net::io_context& ioc)
|
||||
{
|
||||
char const* wire = "|1\r\n+key-popularity\r\n%2\r\n$1\r\na\r\n,0.1923\r\n$1\r\nb\r\n,0.0012\r\n";
|
||||
|
||||
std::vector<node_type> e1a
|
||||
{ {resp3::type::attribute, 1UL, 0UL, {}}
|
||||
, {resp3::type::simple_string, 1UL, 1UL, "key-popularity"}
|
||||
@@ -317,8 +349,8 @@ void test_attribute(net::io_context& ioc)
|
||||
|
||||
std::vector<node_type> e1b;
|
||||
|
||||
auto const in01 = expect<std::vector<node_type>>{wire, e1a, "attribute.node"};
|
||||
auto const in02 = expect<std::vector<node_type>>{"|0\r\n", e1b, "attribute.node.empty"};
|
||||
auto const in01 = expect<std::vector<node_type>>{S18, e1a, "attribute.node"};
|
||||
auto const in02 = expect<std::vector<node_type>>{S19, e1b, "attribute.node.empty"};
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
|
||||
@@ -336,9 +368,6 @@ BOOST_AUTO_TEST_CASE(test_array)
|
||||
using array_type2 = std::array<int, 1>;
|
||||
|
||||
net::io_context ioc;
|
||||
char const* wire = "*3\r\n$2\r\n11\r\n$2\r\n22\r\n$1\r\n3\r\n";
|
||||
char const* wire_nested = "*1\r\n*1\r\n$2\r\nab\r\n";
|
||||
char const* wire_nested2 = "*1\r\n*1\r\n*1\r\n*1\r\n*1\r\n*1\r\na\r\n";
|
||||
|
||||
std::vector<node_type> e1a
|
||||
{ {resp3::type::array, 3UL, 0UL, {}}
|
||||
@@ -355,22 +384,22 @@ BOOST_AUTO_TEST_CASE(test_array)
|
||||
std::list<int> const e1g{11, 22, 3};
|
||||
std::deque<int> const e1h{11, 22, 3};
|
||||
|
||||
auto const in01 = expect<std::vector<node_type>>{wire, e1a, "array.node"};
|
||||
auto const in02 = expect<std::vector<int>>{wire, e1b, "array.int"};
|
||||
auto const in03 = expect<std::vector<node_type>>{"*0\r\n", e1e, "array.node.empty"};
|
||||
auto const in04 = expect<std::vector<std::string>>{"*0\r\n", e1d, "array.string.empty"};
|
||||
auto const in05 = expect<std::vector<std::string>>{wire, e1c, "array.string"};
|
||||
auto const in06 = expect<array_type>{wire, e1f, "array.array"};
|
||||
auto const in07 = expect<std::list<int>>{wire, e1g, "array.list"};
|
||||
auto const in08 = expect<std::deque<int>>{wire, e1h, "array.deque"};
|
||||
auto const in09 = expect<std::vector<int>>{"_\r\n", std::vector<int>{}, "array.vector", aedis::error::resp3_null};
|
||||
auto const in10 = expect<std::list<int>>{"_\r\n", std::list<int>{}, "array.list", aedis::error::resp3_null};
|
||||
auto const in11 = expect<array_type>{"_\r\n", array_type{}, "array.null", aedis::error::resp3_null};
|
||||
auto const in12 = expect<tuple_type>{wire, tuple_type{}, "array.list", aedis::error::incompatible_size};
|
||||
auto const in13 = expect<array_type2>{wire_nested, array_type2{}, "array.nested", aedis::error::nested_aggregate_not_supported};
|
||||
auto const in14 = expect<array_type2>{wire, array_type2{}, "array.null", aedis::error::incompatible_size};
|
||||
auto const in15 = expect<array_type2>{":3\r\n", array_type2{}, "array.array", aedis::error::expects_resp3_aggregate};
|
||||
auto const in16 = expect<vec_node_type>{wire_nested2, vec_node_type{}, "array.depth.exceeds", aedis::error::exceeeds_max_nested_depth};
|
||||
auto const in01 = expect<std::vector<node_type>>{S20, e1a, "array.node"};
|
||||
auto const in02 = expect<std::vector<int>>{S20, e1b, "array.int"};
|
||||
auto const in03 = expect<std::vector<node_type>>{S23, e1e, "array.node.empty"};
|
||||
auto const in04 = expect<std::vector<std::string>>{S23, e1d, "array.string.empty"};
|
||||
auto const in05 = expect<std::vector<std::string>>{S20, e1c, "array.string"};
|
||||
auto const in06 = expect<array_type>{S20, e1f, "array.array"};
|
||||
auto const in07 = expect<std::list<int>>{S20, e1g, "array.list"};
|
||||
auto const in08 = expect<std::deque<int>>{S20, e1h, "array.deque"};
|
||||
auto const in09 = expect<std::vector<int>>{S12, std::vector<int>{}, "array.vector", aedis::error::resp3_null};
|
||||
auto const in10 = expect<std::list<int>>{S12, std::list<int>{}, "array.list", aedis::error::resp3_null};
|
||||
auto const in11 = expect<array_type>{S12, array_type{}, "array.null", aedis::error::resp3_null};
|
||||
auto const in12 = expect<tuple_type>{S20, tuple_type{}, "array.list", aedis::error::incompatible_size};
|
||||
auto const in13 = expect<array_type2>{S21, array_type2{}, "array.nested", aedis::error::nested_aggregate_not_supported};
|
||||
auto const in14 = expect<array_type2>{S20, array_type2{}, "array.null", aedis::error::incompatible_size};
|
||||
auto const in15 = expect<array_type2>{S11, array_type2{}, "array.array", aedis::error::expects_resp3_aggregate};
|
||||
auto const in16 = expect<vec_node_type>{S22, vec_node_type{}, "array.depth.exceeds", aedis::error::exceeeds_max_nested_depth};
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
|
||||
@@ -421,9 +450,6 @@ BOOST_AUTO_TEST_CASE(test_set)
|
||||
using vec_type = std::vector<std::string>;
|
||||
using op_vec_type = std::optional<std::vector<std::string>>;
|
||||
|
||||
std::string const wire2 = "*3\r\n$2\r\n11\r\n$2\r\n22\r\n$1\r\n3\r\n";
|
||||
std::string const wire = "~6\r\n+orange\r\n+apple\r\n+one\r\n+two\r\n+three\r\n+orange\r\n";
|
||||
|
||||
std::vector<node_type> const expected1a
|
||||
{ {resp3::type::set, 6UL, 0UL, {}}
|
||||
, {resp3::type::simple_string, 1UL, 1UL, {"orange"}}
|
||||
@@ -441,16 +467,16 @@ BOOST_AUTO_TEST_CASE(test_set)
|
||||
op_vec_type expected_1e;
|
||||
expected_1e = e1d;
|
||||
|
||||
auto const in00 = expect<std::vector<node_type>>{wire, expected1a, "set.node"};
|
||||
auto const in01 = expect<std::vector<node_type>>{"~0\r\n", std::vector<node_type>{ {resp3::type::set, 0UL, 0UL, {}} }, "set.node (empty)"};
|
||||
auto const in02 = expect<set_type>{wire, set_type{"apple", "one", "orange", "three", "two"}, "set.set"};
|
||||
auto const in03 = expect<mset_type>{wire, e1f, "set.multiset"};
|
||||
auto const in04 = expect<vec_type>{wire, e1d, "set.vector "};
|
||||
auto const in05 = expect<op_vec_type>{wire, expected_1e, "set.vector "};
|
||||
auto const in06 = expect<uset_type>{wire, e1c, "set.unordered_set"};
|
||||
auto const in07 = expect<muset_type>{wire, e1g, "set.unordered_multiset"};
|
||||
auto const in08 = expect<std::tuple<uset_type>>{"*1\r\n" + wire, std::tuple<uset_type>{e1c}, "set.tuple"};
|
||||
auto const in09 = expect<set_type>{wire2, set_type{}, "set.error", aedis::error::expects_resp3_set};
|
||||
auto const in00 = expect<std::vector<node_type>>{S25, expected1a, "set.node"};
|
||||
auto const in01 = expect<std::vector<node_type>>{S27, std::vector<node_type>{ {resp3::type::set, 0UL, 0UL, {}} }, "set.node (empty)"};
|
||||
auto const in02 = expect<set_type>{S25, set_type{"apple", "one", "orange", "three", "two"}, "set.set"};
|
||||
auto const in03 = expect<mset_type>{S25, e1f, "set.multiset"};
|
||||
auto const in04 = expect<vec_type>{S25, e1d, "set.vector "};
|
||||
auto const in05 = expect<op_vec_type>{S25, expected_1e, "set.vector "};
|
||||
auto const in06 = expect<uset_type>{S25, e1c, "set.unordered_set"};
|
||||
auto const in07 = expect<muset_type>{S25, e1g, "set.unordered_multiset"};
|
||||
auto const in08 = expect<std::tuple<uset_type>>{S26, std::tuple<uset_type>{e1c}, "set.tuple"};
|
||||
auto const in09 = expect<set_type>{S24, set_type{}, "set.error", aedis::error::expects_resp3_set};
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
|
||||
@@ -481,16 +507,19 @@ BOOST_AUTO_TEST_CASE(test_set)
|
||||
BOOST_AUTO_TEST_CASE(test_simple_error)
|
||||
{
|
||||
net::io_context ioc;
|
||||
auto const in01 = expect<node_type>{"-Error\r\n", node_type{resp3::type::simple_error, 1UL, 0UL, {"Error"}}, "simple_error.node", aedis::error::resp3_simple_error};
|
||||
auto const in02 = expect<node_type>{"-\r\n", node_type{resp3::type::simple_error, 1UL, 0UL, {""}}, "simple_error.node.empty", aedis::error::resp3_simple_error};
|
||||
auto const in01 = expect<node_type>{S28, node_type{resp3::type::simple_error, 1UL, 0UL, {"Error"}}, "simple_error.node", aedis::error::resp3_simple_error};
|
||||
auto const in02 = expect<node_type>{S29, node_type{resp3::type::simple_error, 1UL, 0UL, {""}}, "simple_error.node.empty", aedis::error::resp3_simple_error};
|
||||
auto const in03 = expect<aedis::ignore>{S28, aedis::ignore{}, "simple_error.not.ignore.error", aedis::error::resp3_simple_error};
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
|
||||
test_sync(ex, in01);
|
||||
test_sync(ex, in02);
|
||||
test_sync(ex, in03);
|
||||
|
||||
test_async(ex, in01);
|
||||
test_async(ex, in02);
|
||||
test_async(ex, in03);
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
@@ -557,14 +586,17 @@ BOOST_AUTO_TEST_CASE(test_blob_error)
|
||||
net::io_context ioc;
|
||||
auto const in01 = expect<node_type>{"!21\r\nSYNTAX invalid syntax\r\n", node_type{resp3::type::blob_error, 1UL, 0UL, {"SYNTAX invalid syntax"}}, "blob_error", aedis::error::resp3_blob_error};
|
||||
auto const in02 = expect<node_type>{"!0\r\n\r\n", node_type{resp3::type::blob_error, 1UL, 0UL, {}}, "blob_error.empty", aedis::error::resp3_blob_error};
|
||||
auto const in03 = expect<aedis::ignore>{"!3\r\nfoo\r\n", aedis::ignore{}, "blob_error.ignore.adapter.error", aedis::error::resp3_blob_error};
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
|
||||
test_sync(ex, in01);
|
||||
test_sync(ex, in02);
|
||||
test_sync(ex, in03);
|
||||
|
||||
test_async(ex, in01);
|
||||
test_async(ex, in02);
|
||||
test_async(ex, in03);
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
@@ -679,15 +711,15 @@ BOOST_AUTO_TEST_CASE(test_null)
|
||||
using op_type_08 = std::optional<std::set<std::string>>;
|
||||
using op_type_09 = std::optional<std::unordered_set<std::string>>;
|
||||
|
||||
auto const in01 = expect<op_type_01>{"_\r\n", op_type_01{}, "null.optional.bool"};
|
||||
auto const in02 = expect<op_type_02>{"_\r\n", op_type_02{}, "null.optional.int"};
|
||||
auto const in03 = expect<op_type_03>{"_\r\n", op_type_03{}, "null.optional.string"};
|
||||
auto const in04 = expect<op_type_04>{"_\r\n", op_type_04{}, "null.optional.vector"};
|
||||
auto const in05 = expect<op_type_05>{"_\r\n", op_type_05{}, "null.optional.list"};
|
||||
auto const in06 = expect<op_type_06>{"_\r\n", op_type_06{}, "null.optional.map"};
|
||||
auto const in07 = expect<op_type_07>{"_\r\n", op_type_07{}, "null.optional.unordered_map"};
|
||||
auto const in08 = expect<op_type_08>{"_\r\n", op_type_08{}, "null.optional.set"};
|
||||
auto const in09 = expect<op_type_09>{"_\r\n", op_type_09{}, "null.optional.unordered_set"};
|
||||
auto const in01 = expect<op_type_01>{S12, op_type_01{}, "null.optional.bool"};
|
||||
auto const in02 = expect<op_type_02>{S12, op_type_02{}, "null.optional.int"};
|
||||
auto const in03 = expect<op_type_03>{S12, op_type_03{}, "null.optional.string"};
|
||||
auto const in04 = expect<op_type_04>{S12, op_type_04{}, "null.optional.vector"};
|
||||
auto const in05 = expect<op_type_05>{S12, op_type_05{}, "null.optional.list"};
|
||||
auto const in06 = expect<op_type_06>{S12, op_type_06{}, "null.optional.map"};
|
||||
auto const in07 = expect<op_type_07>{S12, op_type_07{}, "null.optional.unordered_map"};
|
||||
auto const in08 = expect<op_type_08>{S12, op_type_08{}, "null.optional.set"};
|
||||
auto const in09 = expect<op_type_09>{S12, op_type_09{}, "null.optional.unordered_set"};
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
|
||||
@@ -713,6 +745,45 @@ BOOST_AUTO_TEST_CASE(test_null)
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(ignore_adapter_simple_error)
|
||||
{
|
||||
net::io_context ioc;
|
||||
std::string rbuffer;
|
||||
|
||||
boost::system::error_code ec;
|
||||
|
||||
test_stream ts {ioc};
|
||||
ts.append("-Error\r\n");
|
||||
resp3::read(ts, net::dynamic_buffer(rbuffer), adapt2(), ec);
|
||||
BOOST_CHECK_EQUAL(ec, aedis::error::resp3_simple_error);
|
||||
BOOST_TEST(!rbuffer.empty());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(ignore_adapter_blob_error)
|
||||
{
|
||||
net::io_context ioc;
|
||||
std::string rbuffer;
|
||||
boost::system::error_code ec;
|
||||
|
||||
test_stream ts {ioc};
|
||||
ts.append("!21\r\nSYNTAX invalid syntax\r\n");
|
||||
resp3::read(ts, net::dynamic_buffer(rbuffer), adapt2(), ec);
|
||||
BOOST_CHECK_EQUAL(ec, aedis::error::resp3_blob_error);
|
||||
BOOST_TEST(!rbuffer.empty());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(ignore_adapter_no_error)
|
||||
{
|
||||
net::io_context ioc;
|
||||
std::string rbuffer;
|
||||
boost::system::error_code ec;
|
||||
test_stream ts {ioc};
|
||||
ts.append(":10\r\n");
|
||||
resp3::read(ts, net::dynamic_buffer(rbuffer), adapt2(), ec);
|
||||
BOOST_TEST(!ec);
|
||||
BOOST_TEST(rbuffer.empty());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(all_tests)
|
||||
{
|
||||
net::io_context ioc;
|
||||
@@ -747,11 +818,6 @@ void check_error(char const* name, aedis::error ev)
|
||||
|
||||
BOOST_AUTO_TEST_CASE(error)
|
||||
{
|
||||
check_error("aedis", aedis::error::resolve_timeout);
|
||||
check_error("aedis", aedis::error::resolve_timeout);
|
||||
check_error("aedis", aedis::error::connect_timeout);
|
||||
check_error("aedis", aedis::error::idle_timeout);
|
||||
check_error("aedis", aedis::error::exec_timeout);
|
||||
check_error("aedis", aedis::error::invalid_data_type);
|
||||
check_error("aedis", aedis::error::not_a_number);
|
||||
check_error("aedis", aedis::error::exceeeds_max_nested_depth);
|
||||
@@ -767,7 +833,7 @@ BOOST_AUTO_TEST_CASE(error)
|
||||
check_error("aedis", aedis::error::incompatible_size);
|
||||
check_error("aedis", aedis::error::not_a_double);
|
||||
check_error("aedis", aedis::error::resp3_null);
|
||||
check_error("aedis", aedis::error::unexpected_server_role);
|
||||
check_error("aedis", aedis::error::not_connected);
|
||||
}
|
||||
|
||||
std::string get_type_as_str(aedis::resp3::type t)
|
||||
@@ -824,3 +890,13 @@ BOOST_AUTO_TEST_CASE(type_convert)
|
||||
#undef CHECK_CASE
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(adapter)
|
||||
{
|
||||
using aedis::adapt;
|
||||
|
||||
std::string s;
|
||||
auto resp = std::tie(s, std::ignore);
|
||||
auto f = adapt(resp);
|
||||
(void)f;
|
||||
}
|
||||
|
||||
|
||||
57
tests/request.cpp
Normal file
57
tests/request.cpp
Normal file
@@ -0,0 +1,57 @@
|
||||
/* 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 <memory_resource>
|
||||
|
||||
#define BOOST_TEST_MODULE low level
|
||||
#include <boost/test/included/unit_test.hpp>
|
||||
|
||||
#include <aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
using aedis::resp3::request;
|
||||
|
||||
// TODO: Serialization.
|
||||
|
||||
BOOST_AUTO_TEST_CASE(single_arg_allocator)
|
||||
{
|
||||
char buf[4096];
|
||||
std::pmr::monotonic_buffer_resource resource{buf, 4096};
|
||||
request req1{{}, &resource};
|
||||
req1.push("PING");
|
||||
BOOST_CHECK_EQUAL(req1.payload(), std::pmr::string{"*1\r\n$4\r\nPING\r\n"});
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(arg_int)
|
||||
{
|
||||
request req;
|
||||
req.push("PING", 42);
|
||||
BOOST_CHECK_EQUAL(req.payload(), std::pmr::string{"*2\r\n$4\r\nPING\r\n$2\r\n42\r\n"});
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(multiple_args)
|
||||
{
|
||||
char const* res = "*5\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n$2\r\nEX\r\n$1\r\n2\r\n";
|
||||
request req;
|
||||
req.push("SET", "key", "value", "EX", "2");
|
||||
BOOST_CHECK_EQUAL(req.payload(), std::pmr::string{res});
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(container_and_range)
|
||||
{
|
||||
std::map<std::string, std::string> in{{"key1", "value1"}, {"key2", "value2"}};
|
||||
|
||||
char const* res = "*6\r\n$4\r\nHSET\r\n$3\r\nkey\r\n$4\r\nkey1\r\n$6\r\nvalue1\r\n$4\r\nkey2\r\n$6\r\nvalue2\r\n";
|
||||
|
||||
request req1;
|
||||
req1.push_range("HSET", "key", in);
|
||||
BOOST_CHECK_EQUAL(req1.payload(), std::pmr::string{res});
|
||||
|
||||
request req2;
|
||||
req2.push_range("HSET", "key", std::cbegin(in), std::cend(in));
|
||||
BOOST_CHECK_EQUAL(req2.payload(), std::pmr::string{res});
|
||||
}
|
||||
Reference in New Issue
Block a user