2
0
mirror of https://github.com/boostorg/redis.git synced 2026-01-24 18:22:09 +00:00

Compare commits

...

33 Commits

Author SHA1 Message Date
Marcelo Zimbres
c88fcfb9ed Adds more doc to node class. 2023-01-08 21:51:41 +01:00
Marcelo
a56bf982ab Merge pull request #41 from Hailios/remove_duplicate_cmake
remove duplicate line in cmake
2023-01-07 20:11:50 +01:00
Jakob Lövhall
5d0ed0e986 remove duplicate line in cmake 2023-01-07 16:58:50 +01:00
Marcelo Zimbres
15deaa637d Doc improvements. 2023-01-07 00:14:29 +01:00
Marcelo Zimbres
bb8ff90351 Fixes issue 39. 2023-01-06 17:38:10 +01:00
Marcelo Zimbres
7d4902369a Doc improvements and replaces async_main to co_main. 2023-01-05 23:37:55 +01:00
Marcelo Zimbres
607ca17a89 Improvements in the documentation. 2023-01-04 22:51:53 +01:00
Marcelo Zimbres
3849ba42fd Changes:
- Fix include header order.
- Removes default completion token where it is not needed.
- Replaces yield with BOOST_ macros.
2023-01-02 23:51:50 +01:00
Marcelo Zimbres
56bcdb7914 Improvements in the docs. 2022-12-31 15:58:31 +01:00
Marcelo Zimbres
73ad66eb93 Adds example that does not user awaitable ops. 2022-12-30 18:13:09 +01:00
Marcelo Zimbres
9cf00d6a23 Adds cpp17 async example. 2022-12-30 00:04:41 +01:00
Marcelo Zimbres
a00c9e7439 Doc improvements. 2022-12-27 21:21:43 +01:00
Marcelo Zimbres
0520791100 Renames request flag. 2022-12-27 18:46:27 +01:00
Marcelo Zimbres
14b376e36e Fixes cancelation of async_exec (2). 2022-12-26 11:02:13 +01:00
Marcelo Zimbres
4f9dcc7dc5 Fixes async_exec terminal cancellation. 2022-12-25 20:01:35 +01:00
Marcelo Zimbres
ad5dd8c30b Refactors the parser so it is not header-only. 2022-12-22 21:42:41 +01:00
Marcelo Zimbres
842f864689 Using doxygen-awesome css. 2022-12-19 21:40:44 +01:00
Marcelo Zimbres
63f9b74502 Improves the stress test. 2022-12-18 09:20:29 +01:00
Marcelo Zimbres
801f60a026 Readme improvements. 2022-12-17 22:59:44 +01:00
Marcelo Zimbres
c37fcb641c Documentation improvements. 2022-12-17 16:59:06 +01:00
Marcelo Zimbres
48c3f37168 Test improvements and bugfix in send-retry. 2022-12-11 22:19:37 +01:00
Marcelo Zimbres
3c63911802 Removes some boost dependencies. 2022-12-10 19:42:51 +01:00
Marcelo Zimbres
1645881a44 Doc improvements and add guarded_op class. 2022-12-07 22:32:49 +01:00
Marcelo Zimbres
730e06c38d Adds bigobj and other vs flags. 2022-12-04 20:36:17 +01:00
Marcelo Zimbres
cf3a79737d Removes mingw from windows builds. 2022-12-04 16:15:12 +01:00
Marcelo Zimbres
edb384c843 Build fix on windows. 2022-12-04 15:13:14 +01:00
Marcelo Zimbres
f745faddf8 Replaces boost::string_view with std::string_view. 2022-12-04 13:53:44 +01:00
Marcelo Zimbres
927117568e Build fix. 2022-12-04 13:53:44 +01:00
Marcelo Zimbres
1e7c176f92 Removes dependency on Boost.Hana. 2022-12-03 22:29:04 +01:00
Marcelo Zimbres
449b5f7e7c First steps with windows CI. 2022-12-03 18:14:10 +01:00
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
64 changed files with 5014 additions and 2217 deletions

View File

@@ -3,6 +3,92 @@ name: CI
on: [push, pull_request]
jobs:
windows:
name: "${{matrix.generator}} ${{matrix.toolset}} Boost ${{matrix.boost_version}} ${{matrix.build_type}} ${{matrix.name_args}}"
runs-on: ${{matrix.os}}
defaults:
run:
shell: bash
strategy:
fail-fast: false
matrix:
boost_version: ["1.80.0"]
os: [windows-2019, windows-2022]
toolset: [v142, v143]
build_type: [Release]
generator: ["Visual Studio 16 2019", "Visual Studio 17 2022"]
config_args: [""]
build_args: [""]
name_args: [""]
exclude:
- { os: windows-2019, toolset: v143 }
- { os: windows-2019, generator: "Visual Studio 17 2022" }
- { os: windows-2022, generator: "Visual Studio 16 2019" }
# The following combinations are not available through install-boost
- { boost_version: "1.80.0", toolset: v143 }
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Add boost toolset to environment
if: contains(fromJson('["1.80.0"]'), matrix.boost_version)
run: echo BOOST_TOOLSET=$(echo "msvc") >> $GITHUB_ENV
# The platform_version passed to boost-install determines the msvc toolset version for which static libs are installed.
- name: Add boost platform version to environment
run: |
declare -A toolset_to_platform_version=( [v142]="2019" [v143]="2022" )
key=$(echo "${{matrix.toolset}}")
echo BOOST_PLATFORM_VERSION="${toolset_to_platform_version[$key]}" >> $GITHUB_ENV
- name: Add boost install path to environment
run: echo BOOST_INSTALL_PATH="${GITHUB_WORKSPACE}/boost-${{matrix.boost_version}}${BOOST_TOOLSET}${BOOST_PLATFORM_VERSION}" >> $GITHUB_ENV
- name: Add build type configuration to environment
run: echo BUILD_CONFIG_ARG="--config ${{matrix.build_type}}" >> $GITHUB_ENV
- name: Cache Boost installation
id: cache-boost
uses: actions/cache@v3
with:
path: ${{env.BOOST_INSTALL_PATH}}
key: ${{matrix.boost_version}}${{env.BOOST_TOOLSET}}${{env.BOOST_PLATFORM_VERSION}}
- name: Install Boost
if: steps.cache-boost.outputs.cache-hit != 'true'
uses: MarkusJx/install-boost@v2.4.1
with:
boost_version: ${{matrix.boost_version}}
toolset: ${{env.BOOST_TOOLSET}}
boost_install_dir: ${{env.BOOST_INSTALL_PATH}}
platform_version: ${{env.BOOST_PLATFORM_VERSION}}
arch: null
- name: Install packages
run: cinst openssl
- name: Create build directory
run: mkdir build
- name: Configure
working-directory: build
run: |
cmake -T "${{matrix.toolset}}" \
-G "${{matrix.generator}}" \
${{matrix.config_args}} \
${BOOST_COMPILER_ARG}\
"${GITHUB_WORKSPACE}"
env:
BOOST_ROOT: ${{env.BOOST_INSTALL_PATH}}/boost
- name: Build
working-directory: build
run: |
cmake --build . ${BUILD_CONFIG_ARG} ${{matrix.build_args}}
posix:
defaults:
run:
@@ -12,7 +98,6 @@ jobs:
fail-fast: false
matrix:
include:
- { toolset: gcc, compiler: g++-10, install: g++-10, os: ubuntu-22.04, cxxstd: 'c++17' }
- { toolset: gcc, compiler: g++-11, install: g++-11, os: ubuntu-22.04, cxxstd: 'c++17' }
- { toolset: gcc, compiler: g++-11, install: g++-11, os: ubuntu-22.04, cxxstd: 'c++20' }
- { toolset: clang, compiler: clang++-11, install: clang-11, os: ubuntu-22.04, cxxstd: 'c++17' }
@@ -35,7 +120,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,8 +10,8 @@ cmake_minimum_required(VERSION 3.14)
project(
Aedis
VERSION 1.3.0
DESCRIPTION "A redis client designed for performance and scalability"
VERSION 1.4.1
DESCRIPTION "A redis client library"
HOMEPAGE_URL "https://mzimbres.github.io/aedis"
LANGUAGES CXX
)
@@ -22,28 +22,33 @@ target_include_directories(aedis INTERFACE
$<INSTALL_INTERFACE:include>
)
target_link_libraries(aedis
target_link_libraries(
aedis
INTERFACE
Boost::asio
Boost::assert
Boost::config
Boost::core
Boost::mp11
Boost::optional
Boost::system
Boost::utility
Boost::winapi
)
target_compile_features(aedis INTERFACE cxx_std_17)
# Asio bases C++ feature detection on __cplusplus. Make MSVC
# define it correctly
if (MSVC)
target_compile_options(aedis INTERFACE /Zc:__cplusplus)
endif()
include(CMakePackageConfigHelpers)
write_basic_package_version_file(
"${PROJECT_BINARY_DIR}/AedisConfigVersion.cmake"
COMPATIBILITY AnyNewerVersion
)
find_package(Boost 1.79 REQUIRED)
find_package(Boost 1.80 REQUIRED)
include_directories(${Boost_INCLUDE_DIRS})
find_package(OpenSSL REQUIRED)
@@ -54,103 +59,245 @@ include_directories(include)
# Main function for the examples.
#=======================================================================
add_library(common STATIC examples/common.cpp)
add_library(common STATIC
examples/common/common.cpp
examples/common/main.cpp
examples/common/aedis.cpp
)
target_compile_features(common PUBLIC cxx_std_20)
if (MSVC)
target_compile_options(common PRIVATE /bigobj)
target_compile_definitions(common PRIVATE _WIN32_WINNT=0x0601)
endif()
# Executables
#=======================================================================
#add_executable(intro_sync examples/intro_sync.cpp) // Uncomment after update to Boost 1.80
add_executable(cpp20_intro examples/cpp20_intro.cpp)
target_link_libraries(cpp20_intro common)
target_compile_features(cpp20_intro PUBLIC cxx_std_20)
add_test(cpp20_intro cpp20_intro)
if (MSVC)
target_compile_options(cpp20_intro PRIVATE /bigobj)
target_compile_definitions(cpp20_intro PRIVATE _WIN32_WINNT=0x0601)
endif()
add_executable(intro examples/intro.cpp)
target_link_libraries(intro common)
target_compile_features(intro PUBLIC cxx_std_20)
add_test(intro intro)
add_executable(cpp20_intro_awaitable_ops examples/cpp20_intro_awaitable_ops.cpp)
target_link_libraries(cpp20_intro_awaitable_ops common)
target_compile_features(cpp20_intro_awaitable_ops PUBLIC cxx_std_20)
add_test(cpp20_intro_awaitable_ops cpp20_intro_awaitable_ops)
if (MSVC)
target_compile_options(cpp20_intro_awaitable_ops PRIVATE /bigobj)
target_compile_definitions(cpp20_intro_awaitable_ops PRIVATE _WIN32_WINNT=0x0601)
endif()
add_executable(chat_room examples/chat_room.cpp)
target_compile_features(chat_room PUBLIC cxx_std_20)
target_link_libraries(chat_room common)
add_executable(cpp17_intro examples/cpp17_intro.cpp)
target_compile_features(cpp17_intro PUBLIC cxx_std_17)
add_test(cpp17_intro cpp17_intro)
if (MSVC)
target_compile_options(cpp17_intro PRIVATE /bigobj)
target_compile_definitions(cpp17_intro PRIVATE _WIN32_WINNT=0x0601)
endif()
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(cpp17_intro_sync examples/cpp17_intro_sync.cpp)
target_compile_features(cpp17_intro_sync PUBLIC cxx_std_17)
add_test(cpp17_intro_sync cpp17_intro_sync)
if (MSVC)
target_compile_options(cpp17_intro_sync PRIVATE /bigobj)
target_compile_definitions(cpp17_intro_sync PRIVATE _WIN32_WINNT=0x0601)
endif()
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(cpp20_chat_room examples/cpp20_chat_room.cpp)
target_compile_features(cpp20_chat_room PUBLIC cxx_std_20)
target_link_libraries(cpp20_chat_room common)
if (MSVC)
target_compile_options(cpp20_chat_room PRIVATE /bigobj)
target_compile_definitions(cpp20_chat_room PRIVATE _WIN32_WINNT=0x0601)
endif()
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(cpp20_containers examples/cpp20_containers.cpp)
target_compile_features(cpp20_containers PUBLIC cxx_std_20)
target_link_libraries(cpp20_containers common)
add_test(cpp20_containers cpp20_containers)
if (MSVC)
target_compile_options(cpp20_containers PRIVATE /bigobj)
target_compile_definitions(cpp20_containers PRIVATE _WIN32_WINNT=0x0601)
endif()
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(cpp20_echo_server examples/cpp20_echo_server.cpp)
target_compile_features(cpp20_echo_server PUBLIC cxx_std_20)
target_link_libraries(cpp20_echo_server common)
if (MSVC)
target_compile_options(cpp20_echo_server PRIVATE /bigobj)
target_compile_definitions(cpp20_echo_server PRIVATE _WIN32_WINNT=0x0601)
endif()
add_executable(subscriber examples/subscriber.cpp)
target_compile_features(subscriber PUBLIC cxx_std_20)
target_link_libraries(subscriber common)
add_executable(cpp20_resolve_with_sentinel examples/cpp20_resolve_with_sentinel.cpp)
target_compile_features(cpp20_resolve_with_sentinel PUBLIC cxx_std_20)
target_link_libraries(cpp20_resolve_with_sentinel common)
#add_test(cpp20_resolve_with_sentinel cpp20_resolve_with_sentinel)
if (MSVC)
target_compile_options(cpp20_resolve_with_sentinel PRIVATE /bigobj)
target_compile_definitions(cpp20_resolve_with_sentinel PRIVATE _WIN32_WINNT=0x0601)
endif()
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(intro_tls common)
add_executable(cpp20_serialization examples/cpp20_serialization.cpp)
target_compile_features(cpp20_serialization PUBLIC cxx_std_20)
target_link_libraries(cpp20_serialization common)
add_test(cpp20_serialization cpp20_serialization)
if (MSVC)
target_compile_options(cpp20_serialization PRIVATE /bigobj)
target_compile_definitions(cpp20_serialization PRIVATE _WIN32_WINNT=0x0601)
endif()
add_executable(cpp20_subscriber examples/cpp20_subscriber.cpp)
target_compile_features(cpp20_subscriber PUBLIC cxx_std_20)
target_link_libraries(cpp20_subscriber common)
if (MSVC)
target_compile_options(cpp20_subscriber PRIVATE /bigobj)
target_compile_definitions(cpp20_subscriber PRIVATE _WIN32_WINNT=0x0601)
endif()
add_executable(cpp20_intro_tls examples/cpp20_intro_tls.cpp)
target_compile_features(cpp20_intro_tls PUBLIC cxx_std_20)
add_test(cpp20_intro_tls cpp20_intro_tls)
target_link_libraries(cpp20_intro_tls OpenSSL::Crypto OpenSSL::SSL)
target_link_libraries(cpp20_intro_tls common)
if (MSVC)
target_compile_options(cpp20_intro_tls PRIVATE /bigobj)
target_compile_definitions(cpp20_intro_tls PRIVATE _WIN32_WINNT=0x0601)
endif()
add_executable(cpp20_low_level_async examples/cpp20_low_level_async.cpp)
target_compile_features(cpp20_low_level_async PUBLIC cxx_std_20)
add_test(cpp20_low_level_async cpp20_low_level_async)
target_link_libraries(cpp20_low_level_async common)
if (MSVC)
target_compile_options(cpp20_low_level_async PRIVATE /bigobj)
target_compile_definitions(cpp20_low_level_async PRIVATE _WIN32_WINNT=0x0601)
endif()
add_executable(echo_server_client benchmarks/cpp/asio/echo_server_client.cpp)
add_executable(echo_server_direct benchmarks/cpp/asio/echo_server_direct.cpp)
add_executable(low_level_sync examples/low_level_sync.cpp)
add_executable(low_level_async examples/low_level_async.cpp)
add_executable(test_conn_exec tests/conn_exec.cpp)
add_executable(test_conn_push tests/conn_push.cpp)
add_executable(test_conn_quit tests/conn_quit.cpp)
add_executable(test_conn_quit_coalesce tests/conn_quit_coalesce.cpp)
add_executable(test_conn_reconnect tests/conn_reconnect.cpp)
add_executable(test_conn_tls tests/conn_tls.cpp)
add_executable(test_low_level tests/low_level.cpp)
add_executable(test_conn_run_cancel tests/conn_run_cancel.cpp)
add_executable(test_conn_exec_cancel tests/conn_exec_cancel.cpp)
add_executable(test_conn_echo_stress tests/conn_echo_stress.cpp)
add_executable(test_request tests/request.cpp)
target_compile_features(echo_server_client PUBLIC cxx_std_20)
if (MSVC)
target_compile_options(echo_server_client PRIVATE /bigobj)
target_compile_definitions(echo_server_client PRIVATE _WIN32_WINNT=0x0601)
endif()
add_executable(echo_server_direct benchmarks/cpp/asio/echo_server_direct.cpp)
target_compile_features(echo_server_direct PUBLIC cxx_std_20)
target_compile_features(low_level_sync PUBLIC cxx_std_17)
target_compile_features(low_level_async PUBLIC cxx_std_20)
if (MSVC)
target_compile_options(echo_server_direct PRIVATE /bigobj)
target_compile_definitions(echo_server_direct PRIVATE _WIN32_WINNT=0x0601)
endif()
add_executable(cpp17_low_level_sync examples/cpp17_low_level_sync.cpp)
target_compile_features(cpp17_low_level_sync PUBLIC cxx_std_17)
add_test(cpp17_low_level_sync cpp17_low_level_sync)
if (MSVC)
target_compile_options(cpp17_low_level_sync PRIVATE /bigobj)
target_compile_definitions(cpp17_low_level_sync PRIVATE _WIN32_WINNT=0x0601)
endif()
add_executable(test_conn_exec tests/conn_exec.cpp)
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(intro_sync intro_sync)
add_test(low_level_sync low_level_sync)
add_test(low_level_async low_level_async)
add_test(test_low_level test_low_level)
add_test(test_conn_exec test_conn_exec)
if (MSVC)
target_compile_options(test_conn_exec PRIVATE /bigobj)
target_compile_definitions(test_conn_exec PRIVATE _WIN32_WINNT=0x0601)
endif()
add_executable(test_conn_exec_retry tests/conn_exec_retry.cpp)
target_compile_features(test_conn_exec_retry PUBLIC cxx_std_20)
add_test(test_conn_exec_retry test_conn_exec_retry)
if (MSVC)
target_compile_options(test_conn_exec_retry PRIVATE /bigobj)
target_compile_definitions(test_conn_exec_retry PRIVATE _WIN32_WINNT=0x0601)
endif()
add_executable(test_conn_push tests/conn_push.cpp)
target_compile_features(test_conn_push PUBLIC cxx_std_20)
add_test(test_conn_push test_conn_push)
if (MSVC)
target_compile_options(test_conn_push PRIVATE /bigobj)
target_compile_definitions(test_conn_push PRIVATE _WIN32_WINNT=0x0601)
endif()
add_executable(test_conn_quit tests/conn_quit.cpp)
target_compile_features(test_conn_quit PUBLIC cxx_std_17)
add_test(test_conn_quit test_conn_quit)
if (MSVC)
target_compile_options(test_conn_quit PRIVATE /bigobj)
target_compile_definitions(test_conn_quit PRIVATE _WIN32_WINNT=0x0601)
endif()
add_executable(test_conn_quit_coalesce tests/conn_quit_coalesce.cpp)
add_test(test_conn_quit_coalesce test_conn_quit_coalesce)
target_compile_features(test_conn_quit_coalesce PUBLIC cxx_std_17)
if (MSVC)
target_compile_options(test_conn_quit_coalesce PRIVATE /bigobj)
target_compile_definitions(test_conn_quit_coalesce PRIVATE _WIN32_WINNT=0x0601)
endif()
add_executable(test_conn_reconnect tests/conn_reconnect.cpp)
target_compile_features(test_conn_reconnect PUBLIC cxx_std_20)
target_link_libraries(test_conn_reconnect common)
add_test(test_conn_reconnect test_conn_reconnect)
if (MSVC)
target_compile_options(test_conn_reconnect PRIVATE /bigobj)
target_compile_definitions(test_conn_reconnect PRIVATE _WIN32_WINNT=0x0601)
endif()
add_executable(test_conn_tls tests/conn_tls.cpp)
add_test(test_conn_tls test_conn_tls)
target_compile_features(test_conn_tls PUBLIC cxx_std_17)
target_link_libraries(test_conn_tls OpenSSL::Crypto OpenSSL::SSL)
if (MSVC)
target_compile_options(test_conn_tls PRIVATE /bigobj)
target_compile_definitions(test_conn_tls PRIVATE _WIN32_WINNT=0x0601)
endif()
add_executable(test_low_level tests/low_level.cpp)
target_compile_features(test_low_level PUBLIC cxx_std_17)
add_test(test_low_level test_low_level)
if (MSVC)
target_compile_options(test_low_level PRIVATE /bigobj)
target_compile_definitions(test_low_level PRIVATE _WIN32_WINNT=0x0601)
endif()
add_executable(test_conn_run_cancel tests/conn_run_cancel.cpp)
target_compile_features(test_conn_run_cancel PUBLIC cxx_std_20)
add_test(test_conn_run_cancel test_conn_run_cancel)
if (MSVC)
target_compile_options(test_conn_run_cancel PRIVATE /bigobj)
target_compile_definitions(test_conn_run_cancel PRIVATE _WIN32_WINNT=0x0601)
endif()
add_executable(test_conn_exec_cancel tests/conn_exec_cancel.cpp)
target_compile_features(test_conn_exec_cancel PUBLIC cxx_std_20)
target_link_libraries(test_conn_exec_cancel common)
add_test(test_conn_exec_cancel test_conn_exec_cancel)
if (MSVC)
target_compile_options(test_conn_exec_cancel PRIVATE /bigobj)
target_compile_definitions(test_conn_exec_cancel PRIVATE _WIN32_WINNT=0x0601)
endif()
add_executable(test_conn_echo_stress tests/conn_echo_stress.cpp)
target_compile_features(test_conn_echo_stress PUBLIC cxx_std_20)
target_link_libraries(test_conn_echo_stress common)
add_test(test_conn_echo_stress test_conn_echo_stress)
if (MSVC)
target_compile_options(test_conn_echo_stress PRIVATE /bigobj)
target_compile_definitions(test_conn_echo_stress PRIVATE _WIN32_WINNT=0x0601)
endif()
add_executable(test_request tests/request.cpp)
target_compile_features(test_request PUBLIC cxx_std_17)
add_test(test_request test_request)
if (MSVC)
target_compile_options(test_request PRIVATE /bigobj)
target_compile_definitions(test_request PRIVATE _WIN32_WINNT=0x0601)
endif()
# Install
#=======================================================================

View File

@@ -49,6 +49,7 @@
"CMAKE_BUILD_TYPE": "Debug",
"CMAKE_CXX_EXTENSIONS": "OFF",
"CMAKE_CXX_FLAGS": "-Wall -Wextra -fsanitize=address",
"CMAKE_CXX_COMPILER": "g++-11",
"CMAKE_SHARED_LINKER_FLAGS": "-fsanitize=address",
"CMAKE_CXX_STANDARD_REQUIRED": "ON",
"PROJECT_BINARY_DIR": "${sourceDir}/build/dev",

788
README.md

File diff suppressed because it is too large Load Diff

View File

@@ -14,8 +14,7 @@ using net::ip::tcp;
using tcp_socket = net::use_awaitable_t<>::as_default_on_t<net::ip::tcp::socket>;
using timer_type = net::use_awaitable_t<>::as_default_on_t<net::steady_timer>;
net::awaitable<void>
example(boost::asio::ip::tcp::endpoint ep, std::string msg, int n)
auto example(boost::asio::ip::tcp::endpoint ep, std::string msg, int n) -> net::awaitable<void>
{
try {
auto ex = co_await net::this_coro::executor;

View File

@@ -33,7 +33,7 @@ awaitable_type echo(tcp_socket socket)
std::size_t n = co_await socket.async_read_some(net::buffer(data), use_awaitable);
co_await async_write(socket, net::buffer(data, n), use_awaitable);
}
} catch (std::exception const& e) {
} catch (std::exception const&) {
//std::printf("echo Exception: %s\n", e.what());
}
}

View File

@@ -1250,7 +1250,7 @@ HTML_FILE_EXTENSION = .html
# of the possible markers and block names see the documentation.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_HEADER = doc/htmlheader.html
HTML_HEADER =
# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each
# generated HTML page. If the tag is left blank doxygen will generate a standard
@@ -1260,7 +1260,7 @@ HTML_HEADER = doc/htmlheader.html
# that doxygen normally uses.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_FOOTER = doc/htmlfooter.html
HTML_FOOTER =
# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style
# sheet that is used by each HTML page. It can be used to fine-tune the look of
@@ -1285,7 +1285,7 @@ HTML_STYLESHEET =
# list). For an example see the documentation.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_EXTRA_STYLESHEET = doc/aedis.css
HTML_EXTRA_STYLESHEET = doc/doxygen-awesome.css doc/doxygen-awesome-sidebar-only.css
# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
# other source files which should be copied to the HTML output directory. Note
@@ -1578,7 +1578,7 @@ ECLIPSE_DOC_ID = org.doxygen.Project
# The default value is: NO.
# This tag requires that the tag GENERATE_HTML is set to YES.
DISABLE_INDEX = YES
DISABLE_INDEX = NO
# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
# structure should be generated to display hierarchical information. If the tag
@@ -1595,7 +1595,7 @@ DISABLE_INDEX = YES
# The default value is: NO.
# This tag requires that the tag GENERATE_HTML is set to YES.
GENERATE_TREEVIEW = NO
GENERATE_TREEVIEW = YES
# When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the
# FULL_SIDEBAR option determines if the side bar is limited to only the treeview

View File

@@ -3,7 +3,7 @@
<!-- Navigation index tabs for HTML output -->
<navindex>
<tab type="mainpage" visible="yes" title="Contents"/>
<tab type="pages" visible="no" title="" intro=""/>
<tab type="pages" visible="yes" title="" intro=""/>
<tab type="modules" visible="no" title="Reference" intro=""/>
<tab type="namespaces" visible="no" title="">
<tab type="namespacelist" visible="yes" title="" intro=""/>

View File

@@ -1,30 +0,0 @@
/* Doxygen HTML_EXTRA_STYLESHEET */
div.contents {
max-width: 100em;
margin-right: 5em;
margin-left: 5em;
}
.ui-resizable-e {
background-image:url("splitbar.png");
background-size:100%;
background-repeat:repeat-y;
background-attachment: scroll;
cursor:ew-resize;
height:100%;
right:0;
top:0;
width:1px;
}
.pyrootbox {
border: 1px solid #879ecb;
background-color: #f9fafc;
padding: 15px;
}
code
{
background-color:#fffbeb;
}

View File

@@ -0,0 +1,115 @@
/**
Doxygen Awesome
https://github.com/jothepro/doxygen-awesome-css
MIT License
Copyright (c) 2021 jothepro
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
html {
/* side nav width. MUST be = `TREEVIEW_WIDTH`.
* Make sure it is wide enough to contain the page title (logo + title + version)
*/
--side-nav-fixed-width: 335px;
--menu-display: none;
--top-height: 120px;
--toc-sticky-top: -25px;
--toc-max-height: calc(100vh - 2 * var(--spacing-medium) - 25px);
}
#projectname {
white-space: nowrap;
}
@media screen and (min-width: 768px) {
html {
--searchbar-background: var(--page-background-color);
}
#side-nav {
min-width: var(--side-nav-fixed-width);
max-width: var(--side-nav-fixed-width);
top: var(--top-height);
overflow: visible;
}
#nav-tree, #side-nav {
height: calc(100vh - var(--top-height)) !important;
}
#nav-tree {
padding: 0;
}
#top {
display: block;
border-bottom: none;
height: var(--top-height);
margin-bottom: calc(0px - var(--top-height));
max-width: var(--side-nav-fixed-width);
overflow: hidden;
background: var(--side-nav-background);
}
#main-nav {
float: left;
padding-right: 0;
}
.ui-resizable-handle {
cursor: default;
width: 1px !important;
box-shadow: 0 calc(-2 * var(--top-height)) 0 0 var(--separator-color);
}
#nav-path {
position: fixed;
right: 0;
left: var(--side-nav-fixed-width);
bottom: 0;
width: auto;
}
#doc-content {
height: calc(100vh - 31px) !important;
padding-bottom: calc(3 * var(--spacing-large));
padding-top: calc(var(--top-height) - 80px);
box-sizing: border-box;
margin-left: var(--side-nav-fixed-width) !important;
}
#MSearchBox {
width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium)));
}
#MSearchField {
width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium)) - 65px);
}
#MSearchResultsWindow {
left: var(--spacing-medium) !important;
right: auto;
}
}

2405
doc/doxygen-awesome.css Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,19 +0,0 @@
<!-- HTML footer for doxygen 1.8.14-->
<!-- start footer part -->
<!--BEGIN GENERATE_TREEVIEW-->
<div id="nav-path" class="navpath"><!-- id is needed for treeview function! -->
<ul>
$navpath
<li class="footer">
Aedis 1.0.0 - Reference Guide generated on $datetime using Doxygen $doxygenversion &#160;&#160;
<img class="footer" src="rootlogo_s.gif" alt="root"/></li>
</ul>
</div>
<!--END GENERATE_TREEVIEW-->
<!--BEGIN !GENERATE_TREEVIEW-->
<hr class="footer"/><address class="footer">
Author: Marcelo Zimbres Silva.
</address>
<!--END !GENERATE_TREEVIEW-->
</body>
</html>

View File

@@ -1,34 +0,0 @@
<!-- HTML header for doxygen 1.8.14-->
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=9"/>
<meta name="generator" content="Doxygen $doxygenversion"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<!--BEGIN PROJECT_NAME--><title>$projectname: $title</title><!--END PROJECT_NAME-->
<!--BEGIN !PROJECT_NAME--><title>$title</title><!--END !PROJECT_NAME-->
<link href="$relpath^tabs.css" rel="stylesheet" type="text/css"/>
<script type="text/javascript" src="$relpath^jquery.js"></script>
<script type="text/javascript" src="$relpath^dynsections.js"></script>
$search
<link href="$relpath^$stylesheet" rel="stylesheet" type="text/css" />
$extrastylesheet
</head>
<body>
<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
<!--BEGIN TITLEAREA-->
<div id="titlearea">
<table bgcolor="#346295" cellspacing="0" cellpadding="6">
<tbody>
<tr>
<td valign="middle" style="color: #FFFFFF" nowrap="nowrap"><font size="6">$projectname $projectnumber</font> &#160; <br> $projectbrief </td>
<td style="width:100%"> $searchbox </td>
</tr>
</tbody>
</table>
</div>
<!--END TITLEAREA-->
<!-- end header part -->

View File

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

View File

@@ -6,6 +6,7 @@
#include "common.hpp"
#include <boost/asio.hpp>
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
#include <boost/asio/experimental/awaitable_operators.hpp>
#include <iostream>
@@ -18,9 +19,6 @@ using aedis::resp3::request;
using aedis::adapt;
using aedis::operation;
// Include this in no more than one .cpp file.
#include <aedis/src.hpp>
namespace
{
auto redir(boost::system::error_code& ec)
@@ -31,7 +29,6 @@ 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};
@@ -74,21 +71,23 @@ connect(
throw std::runtime_error("Connect timeout");
}
extern net::awaitable<void> async_main();
// Main function used in our examples.
auto main() -> int
auto run(net::awaitable<void> op) -> int
{
try {
net::io_context ioc;
net::co_spawn(ioc, async_main(), net::detached);
net::co_spawn(ioc, std::move(op), [](std::exception_ptr p) {
if (p)
std::rethrow_exception(p);
});
ioc.run();
return 0;
} catch (std::exception const& e) {
std::cerr << "Error: " << e.what() << std::endl;
return 1;
}
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

@@ -28,5 +28,7 @@ connect(
auto healthy_checker(std::shared_ptr<connection> conn) -> boost::asio::awaitable<void>;
auto run(boost::asio::awaitable<void> op) -> int;
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
#endif // AEDIS_EXAMPLES_COMMON_HPP

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

@@ -0,0 +1,38 @@
/* 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 "common.hpp"
extern boost::asio::awaitable<void> co_main(std::string, std::string);
auto main(int argc, char * argv[]) -> int
{
std::string host = "127.0.0.1";
std::string port = "6379";
if (argc == 3) {
host = argv[1];
port = argv[2];
}
return run(co_main(host, port));
}
#else // defined(BOOST_ASIO_HAS_CO_AWAIT)
#include <iostream>
auto main() -> int
{
std::cout << "Requires coroutine support." << std::endl;
return 0;
}
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)

101
examples/cpp17_intro.cpp Normal file
View File

@@ -0,0 +1,101 @@
/* 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 <aedis.hpp>
#include <aedis/src.hpp>
namespace net = boost::asio;
namespace resp3 = aedis::resp3;
using aedis::resp3::request;
using aedis::adapt;
using aedis::operation;
void log(boost::system::error_code const& ec, char const* prefix)
{
std::clog << prefix << ec.message() << std::endl;
}
auto main(int argc, char * argv[]) -> int
{
try {
std::string host = "127.0.0.1";
std::string port = "6379";
if (argc == 3) {
host = argv[1];
port = argv[2];
}
// The request
resp3::request req;
req.push("HELLO", 3);
req.push("PING", "Hello world");
req.push("QUIT");
// The response.
std::tuple<aedis::ignore, std::string, aedis::ignore> resp;
net::io_context ioc;
// IO objects.
net::ip::tcp::resolver resv{ioc};
aedis::connection conn{ioc};
// Resolve endpoints.
net::ip::tcp::resolver::results_type endpoints;
// async_run callback.
auto on_run = [](auto ec)
{
if (ec)
return log(ec, "on_run: ");
};
// async_exec callback.
auto on_exec = [&](auto ec, auto)
{
if (ec) {
conn.cancel(operation::run);
return log(ec, "on_exec: ");
}
std::cout << "PING: " << std::get<1>(resp) << std::endl;
};
// Connect callback.
auto on_connect = [&](auto ec, auto)
{
if (ec)
return log(ec, "on_connect: ");
conn.async_run(on_run);
conn.async_exec(req, adapt(resp), on_exec);
};
// Resolve callback.
auto on_resolve = [&](auto ec, auto const& addrs)
{
if (ec)
return log(ec, "on_resolve: ");
endpoints = addrs;
net::async_connect(conn.next_layer(), endpoints, on_connect);
};
resv.async_resolve(host, port, on_resolve);
ioc.run();
return 0;
} catch (std::exception const& e) {
std::cerr << "Error: " << e.what() << std::endl;
}
return 1;
}

View File

@@ -0,0 +1,79 @@
/* 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 <tuple>
#include <string>
#include <thread>
#include <iostream>
#include <boost/asio.hpp>
#include <aedis.hpp>
// Include this in no more than one .cpp file.
#include <aedis/src.hpp>
namespace net = boost::asio;
namespace resp3 = aedis::resp3;
using aedis::adapt;
using connection = aedis::connection;
template <class Adapter>
auto exec(std::shared_ptr<connection> conn, resp3::request const& req, Adapter adapter)
{
net::dispatch(
conn->get_executor(),
net::deferred([&]() { return conn->async_exec(req, adapter, net::deferred); }))
(net::use_future).get();
}
auto logger = [](auto const& ec)
{ std::clog << "Run: " << ec.message() << std::endl; };
auto main(int argc, char * argv[]) -> int
{
try {
std::string host = "127.0.0.1";
std::string port = "6379";
if (argc == 3) {
host = argv[1];
port = argv[2];
}
net::io_context ioc{1};
auto conn = std::make_shared<connection>(ioc);
// Resolves the address
net::ip::tcp::resolver resv{ioc};
auto const res = resv.resolve(host, port);
// Connect to Redis
net::connect(conn->next_layer(), res);
// Starts a thread that will can io_context::run on which
// the connection will run.
std::thread t{[conn, &ioc]() {
conn->async_run(logger);
ioc.run();
}};
resp3::request req;
req.push("HELLO", 3);
req.push("PING");
req.push("QUIT");
std::tuple<aedis::ignore, std::string, aedis::ignore> resp;
// Executes commands synchronously.
exec(conn, req, adapt(resp));
std::cout << "Response: " << std::get<1>(resp) << std::endl;
t.join();
} catch (std::exception const& e) {
std::cerr << e.what() << std::endl;
}
}

View File

@@ -14,24 +14,29 @@
namespace net = boost::asio;
namespace resp3 = aedis::resp3;
using aedis::resp3::request;
using aedis::adapter::adapt2;
using net::ip::tcp;
int main()
auto main(int argc, char * argv[]) -> int
{
try {
std::string host = "127.0.0.1";
std::string port = "6379";
if (argc == 3) {
host = argv[1];
port = argv[2];
}
net::io_context ioc;
tcp::resolver resv{ioc};
auto const res = resv.resolve("127.0.0.1", "6379");
tcp::socket socket{ioc};
net::ip::tcp::resolver resv{ioc};
auto const res = resv.resolve(host, port);
net::ip::tcp::socket socket{ioc};
net::connect(socket, res);
// Creates the request and writes to the socket.
request req;
resp3::request req;
req.push("HELLO", 3);
req.push("PING");
req.push("PING", "Hello world");
req.push("QUIT");
resp3::write(socket, req);

View File

@@ -5,28 +5,29 @@
*/
#include <boost/asio.hpp>
#if defined(BOOST_ASIO_HAS_CO_AWAIT) && defined(BOOST_ASIO_HAS_POSIX_STREAM_DESCRIPTOR)
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
#include <iostream>
namespace net = boost::asio;
#if defined(BOOST_ASIO_HAS_POSIX_STREAM_DESCRIPTOR)
#include <boost/asio/experimental/awaitable_operators.hpp>
#include <aedis.hpp>
#include <unistd.h>
#include "common.hpp"
#include "common/common.hpp"
namespace net = boost::asio;
namespace resp3 = aedis::resp3;
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 signal_set = net::use_awaitable_t<>::as_default_on_t<net::signal_set>;
using aedis::adapt;
using aedis::resp3::request;
using aedis::resp3::node;
// Chat over Redis pubsub. To test, run this program from different
// Chat over Redis pubsub. To test, run this program from multiple
// terminals and type messages to stdin.
// Receives Redis server-side pushes.
// Receives Redis pushes.
auto receiver(std::shared_ptr<connection> conn) -> net::awaitable<void>
{
for (std::vector<node<std::string>> resp;;) {
for (std::vector<resp3::node<std::string>> 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();
@@ -38,33 +39,35 @@ auto publisher(std::shared_ptr<stream_descriptor> in, std::shared_ptr<connection
{
for (std::string msg;;) {
auto n = co_await net::async_read_until(*in, net::dynamic_buffer(msg, 1024), "\n");
request req;
resp3::request req;
req.push("PUBLISH", "chat-channel", msg);
co_await conn->async_exec(req);
msg.erase(0, n);
}
}
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", "chat-channel");
co_await conn->async_exec(req);
}
auto async_main() -> net::awaitable<void>
// Called from the main function (see main.cpp)
auto co_main(std::string host, std::string port) -> net::awaitable<void>
{
auto ex = co_await net::this_coro::executor;
auto conn = std::make_shared<connection>(ex);
auto stream = std::make_shared<stream_descriptor>(ex, ::dup(STDIN_FILENO));
signal_set_type sig{ex, SIGINT, SIGTERM};
signal_set sig{ex, SIGINT, SIGTERM};
co_await connect(conn, "127.0.0.1", "6379");
resp3::request req;
req.push("HELLO", 3);
req.push("SUBSCRIBE", "chat-channel");
co_await connect(conn, host, port);
co_await ((conn->async_run() || publisher(stream, conn) || receiver(conn) ||
healthy_checker(conn) || sig.async_wait()) && subscriber(conn));
healthy_checker(conn) || sig.async_wait()) && conn->async_exec(req));
}
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT) && defined(BOOST_ASIO_HAS_POSIX_STREAM_DESCRIPTOR)
#else // defined(BOOST_ASIO_HAS_POSIX_STREAM_DESCRIPTOR)
auto co_main(std::string host, std::string port) -> net::awaitable<void>
{
std::cout << "Requires support for posix streams." << std::endl;
co_return;
}
#endif // defined(BOOST_ASIO_HAS_POSIX_STREAM_DESCRIPTOR)
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)

View File

@@ -11,12 +11,12 @@
#include <map>
#include <vector>
#include "common.hpp"
#include "common/common.hpp"
namespace net = boost::asio;
namespace resp3 = aedis::resp3;
using namespace net::experimental::awaitable_operators;
using aedis::adapt;
using aedis::resp3::request;
void print(std::map<std::string, std::string> const& cont)
{
@@ -30,6 +30,12 @@ void print(std::vector<int> const& cont)
std::cout << "\n";
}
auto run(std::shared_ptr<connection> conn, std::string host, std::string port) -> net::awaitable<void>
{
co_await connect(conn, host, port);
co_await conn->async_run();
}
// Stores the content of some STL containers in Redis.
auto store(std::shared_ptr<connection> conn) -> net::awaitable<void>
{
@@ -39,27 +45,25 @@ auto store(std::shared_ptr<connection> conn) -> net::awaitable<void>
std::map<std::string, std::string> map
{{"key1", "value1"}, {"key2", "value2"}, {"key3", "value3"}};
request req;
req.get_config().cancel_on_connection_lost = true;
resp3::request req;
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;
// A request contains multiple commands.
resp3::request req;
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;
// Responses as tuple elements.
std::tuple<aedis::ignore, std::map<std::string, std::string>> resp;
// Executes the request and reads the response.
co_await conn->async_exec(req, adapt(resp));
print(std::get<1>(resp));
@@ -68,22 +72,19 @@ auto hgetall(std::shared_ptr<connection> conn) -> net::awaitable<void>
// Retrieves in a transaction.
auto transaction(std::shared_ptr<connection> conn) -> net::awaitable<void>
{
request req;
req.get_config().cancel_on_connection_lost = true;
resp3::request req;
req.push("HELLO", 3);
req.push("MULTI");
req.push("LRANGE", "rpush-key", 0, -1); // Retrieves
req.push("HGETALL", "hset-key"); // Retrieves
req.push("EXEC");
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
std::tuple<std::optional<std::vector<int>>, std::optional<std::map<std::string, std::string>>> // exec
> resp;
co_await conn->async_exec(req, adapt(resp));
@@ -92,20 +93,24 @@ auto transaction(std::shared_ptr<connection> conn) -> net::awaitable<void>
print(std::get<1>(std::get<4>(resp)).value());
}
net::awaitable<void> async_main()
auto quit(std::shared_ptr<connection> conn) -> net::awaitable<void>
{
auto conn = std::make_shared<connection>(co_await net::this_coro::executor);
resp3::request req;
req.push("QUIT");
// 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 conn->async_exec(req);
}
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));
// Called from the main function (see main.cpp)
net::awaitable<void> co_main(std::string host, std::string port)
{
auto ex = co_await net::this_coro::executor;
auto conn = std::make_shared<connection>(ex);
net::co_spawn(ex, run(conn, host, port), net::detached);
co_await store(conn);
co_await transaction(conn);
co_await hgetall(conn);
co_await quit(conn);
}
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)

View File

@@ -8,19 +8,19 @@
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
#include <boost/asio/experimental/awaitable_operators.hpp>
#include <aedis.hpp>
#include "common.hpp"
#include "common/common.hpp"
namespace net = boost::asio;
namespace resp3 = aedis::resp3;
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 signal_set = net::use_awaitable_t<>::as_default_on_t<net::signal_set>;
using aedis::adapt;
using aedis::resp3::request;
auto echo_server_session(tcp_socket socket, std::shared_ptr<connection> conn) -> net::awaitable<void>
{
request req;
resp3::request req;
std::string resp;
for (std::string buffer;;) {
@@ -35,6 +35,7 @@ auto echo_server_session(tcp_socket socket, std::shared_ptr<connection> conn) ->
}
}
// Listens for tcp connections.
auto listener(std::shared_ptr<connection> conn) -> net::awaitable<void>
{
auto ex = co_await net::this_coro::executor;
@@ -43,17 +44,17 @@ auto listener(std::shared_ptr<connection> conn) -> net::awaitable<void>
net::co_spawn(ex, echo_server_session(co_await acc.async_accept(), conn), net::detached);
}
auto async_main() -> net::awaitable<void>
// Called from the main function (see main.cpp)
auto co_main(std::string host, std::string port) -> net::awaitable<void>
{
auto ex = co_await net::this_coro::executor;
auto conn = std::make_shared<connection>(ex);
signal_set_type sig{ex, SIGINT, SIGTERM};
signal_set sig{ex, SIGINT, SIGTERM};
request req;
req.get_config().cancel_on_connection_lost = true;
resp3::request req;
req.push("HELLO", 3);
co_await connect(conn, "127.0.0.1", "6379");
co_await connect(conn, host, port);
co_await ((conn->async_run() || listener(conn) || healthy_checker(conn) ||
sig.async_wait()) && conn->async_exec(req));
}

61
examples/cpp20_intro.cpp Normal file
View File

@@ -0,0 +1,61 @@
/* 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 "common/common.hpp"
namespace net = boost::asio;
namespace resp3 = aedis::resp3;
using aedis::adapt;
using aedis::operation;
auto run(std::shared_ptr<connection> conn, std::string host, std::string port) -> net::awaitable<void>
{
co_await connect(conn, host, port);
co_await conn->async_run();
}
auto hello(std::shared_ptr<connection> conn) -> net::awaitable<void>
{
resp3::request req;
req.push("HELLO", 3);
co_await conn->async_exec(req);
}
auto ping(std::shared_ptr<connection> conn) -> net::awaitable<void>
{
resp3::request req;
req.push("PING", "Hello world");
std::tuple<std::string> resp;
co_await conn->async_exec(req, adapt(resp));
std::cout << "PING: " << std::get<0>(resp) << std::endl;
}
auto quit(std::shared_ptr<connection> conn) -> net::awaitable<void>
{
resp3::request req;
req.push("QUIT");
co_await conn->async_exec(req);
}
// Called from the main function (see main.cpp)
auto co_main(std::string host, std::string port) -> net::awaitable<void>
{
auto ex = co_await net::this_coro::executor;
auto conn = std::make_shared<connection>(ex);
net::co_spawn(ex, run(conn, host, port), net::detached);
co_await hello(conn);
co_await ping(conn);
co_await quit(conn);
}
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)

View File

@@ -8,17 +8,17 @@
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
#include <boost/asio/experimental/awaitable_operators.hpp>
#include <aedis.hpp>
#include "common.hpp"
#include "common/common.hpp"
namespace net = boost::asio;
namespace resp3 = aedis::resp3;
using namespace net::experimental::awaitable_operators;
using aedis::adapt;
using aedis::resp3::request;
net::awaitable<void> async_main()
// Called from the main function (see main.cpp)
auto co_main(std::string host, std::string port) -> net::awaitable<void>
{
request req;
req.get_config().cancel_on_connection_lost = true;
resp3::request req;
req.push("HELLO", 3);
req.push("PING", "Hello world");
req.push("QUIT");
@@ -26,7 +26,7 @@ net::awaitable<void> async_main()
std::tuple<aedis::ignore, std::string, aedis::ignore> resp;
auto conn = std::make_shared<connection>(co_await net::this_coro::executor);
co_await connect(conn, "127.0.0.1", "6379");
co_await connect(conn, host, port);
co_await (conn->async_run() || conn->async_exec(req, adapt(resp)));
std::cout << "PING: " << std::get<1>(resp) << std::endl;

View File

@@ -17,10 +17,10 @@
#include <aedis/ssl/connection.hpp>
namespace net = boost::asio;
namespace resp3 = aedis::resp3;
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 connection = net::use_awaitable_t<>::as_default_on_t<aedis::ssl::connection>;
auto verify_certificate(bool, net::ssl::verify_context&) -> bool
@@ -29,10 +29,9 @@ auto verify_certificate(bool, net::ssl::verify_context&) -> bool
return true;
}
net::awaitable<void> async_main()
net::awaitable<void> co_main(std::string, std::string)
{
request req;
req.get_config().cancel_on_connection_lost = true;
resp3::request req;
req.push("HELLO", 3, "AUTH", "aedis", "aedis");
req.push("PING");
req.push("QUIT");

View File

@@ -4,32 +4,32 @@
* accompanying file LICENSE.txt)
*/
#include <boost/asio.hpp>
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
#include <aedis.hpp>
#include <string>
#include <iostream>
#include <boost/asio.hpp>
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
#include <aedis.hpp>
#include <aedis/src.hpp>
namespace net = boost::asio;
namespace resp3 = aedis::resp3;
using endpoints = net::ip::tcp::resolver::results_type;
using 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;
net::awaitable<void> ping(endpoints const& addrs)
auto co_main(std::string host, std::string port) -> net::awaitable<void>
{
tcp_socket socket{co_await net::this_coro::executor};
net::connect(socket, addrs);
auto ex = co_await net::this_coro::executor;
resolver resv{ex};
auto const addrs = co_await resv.async_resolve(host, port);
tcp_socket socket{ex};
co_await net::async_connect(socket, addrs);
// Creates the request and writes to the socket.
request req;
resp3::request req;
req.push("HELLO", 3);
req.push("PING");
req.push("PING", "Hello world");
req.push("QUIT");
co_await resp3::async_write(socket, req);
@@ -45,19 +45,4 @@ net::awaitable<void> ping(endpoints const& addrs)
std::cout << "Ping: " << resp << std::endl;
}
int main()
{
try {
net::io_context ioc;
net::ip::tcp::resolver resv{ioc};
auto const addrs = resv.resolve("127.0.0.1", "6379");
net::co_spawn(ioc, ping(addrs), net::detached);
ioc.run();
} catch (std::exception const& e) {
std::cerr << "Error: " << e.what() << std::endl;
}
}
#else // defined(BOOST_ASIO_HAS_CO_AWAIT)
auto main() -> int {std::cout << "Requires coroutine support." << std::endl; return 0;}
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)

View File

@@ -8,18 +8,19 @@
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
#include <boost/asio/experimental/awaitable_operators.hpp>
#include <aedis.hpp>
#include "common.hpp"
#include "common/common.hpp"
namespace net = boost::asio;
namespace resp3 = aedis::resp3;
using namespace net::experimental::awaitable_operators;
using endpoints = net::ip::tcp::resolver::results_type;
using aedis::adapt;
using aedis::resp3::request;
auto redir(boost::system::error_code& ec)
{ return net::redirect_error(net::use_awaitable, ec); }
struct endpoint {
struct address {
std::string host;
std::string port;
};
@@ -27,10 +28,9 @@ struct endpoint {
// For more info see
// - https://redis.io/docs/manual/sentinel.
// - https://redis.io/docs/reference/sentinel-clients.
auto resolve_master_address(std::vector<endpoint> const& endpoints) -> net::awaitable<endpoint>
auto resolve_master_address(std::vector<address> const& endpoints) -> net::awaitable<address>
{
request req;
req.get_config().cancel_on_connection_lost = true;
resp3::request req;
req.push("SENTINEL", "get-master-addr-by-name", "mymaster");
req.push("QUIT");
@@ -43,20 +43,20 @@ auto resolve_master_address(std::vector<endpoint> const& endpoints) -> net::awai
co_await (conn->async_run() && conn->async_exec(req, adapt(addr), redir(ec)));
conn->reset_stream();
if (std::get<0>(addr))
co_return endpoint{std::get<0>(addr).value().at(0), std::get<0>(addr).value().at(1)};
co_return address{std::get<0>(addr).value().at(0), std::get<0>(addr).value().at(1)};
}
co_return endpoint{};
co_return address{};
}
auto async_main() -> net::awaitable<void>
auto co_main(std::string host, std::string port) -> net::awaitable<void>
{
// A list of sentinel addresses from which only one is responsive
// to simulate sentinels that are down.
std::vector<endpoint> const endpoints
// A list of sentinel addresses from which only one is responsive.
// This simulates sentinels that are down.
std::vector<address> const endpoints
{ {"foo", "26379"}
, {"bar", "26379"}
, {"127.0.0.1", "26379"}
, {host, port}
};
auto const ep = co_await resolve_master_address(endpoints);

View File

@@ -7,31 +7,48 @@
#include <boost/asio.hpp>
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
#include <boost/asio/experimental/awaitable_operators.hpp>
#define BOOST_JSON_NO_LIB
#define BOOST_CONTAINER_NO_LIB
#include <boost/json.hpp>
#include <aedis.hpp>
#include "common.hpp"
#include <algorithm>
#include <cstdint>
#include <iostream>
#include <set>
#include <iterator>
#include <string>
#include "common/common.hpp"
// Include this in no more than one .cpp file.
#include <boost/json/src.hpp>
namespace net = boost::asio;
namespace resp3 = aedis::resp3;
using namespace net::experimental::awaitable_operators;
using aedis::resp3::request;
using aedis::adapt;
using namespace boost::json;
using aedis::adapt;
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 =
@@ -42,7 +59,7 @@ 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)
void extract(object const& obj, T& t, std::string_view key)
{
t = value_to<T>(obj.at(key));
}
@@ -57,40 +74,24 @@ auto tag_invoke(value_to_tag<user>, value const& jv)
return u;
}
// Serializes
// 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&)
void from_bulk(user& u, std::string_view sv, boost::system::error_code&)
{
value jv = parse(sv);
u = value_to<user>(jv);
}
auto operator<<(std::ostream& os, user const& u) -> std::ostream&
{
os << "Name: " << u.name << "\n"
<< "Age: " << u.age << "\n"
<< "Country: " << u.country;
return os;
}
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);
}
net::awaitable<void> async_main()
net::awaitable<void> co_main(std::string host, std::string port)
{
std::set<user> users
{{"Joao", "58", "Brazil"} , {"Serge", "60", "France"}};
request req;
req.get_config().cancel_on_connection_lost = true;
resp3::request req;
req.push("HELLO", 3);
req.push_range("SADD", "sadd-key", users); // Sends
req.push("SMEMBERS", "sadd-key"); // Retrieves
@@ -99,7 +100,8 @@ net::awaitable<void> async_main()
std::tuple<aedis::ignore, int, std::set<user>, std::string> resp;
auto conn = std::make_shared<connection>(co_await net::this_coro::executor);
co_await connect(conn, "127.0.0.1", "6379");
co_await connect(conn, host, port);
co_await (conn->async_run() || conn->async_exec(req, adapt(resp)));
for (auto const& e: std::get<2>(resp))

View File

@@ -9,16 +9,13 @@
#include <boost/asio/experimental/awaitable_operators.hpp>
#include <aedis.hpp>
#include "common.hpp"
#include "common/common.hpp"
namespace net = boost::asio;
namespace resp3 = aedis::resp3;
using namespace net::experimental::awaitable_operators;
using resolver = net::use_awaitable_t<>::as_default_on_t<net::ip::tcp::resolver>;
using signal_set_type = net::use_awaitable_t<>::as_default_on_t<net::signal_set>;
using timer_type = net::use_awaitable_t<>::as_default_on_t<net::steady_timer>;
using steady_timer = net::use_awaitable_t<>::as_default_on_t<net::steady_timer>;
using aedis::adapt;
using aedis::resp3::request;
using aedis::resp3::node;
/* This example will subscribe and read pushes indefinitely.
*
@@ -39,30 +36,29 @@ using aedis::resp3::node;
// Receives pushes.
auto receiver(std::shared_ptr<connection> conn) -> net::awaitable<void>
{
for (std::vector<node<std::string>> resp;;) {
using resp_type = std::vector<resp3::node<std::string>>;
for (resp_type 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();
}
}
auto async_main() -> net::awaitable<void>
auto co_main(std::string host, std::string port) -> net::awaitable<void>
{
auto ex = co_await net::this_coro::executor;
auto conn = std::make_shared<connection>(ex);
signal_set_type sig{ex, SIGINT, SIGTERM};
timer_type timer{ex};
steady_timer timer{ex};
request req;
req.get_config().cancel_on_connection_lost = true;
resp3::request req;
req.push("HELLO", 3);
req.push("SUBSCRIBE", "channel");
// The loop will reconnect on connection lost. To exit type Ctrl-C twice.
for (;;) {
co_await connect(conn, "127.0.0.1", "6379");
co_await ((conn->async_run() || healthy_checker(conn) || sig.async_wait() ||
receiver(conn)) && conn->async_exec(req));
co_await connect(conn, host, port);
co_await ((conn->async_run() || healthy_checker(conn) || receiver(conn)) && conn->async_exec(req));
conn->reset_stream();
timer.expires_after(std::chrono::seconds{1});
co_await timer.async_wait();

View File

@@ -1,64 +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 <tuple>
#include <string>
#include <thread>
#include <boost/asio.hpp>
#include <aedis.hpp>
// TODO: Fix this after updating to 1.80.
// Include this in no more than one .cpp file.
#include <aedis/src.hpp>
namespace net = boost::asio;
using aedis::adapt;
using aedis::resp3::request;
using connection = aedis::connection<>;
template <class Adapter>
auto exec(connection& conn, request const& req, Adapter adapter, boost::system::error_code& ec)
{
net::dispatch(
conn.get_executor(),
net::deferred([&]() { return conn.async_exec(req, adapter, net::deferred); }))
(net::redirect_error(net::use_future, ec)).get();
}
auto logger = [](auto const& ec)
{ std::clog << "Run: " << ec.message() << std::endl; };
int main()
{
try {
net::io_context ioc{1};
connection conn{ioc};
std::thread t{[&]() {
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::cout
<< "Exec: " << ec.message() << "\n"
<< "Response: " << std::get<0>(resp) << std::endl;
t.join();
} catch (std::exception const& e) {
std::cerr << e.what() << std::endl;
}
}

View File

View File

@@ -7,18 +7,18 @@
#ifndef AEDIS_ADAPT_HPP
#define AEDIS_ADAPT_HPP
#include <tuple>
#include <limits>
#include <boost/mp11.hpp>
#include <boost/variant2.hpp>
#include <boost/utility/string_view.hpp>
#include <boost/system.hpp>
#include <aedis/resp3/node.hpp>
#include <aedis/adapter/adapt.hpp>
#include <aedis/adapter/detail/response_traits.hpp>
#include <boost/mp11.hpp>
#include <boost/system.hpp>
#include <tuple>
#include <limits>
#include <string_view>
#include <variant>
namespace aedis {
/** @brief Tag used to ignore responses.
@@ -44,7 +44,7 @@ public:
void
operator()(
std::size_t, resp3::node<boost::string_view> const&, boost::system::error_code&) { }
std::size_t, resp3::node<std::string_view> const&, boost::system::error_code&) { }
[[nodiscard]]
auto get_supported_response_size() const noexcept
@@ -63,7 +63,7 @@ class static_adapter {
private:
static constexpr auto size = std::tuple_size<Tuple>::value;
using adapter_tuple = boost::mp11::mp_transform<adapter::adapter_t, Tuple>;
using variant_type = boost::mp11::mp_rename<adapter_tuple, boost::variant2::variant>;
using variant_type = boost::mp11::mp_rename<adapter_tuple, std::variant>;
using adapters_array_type = std::array<variant_type, size>;
adapters_array_type adapters_;
@@ -87,10 +87,10 @@ public:
void
operator()(
std::size_t i,
resp3::node<boost::string_view> const& nd,
resp3::node<std::string_view> const& nd,
boost::system::error_code& ec)
{
using boost::variant2::visit;
using std::visit;
// I am usure whether this should be an error or an assertion.
BOOST_ASSERT(i < adapters_.size());
visit([&](auto& arg){arg(nd, ec);}, adapters_.at(i));
@@ -122,7 +122,7 @@ public:
void
operator()(
std::size_t,
resp3::node<boost::string_view> const& nd,
resp3::node<std::string_view> const& nd,
boost::system::error_code& ec)
{
adapter_(nd, ec);
@@ -164,7 +164,7 @@ class wrapper {
public:
explicit wrapper(Adapter adapter) : adapter_{adapter} {}
void operator()(resp3::node<boost::string_view> const& node, boost::system::error_code& ec)
void operator()(resp3::node<std::string_view> const& node, boost::system::error_code& ec)
{ return adapter_(0, node, ec); }
[[nodiscard]]
@@ -210,7 +210,7 @@ inline auto adapt(std::size_t max_read_size = (std::numeric_limits<std::size_t>:
* 2. std::vector<node<String>>
*
* The types T1, T2, etc can be any STL container, any integer type
* and \c std::string
* and `std::string`.
*
* @param t Tuple containing the responses.
* @param max_read_size Specifies the maximum size of the read

View File

@@ -7,6 +7,14 @@
#ifndef AEDIS_ADAPTER_ADAPTERS_HPP
#define AEDIS_ADAPTER_ADAPTERS_HPP
#include <aedis/error.hpp>
#include <aedis/resp3/type.hpp>
#include <aedis/resp3/request.hpp>
#include <aedis/resp3/detail/parser.hpp>
#include <aedis/resp3/node.hpp>
#include <boost/assert.hpp>
#include <set>
#include <optional>
#include <unordered_set>
@@ -18,68 +26,40 @@
#include <deque>
#include <vector>
#include <array>
#include <boost/assert.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/home/x3.hpp>
#include <boost/utility/string_view.hpp>
#include <aedis/error.hpp>
#include <aedis/resp3/type.hpp>
#include <aedis/resp3/request.hpp>
#include <aedis/resp3/detail/parser.hpp>
#include <aedis/resp3/node.hpp>
#include <string_view>
#include <charconv>
namespace aedis::adapter::detail {
inline
auto parse_double(
char const* data,
std::size_t size,
boost::system::error_code& ec) -> double
{
static constexpr boost::spirit::x3::real_parser<double> p{};
double ret = 0;
if (!parse(data, data + size, p, ret))
ec = error::not_a_double;
return ret;
}
// Serialization.
template <class T>
auto from_bulk(
T& i,
boost::string_view sv,
boost::system::error_code& ec) -> typename std::enable_if<std::is_integral<T>::value, void>::type
auto from_bulk(T& i, std::string_view sv, 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);
auto const res = std::from_chars(sv.data(), sv.data() + std::size(sv), i);
if (res.ec != std::errc())
ec = error::not_a_number;
}
inline
void from_bulk(
bool& t,
boost::string_view sv,
boost::system::error_code&)
void from_bulk(bool& t, std::string_view sv, boost::system::error_code&)
{
t = *sv.data() == 't';
}
inline
void from_bulk(
double& d,
boost::string_view sv,
boost::system::error_code& ec)
void from_bulk(double& d, std::string_view sv, boost::system::error_code& ec)
{
d = parse_double(sv.data(), sv.size(), ec);
auto const res = std::from_chars(sv.data(), sv.data() + std::size(sv), d);
if (res.ec != std::errc())
ec = error::not_a_double;
}
template <class CharT, class Traits, class Allocator>
void
from_bulk(
std::basic_string<CharT, Traits, Allocator>& s,
boost::string_view sv,
std::string_view sv,
boost::system::error_code&)
{
s.append(sv.data(), sv.size());
@@ -105,7 +85,7 @@ private:
public:
explicit general_aggregate(Result* c = nullptr): result_(c) {}
void operator()(resp3::node<boost::string_view> const& n, boost::system::error_code&)
void operator()(resp3::node<std::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)}});
}
@@ -119,7 +99,7 @@ private:
public:
explicit general_simple(Node* t = nullptr) : result_(t) {}
void operator()(resp3::node<boost::string_view> const& n, boost::system::error_code& ec)
void operator()(resp3::node<std::string_view> const& n, boost::system::error_code& ec)
{
result_->data_type = n.data_type;
result_->aggregate_size = n.aggregate_size;
@@ -137,7 +117,7 @@ public:
void
operator()(
Result& result,
resp3::node<boost::string_view> const& n,
resp3::node<std::string_view> const& n,
boost::system::error_code& ec)
{
set_on_resp3_error(n.data_type, ec);
@@ -165,7 +145,7 @@ public:
void
operator()(
Result& result,
resp3::node<boost::string_view> const& nd,
resp3::node<std::string_view> const& nd,
boost::system::error_code& ec)
{
set_on_resp3_error(nd.data_type, ec);
@@ -204,7 +184,7 @@ public:
void
operator()(
Result& result,
resp3::node<boost::string_view> const& nd,
resp3::node<std::string_view> const& nd,
boost::system::error_code& ec)
{
set_on_resp3_error(nd.data_type, ec);
@@ -246,7 +226,7 @@ public:
void
operator()(
Result& result,
resp3::node<boost::string_view> const& nd,
resp3::node<std::string_view> const& nd,
boost::system::error_code& ec)
{
set_on_resp3_error(nd.data_type, ec);
@@ -274,7 +254,7 @@ public:
void
operator()(
Result& result,
resp3::node<boost::string_view> const& nd,
resp3::node<std::string_view> const& nd,
boost::system::error_code& ec)
{
set_on_resp3_error(nd.data_type, ec);
@@ -313,7 +293,7 @@ struct list_impl {
void
operator()(
Result& result,
resp3::node<boost::string_view> const& nd,
resp3::node<std::string_view> const& nd,
boost::system::error_code& ec)
{
set_on_resp3_error(nd.data_type, ec);
@@ -388,7 +368,7 @@ public:
void
operator()(
resp3::node<boost::string_view> const& nd,
resp3::node<std::string_view> const& nd,
boost::system::error_code& ec)
{
BOOST_ASSERT(result_);
@@ -407,7 +387,7 @@ public:
void
operator()(
resp3::node<boost::string_view> const& nd,
resp3::node<std::string_view> const& nd,
boost::system::error_code& ec)
{
if (nd.data_type == resp3::type::null)

View File

@@ -7,17 +7,18 @@
#ifndef AEDIS_ADAPTER_RESPONSE_TRAITS_HPP
#define AEDIS_ADAPTER_RESPONSE_TRAITS_HPP
#include <vector>
#include <tuple>
#include <boost/mp11.hpp>
#include <boost/variant2.hpp>
#include <aedis/error.hpp>
#include <aedis/resp3/type.hpp>
#include <aedis/resp3/read.hpp>
#include <aedis/adapter/detail/adapters.hpp>
#include <boost/mp11.hpp>
#include <vector>
#include <tuple>
#include <string_view>
#include <variant>
namespace aedis::adapter::detail {
using ignore = std::decay_t<decltype(std::ignore)>;
@@ -74,7 +75,7 @@ struct assigner {
template <class T1, class T2>
static void assign(T1& dest, T2& from)
{
dest[N] = internal_adapt(std::get<N>(from));
dest[N].template emplace<N>(internal_adapt(std::get<N>(from)));
assigner<N - 1>::assign(dest, from);
}
};
@@ -84,7 +85,7 @@ struct assigner<0> {
template <class T1, class T2>
static void assign(T1& dest, T2& from)
{
dest[0] = internal_adapt(std::get<0>(from));
dest[0].template emplace<0>(internal_adapt(std::get<0>(from)));
}
};
@@ -96,7 +97,7 @@ private:
boost::mp11::mp_rename<
boost::mp11::mp_transform<
adapter_t, Tuple>,
boost::variant2::variant>,
std::variant>,
std::tuple_size<Tuple>::value>;
std::size_t i_ = 0;
@@ -109,7 +110,7 @@ public:
detail::assigner<std::tuple_size<Tuple>::value - 1>::assign(adapters_, *r);
}
void count(resp3::node<boost::string_view> const& nd)
void count(resp3::node<std::string_view> const& nd)
{
if (nd.depth == 1) {
if (is_aggregate(nd.data_type))
@@ -126,10 +127,10 @@ public:
void
operator()(
resp3::node<boost::string_view> const& nd,
resp3::node<std::string_view> const& nd,
boost::system::error_code& ec)
{
using boost::variant2::visit;
using std::visit;
if (nd.depth == 0) {
auto const real_aggr_size = nd.aggregate_size * element_multiplicity(nd.data_type);

View File

@@ -7,20 +7,20 @@
#ifndef AEDIS_CONNECTION_HPP
#define AEDIS_CONNECTION_HPP
#include <chrono>
#include <memory>
#include <aedis/detail/connection_base.hpp>
#include <boost/asio/io_context.hpp>
#include <aedis/detail/connection_base.hpp>
#include <chrono>
#include <memory>
namespace aedis {
/** @brief A connection to the Redis server.
* @ingroup high-level-api
*
* This class keeps a healthy connection to the Redis instance where
* commands can be sent at any time. For more details, please see the
* documentation of each individual function.
* For more details, please see the documentation of each individual
* function.
*
* @tparam AsyncReadWriteStream A stream that supports reading and
* writing.
@@ -47,7 +47,7 @@ public:
using base_type = detail::connection_base<executor_type, basic_connection<AsyncReadWriteStream>>;
/// Constructor
/// Contructs from an executor.
explicit
basic_connection(
executor_type ex,
@@ -56,6 +56,7 @@ public:
, stream_{ex}
{}
/// Contructs from a context.
explicit
basic_connection(
boost::asio::io_context& ioc,
@@ -82,10 +83,11 @@ public:
/// Returns a const reference to the next layer.
auto next_layer() const noexcept -> auto const& { return stream_; }
/** @brief Establishes a connection with the Redis server asynchronously.
/** @brief Starts read and write operations
*
* This function will start reading from the socket and executes
* all requests that have been started prior to this function
* 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.
*
* @param token Completion token.
@@ -96,9 +98,9 @@ public:
* 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.
* This function will complete when the connection is lost. 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(CompletionToken token = CompletionToken{})
@@ -108,18 +110,18 @@ public:
/** @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
* This function sends a request to the Redis server and
* 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.
* @param adapter Response adapter.
* @param token Asio completion token.
*
* For an example see echo_server.cpp. The completion token must
* For an example see cpp20_echo_server.cpp. The completion token must
* have the following signature
*
* @code
@@ -144,12 +146,12 @@ public:
*
* Users that expect server pushes should call this function in a
* loop. If a push arrives and there is no reader, the connection
* will hang and eventually timeout.
* will hang.
*
* @param adapter The response adapter.
* @param token The Asio completion token.
*
* For an example see subscriber.cpp. The completion token must
* For an example see cpp20_subscriber.cpp. The completion token must
* have the following signature
*
* @code
@@ -177,12 +179,7 @@ public:
* @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: Cancels any ongoing callto
* `async_receive`.
* @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.
@@ -196,7 +193,6 @@ private:
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_op;
template <class> friend struct detail::reader_op;
template <class> friend struct detail::writer_op;
template <class> friend struct detail::run_op;

View File

@@ -7,6 +7,18 @@
#ifndef AEDIS_CONNECTION_BASE_HPP
#define AEDIS_CONNECTION_BASE_HPP
#include <aedis/adapt.hpp>
#include <aedis/operation.hpp>
#include <aedis/resp3/request.hpp>
#include <aedis/detail/connection_ops.hpp>
#include <boost/assert.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/asio/bind_executor.hpp>
#include <boost/asio/deferred.hpp>
#include <boost/asio/experimental/channel.hpp>
#include <vector>
#include <queue>
#include <limits>
@@ -15,17 +27,6 @@
#include <type_traits>
#include <memory_resource>
#include <boost/assert.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/asio/bind_executor.hpp>
#include <boost/asio/experimental/channel.hpp>
#include <aedis/adapt.hpp>
#include <aedis/operation.hpp>
#include <aedis/resp3/request.hpp>
#include <aedis/detail/connection_ops.hpp>
namespace aedis::detail {
/** Base class for high level Redis asynchronous connections.
@@ -43,11 +44,10 @@ public:
using executor_type = Executor;
using this_type = connection_base<Executor, Derived>;
explicit
connection_base(executor_type ex, std::pmr::memory_resource* resource)
: writer_timer_{ex}
, read_timer_{ex}
, push_channel_{ex}
, guarded_op_{ex}
, read_buffer_{resource}
, write_buffer_{resource}
, reqs_{resource}
@@ -76,7 +76,7 @@ public:
}
case operation::receive:
{
push_channel_.cancel();
guarded_op_.cancel();
return 1U;
}
default: BOOST_ASSERT(false); return 0;
@@ -105,14 +105,16 @@ public:
auto cancel_on_conn_lost() -> std::size_t
{
// Must return false if the request should be removed.
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());
if (ptr->is_written()) {
return !ptr->get_request().get_config().cancel_if_unresponded;
} else {
return !ptr->get_request().get_config().cancel_on_connection_lost;
}
};
auto point = std::stable_partition(std::begin(reqs_), std::end(reqs_), cond);
@@ -127,16 +129,12 @@ public:
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>>
auto async_exec(
resp3::request const& req,
Adapter adapter = adapt(),
CompletionToken token = CompletionToken{})
template <class Adapter, class CompletionToken>
auto async_exec(resp3::request const& req, Adapter adapter, CompletionToken token)
{
BOOST_ASSERT_MSG(req.size() <= adapter.get_supported_response_size(), "Request and adapter have incompatible sizes.");
@@ -146,18 +144,14 @@ public:
>(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(
Adapter adapter = adapt(),
CompletionToken token = CompletionToken{})
template <class Adapter, class CompletionToken>
auto async_receive(Adapter adapter, CompletionToken token)
{
auto f = detail::make_adapter_wrapper(adapter);
return boost::asio::async_compose
< CompletionToken
, void(boost::system::error_code, std::size_t)
>(detail::receive_op<Derived, decltype(f)>{&derived(), f}, token, writer_timer_);
return guarded_op_.async_wait(
resp3::async_read(derived().next_layer(), make_dynamic_buffer(adapter.get_max_read_size(0)), f, boost::asio::deferred),
std::move(token));
}
template <class CompletionToken>
@@ -173,9 +167,6 @@ private:
using clock_type = std::chrono::steady_clock;
using clock_traits_type = boost::asio::wait_traits<clock_type>;
using timer_type = boost::asio::basic_waitable_timer<clock_type, clock_traits_type, executor_type>;
using resolver_type = boost::asio::ip::basic_resolver<boost::asio::ip::tcp, executor_type>;
using push_channel_type = boost::asio::experimental::channel<executor_type, void(boost::system::error_code, std::size_t)>;
using time_point_type = std::chrono::time_point<std::chrono::steady_clock>;
auto derived() -> Derived& { return static_cast<Derived&>(*this); }
@@ -246,8 +237,8 @@ private:
[[nodiscard]] auto get_request() const noexcept -> auto const&
{ return *req_; }
[[nodiscard]] auto get_action() const noexcept
{ return action_;}
[[nodiscard]] auto stop_requested() const noexcept
{ return action_ == action::stop;}
template <class CompletionToken>
auto async_wait(CompletionToken token)
@@ -276,7 +267,6 @@ private:
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::run_op;
@@ -368,10 +358,12 @@ private:
}
}
// IO objects
// Notice we use a timer to simulate a condition-variable. It is
// also more suitable than a channel and the notify operation does
// not suspend.
timer_type writer_timer_;
timer_type read_timer_;
push_channel_type push_channel_;
detail::guarded_operation<executor_type> guarded_op_;
std::pmr::string read_buffer_;
std::pmr::string write_buffer_;

View File

@@ -7,8 +7,14 @@
#ifndef AEDIS_CONNECTION_OPS_HPP
#define AEDIS_CONNECTION_OPS_HPP
#include <array>
#include <algorithm>
#include <aedis/adapt.hpp>
#include <aedis/error.hpp>
#include <aedis/detail/guarded_operation.hpp>
#include <aedis/resp3/type.hpp>
#include <aedis/resp3/detail/parser.hpp>
#include <aedis/resp3/read.hpp>
#include <aedis/resp3/write.hpp>
#include <aedis/resp3/request.hpp>
#include <boost/assert.hpp>
#include <boost/system.hpp>
@@ -16,59 +22,12 @@
#include <boost/core/ignore_unused.hpp>
#include <boost/asio/experimental/parallel_group.hpp>
#include <aedis/adapt.hpp>
#include <aedis/error.hpp>
#include <aedis/detail/net.hpp>
#include <aedis/resp3/type.hpp>
#include <aedis/resp3/detail/parser.hpp>
#include <aedis/resp3/read.hpp>
#include <aedis/resp3/write.hpp>
#include <aedis/resp3/request.hpp>
#include <boost/asio/yield.hpp>
#include <array>
#include <algorithm>
#include <string_view>
namespace aedis::detail {
template <class Conn, class Adapter>
struct receive_op {
Conn* conn = nullptr;
Adapter adapter;
std::size_t read_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)
{
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));
// 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));
AEDIS_CHECK_OP1();
self.complete({}, read_size);
return;
}
}
};
template <class Conn, class Adapter>
struct exec_read_op {
Conn* conn;
@@ -84,7 +43,7 @@ struct exec_read_op {
, boost::system::error_code ec = {}
, std::size_t n = 0)
{
reenter (coro)
BOOST_ASIO_CORO_REENTER (coro)
{
// Loop reading the responses to this request.
BOOST_ASSERT(!conn->reqs_.empty());
@@ -96,34 +55,34 @@ struct exec_read_op {
// to hand it to the push consumer. To do that we need
// some data in the read bufer.
if (conn->read_buffer_.empty()) {
yield
BOOST_ASIO_CORO_YIELD
boost::asio::async_read_until(
conn->next_layer(),
conn->make_dynamic_buffer(),
"\r\n", std::move(self));
AEDIS_CHECK_OP1(conn->cancel(operation::run));
AEDIS_CHECK_OP1(conn->cancel(operation::run););
}
// If the next request is a push we have to handle it to
// the receive_op wait for it to be done and continue.
if (resp3::to_type(conn->read_buffer_.front()) == resp3::type::push) {
yield
async_send_receive(conn->push_channel_, std::move(self));
AEDIS_CHECK_OP1(conn->cancel(operation::run));
BOOST_ASIO_CORO_YIELD
conn->guarded_op_.async_run(std::move(self));
AEDIS_CHECK_OP1(conn->cancel(operation::run););
continue;
}
//-----------------------------------
yield
BOOST_ASIO_CORO_YIELD
resp3::async_read(
conn->next_layer(),
conn->make_dynamic_buffer(adapter.get_max_read_size(index)),
[i = index, adpt = adapter] (resp3::node<boost::string_view> const& nd, boost::system::error_code& ec) mutable { adpt(i, nd, ec); },
[i = index, adpt = adapter] (resp3::node<std::string_view> const& nd, boost::system::error_code& ec) mutable { adpt(i, nd, ec); },
std::move(self));
++index;
AEDIS_CHECK_OP1(conn->cancel(operation::run));
AEDIS_CHECK_OP1(conn->cancel(operation::run););
read_size += n;
@@ -156,7 +115,7 @@ struct exec_op {
, boost::system::error_code ec = {}
, std::size_t n = 0)
{
reenter (coro)
BOOST_ASIO_CORO_REENTER (coro)
{
// Check whether the user wants to wait for the connection to
// be stablished.
@@ -168,10 +127,11 @@ struct exec_op {
conn->add_request_info(info);
EXEC_OP_WAIT:
yield info->async_wait(std::move(self));
BOOST_ASIO_CORO_YIELD
info->async_wait(std::move(self));
BOOST_ASSERT(ec == boost::asio::error::operation_aborted);
if (info->get_action() == Conn::req_info::action::stop) {
if (info->stop_requested()) {
// Don't have to call remove_request as it has already
// been by cancel(exec).
return self.complete(ec, 0);
@@ -179,9 +139,20 @@ EXEC_OP_WAIT:
if (is_cancelled(self)) {
if (info->is_written()) {
self.get_cancellation_state().clear();
goto EXEC_OP_WAIT; // Too late, can't cancel.
using c_t = boost::asio::cancellation_type;
auto const c = self.get_cancellation_state().cancelled();
if ((c & c_t::terminal) != c_t::none) {
// Cancellation requires closing the connection
// otherwise it stays in inconsistent state.
conn->cancel(operation::run);
return self.complete(ec, 0);
} else {
// Can't implement other cancelation types, ignoring.
self.get_cancellation_state().clear();
goto EXEC_OP_WAIT;
}
} else {
// Cancelation can be honored.
conn->remove_request(info);
self.complete(ec, 0);
return;
@@ -199,16 +170,9 @@ EXEC_OP_WAIT:
BOOST_ASSERT(!conn->reqs_.empty());
BOOST_ASSERT(conn->reqs_.front() != nullptr);
BOOST_ASSERT(conn->cmds_ != 0);
yield
BOOST_ASIO_CORO_YIELD
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) {
return self.complete(ec, {});
}
AEDIS_CHECK_OP1(;);
read_size = n;
@@ -240,12 +204,12 @@ struct run_op {
, boost::system::error_code ec0 = {}
, boost::system::error_code ec1 = {})
{
reenter (coro)
BOOST_ASIO_CORO_REENTER (coro)
{
conn->write_buffer_.clear();
conn->cmds_ = 0;
yield
BOOST_ASIO_CORO_YIELD
boost::asio::experimental::make_parallel_group(
[this](auto token) { return conn->reader(token);},
[this](auto token) { return conn->writer(token);}
@@ -279,13 +243,13 @@ struct writer_op {
{
boost::ignore_unused(n);
reenter (coro) for (;;)
BOOST_ASIO_CORO_REENTER (coro) for (;;)
{
while (!conn->reqs_.empty() && conn->cmds_ == 0 && conn->write_buffer_.empty()) {
conn->coalesce_requests();
yield
BOOST_ASIO_CORO_YIELD
boost::asio::async_write(conn->next_layer(), boost::asio::buffer(conn->write_buffer_), std::move(self));
AEDIS_CHECK_OP0(conn->cancel(operation::run));
AEDIS_CHECK_OP0(conn->cancel(operation::run););
conn->on_write();
@@ -298,7 +262,8 @@ struct writer_op {
}
}
yield conn->writer_timer_.async_wait(std::move(self));
BOOST_ASIO_CORO_YIELD
conn->writer_timer_.async_wait(std::move(self));
if (!conn->is_open() || is_cancelled(self)) {
// Notice this is not an error of the op, stoping was
// requested from the outside, so we complete with
@@ -322,9 +287,9 @@ struct reader_op {
{
boost::ignore_unused(n);
reenter (coro) for (;;)
BOOST_ASIO_CORO_REENTER (coro) for (;;)
{
yield
BOOST_ASIO_CORO_YIELD
boost::asio::async_read_until(
conn->next_layer(),
conn->make_dynamic_buffer(),
@@ -335,7 +300,7 @@ struct reader_op {
return self.complete({}); // EOFINAE: EOF is not an error.
}
AEDIS_CHECK_OP0(conn->cancel(operation::run));
AEDIS_CHECK_OP0(conn->cancel(operation::run););
// We handle unsolicited events in the following way
//
@@ -358,25 +323,22 @@ struct reader_op {
if (resp3::to_type(conn->read_buffer_.front()) == resp3::type::push
|| conn->reqs_.empty()
|| (!conn->reqs_.empty() && conn->reqs_.front()->get_number_of_commands() == 0)) {
yield async_send_receive(conn->push_channel_, std::move(self));
if (!conn->is_open() || ec || is_cancelled(self)) {
conn->cancel(operation::run);
self.complete(boost::asio::error::basic_errors::operation_aborted);
return;
}
BOOST_ASIO_CORO_YIELD
conn->guarded_op_.async_run(std::move(self));
} else {
BOOST_ASSERT(conn->cmds_ != 0);
BOOST_ASSERT(!conn->reqs_.empty());
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(boost::asio::error::basic_errors::operation_aborted);
return;
}
BOOST_ASIO_CORO_YIELD
conn->read_timer_.async_wait(std::move(self));
ec = {};
}
if (!conn->is_open() || ec || is_cancelled(self)) {
conn->cancel(operation::run);
self.complete(boost::asio::error::basic_errors::operation_aborted);
return;
}
}
}
@@ -384,5 +346,4 @@ struct reader_op {
} // aedis::detail
#include <boost/asio/unyield.hpp>
#endif // AEDIS_CONNECTION_OPS_HPP

View File

@@ -0,0 +1,111 @@
/* 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_DETAIL_GUARDED_OPERATION_HPP
#define AEDIS_DETAIL_GUARDED_OPERATION_HPP
#include <boost/asio/experimental/channel.hpp>
namespace aedis::detail {
template <class Executor>
struct send_receive_op {
using channel_type = boost::asio::experimental::channel<Executor, void(boost::system::error_code, std::size_t)>;
channel_type* channel;
boost::asio::coroutine coro{};
template <class Self>
void operator()(Self& self, boost::system::error_code ec = {})
{
BOOST_ASIO_CORO_REENTER (coro)
{
BOOST_ASIO_CORO_YIELD
channel->async_send(boost::system::error_code{}, 0, std::move(self));
AEDIS_CHECK_OP0(;);
BOOST_ASIO_CORO_YIELD
channel->async_send(boost::system::error_code{}, 0, std::move(self));
AEDIS_CHECK_OP0(;);
self.complete({});
}
}
};
template <class Executor, class Op>
struct wait_op {
using channel_type = boost::asio::experimental::channel<Executor, void(boost::system::error_code, std::size_t)>;
channel_type* channel;
Op op;
std::size_t res = 0;
boost::asio::coroutine coro{};
template <class Self>
void
operator()( Self& self
, boost::system::error_code ec = {}
, std::size_t n = 0)
{
BOOST_ASIO_CORO_REENTER (coro)
{
BOOST_ASIO_CORO_YIELD
channel->async_receive(std::move(self));
AEDIS_CHECK_OP1(;);
BOOST_ASIO_CORO_YIELD
std::move(op)(std::move(self));
AEDIS_CHECK_OP1(channel->cancel(););
res = n;
BOOST_ASIO_CORO_YIELD
channel->async_receive(std::move(self));
AEDIS_CHECK_OP1(;);
self.complete({}, res);
return;
}
}
};
template <class Executor>
class guarded_operation {
public:
using executor_type = Executor;
guarded_operation(executor_type ex) : channel_{ex} {}
template <class CompletionToken>
auto async_run(CompletionToken&& token)
{
return boost::asio::async_compose
< CompletionToken
, void(boost::system::error_code)
>(send_receive_op<executor_type>{&channel_}, token, channel_);
}
template <class Op, class CompletionToken>
auto async_wait(Op&& op, CompletionToken token)
{
return boost::asio::async_compose
< CompletionToken
, void(boost::system::error_code, std::size_t)
>(wait_op<executor_type, Op>{&channel_, std::move(op)}, token, channel_);
}
void cancel() {channel_.cancel();}
private:
using channel_type = boost::asio::experimental::channel<executor_type, void(boost::system::error_code, std::size_t)>;
template <class> friend struct send_receive_op;
template <class, class> friend struct wait_op;
channel_type channel_;
};
} // aedis::detail
#endif // AEDIS_DETAIL_GUARDED_OPERATION_HPP

View File

@@ -1,62 +0,0 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#ifndef AEDIS_NET_HPP
#define AEDIS_NET_HPP
#include <array>
#include <boost/system.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/connect.hpp>
#include <boost/assert.hpp>
#include <boost/asio/experimental/parallel_group.hpp>
#include <boost/asio/yield.hpp>
namespace aedis::detail {
template <class Channel>
struct send_receive_op {
Channel* channel;
boost::asio::coroutine coro{};
template <class Self>
void operator()( Self& self
, boost::system::error_code ec = {}
, std::size_t = 0)
{
reenter (coro)
{
yield
channel->async_send(boost::system::error_code{}, 0, std::move(self));
AEDIS_CHECK_OP1();
yield
channel->async_receive(std::move(self));
AEDIS_CHECK_OP1();
self.complete({}, 0);
}
}
};
template <
class Channel,
class CompletionToken =
boost::asio::default_completion_token_t<typename Channel::executor_type>
>
auto async_send_receive(Channel& channel, CompletionToken&& token = CompletionToken{})
{
return boost::asio::async_compose
< CompletionToken
, void(boost::system::error_code, std::size_t)
>(send_receive_op<Channel>{&channel}, token, channel);
}
} // aedis::detail
#include <boost/asio/unyield.hpp>
#endif // AEDIS_NET_HPP

View File

@@ -5,6 +5,7 @@
*/
#include <aedis/error.hpp>
#include <boost/assert.hpp>
namespace aedis {
namespace detail {

View File

@@ -4,22 +4,153 @@
* accompanying file LICENSE.txt)
*/
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/home/x3.hpp>
#include <aedis/resp3/detail/parser.hpp>
#include <aedis/resp3/type.hpp>
#include <aedis/error.hpp>
#include <boost/assert.hpp>
#include <charconv>
namespace aedis::resp3::detail {
auto parse_uint(char const* data, std::size_t size, boost::system::error_code& ec) -> std::size_t
void to_int(int_type& i, std::string_view sv, boost::system::error_code& ec)
{
static constexpr boost::spirit::x3::uint_parser<std::size_t, 10> p{};
std::size_t ret = 0;
if (!parse(data, data + size, p, ret))
auto const res = std::from_chars(sv.data(), sv.data() + std::size(sv), i);
if (res.ec != std::errc())
ec = error::not_a_number;
return ret;
}
parser::parser()
{
sizes_[0] = 2; // The sentinel must be more than 1.
}
auto
parser::consume(
char const* data,
std::size_t n,
boost::system::error_code& ec) -> std::pair<node_type, std::size_t>
{
node_type ret;
if (bulk_expected()) {
n = bulk_length_ + 2;
ret = {bulk_, 1, depth_, {data, bulk_length_}};
bulk_ = type::invalid;
--sizes_[depth_];
} else if (sizes_[depth_] != 0) {
auto const t = to_type(*data);
switch (t) {
case type::streamed_string_part:
{
to_int(bulk_length_ , std::string_view{data + 1, n - 3}, ec);
if (ec)
return std::make_pair(node_type{}, 0);
if (bulk_length_ == 0) {
ret = {type::streamed_string_part, 1, depth_, {}};
sizes_[depth_] = 0; // We are done.
bulk_ = type::invalid;
} else {
bulk_ = type::streamed_string_part;
}
} break;
case type::blob_error:
case type::verbatim_string:
case type::blob_string:
{
if (data[1] == '?') {
// NOTE: This can only be triggered with blob_string.
// Trick: A streamed string is read as an aggregate
// of infinite lenght. When the streaming is done
// the server is supposed to send a part with length
// 0.
sizes_[++depth_] = (std::numeric_limits<std::size_t>::max)();
ret = {type::streamed_string, 0, depth_, {}};
} else {
to_int(bulk_length_ , std::string_view{data + 1, n - 3} , ec);
if (ec)
return std::make_pair(node_type{}, 0);
bulk_ = t;
}
} break;
case type::boolean:
{
if (n == 3) {
ec = error::empty_field;
return std::make_pair(node_type{}, 0);
}
if (data[1] != 'f' && data[1] != 't') {
ec = error::unexpected_bool_value;
return std::make_pair(node_type{}, 0);
}
ret = {t, 1, depth_, {data + 1, n - 3}};
--sizes_[depth_];
} break;
case type::doublean:
case type::big_number:
case type::number:
{
if (n == 3) {
ec = error::empty_field;
return std::make_pair(node_type{}, 0);
}
ret = {t, 1, depth_, {data + 1, n - 3}};
--sizes_[depth_];
} break;
case type::simple_error:
case type::simple_string:
{
ret = {t, 1, depth_, {&data[1], n - 3}};
--sizes_[depth_];
} break;
case type::null:
{
ret = {type::null, 1, depth_, {}};
--sizes_[depth_];
} break;
case type::push:
case type::set:
case type::array:
case type::attribute:
case type::map:
{
int_type l = -1;
to_int(l, std::string_view{data + 1, n - 3}, ec);
if (ec)
return std::make_pair(node_type{}, 0);
ret = {t, l, depth_, {}};
if (l == 0) {
--sizes_[depth_];
} else {
if (depth_ == max_embedded_depth) {
ec = error::exceeeds_max_nested_depth;
return std::make_pair(node_type{}, 0);
}
++depth_;
sizes_[depth_] = l * element_multiplicity(t);
}
} break;
default:
{
ec = error::invalid_data_type;
return std::make_pair(node_type{}, 0);
}
}
}
while (sizes_[depth_] == 0) {
--depth_;
--sizes_[depth_];
}
return std::make_pair(ret, n);
}
} // aedis::resp3::detail

View File

@@ -7,28 +7,22 @@
#ifndef AEDIS_RESP3_PARSER_HPP
#define AEDIS_RESP3_PARSER_HPP
#include <aedis/resp3/node.hpp>
#include <array>
#include <limits>
#include <system_error>
#include <boost/assert.hpp>
#include <boost/utility/string_view.hpp>
#include <aedis/error.hpp>
#include <aedis/resp3/node.hpp>
#include <string_view>
#include <cstdint>
namespace aedis::resp3::detail {
auto parse_uint(char const* data, std::size_t size, boost::system::error_code& ec) -> std::size_t;
using int_type = std::uint64_t;
template <class ResponseAdapter>
class parser {
private:
using node_type = node<boost::string_view>;
using node_type = node<std::string_view>;
static constexpr std::size_t max_embedded_depth = 5;
ResponseAdapter adapter_;
// The current depth. Simple data types will have depth 0, whereas
// the elements of aggregates will have depth 1. Embedded types
// will have increasing depth.
@@ -40,182 +34,34 @@ private:
std::array<std::size_t, max_embedded_depth + 1> sizes_ = {{1}};
// Contains the length expected in the next bulk read.
std::size_t bulk_length_ = (std::numeric_limits<std::size_t>::max)();
int_type bulk_length_ = (std::numeric_limits<unsigned long>::max)();
// The type of the next bulk. Contains type::invalid if no bulk is
// expected.
type bulk_ = type::invalid;
public:
explicit parser(ResponseAdapter adapter)
: adapter_{adapter}
{
sizes_[0] = 2; // The sentinel must be more than 1.
}
parser();
// Returns the number of bytes that have been consumed.
auto
consume(char const* data, std::size_t n, boost::system::error_code& ec) -> std::size_t
{
if (bulk_ != type::invalid) {
n = bulk_length_ + 2;
switch (bulk_) {
case type::streamed_string_part:
{
BOOST_ASSERT(bulk_length_ != 0);
adapter_({bulk_, 1, depth_, {data, bulk_length_}}, ec);
if (ec)
return 0;
} break;
default:
{
adapter_({bulk_, 1, depth_, {data, bulk_length_}}, ec);
if (ec)
return 0;
}
}
bulk_ = type::invalid;
--sizes_[depth_];
} else if (sizes_[depth_] != 0) {
auto const t = to_type(*data);
switch (t) {
case type::streamed_string_part:
{
bulk_length_ = parse_uint(data + 1, n - 2, ec);
if (ec)
return 0;
if (bulk_length_ == 0) {
adapter_({type::streamed_string_part, 1, depth_, {}}, ec);
sizes_[depth_] = 0; // We are done.
} else {
bulk_ = type::streamed_string_part;
}
} break;
case type::blob_error:
case type::verbatim_string:
case type::blob_string:
{
if (data[1] == '?') {
// NOTE: This can only be triggered with blob_string.
// Trick: A streamed string is read as an aggregate
// of infinite lenght. When the streaming is done
// the server is supposed to send a part with length
// 0.
sizes_[++depth_] = (std::numeric_limits<std::size_t>::max)();
} else {
bulk_length_ = parse_uint(data + 1, n - 2, ec);
if (ec)
return 0;
bulk_ = t;
}
} break;
case type::boolean:
{
if (n == 3) {
ec = error::empty_field;
return 0;
}
if (data[1] != 'f' && data[1] != 't') {
ec = error::unexpected_bool_value;
return 0;
}
adapter_({t, 1, depth_, {data + 1, n - 3}}, ec);
if (ec)
return 0;
--sizes_[depth_];
} break;
case type::doublean:
case type::big_number:
case type::number:
{
if (n == 3) {
ec = error::empty_field;
return 0;
}
adapter_({t, 1, depth_, {data + 1, n - 3}}, ec);
if (ec)
return 0;
--sizes_[depth_];
} break;
case type::simple_error:
case type::simple_string:
{
adapter_({t, 1, depth_, {&data[1], n - 3}}, ec);
if (ec)
return 0;
--sizes_[depth_];
} break;
case type::null:
{
adapter_({type::null, 1, depth_, {}}, ec);
if (ec)
return 0;
--sizes_[depth_];
} break;
case type::push:
case type::set:
case type::array:
case type::attribute:
case type::map:
{
auto const l = parse_uint(data + 1, n - 2, ec);
if (ec)
return 0;
adapter_({t, l, depth_, {}}, ec);
if (ec)
return 0;
if (l == 0) {
--sizes_[depth_];
} else {
if (depth_ == max_embedded_depth) {
ec = error::exceeeds_max_nested_depth;
return 0;
}
++depth_;
sizes_[depth_] = l * element_multiplicity(t);
}
} break;
default:
{
ec = error::invalid_data_type;
return 0;
}
}
}
while (sizes_[depth_] == 0) {
--depth_;
--sizes_[depth_];
}
return n;
}
consume(
char const* data,
std::size_t n,
boost::system::error_code& ec) -> std::pair<node_type, std::size_t>;
// Returns true when the parser is done with the current message.
[[nodiscard]] auto done() const noexcept
{ return depth_ == 0 && bulk_ == type::invalid; }
// The bulk type expected in the next read. If none is expected returns
// type::invalid.
[[nodiscard]] auto bulk() const noexcept { return bulk_; }
// The bulk type expected in the next read. If none is expected
// returns type::invalid.
[[nodiscard]] auto bulk_expected() const noexcept -> bool
{ return bulk_ != type::invalid; }
// The length expected in the the next bulk.
[[nodiscard]] auto bulk_length() const noexcept { return bulk_length_; }
[[nodiscard]] auto bulk_length() const noexcept
{ return bulk_length_; }
};
} // detail::resp3::aedis

View File

@@ -7,15 +7,15 @@
#ifndef AEDIS_RESP3_READ_OPS_HPP
#define AEDIS_RESP3_READ_OPS_HPP
#include <aedis/resp3/detail/parser.hpp>
#include <boost/assert.hpp>
#include <boost/asio/read.hpp>
#include <boost/asio/read_until.hpp>
#include <boost/asio/coroutine.hpp>
#include <boost/core/ignore_unused.hpp>
#include <boost/utility/string_view.hpp>
#include <aedis/resp3/detail/parser.hpp>
#include <boost/asio/yield.hpp>
#include <string_view>
namespace aedis::detail
{
@@ -28,14 +28,14 @@ auto is_cancelled(T const& self)
#define AEDIS_CHECK_OP0(X)\
if (ec || aedis::detail::is_cancelled(self)) {\
X;\
X\
self.complete(!!ec ? ec : boost::asio::error::operation_aborted);\
return;\
}
#define AEDIS_CHECK_OP1(X)\
if (ec || aedis::detail::is_cancelled(self)) {\
X;\
X\
self.complete(!!ec ? ec : boost::asio::error::operation_aborted, {});\
return;\
}
@@ -43,7 +43,7 @@ auto is_cancelled(T const& self)
namespace aedis::resp3::detail {
struct ignore_response {
void operator()(node<boost::string_view> nd, boost::system::error_code& ec)
void operator()(node<std::string_view> nd, boost::system::error_code& ec)
{
switch (nd.data_type) {
case resp3::type::simple_error: ec = error::resp3_simple_error; return;
@@ -61,7 +61,8 @@ class parse_op {
private:
AsyncReadStream& stream_;
DynamicBuffer buf_;
parser<ResponseAdapter> parser_;
parser parser_;
ResponseAdapter adapter_;
std::size_t consumed_ = 0;
std::size_t buffer_size_ = 0;
boost::asio::coroutine coro_{};
@@ -70,7 +71,7 @@ public:
parse_op(AsyncReadStream& stream, DynamicBuffer buf, ResponseAdapter adapter)
: stream_ {stream}
, buf_ {std::move(buf)}
, parser_ {std::move(adapter)}
, adapter_ {std::move(adapter)}
{ }
template <class Self>
@@ -78,11 +79,11 @@ public:
, boost::system::error_code ec = {}
, std::size_t n = 0)
{
reenter (coro_) for (;;) {
if (parser_.bulk() == type::invalid) {
yield
BOOST_ASIO_CORO_REENTER (coro_) for (;;) {
if (!parser_.bulk_expected()) {
BOOST_ASIO_CORO_YIELD
boost::asio::async_read_until(stream_, buf_, "\r\n", std::move(self));
AEDIS_CHECK_OP1();
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
@@ -95,27 +96,31 @@ public:
buffer_size_ = buf_.size();
buf_.grow(parser_.bulk_length() + 2 - buffer_size_);
yield
BOOST_ASIO_CORO_YIELD
boost::asio::async_read(
stream_,
buf_.data(buffer_size_, parser_.bulk_length() + 2 - buffer_size_),
boost::asio::transfer_all(),
std::move(self));
AEDIS_CHECK_OP1();
AEDIS_CHECK_OP1(;);
}
n = parser_.bulk_length() + 2;
BOOST_ASSERT(buf_.size() >= n);
}
n = parser_.consume(static_cast<char const*>(buf_.data(0, n).data()), n, ec);
if (ec) {
self.complete(ec, 0);
return;
auto const res = parser_.consume(static_cast<char const*>(buf_.data(0, n).data()), n, ec);
if (ec)
return self.complete(ec, 0);
if (!parser_.bulk_expected()) {
adapter_(res.first, ec);
if (ec)
return self.complete(ec, 0);
}
buf_.consume(n);
consumed_ += n;
buf_.consume(res.second);
consumed_ += res.second;
if (parser_.done()) {
self.complete({}, consumed_);
return;
@@ -126,5 +131,4 @@ public:
} // aedis::resp3::detail
#include <boost/asio/unyield.hpp>
#endif // AEDIS_RESP3_READ_OPS_HPP

View File

@@ -6,9 +6,11 @@
#include <aedis/resp3/request.hpp>
#include <string_view>
namespace aedis::resp3::detail {
auto has_push_response(boost::string_view cmd) -> bool
auto has_response(std::string_view cmd) -> bool
{
if (cmd == "SUBSCRIBE") return true;
if (cmd == "PSUBSCRIBE") return true;
@@ -16,9 +18,4 @@ 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

@@ -4,8 +4,8 @@
* accompanying file LICENSE.txt)
*/
#include <boost/assert.hpp>
#include <aedis/resp3/type.hpp>
#include <boost/assert.hpp>
namespace aedis::resp3 {
@@ -27,6 +27,7 @@ auto to_string(type t) -> char const*
case type::blob_error: return "blob_error";
case type::verbatim_string: return "verbatim_string";
case type::blob_string: return "blob_string";
case type::streamed_string: return "streamed_string";
case type::streamed_string_part: return "streamed_string_part";
default: return "invalid";
}

View File

@@ -9,19 +9,38 @@
#include <aedis/resp3/type.hpp>
#include <string>
#include <vector>
namespace aedis::resp3 {
/** \brief A node in the response tree.
* \ingroup high-level-api
*
* Redis responses are the pre-order view of the response tree (see
* https://en.wikipedia.org/wiki/Tree_traversal#Pre-order,_NLR).
* RESP3 can contain recursive data structures: A map of sets of
* vector of etc. As it is parsed each element is passed to user
* callbacks (push parser), the `aedis::adapt` function. The signature of this
* callback is `f(resp3::node<std::string_view)`. This class is called a node
* because it can be seen as the element of the response tree. It
* is a template so that users can use it with owing strings e.g.
* `std::string` or `boost::static_string` etc. if they decide to use a node as
* response type, for example, to read a non-aggregate data-type use
*
* \remark Any Redis response can be received in an array of nodes,
* for example \c std::vector<node<std::string>>.
* ```cpp
* resp3::node<std::string> resp;
* co_await conn->async_exec(req, adapt(resp));
* ```
*
* for an aggregate use instead
*
* ```cpp
* std::vector<resp3::node<std::string>> resp; co_await
* conn->async_exec(req, adapt(resp));
* ```
*
* The vector will contain the
* [pre-order](https://en.wikipedia.org/wiki/Tree_traversal#Pre-order,_NLR)
* view of the response tree. Any Redis response can be received in
* an array of nodes as shown above.
*
* \tparam String A `std::string`-like type.
*/
template <class String>
struct node {
@@ -38,27 +57,6 @@ struct node {
String value{};
};
/** @brief Converts the node to a string.
* @relates node
*
* @param in The node object.
*/
template <class String>
auto to_string(node<String> const& in)
{
std::string out;
out += std::to_string(in.depth);
out += '\t';
out += to_string(in.data_type);
out += '\t';
out += std::to_string(in.aggregate_size);
out += '\t';
if (!is_aggregate(in.data_type))
out.append(in.value.data(), in.value.size());
return out;
}
/** @brief Compares a node for equality.
* @relates node
*
@@ -74,21 +72,6 @@ auto operator==(node<String> const& a, node<String> const& b)
&& a.value == b.value;
};
/** @brief Writes the node string to the stream.
* @relates node
*
* @param os Output stream.
* @param node Node object.
*
* \remark Binary data is not converted to text.
*/
template <class String>
auto operator<<(std::ostream& os, node<String> const& node) -> std::ostream&
{
os << to_string(node);
return os;
}
} // aedis::resp3
#endif // AEDIS_RESP3_NODE_HPP

View File

@@ -59,11 +59,11 @@ read(
ResponseAdapter adapter,
boost::system::error_code& ec) -> std::size_t
{
detail::parser<ResponseAdapter> p {adapter};
detail::parser p;
std::size_t n = 0;
std::size_t consumed = 0;
do {
if (p.bulk() == type::invalid) {
if (!p.bulk_expected()) {
n = boost::asio::read_until(stream, buf, "\r\n", ec);
if (ec)
return 0;
@@ -81,12 +81,18 @@ read(
}
auto const* data = static_cast<char const*>(buf.data(0, n).data());
n = p.consume(data, n, ec);
auto const res = p.consume(data, n, ec);
if (ec)
return 0;
buf.consume(n);
consumed += n;
if (!p.bulk_expected()) {
adapter(res.first, ec);
if (ec)
return 0;
}
buf.consume(res.second);
consumed += res.second;
} while (!p.done());
return consumed;

View File

@@ -7,15 +7,12 @@
#ifndef AEDIS_RESP3_REQUEST_HPP
#define AEDIS_RESP3_REQUEST_HPP
#include <aedis/resp3/type.hpp>
#include <string>
#include <tuple>
#include <memory_resource>
#include <boost/hana.hpp>
#include <boost/utility/string_view.hpp>
#include <aedis/resp3/type.hpp>
// NOTE: Consider detecting tuples in the type in the parameter pack
// to calculate the header size correctly.
//
@@ -46,7 +43,7 @@ constexpr char const* separator = "\r\n";
* See more in @ref serialization.
*/
template <class Request>
void to_bulk(Request& to, boost::string_view data)
void to_bulk(Request& to, std::string_view data)
{
auto const str = std::to_string(data.size());
@@ -61,14 +58,12 @@ template <class Request, class T, typename = typename std::enable_if<std::is_int
void to_bulk(Request& to, T n)
{
auto const s = std::to_string(n);
to_bulk(to, boost::string_view{s});
to_bulk(to, std::string_view{s});
}
namespace detail {
auto has_push_response(boost::string_view cmd) -> bool;
auto is_hello(boost::string_view cmd) -> bool;
auto has_response(std::string_view cmd) -> bool;
template <class T>
struct add_bulk_impl {
@@ -80,6 +75,21 @@ struct add_bulk_impl {
}
};
template <class ...Ts>
struct add_bulk_impl<std::tuple<Ts...>> {
template <class Request>
static void add(Request& to, std::tuple<Ts...> const& t)
{
auto f = [&](auto const&... vs)
{
using namespace aedis::resp3;
(to_bulk(to, vs), ...);
};
std::apply(f, t);
}
};
template <class U, class V>
struct add_bulk_impl<std::pair<U, V>> {
template <class Request>
@@ -91,23 +101,6 @@ struct add_bulk_impl<std::pair<U, V>> {
}
};
template <class ...Ts>
struct add_bulk_impl<boost::hana::tuple<Ts...>> {
template <class Request>
static void add(Request& to, boost::hana::tuple<Ts...> const& from)
{
using boost::hana::for_each;
// Fold expressions is C++17 so we use hana.
//(detail::add_bulk(*request_, args), ...);
for_each(from, [&](auto const& e) {
using namespace aedis::resp3;
to_bulk(to, e);
});
}
};
template <class Request>
void add_header(Request& to, type t, std::size_t size)
{
@@ -138,7 +131,7 @@ struct bulk_counter<std::pair<T, U>> {
};
template <class Request>
void add_blob(Request& to, boost::string_view blob)
void add_blob(Request& to, std::string_view blob)
{
to.append(std::cbegin(blob), std::cend(blob));
to += separator;
@@ -171,43 +164,43 @@ void add_separator(Request& to)
*
* \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
* `aedis::connection::async_exec` will fail if the connection is
* lost while the request is pending. The default
* behaviour is not to close requests.
/** \brief If `true`
* `aedis::connection::async_exec` will complete with error if the
* connection is lost. Affects only requests that haven't been
* sent yet.
*/
bool cancel_on_connection_lost = false;
bool cancel_on_connection_lost = true;
/** \brief If true this request will be coalesced with other requests,
* see https://redis.io/topics/pipelining. If false, this
* request will be sent individually.
/** \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 set to true, requests started with
* `aedis::connection::async_exec` will fail if the call happens
* before the connection with Redis was stablished.
/** \brief If `true` the request will complete with
* aedis::error::not_connected if `async_exec` is called before
* the connection with Redis was established.
*/
bool cancel_if_not_connected = false;
/** \brief If true, the implementation will resend this
* request if it remained unresponded when
* `aedis::connection::async_run` completed. Has effect only if
* cancel_on_connection_lost is true.
/** \brief If `false` `aedis::connection::async_exec` will not
* automatically cancel this request if the connection is lost.
* Affects only requests that have been written to the socket
* but remained unresponded when `aedis::connection::async_run`
* completed.
*/
bool retry = true;
bool cancel_if_unresponded = true;
/** \brief If this request has a HELLO command and this flag is
* set to true, the `aedis::connection` will move it to the
* front of the queue of awaiting requests. This makes it
* possible to send HELLO and authenticate before other
* commands are sent.
/** \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;
};
@@ -218,7 +211,7 @@ public:
* \param resource Memory resource.
*/
explicit
request(config cfg = config{false, true, false, true, true},
request(config cfg = config{true, true, false, true, true},
std::pmr::memory_resource* resource = std::pmr::get_default_resource())
: cfg_{cfg}, payload_(resource) {}
@@ -239,7 +232,7 @@ public:
commands_ = 0;
}
/// Calls std::string::reserve on the internal storage.
/// Calls std::pmr::string::reserve on the internal storage.
void reserve(std::size_t new_cap = 0)
{ payload_.reserve(new_cap); }
@@ -265,16 +258,14 @@ public:
* \param args Command arguments.
*/
template <class... Ts>
void push(boost::string_view cmd, Ts const&... args)
void push(std::string_view cmd, Ts const&... args)
{
using boost::hana::for_each;
using boost::hana::make_tuple;
using resp3::type;
auto constexpr pack_size = sizeof...(Ts);
detail::add_header(payload_, type::array, 1 + pack_size);
detail::add_bulk(payload_, cmd);
detail::add_bulk(payload_, make_tuple(args...));
detail::add_bulk(payload_, std::tie(std::forward<Ts const&>(args)...));
check_cmd(cmd);
}
@@ -301,7 +292,7 @@ public:
* \param end Iterator to the end of the range.
*/
template <class Key, class ForwardIterator>
void push_range(boost::string_view cmd, Key const& key, ForwardIterator begin, ForwardIterator end,
void push_range(std::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;
@@ -340,7 +331,7 @@ public:
* \param end Iterator to the end of the range.
*/
template <class ForwardIterator>
void push_range(boost::string_view cmd, ForwardIterator begin, ForwardIterator end,
void push_range(std::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;
@@ -369,7 +360,7 @@ 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(std::string_view cmd, Key const& key, Range const& range,
decltype(std::begin(range)) * = nullptr)
{
using std::begin;
@@ -385,7 +376,7 @@ 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(std::string_view cmd, Range const& range,
decltype(std::begin(range)) * = nullptr)
{
using std::begin;
@@ -394,12 +385,13 @@ public:
}
private:
void check_cmd(boost::string_view cmd)
void check_cmd(std::string_view cmd)
{
if (!detail::has_push_response(cmd))
if (!detail::has_response(cmd))
++commands_;
has_hello_priority_ = detail::is_hello(cmd) && cfg_.hello_with_priority;
if (cmd == "HELLO")
has_hello_priority_ = cfg_.hello_with_priority;
}
config cfg_;

View File

@@ -50,6 +50,8 @@ enum class type
/// Simple
blob_string,
/// Simple
streamed_string,
/// Simple
streamed_string_part,
/// Invalid
invalid

View File

@@ -7,11 +7,12 @@
#ifndef AEDIS_SSL_CONNECTION_HPP
#define AEDIS_SSL_CONNECTION_HPP
#include <chrono>
#include <memory>
#include <aedis/detail/connection_base.hpp>
#include <boost/asio/io_context.hpp>
#include <aedis/detail/connection_base.hpp>
#include <chrono>
#include <memory>
namespace aedis::ssl {
@@ -139,7 +140,6 @@ private:
template <class, class> friend class aedis::detail::connection_base;
template <class, class> friend struct aedis::detail::exec_op;
template <class, class> friend struct aedis::detail::exec_read_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;

View File

@@ -15,3 +15,9 @@ resolve(
net::ip::tcp::resolver resv{ioc};
return resv.resolve(host, port);
}
#ifdef BOOST_ASIO_HAS_CO_AWAIT
inline
auto redir(boost::system::error_code& ec)
{ return net::redirect_error(net::use_awaitable, ec); }
#endif // BOOST_ASIO_HAS_CO_AWAIT

View File

@@ -8,27 +8,20 @@
#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"
#include "../examples/common/common.hpp"
namespace net = boost::asio;
namespace resp3 = aedis::resp3;
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)
auto push_consumer(std::shared_ptr<connection> conn, int expected) -> net::awaitable<void>
{
int c = 0;
for (;;) {
@@ -37,7 +30,7 @@ net::awaitable<void> push_consumer(std::shared_ptr<connection> conn, int expecte
break;
}
request req;
resp3::request req;
req.push("HELLO", 3);
req.push("QUIT");
co_await conn->async_exec(req, adapt());
@@ -47,7 +40,7 @@ auto echo_session(std::shared_ptr<connection> conn, std::string id, int n) -> ne
{
auto ex = co_await net::this_coro::executor;
request req;
resp3::request req;
std::tuple<aedis::ignore, std::string> resp;
for (auto i = 0; i < n; ++i) {
@@ -57,8 +50,8 @@ auto echo_session(std::shared_ptr<connection> conn, std::string id, int n) -> ne
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);
co_await conn->async_exec(req, adapt(resp), redir(ec));
BOOST_CHECK_EQUAL(ec, boost::system::error_code{});
BOOST_CHECK_EQUAL(msg, std::get<1>(resp));
req.clear();
std::get<1>(resp).clear();
@@ -70,8 +63,8 @@ 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 const sessions = 500;
int const msgs = 1000;
int total = sessions * msgs;
net::co_spawn(ex, push_consumer(conn, total), net::detached);
@@ -79,16 +72,13 @@ auto async_echo_stress() -> net::awaitable<void>
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 connect(conn, "127.0.0.1", "6379");
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();
run(async_echo_stress());
}
#else

View File

@@ -20,15 +20,72 @@
// been already writen.
namespace net = boost::asio;
using aedis::resp3::request;
using aedis::adapt;
using connection = aedis::connection;
namespace resp3 = aedis::resp3;
using error_code = boost::system::error_code;
using connection = aedis::connection;
using aedis::adapt;
BOOST_AUTO_TEST_CASE(hello_priority)
{
resp3::request req1;
req1.get_config().coalesce = false;
req1.push("PING", "req1");
resp3::request req2;
req2.get_config().coalesce = false;
req2.get_config().hello_with_priority = false;
req2.push("HELLO", 3);
req2.push("PING", "req2");
req2.push("QUIT");
resp3::request req3;
req3.get_config().coalesce = false;
req3.get_config().hello_with_priority = true;
req3.push("HELLO", 3);
req3.push("PING", "req3");
net::io_context ioc;
auto const endpoints = resolve();
connection conn{ioc};
net::connect(conn.next_layer(), endpoints);
bool seen1 = false;
bool seen2 = false;
bool seen3 = false;
conn.async_exec(req1, adapt(), [&](auto ec, auto){
std::cout << "bbb" << std::endl;
BOOST_TEST(!ec);
BOOST_TEST(!seen2);
BOOST_TEST(seen3);
seen1 = true;
});
conn.async_exec(req2, adapt(), [&](auto ec, auto){
std::cout << "ccc" << std::endl;
BOOST_TEST(!ec);
BOOST_TEST(seen1);
BOOST_TEST(seen3);
seen2 = true;
});
conn.async_exec(req3, adapt(), [&](auto ec, auto){
std::cout << "ddd" << std::endl;
BOOST_TEST(!ec);
BOOST_TEST(!seen1);
BOOST_TEST(!seen2);
seen3 = true;
});
conn.async_run([](auto ec){
BOOST_TEST(!ec);
});
ioc.run();
}
BOOST_AUTO_TEST_CASE(wrong_response_data_type)
{
request req;
resp3::request req;
req.push("HELLO", 3);
req.push("QUIT");
@@ -52,7 +109,7 @@ BOOST_AUTO_TEST_CASE(wrong_response_data_type)
BOOST_AUTO_TEST_CASE(cancel_request_if_not_connected)
{
request req;
resp3::request req;
req.get_config().cancel_if_not_connected = true;
req.push("HELLO", 3);
req.push("PING");
@@ -65,45 +122,3 @@ BOOST_AUTO_TEST_CASE(cancel_request_if_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();
}

View File

@@ -8,48 +8,48 @@
#include <boost/asio.hpp>
#ifdef BOOST_ASIO_HAS_CO_AWAIT
#include <boost/system/errc.hpp>
#include <boost/asio/experimental/awaitable_operators.hpp>
#define BOOST_TEST_MODULE low level
#include <boost/test/included/unit_test.hpp>
#include <aedis.hpp>
#include <aedis/src.hpp>
#include "common.hpp"
#include "../examples/common/common.hpp"
// NOTE1: Sends hello separately. I have observed that if hello and
// blpop are sent toguether, Redis will send the response of hello
// right away, not waiting for blpop. That is why we have to send it
// separately here.
namespace net = boost::asio;
using aedis::resp3::request;
namespace resp3 = aedis::resp3;
using error_code = boost::system::error_code;
using namespace net::experimental::awaitable_operators;
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 async_ignore_explicit_cancel_of_req_written() -> 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 conn = std::make_shared<connection>(ex);
co_await connect(conn, "127.0.0.1", "6379");
auto async_cancel_exec(std::shared_ptr<connection> conn) -> net::awaitable<void>
{
auto ex = co_await net::this_coro::executor;
conn->async_run([conn](auto ec) {
BOOST_TEST(!ec);
});
net::steady_timer st{ex};
st.expires_after(std::chrono::seconds{1});
boost::system::error_code ec1;
// See NOTE1.
resp3::request req0;
req0.get_config().coalesce = false;
req0.push("HELLO", 3);
std::ignore = co_await conn->async_exec(req0, adapt(), net::use_awaitable);
request req1;
resp3::request req1;
req1.get_config().coalesce = false;
req1.push("HELLO", 3);
req1.push("BLPOP", "any", 3);
// Should not be canceled.
@@ -57,22 +57,23 @@ auto async_cancel_exec(std::shared_ptr<connection> conn) -> net::awaitable<void>
BOOST_TEST(!ec);
});
request req2;
resp3::request req2;
req2.get_config().coalesce = false;
req2.push("PING", "second");
// Should be canceled.
conn->async_exec(req1, adapt(), [](auto ec, auto){
conn->async_exec(req2, adapt(), [](auto ec, auto){
BOOST_CHECK_EQUAL(ec, net::error::basic_errors::operation_aborted);
});
// Will complete while BLPOP is pending.
boost::system::error_code ec1;
co_await st.async_wait(net::redirect_error(net::use_awaitable, ec1));
conn->cancel(operation::exec);
BOOST_TEST(!ec1);
request req3;
resp3::request req3;
req3.push("QUIT");
// Test whether the connection remains usable after a call to
@@ -82,44 +83,44 @@ auto async_cancel_exec(std::shared_ptr<connection> conn) -> net::awaitable<void>
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 ignore_implicit_cancel_of_req_written() -> net::awaitable<void>
{
auto ex = co_await net::this_coro::executor;
auto conn = std::make_shared<connection>(ex);
co_await connect(conn, "127.0.0.1", "6379");
// Calls async_run separately from the group of ops below to avoid
// having it canceled when the timer fires.
conn->async_run([conn](auto ec) {
BOOST_CHECK_EQUAL(ec, net::error::basic_errors::operation_aborted);
});
// See NOTE1.
resp3::request req0;
req0.get_config().coalesce = false;
req0.push("HELLO", 3);
std::ignore = co_await conn->async_exec(req0, adapt(), net::use_awaitable);
// Will be cancelled after it has been written but before the
// response arrives.
resp3::request req1;
req1.get_config().coalesce = false;
req1.push("BLPOP", "any", 3);
// Will be cancelled before it is written.
resp3::request req2;
req2.get_config().coalesce = false;
req2.get_config().cancel_on_connection_lost = true;
req2.push("PING");
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))
conn->async_exec(req1, adapt(), redir(ec1)) ||
conn->async_exec(req2, adapt(), redir(ec2)) ||
st.async_wait(redir(ec3))
);
BOOST_CHECK_EQUAL(ec1, net::error::basic_errors::operation_aborted);
@@ -127,19 +128,53 @@ auto async_ignore_cancel_of_written_req(std::shared_ptr<connection> conn) -> net
BOOST_TEST(!ec3);
}
BOOST_AUTO_TEST_CASE(ignore_cancel_of_written_req)
auto cancel_of_req_written_on_run_canceled() -> net::awaitable<void>
{
auto const endpoints = resolve();
resp3::request req0;
req0.get_config().coalesce = false;
req0.push("HELLO", 3);
net::io_context ioc;
auto conn = std::make_shared<connection>(ioc);
net::connect(conn->next_layer(), endpoints);
resp3::request req1;
req1.get_config().cancel_on_connection_lost = true;
req1.get_config().cancel_if_unresponded = true;
req1.push("BLPOP", "any", 0);
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();
auto ex = co_await net::this_coro::executor;
auto conn = std::make_shared<connection>(ex);
co_await connect(conn, "127.0.0.1", "6379");
net::steady_timer st{ex};
st.expires_after(std::chrono::seconds{1});
boost::system::error_code ec0, ec1, ec2, ec3;
co_await (
conn->async_exec(req0, adapt(), redir(ec0)) &&
(conn->async_exec(req1, adapt(), redir(ec1)) ||
conn->async_run(redir(ec2)) ||
st.async_wait(redir(ec3)))
);
BOOST_TEST(!ec0);
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(test_ignore_explicit_cancel_of_req_written)
{
run(async_ignore_explicit_cancel_of_req_written());
}
BOOST_AUTO_TEST_CASE(test_ignore_implicit_cancel_of_req_written)
{
run(ignore_implicit_cancel_of_req_written());
}
BOOST_AUTO_TEST_CASE(test_cancel_of_req_written_on_run_canceled)
{
run(cancel_of_req_written_on_run_canceled());
}
#else
int main(){}
#endif

148
tests/conn_exec_retry.cpp Normal file
View File

@@ -0,0 +1,148 @@
/* 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"
namespace net = boost::asio;
namespace resp3 = aedis::resp3;
using error_code = boost::system::error_code;
using connection = aedis::connection;
using aedis::adapt;
BOOST_AUTO_TEST_CASE(request_retry_false)
{
resp3::request req0;
req0.get_config().coalesce = false;
req0.get_config().cancel_on_connection_lost = true;
req0.push("HELLO", 3);
resp3::request req1;
req1.get_config().coalesce = true;
req1.get_config().cancel_on_connection_lost = true;
req1.push("BLPOP", "any", 0);
resp3::request req2;
req2.get_config().coalesce = true;
req2.get_config().cancel_on_connection_lost = false;
req2.get_config().cancel_if_unresponded = true;
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 third request to complete with error
// although it has cancel_on_connection_lost = false. The reason
// being is has already been written so
// cancel_on_connection_lost does not apply.
conn.cancel(aedis::operation::run);
});
auto const endpoints = resolve();
net::connect(conn.next_layer(), endpoints);
conn.async_exec(req0, adapt(), [](auto ec, auto){
BOOST_TEST(!ec);
});
conn.async_exec(req1, adapt(), [](auto ec, auto){
BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled);
});
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();
}
BOOST_AUTO_TEST_CASE(request_retry_true)
{
resp3::request req0;
req0.get_config().coalesce = false;
req0.get_config().cancel_on_connection_lost = true;
req0.push("HELLO", 3);
resp3::request req1;
req1.get_config().coalesce = true;
req1.get_config().cancel_on_connection_lost = true;
req1.push("BLPOP", "any", 0);
resp3::request req2;
req2.get_config().coalesce = true;
req2.get_config().cancel_on_connection_lost = false;
req2.get_config().cancel_if_unresponded = false;
req2.push("PING");
resp3::request req3;
req3.get_config().coalesce = true;
req3.get_config().cancel_on_connection_lost = true;
req3.get_config().cancel_if_unresponded = true;
req3.push("QUIT");
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 thrid request to not complete with error
// since it has cancel_if_unresponded = true and cancellation commes
// after it was written.
conn.cancel(aedis::operation::run);
});
auto const endpoints = resolve();
net::connect(conn.next_layer(), endpoints);
conn.async_exec(req0, adapt(), [](auto ec, auto){
BOOST_TEST(!ec);
});
conn.async_exec(req1, adapt(), [](auto ec, auto){
BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled);
});
conn.async_exec(req2, adapt(), [&](auto ec, auto){
BOOST_TEST(!ec);
conn.async_exec(req3, adapt(), [&](auto ec, auto){
BOOST_TEST(!ec);
});
});
conn.async_run([&](auto ec){
// The first cacellation.
BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled);
conn.reset_stream();
// Reconnects and runs again to test req3.
net::connect(conn.next_layer(), endpoints);
conn.async_run([&](auto ec){
std::cout << ec.message() << std::endl;
BOOST_TEST(!ec);
});
});
ioc.run();
}

View File

@@ -49,7 +49,6 @@ BOOST_AUTO_TEST_CASE(push_filtered_out)
});
conn.async_run([](auto ec){
std::cout << "===> " << ec.message() << std::endl;
BOOST_TEST(!ec);
});
@@ -101,7 +100,7 @@ net::awaitable<void> push_consumer1(connection& conn, bool& push_received)
struct adapter_error {
void
operator()(
std::size_t, aedis::resp3::node<boost::string_view> const&, boost::system::error_code& ec)
std::size_t, aedis::resp3::node<std::string_view> const&, boost::system::error_code& ec)
{
ec = aedis::error::incompatible_size;
}

View File

@@ -32,11 +32,14 @@ BOOST_AUTO_TEST_CASE(test_quit_no_coalesce)
connection conn{ioc};
net::connect(conn.next_layer(), endpoints);
request req1{{false, false}};
req1.push("HELLO", 3);
request req1;
req1.get_config().cancel_on_connection_lost = false;
req1.get_config().coalesce = false;
req1.push("PING");
request req2{{false, false}};
request req2;
req2.get_config().cancel_on_connection_lost = false;
req2.get_config().coalesce = false;
req2.push("QUIT");
conn.async_exec(req1, adapt(), [](auto ec, auto){

View File

@@ -31,7 +31,6 @@ BOOST_AUTO_TEST_CASE(test_quit_coalesce)
net::connect(conn.next_layer(), endpoints);
request req1{{false, true}};
req1.push("HELLO", 3);
req1.push("PING");
request req2{{false, true}};

View File

@@ -14,12 +14,12 @@
#include <aedis.hpp>
#include <aedis/src.hpp>
#include "common.hpp"
#include "../examples/common/common.hpp"
namespace net = boost::asio;
namespace resp3 = aedis::resp3;
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>
@@ -29,7 +29,7 @@ net::awaitable<void> test_reconnect_impl()
{
auto ex = co_await net::this_coro::executor;
request req;
resp3::request req;
req.push("QUIT");
auto const endpoints = resolve();
@@ -68,41 +68,42 @@ auto async_test_reconnect_timeout() -> net::awaitable<void>
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;
resp3::request req1;
req1.get_config().cancel_if_not_connected = false;
req1.get_config().cancel_on_connection_lost = true;
req1.get_config().cancel_if_unresponded = true;
req1.push("HELLO", 3);
req1.push("CLIENT", "PAUSE", 7000);
req1.push("BLPOP", "any", 0);
net::connect(conn->next_layer(), endpoints);
co_await connect(conn, "127.0.0.1", "6379");
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))
conn->async_exec(req1, adapt(), redir(ec1)) ||
conn->async_run(redir(ec2)) ||
st.async_wait(redir(ec3))
);
BOOST_TEST(!ec1);
//BOOST_TEST(!ec1);
BOOST_CHECK_EQUAL(ec2, boost::system::errc::errc_t::operation_canceled);
//BOOST_TEST(!ec3);
request req2;
resp3::request req2;
req2.get_config().cancel_if_not_connected = false;
req2.get_config().cancel_on_connection_lost = true;
req2.get_config().cancel_if_unresponded= true;
req2.push("HELLO", 3);
req2.push("QUIT");
net::connect(conn->next_layer(), endpoints);
co_await connect(conn, "127.0.0.1", "6379");
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))
);
std::cout << "ccc" << std::endl;
BOOST_CHECK_EQUAL(ec1, boost::system::errc::errc_t::operation_canceled);
BOOST_CHECK_EQUAL(ec2, boost::asio::error::basic_errors::operation_aborted);
@@ -110,9 +111,7 @@ auto async_test_reconnect_timeout() -> net::awaitable<void>
BOOST_AUTO_TEST_CASE(test_reconnect_and_idle)
{
net::io_context ioc;
net::co_spawn(ioc, async_test_reconnect_timeout(), net::detached);
ioc.run();
run(async_test_reconnect_timeout());
}
#else
int main(){}

View File

@@ -40,10 +40,7 @@ auto async_cancel_run_with_timer() -> net::awaitable<void>
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))
);
co_await (conn.async_run(redir(ec1)) || st.async_wait(redir(ec2)));
BOOST_CHECK_EQUAL(ec1, boost::asio::error::basic_errors::operation_aborted);
BOOST_TEST(!ec2);
@@ -69,10 +66,7 @@ async_check_cancellation_not_missed(int n, std::chrono::milliseconds ms) -> net:
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))
);
co_await (conn.async_run(redir(ec1)) || timer.async_wait(redir(ec2)));
BOOST_CHECK_EQUAL(ec1, boost::asio::error::basic_errors::operation_aborted);
std::cout << "Counter: " << i << std::endl;
}
@@ -174,6 +168,31 @@ BOOST_AUTO_TEST_CASE(reset_before_run_completes)
ioc.run();
}
using slave_operation = aedis::detail::guarded_operation<net::any_io_executor>;
auto master(std::shared_ptr<slave_operation> op) -> net::awaitable<void>
{
co_await op->async_run(net::use_awaitable);
}
auto slave(std::shared_ptr<slave_operation> op) -> net::awaitable<void>
{
net::steady_timer timer{co_await net::this_coro::executor};
timer.expires_after(std::chrono::seconds{1});
co_await op->async_wait(timer.async_wait(net::deferred), net::use_awaitable);
std::cout << "Kabuf" << std::endl;
}
BOOST_AUTO_TEST_CASE(slave_op)
{
net::io_context ioc;
auto op = std::make_shared<slave_operation>(ioc.get_executor());
net::co_spawn(ioc, master(op), net::detached);
net::co_spawn(ioc, slave(op), net::detached);
ioc.run();
}
#else
int main(){}
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -10,7 +10,7 @@
#define BOOST_TEST_MODULE low level
#include <boost/test/included/unit_test.hpp>
#include <aedis.hpp>
#include <aedis/resp3/request.hpp>
#include <aedis/src.hpp>
using aedis::resp3::request;