mirror of
https://github.com/boostorg/redis.git
synced 2026-01-24 18:22:09 +00:00
Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c88fcfb9ed | ||
|
|
a56bf982ab | ||
|
|
5d0ed0e986 | ||
|
|
15deaa637d | ||
|
|
bb8ff90351 | ||
|
|
7d4902369a | ||
|
|
607ca17a89 | ||
|
|
3849ba42fd | ||
|
|
56bcdb7914 | ||
|
|
73ad66eb93 | ||
|
|
9cf00d6a23 | ||
|
|
a00c9e7439 | ||
|
|
0520791100 | ||
|
|
14b376e36e | ||
|
|
4f9dcc7dc5 | ||
|
|
ad5dd8c30b | ||
|
|
842f864689 | ||
|
|
63f9b74502 | ||
|
|
801f60a026 | ||
|
|
c37fcb641c | ||
|
|
48c3f37168 | ||
|
|
3c63911802 | ||
|
|
1645881a44 | ||
|
|
730e06c38d | ||
|
|
cf3a79737d | ||
|
|
edb384c843 | ||
|
|
f745faddf8 | ||
|
|
927117568e | ||
|
|
1e7c176f92 | ||
|
|
449b5f7e7c | ||
|
|
75f91f3b11 | ||
|
|
b9a23568e3 | ||
|
|
4ac2509afa |
89
.github/workflows/ci.yml
vendored
89
.github/workflows/ci.yml
vendored
@@ -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: |
|
||||
|
||||
2
.github/workflows/coverage.yml
vendored
2
.github/workflows/coverage.yml
vendored
@@ -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: |
|
||||
|
||||
297
CMakeLists.txt
297
CMakeLists.txt
@@ -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
|
||||
#=======================================================================
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=""/>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
115
doc/doxygen-awesome-sidebar-only.css
Normal file
115
doc/doxygen-awesome-sidebar-only.css
Normal 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
2405
doc/doxygen-awesome.css
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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   
|
||||
<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>
|
||||
@@ -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>   <br> $projectbrief </td>
|
||||
<td style="width:100%"> $searchbox </td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--END TITLEAREA-->
|
||||
<!-- end header part -->
|
||||
7
examples/common/aedis.cpp
Normal file
7
examples/common/aedis.cpp
Normal 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>
|
||||
@@ -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)
|
||||
@@ -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
38
examples/common/main.cpp
Normal 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
101
examples/cpp17_intro.cpp
Normal 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;
|
||||
}
|
||||
|
||||
79
examples/cpp17_intro_sync.cpp
Normal file
79
examples/cpp17_intro_sync.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
61
examples/cpp20_intro.cpp
Normal 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)
|
||||
@@ -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;
|
||||
@@ -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");
|
||||
@@ -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)
|
||||
@@ -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);
|
||||
@@ -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))
|
||||
@@ -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();
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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
|
||||
|
||||
111
include/aedis/detail/guarded_operation.hpp
Normal file
111
include/aedis/detail/guarded_operation.hpp
Normal 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
|
||||
@@ -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
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
#include <aedis/error.hpp>
|
||||
#include <boost/assert.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace detail {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -50,6 +50,8 @@ enum class type
|
||||
/// Simple
|
||||
blob_string,
|
||||
/// Simple
|
||||
streamed_string,
|
||||
/// Simple
|
||||
streamed_string_part,
|
||||
/// Invalid
|
||||
invalid
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
148
tests/conn_exec_retry.cpp
Normal 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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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){
|
||||
|
||||
@@ -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}};
|
||||
|
||||
@@ -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(){}
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user