2
0
mirror of https://github.com/boostorg/redis.git synced 2026-01-26 19:02:08 +00:00

Compare commits

...

45 Commits

Author SHA1 Message Date
Marcelo Zimbres
75f91f3b11 v1.3.1 and build fixes. 2022-12-03 14:34:15 +01:00
Marcelo Zimbres
b9a23568e3 Many improvements in the examples. 2022-12-02 22:58:39 +01:00
Marcelo Zimbres
4ac2509afa Improvements in the docs and examples. 2022-11-27 21:59:02 +01:00
Marcelo Zimbres
e9dab97992 v1.3.0 2022-11-26 22:22:56 +01:00
Marcelo Zimbres
2e8cad858d Improvements in the examples. 2022-11-26 19:42:39 +01:00
Marcelo Zimbres
5a6e426028 Build fix and improvements in the examples. 2022-11-22 22:57:33 +01:00
Marcelo Zimbres
c55978a379 CI fix and improvements in the examples. 2022-11-21 23:41:41 +01:00
Marcelo Zimbres
6f51397e49 Build fix. 2022-11-20 14:06:07 +01:00
Marcelo Zimbres
6b9ba6b2d9 Adds connection typedef and improves docs. 2022-11-19 23:53:26 +01:00
Marcelo Zimbres
d29c03cb38 Changes:
* Uses pmr::string for the connection read and write buffer.
* Improvements in the examples.
2022-11-18 23:15:47 +01:00
Marcelo Zimbres
34cfbaa22f Removes healthy checks from the connection class. 2022-11-13 21:22:50 +01:00
Marcelo Zimbres
c9354fe320 Test improvements. 2022-11-13 18:39:28 +01:00
Marcelo Zimbres
bb555cb509 Remove built-in resolve and connect operation in async_run. 2022-11-13 00:10:26 +01:00
Marcelo Zimbres
5b209afa1d Removes endpoint class. 2022-11-09 23:05:52 +01:00
Marcelo Zimbres
3f5491654d Removes built-in HELLO from the connection. 2022-11-08 00:04:52 +01:00
Marcelo Zimbres
2bdc25752f Simplifications in the low-level tests. 2022-11-06 22:40:00 +01:00
Marcelo Zimbres
faafce1c64 Adds tls test. 2022-11-06 19:12:36 +01:00
Marcelo Zimbres
562075230f v1.2.0 2022-11-05 19:06:56 +01:00
Marcelo Zimbres
5dc677c6d8 Changes:
* Adds allocator support for the internal connection queue.
* Support for std::tie in aedis::adapt.
* Docs.
2022-11-05 12:07:22 +01:00
Marcelo Zimbres
395a167d48 Improvements in the coverage. 2022-11-01 14:13:33 +01:00
Marcelo Zimbres
f93f3cab58 Merge branch 'klemens-morgenstern-allocator-nonsense' 2022-10-31 22:20:54 +01:00
Marcelo Zimbres
df68fb0235 Changes:
- Ports from boost::container::pmr to std::pmr.
- Fixes clang-tidy issues.
- Adds resp3::request unit-tests.
2022-10-31 22:17:58 +01:00
Klemens Morgenstern
15e6883bc1 Added boost.container.pmr to request. 2022-10-31 07:37:03 +01:00
Marcelo Zimbres
3816d1d358 Documentation and stress test. 2022-10-30 19:48:04 +01:00
Marcelo
bb15c70723 Merge pull request #36 from klemens-morgenstern/sfinae
Added sfinae to push_range.
2022-10-30 19:47:16 +01:00
Klemens Morgenstern
297b7f15eb Added sfinae to push_range. 2022-10-30 23:11:35 +08:00
Marcelo Zimbres
ec6e99d99a Docs and example improvements. 2022-10-29 22:49:53 +02:00
Marcelo Zimbres
8dc6db069b Docs and examples. 2022-10-27 23:09:59 +02:00
Marcelo Zimbres
bac27c1770 Fixes cancellation. 2022-10-25 20:58:16 +02:00
Marcelo Zimbres
feaaedc6c0 Improvements in the cancellation support. 2022-10-23 22:32:58 +02:00
Marcelo Zimbres
000ebddf44 Fixes bug that caused unwritten request to be closed if write fails. 2022-10-22 22:34:10 +02:00
Marcelo Zimbres
268ea2c10f Improvements in the writer cancellation. 2022-10-22 21:23:33 +02:00
Marcelo Zimbres
d8b67f6e23 Improves async_exec cancellation support. 2022-10-22 20:43:37 +02:00
Marcelo Zimbres
ce1fa6a683 Implements per-op cancelation of async_exec. 2022-10-18 20:52:18 +02:00
Marcelo Zimbres
b8ede6ccb7 Fixes bug in conn.cancel(exec). 2022-10-16 22:44:44 +02:00
Marcelo Zimbres
6dce1a9226 Marks function inline. 2022-10-15 13:08:50 +02:00
Marcelo Zimbres
8566745d83 Changes the behaviour of adapt() with vector<node>. 2022-10-13 21:45:01 +02:00
Marcelo Zimbres
0b4906fcba Test improvements. 2022-10-13 20:51:35 +02:00
Marcelo Zimbres
2c8bb92071 Improvements in the docs. 2022-10-10 23:14:54 +02:00
Marcelo Zimbres
770e224917 Changes:
- CI fix.
- Renames request::fail_* to request::cancel_*.
- Adds a second parameter to async_run.
- Adds request::retry flag.
2022-10-09 22:45:42 +02:00
Marcelo Zimbres
4fb2b20954 Build fixes. 2022-10-08 22:24:20 +02:00
Marcelo Zimbres
c01a57b6cb Adds cancelation test. 2022-10-08 22:07:51 +02:00
Marcelo Zimbres
ea0b333c4d Removes the second async_run overload. 2022-10-08 17:07:28 +02:00
Marcelo Zimbres
ba82c6cd84 Progresses removing the second async_run overload. 2022-10-07 22:43:32 +02:00
Marcelo Zimbres
4c298ddc6b Adds doxygen output to the preset. 2022-10-03 14:32:36 +02:00
66 changed files with 3098 additions and 3080 deletions

View File

@@ -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:

View File

@@ -35,7 +35,7 @@ jobs:
uses: MarkusJx/install-boost@v2.3.0
id: install-boost
with:
boost_version: 1.79.0
boost_version: 1.80.0
platform_version: 22.04
- name: Run CMake
run: |

View File

@@ -30,7 +30,7 @@ jobs:
uses: MarkusJx/install-boost@v2.3.0
id: install-boost
with:
boost_version: 1.79.0
boost_version: 1.80.0
platform_version: 22.04
- name: Run CMake
run: |

View File

@@ -10,7 +10,7 @@ cmake_minimum_required(VERSION 3.14)
project(
Aedis
VERSION 1.1.0
VERSION 1.3.1
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
@@ -42,7 +43,7 @@ write_basic_package_version_file(
COMPATIBILITY AnyNewerVersion
)
find_package(Boost 1.79 REQUIRED)
find_package(Boost 1.80 REQUIRED)
include_directories(${Boost_INCLUDE_DIRS})
find_package(OpenSSL REQUIRED)
@@ -50,66 +51,114 @@ find_package(OpenSSL REQUIRED)
enable_testing()
include_directories(include)
# Main function for the examples.
#=======================================================================
add_library(common STATIC
examples/common/common.cpp
examples/common/main.cpp
examples/common/aedis.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)
target_link_libraries(intro common)
target_compile_features(intro PUBLIC cxx_std_20)
add_test(intro intro)
add_executable(intro_sync examples/intro_sync.cpp)
target_compile_features(intro_sync PUBLIC cxx_std_20)
add_test(intro_sync intro_sync)
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(low_level_async examples/low_level_async.cpp)
target_compile_features(low_level_async PUBLIC cxx_std_20)
add_test(low_level_async low_level_async)
target_link_libraries(low_level_async 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(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(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(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 +187,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(

View File

@@ -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/"
}
},
{

875
README.md

File diff suppressed because it is too large Load Diff

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -4,96 +4,68 @@
* 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/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 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);
}
// Called from the main function (see main.cpp)
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)

View File

@@ -0,0 +1,8 @@
/* 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.hpp>
#include <aedis/src.hpp>

View File

@@ -0,0 +1,75 @@
/* 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"
#include <boost/asio.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;
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");
}
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)

View 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

29
examples/common/main.cpp Normal file
View File

@@ -0,0 +1,29 @@
/* 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>
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
namespace net = boost::asio;
extern net::awaitable<void> async_main();
// The 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)

View File

@@ -4,60 +4,109 @@
* 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/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());
}
// Called from the main function (see main.cpp)
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)

View File

@@ -4,73 +4,60 @@
* 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/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)
// Listens for tcp connections.
auto listener(std::shared_ptr<connection> conn) -> net::awaitable<void>
{
auto ex = co_await net::this_coro::executor;
tcp_acceptor acc(ex, {net::ip::tcp::v4(), 55555});
for (;;)
net::co_spawn(ex, echo_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
// Called from the main function (see main.cpp)
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)

View File

@@ -4,40 +4,33 @@
* 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/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
// Called from the main function (see main.cpp)
auto async_main() -> net::awaitable<void>
{
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)

View File

@@ -16,16 +16,15 @@
namespace net = boost::asio;
using aedis::adapt;
using aedis::resp3::request;
using aedis::endpoint;
using connection = aedis::connection<>;
using connection = aedis::connection;
template <class Adapter>
auto exec(connection& conn, request const& req, Adapter adapter, boost::system::error_code& ec)
auto exec(std::shared_ptr<connection> conn, request const& req, Adapter adapter)
{
net::dispatch(
conn.get_executor(),
net::deferred([&]() { return conn.async_exec(req, adapter, net::deferred); }))
(net::redirect_error(net::use_future, ec)).get();
conn->get_executor(),
net::deferred([&]() { return conn->async_exec(req, adapter, net::deferred); }))
(net::use_future).get();
}
auto logger = [](auto const& ec)
@@ -36,23 +35,25 @@ int main()
try {
net::io_context ioc{1};
connection conn{ioc};
std::thread t{[&]() {
conn.async_run({"127.0.0.1", "6379"}, {}, logger);
auto conn = std::make_shared<connection>(ioc);
net::ip::tcp::resolver resv{ioc};
auto const res = resv.resolve("127.0.0.1", "6379");
net::connect(conn->next_layer(), res);
std::thread t{[conn, &ioc]() {
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");
boost::system::error_code ec;
std::tuple<std::string, aedis::ignore> resp;
exec(conn, req, adapt(resp), ec);
std::tuple<aedis::ignore, std::string, aedis::ignore> resp;
exec(conn, req, adapt(resp));
std::cout
<< "Exec: " << ec.message() << "\n"
<< "Response: " << std::get<0>(resp) << std::endl;
std::cout << "Response: " << std::get<1>(resp) << std::endl;
t.join();
} catch (std::exception const& e) {

View File

@@ -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)

View File

@@ -0,0 +1,49 @@
/* 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 <aedis.hpp>
#include <string>
#include <iostream>
namespace net = boost::asio;
namespace resp3 = aedis::resp3;
using resolver = net::use_awaitable_t<>::as_default_on_t<net::ip::tcp::resolver>;
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;
auto async_main() -> net::awaitable<void>
{
auto ex = co_await net::this_coro::executor;
resolver resv{ex};
auto const addrs = co_await resv.async_resolve("127.0.0.1", "6379");
tcp_socket socket{ex};
co_await net::async_connect(socket, addrs);
// Creates the request and writes to the socket.
request req;
req.push("HELLO", 3);
req.push("PING", "Hello world");
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;
}
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)

View File

@@ -31,7 +31,7 @@ int main()
// Creates the request and writes to the socket.
request req;
req.push("HELLO", 3);
req.push("PING");
req.push("PING", "Hello world");
req.push("QUIT");
resp3::write(socket, req);

View 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;
}

View File

@@ -0,0 +1,71 @@
/* 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/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 address {
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<address> const& endpoints) -> net::awaitable<address>
{
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 address{std::get<0>(addr).value().at(0), std::get<0>(addr).value().at(1)};
}
co_return address{};
}
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<address> 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)

View File

@@ -4,33 +4,49 @@
* 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 <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 "common/common.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 {
std::string name;
std::string age;
std::string country;
friend 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);
}
friend auto operator<<(std::ostream& os, user const& u) -> std::ostream&
{
os << "Name: " << u.name << "\n"
<< "Age: " << u.age << "\n"
<< "Country: " << u.country;
return os;
}
};
// Boost.Json serialization.
void tag_invoke(value_from_tag, value& jv, user const& u)
{
jv =
@@ -43,55 +59,38 @@ 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)
// Aedis serialization
void to_bulk(std::pmr::string& to, user const& u)
{
aedis::resp3::to_bulk(to, serialize(value_from(u)));
}
// Deserializes
void from_bulk(user& u, boost::string_view sv, boost::system::error_code&)
{
value jv = parse(sv);
u = value_to<user>(jv);
}
std::ostream& operator<<(std::ostream& os, user const& u)
net::awaitable<void> async_main()
{
os << "Name: " << u.name << "\n"
<< "Age: " << u.age << "\n"
<< "Country: " << u.country;
return os;
}
bool 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::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)

View File

@@ -4,27 +4,20 @@
* 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/common.hpp"
namespace net = boost::asio;
using namespace net::experimental::awaitable_operators;
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 +36,42 @@ 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 subscriber(std::shared_ptr<connection> conn) -> net::awaitable<void>
{
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"};
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);
signal_set_type sig{ex, SIGINT, SIGTERM};
timer_type timer{ex};
// 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)) && subscriber(conn));
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)

View File

@@ -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)

View File

@@ -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 `std::string`.
*
* @param t Tuple containing the responses.
* @param max_read_size Specifies the maximum size of the read

View File

@@ -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);
}
};

View File

@@ -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 {

View File

@@ -22,50 +22,46 @@ 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>;
};
/// Constructor
explicit connection(executor_type ex)
: base_type{ex}
using base_type = detail::connection_base<executor_type, basic_connection<AsyncReadWriteStream>>;
/// Contructs from an executor.
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())
/// Contructs from a context.
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 +83,13 @@ 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 Starts read and write operations
*
* This function performs the following steps
* This function starts read and write operations with the Redis
* server. More specifically it will trigger the write of all
* requests i.e. calls to `async_exec` that happened prior to this
* call.
*
* @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,57 +97,24 @@ 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.
*
* This function will send a request to the Redis server and
* complete when the response arrives. If the request contains
* only commands that don't expect a response, the completion
* occurs after it has been written to the underlying stream.
* Multiple concurrent calls to this function will be
* complete after the response has been processed. If the request
* contains only commands that don't expect a response, the
* completion occurs after it has been written to the underlying
* stream. Multiple concurrent calls to this function will be
* automatically queued by the implementation.
*
* @param req Request object.
@@ -234,27 +164,22 @@ 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.
* An unresponsive Redis server will also cause the idle-checks to
* 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 calls to * `async_receive`.
*
* @param op: The operation to be cancelled.
* @returns The number of operations that have been canceled.
@@ -263,41 +188,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

View File

@@ -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

View File

@@ -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>

View File

@@ -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 =

View File

@@ -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

View File

@@ -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,
};

View File

@@ -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

View File

@@ -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.";
}

View File

@@ -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

View File

@@ -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

View File

@@ -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_; }

View File

@@ -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;

View File

@@ -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

View File

@@ -26,22 +26,22 @@ 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.
* @relates node
/** \brief Converts the node to a string.
* \relates node
*
* @param in The node object.
* \param in The node object.
*/
template <class String>
auto to_string(node<String> const& in)

View File

@@ -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,69 @@ 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 a std::pmr::string for 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 true the request will complete with error 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,
* see https://redis.io/topics/pipelining. If false, this
* request will be sent individually.
/** \brief If true the request will be coalesced with other requests,
* see https://redis.io/topics/pipelining. Otherwise the
* request is sent individually.
*/
bool coalesce = true;
/** \brief If true, the request will complete with error 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 remains unresponded when
* `aedis::connection::async_run` completes. 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
* 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 +236,16 @@ public:
commands_ = 0;
}
/// Calls std::pmr::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 +273,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 +289,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 +298,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 +316,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 +329,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 +337,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 +354,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 +366,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 +382,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

View File

@@ -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,

View File

@@ -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>

View File

@@ -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

View File

@@ -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
View 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);
}

View File

@@ -0,0 +1,96 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#include <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
View 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
View 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
View 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

View File

@@ -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);
}

View File

@@ -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
View 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
View 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
View File

@@ -0,0 +1,72 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#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);
}

View File

@@ -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);
}

View File

@@ -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();
}

View File

@@ -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);
}

View File

@@ -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();
}

View File

@@ -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);
}

View File

@@ -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,25 @@ BOOST_AUTO_TEST_CASE(type_convert)
#undef CHECK_CASE
}
BOOST_AUTO_TEST_CASE(adapter)
{
using aedis::adapt;
using resp3::type;
boost::system::error_code ec;
std::string a;
int b;
auto resp = std::tie(a, b, std::ignore);
auto f = adapt(resp);
f(0, resp3::node<boost::string_view>{type::simple_string, 1, 0, "Hello"}, ec);
f(1, resp3::node<boost::string_view>{type::number, 1, 0, "42"}, ec);
BOOST_CHECK_EQUAL(a, "Hello");
BOOST_TEST(!ec);
BOOST_CHECK_EQUAL(b, 42);
BOOST_TEST(!ec);
}

57
tests/request.cpp Normal file
View 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});
}