mirror of
https://github.com/boostorg/redis.git
synced 2026-01-23 06:02:09 +00:00
Compare commits
116 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
562075230f | ||
|
|
5dc677c6d8 | ||
|
|
395a167d48 | ||
|
|
f93f3cab58 | ||
|
|
df68fb0235 | ||
|
|
15e6883bc1 | ||
|
|
3816d1d358 | ||
|
|
bb15c70723 | ||
|
|
297b7f15eb | ||
|
|
ec6e99d99a | ||
|
|
8dc6db069b | ||
|
|
bac27c1770 | ||
|
|
feaaedc6c0 | ||
|
|
000ebddf44 | ||
|
|
268ea2c10f | ||
|
|
d8b67f6e23 | ||
|
|
ce1fa6a683 | ||
|
|
b8ede6ccb7 | ||
|
|
6dce1a9226 | ||
|
|
8566745d83 | ||
|
|
0b4906fcba | ||
|
|
2c8bb92071 | ||
|
|
770e224917 | ||
|
|
4fb2b20954 | ||
|
|
c01a57b6cb | ||
|
|
ea0b333c4d | ||
|
|
ba82c6cd84 | ||
|
|
4c298ddc6b | ||
|
|
690002b2f1 | ||
|
|
12899da4db | ||
|
|
b2c19df113 | ||
|
|
61f9a29ebc | ||
|
|
dc9b333f1e | ||
|
|
dac7bea54f | ||
|
|
da84321378 | ||
|
|
3295459700 | ||
|
|
dff8833fe3 | ||
|
|
3a03a43c06 | ||
|
|
b614826bb4 | ||
|
|
4e30a9d53d | ||
|
|
85ba41ae5a | ||
|
|
fd82204ba9 | ||
|
|
084e95cbc7 | ||
|
|
798f193f14 | ||
|
|
a23a3db9ac | ||
|
|
bca6511333 | ||
|
|
e2d642f34c | ||
|
|
d8607af669 | ||
|
|
223a9aa74b | ||
|
|
75433cd028 | ||
|
|
552c6cf6e4 | ||
|
|
16b9347c51 | ||
|
|
03558b1466 | ||
|
|
f8165bcb6f | ||
|
|
8aad27269c | ||
|
|
f11622e746 | ||
|
|
8249360a52 | ||
|
|
d00f26d3da | ||
|
|
480ec13119 | ||
|
|
4cc3fc59a1 | ||
|
|
11807c82b7 | ||
|
|
24a215d78b | ||
|
|
b7abe20703 | ||
|
|
225095944c | ||
|
|
a31d797e43 | ||
|
|
cca8d5d6dc | ||
|
|
6c5bee6920 | ||
|
|
c4714d0037 | ||
|
|
38bf2395af | ||
|
|
7511d6b4d8 | ||
|
|
ddc2815fe5 | ||
|
|
de6f5de655 | ||
|
|
8d454ada0e | ||
|
|
ebac88f2ca | ||
|
|
d26ecb65ca | ||
|
|
c57f97b8c1 | ||
|
|
37ab1e7387 | ||
|
|
54d448cad4 | ||
|
|
97428dedb3 | ||
|
|
83802f217a | ||
|
|
08140f9186 | ||
|
|
3ddb017edb | ||
|
|
20328cd423 | ||
|
|
6577ddbaab | ||
|
|
217d2bd87b | ||
|
|
f96dd22153 | ||
|
|
f1fd0cfa8c | ||
|
|
8728914109 | ||
|
|
e0041ac7ae | ||
|
|
317a185eb0 | ||
|
|
aa81200a8f | ||
|
|
55fc0e861c | ||
|
|
04271855b0 | ||
|
|
700e0c823e | ||
|
|
63c6465a4a | ||
|
|
c86422cf50 | ||
|
|
0168ed5faf | ||
|
|
7bffa252f4 | ||
|
|
0bb65599c4 | ||
|
|
edd538944f | ||
|
|
42880e788b | ||
|
|
bcc3917174 | ||
|
|
b08dd63192 | ||
|
|
76b6106caa | ||
|
|
ab68e8a31d | ||
|
|
2673557ce5 | ||
|
|
2a302dcb65 | ||
|
|
ffc4230368 | ||
|
|
59b5d35672 | ||
|
|
835a1decf4 | ||
|
|
3fb018ccc6 | ||
|
|
1fe4a87287 | ||
|
|
70cdff41e0 | ||
|
|
2edd9f3d87 | ||
|
|
fa4181b197 | ||
|
|
9e2cd8855e |
172
.clang-tidy
Normal file
172
.clang-tidy
Normal file
@@ -0,0 +1,172 @@
|
||||
---
|
||||
# Enable ALL the things! Except not really
|
||||
# misc-non-private-member-variables-in-classes: the options don't do anything
|
||||
Checks: "*,\
|
||||
-readability-*,\
|
||||
-readability-identifier-length,\
|
||||
-google-readability-todo,\
|
||||
-google-readability-namespace-comments,\
|
||||
-google-readability-braces-around-statements,\
|
||||
-hicpp-braces-around-statements,\
|
||||
-hicpp-named-parameter,\
|
||||
-hicpp-avoid-goto,\
|
||||
-google-build-using-namespace,\
|
||||
-altera-*,\
|
||||
-fuchsia-*,\
|
||||
fuchsia-multiple-inheritance,\
|
||||
-llvm-namespace-comment,\
|
||||
-llvm-header-guard,\
|
||||
-llvm-include-order,\
|
||||
-llvmlibc-*,\
|
||||
-misc-non-private-member-variables-in-classes,\
|
||||
-bugprone-use-after-move,\
|
||||
-hicpp-invalid-access-moved,\
|
||||
-misc-no-recursion,\
|
||||
-cppcoreguidelines-pro-bounds-pointer-arithmetic,\
|
||||
-cppcoreguidelines-avoid-magic-numbers,\
|
||||
-cppcoreguidelines-pro-bounds-constant-array-index,\
|
||||
-cppcoreguidelines-interfaces-global-init,\
|
||||
-cppcoreguidelines-macro-usage,\
|
||||
-cppcoreguidelines-avoid-goto,\
|
||||
-cppcoreguidelines-non-private-member-variables-in-classes"
|
||||
WarningsAsErrors: ''
|
||||
CheckOptions:
|
||||
- key: 'bugprone-argument-comment.StrictMode'
|
||||
value: 'true'
|
||||
# Prefer using enum classes with 2 values for parameters instead of bools
|
||||
- key: 'bugprone-argument-comment.CommentBoolLiterals'
|
||||
value: 'true'
|
||||
- key: 'bugprone-misplaced-widening-cast.CheckImplicitCasts'
|
||||
value: 'true'
|
||||
- key: 'bugprone-sizeof-expression.WarnOnSizeOfIntegerExpression'
|
||||
value: 'true'
|
||||
- key: 'bugprone-suspicious-string-compare.WarnOnLogicalNotComparison'
|
||||
value: 'true'
|
||||
- key: 'readability-simplify-boolean-expr.ChainedConditionalReturn'
|
||||
value: 'true'
|
||||
- key: 'readability-simplify-boolean-expr.ChainedConditionalAssignment'
|
||||
value: 'true'
|
||||
- key: 'readability-uniqueptr-delete-release.PreferResetCall'
|
||||
value: 'true'
|
||||
- key: 'cppcoreguidelines-init-variables.MathHeader'
|
||||
value: '<cmath>'
|
||||
- key: 'cppcoreguidelines-narrowing-conversions.PedanticMode'
|
||||
value: 'true'
|
||||
- key: 'readability-else-after-return.WarnOnUnfixable'
|
||||
value: 'true'
|
||||
- key: 'readability-else-after-return.WarnOnConditionVariables'
|
||||
value: 'true'
|
||||
- key: 'readability-inconsistent-declaration-parameter-name.Strict'
|
||||
value: 'true'
|
||||
- key: 'readability-qualified-auto.AddConstToQualified'
|
||||
value: 'true'
|
||||
- key: 'readability-redundant-access-specifiers.CheckFirstDeclaration'
|
||||
value: 'true'
|
||||
# These seem to be the most common identifier styles
|
||||
- key: 'readability-identifier-naming.AbstractClassCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.ClassCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.ClassConstantCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.ClassMemberCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.ClassMethodCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.ConstantCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.ConstantMemberCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.ConstantParameterCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.ConstantPointerParameterCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.ConstexprFunctionCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.ConstexprMethodCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.ConstexprVariableCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.EnumCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.EnumConstantCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.FunctionCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.GlobalConstantCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.GlobalConstantPointerCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.GlobalFunctionCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.GlobalPointerCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.GlobalVariableCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.InlineNamespaceCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.LocalConstantCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.LocalConstantPointerCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.LocalPointerCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.LocalVariableCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.MacroDefinitionCase'
|
||||
value: 'UPPER_CASE'
|
||||
- key: 'readability-identifier-naming.MemberCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.MethodCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.NamespaceCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.ParameterCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.ParameterPackCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.PointerParameterCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.PrivateMemberCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.PrivateMemberPrefix'
|
||||
value: 'm_'
|
||||
- key: 'readability-identifier-naming.PrivateMethodCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.ProtectedMemberCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.ProtectedMemberPrefix'
|
||||
value: 'm_'
|
||||
- key: 'readability-identifier-naming.ProtectedMethodCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.PublicMemberCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.PublicMethodCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.ScopedEnumConstantCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.StaticConstantCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.StaticVariableCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.StructCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.TemplateParameterCase'
|
||||
value: 'CamelCase'
|
||||
- key: 'readability-identifier-naming.TemplateTemplateParameterCase'
|
||||
value: 'CamelCase'
|
||||
- key: 'readability-identifier-naming.TypeAliasCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.TypedefCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.TypeTemplateParameterCase'
|
||||
value: 'CamelCase'
|
||||
- key: 'readability-identifier-naming.UnionCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.ValueTemplateParameterCase'
|
||||
value: 'CamelCase'
|
||||
- key: 'readability-identifier-naming.VariableCase'
|
||||
value: 'lower_case'
|
||||
- key: 'readability-identifier-naming.VirtualMethodCase'
|
||||
value: 'lower_case'
|
||||
...
|
||||
22
.codecov.yml
Normal file
22
.codecov.yml
Normal file
@@ -0,0 +1,22 @@
|
||||
codecov:
|
||||
max_report_age: off
|
||||
require_ci_to_pass: yes
|
||||
notify:
|
||||
after_n_builds: 1
|
||||
wait_for_ci: yes
|
||||
|
||||
ignore:
|
||||
- "benchmarks/cpp/asio/*"
|
||||
- "examples/*"
|
||||
- "tests/*"
|
||||
- "/usr/*"
|
||||
- "**/boost/*"
|
||||
|
||||
parsers:
|
||||
gcov:
|
||||
branch_detection:
|
||||
conditional: no
|
||||
loop: no
|
||||
method: no
|
||||
macro: no
|
||||
|
||||
46
.github/workflows/ci.yml
vendored
Normal file
46
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
name: CI
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
posix:
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
strategy:
|
||||
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' }
|
||||
- { toolset: clang, compiler: clang++-11, install: clang-11, os: ubuntu-22.04, cxxstd: 'c++20' }
|
||||
- { toolset: clang, compiler: clang++-13, install: clang-13, os: ubuntu-22.04, cxxstd: 'c++17' }
|
||||
- { toolset: clang, compiler: clang++-13, install: clang-13, os: ubuntu-22.04, cxxstd: 'c++20' }
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
CXXFLAGS: -g -O0 -std=${{matrix.cxxstd}} -Wall -Wextra
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Install CMake
|
||||
run: sudo apt-get -y install cmake
|
||||
- name: Install compiler
|
||||
run: sudo apt-get install -y ${{ matrix.install }}
|
||||
- name: Install Redis
|
||||
run: sudo apt-get install -y redis-server
|
||||
- name: Install boost
|
||||
uses: MarkusJx/install-boost@v2.3.0
|
||||
id: install-boost
|
||||
with:
|
||||
boost_version: 1.79.0
|
||||
platform_version: 22.04
|
||||
- name: Run CMake
|
||||
run: |
|
||||
BOOST_ROOT=${{steps.install-boost.outputs.BOOST_ROOT}} cmake -DCMAKE_CXX_COMPILER="${{matrix.compiler}}" -DCMAKE_CXX_FLAGS="${{env.CXXFLAGS}}"
|
||||
- name: Build
|
||||
run: make
|
||||
- name: Check
|
||||
run: ctest --output-on-failure
|
||||
47
.github/workflows/coverage.yml
vendored
Normal file
47
.github/workflows/coverage.yml
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
name: Coverage
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
jobs:
|
||||
posix:
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
runs-on: ubuntu-22.04
|
||||
env:
|
||||
CXX: g++-11
|
||||
CXXFLAGS: -g -O0 -std=c++20 --coverage -fkeep-inline-functions -fkeep-static-functions
|
||||
LDFLAGS: --coverage
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Install CMake
|
||||
run: sudo apt-get -y install cmake
|
||||
- name: Install lcov
|
||||
run: sudo apt-get -y install lcov
|
||||
- name: Install compiler
|
||||
run: sudo apt-get -y install g++-11
|
||||
- name: Install Redis
|
||||
run: sudo apt-get -y install redis-server
|
||||
- name: Install boost
|
||||
uses: MarkusJx/install-boost@v2.3.0
|
||||
id: install-boost
|
||||
with:
|
||||
boost_version: 1.79.0
|
||||
platform_version: 22.04
|
||||
- name: Run CMake
|
||||
run: |
|
||||
BOOST_ROOT=${{steps.install-boost.outputs.BOOST_ROOT}} cmake --preset coverage .
|
||||
- name: Build
|
||||
run: cmake --build --preset coverage
|
||||
- name: Test
|
||||
run: ctest --preset coverage
|
||||
- name: Make the coverage file
|
||||
run: cmake --build --preset coverage --target coverage
|
||||
- name: Upload to codecov
|
||||
run: |
|
||||
bash <(curl -s https://codecov.io/bash) -f ./build/coverage/coverage.info || echo "Codecov did not collect coverage reports"
|
||||
|
||||
4
BUILD_STATUS.md
Normal file
4
BUILD_STATUS.md
Normal file
@@ -0,0 +1,4 @@
|
||||
Branch | GH Actions | codecov.io |
|
||||
:-------------: | ---------- | ---------- |
|
||||
[`master`](https://github.com/mzimbres/aedis/tree/master) | [](https://github.com/mzimbres/aedis/actions/workflows/ci.yml) | [](https://codecov.io/gh/mzimbres/aedis/branch/master)
|
||||
|
||||
207
CMakeLists.txt
Normal file
207
CMakeLists.txt
Normal file
@@ -0,0 +1,207 @@
|
||||
# At the moment the official build system is still autotools and this
|
||||
# file is meant to support Aedis on windows.
|
||||
|
||||
# BOOST_ROOT=/opt/boost_1_79/ cmake -DCMAKE_CXX_FLAGS="-g -O0
|
||||
# -std=c++20 -Wall -Wextra --coverage -fkeep-inline-functions
|
||||
# -fkeep-static-functions" -DCMAKE_EXE_LINKER_FLAGS="--coverage"
|
||||
# ~/my/aedis
|
||||
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
|
||||
project(
|
||||
Aedis
|
||||
VERSION 1.2.0
|
||||
DESCRIPTION "A redis client designed for performance and scalability"
|
||||
HOMEPAGE_URL "https://mzimbres.github.io/aedis"
|
||||
LANGUAGES CXX
|
||||
)
|
||||
|
||||
add_library(aedis INTERFACE)
|
||||
target_include_directories(aedis INTERFACE
|
||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
|
||||
$<INSTALL_INTERFACE:include>
|
||||
)
|
||||
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)
|
||||
|
||||
include(CMakePackageConfigHelpers)
|
||||
write_basic_package_version_file(
|
||||
"${PROJECT_BINARY_DIR}/AedisConfigVersion.cmake"
|
||||
COMPATIBILITY AnyNewerVersion
|
||||
)
|
||||
|
||||
find_package(Boost 1.79 REQUIRED)
|
||||
include_directories(${Boost_INCLUDE_DIRS})
|
||||
|
||||
find_package(OpenSSL REQUIRED)
|
||||
|
||||
enable_testing()
|
||||
include_directories(include)
|
||||
|
||||
# Executables
|
||||
#=======================================================================
|
||||
|
||||
#add_executable(intro_sync examples/intro_sync.cpp) // Uncomment after update to Boost 1.80
|
||||
add_executable(chat_room examples/chat_room.cpp)
|
||||
add_executable(containers examples/containers.cpp)
|
||||
add_executable(echo_server examples/echo_server.cpp)
|
||||
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(intro examples/intro.cpp)
|
||||
add_executable(intro_tls examples/intro_tls.cpp)
|
||||
add_executable(low_level_sync examples/low_level_sync.cpp)
|
||||
add_executable(serialization examples/serialization.cpp)
|
||||
add_executable(subscriber examples/subscriber.cpp)
|
||||
add_executable(subscriber_sentinel examples/subscriber_sentinel.cpp)
|
||||
add_executable(test_conn_connect tests/conn_connect.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(chat_room PUBLIC cxx_std_20)
|
||||
target_compile_features(containers PUBLIC cxx_std_20)
|
||||
target_compile_features(echo_server PUBLIC cxx_std_20)
|
||||
target_compile_features(echo_server_client PUBLIC cxx_std_20)
|
||||
target_compile_features(echo_server_direct PUBLIC cxx_std_20)
|
||||
target_compile_features(intro PUBLIC cxx_std_17)
|
||||
target_compile_features(intro_tls PUBLIC cxx_std_17)
|
||||
target_compile_features(low_level_sync PUBLIC cxx_std_17)
|
||||
target_compile_features(serialization PUBLIC cxx_std_17)
|
||||
target_compile_features(subscriber PUBLIC cxx_std_20)
|
||||
target_compile_features(subscriber_sentinel PUBLIC cxx_std_20)
|
||||
target_compile_features(test_conn_connect PUBLIC cxx_std_17)
|
||||
target_compile_features(test_conn_exec PUBLIC cxx_std_20)
|
||||
target_compile_features(test_conn_push PUBLIC cxx_std_20)
|
||||
target_compile_features(test_conn_quit PUBLIC cxx_std_17)
|
||||
target_compile_features(test_conn_quit_coalesce PUBLIC cxx_std_17)
|
||||
target_compile_features(test_conn_reconnect PUBLIC cxx_std_20)
|
||||
target_compile_features(test_conn_tls PUBLIC cxx_std_17)
|
||||
target_compile_features(test_low_level PUBLIC cxx_std_17)
|
||||
target_compile_features(test_conn_run_cancel PUBLIC cxx_std_20)
|
||||
target_compile_features(test_conn_exec_cancel PUBLIC cxx_std_20)
|
||||
target_compile_features(test_conn_echo_stress PUBLIC cxx_std_20)
|
||||
target_compile_features(test_request PUBLIC cxx_std_17)
|
||||
|
||||
target_link_libraries(intro_tls OpenSSL::Crypto OpenSSL::SSL)
|
||||
target_link_libraries(test_conn_tls OpenSSL::Crypto OpenSSL::SSL)
|
||||
|
||||
# Tests
|
||||
#=======================================================================
|
||||
|
||||
add_test(containers containers)
|
||||
add_test(intro intro)
|
||||
add_test(intro_tls intro_tls)
|
||||
#add_test(intro_sync intro_sync)
|
||||
add_test(serialization serialization)
|
||||
add_test(low_level_sync low_level_sync)
|
||||
add_test(test_low_level test_low_level)
|
||||
add_test(test_conn_exec test_conn_exec)
|
||||
add_test(test_conn_connect test_conn_connect)
|
||||
add_test(test_conn_push test_conn_push)
|
||||
add_test(test_conn_quit test_conn_quit)
|
||||
add_test(test_conn_quit_coalesce test_conn_quit_coalesce)
|
||||
add_test(test_conn_reconnect test_conn_reconnect)
|
||||
add_test(test_conn_tls test_conn_tls)
|
||||
add_test(test_conn_run_cancel test_conn_run_cancel)
|
||||
add_test(test_conn_exec_cancel test_conn_exec_cancel)
|
||||
add_test(test_conn_echo_stress test_conn_echo_stress)
|
||||
add_test(test_request test_request)
|
||||
|
||||
# Install
|
||||
#=======================================================================
|
||||
|
||||
install(TARGETS aedis
|
||||
EXPORT aedis
|
||||
PUBLIC_HEADER DESTINATION include COMPONENT Development
|
||||
)
|
||||
|
||||
include(CMakePackageConfigHelpers)
|
||||
|
||||
configure_package_config_file(
|
||||
"${PROJECT_SOURCE_DIR}/cmake/AedisConfig.cmake.in"
|
||||
"${PROJECT_BINARY_DIR}/AedisConfig.cmake"
|
||||
INSTALL_DESTINATION lib/cmake/aedis
|
||||
)
|
||||
|
||||
install(EXPORT aedis DESTINATION lib/cmake/aedis)
|
||||
install(FILES "${PROJECT_BINARY_DIR}/AedisConfigVersion.cmake"
|
||||
"${PROJECT_BINARY_DIR}/AedisConfig.cmake"
|
||||
DESTINATION lib/cmake/aedis)
|
||||
|
||||
install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/ DESTINATION include)
|
||||
|
||||
# Doxygen
|
||||
#=======================================================================
|
||||
|
||||
set(DOXYGEN_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/doc")
|
||||
configure_file(doc/Doxyfile.in doc/Doxyfile @ONLY)
|
||||
|
||||
add_custom_target(
|
||||
doc
|
||||
COMMAND doxygen "${PROJECT_BINARY_DIR}/doc/Doxyfile"
|
||||
COMMENT "Building documentation using Doxygen"
|
||||
WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}"
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
# Coverage
|
||||
#=======================================================================
|
||||
|
||||
set(
|
||||
COVERAGE_TRACE_COMMAND
|
||||
lcov --capture
|
||||
-output-file "${PROJECT_BINARY_DIR}/coverage.info"
|
||||
--directory "${PROJECT_BINARY_DIR}"
|
||||
--include "${PROJECT_SOURCE_DIR}/include/*"
|
||||
)
|
||||
|
||||
set(
|
||||
COVERAGE_HTML_COMMAND
|
||||
genhtml --legend -f -q
|
||||
"${PROJECT_BINARY_DIR}/coverage.info"
|
||||
--prefix "${PROJECT_SOURCE_DIR}"
|
||||
--output-directory "${PROJECT_BINARY_DIR}/coverage_html"
|
||||
)
|
||||
|
||||
add_custom_target(
|
||||
coverage
|
||||
COMMAND ${COVERAGE_TRACE_COMMAND}
|
||||
COMMAND ${COVERAGE_HTML_COMMAND}
|
||||
COMMENT "Generating coverage report"
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
# Distribution
|
||||
#=======================================================================
|
||||
|
||||
include(CPack)
|
||||
|
||||
# TODO
|
||||
#=======================================================================
|
||||
|
||||
#.PHONY: bench
|
||||
#bench:
|
||||
# pdflatex --jobname=echo-f0 benchmarks/benchmarks.tex
|
||||
# pdflatex --jobname=echo-f1 benchmarks/benchmarks.tex
|
||||
# pdftoppm {input.pdf} {output.file} -png
|
||||
|
||||
86
CMakePresets.json
Normal file
86
CMakePresets.json
Normal file
@@ -0,0 +1,86 @@
|
||||
{
|
||||
"version": 2,
|
||||
"cmakeMinimumRequired": {
|
||||
"major": 3,
|
||||
"minor": 14,
|
||||
"patch": 0
|
||||
},
|
||||
"configurePresets": [
|
||||
{
|
||||
"name": "cmake-pedantic",
|
||||
"hidden": true,
|
||||
"warnings": {
|
||||
"dev": true,
|
||||
"deprecated": true,
|
||||
"uninitialized": true,
|
||||
"unusedCli": true,
|
||||
"systemVars": false
|
||||
},
|
||||
"errors": {
|
||||
"dev": true,
|
||||
"deprecated": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "coverage",
|
||||
"generator": "Unix Makefiles",
|
||||
"hidden": false,
|
||||
"inherits": ["cmake-pedantic"],
|
||||
"binaryDir": "${sourceDir}/build/coverage",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Coverage",
|
||||
"CMAKE_CXX_EXTENSIONS": "OFF",
|
||||
"CMAKE_CXX_FLAGS": "-Wall -Wextra",
|
||||
"CMAKE_CXX_FLAGS_COVERAGE": "-Og -g --coverage -fkeep-inline-functions -fkeep-static-functions",
|
||||
"CMAKE_CXX_STANDARD_REQUIRED": "ON",
|
||||
"CMAKE_EXE_LINKER_FLAGS_COVERAGE": "--coverage",
|
||||
"CMAKE_SHARED_LINKER_FLAGS_COVERAGE": "--coverage",
|
||||
"PROJECT_BINARY_DIR": "${sourceDir}/build/coverage",
|
||||
"COVERAGE_HTML_COMMAND": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "dev",
|
||||
"generator": "Unix Makefiles",
|
||||
"hidden": false,
|
||||
"inherits": ["cmake-pedantic"],
|
||||
"binaryDir": "${sourceDir}/build/dev",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Debug",
|
||||
"CMAKE_CXX_EXTENSIONS": "OFF",
|
||||
"CMAKE_CXX_FLAGS": "-Wall -Wextra -fsanitize=address",
|
||||
"CMAKE_SHARED_LINKER_FLAGS": "-fsanitize=address",
|
||||
"CMAKE_CXX_STANDARD_REQUIRED": "ON",
|
||||
"PROJECT_BINARY_DIR": "${sourceDir}/build/dev",
|
||||
"DOXYGEN_OUTPUT_DIRECTORY": "${sourceDir}/build/dev/doc/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "clang-tidy",
|
||||
"generator": "Unix Makefiles",
|
||||
"hidden": false,
|
||||
"inherits": ["dev"],
|
||||
"binaryDir": "${sourceDir}/build/clang-tidy",
|
||||
"cacheVariables": {
|
||||
"CMAKE_CXX_CLANG_TIDY": "clang-tidy;--header-filter=${sourceDir}/include/*",
|
||||
"CMAKE_CXX_STANDARD": "20"
|
||||
}
|
||||
}
|
||||
],
|
||||
"buildPresets": [
|
||||
{ "name": "coverage", "configurePreset": "coverage" },
|
||||
{ "name": "dev", "configurePreset": "dev" },
|
||||
{ "name": "clang-tidy", "configurePreset": "clang-tidy" }
|
||||
],
|
||||
"testPresets": [
|
||||
{
|
||||
"name": "test",
|
||||
"hidden": true,
|
||||
"output": {"outputOnFailure": true},
|
||||
"execution": {"noTestsAction": "error", "stopOnFailure": true}
|
||||
},
|
||||
{ "name": "coverage", "configurePreset": "coverage", "inherits": ["test"] },
|
||||
{ "name": "dev", "configurePreset": "dev", "inherits": ["test"] },
|
||||
{ "name": "clang-tidy", "configurePreset": "clang-tidy", "inherits": ["test"] }
|
||||
]
|
||||
}
|
||||
109
Makefile.am
109
Makefile.am
@@ -1,109 +0,0 @@
|
||||
AUTOMAKE_OPTIONS = subdir-objects
|
||||
ACLOCAL_AMFLAGS = -I m4
|
||||
|
||||
AM_COLOR_TESTS = always
|
||||
DISTCHECK_CONFIGURE_FLAGS = CPPFLAGS="$(BOOST_CPPFLAGS) $(CPPFLAGS)" LDFLAGS="$(BOOST_LDFLAGS)"
|
||||
|
||||
AM_CPPFLAGS =
|
||||
AM_CPPFLAGS += $(BOOST_CPPFLAGS)
|
||||
#AM_CPPFLAGS += -I$(top_srcdir)/include
|
||||
|
||||
AM_LDFLAGS =
|
||||
AM_LDFLAGS += -pthread
|
||||
|
||||
check_PROGRAMS =
|
||||
check_PROGRAMS += intro_sync
|
||||
check_PROGRAMS += intro
|
||||
check_PROGRAMS += containers
|
||||
check_PROGRAMS += serialization
|
||||
check_PROGRAMS += test_low_level
|
||||
if HAVE_CXX20
|
||||
check_PROGRAMS += test_high_level
|
||||
endif
|
||||
|
||||
EXTRA_PROGRAMS =
|
||||
EXTRA_PROGRAMS += subscriber
|
||||
if HAVE_CXX20
|
||||
EXTRA_PROGRAMS += echo_server
|
||||
EXTRA_PROGRAMS += echo_server_direct
|
||||
EXTRA_PROGRAMS += chat_room
|
||||
EXTRA_PROGRAMS += echo_server_client
|
||||
endif
|
||||
|
||||
CLEANFILES =
|
||||
CLEANFILES += $(EXTRA_PROGRAMS)
|
||||
|
||||
.PHONY: all
|
||||
all: $(check_PROGRAMS) $(EXTRA_PROGRAMS)
|
||||
|
||||
intro_sync_SOURCES = $(top_srcdir)/tests/intro_sync.cpp
|
||||
subscriber_SOURCES = $(top_srcdir)/examples/subscriber.cpp
|
||||
test_low_level_SOURCES = $(top_srcdir)/tests/low_level.cpp
|
||||
intro_SOURCES = $(top_srcdir)/examples/intro.cpp
|
||||
containers_SOURCES = $(top_srcdir)/examples/containers.cpp
|
||||
serialization_SOURCES = $(top_srcdir)/examples/serialization.cpp
|
||||
if HAVE_CXX20
|
||||
test_high_level_SOURCES = $(top_srcdir)/tests/high_level.cpp
|
||||
chat_room_SOURCES = $(top_srcdir)/examples/chat_room.cpp
|
||||
echo_server_SOURCES = $(top_srcdir)/examples/echo_server.cpp
|
||||
echo_server_direct_SOURCES = $(top_srcdir)/benchmarks/cpp/asio/echo_server_direct.cpp
|
||||
echo_server_client_SOURCES = $(top_srcdir)/benchmarks/cpp/asio/echo_server_client.cpp
|
||||
endif
|
||||
|
||||
nobase_include_HEADERS =\
|
||||
$(top_srcdir)/aedis/src.hpp\
|
||||
$(top_srcdir)/aedis/error.hpp\
|
||||
$(top_srcdir)/aedis/impl/error.ipp\
|
||||
$(top_srcdir)/aedis/detail/net.hpp\
|
||||
$(top_srcdir)/aedis/command.hpp\
|
||||
$(top_srcdir)/aedis/impl/command.ipp\
|
||||
$(top_srcdir)/aedis/connection.hpp\
|
||||
$(top_srcdir)/aedis/adapt.hpp\
|
||||
$(top_srcdir)/aedis/detail/connection_ops.hpp\
|
||||
$(top_srcdir)/aedis/aedis.hpp\
|
||||
$(top_srcdir)/aedis/adapter/detail/adapters.hpp\
|
||||
$(top_srcdir)/aedis/adapter/adapt.hpp\
|
||||
$(top_srcdir)/aedis/adapter/detail/response_traits.hpp\
|
||||
$(top_srcdir)/aedis/resp3/node.hpp\
|
||||
$(top_srcdir)/aedis/resp3/compose.hpp\
|
||||
$(top_srcdir)/aedis/resp3/detail/read_ops.hpp\
|
||||
$(top_srcdir)/aedis/resp3/detail/parser.hpp\
|
||||
$(top_srcdir)/aedis/resp3/type.hpp\
|
||||
$(top_srcdir)/aedis/resp3/read.hpp\
|
||||
$(top_srcdir)/aedis/resp3/exec.hpp\
|
||||
$(top_srcdir)/aedis/resp3/write.hpp\
|
||||
$(top_srcdir)/aedis/resp3/request.hpp\
|
||||
$(top_srcdir)/aedis/resp3/detail/impl/parser.ipp\
|
||||
$(top_srcdir)/aedis/resp3/impl/type.ipp
|
||||
|
||||
nobase_noinst_HEADERS =\
|
||||
$(top_srcdir)/examples/print.hpp\
|
||||
$(top_srcdir)/tests/check.hpp
|
||||
|
||||
TESTS = $(check_PROGRAMS)
|
||||
|
||||
EXTRA_DIST =
|
||||
EXTRA_DIST += $(top_srcdir)/README.md
|
||||
EXTRA_DIST += $(top_srcdir)/doc/DoxygenLayout.xml
|
||||
EXTRA_DIST += $(top_srcdir)/doc/aedis.css
|
||||
EXTRA_DIST += $(top_srcdir)/doc/htmlfooter.html
|
||||
EXTRA_DIST += $(top_srcdir)/doc/htmlheader.html
|
||||
EXTRA_DIST += $(top_srcdir)/benchmarks/cpp/libuv/echo_server_direct.c
|
||||
EXTRA_DIST += $(top_srcdir)/benchmarks/cpp/libuv/README.md
|
||||
EXTRA_DIST += $(top_srcdir)/benchmarks/go/echo_server_direct.go
|
||||
EXTRA_DIST += $(top_srcdir)/benchmarks/nodejs/echo_server_direct.js
|
||||
EXTRA_DIST += $(top_srcdir)/benchmarks/nodejs/echo_server_over_redis.js
|
||||
EXTRA_DIST += $(top_srcdir)/benchmarks/nodejs/package.json
|
||||
EXTRA_DIST += $(top_srcdir)/benchmarks/nodejs/package-lock.json
|
||||
EXTRA_DIST += $(top_srcdir)/benchmarks/rust/echo_server_direct/Cargo.lock
|
||||
EXTRA_DIST += $(top_srcdir)/benchmarks/rust/echo_server_direct/Cargo.toml
|
||||
EXTRA_DIST += $(top_srcdir)/benchmarks/rust/echo_server_direct/src/main.rs
|
||||
EXTRA_DIST += $(top_srcdir)/benchmarks/rust/echo_server_over_redis/Cargo.lock
|
||||
EXTRA_DIST += $(top_srcdir)/benchmarks/rust/echo_server_over_redis/Cargo.toml
|
||||
EXTRA_DIST += $(top_srcdir)/benchmarks/rust/echo_server_over_redis/src/main.rs
|
||||
|
||||
.PHONY: doc
|
||||
doc:
|
||||
rm -rf ../aedis-gh-pages/*
|
||||
doxygen doc/Doxyfile
|
||||
|
||||
129
aedis/adapt.hpp
129
aedis/adapt.hpp
@@ -1,129 +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_ADAPT_HPP
|
||||
#define AEDIS_ADAPT_HPP
|
||||
|
||||
#include <tuple>
|
||||
|
||||
#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>
|
||||
|
||||
namespace aedis {
|
||||
|
||||
using ignore = adapter::detail::ignore;
|
||||
|
||||
namespace detail {
|
||||
|
||||
struct ignore_adapter {
|
||||
void
|
||||
operator()(
|
||||
std::size_t i,
|
||||
resp3::node<boost::string_view> const& nd,
|
||||
boost::system::error_code& ec)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
template <class Tuple>
|
||||
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 adapters_array_type = std::array<variant_type, size>;
|
||||
|
||||
adapters_array_type adapters_;
|
||||
|
||||
public:
|
||||
static_adapter(Tuple& r = nullptr)
|
||||
{
|
||||
adapter::detail::assigner<std::tuple_size<Tuple>::value - 1>::assign(adapters_, r);
|
||||
}
|
||||
|
||||
void
|
||||
operator()(
|
||||
std::size_t i,
|
||||
resp3::node<boost::string_view> const& nd,
|
||||
boost::system::error_code& ec)
|
||||
{
|
||||
using boost::variant2::visit;
|
||||
BOOST_ASSERT(i < adapters_.size());
|
||||
visit([&](auto& arg){arg(nd, ec);}, adapters_.at(i));
|
||||
}
|
||||
};
|
||||
|
||||
template <class Vector>
|
||||
class vector_adapter {
|
||||
private:
|
||||
using adapter_type = typename adapter::detail::response_traits<Vector>::adapter_type;
|
||||
adapter_type adapter_;
|
||||
|
||||
public:
|
||||
vector_adapter(Vector& v) : adapter_{adapter::adapt(v)} { }
|
||||
|
||||
void
|
||||
operator()(
|
||||
std::size_t i,
|
||||
resp3::node<boost::string_view> const& nd,
|
||||
boost::system::error_code& ec)
|
||||
{
|
||||
adapter_(nd, ec);
|
||||
}
|
||||
};
|
||||
|
||||
template <class>
|
||||
struct response_traits;
|
||||
|
||||
template <>
|
||||
struct response_traits<void> {
|
||||
using response_type = void;
|
||||
using adapter_type = detail::ignore_adapter;
|
||||
|
||||
static auto adapt() noexcept
|
||||
{ return detail::ignore_adapter{}; }
|
||||
};
|
||||
|
||||
template <class String, class Allocator>
|
||||
struct response_traits<std::vector<resp3::node<String>, Allocator>> {
|
||||
using response_type = std::vector<resp3::node<String>, Allocator>;
|
||||
using adapter_type = vector_adapter<response_type>;
|
||||
|
||||
static auto adapt(response_type& v) noexcept
|
||||
{ return adapter_type{v}; }
|
||||
};
|
||||
|
||||
template <class ...Ts>
|
||||
struct response_traits<std::tuple<Ts...>> {
|
||||
using response_type = std::tuple<Ts...>;
|
||||
using adapter_type = static_adapter<response_type>;
|
||||
|
||||
static auto adapt(response_type& r) noexcept
|
||||
{ return adapter_type{r}; }
|
||||
};
|
||||
|
||||
} // detail
|
||||
|
||||
auto adapt() noexcept
|
||||
{
|
||||
return detail::response_traits<void>::adapt();
|
||||
}
|
||||
|
||||
template<class T>
|
||||
auto adapt(T& t) noexcept
|
||||
{
|
||||
return detail::response_traits<T>::adapt(t);
|
||||
}
|
||||
|
||||
} // aedis
|
||||
|
||||
#endif // AEDIS_ADAPT_HPP
|
||||
640
aedis/aedis.hpp
640
aedis/aedis.hpp
@@ -1,640 +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_HPP
|
||||
#define AEDIS_HPP
|
||||
|
||||
#include <aedis/error.hpp>
|
||||
#include <aedis/command.hpp>
|
||||
#include <aedis/adapt.hpp>
|
||||
#include <aedis/connection.hpp>
|
||||
#include <aedis/resp3/read.hpp>
|
||||
#include <aedis/resp3/write.hpp>
|
||||
#include <aedis/resp3/exec.hpp>
|
||||
#include <aedis/resp3/request.hpp>
|
||||
#include <aedis/adapter/adapt.hpp>
|
||||
|
||||
// \li Support for Redis [sentinel](https://redis.io/docs/manual/sentinel).
|
||||
// TODO: Reconnect support.
|
||||
// TODO: Remove conflicts of the adapt function.
|
||||
|
||||
/** \mainpage Documentation
|
||||
\tableofcontents
|
||||
|
||||
\section Overview
|
||||
|
||||
Aedis is a high-level [Redis](https://redis.io/) client library
|
||||
built on top of [Asio](https://www.boost.org/doc/libs/release/doc/html/boost_asio.html)
|
||||
that provides simple and efficient communication with a Redis
|
||||
server. Some of its distinctive features are
|
||||
|
||||
\li Support for the latest version of the Redis communication protocol [RESP3](https://github.com/redis/redis-specifications/blob/master/protocol/RESP3.md).
|
||||
\li First class support for STL containers and C++ built-in types.
|
||||
\li Serialization and deserialization of your own data types.
|
||||
\li Zero asymptotic allocations by means of memory reuse.
|
||||
\li Healthy checks, back pressure and low latency.
|
||||
|
||||
The Aedis API hides most of the low level asynchronous operations
|
||||
away from the user, for example, the code below pings a message to
|
||||
the server
|
||||
|
||||
\code
|
||||
int main()
|
||||
{
|
||||
request req;
|
||||
req.push("PING");
|
||||
req.push("QUIT");
|
||||
|
||||
std::tuple<std::string, std::string> resp;
|
||||
|
||||
net::io_context ioc;
|
||||
|
||||
connection db{ioc};
|
||||
db.async_exec("127.0.0.1", "6379", req, adapt(resp),
|
||||
[](auto ec, auto) { std::cout << ec.message() << std::endl; });
|
||||
|
||||
ioc.run();
|
||||
|
||||
// Print
|
||||
std::cout << std::get<0>(resp) << std::endl;
|
||||
std::cout << std::get<1>(resp) << std::endl;
|
||||
}
|
||||
\endcode
|
||||
|
||||
For a detailed comparison of Redis clients and the design
|
||||
rationale behind Aedis jump to \ref why-aedis.
|
||||
|
||||
\section requests Requests
|
||||
|
||||
Redis requests are composed of one of more Redis commands (in
|
||||
Redis documentation they are called
|
||||
[pipelines](https://redis.io/topics/pipelining)). For example
|
||||
|
||||
@code
|
||||
request req;
|
||||
|
||||
// Command with variable length of arguments.
|
||||
req.push("SET", "key", "some value", value, "EX", "2");
|
||||
|
||||
// Pushes a set.
|
||||
std::list<std::string> list
|
||||
{"channel1", "channel2", "channel3"};
|
||||
req.push_range("SUBSCRIBE", list);
|
||||
|
||||
// Same as above but as an iterator range.
|
||||
req.push_range2("SUBSCRIBE", std::cbegin(list), std::cend(list));
|
||||
|
||||
// Sends a map.
|
||||
std::map<std::string, mystruct> map
|
||||
{ {"key1", "value1"}
|
||||
, {"key2", "value2"}
|
||||
, {"key3", "value3"}};
|
||||
req.push_range("HSET", "key", map);
|
||||
@endcode
|
||||
|
||||
Sending a request to Redis is then peformed with the following function
|
||||
|
||||
@code
|
||||
co_await db->async_exec(req, adapt(resp));
|
||||
@endcode
|
||||
|
||||
The second argument \c adapt(resp) will be explained in \ref requests.
|
||||
|
||||
\subsection requests-serialization Serialization
|
||||
|
||||
The \c push and \c push_range functions above work with integers
|
||||
e.g. \c int and \c std::string out of the box. To send your own
|
||||
data type defined a \c to_bulk function like this
|
||||
|
||||
@code
|
||||
struct mystruct {
|
||||
// Example struct.
|
||||
};
|
||||
|
||||
void to_bulk(std::string& to, mystruct const& obj)
|
||||
{
|
||||
// Convert to obj string and call to_bulk.
|
||||
std::string dummy = "Dummy serializaiton string.";
|
||||
aedis::resp3::to_bulk(to, dummy);
|
||||
}
|
||||
@endcode
|
||||
|
||||
Once \c to_bulk is defined and accessible over ADL \c mystruct can
|
||||
be passed to the \c request
|
||||
|
||||
@code
|
||||
request req;
|
||||
|
||||
std::map<std::string, mystruct> map {...};
|
||||
|
||||
req.push_range("HSET", "key", map);
|
||||
@endcode
|
||||
|
||||
It is quite common to store json string in Redis for example.
|
||||
|
||||
\section low-level-responses Responses
|
||||
|
||||
To read responses effectively, users must know their RESP3 type,
|
||||
this can be found in the Redis documentation for each command
|
||||
(https://redis.io/commands). For example
|
||||
|
||||
Command | RESP3 type | Documentation
|
||||
---------|-------------------------------------|--------------
|
||||
lpush | Number | https://redis.io/commands/lpush
|
||||
lrange | Array | https://redis.io/commands/lrange
|
||||
set | Simple-string, null or blob-string | https://redis.io/commands/set
|
||||
get | Blob-string | https://redis.io/commands/get
|
||||
smembers | Set | https://redis.io/commands/smembers
|
||||
hgetall | Map | https://redis.io/commands/hgetall
|
||||
|
||||
Once the RESP3 type of a given response is known we can choose a
|
||||
proper C++ data structure to receive it in. Fortunately, this is a
|
||||
simple task for most types. The table below summarises the options
|
||||
|
||||
RESP3 type | C++ | Type
|
||||
---------------|--------------------------------------------------------------|------------------
|
||||
Simple-string | \c std::string | Simple
|
||||
Simple-error | \c std::string | Simple
|
||||
Blob-string | \c std::string, \c std::vector | Simple
|
||||
Blob-error | \c std::string, \c std::vector | Simple
|
||||
Number | `long long`, `int`, `std::size_t`, \c std::string | Simple
|
||||
Double | `double`, \c std::string | Simple
|
||||
Null | `boost::optional<T>` | Simple
|
||||
Array | \c std::vector, \c std::list, \c std::array, \c std::deque | Aggregate
|
||||
Map | \c std::vector, \c std::map, \c std::unordered_map | Aggregate
|
||||
Set | \c std::vector, \c std::set, \c std::unordered_set | Aggregate
|
||||
Push | \c std::vector, \c std::map, \c std::unordered_map | Aggregate
|
||||
|
||||
Responses that contain nested aggregates or heterogeneous data
|
||||
types will be given special treatment later in \ref gen-case. As
|
||||
of this writing, not all RESP3 types are used by the Redis server,
|
||||
which means in practice users will be concerned with a reduced
|
||||
subset of the RESP3 specification. Now let us see some examples
|
||||
|
||||
@code
|
||||
auto dbuffer = dynamic_buffer(buffer);
|
||||
|
||||
// To ignore the response.
|
||||
co_await resp3::async_read(socket, dbuffer, adapt());
|
||||
|
||||
// Read in a std::string e.g. get.
|
||||
std::string str;
|
||||
co_await resp3::async_read(socket, dbuffer, adapt(str));
|
||||
|
||||
// Read in a long long e.g. rpush.
|
||||
long long number;
|
||||
co_await resp3::async_read(socket, dbuffer, adapt(number));
|
||||
|
||||
// Read in a std::set e.g. smembers.
|
||||
std::set<T, U> set;
|
||||
co_await resp3::async_read(socket, dbuffer, adapt(set));
|
||||
|
||||
// Read in a std::map e.g. hgetall.
|
||||
std::map<T, U> set;
|
||||
co_await resp3::async_read(socket, dbuffer, adapt(map));
|
||||
|
||||
// Read in a std::unordered_map e.g. hgetall.
|
||||
std::unordered_map<T, U> umap;
|
||||
co_await resp3::async_read(socket, dbuffer, adapt(umap));
|
||||
|
||||
// Read in a std::vector e.g. lrange.
|
||||
std::vector<T> vec;
|
||||
co_await resp3::async_read(socket, dbuffer, adapt(vec));
|
||||
@endcode
|
||||
|
||||
In other words, it is straightforward, just pass the result of \c
|
||||
adapt to the read function and make sure the response data type is
|
||||
compatible with the data structure you are calling @c adapter(...)
|
||||
with. All standard C++ containers are supported by Aedis.
|
||||
|
||||
\subsection Optional
|
||||
|
||||
It is not uncommon for apps to access keys that do not exist or
|
||||
that have already expired in the Redis server, to deal with these
|
||||
cases Aedis provides support for \c boost::optional. To use it,
|
||||
wrap your type around \c boost::optional like this
|
||||
|
||||
@code
|
||||
boost::optional<std::unordered_map<T, U>> umap;
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(umap));
|
||||
@endcode
|
||||
|
||||
Everything else stays the same, before accessing data, users will
|
||||
have to check or assert the optional contains a value.
|
||||
|
||||
\subsection heterogeneous_aggregates Heterogeneous aggregates
|
||||
|
||||
There are cases where Redis returns aggregates that
|
||||
contain heterogeneous data, for example, an array that contains
|
||||
integers, strings nested sets etc. Aedis supports reading such
|
||||
aggregates in a \c std::tuple efficiently as long as the they
|
||||
don't contain 3-order nested aggregates e.g. an array that
|
||||
contains an array of arrays. For example, to read the response to
|
||||
a \c hello command we can use the following response type.
|
||||
|
||||
@code
|
||||
using hello_type = std::tuple<
|
||||
std::string, std::string,
|
||||
std::string, std::string,
|
||||
std::string, int,
|
||||
std::string, int,
|
||||
std::string, std::string,
|
||||
std::string, std::string,
|
||||
std::string, std::vector<std::string>>;
|
||||
@endcode
|
||||
|
||||
Transactions are another example where this feature is useful, for
|
||||
example, the response to the transaction below
|
||||
|
||||
@code
|
||||
db.send("MULTI");
|
||||
db.send("GET", "key1");
|
||||
db.send("LRANGE", "key2", 0, -1);
|
||||
db.send("HGETALL", "key3");
|
||||
db.send("EXEC");
|
||||
@endcode
|
||||
|
||||
can be read in the following way
|
||||
|
||||
@code
|
||||
std::tuple<
|
||||
boost::optional<std::string>, // Response to get
|
||||
boost::optional<std::vector<std::string>>, // Response to lrange
|
||||
boost::optional<std::map<std::string, std::string>> // Response to hgetall
|
||||
> trans;
|
||||
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // Ignore multi
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // Ignore get
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // Ignore lrange
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // Ignore hgetall
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(trans));
|
||||
@endcode
|
||||
|
||||
Note that above we are not ignoring the response to the commands
|
||||
themselves but whether they have been successfully queued. Only
|
||||
after @c exec is received Redis will execute them in sequence and
|
||||
send all responses together in an array.
|
||||
|
||||
\subsection Serialization
|
||||
|
||||
As mentioned in \ref requests-serialization, it is common for
|
||||
users to serialized data before sending it to Redis e.g. json
|
||||
strings, for example
|
||||
|
||||
@code
|
||||
sr.push("SET", "key", "{"Server": "Redis"}"); // Unquoted for readability.
|
||||
sr.push("GET", "key")
|
||||
@endcode
|
||||
|
||||
For performance and convenience reasons, we may want to avoid
|
||||
receiving the response to the \c get command above as a string
|
||||
just to convert it later to a e.g. deserialized json. To support
|
||||
this, Aedis calls a user defined \c from_bulk function while
|
||||
parsing the response. In simple terms, define your type
|
||||
|
||||
@code
|
||||
struct mystruct {
|
||||
// struct fields.
|
||||
};
|
||||
@endcode
|
||||
|
||||
and deserialize it from a string in a function \c from_bulk with
|
||||
the following signature
|
||||
|
||||
@code
|
||||
void from_bulk(mystruct& obj, char const* p, std::size_t size, boost::system::error_code& ec)
|
||||
{
|
||||
// Deserializes p into obj.
|
||||
}
|
||||
@endcode
|
||||
|
||||
After that, you can start receiving data efficiently in the desired
|
||||
types e.g. \c mystruct, \c std::map<std::string, mystruct> etc.
|
||||
|
||||
\subsection gen-case The general case
|
||||
|
||||
As already mentioned, there are cases where responses to Redis
|
||||
commands won't fit in the model presented above, some examples are
|
||||
|
||||
@li Commands (like \c set) whose response don't have a fixed
|
||||
RESP3 type. Expecting an \c int and receiving a blob-string
|
||||
will result in error.
|
||||
@li RESP3 aggregates that contain nested aggregates can't be read in STL containers.
|
||||
@li Transactions with a dynamic number of commands can't be read in a \c std::tuple.
|
||||
|
||||
To deal with these cases Aedis provides the \c resp3::node
|
||||
type, that is the most general form of an element in a response,
|
||||
be it a simple RESP3 type or an aggregate. It is defined like this
|
||||
|
||||
@code
|
||||
template <class String>
|
||||
struct node {
|
||||
// The RESP3 type of the data in this node.
|
||||
type data_type;
|
||||
|
||||
// The number of elements of an aggregate (or 1 for simple data).
|
||||
std::size_t aggregate_size;
|
||||
|
||||
// The depth of this node in the response tree.
|
||||
std::size_t depth;
|
||||
|
||||
// The actual data. For aggregate types this is always empty.
|
||||
String value;
|
||||
};
|
||||
@endcode
|
||||
|
||||
Any response to a Redis command can be received in a \c
|
||||
std::vector<node<std::string>>. The vector can be seen as a
|
||||
pre-order view of the response tree
|
||||
(https://en.wikipedia.org/wiki/Tree_traversal#Pre-order,_NLR).
|
||||
Using it is no different that using other types
|
||||
|
||||
@code
|
||||
// Receives any RESP3 simple data type.
|
||||
node<std::string> resp;
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(resp));
|
||||
|
||||
// Receives any RESP3 simple or aggregate data type.
|
||||
std::vector<node<std::string>> resp;
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(resp));
|
||||
@endcode
|
||||
|
||||
For example, suppose we want to retrieve a hash data structure
|
||||
from Redis with \c hgetall, some of the options are
|
||||
|
||||
@li \c std::vector<node<std::string>: Works always.
|
||||
@li \c std::vector<std::string>: Efficient and flat, all elements as string.
|
||||
@li \c std::map<std::string, std::string>: Efficient if you need the data as a \c std::map
|
||||
@li \c std::map<U, V>: Efficient if you are storing serialized data. Avoids temporaries and requires \c from_bulk for \c U and \c V.
|
||||
|
||||
In addition to the above users can also use unordered versions of the containers. The same reasoning also applies to sets e.g. \c smembers.
|
||||
|
||||
\section examples Examples
|
||||
|
||||
The examples listed below cover most use cases presented in the documentation above.
|
||||
|
||||
@li intro.cpp: Basic steps with Aedis.
|
||||
@li containers.cpp: Shows how to send and receive stl containers.
|
||||
@li serialization.cpp: Shows the \c request support to serialization of user types.
|
||||
@li subscriber.cpp: Shows how channel subscription works.
|
||||
@li echo_server.cpp: A simple TCP echo server that users coroutines.
|
||||
@li chat_room.cpp: A simple chat room that uses coroutines.
|
||||
|
||||
\section using-aedis Using Aedis
|
||||
|
||||
To install and use Aedis you will need
|
||||
|
||||
- Boost 1.78 or greater.
|
||||
- Unix Shell and Make (for linux users).
|
||||
- C++14. Some examples require C++20 with coroutine support.
|
||||
- Redis server.
|
||||
|
||||
Some examples will also require interaction with
|
||||
|
||||
- redis-cli: Used in one example.
|
||||
- Redis Sentinel Server: used in some examples.
|
||||
|
||||
Aedis has been tested with the following compilers
|
||||
|
||||
- Tested with gcc: 7.5.0, 8.4.0, 9.3.0, 10.3.0.
|
||||
- Tested with clang: 11.0.0, 10.0.0, 9.0.1, 8.0.1, 7.0.1.
|
||||
|
||||
\section Installation
|
||||
|
||||
The first thing to do is to download and unpack Aedis
|
||||
|
||||
```
|
||||
# Download the latest release on github
|
||||
$ wget https://github.com/mzimbres/aedis/releases
|
||||
|
||||
# Uncompress the tarball and cd into the dir
|
||||
$ tar -xzvf aedis-version.tar.gz
|
||||
```
|
||||
|
||||
If you can't use \c configure and \c make (e.g. Windows users)
|
||||
add the directory where you unpacked Aedis to the
|
||||
include directories in your project, otherwise run
|
||||
|
||||
```
|
||||
# See configure --help for all options.
|
||||
$ ./configure --prefix=/opt/aedis-version --with-boost=/opt/boost_1_78_0
|
||||
|
||||
# Install Aedis in the path specified in --prefix
|
||||
$ sudo make install
|
||||
|
||||
```
|
||||
|
||||
and include the following header
|
||||
|
||||
```cpp
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
```
|
||||
|
||||
in exactly one source file in your applications. At this point you
|
||||
can start using Aedis. To build the examples and run the tests run
|
||||
|
||||
```
|
||||
# Build aedis examples.
|
||||
$ make examples
|
||||
|
||||
# Test aedis in your machine.
|
||||
$ make check
|
||||
```
|
||||
|
||||
\section Developers
|
||||
|
||||
To generate the build system run
|
||||
|
||||
```
|
||||
$ autoreconf -i
|
||||
```
|
||||
|
||||
After that you will have a configure script
|
||||
that you can run as explained above, for example, to use a
|
||||
compiler other that the system compiler run
|
||||
|
||||
```
|
||||
$ CC=/opt/gcc-10.2.0/bin/gcc-10.2.0 CXX=/opt/gcc-10.2.0/bin/g++-10.2.0 CXXFLAGS="-g -Wall -Werror" ./configure ...
|
||||
$ make distcheck
|
||||
```
|
||||
|
||||
\section why-aedis Why Aedis
|
||||
|
||||
At the time of this writing there are seventeen Redis clients
|
||||
listed in the [official](https://redis.io/docs/clients/#cpp) list.
|
||||
With so many clients available it is not unlikely that users are
|
||||
asking themselves why yet another one. In this section I will try
|
||||
to compare Aedis to the most popular clients and why we need
|
||||
Aedis. Notice however that this is ongoing work as comparing
|
||||
client objectively is difficult and time consuming.
|
||||
|
||||
The most popular client at the moment of this writing ranked by
|
||||
github stars is
|
||||
|
||||
@li https://github.com/sewenew/redis-plus-plus
|
||||
|
||||
Before we start it is worth mentioning some of the things it does
|
||||
not support
|
||||
|
||||
@li RESP3. Without RESP3 is impossible to support some important Redis features like client side caching, among other things.
|
||||
@li The Asio asynchronous model.
|
||||
@li Reading response diretly in user data structures avoiding temporaries.
|
||||
@li Error handling with error-code and exception overloads.
|
||||
@li Healthy checks.
|
||||
|
||||
The remaining points will be addressed individually.
|
||||
|
||||
@subsection redis-plus-plus
|
||||
|
||||
Let us first have a look at what sending a command a pipeline and a
|
||||
transaction look like
|
||||
|
||||
@code
|
||||
auto redis = Redis("tcp://127.0.0.1:6379");
|
||||
|
||||
// Send commands
|
||||
redis.set("key", "val");
|
||||
auto val = redis.get("key"); // val is of type OptionalString.
|
||||
if (val)
|
||||
std::cout << *val << std::endl;
|
||||
|
||||
// Sending pipelines
|
||||
auto pipe = redis.pipeline();
|
||||
auto pipe_replies = pipe.set("key", "value")
|
||||
.get("key")
|
||||
.rename("key", "new-key")
|
||||
.rpush("list", {"a", "b", "c"})
|
||||
.lrange("list", 0, -1)
|
||||
.exec();
|
||||
|
||||
// Parse reply with reply type and index.
|
||||
auto set_cmd_result = pipe_replies.get<bool>(0);
|
||||
// ...
|
||||
|
||||
// Sending a transaction
|
||||
auto tx = redis.transaction();
|
||||
auto tx_replies = tx.incr("num0")
|
||||
.incr("num1")
|
||||
.mget({"num0", "num1"})
|
||||
.exec();
|
||||
|
||||
auto incr_result0 = tx_replies.get<long long>(0);
|
||||
// ...
|
||||
@endcode
|
||||
|
||||
Some of the problems with this API are
|
||||
|
||||
@li Heterogeneous treatment of commands, pipelines and transaction.
|
||||
@li Any Api that sends individual commands has a very restricted scope of usability and should be avoided for performance reasons.
|
||||
@li The API imposes exceptions on users, no error-code overload is provided.
|
||||
@li No way to reuse the buffer for new calls to e.g. \c redis.get in order to avoid further dynamic memory allocations.
|
||||
@li Error handling of resolve and connection no clear.
|
||||
|
||||
According to the documentation, pipelines in redis-plus-plus have
|
||||
the following characteristics
|
||||
|
||||
> NOTE: By default, creating a Pipeline object is NOT cheap, since
|
||||
> it creates a new connection.
|
||||
|
||||
This is clearly a downside of the API as pipelines should be the
|
||||
default way of communicating and not an exception, paying such a
|
||||
high price for each pipeline imposes a severe cost in performance.
|
||||
Transactions also suffer from the very same problem
|
||||
|
||||
> NOTE: Creating a Transaction object is NOT cheap, since it
|
||||
> creates a new connection.
|
||||
|
||||
In Aedis there is no difference between sending one command, a
|
||||
pipeline or a transaction because creating the request is decoupled
|
||||
from the IO objects, for example
|
||||
|
||||
@code
|
||||
std::string request;
|
||||
auto sr = make_serializer(request);
|
||||
sr.push("HELLO", 3);
|
||||
sr.push("MULTI");
|
||||
sr.push("PING");
|
||||
sr.push("SET", "low-level-key", "some content", "EX", "2");
|
||||
sr.push("EXEC");
|
||||
sr.push("PING", "Another message.");
|
||||
net::write(socket, net::buffer(request));
|
||||
@endcode
|
||||
|
||||
The request created above will be sent to Redis in a single
|
||||
pipeline and imposes no restriction on what it contains e.g. the
|
||||
number of commands, transactions etc. The problems mentioned above
|
||||
simply do not exist in Aedis. The way responses are read is
|
||||
also more flexible
|
||||
|
||||
@code
|
||||
std::string buffer;
|
||||
auto dbuffer = net::dynamic_buffer(buffer);
|
||||
|
||||
std::tuple<std::string, boost::optional<std::string>> response;
|
||||
resp3::read(socket, dbuffer); // hellp
|
||||
resp3::read(socket, dbuffer); // multi
|
||||
resp3::read(socket, dbuffer); // ping
|
||||
resp3::read(socket, dbuffer); // set
|
||||
resp3::read(socket, dbuffer, adapt(response));
|
||||
resp3::read(socket, dbuffer); // quit
|
||||
@endcode
|
||||
|
||||
@li The response objects are passed by the caller to the read
|
||||
functions so that he has fine control over memory allocations and
|
||||
object lifetime.
|
||||
@li The user can either use error-code or exceptions.
|
||||
@li Each response can be read individually in the response object
|
||||
avoiding temporaries.
|
||||
@li It is possible to ignore responses.
|
||||
|
||||
This was the blocking API, now let us compare the async interface
|
||||
|
||||
> redis-plus-plus also supports async interface, however, async
|
||||
> support for Transaction and Subscriber is still on the way.
|
||||
>
|
||||
> The async interface depends on third-party event library, and so
|
||||
> far, only libuv is supported.
|
||||
|
||||
Async code in redis-plus-plus looks like the following
|
||||
|
||||
@code
|
||||
auto async_redis = AsyncRedis(opts, pool_opts);
|
||||
|
||||
Future<string> ping_res = async_redis.ping();
|
||||
|
||||
cout << ping_res.get() << endl;
|
||||
@endcode
|
||||
|
||||
As the reader can see, the async interface is based on futures
|
||||
which is also known to have a bad performance. The biggest
|
||||
problem however with this async design is that it makes it
|
||||
impossible to write asynchronous programs correctly since it
|
||||
starts an async operation on every command sent instead of
|
||||
enqueueing a message and triggering a write when it can be sent.
|
||||
It is also not clear how are pipelines realised with the design
|
||||
(if at all).
|
||||
|
||||
\section Acknowledgement
|
||||
|
||||
Some people that were helpful in the development of Aedis
|
||||
|
||||
@li Richard Hodges ([madmongo1](https://github.com/madmongo1)): For helping me with Asio and the design of asynchronous programs in general.
|
||||
@li Vinícius dos Santos Oliveira ([vinipsmaker](https://github.com/vinipsmaker)): For useful discussion about how Aedis consumes buffers in the read operation (among other things).
|
||||
@li Petr Dannhofer ([Eddie-cz](https://github.com/Eddie-cz)): For helping me understand how the `AUTH` and `HELLO` command can influence each other.
|
||||
|
||||
\section Reference
|
||||
|
||||
See \subpage any.
|
||||
|
||||
*/
|
||||
|
||||
/** \defgroup any Reference
|
||||
*
|
||||
* This page contains the documentation of all user facing code.
|
||||
*/
|
||||
|
||||
#endif // AEDIS_HPP
|
||||
@@ -1,472 +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_CONNECTION_HPP
|
||||
#define AEDIS_CONNECTION_HPP
|
||||
|
||||
#include <vector>
|
||||
#include <queue>
|
||||
#include <limits>
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
#include <boost/asio/experimental/channel.hpp>
|
||||
|
||||
#include <aedis/adapt.hpp>
|
||||
#include <aedis/resp3/request.hpp>
|
||||
#include <aedis/detail/connection_ops.hpp>
|
||||
|
||||
namespace aedis {
|
||||
|
||||
// https://redis.io/docs/reference/sentinel-clients
|
||||
|
||||
/** \brief A high level Redis connection.
|
||||
* \ingroup any
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
template <class AsyncReadWriteStream = boost::asio::ip::tcp::socket>
|
||||
class connection {
|
||||
public:
|
||||
/// Executor type.
|
||||
using executor_type = typename AsyncReadWriteStream::executor_type;
|
||||
|
||||
/// Type of the next layer
|
||||
using next_layer_type = AsyncReadWriteStream;
|
||||
|
||||
using default_completion_token_type = boost::asio::default_completion_token_t<executor_type>;
|
||||
|
||||
/** @brief Configuration parameters.
|
||||
*/
|
||||
struct config {
|
||||
/// Timeout of the resolve operation.
|
||||
std::chrono::milliseconds resolve_timeout = std::chrono::seconds{10};
|
||||
|
||||
/// Timeout of the connect operation.
|
||||
std::chrono::milliseconds connect_timeout = std::chrono::seconds{10};
|
||||
|
||||
/// Time interval ping operations.
|
||||
std::chrono::milliseconds ping_interval = std::chrono::seconds{1};
|
||||
|
||||
/// The maximum size allowed of read operations.
|
||||
std::size_t max_read_size = (std::numeric_limits<std::size_t>::max)();
|
||||
|
||||
/// Whether to coalesce requests or not.
|
||||
bool coalesce_requests = true;
|
||||
};
|
||||
|
||||
/** \brief Constructor.
|
||||
*
|
||||
* \param ex The executor.
|
||||
* \param cfg Configuration parameters.
|
||||
*/
|
||||
connection(boost::asio::any_io_executor ex, config cfg = config{})
|
||||
: resv_{ex}
|
||||
, ping_timer_{ex}
|
||||
, check_idle_timer_{ex}
|
||||
, writer_timer_{ex}
|
||||
, read_timer_{ex}
|
||||
, push_channel_{ex}
|
||||
, cfg_{cfg}
|
||||
, last_data_{std::chrono::time_point<std::chrono::steady_clock>::min()}
|
||||
{
|
||||
writer_timer_.expires_at(std::chrono::steady_clock::time_point::max());
|
||||
read_timer_.expires_at(std::chrono::steady_clock::time_point::max());
|
||||
}
|
||||
|
||||
connection(boost::asio::io_context& ioc, config cfg = config{})
|
||||
: connection(ioc.get_executor(), cfg)
|
||||
{ }
|
||||
|
||||
/// Returns the executor.
|
||||
auto get_executor() {return resv_.get_executor();}
|
||||
|
||||
/** @brief Starts communication with the Redis server asynchronously.
|
||||
*
|
||||
* This function performs the following steps
|
||||
*
|
||||
* \li Resolves the Redis host as of \c async_resolve with the
|
||||
* timeout passed in connection::config::resolve_timeout.
|
||||
*
|
||||
* \li Connects to one of the endpoints returned by the resolve
|
||||
* operation with the timeout passed in connection::config::connect_timeout.
|
||||
*
|
||||
* \li Starts the idle check operation with the timeout of twice
|
||||
* the value of connection::config::ping_interval. If no data is
|
||||
* received during that time interval \c async_run completes with
|
||||
* error::idle_timeout.
|
||||
*
|
||||
* \li Starts the healthy check operation that sends command::ping
|
||||
* to Redis with a frequency equal to
|
||||
* connection::config::ping_interval.
|
||||
*
|
||||
* \li Starts reading from the socket and delivering events to the
|
||||
* request started with \c async_exec or \c async_read_push.
|
||||
*
|
||||
* For an example see echo_server.cpp.
|
||||
*
|
||||
* \param host Redis address.
|
||||
* \param port Redis port.
|
||||
* \param token Completion token.
|
||||
*
|
||||
* The completion token must have the following signature
|
||||
*
|
||||
* @code
|
||||
* void f(boost::system::error_code);
|
||||
* @endcode
|
||||
*
|
||||
* \return This function returns only when there is an error.
|
||||
*/
|
||||
template <class CompletionToken = default_completion_token_type>
|
||||
auto
|
||||
async_run(
|
||||
boost::string_view host = "127.0.0.1",
|
||||
boost::string_view port = "6379",
|
||||
CompletionToken token = CompletionToken{})
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code)
|
||||
>(detail::run_op<connection>{this, host, port}, token, resv_);
|
||||
}
|
||||
|
||||
/** @brief Executes a request on the redis server.
|
||||
*
|
||||
* \param req Request object.
|
||||
* \param adapter Response adapter.
|
||||
* \param token Asio completion token.
|
||||
*
|
||||
* For an example see containers.cpp. The completion token must
|
||||
* have the following signature
|
||||
*
|
||||
* @code
|
||||
* void f(boost::system::error_code, std::size_t);
|
||||
* @endcode
|
||||
*
|
||||
* Where the second parameter is the size of the response that has
|
||||
* just been read.
|
||||
*/
|
||||
template <
|
||||
class Adapter = detail::response_traits<void>::adapter_type,
|
||||
class CompletionToken = default_completion_token_type>
|
||||
auto async_exec(
|
||||
resp3::request const& req,
|
||||
Adapter adapter = adapt(),
|
||||
CompletionToken token = CompletionToken{})
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code, std::size_t)
|
||||
>(detail::exec_op<connection, Adapter>{this, &req, adapter}, token, resv_);
|
||||
}
|
||||
|
||||
/** @brief Connects and executes a single request.
|
||||
*
|
||||
* Combines \c async_run and the other \c async_exec overload in a
|
||||
* single function. This function is useful for users that want to
|
||||
* send a single request to the server.
|
||||
*
|
||||
* \param host Address of the Redis server.
|
||||
* \param port Port of the Redis server.
|
||||
* \param req Request object.
|
||||
* \param adapter Response adapter.
|
||||
* \param token Asio completion token.
|
||||
*
|
||||
* For an example see intro.cpp. The completion token must have the following signature
|
||||
*
|
||||
* @code
|
||||
* void f(boost::system::error_code, std::size_t);
|
||||
* @endcode
|
||||
*
|
||||
* Where the second parameter is the size of the response that has
|
||||
* just been read.
|
||||
*/
|
||||
template <
|
||||
class Adapter = detail::response_traits<void>::adapter_type,
|
||||
class CompletionToken = default_completion_token_type>
|
||||
auto async_exec(
|
||||
boost::string_view host,
|
||||
boost::string_view port,
|
||||
resp3::request const& req,
|
||||
Adapter adapter = adapt(),
|
||||
CompletionToken token = CompletionToken{})
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code, std::size_t)
|
||||
>(detail::runexec_op<connection, Adapter>
|
||||
{this, host, port, &req, adapter}, token, resv_);
|
||||
}
|
||||
|
||||
/** @brief Receives Redis unsolicited events like pushes.
|
||||
*
|
||||
* Users that expect unsolicited events should call this function
|
||||
* in a loop. If an unsolicited events comes in and there is no
|
||||
* reader, the connection will hang and eventually timeout.
|
||||
*
|
||||
* \param adapter The response adapter.
|
||||
* \param token The Asio completion token.
|
||||
*
|
||||
* For an example see subscriber.cpp. The completion token must
|
||||
* have the following signature
|
||||
*
|
||||
* @code
|
||||
* void f(boost::system::error_code, std::size_t);
|
||||
* @endcode
|
||||
*
|
||||
* Where the second parameter is the size of the response that has
|
||||
* just been read.
|
||||
*/
|
||||
template <
|
||||
class Adapter = detail::response_traits<void>::adapter_type,
|
||||
class CompletionToken = default_completion_token_type>
|
||||
auto async_read_push(
|
||||
Adapter adapter = adapt(),
|
||||
CompletionToken token = CompletionToken{})
|
||||
{
|
||||
auto f =
|
||||
[adapter]
|
||||
(resp3::node<boost::string_view> const& node, boost::system::error_code& ec) mutable
|
||||
{
|
||||
adapter(std::size_t(-1), node, ec);
|
||||
};
|
||||
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code, std::size_t)
|
||||
>(detail::read_push_op<connection, decltype(f)>{this, f}, token, resv_);
|
||||
}
|
||||
|
||||
/** @brief Cancel all pending sessions and push operations to return
|
||||
*
|
||||
* \returns The number of requests that have been canceled.
|
||||
*/
|
||||
std::size_t cancel_requests()
|
||||
{
|
||||
for (auto& e: reqs_) {
|
||||
e->stop = true;
|
||||
e->timer.cancel_one();
|
||||
}
|
||||
|
||||
auto const ret = reqs_.size();
|
||||
reqs_ = {};
|
||||
return ret;
|
||||
}
|
||||
|
||||
/** @brief Closes the connection with the database.
|
||||
*
|
||||
* Calling this function will cause \c async_run to return. It is
|
||||
* safe to try a reconnect after that i.e. calling it again.
|
||||
*
|
||||
* Note however that the prefered way to close a connection is to
|
||||
* send a \c quit command if you are actively closing it.
|
||||
* Otherwise an unresponsive Redis server will cause the
|
||||
* idle-checks to fail, which will also lead to \c async_run
|
||||
* returning.
|
||||
*
|
||||
* @remark This function won't cancel pending requests, see
|
||||
* \c cancel_requests.
|
||||
*/
|
||||
void cancel_run()
|
||||
{
|
||||
socket_->close();
|
||||
read_timer_.cancel();
|
||||
check_idle_timer_.cancel();
|
||||
writer_timer_.cancel();
|
||||
ping_timer_.cancel();
|
||||
|
||||
// TODO: How to avoid calling this here.
|
||||
push_channel_.cancel();
|
||||
|
||||
// Cancel own pings if there is any waiting.
|
||||
auto point = std::stable_partition(std::begin(reqs_), std::end(reqs_), [](auto const& ptr) {
|
||||
return !ptr->req->is_internal();
|
||||
});
|
||||
|
||||
std::for_each(point, std::end(reqs_), [](auto const& ptr) {
|
||||
ptr->stop = true;
|
||||
ptr->timer.cancel();
|
||||
});
|
||||
|
||||
reqs_.erase(point, std::end(reqs_));
|
||||
}
|
||||
|
||||
private:
|
||||
struct req_info {
|
||||
req_info(boost::asio::any_io_executor ex) : timer{ex} {}
|
||||
boost::asio::steady_timer timer;
|
||||
resp3::request const* req = nullptr;
|
||||
std::size_t cmds = 0;
|
||||
bool stop = false;
|
||||
};
|
||||
|
||||
using time_point_type = std::chrono::time_point<std::chrono::steady_clock>;
|
||||
using reqs_type = std::deque<std::shared_ptr<req_info>>;
|
||||
|
||||
template <class T, class U> friend struct detail::read_push_op;
|
||||
template <class T> friend struct detail::reader_op;
|
||||
template <class T> friend struct detail::writer_op;
|
||||
template <class T> friend struct detail::ping_op;
|
||||
template <class T> friend struct detail::run_op;
|
||||
template <class T, class U> friend struct detail::exec_op;
|
||||
template <class T, class U> friend struct detail::exec_read_op;
|
||||
template <class T, class U> friend struct detail::runexec_op;
|
||||
template <class T> friend struct detail::connect_with_timeout_op;
|
||||
template <class T> friend struct detail::resolve_with_timeout_op;
|
||||
template <class T> friend struct detail::check_idle_op;
|
||||
template <class T> friend struct detail::start_op;
|
||||
|
||||
void cancel_push_requests(typename reqs_type::iterator end)
|
||||
{
|
||||
auto point = std::stable_partition(std::begin(reqs_), end, [](auto const& ptr) {
|
||||
return ptr->req->commands() != 0;
|
||||
});
|
||||
|
||||
std::for_each(point, end, [](auto const& ptr) {
|
||||
ptr->timer.cancel();
|
||||
});
|
||||
|
||||
reqs_.erase(point, end);
|
||||
}
|
||||
|
||||
void add_request_info(std::shared_ptr<req_info> const& info)
|
||||
{
|
||||
reqs_.push_back(info);
|
||||
if (socket_ != nullptr && socket_->is_open() && cmds_ == 0 && write_buffer_.empty())
|
||||
writer_timer_.cancel();
|
||||
}
|
||||
|
||||
auto make_dynamic_buffer()
|
||||
{ return boost::asio::dynamic_buffer(read_buffer_, cfg_.max_read_size); }
|
||||
|
||||
template <class CompletionToken>
|
||||
auto
|
||||
async_resolve_with_timeout(
|
||||
boost::string_view host,
|
||||
boost::string_view port,
|
||||
CompletionToken&& token)
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code)
|
||||
>(detail::resolve_with_timeout_op<connection>{this, host, port},
|
||||
token, resv_);
|
||||
}
|
||||
|
||||
template <class CompletionToken>
|
||||
auto async_connect_with_timeout(CompletionToken&& token)
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code)
|
||||
>(detail::connect_with_timeout_op<connection>{this}, token, resv_);
|
||||
}
|
||||
|
||||
// Loops on async_read_with_timeout described above.
|
||||
template <class CompletionToken>
|
||||
auto reader(CompletionToken&& token)
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code)
|
||||
>(detail::reader_op<connection>{this}, token, resv_.get_executor());
|
||||
}
|
||||
|
||||
template <class CompletionToken>
|
||||
auto writer(CompletionToken&& token)
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code)
|
||||
>(detail::writer_op<connection>{this}, token, resv_.get_executor());
|
||||
}
|
||||
|
||||
template <class CompletionToken>
|
||||
auto
|
||||
async_start(CompletionToken&& token)
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code)
|
||||
>(detail::start_op<connection>{this}, token, resv_);
|
||||
}
|
||||
|
||||
template <class CompletionToken>
|
||||
auto async_ping(CompletionToken&& token)
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code)
|
||||
>(detail::ping_op<connection>{this}, token, resv_);
|
||||
}
|
||||
|
||||
template <class CompletionToken>
|
||||
auto async_check_idle(CompletionToken&& token)
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code)
|
||||
>(detail::check_idle_op<connection>{this}, token, check_idle_timer_);
|
||||
}
|
||||
|
||||
template <class Adapter, class CompletionToken>
|
||||
auto async_exec_read(Adapter adapter, CompletionToken token)
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code, std::size_t)
|
||||
>(detail::exec_read_op<connection, Adapter>{this, adapter}, token, resv_);
|
||||
}
|
||||
|
||||
void coalesce_requests()
|
||||
{
|
||||
// Coaleces all requests: Copies the request to the variables
|
||||
// that won't be touched while async_write is suspended.
|
||||
BOOST_ASSERT(write_buffer_.empty());
|
||||
BOOST_ASSERT(!reqs_.empty());
|
||||
|
||||
auto const size = cfg_.coalesce_requests ? reqs_.size() : 1;
|
||||
for (auto i = 0UL; i < size; ++i) {
|
||||
write_buffer_ += reqs_.at(i)->req->payload();
|
||||
cmds_ += reqs_.at(i)->req->commands();
|
||||
}
|
||||
}
|
||||
|
||||
using channel_type = boost::asio::experimental::channel<void(boost::system::error_code, std::size_t)>;
|
||||
|
||||
// IO objects
|
||||
boost::asio::ip::tcp::resolver resv_;
|
||||
std::shared_ptr<AsyncReadWriteStream> socket_;
|
||||
boost::asio::steady_timer ping_timer_;
|
||||
boost::asio::steady_timer check_idle_timer_;
|
||||
boost::asio::steady_timer writer_timer_;
|
||||
boost::asio::steady_timer read_timer_;
|
||||
channel_type push_channel_;
|
||||
|
||||
config cfg_;
|
||||
std::string read_buffer_;
|
||||
std::string write_buffer_;
|
||||
std::size_t cmds_ = 0;
|
||||
reqs_type reqs_;
|
||||
|
||||
// Last time we received data.
|
||||
time_point_type last_data_;
|
||||
|
||||
// The result of async_resolve.
|
||||
boost::asio::ip::tcp::resolver::results_type endpoints_;
|
||||
|
||||
resp3::request req_;
|
||||
};
|
||||
|
||||
} // aedis
|
||||
|
||||
#endif // AEDIS_CONNECTION_HPP
|
||||
@@ -1,563 +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_CONNECTION_OPS_HPP
|
||||
#define AEDIS_CONNECTION_OPS_HPP
|
||||
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
|
||||
#include <boost/assert.hpp>
|
||||
#include <boost/system.hpp>
|
||||
#include <boost/asio/write.hpp>
|
||||
#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/exec.hpp>
|
||||
#include <aedis/resp3/write.hpp>
|
||||
#include <aedis/resp3/request.hpp>
|
||||
|
||||
#define HANDLER_LOCATION \
|
||||
BOOST_ASIO_HANDLER_LOCATION((__FILE__, __LINE__, __func__))
|
||||
|
||||
namespace aedis {
|
||||
namespace detail {
|
||||
|
||||
#include <boost/asio/yield.hpp>
|
||||
|
||||
template <class Conn>
|
||||
struct connect_with_timeout_op {
|
||||
Conn* conn;
|
||||
boost::asio::coroutine coro;
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, boost::system::error_code ec = {}
|
||||
, boost::asio::ip::tcp::endpoint const& ep = {})
|
||||
{
|
||||
reenter (coro)
|
||||
{
|
||||
BOOST_ASSERT(conn->socket_ != nullptr);
|
||||
conn->ping_timer_.expires_after(conn->cfg_.connect_timeout);
|
||||
yield aedis::detail::async_connect(*conn->socket_, conn->ping_timer_, conn->endpoints_, std::move(self));
|
||||
self.complete(ec);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Conn>
|
||||
struct resolve_with_timeout_op {
|
||||
Conn* conn;
|
||||
boost::string_view host;
|
||||
boost::string_view port;
|
||||
boost::asio::coroutine coro;
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, boost::system::error_code ec = {}
|
||||
, boost::asio::ip::tcp::resolver::results_type res = {})
|
||||
{
|
||||
reenter (coro)
|
||||
{
|
||||
conn->ping_timer_.expires_after(conn->cfg_.resolve_timeout);
|
||||
yield aedis::detail::async_resolve(conn->resv_, conn->ping_timer_, host, port, std::move(self));
|
||||
conn->endpoints_ = res;
|
||||
self.complete(ec);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Conn, class Adapter>
|
||||
struct read_push_op {
|
||||
Conn* conn;
|
||||
Adapter adapter;
|
||||
std::size_t read_size;
|
||||
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));
|
||||
if (ec) {
|
||||
self.complete(ec, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
BOOST_ASSERT(conn->socket_ != nullptr);
|
||||
yield resp3::async_read(*conn->socket_, conn->make_dynamic_buffer(), adapter, std::move(self));
|
||||
if (ec) {
|
||||
conn->cancel_run();
|
||||
self.complete(ec, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
read_size = n;
|
||||
|
||||
yield conn->push_channel_.async_send({}, 0, std::move(self));
|
||||
self.complete(ec, read_size);
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Conn, class Adapter>
|
||||
struct exec_read_op {
|
||||
Conn* conn;
|
||||
Adapter adapter;
|
||||
std::size_t read_size = 0;
|
||||
std::size_t index = 0;
|
||||
boost::asio::coroutine coro;
|
||||
|
||||
template <class Self>
|
||||
void
|
||||
operator()( Self& self
|
||||
, boost::system::error_code ec = {}
|
||||
, std::size_t n = 0)
|
||||
{
|
||||
reenter (coro)
|
||||
{
|
||||
// Loop reading the responses to this request.
|
||||
BOOST_ASSERT(!conn->reqs_.empty());
|
||||
while (conn->reqs_.front()->cmds != 0) {
|
||||
BOOST_ASSERT(conn->cmds_ != 0);
|
||||
|
||||
//-----------------------------------
|
||||
// If we detect a push in the middle of a request we have
|
||||
// to hand it to the push consumer. To do that we need
|
||||
// some data in the read bufer.
|
||||
if (conn->read_buffer_.empty()) {
|
||||
BOOST_ASSERT(conn->socket_ != nullptr);
|
||||
yield boost::asio::async_read_until(*conn->socket_, conn->make_dynamic_buffer(), "\r\n", std::move(self));
|
||||
if (ec) {
|
||||
conn->cancel_run();
|
||||
self.complete(ec, 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If the next request is a push we have to handle it to
|
||||
// the read_push_op wait for it to be done and continue.
|
||||
if (resp3::to_type(conn->read_buffer_.front()) == resp3::type::push) {
|
||||
yield async_send_receive(conn->push_channel_, std::move(self));
|
||||
if (ec) {
|
||||
// Notice we don't call cancel_run() as that is the
|
||||
// responsability of the read_push_op.
|
||||
self.complete(ec, 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
//-----------------------------------
|
||||
|
||||
yield
|
||||
resp3::async_read(*conn->socket_, conn->make_dynamic_buffer(),
|
||||
[i = index, adpt = adapter] (resp3::node<boost::string_view> const& nd, boost::system::error_code& ec) mutable { adpt(i, nd, ec); },
|
||||
std::move(self));
|
||||
|
||||
++index;
|
||||
|
||||
if (ec) {
|
||||
conn->cancel_run();
|
||||
self.complete(ec, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
read_size += n;
|
||||
|
||||
BOOST_ASSERT(conn->reqs_.front()->cmds != 0);
|
||||
--conn->reqs_.front()->cmds;
|
||||
|
||||
BOOST_ASSERT(conn->cmds_ != 0);
|
||||
--conn->cmds_;
|
||||
}
|
||||
|
||||
self.complete({}, read_size);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Conn, class Adapter>
|
||||
struct exec_op {
|
||||
using req_info_type = typename Conn::req_info;
|
||||
|
||||
Conn* conn;
|
||||
resp3::request const* req;
|
||||
Adapter adapter;
|
||||
std::shared_ptr<req_info_type> info;
|
||||
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)
|
||||
{
|
||||
info = std::allocate_shared<req_info_type>(boost::asio::get_associated_allocator(self), conn->resv_.get_executor());
|
||||
info->timer.expires_at(std::chrono::steady_clock::time_point::max());
|
||||
info->req = req;
|
||||
info->cmds = req->commands();
|
||||
info->stop = false;
|
||||
|
||||
conn->add_request_info(info);
|
||||
yield info->timer.async_wait(std::move(self));
|
||||
BOOST_ASSERT(conn->socket_ != nullptr);
|
||||
BOOST_ASSERT(!!ec);
|
||||
if (info->stop) {
|
||||
self.complete(ec, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
BOOST_ASSERT(conn->socket_->is_open());
|
||||
|
||||
if (req->commands() == 0) {
|
||||
self.complete({}, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
yield conn->async_exec_read(adapter, std::move(self));
|
||||
if (ec) {
|
||||
self.complete(ec, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
read_size = n;
|
||||
|
||||
BOOST_ASSERT(!conn->reqs_.empty());
|
||||
conn->reqs_.pop_front();
|
||||
|
||||
if (conn->cmds_ == 0) {
|
||||
conn->read_timer_.cancel();
|
||||
if (!conn->reqs_.empty())
|
||||
conn->writer_timer_.cancel();
|
||||
} else {
|
||||
BOOST_ASSERT(!conn->reqs_.empty());
|
||||
conn->reqs_.front()->timer.cancel_one();
|
||||
}
|
||||
|
||||
self.complete({}, read_size);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Conn>
|
||||
struct ping_op {
|
||||
Conn* conn;
|
||||
boost::asio::coroutine coro;
|
||||
|
||||
template <class Self>
|
||||
void
|
||||
operator()( Self& self
|
||||
, boost::system::error_code ec = {}
|
||||
, std::size_t read_size = 0)
|
||||
{
|
||||
reenter (coro) for (;;)
|
||||
{
|
||||
conn->ping_timer_.expires_after(conn->cfg_.ping_interval);
|
||||
yield conn->ping_timer_.async_wait(std::move(self));
|
||||
BOOST_ASSERT(conn->socket_ != nullptr);
|
||||
if (ec || !conn->socket_->is_open()) {
|
||||
self.complete(ec);
|
||||
return;
|
||||
}
|
||||
|
||||
conn->req_.clear();
|
||||
conn->req_.push("PING");
|
||||
conn->req_.set_internal();
|
||||
yield conn->async_exec(conn->req_, aedis::adapt(), std::move(self));
|
||||
if (ec) {
|
||||
// Notice we don't report error but let the idle check
|
||||
// timeout. It is enough to finish the op.
|
||||
self.complete({});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Conn>
|
||||
struct check_idle_op {
|
||||
Conn* conn;
|
||||
boost::asio::coroutine coro;
|
||||
|
||||
template <class Self>
|
||||
void operator()(Self& self, boost::system::error_code ec = {})
|
||||
{
|
||||
reenter (coro) for (;;)
|
||||
{
|
||||
conn->check_idle_timer_.expires_after(2 * conn->cfg_.ping_interval);
|
||||
yield conn->check_idle_timer_.async_wait(std::move(self));
|
||||
BOOST_ASSERT(conn->socket_ != nullptr);
|
||||
if (ec || !conn->socket_->is_open()) {
|
||||
// Notice this is not an error, it was requested from an
|
||||
// external op.
|
||||
self.complete({});
|
||||
return;
|
||||
}
|
||||
|
||||
auto const now = std::chrono::steady_clock::now();
|
||||
if (conn->last_data_ + (2 * conn->cfg_.ping_interval) < now) {
|
||||
conn->cancel_run();
|
||||
self.complete(error::idle_timeout);
|
||||
return;
|
||||
}
|
||||
|
||||
conn->last_data_ = now;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Conn>
|
||||
struct start_op {
|
||||
Conn* conn;
|
||||
boost::asio::coroutine coro;
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, std::array<std::size_t, 4> order = {}
|
||||
, boost::system::error_code ec0 = {}
|
||||
, boost::system::error_code ec1 = {}
|
||||
, boost::system::error_code ec2 = {}
|
||||
, boost::system::error_code ec3 = {})
|
||||
{
|
||||
reenter (coro)
|
||||
{
|
||||
yield
|
||||
boost::asio::experimental::make_parallel_group(
|
||||
[this](auto token) { return conn->reader(token);},
|
||||
[this](auto token) { return conn->writer(token);},
|
||||
[this](auto token) { return conn->async_check_idle(token);},
|
||||
[this](auto token) { return conn->async_ping(token);}
|
||||
).async_wait(
|
||||
boost::asio::experimental::wait_for_one_error(),
|
||||
std::move(self));
|
||||
|
||||
switch (order[0]) {
|
||||
case 0:
|
||||
{
|
||||
self.complete(ec0);
|
||||
} break;
|
||||
case 1:
|
||||
{
|
||||
self.complete(ec1);
|
||||
} break;
|
||||
case 2:
|
||||
{
|
||||
self.complete(ec2);
|
||||
} break;
|
||||
case 3:
|
||||
{
|
||||
self.complete(ec3);
|
||||
} break;
|
||||
default: BOOST_ASSERT(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Conn>
|
||||
struct run_op {
|
||||
Conn* conn;
|
||||
boost::string_view host;
|
||||
boost::string_view port;
|
||||
boost::asio::coroutine coro;
|
||||
|
||||
template <class Self>
|
||||
void operator()(Self& self, boost::system::error_code ec = {})
|
||||
{
|
||||
reenter (coro)
|
||||
{
|
||||
yield conn->async_resolve_with_timeout(host, port, std::move(self));
|
||||
if (ec) {
|
||||
self.complete(ec);
|
||||
return;
|
||||
}
|
||||
|
||||
conn->socket_ = std::make_shared<typename Conn::next_layer_type>(conn->resv_.get_executor());
|
||||
|
||||
yield conn->async_connect_with_timeout(std::move(self));
|
||||
if (ec) {
|
||||
self.complete(ec);
|
||||
return;
|
||||
}
|
||||
|
||||
yield conn->async_start(std::move(self));
|
||||
self.complete(ec);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Conn>
|
||||
struct writer_op {
|
||||
Conn* conn;
|
||||
typename Conn::reqs_type::iterator end;
|
||||
boost::asio::coroutine coro;
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, boost::system::error_code ec = {}
|
||||
, std::size_t n = 0)
|
||||
{
|
||||
boost::ignore_unused(n);
|
||||
|
||||
reenter (coro) for (;;)
|
||||
{
|
||||
while (!conn->reqs_.empty() && conn->cmds_ == 0 && conn->write_buffer_.empty()) {
|
||||
conn->coalesce_requests();
|
||||
end = conn->reqs_.end();
|
||||
yield boost::asio::async_write(*conn->socket_, boost::asio::buffer(conn->write_buffer_), std::move(self));
|
||||
if (ec) {
|
||||
self.complete(ec);
|
||||
return;
|
||||
}
|
||||
|
||||
// We have to clear the payload right after the read op in
|
||||
// order to to use it as a flag that informs there is no
|
||||
// ongoing write.
|
||||
conn->write_buffer_.clear();
|
||||
conn->cancel_push_requests(end);
|
||||
}
|
||||
|
||||
if (conn->socket_->is_open()) {
|
||||
yield conn->writer_timer_.async_wait(std::move(self));
|
||||
// The timer may be canceled either to stop the write op
|
||||
// or to proceed to the next write, the difference between
|
||||
// the two is that for the former the socket will be
|
||||
// closed first. We check for that below.
|
||||
}
|
||||
|
||||
if (!conn->socket_->is_open()) {
|
||||
// Notice this is not an error of the op, stoping was
|
||||
// requested from the outside, so we complete with
|
||||
// success.
|
||||
self.complete({});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Conn>
|
||||
struct reader_op {
|
||||
Conn* conn;
|
||||
boost::asio::coroutine coro;
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, boost::system::error_code ec = {}
|
||||
, std::size_t n = 0)
|
||||
{
|
||||
boost::ignore_unused(n);
|
||||
|
||||
reenter (coro) for (;;)
|
||||
{
|
||||
BOOST_ASSERT(conn->socket_->is_open());
|
||||
yield boost::asio::async_read_until(*conn->socket_, conn->make_dynamic_buffer(), "\r\n", std::move(self));
|
||||
if (ec) {
|
||||
conn->cancel_run();
|
||||
self.complete(ec);
|
||||
return;
|
||||
}
|
||||
|
||||
conn->last_data_ = std::chrono::steady_clock::now();
|
||||
|
||||
// We handle unsolicited events in the following way
|
||||
//
|
||||
// 1. Its resp3 type is a push.
|
||||
//
|
||||
// 2. A non-push type is received with an empty requests
|
||||
// queue. I have noticed this is possible (e.g. -MISCONF).
|
||||
// I expect them to have type push so we can distinguish
|
||||
// them from responses to commands, but it is a
|
||||
// simple-error. If we are lucky enough to receive them
|
||||
// when the command queue is empty we can treat them as
|
||||
// server pushes, otherwise it is impossible to handle
|
||||
// them properly
|
||||
//
|
||||
// 3. The request does not expect any response but we got
|
||||
// one. This may happen if for example, subscribe with
|
||||
// wrong syntax.
|
||||
//
|
||||
BOOST_ASSERT(!conn->read_buffer_.empty());
|
||||
if (resp3::to_type(conn->read_buffer_.front()) == resp3::type::push
|
||||
|| conn->reqs_.empty()
|
||||
|| (!conn->reqs_.empty() && conn->reqs_.front()->cmds == 0)) {
|
||||
yield async_send_receive(conn->push_channel_, std::move(self));
|
||||
if (ec) {
|
||||
self.complete(ec);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
BOOST_ASSERT(conn->cmds_ != 0);
|
||||
BOOST_ASSERT(!conn->reqs_.empty());
|
||||
BOOST_ASSERT(conn->reqs_.front()->cmds != 0);
|
||||
conn->reqs_.front()->timer.cancel();
|
||||
yield conn->read_timer_.async_wait(std::move(self));
|
||||
if (!conn->socket_->is_open()) {
|
||||
self.complete({});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Conn, class Adapter>
|
||||
struct runexec_op {
|
||||
Conn* conn;
|
||||
boost::string_view host;
|
||||
boost::string_view port;
|
||||
resp3::request const* req;
|
||||
Adapter adapter;
|
||||
boost::asio::coroutine coro;
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, std::array<std::size_t, 2> order = {}
|
||||
, boost::system::error_code ec1 = {}
|
||||
, boost::system::error_code ec2 = {}
|
||||
, std::size_t n = 0)
|
||||
{
|
||||
reenter (coro)
|
||||
{
|
||||
yield
|
||||
boost::asio::experimental::make_parallel_group(
|
||||
[this](auto token) { return conn->async_run(host, port, token);},
|
||||
[this](auto token) { return conn->async_exec(*req, adapter, token);}
|
||||
).async_wait(
|
||||
boost::asio::experimental::wait_for_one_error(),
|
||||
std::move(self));
|
||||
|
||||
if (ec2) {
|
||||
self.complete(ec2, n);
|
||||
} else {
|
||||
// If there was no error in the async_exec we complete
|
||||
// with the async_run error, if any.
|
||||
self.complete(ec1, n);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#include <boost/asio/unyield.hpp>
|
||||
|
||||
} // detail
|
||||
} // aedis
|
||||
|
||||
#endif // AEDIS_CONNECTION_OPS_HPP
|
||||
@@ -1,161 +0,0 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#ifndef AEDIS_RESP3_COMPOSE_HPP
|
||||
#define AEDIS_RESP3_COMPOSE_HPP
|
||||
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
|
||||
#include <boost/hana.hpp>
|
||||
#include <boost/utility/string_view.hpp>
|
||||
|
||||
#include <aedis/resp3/type.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
|
||||
constexpr char separator[] = "\r\n";
|
||||
|
||||
/** @brief Adds a bulk to the request.
|
||||
* @ingroup any
|
||||
*
|
||||
* This function is useful in serialization of your own data
|
||||
* structures in a request. For example
|
||||
*
|
||||
* @code
|
||||
* void to_bulk(std::string& to, mystruct const& obj)
|
||||
* {
|
||||
* auto const str = // Convert obj to a string.
|
||||
* resp3::to_bulk(to, str);
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* See more in \ref requests-serialization.
|
||||
*/
|
||||
template <class Request>
|
||||
void to_bulk(Request& to, boost::string_view data)
|
||||
{
|
||||
auto const str = std::to_string(data.size());
|
||||
|
||||
to += to_code(type::blob_string);
|
||||
to.append(std::cbegin(str), std::cend(str));
|
||||
to += separator;
|
||||
to.append(std::cbegin(data), std::cend(data));
|
||||
to += separator;
|
||||
}
|
||||
|
||||
template <class Request, class T, typename = typename std::enable_if<std::is_integral<T>::value>::type>
|
||||
void to_bulk(Request& to, T n)
|
||||
{
|
||||
auto const s = std::to_string(n);
|
||||
to_bulk(to, boost::string_view{s});
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <class T>
|
||||
struct add_bulk_impl {
|
||||
template <class Request>
|
||||
static void add(Request& to, T const& from)
|
||||
{
|
||||
using namespace aedis::resp3;
|
||||
to_bulk(to, from);
|
||||
}
|
||||
};
|
||||
|
||||
template <class U, class V>
|
||||
struct add_bulk_impl<std::pair<U, V>> {
|
||||
template <class Request>
|
||||
static void add(Request& to, std::pair<U, V> const& from)
|
||||
{
|
||||
using namespace aedis::resp3;
|
||||
to_bulk(to, from.first);
|
||||
to_bulk(to, from.second);
|
||||
}
|
||||
};
|
||||
|
||||
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.
|
||||
//(resp3::add_bulk(*request_, args), ...);
|
||||
|
||||
for_each(from, [&](auto const& e) {
|
||||
using namespace aedis::resp3;
|
||||
to_bulk(to, e);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
} // detail
|
||||
|
||||
/** \internal
|
||||
* \brief Adds a resp3 header to the request.
|
||||
* \ingroup any
|
||||
*
|
||||
* See mystruct.hpp for an example.
|
||||
*/
|
||||
template <class Request>
|
||||
void add_header(Request& to, type t, std::size_t size)
|
||||
{
|
||||
auto const str = std::to_string(size);
|
||||
|
||||
to += to_code(t);
|
||||
to.append(std::cbegin(str), std::cend(str));
|
||||
to += separator;
|
||||
}
|
||||
|
||||
/* Adds a rep3 bulk to the request.
|
||||
*
|
||||
* This function adds \c data as a bulk string to the request \c to.
|
||||
*/
|
||||
template <class Request, class T>
|
||||
void add_bulk(Request& to, T const& data)
|
||||
{
|
||||
detail::add_bulk_impl<T>::add(to, data);
|
||||
}
|
||||
|
||||
template <class>
|
||||
struct bulk_counter;
|
||||
|
||||
template <class>
|
||||
struct bulk_counter {
|
||||
static constexpr auto size = 1U;
|
||||
};
|
||||
|
||||
template <class T, class U>
|
||||
struct bulk_counter<std::pair<T, U>> {
|
||||
static constexpr auto size = 2U;
|
||||
};
|
||||
|
||||
template <class Request>
|
||||
void add_blob(Request& to, boost::string_view blob)
|
||||
{
|
||||
to.append(std::cbegin(blob), std::cend(blob));
|
||||
to += separator;
|
||||
}
|
||||
|
||||
/** \internal
|
||||
* \brief Adds a separator to the request.
|
||||
* \ingroup any
|
||||
*
|
||||
* See mystruct.hpp for an example.
|
||||
*/
|
||||
template <class Request>
|
||||
void add_separator(Request& to)
|
||||
{
|
||||
to += separator;
|
||||
}
|
||||
|
||||
} // resp3
|
||||
} // aedis
|
||||
|
||||
#endif // AEDIS_RESP3_COMPOSE_HPP
|
||||
@@ -1,221 +0,0 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#ifndef AEDIS_RESP3_REQUEST_HPP
|
||||
#define AEDIS_RESP3_REQUEST_HPP
|
||||
|
||||
#include <boost/hana.hpp>
|
||||
#include <aedis/resp3/compose.hpp>
|
||||
|
||||
// NOTE: Consider detecting tuples in the type in the parameter pack
|
||||
// to calculate the header size correctly.
|
||||
//
|
||||
// NOTE: For some commands like hset it would be a good idea to assert
|
||||
// the value type is a pair.
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
|
||||
/** @brief Creates Redis requests from user data.
|
||||
* \ingroup any
|
||||
*
|
||||
* A request is composed of one or more Redis commands and is
|
||||
* referred to in the redis documentation as a pipeline, see
|
||||
* https://redis.io/topics/pipelining.
|
||||
*
|
||||
* Example
|
||||
*
|
||||
* @code
|
||||
* request r;
|
||||
* r.push("HELLO", 3);
|
||||
* r.push("FLUSHALL");
|
||||
* r.push("PING");
|
||||
* r.push("PING", "key");
|
||||
* r.push("QUIT");
|
||||
* co_await async_write(socket, buffer(r));
|
||||
* @endcode
|
||||
*
|
||||
* \remarks Non-string types will be converted to string by using \c
|
||||
* to_bulk, which must be made available over ADL.
|
||||
*/
|
||||
class request {
|
||||
public:
|
||||
//// Returns the number of commands contained in this request.
|
||||
std::size_t commands() const noexcept { return commands_;};
|
||||
|
||||
/// Returns the request payload.
|
||||
auto const& payload() const noexcept { return payload_;}
|
||||
|
||||
/// Clears the request preserving allocated memory.
|
||||
void clear()
|
||||
{
|
||||
payload_.clear();
|
||||
commands_ = 0;
|
||||
}
|
||||
|
||||
/** @brief Appends a new command to the end of the request.
|
||||
*
|
||||
* For example
|
||||
*
|
||||
* \code
|
||||
* request req;
|
||||
* req.push("SET", "key", "some string", "EX", "2");
|
||||
* \endcode
|
||||
*
|
||||
* will add the \c set command with value "some string" and an
|
||||
* expiration of 2 seconds.
|
||||
*
|
||||
* \param cmd The command e.g redis or sentinel command.
|
||||
* \param args Command arguments.
|
||||
*/
|
||||
template <class... Ts>
|
||||
void push(boost::string_view cmd, Ts const&... args)
|
||||
{
|
||||
using boost::hana::for_each;
|
||||
using boost::hana::make_tuple;
|
||||
using resp3::type;
|
||||
|
||||
auto const before = payload_.size();
|
||||
auto constexpr pack_size = sizeof...(Ts);
|
||||
resp3::add_header(payload_, type::array, 1 + pack_size);
|
||||
resp3::add_bulk(payload_, cmd);
|
||||
resp3::add_bulk(payload_, make_tuple(args...));
|
||||
|
||||
auto const after = payload_.size();
|
||||
if (!has_push_response(cmd))
|
||||
++commands_;
|
||||
}
|
||||
|
||||
/** @brief Appends a new command to the end of the request.
|
||||
*
|
||||
* This overload is useful for commands that have a key and have a
|
||||
* dynamic range of arguments. For example
|
||||
*
|
||||
* @code
|
||||
* std::map<std::string, std::string> map
|
||||
* { {"key1", "value1"}
|
||||
* , {"key2", "value2"}
|
||||
* , {"key3", "value3"}
|
||||
* };
|
||||
*
|
||||
* request req;
|
||||
* req.push_range2("HSET", "key", std::cbegin(map), std::cend(map));
|
||||
* @endcode
|
||||
*
|
||||
* \param cmd The command e.g. Redis or Sentinel command.
|
||||
* \param key The command key.
|
||||
* \param begin Iterator to the begin of the range.
|
||||
* \param end Iterator to the end of the range.
|
||||
*/
|
||||
template <class Key, class ForwardIterator>
|
||||
void push_range2(boost::string_view cmd, Key const& key, ForwardIterator begin, ForwardIterator end)
|
||||
{
|
||||
using value_type = typename std::iterator_traits<ForwardIterator>::value_type;
|
||||
using resp3::type;
|
||||
|
||||
if (begin == end)
|
||||
return;
|
||||
|
||||
auto const before = payload_.size();
|
||||
|
||||
auto constexpr size = resp3::bulk_counter<value_type>::size;
|
||||
auto const distance = std::distance(begin, end);
|
||||
resp3::add_header(payload_, type::array, 2 + size * distance);
|
||||
resp3::add_bulk(payload_, cmd);
|
||||
resp3::add_bulk(payload_, key);
|
||||
|
||||
for (; begin != end; ++begin)
|
||||
resp3::add_bulk(payload_, *begin);
|
||||
|
||||
auto const after = payload_.size();
|
||||
if (!has_push_response(cmd))
|
||||
++commands_;
|
||||
}
|
||||
|
||||
/** @brief Appends a new command to the end of the request.
|
||||
*
|
||||
* This overload is useful for commands that have a dynamic number
|
||||
* of arguments and don't have a key. For example
|
||||
*
|
||||
* \code
|
||||
* std::set<std::string> channels
|
||||
* { "channel1" , "channel2" , "channel3" }
|
||||
*
|
||||
* request req;
|
||||
* req.push("SUBSCRIBE", std::cbegin(channels), std::cedn(channels));
|
||||
* \endcode
|
||||
*
|
||||
* \param cmd The Redis command
|
||||
* \param begin Iterator to the begin of the range.
|
||||
* \param end Iterator to the end of the range.
|
||||
*/
|
||||
template <class ForwardIterator>
|
||||
void push_range2(boost::string_view cmd, ForwardIterator begin, ForwardIterator end)
|
||||
{
|
||||
using value_type = typename std::iterator_traits<ForwardIterator>::value_type;
|
||||
using resp3::type;
|
||||
|
||||
if (begin == end)
|
||||
return;
|
||||
|
||||
auto const before = payload_.size();
|
||||
auto constexpr size = resp3::bulk_counter<value_type>::size;
|
||||
auto const distance = std::distance(begin, end);
|
||||
resp3::add_header(payload_, type::array, 1 + size * distance);
|
||||
resp3::add_bulk(payload_, cmd);
|
||||
|
||||
for (; begin != end; ++begin)
|
||||
resp3::add_bulk(payload_, *begin);
|
||||
|
||||
auto const after = payload_.size();
|
||||
if (!has_push_response(cmd))
|
||||
++commands_;
|
||||
}
|
||||
|
||||
/** @brief Appends a new command to the end of the request.
|
||||
*
|
||||
* Equivalent to the overload taking a range (i.e. send_range2).
|
||||
*
|
||||
* \param cmd Redis command.
|
||||
* \param key Redis key.
|
||||
* \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)
|
||||
{
|
||||
using std::begin;
|
||||
using std::end;
|
||||
push_range2(cmd, key, begin(range), end(range));
|
||||
}
|
||||
|
||||
/** @brief Appends a new command to the end of the request.
|
||||
*
|
||||
* Equivalent to the overload taking a range (i.e. send_range2).
|
||||
*
|
||||
* \param cmd Redis command.
|
||||
* \param range Range to send e.g. and \c std::map.
|
||||
*/
|
||||
template <class Range>
|
||||
void push_range(boost::string_view cmd, Range const& range)
|
||||
{
|
||||
using std::begin;
|
||||
using std::end;
|
||||
push_range2(cmd, begin(range), end(range));
|
||||
}
|
||||
|
||||
void set_internal() noexcept { is_internal_ = true;}
|
||||
bool is_internal() const noexcept { return is_internal_;}
|
||||
|
||||
private:
|
||||
std::string payload_;
|
||||
std::size_t commands_ = 0;
|
||||
bool is_internal_ = true;
|
||||
};
|
||||
|
||||
} // resp3
|
||||
} // aedis
|
||||
|
||||
#endif // AEDIS_RESP3_SERIALIZER_HPP
|
||||
71
benchmarks/benchmarks.tex
Normal file
71
benchmarks/benchmarks.tex
Normal file
@@ -0,0 +1,71 @@
|
||||
\documentclass{article}
|
||||
\usepackage{pgfplots}
|
||||
\pgfrealjobname{echo}
|
||||
\pgfplotsset{compat=newest}
|
||||
|
||||
\begin{document}
|
||||
|
||||
\beginpgfgraphicnamed{echo-f0}
|
||||
% time ./echo_server_client 1000 5000
|
||||
\begin{tikzpicture}[scale=1.0]
|
||||
\begin{axis}[
|
||||
y dir=reverse,
|
||||
%xbar stacked,
|
||||
xbar, xmin=0,
|
||||
%hide x axis,
|
||||
bar shift=0pt,
|
||||
width=15cm, height=6cm, enlarge y limits=0.5,
|
||||
title={TCP Echo Server Performance},
|
||||
xlabel={Seconds},
|
||||
symbolic y coords={Asio,Tokio,Go,Libuv,Nodejs},
|
||||
ytick=data,
|
||||
%bar width=1cm,
|
||||
nodes near coords,
|
||||
nodes near coords align={horizontal},
|
||||
]
|
||||
\addplot coordinates {
|
||||
(29.5,Asio)
|
||||
(30.7,Tokio)
|
||||
(35.6,Go)
|
||||
(43.6,Libuv)
|
||||
(74.2,Nodejs)
|
||||
};
|
||||
\end{axis}
|
||||
\end{tikzpicture}
|
||||
\endpgfgraphicnamed
|
||||
|
||||
\beginpgfgraphicnamed{echo-f1}
|
||||
%$ time ./echo_server_client 1000 1000
|
||||
\begin{tikzpicture}[scale=1.0]
|
||||
\begin{axis}[
|
||||
y dir=reverse,
|
||||
%xbar stacked,
|
||||
xbar, xmin=0,
|
||||
%hide x axis,
|
||||
bar shift=0pt,
|
||||
width=12cm, height=6cm, enlarge y limits=0.5,
|
||||
title={TCP Echo Server Performance (over Redis)},
|
||||
xlabel={Seconds},
|
||||
symbolic y coords={Aedis,Rust-rs,Libuv,Node-redis,Go-redis},
|
||||
ytick=data,
|
||||
%bar width=1cm,
|
||||
nodes near coords,
|
||||
nodes near coords align={horizontal},
|
||||
]
|
||||
\addplot coordinates {
|
||||
(12.6,Aedis)
|
||||
(28.8,Node-redis)
|
||||
(352.4,Go-redis)
|
||||
};
|
||||
%\addplot coordinates {
|
||||
% (30.0,Asio)
|
||||
% (90.6,Rust-rs)
|
||||
% (0.0,Libuv)
|
||||
% (68.9,Nodejs)
|
||||
% (0.0,Go)
|
||||
%};
|
||||
\end{axis}
|
||||
\end{tikzpicture}
|
||||
\endpgfgraphicnamed
|
||||
|
||||
\end{document}
|
||||
@@ -4,7 +4,7 @@
|
||||
#include <uv.h>
|
||||
|
||||
#define DEFAULT_PORT 55555
|
||||
#define DEFAULT_BACKLOG 128
|
||||
#define DEFAULT_BACKLOG 1024
|
||||
|
||||
uv_loop_t *loop;
|
||||
struct sockaddr_in addr;
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <iostream>
|
||||
#include <boost/asio.hpp>
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
|
||||
namespace net = boost::asio;
|
||||
|
||||
@@ -62,3 +63,6 @@ int main(int argc, char* argv[])
|
||||
std::cerr << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
#else // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
auto main() -> int {std::cout << "Requires coroutine support." << std::endl; return 1;}
|
||||
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
|
||||
@@ -1,26 +1,31 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
//
|
||||
// echo_server.cpp
|
||||
// ~~~~~~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2003-2022 Christopher M. Kohlhoff (chris at kohlhoff dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#include <boost/asio/co_spawn.hpp>
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/signal_set.hpp>
|
||||
#include <boost/asio/write.hpp>
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
#include <boost/asio.hpp>
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
|
||||
namespace net = boost::asio;
|
||||
namespace this_coro = net::this_coro;
|
||||
using net::ip::tcp;
|
||||
using net::awaitable;
|
||||
using net::co_spawn;
|
||||
using net::detached;
|
||||
using net::use_awaitable;
|
||||
using executor_type = net::io_context::executor_type;
|
||||
using socket_type = net::basic_stream_socket<net::ip::tcp, executor_type>;
|
||||
using tcp_socket = net::use_awaitable_t<executor_type>::as_default_on_t<socket_type>;
|
||||
using acceptor_type = net::basic_socket_acceptor<net::ip::tcp, executor_type>;
|
||||
using tcp_acceptor = net::use_awaitable_t<executor_type>::as_default_on_t<acceptor_type>;
|
||||
using awaitable_type = net::awaitable<void, executor_type>;
|
||||
constexpr net::use_awaitable_t<executor_type> use_awaitable;
|
||||
|
||||
awaitable<void> echo(tcp::socket socket)
|
||||
awaitable_type echo(tcp_socket socket)
|
||||
{
|
||||
try {
|
||||
char data[1024];
|
||||
@@ -33,23 +38,26 @@ awaitable<void> echo(tcp::socket socket)
|
||||
}
|
||||
}
|
||||
|
||||
awaitable<void> listener()
|
||||
awaitable_type listener()
|
||||
{
|
||||
auto executor = co_await this_coro::executor;
|
||||
tcp::acceptor acceptor(executor, {tcp::v4(), 55555});
|
||||
auto ex = co_await this_coro::executor;
|
||||
tcp_acceptor acceptor(ex, {tcp::v4(), 55555});
|
||||
for (;;) {
|
||||
tcp::socket socket = co_await acceptor.async_accept(use_awaitable);
|
||||
co_spawn(executor, echo(std::move(socket)), detached);
|
||||
tcp_socket socket = co_await acceptor.async_accept(use_awaitable);
|
||||
co_spawn(ex, echo(std::move(socket)), detached);
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
try {
|
||||
net::io_context io_context(1);
|
||||
net::io_context io_context{BOOST_ASIO_CONCURRENCY_HINT_UNSAFE_IO};
|
||||
co_spawn(io_context, listener(), detached);
|
||||
io_context.run();
|
||||
} catch (std::exception const& e) {
|
||||
std::printf("Exception: %s\n", e.what());
|
||||
}
|
||||
}
|
||||
#else // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
auto main() -> int {std::cout << "Requires coroutine support." << std::endl; return 1;}
|
||||
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
|
||||
@@ -1,29 +1,31 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func echo(conn net.Conn) {
|
||||
r := bufio.NewReader(conn)
|
||||
buf := make([]byte, 1024)
|
||||
for {
|
||||
line, err := r.ReadBytes(byte('\n'))
|
||||
switch err {
|
||||
case nil:
|
||||
break
|
||||
case io.EOF:
|
||||
default:
|
||||
fmt.Println("ERROR", err)
|
||||
}
|
||||
conn.Write(line)
|
||||
n, err := conn.Read(buf)
|
||||
if err != nil {
|
||||
break;
|
||||
}
|
||||
|
||||
conn.Write(buf[:n])
|
||||
if err != nil {
|
||||
fmt.Println("ERROR", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
runtime.GOMAXPROCS(1)
|
||||
|
||||
l, err := net.Listen("tcp", "0.0.0.0:55555")
|
||||
if err != nil {
|
||||
fmt.Println("ERROR", err)
|
||||
|
||||
54
benchmarks/go/echo_server_over_redis.go
Normal file
54
benchmarks/go/echo_server_over_redis.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/go-redis/redis/v8"
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
)
|
||||
|
||||
var ctx = context.Background()
|
||||
var rdb = redis.NewClient(&redis.Options{Addr: "db.occase.de:6379", Password: "", DB: 0,})
|
||||
|
||||
func echo(conn net.Conn) {
|
||||
r := bufio.NewReader(conn)
|
||||
for {
|
||||
line, err := r.ReadBytes(byte('\n'))
|
||||
switch err {
|
||||
case nil:
|
||||
break
|
||||
case io.EOF:
|
||||
default:
|
||||
fmt.Println("ERROR", err)
|
||||
}
|
||||
|
||||
err2 := rdb.Ping(ctx).Err()
|
||||
if err2 != nil {
|
||||
fmt.Println("ERROR", err2)
|
||||
panic(err2)
|
||||
}
|
||||
|
||||
conn.Write(line)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
l, err := net.Listen("tcp", "0.0.0.0:55555")
|
||||
if err != nil {
|
||||
fmt.Println("ERROR", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
for {
|
||||
conn, err := l.Accept()
|
||||
if err != nil {
|
||||
fmt.Println("ERROR", err)
|
||||
continue
|
||||
}
|
||||
|
||||
go echo(conn)
|
||||
}
|
||||
}
|
||||
105
benchmarks/java/echo_server_direct/TcpEchoServer.java
Normal file
105
benchmarks/java/echo_server_direct/TcpEchoServer.java
Normal file
@@ -0,0 +1,105 @@
|
||||
import java.nio.*;
|
||||
import java.nio.channels.*;
|
||||
import java.net.*;
|
||||
import java.util.*;
|
||||
import java.io.IOException;
|
||||
|
||||
public class TcpEchoServer {
|
||||
|
||||
public static int DEFAULT_PORT = 55555;
|
||||
|
||||
public static void main(String[] args) {
|
||||
int port;
|
||||
try {
|
||||
port = Integer.parseInt(args[0]);
|
||||
}
|
||||
|
||||
catch (Exception ex) {
|
||||
port = DEFAULT_PORT;
|
||||
}
|
||||
|
||||
//System.out.println("Listening for connections on port " + port);
|
||||
ServerSocketChannel serverChannel;
|
||||
Selector selector;
|
||||
try {
|
||||
serverChannel = ServerSocketChannel.open( );
|
||||
ServerSocket ss = serverChannel.socket( );
|
||||
InetSocketAddress address = new InetSocketAddress(port);
|
||||
ss.bind(address);
|
||||
serverChannel.configureBlocking(false);
|
||||
selector = Selector.open( );
|
||||
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
ex.printStackTrace( );
|
||||
return;
|
||||
}
|
||||
while (true) {
|
||||
try {
|
||||
selector.select( );
|
||||
}
|
||||
catch (IOException ex) {
|
||||
ex.printStackTrace( );
|
||||
break;
|
||||
}
|
||||
Set readyKeys = selector.selectedKeys( );
|
||||
Iterator iterator = readyKeys.iterator( );
|
||||
while (iterator.hasNext( )) {
|
||||
SelectionKey key = (SelectionKey) iterator.next( );
|
||||
iterator.remove( );
|
||||
try {
|
||||
if (key.isAcceptable( )) {
|
||||
ServerSocketChannel server = (ServerSocketChannel ) key.channel( );
|
||||
SocketChannel client = server.accept( );
|
||||
//System.out.println("Accepted connection from " + client);
|
||||
client.configureBlocking(false);
|
||||
SelectionKey clientKey = client.register(
|
||||
selector, SelectionKey.OP_WRITE | SelectionKey.OP_READ);
|
||||
ByteBuffer buffer = ByteBuffer.allocate(100);
|
||||
clientKey.attach(buffer);
|
||||
//System.out.println(buffer.toString());
|
||||
|
||||
}
|
||||
|
||||
if (key.isReadable( )) {
|
||||
SocketChannel client = (SocketChannel) key.channel( );
|
||||
ByteBuffer output = (ByteBuffer) key.attachment( );
|
||||
client.read(output);
|
||||
}
|
||||
|
||||
if (key.isWritable( )) {
|
||||
SocketChannel client = (SocketChannel) key.channel( );
|
||||
ByteBuffer output = (ByteBuffer) key.attachment( );
|
||||
output.flip( );
|
||||
client.write(output);
|
||||
output.compact( );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
catch (IOException ex) {
|
||||
key.cancel( );
|
||||
try {
|
||||
key.channel().close();
|
||||
}
|
||||
|
||||
catch (IOException cex) {}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
2
benchmarks/nodejs/echo_server_direct/package.json
Normal file
2
benchmarks/nodejs/echo_server_direct/package.json
Normal file
@@ -0,0 +1,2 @@
|
||||
{
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createClient } from 'redis';
|
||||
import * as net from 'net';
|
||||
|
||||
const client = createClient();
|
||||
const client = createClient({url: 'redis://db.occase.de:6379' });
|
||||
client.on('error', (err) => console.log('Redis Client Error', err));
|
||||
await client.connect();
|
||||
|
||||
169
benchmarks/nodejs/echo_server_over_redis/package-lock.json
generated
Normal file
169
benchmarks/nodejs/echo_server_over_redis/package-lock.json
generated
Normal file
@@ -0,0 +1,169 @@
|
||||
{
|
||||
"name": "echo_server_over_redis",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"redis": "^4.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@redis/bloom": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.0.2.tgz",
|
||||
"integrity": "sha512-EBw7Ag1hPgFzdznK2PBblc1kdlj5B5Cw3XwI9/oG7tSn85/HKy3X9xHy/8tm/eNXJYHLXHJL/pkwBpFMVVefkw==",
|
||||
"peerDependencies": {
|
||||
"@redis/client": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@redis/client": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@redis/client/-/client-1.2.0.tgz",
|
||||
"integrity": "sha512-a8Nlw5fv2EIAFJxTDSSDVUT7yfBGpZO96ybZXzQpgkyLg/dxtQ1uiwTc0EGfzg1mrPjZokeBSEGTbGXekqTNOg==",
|
||||
"dependencies": {
|
||||
"cluster-key-slot": "1.1.0",
|
||||
"generic-pool": "3.8.2",
|
||||
"yallist": "4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@redis/graph": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.0.1.tgz",
|
||||
"integrity": "sha512-oDE4myMCJOCVKYMygEMWuriBgqlS5FqdWerikMoJxzmmTUErnTRRgmIDa2VcgytACZMFqpAOWDzops4DOlnkfQ==",
|
||||
"peerDependencies": {
|
||||
"@redis/client": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@redis/json": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.3.tgz",
|
||||
"integrity": "sha512-4X0Qv0BzD9Zlb0edkUoau5c1bInWSICqXAGrpwEltkncUwcxJIGEcVryZhLgb0p/3PkKaLIWkjhHRtLe9yiA7Q==",
|
||||
"peerDependencies": {
|
||||
"@redis/client": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@redis/search": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@redis/search/-/search-1.0.6.tgz",
|
||||
"integrity": "sha512-pP+ZQRis5P21SD6fjyCeLcQdps+LuTzp2wdUbzxEmNhleighDDTD5ck8+cYof+WLec4csZX7ks+BuoMw0RaZrA==",
|
||||
"peerDependencies": {
|
||||
"@redis/client": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@redis/time-series": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.0.3.tgz",
|
||||
"integrity": "sha512-OFp0q4SGrTH0Mruf6oFsHGea58u8vS/iI5+NpYdicaM+7BgqBZH8FFvNZ8rYYLrUO/QRqMq72NpXmxLVNcdmjA==",
|
||||
"peerDependencies": {
|
||||
"@redis/client": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cluster-key-slot": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz",
|
||||
"integrity": "sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/generic-pool": {
|
||||
"version": "3.8.2",
|
||||
"resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.8.2.tgz",
|
||||
"integrity": "sha512-nGToKy6p3PAbYQ7p1UlWl6vSPwfwU6TMSWK7TTu+WUY4ZjyZQGniGGt2oNVvyNSpyZYSB43zMXVLcBm08MTMkg==",
|
||||
"engines": {
|
||||
"node": ">= 4"
|
||||
}
|
||||
},
|
||||
"node_modules/redis": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/redis/-/redis-4.2.0.tgz",
|
||||
"integrity": "sha512-bCR0gKVhIXFg8zCQjXEANzgI01DDixtPZgIUZHBCmwqixnu+MK3Tb2yqGjh+HCLASQVVgApiwhNkv+FoedZOGQ==",
|
||||
"dependencies": {
|
||||
"@redis/bloom": "1.0.2",
|
||||
"@redis/client": "1.2.0",
|
||||
"@redis/graph": "1.0.1",
|
||||
"@redis/json": "1.0.3",
|
||||
"@redis/search": "1.0.6",
|
||||
"@redis/time-series": "1.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@redis/bloom": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.0.2.tgz",
|
||||
"integrity": "sha512-EBw7Ag1hPgFzdznK2PBblc1kdlj5B5Cw3XwI9/oG7tSn85/HKy3X9xHy/8tm/eNXJYHLXHJL/pkwBpFMVVefkw==",
|
||||
"requires": {}
|
||||
},
|
||||
"@redis/client": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@redis/client/-/client-1.2.0.tgz",
|
||||
"integrity": "sha512-a8Nlw5fv2EIAFJxTDSSDVUT7yfBGpZO96ybZXzQpgkyLg/dxtQ1uiwTc0EGfzg1mrPjZokeBSEGTbGXekqTNOg==",
|
||||
"requires": {
|
||||
"cluster-key-slot": "1.1.0",
|
||||
"generic-pool": "3.8.2",
|
||||
"yallist": "4.0.0"
|
||||
}
|
||||
},
|
||||
"@redis/graph": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.0.1.tgz",
|
||||
"integrity": "sha512-oDE4myMCJOCVKYMygEMWuriBgqlS5FqdWerikMoJxzmmTUErnTRRgmIDa2VcgytACZMFqpAOWDzops4DOlnkfQ==",
|
||||
"requires": {}
|
||||
},
|
||||
"@redis/json": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.3.tgz",
|
||||
"integrity": "sha512-4X0Qv0BzD9Zlb0edkUoau5c1bInWSICqXAGrpwEltkncUwcxJIGEcVryZhLgb0p/3PkKaLIWkjhHRtLe9yiA7Q==",
|
||||
"requires": {}
|
||||
},
|
||||
"@redis/search": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@redis/search/-/search-1.0.6.tgz",
|
||||
"integrity": "sha512-pP+ZQRis5P21SD6fjyCeLcQdps+LuTzp2wdUbzxEmNhleighDDTD5ck8+cYof+WLec4csZX7ks+BuoMw0RaZrA==",
|
||||
"requires": {}
|
||||
},
|
||||
"@redis/time-series": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.0.3.tgz",
|
||||
"integrity": "sha512-OFp0q4SGrTH0Mruf6oFsHGea58u8vS/iI5+NpYdicaM+7BgqBZH8FFvNZ8rYYLrUO/QRqMq72NpXmxLVNcdmjA==",
|
||||
"requires": {}
|
||||
},
|
||||
"cluster-key-slot": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz",
|
||||
"integrity": "sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw=="
|
||||
},
|
||||
"generic-pool": {
|
||||
"version": "3.8.2",
|
||||
"resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.8.2.tgz",
|
||||
"integrity": "sha512-nGToKy6p3PAbYQ7p1UlWl6vSPwfwU6TMSWK7TTu+WUY4ZjyZQGniGGt2oNVvyNSpyZYSB43zMXVLcBm08MTMkg=="
|
||||
},
|
||||
"redis": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/redis/-/redis-4.2.0.tgz",
|
||||
"integrity": "sha512-bCR0gKVhIXFg8zCQjXEANzgI01DDixtPZgIUZHBCmwqixnu+MK3Tb2yqGjh+HCLASQVVgApiwhNkv+FoedZOGQ==",
|
||||
"requires": {
|
||||
"@redis/bloom": "1.0.2",
|
||||
"@redis/client": "1.2.0",
|
||||
"@redis/graph": "1.0.1",
|
||||
"@redis/json": "1.0.3",
|
||||
"@redis/search": "1.0.6",
|
||||
"@redis/time-series": "1.0.3"
|
||||
}
|
||||
},
|
||||
"yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||
}
|
||||
}
|
||||
}
|
||||
6
benchmarks/nodejs/echo_server_over_redis/package.json
Normal file
6
benchmarks/nodejs/echo_server_over_redis/package.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"redis": "^4.2.0"
|
||||
}
|
||||
}
|
||||
169
benchmarks/nodejs/package-lock.json
generated
169
benchmarks/nodejs/package-lock.json
generated
@@ -1,169 +0,0 @@
|
||||
{
|
||||
"name": "aedis",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"redis": "^4.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@redis/bloom": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.0.2.tgz",
|
||||
"integrity": "sha512-EBw7Ag1hPgFzdznK2PBblc1kdlj5B5Cw3XwI9/oG7tSn85/HKy3X9xHy/8tm/eNXJYHLXHJL/pkwBpFMVVefkw==",
|
||||
"peerDependencies": {
|
||||
"@redis/client": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@redis/client": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@redis/client/-/client-1.1.0.tgz",
|
||||
"integrity": "sha512-xO9JDIgzsZYDl3EvFhl6LC52DP3q3GCMUer8zHgKV6qSYsq1zB+pZs9+T80VgcRogrlRYhi4ZlfX6A+bHiBAgA==",
|
||||
"dependencies": {
|
||||
"cluster-key-slot": "1.1.0",
|
||||
"generic-pool": "3.8.2",
|
||||
"yallist": "4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@redis/graph": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.0.1.tgz",
|
||||
"integrity": "sha512-oDE4myMCJOCVKYMygEMWuriBgqlS5FqdWerikMoJxzmmTUErnTRRgmIDa2VcgytACZMFqpAOWDzops4DOlnkfQ==",
|
||||
"peerDependencies": {
|
||||
"@redis/client": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@redis/json": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.3.tgz",
|
||||
"integrity": "sha512-4X0Qv0BzD9Zlb0edkUoau5c1bInWSICqXAGrpwEltkncUwcxJIGEcVryZhLgb0p/3PkKaLIWkjhHRtLe9yiA7Q==",
|
||||
"peerDependencies": {
|
||||
"@redis/client": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@redis/search": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@redis/search/-/search-1.0.6.tgz",
|
||||
"integrity": "sha512-pP+ZQRis5P21SD6fjyCeLcQdps+LuTzp2wdUbzxEmNhleighDDTD5ck8+cYof+WLec4csZX7ks+BuoMw0RaZrA==",
|
||||
"peerDependencies": {
|
||||
"@redis/client": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@redis/time-series": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.0.3.tgz",
|
||||
"integrity": "sha512-OFp0q4SGrTH0Mruf6oFsHGea58u8vS/iI5+NpYdicaM+7BgqBZH8FFvNZ8rYYLrUO/QRqMq72NpXmxLVNcdmjA==",
|
||||
"peerDependencies": {
|
||||
"@redis/client": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cluster-key-slot": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz",
|
||||
"integrity": "sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/generic-pool": {
|
||||
"version": "3.8.2",
|
||||
"resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.8.2.tgz",
|
||||
"integrity": "sha512-nGToKy6p3PAbYQ7p1UlWl6vSPwfwU6TMSWK7TTu+WUY4ZjyZQGniGGt2oNVvyNSpyZYSB43zMXVLcBm08MTMkg==",
|
||||
"engines": {
|
||||
"node": ">= 4"
|
||||
}
|
||||
},
|
||||
"node_modules/redis": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/redis/-/redis-4.1.0.tgz",
|
||||
"integrity": "sha512-5hvJ8wbzpCCiuN1ges6tx2SAh2XXCY0ayresBmu40/SGusWHFW86TAlIPpbimMX2DFHOX7RN34G2XlPA1Z43zg==",
|
||||
"dependencies": {
|
||||
"@redis/bloom": "1.0.2",
|
||||
"@redis/client": "1.1.0",
|
||||
"@redis/graph": "1.0.1",
|
||||
"@redis/json": "1.0.3",
|
||||
"@redis/search": "1.0.6",
|
||||
"@redis/time-series": "1.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@redis/bloom": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.0.2.tgz",
|
||||
"integrity": "sha512-EBw7Ag1hPgFzdznK2PBblc1kdlj5B5Cw3XwI9/oG7tSn85/HKy3X9xHy/8tm/eNXJYHLXHJL/pkwBpFMVVefkw==",
|
||||
"requires": {}
|
||||
},
|
||||
"@redis/client": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@redis/client/-/client-1.1.0.tgz",
|
||||
"integrity": "sha512-xO9JDIgzsZYDl3EvFhl6LC52DP3q3GCMUer8zHgKV6qSYsq1zB+pZs9+T80VgcRogrlRYhi4ZlfX6A+bHiBAgA==",
|
||||
"requires": {
|
||||
"cluster-key-slot": "1.1.0",
|
||||
"generic-pool": "3.8.2",
|
||||
"yallist": "4.0.0"
|
||||
}
|
||||
},
|
||||
"@redis/graph": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.0.1.tgz",
|
||||
"integrity": "sha512-oDE4myMCJOCVKYMygEMWuriBgqlS5FqdWerikMoJxzmmTUErnTRRgmIDa2VcgytACZMFqpAOWDzops4DOlnkfQ==",
|
||||
"requires": {}
|
||||
},
|
||||
"@redis/json": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.3.tgz",
|
||||
"integrity": "sha512-4X0Qv0BzD9Zlb0edkUoau5c1bInWSICqXAGrpwEltkncUwcxJIGEcVryZhLgb0p/3PkKaLIWkjhHRtLe9yiA7Q==",
|
||||
"requires": {}
|
||||
},
|
||||
"@redis/search": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@redis/search/-/search-1.0.6.tgz",
|
||||
"integrity": "sha512-pP+ZQRis5P21SD6fjyCeLcQdps+LuTzp2wdUbzxEmNhleighDDTD5ck8+cYof+WLec4csZX7ks+BuoMw0RaZrA==",
|
||||
"requires": {}
|
||||
},
|
||||
"@redis/time-series": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.0.3.tgz",
|
||||
"integrity": "sha512-OFp0q4SGrTH0Mruf6oFsHGea58u8vS/iI5+NpYdicaM+7BgqBZH8FFvNZ8rYYLrUO/QRqMq72NpXmxLVNcdmjA==",
|
||||
"requires": {}
|
||||
},
|
||||
"cluster-key-slot": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz",
|
||||
"integrity": "sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw=="
|
||||
},
|
||||
"generic-pool": {
|
||||
"version": "3.8.2",
|
||||
"resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.8.2.tgz",
|
||||
"integrity": "sha512-nGToKy6p3PAbYQ7p1UlWl6vSPwfwU6TMSWK7TTu+WUY4ZjyZQGniGGt2oNVvyNSpyZYSB43zMXVLcBm08MTMkg=="
|
||||
},
|
||||
"redis": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/redis/-/redis-4.1.0.tgz",
|
||||
"integrity": "sha512-5hvJ8wbzpCCiuN1ges6tx2SAh2XXCY0ayresBmu40/SGusWHFW86TAlIPpbimMX2DFHOX7RN34G2XlPA1Z43zg==",
|
||||
"requires": {
|
||||
"@redis/bloom": "1.0.2",
|
||||
"@redis/client": "1.1.0",
|
||||
"@redis/graph": "1.0.1",
|
||||
"@redis/json": "1.0.3",
|
||||
"@redis/search": "1.0.6",
|
||||
"@redis/time-series": "1.0.3"
|
||||
}
|
||||
},
|
||||
"yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"type": "module",
|
||||
"dependencies": { "redis": "^4.1.0" }
|
||||
}
|
||||
293
benchmarks/rust/echo_server_direct/Cargo.lock
generated
293
benchmarks/rust/echo_server_direct/Cargo.lock
generated
@@ -2,6 +2,12 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
@@ -10,15 +16,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "0.5.6"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
||||
checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
@@ -33,34 +33,6 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "fuchsia-zircon"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"fuchsia-zircon-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fuchsia-zircon-sys"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.19"
|
||||
@@ -70,44 +42,29 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iovec"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kernel32-sys"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
|
||||
dependencies = [
|
||||
"winapi 0.2.8",
|
||||
"winapi-build",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.126"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -118,76 +75,14 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.6.23"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4"
|
||||
checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10",
|
||||
"fuchsia-zircon",
|
||||
"fuchsia-zircon-sys",
|
||||
"iovec",
|
||||
"kernel32-sys",
|
||||
"libc",
|
||||
"log",
|
||||
"miow 0.2.2",
|
||||
"net2",
|
||||
"slab",
|
||||
"winapi 0.2.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio-named-pipes"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0840c1c50fd55e521b247f949c241c9997709f23bd7f023b9762cd561e935656"
|
||||
dependencies = [
|
||||
"log",
|
||||
"mio",
|
||||
"miow 0.3.7",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio-uds"
|
||||
version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0"
|
||||
dependencies = [
|
||||
"iovec",
|
||||
"libc",
|
||||
"mio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miow"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d"
|
||||
dependencies = [
|
||||
"kernel32-sys",
|
||||
"net2",
|
||||
"winapi 0.2.8",
|
||||
"ws2_32-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miow"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21"
|
||||
dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "net2"
|
||||
version = "0.2.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10",
|
||||
"libc",
|
||||
"winapi 0.3.9",
|
||||
"wasi",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -201,10 +96,39 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.1.12"
|
||||
name = "once_cell"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "257b64915a082f7811703966789728173279bdebb956b143dbcd23f6f970a777"
|
||||
checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
@@ -224,6 +148,21 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.0"
|
||||
@@ -234,10 +173,20 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.6"
|
||||
name = "smallvec"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32"
|
||||
checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
@@ -252,33 +201,30 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "0.2.25"
|
||||
version = "1.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6703a273949a90131b290be1fe7b039d0fc884aa1935860dfcbe056f28cd8092"
|
||||
checksum = "57aec3cfa4c296db7255446efb4928a6be304b431a806216105542a67b6ca82e"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"bytes",
|
||||
"fnv",
|
||||
"futures-core",
|
||||
"iovec",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"memchr",
|
||||
"mio",
|
||||
"mio-named-pipes",
|
||||
"mio-uds",
|
||||
"num_cpus",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"slab",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"winapi 0.3.9",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "0.2.6"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e44da00bfc73a25f814cd8d7e57a68a5c31b74b3152a0a1d1f590c97ed06265a"
|
||||
checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -292,10 +238,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.2.8"
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
@@ -307,12 +253,6 @@ dependencies = [
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-build"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
@@ -326,11 +266,44 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "ws2_32-sys"
|
||||
version = "0.2.1"
|
||||
name = "windows-sys"
|
||||
version = "0.36.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e"
|
||||
checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2"
|
||||
dependencies = [
|
||||
"winapi 0.2.8",
|
||||
"winapi-build",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.36.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.36.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.36.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.36.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.36.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
|
||||
|
||||
@@ -6,4 +6,4 @@ edition = "2021"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "0.2", features = ["full"] }
|
||||
tokio = { version = "1.0", features = ["full"] }
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::prelude::*;
|
||||
use tokio::io::AsyncReadExt;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
|
||||
#[tokio::main]
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut listener = TcpListener::bind("127.0.0.1:55555").await?;
|
||||
let listener = TcpListener::bind("127.0.0.1:55555").await?;
|
||||
|
||||
loop {
|
||||
let (mut socket, _) = listener.accept().await?;
|
||||
|
||||
@@ -7,7 +7,7 @@ use std::sync::{Arc};
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let listener = TcpListener::bind("127.0.0.1:55555").await?;
|
||||
let client = redis::Client::open("redis://127.0.0.1/").unwrap();
|
||||
let client = redis::Client::open("redis://db.occase.de/").unwrap();
|
||||
let con = Arc::new(Mutex::new(client.get_async_connection().await?));
|
||||
|
||||
loop {
|
||||
|
||||
4
cmake/AedisConfig.cmake.in
Normal file
4
cmake/AedisConfig.cmake.in
Normal file
@@ -0,0 +1,4 @@
|
||||
@PACKAGE_INIT@
|
||||
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/Aedis.cmake")
|
||||
check_required_components("@PROJECT_NAME@")
|
||||
27
configure.ac
27
configure.ac
@@ -1,27 +0,0 @@
|
||||
AC_PREREQ([2.69])
|
||||
AC_INIT([Aedis], [0.1.2], [mzimbres@gmail.com])
|
||||
AC_CONFIG_MACRO_DIR([m4])
|
||||
#AC_CONFIG_SRCDIR([src/aedis.cpp])
|
||||
AC_CONFIG_HEADERS([config.h])
|
||||
AM_INIT_AUTOMAKE([-Wall foreign])
|
||||
|
||||
# Checks for programs.
|
||||
AC_PROG_CXX
|
||||
AC_PROG_INSTALL
|
||||
AC_PROG_LN_S
|
||||
AC_PROG_RANLIB
|
||||
AM_PROG_AR
|
||||
|
||||
AX_BOOST_BASE([1.78],, AC_MSG_ERROR[Boost not found])
|
||||
|
||||
AC_CHECK_HEADER_STDBOOL
|
||||
AC_TYPE_UINT64_T
|
||||
AC_CHECK_TYPES([ptrdiff_t])
|
||||
|
||||
AX_CXX_COMPILE_STDCXX(14, , mandatory)
|
||||
AX_CXX_COMPILE_STDCXX(20, , optional)
|
||||
|
||||
AM_CONDITIONAL(HAVE_CXX20,[test x$HAVE_CXX20 == x1])
|
||||
|
||||
AC_CONFIG_FILES([Makefile doc/Doxyfile])
|
||||
AC_OUTPUT
|
||||
534
doc/Doxyfile.in
534
doc/Doxyfile.in
@@ -1,4 +1,4 @@
|
||||
# Doxyfile 1.8.17
|
||||
# Doxyfile 1.9.4
|
||||
|
||||
# This file describes the settings to be used by the documentation system
|
||||
# doxygen (www.doxygen.org) for a project.
|
||||
@@ -12,6 +12,15 @@
|
||||
# For lists, items can also be appended using:
|
||||
# TAG += value [value, ...]
|
||||
# Values that contain spaces should be placed between quotes (\" \").
|
||||
#
|
||||
# Note:
|
||||
#
|
||||
# Use doxygen to compare the used configuration file with the template
|
||||
# configuration file:
|
||||
# doxygen -x [configFile]
|
||||
# Use doxygen to compare the used configuration file with the template
|
||||
# configuration file without replacing the environment variables:
|
||||
# doxygen -x_noenv [configFile]
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Project related configuration options
|
||||
@@ -32,19 +41,19 @@ DOXYFILE_ENCODING = UTF-8
|
||||
# title of most generated pages and in a few other places.
|
||||
# The default value is: My Project.
|
||||
|
||||
PROJECT_NAME = "@PACKAGE_NAME@"
|
||||
PROJECT_NAME = @PROJECT_NAME@
|
||||
|
||||
# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
|
||||
# could be handy for archiving the generated documentation or if some version
|
||||
# control system is used.
|
||||
|
||||
PROJECT_NUMBER = "@PACKAGE_VERSION@"
|
||||
PROJECT_NUMBER = @PROJECT_VERSION@
|
||||
|
||||
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
||||
# for a project that appears at the top of each page and should give viewer a
|
||||
# quick idea about the purpose of the project. Keep the description short.
|
||||
|
||||
PROJECT_BRIEF = "High level Redis client library"
|
||||
PROJECT_BRIEF = @PROJECT_DESCRIPTION@
|
||||
|
||||
# With the PROJECT_LOGO tag one can specify a logo or an icon that is included
|
||||
# in the documentation. The maximum height of the logo should not exceed 55
|
||||
@@ -58,18 +67,30 @@ PROJECT_LOGO =
|
||||
# entered, it will be relative to the location where doxygen was started. If
|
||||
# left blank the current directory will be used.
|
||||
|
||||
OUTPUT_DIRECTORY = ../aedis-gh-pages
|
||||
OUTPUT_DIRECTORY = @DOXYGEN_OUTPUT_DIRECTORY@
|
||||
|
||||
# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub-
|
||||
# directories (in 2 levels) under the output directory of each output format and
|
||||
# will distribute the generated files over these directories. Enabling this
|
||||
# If the CREATE_SUBDIRS tag is set to YES then doxygen will create up to 4096
|
||||
# sub-directories (in 2 levels) under the output directory of each output format
|
||||
# and will distribute the generated files over these directories. Enabling this
|
||||
# option can be useful when feeding doxygen a huge amount of source files, where
|
||||
# putting all generated files in the same directory would otherwise causes
|
||||
# performance problems for the file system.
|
||||
# performance problems for the file system. Adapt CREATE_SUBDIRS_LEVEL to
|
||||
# control the number of sub-directories.
|
||||
# The default value is: NO.
|
||||
|
||||
CREATE_SUBDIRS = NO
|
||||
|
||||
# Controls the number of sub-directories that will be created when
|
||||
# CREATE_SUBDIRS tag is set to YES. Level 0 represents 16 directories, and every
|
||||
# level increment doubles the number of directories, resulting in 4096
|
||||
# directories at level 8 which is the default and also the maximum value. The
|
||||
# sub-directories are organized in 2 levels, the first level always has a fixed
|
||||
# numer of 16 directories.
|
||||
# Minimum value: 0, maximum value: 8, default value: 8.
|
||||
# This tag requires that the tag CREATE_SUBDIRS is set to YES.
|
||||
|
||||
CREATE_SUBDIRS_LEVEL = 8
|
||||
|
||||
# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII
|
||||
# characters to appear in the names of generated files. If set to NO, non-ASCII
|
||||
# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode
|
||||
@@ -81,26 +102,18 @@ ALLOW_UNICODE_NAMES = NO
|
||||
# The OUTPUT_LANGUAGE tag is used to specify the language in which all
|
||||
# documentation generated by doxygen is written. Doxygen will use this
|
||||
# information to generate all constant output in the proper language.
|
||||
# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese,
|
||||
# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States),
|
||||
# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian,
|
||||
# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages),
|
||||
# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian,
|
||||
# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian,
|
||||
# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish,
|
||||
# Ukrainian and Vietnamese.
|
||||
# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Bulgarian,
|
||||
# Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, English
|
||||
# (United States), Esperanto, Farsi (Persian), Finnish, French, German, Greek,
|
||||
# Hindi, Hungarian, Indonesian, Italian, Japanese, Japanese-en (Japanese with
|
||||
# English messages), Korean, Korean-en (Korean with English messages), Latvian,
|
||||
# Lithuanian, Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese,
|
||||
# Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish,
|
||||
# Swedish, Turkish, Ukrainian and Vietnamese.
|
||||
# The default value is: English.
|
||||
|
||||
OUTPUT_LANGUAGE = English
|
||||
|
||||
# The OUTPUT_TEXT_DIRECTION tag is used to specify the direction in which all
|
||||
# documentation generated by doxygen is written. Doxygen will use this
|
||||
# information to generate all generated output in the proper direction.
|
||||
# Possible values are: None, LTR, RTL and Context.
|
||||
# The default value is: None.
|
||||
|
||||
OUTPUT_TEXT_DIRECTION = None
|
||||
|
||||
# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member
|
||||
# descriptions after the members that are listed in the file and class
|
||||
# documentation (similar to Javadoc). Set to NO to disable this.
|
||||
@@ -170,7 +183,8 @@ FULL_PATH_NAMES = YES
|
||||
# will be relative from the directory where doxygen is started.
|
||||
# This tag requires that the tag FULL_PATH_NAMES is set to YES.
|
||||
|
||||
STRIP_FROM_PATH =
|
||||
STRIP_FROM_PATH = @PROJECT_SOURCE_DIR@/include \
|
||||
@PROJECT_SOURCE_DIR@
|
||||
|
||||
# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the
|
||||
# path mentioned in the documentation of a class, which tells the reader which
|
||||
@@ -227,6 +241,14 @@ QT_AUTOBRIEF = NO
|
||||
|
||||
MULTILINE_CPP_IS_BRIEF = NO
|
||||
|
||||
# By default Python docstrings are displayed as preformatted text and doxygen's
|
||||
# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the
|
||||
# doxygen's special commands can be used and the contents of the docstring
|
||||
# documentation blocks is shown as doxygen documentation.
|
||||
# The default value is: YES.
|
||||
|
||||
PYTHON_DOCSTRING = YES
|
||||
|
||||
# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the
|
||||
# documentation from any documented member that it re-implements.
|
||||
# The default value is: YES.
|
||||
@@ -250,16 +272,16 @@ TAB_SIZE = 4
|
||||
# the documentation. An alias has the form:
|
||||
# name=value
|
||||
# For example adding
|
||||
# "sideeffect=@par Side Effects:\n"
|
||||
# "sideeffect=@par Side Effects:^^"
|
||||
# will allow you to put the command \sideeffect (or @sideeffect) in the
|
||||
# documentation, which will result in a user-defined paragraph with heading
|
||||
# "Side Effects:". You can put \n's in the value part of an alias to insert
|
||||
# newlines (in the resulting output). You can put ^^ in the value part of an
|
||||
# alias to insert a newline as if a physical newline was in the original file.
|
||||
# When you need a literal { or } or , in the value part of an alias you have to
|
||||
# escape them by means of a backslash (\), this can lead to conflicts with the
|
||||
# commands \{ and \} for these it is advised to use the version @{ and @} or use
|
||||
# a double escape (\\{ and \\})
|
||||
# "Side Effects:". Note that you cannot put \n's in the value part of an alias
|
||||
# to insert newlines (in the resulting output). You can put ^^ in the value part
|
||||
# of an alias to insert a newline as if a physical newline was in the original
|
||||
# file. When you need a literal { or } or , in the value part of an alias you
|
||||
# have to escape them by means of a backslash (\), this can lead to conflicts
|
||||
# with the commands \{ and \} for these it is advised to use the version @{ and
|
||||
# @} or use a double escape (\\{ and \\})
|
||||
|
||||
ALIASES =
|
||||
|
||||
@@ -304,18 +326,21 @@ OPTIMIZE_OUTPUT_SLICE = NO
|
||||
# extension. Doxygen has a built-in mapping, but you can override or extend it
|
||||
# using this tag. The format is ext=language, where ext is a file extension, and
|
||||
# language is one of the parsers supported by doxygen: IDL, Java, JavaScript,
|
||||
# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice,
|
||||
# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran:
|
||||
# Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice,
|
||||
# VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran:
|
||||
# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser
|
||||
# tries to guess whether the code is fixed or free formatted code, this is the
|
||||
# default for Fortran type files), VHDL, tcl. For instance to make doxygen treat
|
||||
# .inc files as Fortran files (default is PHP), and .f files as C (default is
|
||||
# Fortran), use: inc=Fortran f=C.
|
||||
# default for Fortran type files). For instance to make doxygen treat .inc files
|
||||
# as Fortran files (default is PHP), and .f files as C (default is Fortran),
|
||||
# use: inc=Fortran f=C.
|
||||
#
|
||||
# Note: For files without extension you can use no_extension as a placeholder.
|
||||
#
|
||||
# Note that for custom extensions you also need to set FILE_PATTERNS otherwise
|
||||
# the files are not read by doxygen.
|
||||
# the files are not read by doxygen. When specifying no_extension you should add
|
||||
# * to the FILE_PATTERNS.
|
||||
#
|
||||
# Note see also the list of default file extension mappings.
|
||||
|
||||
EXTENSION_MAPPING =
|
||||
|
||||
@@ -449,6 +474,19 @@ TYPEDEF_HIDES_STRUCT = NO
|
||||
|
||||
LOOKUP_CACHE_SIZE = 0
|
||||
|
||||
# The NUM_PROC_THREADS specifies the number of threads doxygen is allowed to use
|
||||
# during processing. When set to 0 doxygen will based this on the number of
|
||||
# cores available in the system. You can set it explicitly to a value larger
|
||||
# than 0 to get more control over the balance between CPU load and processing
|
||||
# speed. At this moment only the input processing can be done using multiple
|
||||
# threads. Since this is still an experimental feature the default is set to 1,
|
||||
# which effectively disables parallel processing. Please report any issues you
|
||||
# encounter. Generating dot graphs in parallel is controlled by the
|
||||
# DOT_NUM_THREADS setting.
|
||||
# Minimum value: 0, maximum value: 32, default value: 1.
|
||||
|
||||
NUM_PROC_THREADS = 1
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Build related configuration options
|
||||
#---------------------------------------------------------------------------
|
||||
@@ -512,6 +550,13 @@ EXTRACT_LOCAL_METHODS = NO
|
||||
|
||||
EXTRACT_ANON_NSPACES = NO
|
||||
|
||||
# If this flag is set to YES, the name of an unnamed parameter in a declaration
|
||||
# will be determined by the corresponding definition. By default unnamed
|
||||
# parameters remain unnamed in the output.
|
||||
# The default value is: YES.
|
||||
|
||||
RESOLVE_UNNAMED_PARAMS = YES
|
||||
|
||||
# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all
|
||||
# undocumented members inside documented classes or files. If set to NO these
|
||||
# members will be included in the various overviews, but no documentation
|
||||
@@ -549,11 +594,18 @@ HIDE_IN_BODY_DOCS = NO
|
||||
|
||||
INTERNAL_DOCS = NO
|
||||
|
||||
# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file
|
||||
# names in lower-case letters. If set to YES, upper-case letters are also
|
||||
# allowed. This is useful if you have classes or files whose names only differ
|
||||
# in case and if your file system supports case sensitive file names. Windows
|
||||
# (including Cygwin) ands Mac users are advised to set this option to NO.
|
||||
# With the correct setting of option CASE_SENSE_NAMES doxygen will better be
|
||||
# able to match the capabilities of the underlying filesystem. In case the
|
||||
# filesystem is case sensitive (i.e. it supports files in the same directory
|
||||
# whose names only differ in casing), the option must be set to YES to properly
|
||||
# deal with such files in case they appear in the input. For filesystems that
|
||||
# are not case sensitive the option should be set to NO to properly deal with
|
||||
# output files written for symbols that only differ in casing, such as for two
|
||||
# classes, one named CLASS and the other named Class, and to also support
|
||||
# references to files without having to specify the exact matching casing. On
|
||||
# Windows (including Cygwin) and MacOS, users should typically set this option
|
||||
# to NO, whereas on Linux or other Unix flavors it should typically be set to
|
||||
# YES.
|
||||
# The default value is: system dependent.
|
||||
|
||||
CASE_SENSE_NAMES = YES
|
||||
@@ -572,6 +624,12 @@ HIDE_SCOPE_NAMES = NO
|
||||
|
||||
HIDE_COMPOUND_REFERENCE= NO
|
||||
|
||||
# If the SHOW_HEADERFILE tag is set to YES then the documentation for a class
|
||||
# will show which file needs to be included to use the class.
|
||||
# The default value is: YES.
|
||||
|
||||
SHOW_HEADERFILE = YES
|
||||
|
||||
# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of
|
||||
# the files that are included by a file in the documentation of that file.
|
||||
# The default value is: YES.
|
||||
@@ -729,7 +787,8 @@ FILE_VERSION_FILTER =
|
||||
# output files in an output format independent way. To create the layout file
|
||||
# that represents doxygen's defaults, run doxygen with the -l option. You can
|
||||
# optionally specify a file name after the option, if omitted DoxygenLayout.xml
|
||||
# will be used as the name of the layout file.
|
||||
# will be used as the name of the layout file. See also section "Changing the
|
||||
# layout of pages" for information.
|
||||
#
|
||||
# Note that if you run doxygen from a directory containing a file called
|
||||
# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE
|
||||
@@ -775,24 +834,35 @@ WARNINGS = YES
|
||||
WARN_IF_UNDOCUMENTED = YES
|
||||
|
||||
# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for
|
||||
# potential errors in the documentation, such as not documenting some parameters
|
||||
# in a documented function, or documenting parameters that don't exist or using
|
||||
# markup commands wrongly.
|
||||
# potential errors in the documentation, such as documenting some parameters in
|
||||
# a documented function twice, or documenting parameters that don't exist or
|
||||
# using markup commands wrongly.
|
||||
# The default value is: YES.
|
||||
|
||||
WARN_IF_DOC_ERROR = YES
|
||||
|
||||
# If WARN_IF_INCOMPLETE_DOC is set to YES, doxygen will warn about incomplete
|
||||
# function parameter documentation. If set to NO, doxygen will accept that some
|
||||
# parameters have no documentation without warning.
|
||||
# The default value is: YES.
|
||||
|
||||
WARN_IF_INCOMPLETE_DOC = YES
|
||||
|
||||
# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
|
||||
# are documented, but have no documentation for their parameters or return
|
||||
# value. If set to NO, doxygen will only warn about wrong or incomplete
|
||||
# parameter documentation, but not about the absence of documentation. If
|
||||
# EXTRACT_ALL is set to YES then this flag will automatically be disabled.
|
||||
# value. If set to NO, doxygen will only warn about wrong parameter
|
||||
# documentation, but not about the absence of documentation. If EXTRACT_ALL is
|
||||
# set to YES then this flag will automatically be disabled. See also
|
||||
# WARN_IF_INCOMPLETE_DOC
|
||||
# The default value is: NO.
|
||||
|
||||
WARN_NO_PARAMDOC = NO
|
||||
|
||||
# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when
|
||||
# a warning is encountered.
|
||||
# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS
|
||||
# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but
|
||||
# at the end of the doxygen process doxygen will return with a non-zero status.
|
||||
# Possible values are: NO, YES and FAIL_ON_WARNINGS.
|
||||
# The default value is: NO.
|
||||
|
||||
WARN_AS_ERROR = NO
|
||||
@@ -803,13 +873,27 @@ WARN_AS_ERROR = NO
|
||||
# and the warning text. Optionally the format may contain $version, which will
|
||||
# be replaced by the version of the file (if it could be obtained via
|
||||
# FILE_VERSION_FILTER)
|
||||
# See also: WARN_LINE_FORMAT
|
||||
# The default value is: $file:$line: $text.
|
||||
|
||||
WARN_FORMAT = "$file:$line: $text"
|
||||
|
||||
# In the $text part of the WARN_FORMAT command it is possible that a reference
|
||||
# to a more specific place is given. To make it easier to jump to this place
|
||||
# (outside of doxygen) the user can define a custom "cut" / "paste" string.
|
||||
# Example:
|
||||
# WARN_LINE_FORMAT = "'vi $file +$line'"
|
||||
# See also: WARN_FORMAT
|
||||
# The default value is: at line $line of file $file.
|
||||
|
||||
WARN_LINE_FORMAT = "at line $line of file $file"
|
||||
|
||||
# The WARN_LOGFILE tag can be used to specify a file to which warning and error
|
||||
# messages should be written. If left blank the output is written to standard
|
||||
# error (stderr).
|
||||
# error (stderr). In case the file specified cannot be opened for writing the
|
||||
# warning and error messages are written to standard error. When as file - is
|
||||
# specified the warning and error messages are written to standard output
|
||||
# (stdout).
|
||||
|
||||
WARN_LOGFILE =
|
||||
|
||||
@@ -823,13 +907,13 @@ WARN_LOGFILE =
|
||||
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
|
||||
# Note: If this tag is empty the current directory is searched.
|
||||
|
||||
INPUT = aedis examples
|
||||
INPUT = include examples README.md
|
||||
|
||||
# This tag can be used to specify the character encoding of the source files
|
||||
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
|
||||
# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
|
||||
# documentation (see: https://www.gnu.org/software/libiconv/) for the list of
|
||||
# possible encodings.
|
||||
# documentation (see:
|
||||
# https://www.gnu.org/software/libiconv/) for the list of possible encodings.
|
||||
# The default value is: UTF-8.
|
||||
|
||||
INPUT_ENCODING = UTF-8
|
||||
@@ -842,15 +926,18 @@ INPUT_ENCODING = UTF-8
|
||||
# need to set EXTENSION_MAPPING for the extension otherwise the files are not
|
||||
# read by doxygen.
|
||||
#
|
||||
# Note the list of default checked file patterns might differ from the list of
|
||||
# default file extension mappings.
|
||||
#
|
||||
# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,
|
||||
# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,
|
||||
# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc,
|
||||
# *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment),
|
||||
# *.doc (to be provided as doxygen C comment), *.txt (to be provided as doxygen
|
||||
# C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f, *.for, *.tcl, *.vhd,
|
||||
# *.hh, *.hxx, *.hpp, *.h++, *.l, *.cs, *.d, *.php, *.php4, *.php5, *.phtml,
|
||||
# *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C
|
||||
# comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd,
|
||||
# *.vhdl, *.ucf, *.qsf and *.ice.
|
||||
|
||||
FILE_PATTERNS = *.hpp *.cpp
|
||||
FILE_PATTERNS = *.hpp \
|
||||
*.cpp
|
||||
|
||||
# The RECURSIVE tag can be used to specify whether or not subdirectories should
|
||||
# be searched for input files as well.
|
||||
@@ -887,7 +974,7 @@ EXCLUDE_PATTERNS =
|
||||
# (namespaces, classes, functions, etc.) that should be excluded from the
|
||||
# output. The symbol name can be a fully qualified name, a word, or if the
|
||||
# wildcard * is used, a substring. Examples: ANamespace, AClass,
|
||||
# AClass::ANamespace, ANamespace::*Test
|
||||
# ANamespace::AClass, ANamespace::*Test
|
||||
#
|
||||
# Note that the wildcards are matched against the file with absolute path, so to
|
||||
# exclude all test directories use the pattern */test/*
|
||||
@@ -974,7 +1061,7 @@ FILTER_SOURCE_PATTERNS =
|
||||
# (index.html). This can be useful if you have a project on for instance GitHub
|
||||
# and want to reuse the introduction page also for the doxygen output.
|
||||
|
||||
USE_MDFILE_AS_MAINPAGE =
|
||||
USE_MDFILE_AS_MAINPAGE = README.md
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Configuration options related to source browsing
|
||||
@@ -1063,16 +1150,24 @@ USE_HTAGS = NO
|
||||
VERBATIM_HEADERS = YES
|
||||
|
||||
# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the
|
||||
# clang parser (see: http://clang.llvm.org/) for more accurate parsing at the
|
||||
# cost of reduced performance. This can be particularly helpful with template
|
||||
# rich C++ code for which doxygen's built-in parser lacks the necessary type
|
||||
# information.
|
||||
# clang parser (see:
|
||||
# http://clang.llvm.org/) for more accurate parsing at the cost of reduced
|
||||
# performance. This can be particularly helpful with template rich C++ code for
|
||||
# which doxygen's built-in parser lacks the necessary type information.
|
||||
# Note: The availability of this option depends on whether or not doxygen was
|
||||
# generated with the -Duse_libclang=ON option for CMake.
|
||||
# The default value is: NO.
|
||||
|
||||
CLANG_ASSISTED_PARSING = NO
|
||||
|
||||
# If the CLANG_ASSISTED_PARSING tag is set to YES and the CLANG_ADD_INC_PATHS
|
||||
# tag is set to YES then doxygen will add the directory of each input to the
|
||||
# include path.
|
||||
# The default value is: YES.
|
||||
# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES.
|
||||
|
||||
CLANG_ADD_INC_PATHS = YES
|
||||
|
||||
# If clang assisted parsing is enabled you can provide the compiler with command
|
||||
# line options that you would normally use when invoking the compiler. Note that
|
||||
# the include paths will already be set by doxygen for the files and directories
|
||||
@@ -1082,10 +1177,13 @@ CLANG_ASSISTED_PARSING = NO
|
||||
CLANG_OPTIONS =
|
||||
|
||||
# If clang assisted parsing is enabled you can provide the clang parser with the
|
||||
# path to the compilation database (see:
|
||||
# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) used when the files
|
||||
# were built. This is equivalent to specifying the "-p" option to a clang tool,
|
||||
# such as clang-check. These options will then be passed to the parser.
|
||||
# path to the directory containing a file called compile_commands.json. This
|
||||
# file is the compilation database (see:
|
||||
# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) containing the
|
||||
# options used when the source files were built. This is equivalent to
|
||||
# specifying the -p option to a clang tool, such as clang-check. These options
|
||||
# will then be passed to the parser. Any options specified with CLANG_OPTIONS
|
||||
# will be added as well.
|
||||
# Note: The availability of this option depends on whether or not doxygen was
|
||||
# generated with the -Duse_libclang=ON option for CMake.
|
||||
|
||||
@@ -1201,7 +1299,7 @@ HTML_EXTRA_FILES =
|
||||
|
||||
# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
|
||||
# will adjust the colors in the style sheet and background images according to
|
||||
# this color. Hue is specified as an angle on a colorwheel, see
|
||||
# this color. Hue is specified as an angle on a color-wheel, see
|
||||
# https://en.wikipedia.org/wiki/Hue for more information. For instance the value
|
||||
# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300
|
||||
# purple, and 360 is red again.
|
||||
@@ -1211,7 +1309,7 @@ HTML_EXTRA_FILES =
|
||||
HTML_COLORSTYLE_HUE = 220
|
||||
|
||||
# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors
|
||||
# in the HTML output. For a value of 0 the output will use grayscales only. A
|
||||
# in the HTML output. For a value of 0 the output will use gray-scales only. A
|
||||
# value of 255 will produce the most vivid colors.
|
||||
# Minimum value: 0, maximum value: 255, default value: 100.
|
||||
# This tag requires that the tag GENERATE_HTML is set to YES.
|
||||
@@ -1272,10 +1370,11 @@ HTML_INDEX_NUM_ENTRIES = 100
|
||||
|
||||
# If the GENERATE_DOCSET tag is set to YES, additional index files will be
|
||||
# generated that can be used as input for Apple's Xcode 3 integrated development
|
||||
# environment (see: https://developer.apple.com/xcode/), introduced with OSX
|
||||
# 10.5 (Leopard). To create a documentation set, doxygen will generate a
|
||||
# Makefile in the HTML output directory. Running make will produce the docset in
|
||||
# that directory and running make install will install the docset in
|
||||
# environment (see:
|
||||
# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To
|
||||
# create a documentation set, doxygen will generate a Makefile in the HTML
|
||||
# output directory. Running make will produce the docset in that directory and
|
||||
# running make install will install the docset in
|
||||
# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at
|
||||
# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy
|
||||
# genXcode/_index.html for more information.
|
||||
@@ -1292,6 +1391,13 @@ GENERATE_DOCSET = NO
|
||||
|
||||
DOCSET_FEEDNAME = "Doxygen generated docs"
|
||||
|
||||
# This tag determines the URL of the docset feed. A documentation feed provides
|
||||
# an umbrella under which multiple documentation sets from a single provider
|
||||
# (such as a company or product suite) can be grouped.
|
||||
# This tag requires that the tag GENERATE_DOCSET is set to YES.
|
||||
|
||||
DOCSET_FEEDURL =
|
||||
|
||||
# This tag specifies a string that should uniquely identify the documentation
|
||||
# set bundle. This should be a reverse domain-name style string, e.g.
|
||||
# com.mycompany.MyDocSet. Doxygen will append .docset to the name.
|
||||
@@ -1317,8 +1423,12 @@ DOCSET_PUBLISHER_NAME = Publisher
|
||||
# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three
|
||||
# additional HTML index files: index.hhp, index.hhc, and index.hhk. The
|
||||
# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop
|
||||
# (see: https://www.microsoft.com/en-us/download/details.aspx?id=21138) on
|
||||
# Windows.
|
||||
# on Windows. In the beginning of 2021 Microsoft took the original page, with
|
||||
# a.o. the download links, offline the HTML help workshop was already many years
|
||||
# in maintenance mode). You can download the HTML help workshop from the web
|
||||
# archives at Installation executable (see:
|
||||
# http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo
|
||||
# ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe).
|
||||
#
|
||||
# The HTML Help Workshop contains a compiler that can convert all HTML output
|
||||
# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML
|
||||
@@ -1348,7 +1458,7 @@ CHM_FILE =
|
||||
HHC_LOCATION =
|
||||
|
||||
# The GENERATE_CHI flag controls if a separate .chi index file is generated
|
||||
# (YES) or that it should be included in the master .chm file (NO).
|
||||
# (YES) or that it should be included in the main .chm file (NO).
|
||||
# The default value is: NO.
|
||||
# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
|
||||
|
||||
@@ -1393,7 +1503,8 @@ QCH_FILE =
|
||||
|
||||
# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help
|
||||
# Project output. For more information please see Qt Help Project / Namespace
|
||||
# (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace).
|
||||
# (see:
|
||||
# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace).
|
||||
# The default value is: org.doxygen.Project.
|
||||
# This tag requires that the tag GENERATE_QHP is set to YES.
|
||||
|
||||
@@ -1401,8 +1512,8 @@ QHP_NAMESPACE = org.doxygen.Project
|
||||
|
||||
# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt
|
||||
# Help Project output. For more information please see Qt Help Project / Virtual
|
||||
# Folders (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-
|
||||
# folders).
|
||||
# Folders (see:
|
||||
# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders).
|
||||
# The default value is: doc.
|
||||
# This tag requires that the tag GENERATE_QHP is set to YES.
|
||||
|
||||
@@ -1410,16 +1521,16 @@ QHP_VIRTUAL_FOLDER = doc
|
||||
|
||||
# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom
|
||||
# filter to add. For more information please see Qt Help Project / Custom
|
||||
# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-
|
||||
# filters).
|
||||
# Filters (see:
|
||||
# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters).
|
||||
# This tag requires that the tag GENERATE_QHP is set to YES.
|
||||
|
||||
QHP_CUST_FILTER_NAME =
|
||||
|
||||
# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the
|
||||
# custom filter to add. For more information please see Qt Help Project / Custom
|
||||
# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-
|
||||
# filters).
|
||||
# Filters (see:
|
||||
# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters).
|
||||
# This tag requires that the tag GENERATE_QHP is set to YES.
|
||||
|
||||
QHP_CUST_FILTER_ATTRS =
|
||||
@@ -1431,9 +1542,9 @@ QHP_CUST_FILTER_ATTRS =
|
||||
|
||||
QHP_SECT_FILTER_ATTRS =
|
||||
|
||||
# The QHG_LOCATION tag can be used to specify the location of Qt's
|
||||
# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the
|
||||
# generated .qhp file.
|
||||
# The QHG_LOCATION tag can be used to specify the location (absolute path
|
||||
# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to
|
||||
# run qhelpgenerator on the generated .qhp file.
|
||||
# This tag requires that the tag GENERATE_QHP is set to YES.
|
||||
|
||||
QHG_LOCATION =
|
||||
@@ -1476,16 +1587,28 @@ DISABLE_INDEX = YES
|
||||
# to work a browser that supports JavaScript, DHTML, CSS and frames is required
|
||||
# (i.e. any modern browser). Windows users are probably better off using the
|
||||
# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can
|
||||
# further fine-tune the look of the index. As an example, the default style
|
||||
# sheet generated by doxygen has an example that shows how to put an image at
|
||||
# the root of the tree instead of the PROJECT_NAME. Since the tree basically has
|
||||
# the same information as the tab index, you could consider setting
|
||||
# DISABLE_INDEX to YES when enabling this option.
|
||||
# further fine tune the look of the index (see "Fine-tuning the output"). As an
|
||||
# example, the default style sheet generated by doxygen has an example that
|
||||
# shows how to put an image at the root of the tree instead of the PROJECT_NAME.
|
||||
# Since the tree basically has the same information as the tab index, you could
|
||||
# consider setting DISABLE_INDEX to YES when enabling this option.
|
||||
# The default value is: NO.
|
||||
# This tag requires that the tag GENERATE_HTML is set to YES.
|
||||
|
||||
GENERATE_TREEVIEW = NO
|
||||
|
||||
# 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
|
||||
# area (value NO) or if it should extend to the full height of the window (value
|
||||
# YES). Setting this to YES gives a layout similar to
|
||||
# https://docs.readthedocs.io with more room for contents, but less room for the
|
||||
# project logo, title, and description. If either GENERATE_TREEVIEW or
|
||||
# DISABLE_INDEX is set to NO, this option has no effect.
|
||||
# The default value is: NO.
|
||||
# This tag requires that the tag GENERATE_HTML is set to YES.
|
||||
|
||||
FULL_SIDEBAR = NO
|
||||
|
||||
# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that
|
||||
# doxygen will group on one line in the generated HTML documentation.
|
||||
#
|
||||
@@ -1510,6 +1633,24 @@ TREEVIEW_WIDTH = 250
|
||||
|
||||
EXT_LINKS_IN_WINDOW = NO
|
||||
|
||||
# If the OBFUSCATE_EMAILS tag is set to YES, doxygen will obfuscate email
|
||||
# addresses.
|
||||
# The default value is: YES.
|
||||
# This tag requires that the tag GENERATE_HTML is set to YES.
|
||||
|
||||
OBFUSCATE_EMAILS = YES
|
||||
|
||||
# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg
|
||||
# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see
|
||||
# https://inkscape.org) to generate formulas as SVG images instead of PNGs for
|
||||
# the HTML output. These images will generally look nicer at scaled resolutions.
|
||||
# Possible values are: png (the default) and svg (looks nicer but requires the
|
||||
# pdf2svg or inkscape tool).
|
||||
# The default value is: png.
|
||||
# This tag requires that the tag GENERATE_HTML is set to YES.
|
||||
|
||||
HTML_FORMULA_FORMAT = png
|
||||
|
||||
# Use this tag to change the font size of LaTeX formulas included as images in
|
||||
# the HTML documentation. When you change the font size after a successful
|
||||
# doxygen run you need to manually remove any form_*.png images from the HTML
|
||||
@@ -1547,11 +1688,29 @@ FORMULA_MACROFILE =
|
||||
|
||||
USE_MATHJAX = YES
|
||||
|
||||
# With MATHJAX_VERSION it is possible to specify the MathJax version to be used.
|
||||
# Note that the different versions of MathJax have different requirements with
|
||||
# regards to the different settings, so it is possible that also other MathJax
|
||||
# settings have to be changed when switching between the different MathJax
|
||||
# versions.
|
||||
# Possible values are: MathJax_2 and MathJax_3.
|
||||
# The default value is: MathJax_2.
|
||||
# This tag requires that the tag USE_MATHJAX is set to YES.
|
||||
|
||||
MATHJAX_VERSION = MathJax_2
|
||||
|
||||
# When MathJax is enabled you can set the default output format to be used for
|
||||
# the MathJax output. See the MathJax site (see:
|
||||
# http://docs.mathjax.org/en/latest/output.html) for more details.
|
||||
# the MathJax output. For more details about the output format see MathJax
|
||||
# version 2 (see:
|
||||
# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3
|
||||
# (see:
|
||||
# http://docs.mathjax.org/en/latest/web/components/output.html).
|
||||
# Possible values are: HTML-CSS (which is slower, but has the best
|
||||
# compatibility), NativeMML (i.e. MathML) and SVG.
|
||||
# compatibility. This is the name for Mathjax version 2, for MathJax version 3
|
||||
# this will be translated into chtml), NativeMML (i.e. MathML. Only supported
|
||||
# for NathJax 2. For MathJax version 3 chtml will be used instead.), chtml (This
|
||||
# is the name for Mathjax version 3, for MathJax version 2 this will be
|
||||
# translated into HTML-CSS) and SVG.
|
||||
# The default value is: HTML-CSS.
|
||||
# This tag requires that the tag USE_MATHJAX is set to YES.
|
||||
|
||||
@@ -1564,22 +1723,29 @@ MATHJAX_FORMAT = HTML-CSS
|
||||
# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax
|
||||
# Content Delivery Network so you can quickly see the result without installing
|
||||
# MathJax. However, it is strongly recommended to install a local copy of
|
||||
# MathJax from https://www.mathjax.org before deployment.
|
||||
# The default value is: https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/.
|
||||
# MathJax from https://www.mathjax.org before deployment. The default value is:
|
||||
# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2
|
||||
# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3
|
||||
# This tag requires that the tag USE_MATHJAX is set to YES.
|
||||
|
||||
MATHJAX_RELPATH = https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/
|
||||
|
||||
# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
|
||||
# extension names that should be enabled during MathJax rendering. For example
|
||||
# for MathJax version 2 (see
|
||||
# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions):
|
||||
# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
|
||||
# For example for MathJax version 3 (see
|
||||
# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html):
|
||||
# MATHJAX_EXTENSIONS = ams
|
||||
# This tag requires that the tag USE_MATHJAX is set to YES.
|
||||
|
||||
MATHJAX_EXTENSIONS =
|
||||
|
||||
# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces
|
||||
# of code that will be used on startup of the MathJax code. See the MathJax site
|
||||
# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an
|
||||
# (see:
|
||||
# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an
|
||||
# example see the documentation.
|
||||
# This tag requires that the tag USE_MATHJAX is set to YES.
|
||||
|
||||
@@ -1626,7 +1792,8 @@ SERVER_BASED_SEARCH = NO
|
||||
#
|
||||
# Doxygen ships with an example indexer (doxyindexer) and search engine
|
||||
# (doxysearch.cgi) which are based on the open source search engine library
|
||||
# Xapian (see: https://xapian.org/).
|
||||
# Xapian (see:
|
||||
# https://xapian.org/).
|
||||
#
|
||||
# See the section "External Indexing and Searching" for details.
|
||||
# The default value is: NO.
|
||||
@@ -1639,8 +1806,9 @@ EXTERNAL_SEARCH = NO
|
||||
#
|
||||
# Doxygen ships with an example indexer (doxyindexer) and search engine
|
||||
# (doxysearch.cgi) which are based on the open source search engine library
|
||||
# Xapian (see: https://xapian.org/). See the section "External Indexing and
|
||||
# Searching" for details.
|
||||
# Xapian (see:
|
||||
# https://xapian.org/). See the section "External Indexing and Searching" for
|
||||
# details.
|
||||
# This tag requires that the tag SEARCHENGINE is set to YES.
|
||||
|
||||
SEARCHENGINE_URL =
|
||||
@@ -1749,29 +1917,31 @@ PAPER_TYPE = a4
|
||||
|
||||
EXTRA_PACKAGES =
|
||||
|
||||
# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the
|
||||
# generated LaTeX document. The header should contain everything until the first
|
||||
# chapter. If it is left blank doxygen will generate a standard header. See
|
||||
# section "Doxygen usage" for information on how to let doxygen write the
|
||||
# default header to a separate file.
|
||||
# The LATEX_HEADER tag can be used to specify a user-defined LaTeX header for
|
||||
# the generated LaTeX document. The header should contain everything until the
|
||||
# first chapter. If it is left blank doxygen will generate a standard header. It
|
||||
# is highly recommended to start with a default header using
|
||||
# doxygen -w latex new_header.tex new_footer.tex new_stylesheet.sty
|
||||
# and then modify the file new_header.tex. See also section "Doxygen usage" for
|
||||
# information on how to generate the default header that doxygen normally uses.
|
||||
#
|
||||
# Note: Only use a user-defined header if you know what you are doing! The
|
||||
# following commands have a special meaning inside the header: $title,
|
||||
# $datetime, $date, $doxygenversion, $projectname, $projectnumber,
|
||||
# $projectbrief, $projectlogo. Doxygen will replace $title with the empty
|
||||
# string, for the replacement values of the other commands the user is referred
|
||||
# to HTML_HEADER.
|
||||
# Note: Only use a user-defined header if you know what you are doing!
|
||||
# Note: The header is subject to change so you typically have to regenerate the
|
||||
# default header when upgrading to a newer version of doxygen. The following
|
||||
# commands have a special meaning inside the header (and footer): For a
|
||||
# description of the possible markers and block names see the documentation.
|
||||
# This tag requires that the tag GENERATE_LATEX is set to YES.
|
||||
|
||||
LATEX_HEADER =
|
||||
|
||||
# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the
|
||||
# generated LaTeX document. The footer should contain everything after the last
|
||||
# chapter. If it is left blank doxygen will generate a standard footer. See
|
||||
# The LATEX_FOOTER tag can be used to specify a user-defined LaTeX footer for
|
||||
# the generated LaTeX document. The footer should contain everything after the
|
||||
# last chapter. If it is left blank doxygen will generate a standard footer. See
|
||||
# LATEX_HEADER for more information on how to generate a default footer and what
|
||||
# special commands can be used inside the footer.
|
||||
#
|
||||
# Note: Only use a user-defined footer if you know what you are doing!
|
||||
# special commands can be used inside the footer. See also section "Doxygen
|
||||
# usage" for information on how to generate the default footer that doxygen
|
||||
# normally uses. Note: Only use a user-defined footer if you know what you are
|
||||
# doing!
|
||||
# This tag requires that the tag GENERATE_LATEX is set to YES.
|
||||
|
||||
LATEX_FOOTER =
|
||||
@@ -1804,9 +1974,11 @@ LATEX_EXTRA_FILES =
|
||||
|
||||
PDF_HYPERLINKS = YES
|
||||
|
||||
# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate
|
||||
# the PDF file directly from the LaTeX files. Set this option to YES, to get a
|
||||
# higher quality PDF documentation.
|
||||
# If the USE_PDFLATEX tag is set to YES, doxygen will use the engine as
|
||||
# specified with LATEX_CMD_NAME to generate the PDF file directly from the LaTeX
|
||||
# files. Set this option to YES, to get a higher quality PDF documentation.
|
||||
#
|
||||
# See also section LATEX_CMD_NAME for selecting the engine.
|
||||
# The default value is: YES.
|
||||
# This tag requires that the tag GENERATE_LATEX is set to YES.
|
||||
|
||||
@@ -1814,8 +1986,7 @@ USE_PDFLATEX = YES
|
||||
|
||||
# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode
|
||||
# command to the generated LaTeX files. This will instruct LaTeX to keep running
|
||||
# if errors occur, instead of asking the user for help. This option is also used
|
||||
# when generating formulas in HTML.
|
||||
# if errors occur, instead of asking the user for help.
|
||||
# The default value is: NO.
|
||||
# This tag requires that the tag GENERATE_LATEX is set to YES.
|
||||
|
||||
@@ -1828,16 +1999,6 @@ LATEX_BATCHMODE = NO
|
||||
|
||||
LATEX_HIDE_INDICES = NO
|
||||
|
||||
# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source
|
||||
# code with syntax highlighting in the LaTeX output.
|
||||
#
|
||||
# Note that which sources are shown also depends on other settings such as
|
||||
# SOURCE_BROWSER.
|
||||
# The default value is: NO.
|
||||
# This tag requires that the tag GENERATE_LATEX is set to YES.
|
||||
|
||||
LATEX_SOURCE_CODE = NO
|
||||
|
||||
# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
|
||||
# bibliography, e.g. plainnat, or ieeetr. See
|
||||
# https://en.wikipedia.org/wiki/BibTeX and \cite for more info.
|
||||
@@ -1918,16 +2079,6 @@ RTF_STYLESHEET_FILE =
|
||||
|
||||
RTF_EXTENSIONS_FILE =
|
||||
|
||||
# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code
|
||||
# with syntax highlighting in the RTF output.
|
||||
#
|
||||
# Note that which sources are shown also depends on other settings such as
|
||||
# SOURCE_BROWSER.
|
||||
# The default value is: NO.
|
||||
# This tag requires that the tag GENERATE_RTF is set to YES.
|
||||
|
||||
RTF_SOURCE_CODE = NO
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Configuration options related to the man page output
|
||||
#---------------------------------------------------------------------------
|
||||
@@ -2024,15 +2175,6 @@ GENERATE_DOCBOOK = NO
|
||||
|
||||
DOCBOOK_OUTPUT = docbook
|
||||
|
||||
# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the
|
||||
# program listings (including syntax highlighting and cross-referencing
|
||||
# information) to the DOCBOOK output. Note that enabling this will significantly
|
||||
# increase the size of the DOCBOOK output.
|
||||
# The default value is: NO.
|
||||
# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
|
||||
|
||||
DOCBOOK_PROGRAMLISTING = NO
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Configuration options for the AutoGen Definitions output
|
||||
#---------------------------------------------------------------------------
|
||||
@@ -2119,7 +2261,8 @@ SEARCH_INCLUDES = YES
|
||||
|
||||
# The INCLUDE_PATH tag can be used to specify one or more directories that
|
||||
# contain include files that are not input files but should be processed by the
|
||||
# preprocessor.
|
||||
# preprocessor. Note that the INCLUDE_PATH is not recursive, so the setting of
|
||||
# RECURSIVE has no effect here.
|
||||
# This tag requires that the tag SEARCH_INCLUDES is set to YES.
|
||||
|
||||
INCLUDE_PATH =
|
||||
@@ -2211,15 +2354,6 @@ EXTERNAL_PAGES = YES
|
||||
# Configuration options related to the dot tool
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram
|
||||
# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to
|
||||
# NO turns the diagrams off. Note that this option also works with HAVE_DOT
|
||||
# disabled, but it is recommended to install and use dot, since it yields more
|
||||
# powerful graphs.
|
||||
# The default value is: YES.
|
||||
|
||||
CLASS_DIAGRAMS = NO
|
||||
|
||||
# You can include diagrams made with dia in doxygen documentation. Doxygen will
|
||||
# then run dia to produce the diagram and insert it in the documentation. The
|
||||
# DIA_PATH tag allows you to specify the directory where the dia binary resides.
|
||||
@@ -2276,11 +2410,14 @@ DOT_FONTSIZE = 10
|
||||
|
||||
DOT_FONTPATH =
|
||||
|
||||
# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for
|
||||
# each documented class showing the direct and indirect inheritance relations.
|
||||
# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO.
|
||||
# If the CLASS_GRAPH tag is set to YES (or GRAPH) then doxygen will generate a
|
||||
# graph for each documented class showing the direct and indirect inheritance
|
||||
# relations. In case HAVE_DOT is set as well dot will be used to draw the graph,
|
||||
# otherwise the built-in generator will be used. If the CLASS_GRAPH tag is set
|
||||
# to TEXT the direct and indirect inheritance relations will be shown as texts /
|
||||
# links.
|
||||
# Possible values are: NO, YES, TEXT and GRAPH.
|
||||
# The default value is: YES.
|
||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||
|
||||
CLASS_GRAPH = NO
|
||||
|
||||
@@ -2294,7 +2431,8 @@ CLASS_GRAPH = NO
|
||||
COLLABORATION_GRAPH = YES
|
||||
|
||||
# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for
|
||||
# groups, showing the direct groups dependencies.
|
||||
# groups, showing the direct groups dependencies. See also the chapter Grouping
|
||||
# in the manual.
|
||||
# The default value is: YES.
|
||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||
|
||||
@@ -2317,10 +2455,32 @@ UML_LOOK = NO
|
||||
# but if the number exceeds 15, the total amount of fields shown is limited to
|
||||
# 10.
|
||||
# Minimum value: 0, maximum value: 100, default value: 10.
|
||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||
# This tag requires that the tag UML_LOOK is set to YES.
|
||||
|
||||
UML_LIMIT_NUM_FIELDS = 10
|
||||
|
||||
# If the DOT_UML_DETAILS tag is set to NO, doxygen will show attributes and
|
||||
# methods without types and arguments in the UML graphs. If the DOT_UML_DETAILS
|
||||
# tag is set to YES, doxygen will add type and arguments for attributes and
|
||||
# methods in the UML graphs. If the DOT_UML_DETAILS tag is set to NONE, doxygen
|
||||
# will not generate fields with class member information in the UML graphs. The
|
||||
# class diagrams will look similar to the default class diagrams but using UML
|
||||
# notation for the relationships.
|
||||
# Possible values are: NO, YES and NONE.
|
||||
# The default value is: NO.
|
||||
# This tag requires that the tag UML_LOOK is set to YES.
|
||||
|
||||
DOT_UML_DETAILS = NO
|
||||
|
||||
# The DOT_WRAP_THRESHOLD tag can be used to set the maximum number of characters
|
||||
# to display on a single line. If the actual line length exceeds this threshold
|
||||
# significantly it will wrapped across multiple lines. Some heuristics are apply
|
||||
# to avoid ugly line breaks.
|
||||
# Minimum value: 0, maximum value: 1000, default value: 17.
|
||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||
|
||||
DOT_WRAP_THRESHOLD = 17
|
||||
|
||||
# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and
|
||||
# collaboration graphs will show the relations between templates and their
|
||||
# instances.
|
||||
@@ -2387,6 +2547,13 @@ GRAPHICAL_HIERARCHY = YES
|
||||
|
||||
DIRECTORY_GRAPH = NO
|
||||
|
||||
# The DIR_GRAPH_MAX_DEPTH tag can be used to limit the maximum number of levels
|
||||
# of child directories generated in directory dependency graphs by dot.
|
||||
# Minimum value: 1, maximum value: 25, default value: 1.
|
||||
# This tag requires that the tag DIRECTORY_GRAPH is set to YES.
|
||||
|
||||
DIR_GRAPH_MAX_DEPTH = 1
|
||||
|
||||
# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
|
||||
# generated by dot. For an explanation of the image formats see the section
|
||||
# output formats in the documentation of the dot tool (Graphviz (see:
|
||||
@@ -2394,10 +2561,9 @@ DIRECTORY_GRAPH = NO
|
||||
# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order
|
||||
# to make the SVG files visible in IE 9+ (other browsers do not have this
|
||||
# requirement).
|
||||
# Possible values are: png, png:cairo, png:cairo:cairo, png:cairo:gd, png:gd,
|
||||
# png:gd:gd, jpg, jpg:cairo, jpg:cairo:gd, jpg:gd, jpg:gd:gd, gif, gif:cairo,
|
||||
# gif:cairo:gd, gif:gd, gif:gd:gd, svg, png:gd, png:gd:gd, png:cairo,
|
||||
# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and
|
||||
# Possible values are: png, jpg, jpg:cairo, jpg:cairo:gd, jpg:gd, jpg:gd:gd,
|
||||
# gif, gif:cairo, gif:cairo:gd, gif:gd, gif:gd:gd, svg, png:gd, png:gd:gd,
|
||||
# png:cairo, png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and
|
||||
# png:gdiplus:gdiplus.
|
||||
# The default value is: png.
|
||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||
@@ -2442,10 +2608,10 @@ MSCFILE_DIRS =
|
||||
DIAFILE_DIRS =
|
||||
|
||||
# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the
|
||||
# path where java can find the plantuml.jar file. If left blank, it is assumed
|
||||
# PlantUML is not used or called during a preprocessing step. Doxygen will
|
||||
# generate a warning when it encounters a \startuml command in this case and
|
||||
# will not generate output for the diagram.
|
||||
# path where java can find the plantuml.jar file or to the filename of jar file
|
||||
# to be used. If left blank, it is assumed PlantUML is not used or called during
|
||||
# a preprocessing step. Doxygen will generate a warning when it encounters a
|
||||
# \startuml command in this case and will not generate output for the diagram.
|
||||
|
||||
PLANTUML_JAR_PATH =
|
||||
|
||||
@@ -2507,14 +2673,18 @@ DOT_MULTI_TARGETS = NO
|
||||
# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page
|
||||
# explaining the meaning of the various boxes and arrows in the dot generated
|
||||
# graphs.
|
||||
# Note: This tag requires that UML_LOOK isn't set, i.e. the doxygen internal
|
||||
# graphical representation for inheritance and collaboration diagrams is used.
|
||||
# The default value is: YES.
|
||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||
|
||||
GENERATE_LEGEND = YES
|
||||
|
||||
# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate dot
|
||||
# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate
|
||||
# files that are used to generate the various graphs.
|
||||
#
|
||||
# Note: This setting is not only used for dot files but also for msc temporary
|
||||
# files.
|
||||
# The default value is: YES.
|
||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||
|
||||
DOT_CLEANUP = YES
|
||||
|
||||
@@ -26,5 +26,5 @@ div.contents {
|
||||
|
||||
code
|
||||
{
|
||||
background-color:#f0e9ce;
|
||||
background-color:#fffbeb;
|
||||
}
|
||||
|
||||
@@ -4,156 +4,103 @@
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <queue>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include "unistd.h"
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#include <aedis/aedis.hpp>
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT) && defined(BOOST_ASIO_HAS_POSIX_STREAM_DESCRIPTOR)
|
||||
#include <boost/asio/experimental/awaitable_operators.hpp>
|
||||
#include <aedis.hpp>
|
||||
#include "print.hpp"
|
||||
|
||||
// Include this in no more than one .cpp file.
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
namespace net = boost::asio;
|
||||
using namespace net::experimental::awaitable_operators;
|
||||
using aedis::adapt;
|
||||
using aedis::resp3::request;
|
||||
using aedis::resp3::node;
|
||||
using aedis::endpoint;
|
||||
using tcp_socket = net::use_awaitable_t<>::as_default_on_t<net::ip::tcp::socket>;
|
||||
using tcp_acceptor = net::use_awaitable_t<>::as_default_on_t<net::ip::tcp::acceptor>;
|
||||
using stream_descriptor = net::use_awaitable_t<>::as_default_on_t<net::posix::stream_descriptor>;
|
||||
using connection = aedis::connection<tcp_socket>;
|
||||
using response_type = std::vector<aedis::resp3::node<std::string>>;
|
||||
using stimer = net::use_awaitable_t<>::as_default_on_t<net::steady_timer>;
|
||||
|
||||
class user_session:
|
||||
public std::enable_shared_from_this<user_session> {
|
||||
public:
|
||||
user_session(tcp_socket socket)
|
||||
: socket_(std::move(socket))
|
||||
, timer_(socket_.get_executor())
|
||||
{ timer_.expires_at(std::chrono::steady_clock::time_point::max()); }
|
||||
// Chat over redis pubsub. To test, run this program from different
|
||||
// terminals and type messages to stdin. Use
|
||||
//
|
||||
// $ redis-cli monitor
|
||||
//
|
||||
// to monitor the message traffic.
|
||||
|
||||
void start(std::shared_ptr<connection> db)
|
||||
{
|
||||
co_spawn(socket_.get_executor(),
|
||||
[self = shared_from_this(), db]{ return self->reader(db); },
|
||||
net::detached);
|
||||
|
||||
co_spawn(socket_.get_executor(),
|
||||
[self = shared_from_this()]{ return self->writer(); },
|
||||
net::detached);
|
||||
}
|
||||
|
||||
void deliver(std::string const& msg)
|
||||
{
|
||||
write_msgs_.push_back(msg);
|
||||
timer_.cancel_one();
|
||||
}
|
||||
|
||||
private:
|
||||
net::awaitable<void> reader(std::shared_ptr<connection> db)
|
||||
{
|
||||
try {
|
||||
std::string msg;
|
||||
request req;
|
||||
auto dbuffer = net::dynamic_buffer(msg, 1024);
|
||||
for (;;) {
|
||||
auto const n = co_await net::async_read_until(socket_, dbuffer, "\n");
|
||||
req.push("PUBLISH", "channel", msg);
|
||||
co_await db->async_exec(req);
|
||||
req.clear();
|
||||
msg.erase(0, n);
|
||||
}
|
||||
} catch (std::exception&) {
|
||||
stop();
|
||||
}
|
||||
}
|
||||
|
||||
net::awaitable<void> writer()
|
||||
{
|
||||
try {
|
||||
while (socket_.is_open()) {
|
||||
if (write_msgs_.empty()) {
|
||||
boost::system::error_code ec;
|
||||
co_await timer_.async_wait(net::redirect_error(net::use_awaitable, ec));
|
||||
} else {
|
||||
co_await net::async_write(socket_, net::buffer(write_msgs_.front()));
|
||||
write_msgs_.pop_front();
|
||||
}
|
||||
}
|
||||
} catch (std::exception&) {
|
||||
stop();
|
||||
}
|
||||
}
|
||||
|
||||
void stop()
|
||||
{
|
||||
socket_.close();
|
||||
timer_.cancel();
|
||||
}
|
||||
|
||||
tcp_socket socket_;
|
||||
net::steady_timer timer_;
|
||||
std::deque<std::string> write_msgs_;
|
||||
};
|
||||
|
||||
using sessions_type = std::vector<std::shared_ptr<user_session>>;
|
||||
|
||||
net::awaitable<void>
|
||||
reader(
|
||||
std::shared_ptr<connection> db,
|
||||
std::shared_ptr<sessions_type> sessions)
|
||||
// Receives messages from other users.
|
||||
net::awaitable<void> push_receiver(std::shared_ptr<connection> conn)
|
||||
{
|
||||
request req;
|
||||
req.push("SUBSCRIBE", "channel");
|
||||
co_await db->async_exec(req);
|
||||
|
||||
for (response_type resp;;) {
|
||||
co_await db->async_read_push(adapt(resp));
|
||||
|
||||
for (auto& session: *sessions)
|
||||
session->deliver(resp.at(3).value);
|
||||
|
||||
for (std::vector<node<std::string>> resp;;) {
|
||||
co_await conn->async_receive(adapt(resp));
|
||||
print_push(resp);
|
||||
resp.clear();
|
||||
}
|
||||
}
|
||||
|
||||
net::awaitable<void>
|
||||
listener(
|
||||
std::shared_ptr<tcp_acceptor> acc,
|
||||
std::shared_ptr<connection> db,
|
||||
std::shared_ptr<sessions_type> sessions)
|
||||
// Subscribes to the channels when a new connection is stablished.
|
||||
net::awaitable<void> reconnect(std::shared_ptr<connection> conn)
|
||||
{
|
||||
request req;
|
||||
req.push("HELLO", 3);
|
||||
co_await db->async_exec(req);
|
||||
req.get_config().cancel_on_connection_lost = true;
|
||||
req.push("SUBSCRIBE", "chat-channel");
|
||||
|
||||
stimer timer{co_await net::this_coro::executor};
|
||||
endpoint ep{"127.0.0.1", "6379"};
|
||||
for (;;) {
|
||||
auto socket = co_await acc->async_accept();
|
||||
auto session = std::make_shared<user_session>(std::move(socket));
|
||||
sessions->push_back(session);
|
||||
session->start(db);
|
||||
boost::system::error_code ec1, ec2;
|
||||
co_await (
|
||||
conn->async_run(ep, {}, net::redirect_error(net::use_awaitable, ec1)) &&
|
||||
conn->async_exec(req, adapt(), net::redirect_error(net::use_awaitable, ec2))
|
||||
);
|
||||
std::clog << "async_run: " << ec1.message() << "\n"
|
||||
<< "async_exec: " << ec2.message() << std::endl;
|
||||
conn->reset_stream();
|
||||
timer.expires_after(std::chrono::seconds{1});
|
||||
co_await timer.async_wait();
|
||||
}
|
||||
}
|
||||
|
||||
auto handler =[](auto ec, auto...)
|
||||
{ std::cout << ec.message() << std::endl; };
|
||||
// Publishes messages to other users.
|
||||
net::awaitable<void> publisher(stream_descriptor& in, std::shared_ptr<connection> conn)
|
||||
{
|
||||
for (std::string msg;;) {
|
||||
auto n = co_await net::async_read_until(in, net::dynamic_buffer(msg, 1024), "\n");
|
||||
request req;
|
||||
req.push("PUBLISH", "chat-channel", msg);
|
||||
co_await conn->async_exec(req);
|
||||
msg.erase(0, n);
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
auto main() -> int
|
||||
{
|
||||
try {
|
||||
net::io_context ioc{1};
|
||||
stream_descriptor in{ioc, ::dup(STDIN_FILENO)};
|
||||
|
||||
// Redis client and receiver.
|
||||
auto db = std::make_shared<connection>(ioc);
|
||||
db->async_run("127.0.0.1", "6379", handler);
|
||||
auto conn = std::make_shared<connection>(ioc);
|
||||
co_spawn(ioc, publisher(in, conn), net::detached);
|
||||
co_spawn(ioc, push_receiver(conn), net::detached);
|
||||
co_spawn(ioc, reconnect(conn), net::detached);
|
||||
|
||||
auto sessions = std::make_shared<sessions_type>();
|
||||
net::co_spawn(ioc, reader(db, sessions), net::detached);
|
||||
|
||||
// TCP acceptor.
|
||||
auto endpoint = net::ip::tcp::endpoint{net::ip::tcp::v4(), 55555};
|
||||
auto acc = std::make_shared<tcp_acceptor>(ioc, endpoint);
|
||||
co_spawn(ioc, listener(acc, db, sessions), net::detached);
|
||||
net::signal_set signals(ioc, SIGINT, SIGTERM);
|
||||
signals.async_wait([&](auto, auto){ ioc.stop(); });
|
||||
|
||||
ioc.run();
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
#else // defined(BOOST_ASIO_HAS_CO_AWAIT) && defined(BOOST_ASIO_HAS_POSIX_STREAM_DESCRIPTOR)
|
||||
auto main() -> int {std::cout << "Requires coroutine support." << std::endl; return 0;}
|
||||
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT) && defined(BOOST_ASIO_HAS_POSIX_STREAM_DESCRIPTOR)
|
||||
|
||||
@@ -7,57 +7,119 @@
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
#include <aedis/aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
#include <boost/asio/experimental/awaitable_operators.hpp>
|
||||
#include <aedis.hpp>
|
||||
#include "print.hpp"
|
||||
|
||||
// Include this in no more than one .cpp file.
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
namespace net = boost::asio;
|
||||
using boost::optional;
|
||||
using namespace net::experimental::awaitable_operators;
|
||||
using aedis::adapt;
|
||||
using aedis::resp3::request;
|
||||
using connection = aedis::connection<>;
|
||||
using aedis::endpoint;
|
||||
using tcp_socket = net::use_awaitable_t<>::as_default_on_t<net::ip::tcp::socket>;
|
||||
using connection = aedis::connection<tcp_socket>;
|
||||
|
||||
// $ redis-cli
|
||||
// > ACL SETUSER mzimbres on >Jabuticaba ~* +@all
|
||||
// OK
|
||||
|
||||
int main()
|
||||
// To avoid verbosity.
|
||||
auto redir(boost::system::error_code& ec)
|
||||
{
|
||||
return net::redirect_error(net::use_awaitable, ec);
|
||||
}
|
||||
|
||||
// Sends some containers.
|
||||
net::awaitable<void> send(endpoint ep)
|
||||
{
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
|
||||
std::vector<int> vec
|
||||
{1, 2, 3, 4, 5, 6};
|
||||
|
||||
std::map<std::string, int> map
|
||||
{{"key1", 10}, {"key2", 20}, {"key3", 30}};
|
||||
std::map<std::string, std::string> map
|
||||
{{"key1", "value1"}, {"key2", "value2"}, {"key3", "value3"}};
|
||||
|
||||
request req;
|
||||
req.push("AUTH", "mzimbres", "Jabuticaba");
|
||||
req.push("HELLO", 3);
|
||||
req.push_range("RPUSH", "rpush-key", vec);
|
||||
req.push_range("HSET", "hset-key", map);
|
||||
req.push("MULTI");
|
||||
req.push("LRANGE", "rpush-key", 0, -1);
|
||||
req.get_config().cancel_on_connection_lost = true;
|
||||
req.push_range("RPUSH", "rpush-key", vec); // Sends
|
||||
req.push_range("HSET", "hset-key", map); // Sends
|
||||
req.push("QUIT");
|
||||
|
||||
connection conn{ex};
|
||||
co_await (conn.async_run(ep) || conn.async_exec(req));
|
||||
}
|
||||
|
||||
// Retrieves a Redis hash as an std::map.
|
||||
net::awaitable<std::map<std::string, std::string>> retrieve_hashes(endpoint ep)
|
||||
{
|
||||
connection conn{co_await net::this_coro::executor};
|
||||
|
||||
request req;
|
||||
req.get_config().cancel_on_connection_lost = true;
|
||||
req.push("HGETALL", "hset-key");
|
||||
req.push("QUIT");
|
||||
|
||||
std::map<std::string, std::string> ret;
|
||||
auto resp = std::tie(ret, std::ignore);
|
||||
co_await (conn.async_run(ep) || conn.async_exec(req, adapt(resp)));
|
||||
|
||||
co_return std::move(ret);
|
||||
}
|
||||
|
||||
// Retrieves as a data structure.
|
||||
net::awaitable<void> transaction(endpoint ep)
|
||||
{
|
||||
connection conn{co_await net::this_coro::executor};
|
||||
|
||||
request req;
|
||||
req.get_config().cancel_on_connection_lost = true;
|
||||
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, // auth
|
||||
aedis::ignore, // hello
|
||||
aedis::ignore, // rpush
|
||||
aedis::ignore, // hset
|
||||
aedis::ignore, // multi
|
||||
aedis::ignore, // lrange
|
||||
aedis::ignore, // hgetall
|
||||
std::tuple<optional<std::vector<int>>, optional<std::map<std::string, int>>>, // exec
|
||||
std::tuple<std::optional<std::vector<int>>, std::optional<std::map<std::string, std::string>>>, // exec
|
||||
aedis::ignore // quit
|
||||
> resp;
|
||||
|
||||
net::io_context ioc;
|
||||
connection db{ioc};
|
||||
db.async_exec("127.0.0.1", "6379", req, aedis::adapt(resp),
|
||||
[](auto ec, auto) { std::cout << ec.message() << std::endl; });
|
||||
ioc.run();
|
||||
co_await (conn.async_run(ep) || conn.async_exec(req, adapt(resp)));
|
||||
|
||||
print(std::get<0>(std::get<7>(resp)).value());
|
||||
print(std::get<1>(std::get<7>(resp)).value());
|
||||
print(std::get<0>(std::get<3>(resp)).value());
|
||||
print(std::get<1>(std::get<3>(resp)).value());
|
||||
}
|
||||
|
||||
net::awaitable<void> async_main()
|
||||
{
|
||||
try {
|
||||
endpoint ep{"127.0.0.1", "6379"};
|
||||
co_await send(ep);
|
||||
co_await transaction(ep);
|
||||
auto const hashes = co_await retrieve_hashes(ep);
|
||||
print(hashes);
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
auto main() -> int
|
||||
{
|
||||
try {
|
||||
net::io_context ioc;
|
||||
net::co_spawn(ioc, async_main(), net::detached);
|
||||
ioc.run();
|
||||
} catch (...) {
|
||||
std::cerr << "Error." << 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)
|
||||
|
||||
@@ -6,60 +6,82 @@
|
||||
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#include <aedis/aedis.hpp>
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
#include <aedis.hpp>
|
||||
|
||||
// Include this in no more than one .cpp file.
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
namespace net = boost::asio;
|
||||
using aedis::adapt;
|
||||
using aedis::resp3::request;
|
||||
using 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 aedis::endpoint;
|
||||
using executor_type = net::io_context::executor_type;
|
||||
using socket_type = net::basic_stream_socket<net::ip::tcp, executor_type>;
|
||||
using tcp_socket = net::use_awaitable_t<executor_type>::as_default_on_t<socket_type>;
|
||||
using acceptor_type = net::basic_socket_acceptor<net::ip::tcp, executor_type>;
|
||||
using tcp_acceptor = net::use_awaitable_t<executor_type>::as_default_on_t<acceptor_type>;
|
||||
using awaitable_type = net::awaitable<void, executor_type>;
|
||||
using connection = aedis::connection<tcp_socket>;
|
||||
|
||||
net::awaitable<void> echo_loop(tcp_socket socket, std::shared_ptr<connection> db)
|
||||
awaitable_type echo_server_session(tcp_socket socket, std::shared_ptr<connection> db)
|
||||
{
|
||||
try {
|
||||
request req;
|
||||
std::tuple<std::string> resp;
|
||||
std::string buffer;
|
||||
request req;
|
||||
std::tuple<std::string> response;
|
||||
|
||||
for (;;) {
|
||||
auto n = co_await net::async_read_until(socket, net::dynamic_buffer(buffer, 1024), "\n");
|
||||
req.push("PING", buffer);
|
||||
co_await db->async_exec(req, adapt(resp));
|
||||
co_await net::async_write(socket, net::buffer(std::get<0>(resp)));
|
||||
std::get<0>(resp).clear();
|
||||
req.clear();
|
||||
buffer.erase(0, n);
|
||||
}
|
||||
} catch (std::exception const& e) {
|
||||
std::cout << e.what() << std::endl;
|
||||
for (std::string buffer;;) {
|
||||
auto n = co_await net::async_read_until(socket, net::dynamic_buffer(buffer, 1024), "\n");
|
||||
req.push("PING", buffer);
|
||||
co_await db->async_exec(req, adapt(response));
|
||||
co_await net::async_write(socket, net::buffer(std::get<0>(response)));
|
||||
std::get<0>(response).clear();
|
||||
req.clear();
|
||||
buffer.erase(0, n);
|
||||
}
|
||||
}
|
||||
|
||||
net::awaitable<void> listener()
|
||||
awaitable_type listener(std::shared_ptr<connection> db)
|
||||
{
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
auto db = std::make_shared<connection>(ex);
|
||||
db->async_run("127.0.0.1", "6379", net::detached);
|
||||
|
||||
request req;
|
||||
req.push("HELLO", 3);
|
||||
co_await db->async_exec(req);
|
||||
|
||||
tcp_acceptor acc(ex, {net::ip::tcp::v4(), 55555});
|
||||
for (;;)
|
||||
net::co_spawn(ex, echo_loop(co_await acc.async_accept(), db), net::detached);
|
||||
net::co_spawn(ex, echo_server_session(co_await acc.async_accept(), db), net::detached);
|
||||
}
|
||||
|
||||
int main()
|
||||
net::awaitable<void> reconnect(std::shared_ptr<connection> conn)
|
||||
{
|
||||
net::steady_timer timer{co_await net::this_coro::executor};
|
||||
endpoint ep{"127.0.0.1", "6379"};
|
||||
for (boost::system::error_code ec1;;) {
|
||||
co_await conn->async_run(ep, {}, net::redirect_error(net::use_awaitable, ec1));
|
||||
std::clog << "async_run: " << ec1.message() << std::endl;
|
||||
conn->reset_stream();
|
||||
timer.expires_after(std::chrono::seconds{1});
|
||||
co_await timer.async_wait(net::use_awaitable);
|
||||
}
|
||||
}
|
||||
|
||||
auto main() -> int
|
||||
{
|
||||
try {
|
||||
net::io_context ioc;
|
||||
co_spawn(ioc, listener(), net::detached);
|
||||
net::io_context ioc{1};
|
||||
auto db = std::make_shared<connection>(ioc);
|
||||
co_spawn(ioc, reconnect(db), net::detached);
|
||||
|
||||
net::signal_set signals(ioc, SIGINT, SIGTERM);
|
||||
signals.async_wait([&](auto, auto) {
|
||||
ioc.stop();
|
||||
});
|
||||
|
||||
co_spawn(ioc, listener(db), net::detached);
|
||||
ioc.run();
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << 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)
|
||||
|
||||
@@ -7,31 +7,37 @@
|
||||
#include <tuple>
|
||||
#include <string>
|
||||
#include <boost/asio.hpp>
|
||||
#include <aedis/aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
#include <aedis.hpp>
|
||||
|
||||
namespace net = boost::asio;
|
||||
// Include this in no more than one .cpp file.
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
using aedis::adapt;
|
||||
using aedis::resp3::request;
|
||||
using connection = aedis::connection<>;
|
||||
|
||||
int main()
|
||||
auto const logger = [](auto ec, auto...)
|
||||
{ std::cout << ec.message() << std::endl; };
|
||||
|
||||
auto main() -> int
|
||||
{
|
||||
request req;
|
||||
req.push("HELLO", 3);
|
||||
req.push("PING");
|
||||
req.push("QUIT");
|
||||
try {
|
||||
boost::asio::io_context ioc;
|
||||
connection conn{ioc};
|
||||
|
||||
std::tuple<aedis::ignore, std::string, aedis::ignore> resp;
|
||||
request req;
|
||||
req.get_config().cancel_on_connection_lost = true;
|
||||
req.push("PING");
|
||||
req.push("QUIT");
|
||||
|
||||
net::io_context ioc;
|
||||
std::tuple<std::string, aedis::ignore> resp;
|
||||
conn.async_exec(req, adapt(resp), logger);
|
||||
conn.async_run({"127.0.0.1", "6379"}, {}, logger);
|
||||
|
||||
connection db{ioc};
|
||||
db.async_exec("127.0.0.1", "6379", req, adapt(resp),
|
||||
[](auto ec, auto) { std::cout << ec.message() << std::endl; });
|
||||
ioc.run();
|
||||
|
||||
ioc.run();
|
||||
|
||||
std::cout << std::get<1>(resp) << std::endl;
|
||||
std::cout << std::get<0>(resp) << std::endl;
|
||||
} catch (...) {
|
||||
std::cerr << "Error" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
62
examples/intro_sync.cpp
Normal file
62
examples/intro_sync.cpp
Normal file
@@ -0,0 +1,62 @@
|
||||
/* 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>
|
||||
|
||||
// Include this in no more than one .cpp file.
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
namespace net = boost::asio;
|
||||
using aedis::adapt;
|
||||
using aedis::resp3::request;
|
||||
using aedis::endpoint;
|
||||
using connection = aedis::connection<>;
|
||||
|
||||
template <class Adapter>
|
||||
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({"127.0.0.1", "6379"}, {}, logger);
|
||||
ioc.run();
|
||||
}};
|
||||
|
||||
request req;
|
||||
req.get_config().cancel_on_connection_lost = true;
|
||||
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;
|
||||
}
|
||||
}
|
||||
57
examples/intro_tls.cpp
Normal file
57
examples/intro_tls.cpp
Normal file
@@ -0,0 +1,57 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <tuple>
|
||||
#include <string>
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/asio/ssl.hpp>
|
||||
#include <aedis.hpp>
|
||||
#include <aedis/ssl/connection.hpp>
|
||||
|
||||
// Include this in no more than one .cpp file.
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
namespace net = boost::asio;
|
||||
using aedis::adapt;
|
||||
using aedis::resp3::request;
|
||||
using connection = aedis::ssl::connection<net::ssl::stream<net::ip::tcp::socket>>;
|
||||
|
||||
auto const logger = [](auto ec, auto...)
|
||||
{ std::cout << ec.message() << std::endl; };
|
||||
|
||||
auto verify_certificate(bool, net::ssl::verify_context&) -> bool
|
||||
{
|
||||
std::cout << "set_verify_callback" << std::endl;
|
||||
return true;
|
||||
}
|
||||
|
||||
auto main() -> int
|
||||
{
|
||||
try {
|
||||
net::io_context ioc;
|
||||
|
||||
net::ssl::context ctx{net::ssl::context::sslv23};
|
||||
|
||||
connection conn{ioc, ctx};
|
||||
conn.next_layer().set_verify_mode(net::ssl::verify_peer);
|
||||
conn.next_layer().set_verify_callback(verify_certificate);
|
||||
|
||||
request req;
|
||||
req.get_config().cancel_on_connection_lost = true;
|
||||
req.push("PING");
|
||||
req.push("QUIT");
|
||||
|
||||
std::tuple<std::string, aedis::ignore> resp;
|
||||
conn.async_exec(req, adapt(resp), logger);
|
||||
conn.async_run({"127.0.0.1", "6379"}, {}, logger);
|
||||
|
||||
ioc.run();
|
||||
|
||||
std::cout << "Response: " << std::get<0>(resp) << std::endl;
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << "Error: " << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
@@ -9,14 +9,14 @@
|
||||
|
||||
#include <boost/asio/connect.hpp>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
#include <aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
namespace net = boost::asio;
|
||||
namespace resp3 = aedis::resp3;
|
||||
|
||||
using aedis::resp3::request;
|
||||
using aedis::adapter::adapt;
|
||||
using aedis::adapter::adapt2;
|
||||
using net::ip::tcp;
|
||||
|
||||
int main()
|
||||
@@ -41,7 +41,7 @@ int main()
|
||||
// Reads the responses to all commands in the request.
|
||||
auto dbuffer = net::dynamic_buffer(buffer);
|
||||
resp3::read(socket, dbuffer);
|
||||
resp3::read(socket, dbuffer, adapt(resp));
|
||||
resp3::read(socket, dbuffer, adapt2(resp));
|
||||
resp3::read(socket, dbuffer);
|
||||
|
||||
std::cout << "Ping: " << resp << std::endl;
|
||||
@@ -17,11 +17,12 @@
|
||||
|
||||
namespace net = boost::asio;
|
||||
using aedis::resp3::node;
|
||||
using aedis::adapter::adapt;
|
||||
using aedis::adapter::adapter_t;
|
||||
|
||||
void print_aggr(std::vector<aedis::resp3::node<std::string>>& v)
|
||||
{
|
||||
if (std::empty(v))
|
||||
return;
|
||||
|
||||
auto const m = aedis::resp3::element_multiplicity(v.front().data_type);
|
||||
for (auto i = 0lu; i < m * v.front().aggregate_size; ++i)
|
||||
std::cout << v[i + 1].value << " ";
|
||||
@@ -54,3 +55,11 @@ void print(std::string const& e)
|
||||
std::cout << e << std::endl;
|
||||
}
|
||||
|
||||
void print_push(std::vector<aedis::resp3::node<std::string>>& resp)
|
||||
{
|
||||
std::cout
|
||||
<< "Push type: " << resp.at(1).value << "\n"
|
||||
<< "Channel: " << resp.at(2).value << "\n"
|
||||
<< "Message: " << resp.at(3).value << "\n"
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
@@ -12,12 +12,16 @@
|
||||
#include <string>
|
||||
#include <boost/json.hpp>
|
||||
#include <boost/json/src.hpp>
|
||||
#include <aedis/aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
#include <aedis.hpp>
|
||||
#include "print.hpp"
|
||||
|
||||
// Include this in no more than one .cpp file.
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
namespace net = boost::asio;
|
||||
using aedis::resp3::request;
|
||||
using aedis::adapt;
|
||||
using aedis::endpoint;
|
||||
using connection = aedis::connection<>;
|
||||
using namespace boost::json;
|
||||
|
||||
@@ -42,7 +46,7 @@ void extract(object const& obj, T& t, boost::string_view key)
|
||||
t = value_to<T>(obj.at(key));
|
||||
}
|
||||
|
||||
user tag_invoke(value_to_tag<user>, value const& jv)
|
||||
auto tag_invoke(value_to_tag<user>, value const& jv)
|
||||
{
|
||||
user u;
|
||||
object const& obj = jv.as_object();
|
||||
@@ -53,19 +57,19 @@ user tag_invoke(value_to_tag<user>, value const& jv)
|
||||
}
|
||||
|
||||
// Serializes
|
||||
void to_bulk(std::string& to, user const& u)
|
||||
void to_bulk(std::pmr::string& to, user const& u)
|
||||
{
|
||||
aedis::resp3::to_bulk(to, serialize(value_from(u)));
|
||||
}
|
||||
|
||||
// Deserializes
|
||||
void from_bulk(user& u, boost::string_view sv, boost::system::error_code& ec)
|
||||
void from_bulk(user& u, boost::string_view sv, boost::system::error_code&)
|
||||
{
|
||||
value jv = parse(sv);
|
||||
u = value_to<user>(jv);
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, user const& u)
|
||||
auto operator<<(std::ostream& os, user const& u) -> std::ostream&
|
||||
{
|
||||
os << "Name: " << u.name << "\n"
|
||||
<< "Age: " << u.age << "\n"
|
||||
@@ -74,35 +78,40 @@ std::ostream& operator<<(std::ostream& os, user const& u)
|
||||
return os;
|
||||
}
|
||||
|
||||
bool operator<(user const& a, user const& b)
|
||||
auto operator<(user const& a, user const& b)
|
||||
{
|
||||
return std::tie(a.name, a.age, a.country) < std::tie(b.name, b.age, b.country);
|
||||
}
|
||||
|
||||
int main()
|
||||
auto const logger = [](auto ec, auto...)
|
||||
{ std::cout << ec.message() << std::endl; };
|
||||
|
||||
auto main() -> int
|
||||
{
|
||||
net::io_context ioc;
|
||||
connection db{ioc};
|
||||
try {
|
||||
net::io_context ioc;
|
||||
connection conn{ioc};
|
||||
|
||||
// Request that sends the containers.
|
||||
std::set<user> users
|
||||
{ {"Joao", "56", "Brazil"}
|
||||
, {"Serge", "60", "France"}
|
||||
};
|
||||
std::set<user> users
|
||||
{{"Joao", "58", "Brazil"} , {"Serge", "60", "France"}};
|
||||
|
||||
request req;
|
||||
req.push("HELLO", 3);
|
||||
req.push_range("SADD", "sadd-key", users);
|
||||
req.push("SMEMBERS", "sadd-key");
|
||||
req.push("QUIT");
|
||||
request req;
|
||||
req.get_config().cancel_on_connection_lost = true;
|
||||
req.push("HELLO", 3);
|
||||
req.push_range("SADD", "sadd-key", users); // Sends
|
||||
req.push("SMEMBERS", "sadd-key"); // Retrieves
|
||||
req.push("QUIT");
|
||||
|
||||
std::tuple<aedis::ignore, int, std::set<user>, std::string> resp;
|
||||
std::tuple<aedis::ignore, int, std::set<user>, std::string> resp;
|
||||
|
||||
db.async_exec("127.0.0.1", "6379", req, aedis::adapt(resp),
|
||||
[](auto ec, auto) { std::cout << ec.message() << std::endl; });
|
||||
endpoint ep{"127.0.0.1", "6379"};
|
||||
conn.async_exec(req, adapt(resp),logger);
|
||||
conn.async_run(ep, {}, logger);
|
||||
ioc.run();
|
||||
|
||||
ioc.run();
|
||||
|
||||
// Print
|
||||
print(std::get<2>(resp));
|
||||
// Print
|
||||
print(std::get<2>(resp));
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << "Error: " << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,57 +7,97 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
#include <tuple>
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#include <aedis/aedis.hpp>
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
#include <boost/asio/experimental/awaitable_operators.hpp>
|
||||
#include <aedis.hpp>
|
||||
#include "print.hpp"
|
||||
|
||||
// Include this in no more than one .cpp file.
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
namespace net = boost::asio;
|
||||
using namespace net::experimental::awaitable_operators;
|
||||
using aedis::adapt;
|
||||
using aedis::resp3::request;
|
||||
using aedis::resp3::node;
|
||||
using aedis::endpoint;
|
||||
using tcp_socket = net::use_awaitable_t<>::as_default_on_t<net::ip::tcp::socket>;
|
||||
using stimer = net::use_awaitable_t<>::as_default_on_t<net::steady_timer>;
|
||||
using connection = aedis::connection<tcp_socket>;
|
||||
|
||||
/* In this example we send a subscription to a channel and start
|
||||
* reading server side messages indefinitely.
|
||||
/* This example will subscribe and read pushes indefinitely.
|
||||
*
|
||||
* After starting the example you can test it by sending messages with
|
||||
* redis-cli like this
|
||||
* To test send messages with redis-cli
|
||||
*
|
||||
* $ redis-cli -3
|
||||
* 127.0.0.1:6379> PUBLISH channel1 some-message
|
||||
* 127.0.0.1:6379> PUBLISH channel some-message
|
||||
* (integer) 3
|
||||
* 127.0.0.1:6379>
|
||||
*
|
||||
* The messages will then appear on the terminal you are running the
|
||||
* example.
|
||||
* To test reconnection try, for example, to close all clients currently
|
||||
* connected to the Redis instance
|
||||
*
|
||||
* $ redis-cli
|
||||
* > CLIENT kill TYPE pubsub
|
||||
*/
|
||||
net::awaitable<void> reader(std::shared_ptr<connection> db)
|
||||
|
||||
// Receives pushes.
|
||||
net::awaitable<void> push_receiver(std::shared_ptr<connection> conn)
|
||||
{
|
||||
for (std::vector<node<std::string>> resp;;) {
|
||||
auto n = co_await db->async_read_push(adapt(resp));
|
||||
std::cout
|
||||
<< "Size: " << n << "\n"
|
||||
<< "Event: " << resp.at(1).value << "\n"
|
||||
<< "Channel: " << resp.at(2).value << "\n"
|
||||
<< "Message: " << resp.at(3).value << "\n"
|
||||
<< std::endl;
|
||||
|
||||
co_await conn->async_receive(adapt(resp));
|
||||
print_push(resp);
|
||||
resp.clear();
|
||||
}
|
||||
}
|
||||
|
||||
auto handler = [](auto ec, auto...)
|
||||
{ std::cout << ec.message() << std::endl; };
|
||||
|
||||
int main()
|
||||
// See
|
||||
// - https://redis.io/docs/manual/sentinel.
|
||||
// - https://redis.io/docs/reference/sentinel-clients.
|
||||
net::awaitable<void> reconnect(std::shared_ptr<connection> conn)
|
||||
{
|
||||
net::io_context ioc;
|
||||
auto db = std::make_shared<connection>(ioc);
|
||||
request req;
|
||||
req.push("HELLO", 3);
|
||||
req.get_config().cancel_if_not_connected = false;
|
||||
req.get_config().cancel_on_connection_lost = true;
|
||||
req.push("SUBSCRIBE", "channel");
|
||||
db->async_exec("127.0.0.1", "6379", req, adapt(), handler);
|
||||
net::co_spawn(ioc, reader(db), net::detached);
|
||||
ioc.run();
|
||||
|
||||
stimer timer{co_await net::this_coro::executor};
|
||||
endpoint ep{"127.0.0.1", "6379"};
|
||||
for (;;) {
|
||||
boost::system::error_code ec1, ec2;
|
||||
co_await (
|
||||
conn->async_run(ep, {}, net::redirect_error(net::use_awaitable, ec1)) &&
|
||||
conn->async_exec(req, adapt(), net::redirect_error(net::use_awaitable, ec2))
|
||||
);
|
||||
std::clog << "async_run: " << ec1.message() << "\n"
|
||||
<< "async_exec: " << ec2.message() << std::endl;
|
||||
conn->reset_stream();
|
||||
timer.expires_after(std::chrono::seconds{1});
|
||||
co_await timer.async_wait();
|
||||
}
|
||||
}
|
||||
|
||||
auto main() -> int
|
||||
{
|
||||
try {
|
||||
net::io_context ioc;
|
||||
auto conn = std::make_shared<connection>(ioc);
|
||||
|
||||
net::co_spawn(ioc, push_receiver(conn), net::detached);
|
||||
net::co_spawn(ioc, reconnect(conn), net::detached);
|
||||
|
||||
net::signal_set signals(ioc, SIGINT, SIGTERM);
|
||||
signals.async_wait([&](auto, auto){ ioc.stop(); });
|
||||
|
||||
ioc.run();
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << "Error: " << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
#else // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
auto main() -> int {std::cout << "Requires coroutine support." << std::endl; return 0;}
|
||||
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
|
||||
139
examples/subscriber_sentinel.cpp
Normal file
139
examples/subscriber_sentinel.cpp
Normal file
@@ -0,0 +1,139 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
#include <tuple>
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
#include <boost/asio/experimental/awaitable_operators.hpp>
|
||||
#include <aedis.hpp>
|
||||
#include "print.hpp"
|
||||
|
||||
// Include this in no more than one .cpp file.
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
namespace net = boost::asio;
|
||||
using namespace net::experimental::awaitable_operators;
|
||||
using aedis::adapt;
|
||||
using aedis::resp3::request;
|
||||
using aedis::resp3::node;
|
||||
using aedis::endpoint;
|
||||
using tcp_socket = net::use_awaitable_t<>::as_default_on_t<net::ip::tcp::socket>;
|
||||
using stimer = net::use_awaitable_t<>::as_default_on_t<net::steady_timer>;
|
||||
using connection = aedis::connection<tcp_socket>;
|
||||
|
||||
auto is_valid(endpoint const& ep) noexcept -> bool
|
||||
{
|
||||
return !std::empty(ep.host) && !std::empty(ep.port);
|
||||
}
|
||||
|
||||
// Connects to a Redis instance over sentinel and performs failover in
|
||||
// case of disconnection, see
|
||||
// https://redis.io/docs/reference/sentinel-clients. This example
|
||||
// assumes a sentinel and a redis server running on localhost.
|
||||
|
||||
net::awaitable<void> receive_pushes(std::shared_ptr<connection> conn)
|
||||
{
|
||||
for (std::vector<node<std::string>> resp;;) {
|
||||
co_await conn->async_receive(adapt(resp));
|
||||
print_push(resp);
|
||||
resp.clear();
|
||||
}
|
||||
}
|
||||
|
||||
net::awaitable<endpoint> resolve()
|
||||
{
|
||||
// A list of sentinel addresses from which only one is responsive
|
||||
// to simulate sentinels that are down.
|
||||
std::vector<endpoint> const endpoints
|
||||
{ {"foo", "26379"}
|
||||
, {"bar", "26379"}
|
||||
, {"127.0.0.1", "26379"}
|
||||
};
|
||||
|
||||
request req;
|
||||
req.get_config().cancel_on_connection_lost = true;
|
||||
req.push("SENTINEL", "get-master-addr-by-name", "mymaster");
|
||||
req.push("QUIT");
|
||||
|
||||
connection conn{co_await net::this_coro::executor};
|
||||
|
||||
std::tuple<std::optional<std::array<std::string, 2>>, aedis::ignore> addr;
|
||||
for (auto ep : endpoints) {
|
||||
boost::system::error_code ec1, ec2;
|
||||
co_await (
|
||||
conn.async_run(ep, {}, net::redirect_error(net::use_awaitable, ec1)) &&
|
||||
conn.async_exec(req, adapt(addr), net::redirect_error(net::use_awaitable, ec2))
|
||||
);
|
||||
|
||||
std::clog << "async_run: " << ec1.message() << "\n"
|
||||
<< "async_exec: " << ec2.message() << std::endl;
|
||||
|
||||
conn.reset_stream();
|
||||
if (std::get<0>(addr))
|
||||
break;
|
||||
}
|
||||
|
||||
endpoint ep;
|
||||
if (std::get<0>(addr)) {
|
||||
ep.host = std::get<0>(addr).value().at(0);
|
||||
ep.port = std::get<0>(addr).value().at(1);
|
||||
}
|
||||
|
||||
co_return ep;
|
||||
}
|
||||
|
||||
net::awaitable<void> reconnect(std::shared_ptr<connection> conn)
|
||||
{
|
||||
request req;
|
||||
req.get_config().cancel_on_connection_lost = true;
|
||||
req.push("SUBSCRIBE", "channel");
|
||||
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
stimer timer{ex};
|
||||
for (;;) {
|
||||
auto ep = co_await net::co_spawn(ex, resolve(), net::use_awaitable);
|
||||
if (!is_valid(ep)) {
|
||||
std::clog << "Can't resolve master name" << std::endl;
|
||||
co_return;
|
||||
}
|
||||
|
||||
boost::system::error_code ec1, ec2;
|
||||
co_await (
|
||||
conn->async_run(ep, {}, net::redirect_error(net::use_awaitable, ec1)) &&
|
||||
conn->async_exec(req, adapt(), net::redirect_error(net::use_awaitable, ec2))
|
||||
);
|
||||
|
||||
std::clog << "async_run: " << ec1.message() << "\n"
|
||||
<< "async_exec: " << ec2.message() << "\n"
|
||||
<< "Starting the failover." << std::endl;
|
||||
|
||||
timer.expires_after(std::chrono::seconds{1});
|
||||
co_await timer.async_wait();
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
try {
|
||||
net::io_context ioc;
|
||||
auto conn = std::make_shared<connection>(ioc);
|
||||
net::co_spawn(ioc, receive_pushes(conn), net::detached);
|
||||
net::co_spawn(ioc, reconnect(conn), net::detached);
|
||||
net::signal_set signals(ioc, SIGINT, SIGTERM);
|
||||
signals.async_wait([&](auto, auto){ ioc.stop(); });
|
||||
ioc.run();
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << "Error: " << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
#else // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
int main() {std::cout << "Requires coroutine support." << std::endl; return 0;}
|
||||
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
25
include/aedis.hpp
Normal file
25
include/aedis.hpp
Normal file
@@ -0,0 +1,25 @@
|
||||
/* 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_HPP
|
||||
#define AEDIS_HPP
|
||||
|
||||
#include <aedis/error.hpp>
|
||||
#include <aedis/adapt.hpp>
|
||||
#include <aedis/connection.hpp>
|
||||
#include <aedis/resp3/request.hpp>
|
||||
|
||||
/** @defgroup high-level-api Reference
|
||||
*
|
||||
* This page contains the documentation of the Aedis high-level API.
|
||||
*/
|
||||
|
||||
/** @defgroup low-level-api Reference
|
||||
*
|
||||
* This page contains the documentation of the Aedis low-level API.
|
||||
*/
|
||||
|
||||
#endif // AEDIS_HPP
|
||||
227
include/aedis/adapt.hpp
Normal file
227
include/aedis/adapt.hpp
Normal file
@@ -0,0 +1,227 @@
|
||||
/* 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_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>
|
||||
|
||||
namespace aedis {
|
||||
|
||||
/** @brief Tag used to ignore responses.
|
||||
* @ingroup high-level-api
|
||||
*
|
||||
* For example
|
||||
*
|
||||
* @code
|
||||
* std::tuple<aedis::ignore, std::string, aedis::ignore> resp;
|
||||
* @endcode
|
||||
*
|
||||
* will cause only the second tuple type to be parsed, the others
|
||||
* will be ignored.
|
||||
*/
|
||||
using ignore = adapter::detail::ignore;
|
||||
|
||||
namespace detail
|
||||
{
|
||||
|
||||
class ignore_adapter {
|
||||
public:
|
||||
explicit ignore_adapter(std::size_t max_read_size) : max_read_size_{max_read_size} {}
|
||||
|
||||
void
|
||||
operator()(
|
||||
std::size_t, resp3::node<boost::string_view> const&, boost::system::error_code&) { }
|
||||
|
||||
[[nodiscard]]
|
||||
auto get_supported_response_size() const noexcept
|
||||
{ return static_cast<std::size_t>(-1);}
|
||||
|
||||
[[nodiscard]]
|
||||
auto get_max_read_size(std::size_t) const noexcept
|
||||
{ return max_read_size_;}
|
||||
|
||||
private:
|
||||
std::size_t max_read_size_;
|
||||
};
|
||||
|
||||
template <class Tuple>
|
||||
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 adapters_array_type = std::array<variant_type, size>;
|
||||
|
||||
adapters_array_type adapters_;
|
||||
std::size_t max_read_size_;
|
||||
|
||||
public:
|
||||
explicit static_adapter(Tuple& r, std::size_t max_read_size)
|
||||
: max_read_size_{max_read_size}
|
||||
{
|
||||
adapter::detail::assigner<size - 1>::assign(adapters_, r);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
auto get_supported_response_size() const noexcept
|
||||
{ return size;}
|
||||
|
||||
[[nodiscard]]
|
||||
auto get_max_read_size(std::size_t) const noexcept
|
||||
{ return max_read_size_;}
|
||||
|
||||
void
|
||||
operator()(
|
||||
std::size_t i,
|
||||
resp3::node<boost::string_view> const& nd,
|
||||
boost::system::error_code& ec)
|
||||
{
|
||||
using boost::variant2::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));
|
||||
}
|
||||
};
|
||||
|
||||
template <class Vector>
|
||||
class vector_adapter {
|
||||
private:
|
||||
using adapter_type = typename adapter::detail::response_traits<Vector>::adapter_type;
|
||||
adapter_type adapter_;
|
||||
std::size_t max_read_size_;
|
||||
|
||||
public:
|
||||
explicit vector_adapter(Vector& v, std::size_t max_read_size)
|
||||
: adapter_{adapter::adapt2(v)}
|
||||
, max_read_size_{max_read_size}
|
||||
{ }
|
||||
|
||||
[[nodiscard]]
|
||||
auto
|
||||
get_supported_response_size() const noexcept
|
||||
{ return static_cast<std::size_t>(-1);}
|
||||
|
||||
[[nodiscard]]
|
||||
auto get_max_read_size(std::size_t) const noexcept
|
||||
{ return max_read_size_;}
|
||||
|
||||
void
|
||||
operator()(
|
||||
std::size_t,
|
||||
resp3::node<boost::string_view> const& nd,
|
||||
boost::system::error_code& ec)
|
||||
{
|
||||
adapter_(nd, ec);
|
||||
}
|
||||
};
|
||||
|
||||
template <class>
|
||||
struct response_traits;
|
||||
|
||||
template <>
|
||||
struct response_traits<void> {
|
||||
using response_type = void;
|
||||
using adapter_type = detail::ignore_adapter;
|
||||
|
||||
static auto adapt(std::size_t max_read_size) noexcept
|
||||
{ return detail::ignore_adapter{max_read_size}; }
|
||||
};
|
||||
|
||||
template <class String, class Allocator>
|
||||
struct response_traits<std::vector<resp3::node<String>, Allocator>> {
|
||||
using response_type = std::vector<resp3::node<String>, Allocator>;
|
||||
using adapter_type = vector_adapter<response_type>;
|
||||
|
||||
static auto adapt(response_type& v, std::size_t max_read_size) noexcept
|
||||
{ return adapter_type{v, max_read_size}; }
|
||||
};
|
||||
|
||||
template <class ...Ts>
|
||||
struct response_traits<std::tuple<Ts...>> {
|
||||
using response_type = std::tuple<Ts...>;
|
||||
using adapter_type = static_adapter<response_type>;
|
||||
|
||||
static auto adapt(response_type& r, std::size_t max_read_size) noexcept
|
||||
{ return adapter_type{r, max_read_size}; }
|
||||
};
|
||||
|
||||
template <class Adapter>
|
||||
class wrapper {
|
||||
public:
|
||||
explicit wrapper(Adapter adapter) : adapter_{adapter} {}
|
||||
|
||||
void operator()(resp3::node<boost::string_view> const& node, boost::system::error_code& ec)
|
||||
{ return adapter_(0, node, ec); }
|
||||
|
||||
[[nodiscard]]
|
||||
auto get_supported_response_size() const noexcept
|
||||
{ return adapter_.get_supported_response_size();}
|
||||
|
||||
[[nodiscard]]
|
||||
auto get_max_read_size(std::size_t) const noexcept
|
||||
{ return adapter_.get_max_read_size(0); }
|
||||
|
||||
private:
|
||||
Adapter adapter_;
|
||||
};
|
||||
|
||||
template <class Adapter>
|
||||
auto make_adapter_wrapper(Adapter adapter)
|
||||
{
|
||||
return wrapper{adapter};
|
||||
}
|
||||
|
||||
} // detail
|
||||
|
||||
/** @brief Creates an adapter that ignores responses.
|
||||
* @ingroup high-level-api
|
||||
*
|
||||
* This function can be used to create adapters that ignores
|
||||
* responses.
|
||||
*
|
||||
* @param max_read_size Specifies the maximum size of the read
|
||||
* buffer.
|
||||
*/
|
||||
inline auto adapt(std::size_t max_read_size = (std::numeric_limits<std::size_t>::max)()) noexcept
|
||||
{
|
||||
return detail::response_traits<void>::adapt(max_read_size);
|
||||
}
|
||||
|
||||
/** @brief Adapts a type to be used as a response.
|
||||
* @ingroup high-level-api
|
||||
*
|
||||
* The type T must be either
|
||||
*
|
||||
* 1. a std::tuple<T1, T2, T3, ...> or
|
||||
* 2. std::vector<node<String>>
|
||||
*
|
||||
* The types T1, T2, etc can be any STL container, any integer type
|
||||
* and \c std::string
|
||||
*
|
||||
* @param t Tuple containing the responses.
|
||||
* @param max_read_size Specifies the maximum size of the read
|
||||
* buffer.
|
||||
*/
|
||||
template<class T>
|
||||
auto adapt(T& t, std::size_t max_read_size = (std::numeric_limits<std::size_t>::max)()) noexcept
|
||||
{
|
||||
return detail::response_traits<T>::adapt(t, max_read_size);
|
||||
}
|
||||
|
||||
} // aedis
|
||||
|
||||
#endif // AEDIS_ADAPT_HPP
|
||||
@@ -9,15 +9,13 @@
|
||||
|
||||
#include <aedis/adapter/detail/response_traits.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace adapter {
|
||||
namespace aedis::adapter {
|
||||
|
||||
template <class T>
|
||||
using adapter_t = typename detail::adapter_t<T>;
|
||||
|
||||
/** \internal
|
||||
\brief Creates a dummy response adapter.
|
||||
\ingroup any
|
||||
/** \brief Creates a dummy response adapter.
|
||||
\ingroup low-level-api
|
||||
|
||||
The adapter returned by this function ignores responses. It is
|
||||
useful to avoid wasting time with responses which are not needed.
|
||||
@@ -39,15 +37,14 @@ using adapter_t = typename detail::adapter_t<T>;
|
||||
@endcode
|
||||
*/
|
||||
inline
|
||||
auto adapt() noexcept
|
||||
auto adapt2() noexcept
|
||||
{ return detail::response_traits<void>::adapt(); }
|
||||
|
||||
/** \internal
|
||||
* \brief Adapts user data to read operations.
|
||||
* \ingroup any
|
||||
/** \brief Adapts user data to read operations.
|
||||
* \ingroup low-level-api
|
||||
*
|
||||
* STL containers, \c std::tuple and built-in types are supported and
|
||||
* can be used in conjunction with \c boost::optional<T>.
|
||||
* can be used in conjunction with \c std::optional<T>.
|
||||
*
|
||||
* Example usage:
|
||||
*
|
||||
@@ -75,10 +72,9 @@ auto adapt() noexcept
|
||||
* @endcode
|
||||
*/
|
||||
template<class T>
|
||||
auto adapt(T& t) noexcept
|
||||
auto adapt2(T& t) noexcept
|
||||
{ return detail::response_traits<T>::adapt(t); }
|
||||
|
||||
} // adapter
|
||||
} // aedis
|
||||
} // aedis::adapter
|
||||
|
||||
#endif // AEDIS_ADAPTER_ADAPT_HPP
|
||||
@@ -8,6 +8,7 @@
|
||||
#define AEDIS_ADAPTER_ADAPTERS_HPP
|
||||
|
||||
#include <set>
|
||||
#include <optional>
|
||||
#include <unordered_set>
|
||||
#include <forward_list>
|
||||
#include <system_error>
|
||||
@@ -19,7 +20,6 @@
|
||||
#include <array>
|
||||
|
||||
#include <boost/assert.hpp>
|
||||
#include <boost/optional.hpp>
|
||||
#include <boost/spirit/include/qi.hpp>
|
||||
#include <boost/spirit/home/x3.hpp>
|
||||
#include <boost/utility/string_view.hpp>
|
||||
@@ -30,18 +30,16 @@
|
||||
#include <aedis/resp3/detail/parser.hpp>
|
||||
#include <aedis/resp3/node.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace adapter {
|
||||
namespace detail {
|
||||
namespace aedis::adapter::detail {
|
||||
|
||||
double
|
||||
parse_double(
|
||||
inline
|
||||
auto parse_double(
|
||||
char const* data,
|
||||
std::size_t size,
|
||||
boost::system::error_code& ec)
|
||||
boost::system::error_code& ec) -> double
|
||||
{
|
||||
static constexpr boost::spirit::x3::real_parser<double> p{};
|
||||
double ret;
|
||||
double ret = 0;
|
||||
if (!parse(data, data + size, p, ret))
|
||||
ec = error::not_a_double;
|
||||
|
||||
@@ -51,23 +49,24 @@ parse_double(
|
||||
// Serialization.
|
||||
|
||||
template <class T>
|
||||
typename std::enable_if<std::is_integral<T>::value, void>::type
|
||||
from_bulk(
|
||||
auto from_bulk(
|
||||
T& i,
|
||||
boost::string_view sv,
|
||||
boost::system::error_code& ec)
|
||||
boost::system::error_code& ec) -> typename std::enable_if<std::is_integral<T>::value, void>::type
|
||||
{
|
||||
i = resp3::detail::parse_uint(sv.data(), sv.size(), ec);
|
||||
}
|
||||
|
||||
inline
|
||||
void from_bulk(
|
||||
bool& t,
|
||||
boost::string_view sv,
|
||||
boost::system::error_code& ec)
|
||||
boost::system::error_code&)
|
||||
{
|
||||
t = *sv.data() == 't';
|
||||
}
|
||||
|
||||
inline
|
||||
void from_bulk(
|
||||
double& d,
|
||||
boost::string_view sv,
|
||||
@@ -88,12 +87,13 @@ from_bulk(
|
||||
|
||||
//================================================
|
||||
|
||||
inline
|
||||
void set_on_resp3_error(resp3::type t, boost::system::error_code& ec)
|
||||
{
|
||||
switch (t) {
|
||||
case resp3::type::simple_error: ec = error::simple_error; return;
|
||||
case resp3::type::blob_error: ec = error::blob_error; return;
|
||||
case resp3::type::null: ec = error::null; return;
|
||||
case resp3::type::simple_error: ec = error::resp3_simple_error; return;
|
||||
case resp3::type::blob_error: ec = error::resp3_blob_error; return;
|
||||
case resp3::type::null: ec = error::resp3_null; return;
|
||||
default: return;
|
||||
}
|
||||
}
|
||||
@@ -104,11 +104,10 @@ private:
|
||||
Result* result_;
|
||||
|
||||
public:
|
||||
general_aggregate(Result* c = nullptr): result_(c) {}
|
||||
void operator()(resp3::node<boost::string_view> const& n, boost::system::error_code& ec)
|
||||
explicit general_aggregate(Result* c = nullptr): result_(c) {}
|
||||
void operator()(resp3::node<boost::string_view> const& n, boost::system::error_code&)
|
||||
{
|
||||
result_->push_back({n.data_type, n.aggregate_size, n.depth, std::string{std::cbegin(n.value), std::cend(n.value)}});
|
||||
set_on_resp3_error(n.data_type, ec);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -118,7 +117,7 @@ private:
|
||||
Node* result_;
|
||||
|
||||
public:
|
||||
general_simple(Node* t = nullptr) : result_(t) {}
|
||||
explicit general_simple(Node* t = nullptr) : result_(t) {}
|
||||
|
||||
void operator()(resp3::node<boost::string_view> const& n, boost::system::error_code& ec)
|
||||
{
|
||||
@@ -146,7 +145,7 @@ public:
|
||||
return;
|
||||
|
||||
if (is_aggregate(n.data_type)) {
|
||||
ec = error::expects_simple_type;
|
||||
ec = error::expects_resp3_simple_type;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -175,14 +174,14 @@ public:
|
||||
|
||||
if (is_aggregate(nd.data_type)) {
|
||||
if (nd.data_type != resp3::type::set)
|
||||
ec = error::expects_set_type;
|
||||
ec = error::expects_resp3_set;
|
||||
return;
|
||||
}
|
||||
|
||||
BOOST_ASSERT(nd.aggregate_size == 1);
|
||||
|
||||
if (nd.depth < 1) {
|
||||
ec = error::expects_set_type;
|
||||
ec = error::expects_resp3_set;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -214,14 +213,14 @@ public:
|
||||
|
||||
if (is_aggregate(nd.data_type)) {
|
||||
if (element_multiplicity(nd.data_type) != 2)
|
||||
ec = error::expects_map_type;
|
||||
ec = error::expects_resp3_map;
|
||||
return;
|
||||
}
|
||||
|
||||
BOOST_ASSERT(nd.aggregate_size == 1);
|
||||
|
||||
if (nd.depth < 1) {
|
||||
ec = error::expects_map_type;
|
||||
ec = error::expects_resp3_map;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -284,7 +283,7 @@ public:
|
||||
|
||||
if (is_aggregate(nd.data_type)) {
|
||||
if (i_ != -1) {
|
||||
ec = error::nested_aggregate_unsupported;
|
||||
ec = error::nested_aggregate_not_supported;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -294,7 +293,7 @@ public:
|
||||
}
|
||||
} else {
|
||||
if (i_ == -1) {
|
||||
ec = error::expects_aggregate_type;
|
||||
ec = error::expects_resp3_aggregate;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -324,7 +323,7 @@ struct list_impl {
|
||||
if (!is_aggregate(nd.data_type)) {
|
||||
BOOST_ASSERT(nd.aggregate_size == 1);
|
||||
if (nd.depth < 1) {
|
||||
ec = error::expects_aggregate_type;
|
||||
ec = error::expects_resp3_aggregate;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -384,7 +383,7 @@ private:
|
||||
typename impl_map<Result>::type impl_;
|
||||
|
||||
public:
|
||||
wrapper(Result* t = nullptr) : result_(t)
|
||||
explicit wrapper(Result* t = nullptr) : result_(t)
|
||||
{ impl_.on_value_available(*result_); }
|
||||
|
||||
void
|
||||
@@ -398,13 +397,13 @@ public:
|
||||
};
|
||||
|
||||
template <class T>
|
||||
class wrapper<boost::optional<T>> {
|
||||
class wrapper<std::optional<T>> {
|
||||
private:
|
||||
boost::optional<T>* result_;
|
||||
typename impl_map<T>::type impl_;
|
||||
std::optional<T>* result_;
|
||||
typename impl_map<T>::type impl_{};
|
||||
|
||||
public:
|
||||
wrapper(boost::optional<T>* o = nullptr) : result_(o), impl_{} {}
|
||||
explicit wrapper(std::optional<T>* o = nullptr) : result_(o) {}
|
||||
|
||||
void
|
||||
operator()(
|
||||
@@ -423,8 +422,6 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
} // detail
|
||||
} // adapter
|
||||
} // aedis
|
||||
} // aedis::adapter:.detail
|
||||
|
||||
#endif // AEDIS_ADAPTER_ADAPTERS_HPP
|
||||
@@ -18,11 +18,9 @@
|
||||
#include <aedis/resp3/read.hpp>
|
||||
#include <aedis/adapter/detail/adapters.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace adapter {
|
||||
namespace detail {
|
||||
namespace aedis::adapter::detail {
|
||||
|
||||
struct ignore {};
|
||||
using ignore = std::decay_t<decltype(std::ignore)>;
|
||||
|
||||
/* Traits class for response objects.
|
||||
*
|
||||
@@ -31,18 +29,15 @@ struct ignore {};
|
||||
*/
|
||||
template <class ResponseType>
|
||||
struct response_traits {
|
||||
using adapter_type = adapter::detail::wrapper<ResponseType>;
|
||||
using adapter_type = adapter::detail::wrapper<typename std::decay<ResponseType>::type>;
|
||||
static auto adapt(ResponseType& r) noexcept { return adapter_type{&r}; }
|
||||
};
|
||||
|
||||
template <class T>
|
||||
using adapter_t = typename response_traits<T>::adapter_type;
|
||||
|
||||
template <>
|
||||
struct response_traits<ignore> {
|
||||
using response_type = ignore;
|
||||
using adapter_type = resp3::detail::ignore_response;
|
||||
static auto adapt(response_type&) noexcept { return adapter_type{}; }
|
||||
static auto adapt(response_type) noexcept { return adapter_type{}; }
|
||||
};
|
||||
|
||||
template <class T>
|
||||
@@ -66,10 +61,13 @@ struct response_traits<void> {
|
||||
static auto adapt() noexcept { return adapter_type{}; }
|
||||
};
|
||||
|
||||
template <class T>
|
||||
using adapter_t = typename response_traits<std::decay_t<T>>::adapter_type;
|
||||
|
||||
// Duplicated here to avoid circular include dependency.
|
||||
template<class T>
|
||||
auto internal_adapt(T& t) noexcept
|
||||
{ return response_traits<T>::adapt(t); }
|
||||
{ return response_traits<std::decay_t<T>>::adapt(t); }
|
||||
|
||||
template <std::size_t N>
|
||||
struct assigner {
|
||||
@@ -90,17 +88,15 @@ struct assigner<0> {
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: I am not sure we need the mp_unique below.
|
||||
template <class Tuple>
|
||||
class static_aggregate_adapter {
|
||||
private:
|
||||
using adapters_array_type =
|
||||
std::array<
|
||||
boost::mp11::mp_unique<
|
||||
boost::mp11::mp_rename<
|
||||
boost::mp11::mp_transform<
|
||||
adapter_t, Tuple>,
|
||||
boost::variant2::variant>>,
|
||||
boost::mp11::mp_rename<
|
||||
boost::mp11::mp_transform<
|
||||
adapter_t, Tuple>,
|
||||
boost::variant2::variant>,
|
||||
std::tuple_size<Tuple>::value>;
|
||||
|
||||
std::size_t i_ = 0;
|
||||
@@ -108,7 +104,7 @@ private:
|
||||
adapters_array_type adapters_;
|
||||
|
||||
public:
|
||||
static_aggregate_adapter(Tuple* r = nullptr)
|
||||
explicit static_aggregate_adapter(Tuple* r = nullptr)
|
||||
{
|
||||
detail::assigner<std::tuple_size<Tuple>::value - 1>::assign(adapters_, *r);
|
||||
}
|
||||
@@ -156,8 +152,6 @@ struct response_traits<std::tuple<Ts...>>
|
||||
static auto adapt(response_type& r) noexcept { return adapter_type{&r}; }
|
||||
};
|
||||
|
||||
} // detail
|
||||
} // adapter
|
||||
} // aedis
|
||||
} // aedis::adapter::detail
|
||||
|
||||
#endif // AEDIS_ADAPTER_RESPONSE_TRAITS_HPP
|
||||
276
include/aedis/connection.hpp
Normal file
276
include/aedis/connection.hpp
Normal file
@@ -0,0 +1,276 @@
|
||||
/* 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_CONNECTION_HPP
|
||||
#define AEDIS_CONNECTION_HPP
|
||||
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <aedis/detail/connection_base.hpp>
|
||||
|
||||
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.
|
||||
*
|
||||
* @tparam AsyncReadWriteStream A stream that supports reading and
|
||||
* writing.
|
||||
*/
|
||||
template <class AsyncReadWriteStream = boost::asio::ip::tcp::socket>
|
||||
class connection :
|
||||
private detail::connection_base<
|
||||
typename AsyncReadWriteStream::executor_type,
|
||||
connection<AsyncReadWriteStream>> {
|
||||
public:
|
||||
/// Executor type.
|
||||
using executor_type = typename AsyncReadWriteStream::executor_type;
|
||||
|
||||
/// Type of the next layer
|
||||
using next_layer_type = AsyncReadWriteStream;
|
||||
using base_type = detail::connection_base<executor_type, connection<AsyncReadWriteStream>>;
|
||||
|
||||
/** \brief Connection configuration parameters.
|
||||
*/
|
||||
struct timeouts {
|
||||
/// Timeout of the resolve operation.
|
||||
std::chrono::steady_clock::duration resolve_timeout = std::chrono::seconds{10};
|
||||
|
||||
/// Timeout of the connect operation.
|
||||
std::chrono::steady_clock::duration connect_timeout = std::chrono::seconds{10};
|
||||
|
||||
/// Timeout of the resp3-handshake operation.
|
||||
std::chrono::steady_clock::duration resp3_handshake_timeout = std::chrono::seconds{2};
|
||||
|
||||
/// Time interval with which PING commands are sent to Redis.
|
||||
std::chrono::steady_clock::duration ping_interval = std::chrono::seconds{1};
|
||||
};
|
||||
|
||||
/// Constructor
|
||||
explicit
|
||||
connection(
|
||||
executor_type ex,
|
||||
std::pmr::memory_resource* resource = std::pmr::get_default_resource())
|
||||
: base_type{ex, resource}
|
||||
, stream_{ex}
|
||||
{}
|
||||
|
||||
explicit
|
||||
connection(
|
||||
boost::asio::io_context& ioc,
|
||||
std::pmr::memory_resource* resource = std::pmr::get_default_resource())
|
||||
: connection(ioc.get_executor(), resource)
|
||||
{ }
|
||||
|
||||
/// Returns the associated executor.
|
||||
auto get_executor() {return stream_.get_executor();}
|
||||
|
||||
/// Resets the underlying stream.
|
||||
void reset_stream()
|
||||
{
|
||||
if (stream_.is_open()) {
|
||||
boost::system::error_code ignore;
|
||||
stream_.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ignore);
|
||||
stream_.close(ignore);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a reference to the next layer.
|
||||
auto next_layer() noexcept -> auto& { return stream_; }
|
||||
|
||||
/// 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.
|
||||
*
|
||||
* This function performs the following steps
|
||||
*
|
||||
* @li Resolves the Redis host as of `async_resolve` with the
|
||||
* timeout passed in the base class `connection::timeouts::resolve_timeout`.
|
||||
*
|
||||
* @li Connects to one of the endpoints returned by the resolve
|
||||
* operation with the timeout passed in the base class
|
||||
* `connection::timeouts::connect_timeout`.
|
||||
*
|
||||
* @li Performs a RESP3 handshake by sending a
|
||||
* [HELLO](https://redis.io/commands/hello/) command with protocol
|
||||
* version 3 and the credentials contained in the
|
||||
* `aedis::endpoint` object. The timeout used is the one specified
|
||||
* in `connection::timeouts::resp3_handshake_timeout`.
|
||||
*
|
||||
* @li Erases any password that may be contained in
|
||||
* `endpoint::password`.
|
||||
*
|
||||
* @li Checks whether the server role corresponds to the one
|
||||
* specified in the `endpoint`. If `endpoint::role` is left empty,
|
||||
* no check is performed. If the role is different than the
|
||||
* expected `async_run` will complete with
|
||||
* `error::unexpected_server_role`.
|
||||
*
|
||||
* @li Starts healthy checks with a timeout twice the value of
|
||||
* `connection::timeouts::ping_interval`. If no data is received during that
|
||||
* time interval `connection::async_run` completes with
|
||||
* `error::idle_timeout`.
|
||||
*
|
||||
* @li Starts the healthy check operation that sends the
|
||||
* [PING](https://redis.io/commands/ping/) to Redis with a
|
||||
* frequency equal to `connection::timeouts::ping_interval`.
|
||||
*
|
||||
* @li Starts reading from the socket and executes all requests
|
||||
* that have been started prior to this function call.
|
||||
*
|
||||
* @param ep Redis endpoint.
|
||||
* @param ts Timeouts used by the operations.
|
||||
* @param token Completion token.
|
||||
*
|
||||
* The completion token must have the following signature
|
||||
*
|
||||
* @code
|
||||
* void f(boost::system::error_code);
|
||||
* @endcode
|
||||
*
|
||||
* This function will complete when the connection is lost as
|
||||
* follows. If the error is boost::asio::error::eof this function
|
||||
* will complete without error.
|
||||
*/
|
||||
template <class CompletionToken = boost::asio::default_completion_token_t<executor_type>>
|
||||
auto
|
||||
async_run(
|
||||
endpoint ep,
|
||||
timeouts ts = timeouts{},
|
||||
CompletionToken token = CompletionToken{})
|
||||
{
|
||||
return base_type::async_run(ep, ts, std::move(token));
|
||||
}
|
||||
|
||||
/** @brief Executes a command on the Redis server asynchronously.
|
||||
*
|
||||
* This function will send a request to the Redis server and
|
||||
* complete when the response arrives. If the request contains
|
||||
* only commands that don't expect a response, the completion
|
||||
* occurs after it has been written to the underlying stream.
|
||||
* Multiple concurrent calls to this function will be
|
||||
* 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
|
||||
* have the following signature
|
||||
*
|
||||
* @code
|
||||
* void f(boost::system::error_code, std::size_t);
|
||||
* @endcode
|
||||
*
|
||||
* Where the second parameter is the size of the response in
|
||||
* bytes.
|
||||
*/
|
||||
template <
|
||||
class Adapter = detail::response_traits<void>::adapter_type,
|
||||
class CompletionToken = boost::asio::default_completion_token_t<executor_type>>
|
||||
auto async_exec(
|
||||
resp3::request const& req,
|
||||
Adapter adapter = adapt(),
|
||||
CompletionToken token = CompletionToken{})
|
||||
{
|
||||
return base_type::async_exec(req, adapter, std::move(token));
|
||||
}
|
||||
|
||||
/** @brief Receives server side pushes asynchronously.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @param adapter The response adapter.
|
||||
* @param token The Asio completion token.
|
||||
*
|
||||
* For an example see subscriber.cpp. The completion token must
|
||||
* have the following signature
|
||||
*
|
||||
* @code
|
||||
* void f(boost::system::error_code, std::size_t);
|
||||
* @endcode
|
||||
*
|
||||
* Where the second parameter is the size of the push in
|
||||
* bytes.
|
||||
*/
|
||||
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{})
|
||||
{
|
||||
return base_type::async_receive(adapter, std::move(token));
|
||||
}
|
||||
|
||||
/** @brief Cancel operations.
|
||||
*
|
||||
* @li `operation::exec`: Cancels operations started with
|
||||
* `async_exec`. Affects only requests that haven't been written
|
||||
* yet.
|
||||
* @li operation::run: Cancels the `async_run` operation. Notice
|
||||
* that the preferred way to close a connection is to send a
|
||||
* [QUIT](https://redis.io/commands/quit/) command to the server.
|
||||
* An unresponsive Redis server will also cause the idle-checks to
|
||||
* timeout and lead to `connection::async_run` completing with
|
||||
* `error::idle_timeout`. Calling `cancel(operation::run)`
|
||||
* directly should be seen as the last option.
|
||||
* @li operation::receive: Cancels any ongoing callto
|
||||
* `async_receive`.
|
||||
*
|
||||
* @param op: The operation to be cancelled.
|
||||
* @returns The number of operations that have been canceled.
|
||||
*/
|
||||
auto cancel(operation op) -> std::size_t
|
||||
{ return base_type::cancel(op); }
|
||||
|
||||
private:
|
||||
using this_type = connection<next_layer_type>;
|
||||
|
||||
template <class, class> friend class detail::connection_base;
|
||||
template <class, class> friend struct detail::exec_read_op;
|
||||
template <class, class> friend struct detail::exec_op;
|
||||
template <class, class> friend struct detail::receive_op;
|
||||
template <class> friend struct detail::check_idle_op;
|
||||
template <class> friend struct detail::reader_op;
|
||||
template <class> friend struct detail::writer_op;
|
||||
template <class, class> friend struct detail::connect_with_timeout_op;
|
||||
template <class, class> friend struct detail::run_op;
|
||||
template <class> friend struct detail::ping_op;
|
||||
|
||||
template <class Timer, class CompletionToken>
|
||||
auto
|
||||
async_connect(
|
||||
boost::asio::ip::tcp::resolver::results_type const& endpoints,
|
||||
timeouts ts,
|
||||
Timer& timer,
|
||||
CompletionToken&& token)
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code)
|
||||
>(detail::connect_with_timeout_op<this_type, Timer>{this, &endpoints, ts, &timer},
|
||||
token, stream_);
|
||||
}
|
||||
|
||||
void close() { stream_.close(); }
|
||||
auto is_open() const noexcept { return stream_.is_open(); }
|
||||
auto lowest_layer() noexcept -> auto& { return stream_.lowest_layer(); }
|
||||
|
||||
AsyncReadWriteStream stream_;
|
||||
};
|
||||
|
||||
} // aedis
|
||||
|
||||
#endif // AEDIS_CONNECTION_HPP
|
||||
480
include/aedis/detail/connection_base.hpp
Normal file
480
include/aedis/detail/connection_base.hpp
Normal file
@@ -0,0 +1,480 @@
|
||||
/* 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_CONNECTION_BASE_HPP
|
||||
#define AEDIS_CONNECTION_BASE_HPP
|
||||
|
||||
#include <vector>
|
||||
#include <queue>
|
||||
#include <limits>
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#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/endpoint.hpp>
|
||||
#include <aedis/resp3/request.hpp>
|
||||
#include <aedis/detail/connection_ops.hpp>
|
||||
|
||||
namespace aedis::detail {
|
||||
|
||||
/** Base class for high level Redis asynchronous connections.
|
||||
*
|
||||
* This class is not meant to be instantiated directly but as base
|
||||
* class in the CRTP.
|
||||
*
|
||||
* @tparam Executor The executor type.
|
||||
* @tparam Derived The derived class type.
|
||||
*
|
||||
*/
|
||||
template <class Executor, class Derived>
|
||||
class connection_base {
|
||||
public:
|
||||
using executor_type = Executor;
|
||||
using this_type = connection_base<Executor, Derived>;
|
||||
|
||||
explicit
|
||||
connection_base(executor_type ex, std::pmr::memory_resource* resource)
|
||||
: resv_{ex}
|
||||
, ping_timer_{ex}
|
||||
, check_idle_timer_{ex}
|
||||
, writer_timer_{ex}
|
||||
, read_timer_{ex}
|
||||
, push_channel_{ex}
|
||||
, reqs_{resource}
|
||||
, last_data_{std::chrono::time_point<std::chrono::steady_clock>::min()}
|
||||
{
|
||||
req_.get_config().cancel_if_not_connected = true;
|
||||
req_.get_config().cancel_on_connection_lost = true;
|
||||
writer_timer_.expires_at(std::chrono::steady_clock::time_point::max());
|
||||
read_timer_.expires_at(std::chrono::steady_clock::time_point::max());
|
||||
}
|
||||
|
||||
auto get_executor() {return resv_.get_executor();}
|
||||
|
||||
auto cancel(operation op) -> std::size_t
|
||||
{
|
||||
switch (op) {
|
||||
case operation::exec:
|
||||
{
|
||||
return cancel_unwritten_requests();
|
||||
}
|
||||
case operation::run:
|
||||
{
|
||||
resv_.cancel();
|
||||
derived().close();
|
||||
|
||||
read_timer_.cancel();
|
||||
check_idle_timer_.cancel();
|
||||
writer_timer_.cancel();
|
||||
ping_timer_.cancel();
|
||||
cancel_on_conn_lost();
|
||||
|
||||
return 1U;
|
||||
}
|
||||
case operation::receive:
|
||||
{
|
||||
push_channel_.cancel();
|
||||
return 1U;
|
||||
}
|
||||
default: BOOST_ASSERT(false); return 0;
|
||||
}
|
||||
}
|
||||
|
||||
auto cancel_unwritten_requests() -> std::size_t
|
||||
{
|
||||
auto f = [](auto const& ptr)
|
||||
{
|
||||
BOOST_ASSERT(ptr != nullptr);
|
||||
return ptr->is_written();
|
||||
};
|
||||
|
||||
auto point = std::stable_partition(std::begin(reqs_), std::end(reqs_), f);
|
||||
|
||||
auto const ret = std::distance(point, std::end(reqs_));
|
||||
|
||||
std::for_each(point, std::end(reqs_), [](auto const& ptr) {
|
||||
ptr->stop();
|
||||
});
|
||||
|
||||
reqs_.erase(point, std::end(reqs_));
|
||||
return ret;
|
||||
}
|
||||
|
||||
auto cancel_on_conn_lost() -> std::size_t
|
||||
{
|
||||
auto cond = [](auto const& ptr)
|
||||
{
|
||||
BOOST_ASSERT(ptr != nullptr);
|
||||
|
||||
if (ptr->get_request().get_config().cancel_on_connection_lost)
|
||||
return false;
|
||||
|
||||
return !(!ptr->get_request().get_config().retry && ptr->is_written());
|
||||
};
|
||||
|
||||
auto point = std::stable_partition(std::begin(reqs_), std::end(reqs_), cond);
|
||||
|
||||
auto const ret = std::distance(point, std::end(reqs_));
|
||||
|
||||
std::for_each(point, std::end(reqs_), [](auto const& ptr) {
|
||||
ptr->stop();
|
||||
});
|
||||
|
||||
reqs_.erase(point, std::end(reqs_));
|
||||
std::for_each(std::begin(reqs_), std::end(reqs_), [](auto const& ptr) {
|
||||
return ptr->reset_status();
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <
|
||||
class Adapter = detail::response_traits<void>::adapter_type,
|
||||
class CompletionToken = boost::asio::default_completion_token_t<executor_type>>
|
||||
auto async_exec(
|
||||
resp3::request const& req,
|
||||
Adapter adapter = adapt(),
|
||||
CompletionToken token = CompletionToken{})
|
||||
{
|
||||
BOOST_ASSERT_MSG(req.size() <= adapter.get_supported_response_size(), "Request and adapter have incompatible sizes.");
|
||||
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code, std::size_t)
|
||||
>(detail::exec_op<Derived, Adapter>{&derived(), &req, adapter}, token, resv_);
|
||||
}
|
||||
|
||||
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{})
|
||||
{
|
||||
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, resv_);
|
||||
}
|
||||
|
||||
template <class Timeouts, class CompletionToken>
|
||||
auto
|
||||
async_run(endpoint ep, Timeouts ts, CompletionToken token)
|
||||
{
|
||||
ep_ = std::move(ep);
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code)
|
||||
>(detail::run_op<Derived, Timeouts>{&derived(), ts}, token, resv_);
|
||||
}
|
||||
|
||||
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); }
|
||||
|
||||
void on_write()
|
||||
{
|
||||
// We have to clear the payload right after writing it to use it
|
||||
// as a flag that informs there is no ongoing write.
|
||||
write_buffer_.clear();
|
||||
|
||||
// Notice this must come before the for-each below.
|
||||
cancel_push_requests();
|
||||
|
||||
std::for_each(std::begin(reqs_), std::end(reqs_), [](auto const& ptr) {
|
||||
if (ptr->is_staged())
|
||||
ptr->mark_written();
|
||||
});
|
||||
}
|
||||
|
||||
struct req_info {
|
||||
public:
|
||||
enum class action
|
||||
{
|
||||
stop,
|
||||
proceed,
|
||||
none,
|
||||
};
|
||||
|
||||
explicit req_info(resp3::request const& req, executor_type ex)
|
||||
: timer_{ex}
|
||||
, action_{action::none}
|
||||
, req_{&req}
|
||||
, cmds_{std::size(req)}
|
||||
, status_{status::none}
|
||||
{
|
||||
timer_.expires_at(std::chrono::steady_clock::time_point::max());
|
||||
}
|
||||
|
||||
auto proceed()
|
||||
{
|
||||
timer_.cancel();
|
||||
action_ = action::proceed;
|
||||
}
|
||||
|
||||
void stop()
|
||||
{
|
||||
timer_.cancel();
|
||||
action_ = action::stop;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto is_written() const noexcept
|
||||
{ return status_ == status::written; }
|
||||
|
||||
[[nodiscard]] auto is_staged() const noexcept
|
||||
{ return status_ == status::staged; }
|
||||
|
||||
void mark_written() noexcept
|
||||
{ status_ = status::written; }
|
||||
|
||||
void mark_staged() noexcept
|
||||
{ status_ = status::staged; }
|
||||
|
||||
void reset_status() noexcept
|
||||
{ status_ = status::none; }
|
||||
|
||||
[[nodiscard]] auto get_number_of_commands() const noexcept
|
||||
{ return cmds_; }
|
||||
|
||||
[[nodiscard]] auto get_request() const noexcept -> auto const&
|
||||
{ return *req_; }
|
||||
|
||||
[[nodiscard]] auto get_action() const noexcept
|
||||
{ return action_;}
|
||||
|
||||
template <class CompletionToken>
|
||||
auto async_wait(CompletionToken token)
|
||||
{
|
||||
return timer_.async_wait(std::move(token));
|
||||
}
|
||||
|
||||
private:
|
||||
enum class status
|
||||
{ none
|
||||
, staged
|
||||
, written
|
||||
};
|
||||
|
||||
timer_type timer_;
|
||||
action action_;
|
||||
resp3::request const* req_;
|
||||
std::size_t cmds_;
|
||||
status status_;
|
||||
};
|
||||
|
||||
void remove_request(std::shared_ptr<req_info> const& info)
|
||||
{
|
||||
reqs_.erase(std::remove(std::begin(reqs_), std::end(reqs_), info));
|
||||
}
|
||||
|
||||
using reqs_type = std::pmr::deque<std::shared_ptr<req_info>>;
|
||||
|
||||
template <class, class> friend struct detail::receive_op;
|
||||
template <class> friend struct detail::reader_op;
|
||||
template <class> friend struct detail::writer_op;
|
||||
template <class> friend struct detail::ping_op;
|
||||
template <class, class> friend struct detail::run_op;
|
||||
template <class, class> friend struct detail::exec_op;
|
||||
template <class, class> friend struct detail::exec_read_op;
|
||||
template <class> friend struct detail::resolve_with_timeout_op;
|
||||
template <class> friend struct detail::check_idle_op;
|
||||
template <class, class> friend struct detail::start_op;
|
||||
template <class> friend struct detail::send_receive_op;
|
||||
|
||||
void cancel_push_requests()
|
||||
{
|
||||
auto point = std::stable_partition(std::begin(reqs_), std::end(reqs_), [](auto const& ptr) {
|
||||
return !(ptr->is_staged() && ptr->get_request().size() == 0);
|
||||
});
|
||||
|
||||
std::for_each(point, std::end(reqs_), [](auto const& ptr) {
|
||||
ptr->proceed();
|
||||
});
|
||||
|
||||
reqs_.erase(point, std::end(reqs_));
|
||||
}
|
||||
|
||||
void add_request_info(std::shared_ptr<req_info> const& info)
|
||||
{
|
||||
reqs_.push_back(info);
|
||||
if (derived().is_open() && cmds_ == 0 && write_buffer_.empty())
|
||||
writer_timer_.cancel();
|
||||
}
|
||||
|
||||
auto make_dynamic_buffer(std::size_t max_read_size = 512)
|
||||
{ return boost::asio::dynamic_buffer(read_buffer_, max_read_size); }
|
||||
|
||||
template <class CompletionToken>
|
||||
auto
|
||||
async_resolve_with_timeout(
|
||||
std::chrono::steady_clock::duration d,
|
||||
CompletionToken&& token)
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code)
|
||||
>(detail::resolve_with_timeout_op<this_type>{this, d},
|
||||
token, resv_);
|
||||
}
|
||||
|
||||
template <class CompletionToken>
|
||||
auto reader(CompletionToken&& token)
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code)
|
||||
>(detail::reader_op<Derived>{&derived()}, token, resv_.get_executor());
|
||||
}
|
||||
|
||||
template <class CompletionToken>
|
||||
auto writer(CompletionToken&& token)
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code)
|
||||
>(detail::writer_op<Derived>{&derived()}, token, resv_.get_executor());
|
||||
}
|
||||
|
||||
template <
|
||||
class Timeouts,
|
||||
class CompletionToken>
|
||||
auto async_start(Timeouts ts, CompletionToken&& token)
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code)
|
||||
>(detail::start_op<this_type, Timeouts>{this, ts}, token, resv_);
|
||||
}
|
||||
|
||||
template <class CompletionToken>
|
||||
auto
|
||||
async_ping(
|
||||
std::chrono::steady_clock::duration d,
|
||||
CompletionToken&& token)
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code)
|
||||
>(detail::ping_op<Derived>{&derived(), d}, token, resv_);
|
||||
}
|
||||
|
||||
template <class CompletionToken>
|
||||
auto
|
||||
async_check_idle(
|
||||
std::chrono::steady_clock::duration d,
|
||||
CompletionToken&& token)
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code)
|
||||
>(detail::check_idle_op<Derived>{&derived(), d}, token, check_idle_timer_);
|
||||
}
|
||||
|
||||
template <class Adapter, class CompletionToken>
|
||||
auto async_exec_read(Adapter adapter, std::size_t cmds, CompletionToken token)
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code, std::size_t)
|
||||
>(detail::exec_read_op<Derived, Adapter>{&derived(), adapter, cmds}, token, resv_);
|
||||
}
|
||||
|
||||
void stage_request(req_info& ri)
|
||||
{
|
||||
write_buffer_ += ri.get_request().payload();
|
||||
cmds_ += ri.get_request().size();
|
||||
ri.mark_staged();
|
||||
}
|
||||
|
||||
void coalesce_requests()
|
||||
{
|
||||
// Coalesce the requests and marks them staged. After a
|
||||
// successful write staged requests will be marked as written.
|
||||
BOOST_ASSERT(write_buffer_.empty());
|
||||
BOOST_ASSERT(!reqs_.empty());
|
||||
|
||||
stage_request(*reqs_.at(0));
|
||||
|
||||
for (std::size_t i = 1; i < std::size(reqs_); ++i) {
|
||||
if (!reqs_.at(i - 1)->get_request().get_config().coalesce ||
|
||||
!reqs_.at(i - 0)->get_request().get_config().coalesce) {
|
||||
break;
|
||||
}
|
||||
stage_request(*reqs_.at(i));
|
||||
}
|
||||
}
|
||||
|
||||
void prepare_hello(endpoint const& ep)
|
||||
{
|
||||
req_.clear();
|
||||
if (requires_auth(ep)) {
|
||||
req_.push("HELLO", "3", "AUTH", ep.username, ep.password);
|
||||
} else {
|
||||
req_.push("HELLO", "3");
|
||||
}
|
||||
}
|
||||
|
||||
auto expect_role(std::string const& expected) -> bool
|
||||
{
|
||||
if (std::empty(expected))
|
||||
return true;
|
||||
|
||||
resp3::node<std::string> role_node;
|
||||
role_node.data_type = resp3::type::blob_string;
|
||||
role_node.aggregate_size = 1;
|
||||
role_node.depth = 1;
|
||||
role_node.value = "role";
|
||||
|
||||
auto iter = std::find(std::cbegin(response_), std::cend(response_), role_node);
|
||||
if (iter == std::end(response_))
|
||||
return false;
|
||||
|
||||
++iter;
|
||||
BOOST_ASSERT(iter != std::cend(response_));
|
||||
return iter->value == expected;
|
||||
}
|
||||
|
||||
// IO objects
|
||||
resolver_type resv_;
|
||||
timer_type ping_timer_;
|
||||
timer_type check_idle_timer_;
|
||||
timer_type writer_timer_;
|
||||
timer_type read_timer_;
|
||||
push_channel_type push_channel_;
|
||||
|
||||
std::string read_buffer_;
|
||||
std::string write_buffer_;
|
||||
std::size_t cmds_ = 0;
|
||||
reqs_type reqs_;
|
||||
|
||||
// Last time we received data.
|
||||
time_point_type last_data_;
|
||||
|
||||
resp3::request req_;
|
||||
std::vector<resp3::node<std::string>> response_;
|
||||
endpoint ep_;
|
||||
// The result of async_resolve.
|
||||
boost::asio::ip::tcp::resolver::results_type endpoints_;
|
||||
};
|
||||
|
||||
} // aedis
|
||||
|
||||
#endif // AEDIS_CONNECTION_BASE_HPP
|
||||
570
include/aedis/detail/connection_ops.hpp
Normal file
570
include/aedis/detail/connection_ops.hpp
Normal file
@@ -0,0 +1,570 @@
|
||||
/* 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_CONNECTION_OPS_HPP
|
||||
#define AEDIS_CONNECTION_OPS_HPP
|
||||
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
|
||||
#include <boost/assert.hpp>
|
||||
#include <boost/system.hpp>
|
||||
#include <boost/asio/write.hpp>
|
||||
#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/exec.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>
|
||||
|
||||
namespace aedis::detail {
|
||||
|
||||
template <class Conn, class Timer>
|
||||
struct connect_with_timeout_op {
|
||||
Conn* conn = nullptr;
|
||||
boost::asio::ip::tcp::resolver::results_type const* endpoints = nullptr;
|
||||
typename Conn::timeouts ts;
|
||||
Timer* timer = nullptr;
|
||||
boost::asio::coroutine coro{};
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, boost::system::error_code ec = {}
|
||||
, boost::asio::ip::tcp::endpoint const& = {})
|
||||
{
|
||||
reenter (coro)
|
||||
{
|
||||
timer->expires_after(ts.connect_timeout);
|
||||
yield detail::async_connect(conn->next_layer(), *timer, *endpoints, std::move(self));
|
||||
AEDIS_CHECK_OP0();
|
||||
self.complete({});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Conn>
|
||||
struct resolve_with_timeout_op {
|
||||
Conn* conn = nullptr;
|
||||
std::chrono::steady_clock::duration resolve_timeout{};
|
||||
boost::asio::coroutine coro{};
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, boost::system::error_code ec = {}
|
||||
, boost::asio::ip::tcp::resolver::results_type const& res = {})
|
||||
{
|
||||
reenter (coro)
|
||||
{
|
||||
conn->ping_timer_.expires_after(resolve_timeout);
|
||||
yield
|
||||
aedis::detail::async_resolve(
|
||||
conn->resv_, conn->ping_timer_,
|
||||
conn->ep_.host, conn->ep_.port, std::move(self));
|
||||
AEDIS_CHECK_OP0();
|
||||
conn->endpoints_ = res;
|
||||
self.complete({});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
Adapter adapter;
|
||||
std::size_t cmds = 0;
|
||||
std::size_t read_size = 0;
|
||||
std::size_t index = 0;
|
||||
boost::asio::coroutine coro{};
|
||||
|
||||
template <class Self>
|
||||
void
|
||||
operator()( Self& self
|
||||
, boost::system::error_code ec = {}
|
||||
, std::size_t n = 0)
|
||||
{
|
||||
reenter (coro)
|
||||
{
|
||||
// Loop reading the responses to this request.
|
||||
BOOST_ASSERT(!conn->reqs_.empty());
|
||||
while (cmds != 0) {
|
||||
BOOST_ASSERT(conn->cmds_ != 0);
|
||||
|
||||
//-----------------------------------
|
||||
// If we detect a push in the middle of a request we have
|
||||
// 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::async_read_until(
|
||||
conn->next_layer(),
|
||||
conn->make_dynamic_buffer(),
|
||||
"\r\n", std::move(self));
|
||||
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));
|
||||
continue;
|
||||
}
|
||||
//-----------------------------------
|
||||
|
||||
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); },
|
||||
std::move(self));
|
||||
|
||||
++index;
|
||||
|
||||
AEDIS_CHECK_OP1(conn->cancel(operation::run));
|
||||
|
||||
read_size += n;
|
||||
|
||||
BOOST_ASSERT(cmds != 0);
|
||||
--cmds;
|
||||
|
||||
BOOST_ASSERT(conn->cmds_ != 0);
|
||||
--conn->cmds_;
|
||||
}
|
||||
|
||||
self.complete({}, read_size);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Conn, class Adapter>
|
||||
struct exec_op {
|
||||
using req_info_type = typename Conn::req_info;
|
||||
|
||||
Conn* conn = nullptr;
|
||||
resp3::request const* req = nullptr;
|
||||
Adapter adapter{};
|
||||
std::shared_ptr<req_info_type> info = nullptr;
|
||||
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)
|
||||
{
|
||||
// Check whether the user wants to wait for the connection to
|
||||
// be stablished.
|
||||
// TODO: is_open below reflects only whether a TCP connection
|
||||
// has been stablished. We need a variable that informs
|
||||
// whether HELLO was successfull and we are connected with
|
||||
// Redis.
|
||||
if (req->get_config().cancel_if_not_connected && !conn->is_open())
|
||||
return self.complete(error::not_connected, 0);
|
||||
|
||||
info = std::allocate_shared<req_info_type>(boost::asio::get_associated_allocator(self), *req, conn->resv_.get_executor());
|
||||
|
||||
conn->add_request_info(info);
|
||||
EXEC_OP_WAIT:
|
||||
yield info->async_wait(std::move(self));
|
||||
BOOST_ASSERT(ec == boost::asio::error::operation_aborted);
|
||||
|
||||
if (info->get_action() == Conn::req_info::action::stop) {
|
||||
return self.complete(ec, 0);
|
||||
}
|
||||
|
||||
if (is_cancelled(self)) {
|
||||
if (info->is_written()) {
|
||||
self.get_cancellation_state().clear();
|
||||
goto EXEC_OP_WAIT; // Too late, can't cancel.
|
||||
} else {
|
||||
conn->remove_request(info);
|
||||
self.complete(ec, 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_ASSERT(conn->is_open());
|
||||
|
||||
if (req->size() == 0)
|
||||
return self.complete({}, 0);
|
||||
|
||||
BOOST_ASSERT(!conn->reqs_.empty());
|
||||
BOOST_ASSERT(conn->reqs_.front() != nullptr);
|
||||
BOOST_ASSERT(conn->cmds_ != 0);
|
||||
yield
|
||||
conn->async_exec_read(adapter, conn->reqs_.front()->get_number_of_commands(), std::move(self));
|
||||
AEDIS_CHECK_OP1();
|
||||
|
||||
read_size = n;
|
||||
|
||||
BOOST_ASSERT(!conn->reqs_.empty());
|
||||
conn->reqs_.pop_front();
|
||||
|
||||
if (conn->cmds_ == 0) {
|
||||
conn->read_timer_.cancel_one();
|
||||
if (!conn->reqs_.empty())
|
||||
conn->writer_timer_.cancel_one();
|
||||
} else {
|
||||
BOOST_ASSERT(!conn->reqs_.empty());
|
||||
conn->reqs_.front()->proceed();
|
||||
}
|
||||
|
||||
self.complete({}, read_size);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Conn>
|
||||
struct ping_op {
|
||||
Conn* conn{};
|
||||
std::chrono::steady_clock::duration ping_interval{};
|
||||
boost::asio::coroutine coro{};
|
||||
|
||||
template <class Self>
|
||||
void
|
||||
operator()( Self& self
|
||||
, boost::system::error_code ec = {}
|
||||
, std::size_t = 0)
|
||||
{
|
||||
reenter (coro) for (;;)
|
||||
{
|
||||
conn->ping_timer_.expires_after(ping_interval);
|
||||
yield conn->ping_timer_.async_wait(std::move(self));
|
||||
if (!conn->is_open() || ec || is_cancelled(self)) {
|
||||
// Checking for is_open is necessary becuse the timer can
|
||||
// complete with success although cancel has been called.
|
||||
self.complete({});
|
||||
return;
|
||||
}
|
||||
|
||||
conn->req_.clear();
|
||||
conn->req_.push("PING");
|
||||
yield conn->async_exec(conn->req_, adapt(), std::move(self));
|
||||
if (!conn->is_open() || is_cancelled(self)) {
|
||||
// Checking for is_open is necessary to avoid
|
||||
// looping back on the timer although cancel has been
|
||||
// called.
|
||||
return self.complete({});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Conn>
|
||||
struct check_idle_op {
|
||||
Conn* conn{};
|
||||
std::chrono::steady_clock::duration ping_interval{};
|
||||
boost::asio::coroutine coro{};
|
||||
|
||||
template <class Self>
|
||||
void operator()(Self& self, boost::system::error_code ec = {})
|
||||
{
|
||||
reenter (coro) for (;;)
|
||||
{
|
||||
conn->check_idle_timer_.expires_after(2 * ping_interval);
|
||||
yield conn->check_idle_timer_.async_wait(std::move(self));
|
||||
if (!conn->is_open() || ec || is_cancelled(self)) {
|
||||
// Checking for is_open is necessary becuse the timer can
|
||||
// complete with success although cancel has been called.
|
||||
return self.complete({});
|
||||
}
|
||||
|
||||
auto const now = std::chrono::steady_clock::now();
|
||||
if (conn->last_data_ + (2 * ping_interval) < now) {
|
||||
conn->cancel(operation::run);
|
||||
self.complete(error::idle_timeout);
|
||||
return;
|
||||
}
|
||||
|
||||
conn->last_data_ = now;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Conn, class Timeouts>
|
||||
struct start_op {
|
||||
Conn* conn;
|
||||
Timeouts ts;
|
||||
boost::asio::coroutine coro{};
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, std::array<std::size_t, 4> order = {}
|
||||
, boost::system::error_code ec0 = {}
|
||||
, boost::system::error_code ec1 = {}
|
||||
, boost::system::error_code ec2 = {}
|
||||
, boost::system::error_code ec3 = {})
|
||||
{
|
||||
reenter (coro)
|
||||
{
|
||||
yield
|
||||
boost::asio::experimental::make_parallel_group(
|
||||
[this](auto token) { return conn->reader(token);},
|
||||
[this](auto token) { return conn->writer(token);},
|
||||
[this](auto token) { return conn->async_check_idle(ts.ping_interval, token);},
|
||||
[this](auto token) { return conn->async_ping(ts.ping_interval, token);}
|
||||
).async_wait(
|
||||
boost::asio::experimental::wait_for_one(),
|
||||
std::move(self));
|
||||
|
||||
if (is_cancelled(self)) {
|
||||
self.complete(boost::asio::error::operation_aborted);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (order[0]) {
|
||||
case 0: self.complete(ec0); break;
|
||||
case 1: self.complete(ec1); break;
|
||||
case 2: self.complete(ec2); break;
|
||||
case 3: self.complete(ec3); break;
|
||||
default: BOOST_ASSERT(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
inline
|
||||
auto check_resp3_handshake_failed(std::vector<resp3::node<std::string>> const& resp) -> bool
|
||||
{
|
||||
return std::size(resp) == 1 &&
|
||||
(resp.front().data_type == resp3::type::simple_error ||
|
||||
resp.front().data_type == resp3::type::blob_error ||
|
||||
resp.front().data_type == resp3::type::null);
|
||||
}
|
||||
|
||||
template <class Conn, class Timeouts>
|
||||
struct run_op {
|
||||
Conn* conn = nullptr;
|
||||
Timeouts ts;
|
||||
boost::asio::coroutine coro{};
|
||||
|
||||
template <class Self>
|
||||
void operator()(
|
||||
Self& self,
|
||||
boost::system::error_code ec = {},
|
||||
std::size_t = 0)
|
||||
{
|
||||
reenter (coro)
|
||||
{
|
||||
yield conn->async_resolve_with_timeout(ts.resolve_timeout, std::move(self));
|
||||
AEDIS_CHECK_OP0(conn->cancel(operation::run));
|
||||
|
||||
yield conn->derived().async_connect(conn->endpoints_, ts, conn->ping_timer_, std::move(self));
|
||||
AEDIS_CHECK_OP0(conn->cancel(operation::run));
|
||||
|
||||
conn->prepare_hello(conn->ep_);
|
||||
conn->ping_timer_.expires_after(ts.resp3_handshake_timeout);
|
||||
conn->response_.clear();
|
||||
|
||||
yield
|
||||
resp3::detail::async_exec(
|
||||
conn->next_layer(),
|
||||
conn->ping_timer_,
|
||||
conn->req_,
|
||||
adapter::adapt2(conn->response_),
|
||||
conn->make_dynamic_buffer(),
|
||||
std::move(self)
|
||||
);
|
||||
|
||||
AEDIS_CHECK_OP0(conn->cancel(operation::run));
|
||||
|
||||
if (check_resp3_handshake_failed(conn->response_)) {
|
||||
conn->cancel(operation::run);
|
||||
self.complete(error::resp3_handshake_error);
|
||||
return;
|
||||
}
|
||||
|
||||
conn->ep_.password.clear();
|
||||
|
||||
if (!conn->expect_role(conn->ep_.role)) {
|
||||
conn->cancel(operation::run);
|
||||
self.complete(error::unexpected_server_role);
|
||||
return;
|
||||
}
|
||||
|
||||
conn->write_buffer_.clear();
|
||||
conn->cmds_ = 0;
|
||||
|
||||
yield conn->async_start(ts, std::move(self));
|
||||
AEDIS_CHECK_OP0();
|
||||
self.complete({});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Conn>
|
||||
struct writer_op {
|
||||
Conn* conn;
|
||||
boost::asio::coroutine coro{};
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, boost::system::error_code ec = {}
|
||||
, std::size_t n = 0)
|
||||
{
|
||||
boost::ignore_unused(n);
|
||||
|
||||
reenter (coro) for (;;)
|
||||
{
|
||||
while (!conn->reqs_.empty() && conn->cmds_ == 0 && conn->write_buffer_.empty()) {
|
||||
conn->coalesce_requests();
|
||||
yield
|
||||
boost::asio::async_write(conn->next_layer(), boost::asio::buffer(conn->write_buffer_), std::move(self));
|
||||
AEDIS_CHECK_OP0(conn->cancel(operation::run));
|
||||
|
||||
conn->on_write();
|
||||
|
||||
// A socket.close() may have been called while a
|
||||
// successful write might had already been queued, so we
|
||||
// have to check here before proceeding.
|
||||
if (!conn->is_open()) {
|
||||
self.complete({});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
yield conn->writer_timer_.async_wait(std::move(self));
|
||||
if (!conn->is_open() || is_cancelled(self)) {
|
||||
// Notice this is not an error of the op, stoping was
|
||||
// requested from the outside, so we complete with
|
||||
// success.
|
||||
self.complete({});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Conn>
|
||||
struct reader_op {
|
||||
Conn* conn;
|
||||
boost::asio::coroutine coro{};
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, boost::system::error_code ec = {}
|
||||
, std::size_t n = 0)
|
||||
{
|
||||
boost::ignore_unused(n);
|
||||
|
||||
reenter (coro) for (;;)
|
||||
{
|
||||
yield
|
||||
boost::asio::async_read_until(
|
||||
conn->next_layer(),
|
||||
conn->make_dynamic_buffer(),
|
||||
"\r\n", std::move(self));
|
||||
|
||||
if (ec == boost::asio::error::eof) {
|
||||
conn->cancel(operation::run);
|
||||
return self.complete({}); // EOFINAE: EOF is not an error.
|
||||
}
|
||||
|
||||
AEDIS_CHECK_OP0(conn->cancel(operation::run));
|
||||
|
||||
conn->last_data_ = std::chrono::steady_clock::now();
|
||||
|
||||
// We handle unsolicited events in the following way
|
||||
//
|
||||
// 1. Its resp3 type is a push.
|
||||
//
|
||||
// 2. A non-push type is received with an empty requests
|
||||
// queue. I have noticed this is possible (e.g. -MISCONF).
|
||||
// I expect them to have type push so we can distinguish
|
||||
// them from responses to commands, but it is a
|
||||
// simple-error. If we are lucky enough to receive them
|
||||
// when the command queue is empty we can treat them as
|
||||
// server pushes, otherwise it is impossible to handle
|
||||
// them properly
|
||||
//
|
||||
// 3. The request does not expect any response but we got
|
||||
// one. This may happen if for example, subscribe with
|
||||
// wrong syntax.
|
||||
//
|
||||
BOOST_ASSERT(!conn->read_buffer_.empty());
|
||||
if (resp3::to_type(conn->read_buffer_.front()) == resp3::type::push
|
||||
|| conn->reqs_.empty()
|
||||
|| (!conn->reqs_.empty() && conn->reqs_.front()->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;
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // aedis::detail
|
||||
|
||||
#include <boost/asio/unyield.hpp>
|
||||
#endif // AEDIS_CONNECTION_OPS_HPP
|
||||
@@ -15,27 +15,28 @@
|
||||
#include <boost/assert.hpp>
|
||||
#include <boost/asio/experimental/parallel_group.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace detail {
|
||||
|
||||
#include <boost/asio/yield.hpp>
|
||||
|
||||
namespace aedis::detail {
|
||||
|
||||
template <class Executor>
|
||||
using conn_timer_t = boost::asio::basic_waitable_timer<std::chrono::steady_clock, boost::asio::wait_traits<std::chrono::steady_clock>, Executor>;
|
||||
|
||||
template <
|
||||
class Protocol,
|
||||
class Executor,
|
||||
class Stream,
|
||||
class EndpointSequence
|
||||
>
|
||||
struct connect_op {
|
||||
boost::asio::basic_socket<Protocol, Executor>* socket;
|
||||
boost::asio::steady_timer* timer;
|
||||
Stream* socket;
|
||||
conn_timer_t<typename Stream::executor_type>* timer;
|
||||
EndpointSequence* endpoints;
|
||||
boost::asio::coroutine coro;
|
||||
boost::asio::coroutine coro{};
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, std::array<std::size_t, 2> order = {}
|
||||
, boost::system::error_code ec1 = {}
|
||||
, typename Protocol::endpoint const& ep = {}
|
||||
, typename Stream::protocol_type::endpoint const& ep = {}
|
||||
, boost::system::error_code ec2 = {})
|
||||
{
|
||||
reenter (coro)
|
||||
@@ -44,7 +45,7 @@ struct connect_op {
|
||||
boost::asio::experimental::make_parallel_group(
|
||||
[this](auto token)
|
||||
{
|
||||
auto f = [](boost::system::error_code const&, typename Protocol::endpoint const&) { return true; };
|
||||
auto f = [](boost::system::error_code const&, auto const&) { return true; };
|
||||
return boost::asio::async_connect(*socket, *endpoints, f, token);
|
||||
},
|
||||
[this](auto token) { return timer->async_wait(token);}
|
||||
@@ -52,37 +53,36 @@ struct connect_op {
|
||||
boost::asio::experimental::wait_for_one(),
|
||||
std::move(self));
|
||||
|
||||
switch (order[0]) {
|
||||
case 0:
|
||||
{
|
||||
if (ec1) {
|
||||
self.complete(ec1, ep);
|
||||
return;
|
||||
}
|
||||
} break;
|
||||
if (is_cancelled(self)) {
|
||||
self.complete(boost::asio::error::operation_aborted, {});
|
||||
return;
|
||||
}
|
||||
|
||||
switch (order[0]) {
|
||||
case 0: self.complete(ec1, ep); return;
|
||||
case 1:
|
||||
{
|
||||
if (!ec2) {
|
||||
if (ec2) {
|
||||
self.complete(ec2, {});
|
||||
} else {
|
||||
self.complete(error::connect_timeout, ep);
|
||||
return;
|
||||
}
|
||||
} break;
|
||||
return;
|
||||
}
|
||||
|
||||
default: BOOST_ASSERT(false);
|
||||
}
|
||||
|
||||
self.complete({}, ep);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Resolver, class Timer>
|
||||
struct resolve_op {
|
||||
boost::asio::ip::tcp::resolver* resv;
|
||||
boost::asio::steady_timer* timer;
|
||||
Resolver* resv;
|
||||
Timer* timer;
|
||||
boost::string_view host;
|
||||
boost::string_view port;
|
||||
boost::asio::coroutine coro;
|
||||
boost::asio::coroutine coro{};
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
@@ -101,27 +101,26 @@ struct resolve_op {
|
||||
boost::asio::experimental::wait_for_one(),
|
||||
std::move(self));
|
||||
|
||||
if (is_cancelled(self)) {
|
||||
self.complete(boost::asio::error::operation_aborted, {});
|
||||
return;
|
||||
}
|
||||
|
||||
switch (order[0]) {
|
||||
case 0:
|
||||
{
|
||||
if (ec1) {
|
||||
self.complete(ec1, {});
|
||||
return;
|
||||
}
|
||||
} break;
|
||||
case 0: self.complete(ec1, res); return;
|
||||
|
||||
case 1:
|
||||
{
|
||||
if (!ec2) {
|
||||
if (ec2) {
|
||||
self.complete(ec2, {});
|
||||
} else {
|
||||
self.complete(error::resolve_timeout, {});
|
||||
return;
|
||||
}
|
||||
} break;
|
||||
return;
|
||||
}
|
||||
|
||||
default: BOOST_ASSERT(false);
|
||||
}
|
||||
|
||||
self.complete({}, res);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -129,55 +128,55 @@ struct resolve_op {
|
||||
template <class Channel>
|
||||
struct send_receive_op {
|
||||
Channel* channel;
|
||||
boost::asio::coroutine coro;
|
||||
boost::asio::coroutine coro{};
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, boost::system::error_code ec = {}
|
||||
, std::size_t n = 0)
|
||||
, std::size_t = 0)
|
||||
{
|
||||
reenter (coro)
|
||||
{
|
||||
yield channel->async_send(boost::system::error_code{}, 0, std::move(self));
|
||||
if (ec) {
|
||||
self.complete(ec, 0);
|
||||
return;
|
||||
}
|
||||
yield
|
||||
channel->async_send(boost::system::error_code{}, 0, std::move(self));
|
||||
AEDIS_CHECK_OP1();
|
||||
|
||||
yield channel->async_receive(std::move(self));
|
||||
self.complete(ec, 0);
|
||||
yield
|
||||
channel->async_receive(std::move(self));
|
||||
AEDIS_CHECK_OP1();
|
||||
|
||||
self.complete({}, 0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#include <boost/asio/unyield.hpp>
|
||||
|
||||
template <
|
||||
class Protocol,
|
||||
class Executor,
|
||||
class Stream,
|
||||
class EndpointSequence,
|
||||
class CompletionToken = boost::asio::default_completion_token_t<Executor>
|
||||
class CompletionToken
|
||||
>
|
||||
auto async_connect(
|
||||
boost::asio::basic_socket<Protocol, Executor>& socket,
|
||||
boost::asio::steady_timer& timer,
|
||||
Stream& socket,
|
||||
conn_timer_t<typename Stream::executor_type>& timer,
|
||||
EndpointSequence ep,
|
||||
CompletionToken&& token = boost::asio::default_completion_token_t<Executor>{})
|
||||
CompletionToken&& token)
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code, typename Protocol::endpoint const&)
|
||||
>(connect_op<Protocol, Executor, EndpointSequence>
|
||||
, void(boost::system::error_code, typename Stream::protocol_type::endpoint const&)
|
||||
>(connect_op<Stream, EndpointSequence>
|
||||
{&socket, &timer, &ep}, token, socket, timer);
|
||||
}
|
||||
|
||||
template <
|
||||
class Resolver,
|
||||
class Timer,
|
||||
class CompletionToken =
|
||||
boost::asio::default_completion_token_t<boost::asio::ip::tcp::resolver::executor_type>
|
||||
boost::asio::default_completion_token_t<typename Resolver::executor_type>
|
||||
>
|
||||
auto async_resolve(
|
||||
boost::asio::ip::tcp::resolver& resv,
|
||||
boost::asio::steady_timer& timer,
|
||||
Resolver& resv,
|
||||
Timer& timer,
|
||||
boost::string_view host,
|
||||
boost::string_view port,
|
||||
CompletionToken&& token = CompletionToken{})
|
||||
@@ -185,7 +184,7 @@ auto async_resolve(
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code, boost::asio::ip::tcp::resolver::results_type)
|
||||
>(resolve_op{&resv, &timer, host, port}, token, resv, timer);
|
||||
>(resolve_op<Resolver, Timer>{&resv, &timer, host, port}, token, resv, timer);
|
||||
}
|
||||
|
||||
template <
|
||||
@@ -200,7 +199,7 @@ auto async_send_receive(Channel& channel, CompletionToken&& token = CompletionTo
|
||||
, void(boost::system::error_code, std::size_t)
|
||||
>(send_receive_op<Channel>{&channel}, token, channel);
|
||||
}
|
||||
} // detail
|
||||
} // aedis
|
||||
} // aedis::detail
|
||||
|
||||
#include <boost/asio/unyield.hpp>
|
||||
#endif // AEDIS_NET_HPP
|
||||
38
include/aedis/endpoint.hpp
Normal file
38
include/aedis/endpoint.hpp
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)
|
||||
*/
|
||||
|
||||
#ifndef AEDIS_ENDPOINT_HPP
|
||||
#define AEDIS_ENDPOINT_HPP
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace aedis {
|
||||
|
||||
/** \brief A Redis endpoint.
|
||||
* \ingroup high-level-api
|
||||
*/
|
||||
struct endpoint {
|
||||
/// Redis server address.
|
||||
std::string host;
|
||||
|
||||
/// Redis server port.
|
||||
std::string port;
|
||||
|
||||
/// Expected role if any.
|
||||
std::string role{};
|
||||
|
||||
/// Username if authentication is required.
|
||||
std::string username{};
|
||||
|
||||
/// Password if authentication is required.
|
||||
std::string password{};
|
||||
};
|
||||
|
||||
auto requires_auth(endpoint const& ep) noexcept -> bool;
|
||||
|
||||
} // aedis
|
||||
|
||||
#endif // AEDIS_ENDPOINT_HPP
|
||||
@@ -12,7 +12,7 @@
|
||||
namespace aedis {
|
||||
|
||||
/** \brief Generic errors.
|
||||
* \ingroup any
|
||||
* \ingroup high-level-api
|
||||
*/
|
||||
enum class error
|
||||
{
|
||||
@@ -25,15 +25,15 @@ enum class error
|
||||
/// Idle timeout.
|
||||
idle_timeout,
|
||||
|
||||
/// Exec timeout.
|
||||
exec_timeout,
|
||||
|
||||
/// Invalid RESP3 type.
|
||||
invalid_data_type,
|
||||
|
||||
/// Can't parse the string as a number.
|
||||
not_a_number,
|
||||
|
||||
/// Received less bytes than expected.
|
||||
unexpected_read_size,
|
||||
|
||||
/// The maximum depth of a nested response was exceeded.
|
||||
exceeeds_max_nested_depth,
|
||||
|
||||
@@ -44,25 +44,25 @@ enum class error
|
||||
empty_field,
|
||||
|
||||
/// Expects a simple RESP3 type but got an aggregate.
|
||||
expects_simple_type,
|
||||
expects_resp3_simple_type,
|
||||
|
||||
/// Expects aggregate type.
|
||||
expects_aggregate_type,
|
||||
/// Expects aggregate.
|
||||
expects_resp3_aggregate,
|
||||
|
||||
/// Expects a map but got other aggregate.
|
||||
expects_map_type,
|
||||
expects_resp3_map,
|
||||
|
||||
/// Expects a set aggregate but got something else.
|
||||
expects_set_type,
|
||||
expects_resp3_set,
|
||||
|
||||
/// Nested response not supported.
|
||||
nested_aggregate_unsupported,
|
||||
nested_aggregate_not_supported,
|
||||
|
||||
/// Got RESP3 simple error.
|
||||
simple_error,
|
||||
resp3_simple_error,
|
||||
|
||||
/// Got RESP3 blob_error.
|
||||
blob_error,
|
||||
resp3_blob_error,
|
||||
|
||||
/// Aggregate container has incompatible size.
|
||||
incompatible_size,
|
||||
@@ -70,8 +70,20 @@ enum class error
|
||||
/// Not a double
|
||||
not_a_double,
|
||||
|
||||
/// Got RESP3 null type.
|
||||
null
|
||||
/// Got RESP3 null.
|
||||
resp3_null,
|
||||
|
||||
/// Unexpected server role.
|
||||
unexpected_server_role,
|
||||
|
||||
/// SSL handshake timeout.
|
||||
ssl_handshake_timeout,
|
||||
|
||||
/// There is no stablished connection.
|
||||
not_connected,
|
||||
|
||||
/// RESP3 handshake error (HELLO command).
|
||||
resp3_handshake_error,
|
||||
};
|
||||
|
||||
/** \internal
|
||||
@@ -79,7 +91,7 @@ enum class error
|
||||
* \param e Error code.
|
||||
* \ingroup any
|
||||
*/
|
||||
boost::system::error_code make_error_code(error e);
|
||||
auto make_error_code(error e) -> boost::system::error_code;
|
||||
|
||||
} // aedis
|
||||
|
||||
@@ -4,15 +4,15 @@
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#ifndef AEDIS_COMMAND_HPP
|
||||
#define AEDIS_COMMAND_HPP
|
||||
#include <aedis/endpoint.hpp>
|
||||
|
||||
#include <boost/utility/string_view.hpp>
|
||||
#include <string>
|
||||
|
||||
namespace aedis {
|
||||
|
||||
bool has_push_response(boost::string_view cmd);
|
||||
auto requires_auth(endpoint const& ep) noexcept -> bool
|
||||
{
|
||||
return !std::empty(ep.username) && !std::empty(ep.password);
|
||||
}
|
||||
|
||||
} // aedis
|
||||
|
||||
#endif // AEDIS_COMMAND_HPP
|
||||
@@ -11,41 +11,45 @@ namespace detail {
|
||||
|
||||
struct error_category_impl : boost::system::error_category {
|
||||
|
||||
char const* name() const noexcept override
|
||||
virtual ~error_category_impl() = default;
|
||||
|
||||
auto name() const noexcept -> char const* override
|
||||
{
|
||||
return "aedis";
|
||||
}
|
||||
|
||||
std::string message(int ev) const override
|
||||
auto message(int ev) const -> std::string override
|
||||
{
|
||||
switch(static_cast<error>(ev)) {
|
||||
case error::resolve_timeout: return "Resolve operation timeout.";
|
||||
case error::connect_timeout: return "Connect operation timeout.";
|
||||
case error::idle_timeout: return "Idle timeout.";
|
||||
case error::exec_timeout: return "Exec timeout.";
|
||||
case error::invalid_data_type: return "Invalid resp3 type.";
|
||||
case error::not_a_number: return "Can't convert string to number.";
|
||||
case error::unexpected_read_size: return "Unexpected read size.";
|
||||
case error::exceeeds_max_nested_depth: return "Exceeds the maximum number of nested responses.";
|
||||
case error::unexpected_bool_value: return "Unexpected bool value.";
|
||||
case error::empty_field: return "Expected field value is empty.";
|
||||
case error::expects_simple_type: return "Expects a simple RESP3 type.";
|
||||
case error::expects_aggregate_type: return "Expects aggregate type.";
|
||||
case error::expects_map_type: return "Expects map type.";
|
||||
case error::expects_set_type: return "Expects set type.";
|
||||
case error::nested_aggregate_unsupported: return "Nested aggregate unsupported.";
|
||||
case error::simple_error: return "Got RESP3 simple-error.";
|
||||
case error::blob_error: return "Got RESP3 blob-error.";
|
||||
case error::expects_resp3_simple_type: return "Expects a resp3 simple type.";
|
||||
case error::expects_resp3_aggregate: return "Expects resp3 aggregate.";
|
||||
case error::expects_resp3_map: return "Expects resp3 map.";
|
||||
case error::expects_resp3_set: return "Expects resp3 set.";
|
||||
case error::nested_aggregate_not_supported: return "Nested aggregate not_supported.";
|
||||
case error::resp3_simple_error: return "Got RESP3 simple-error.";
|
||||
case error::resp3_blob_error: return "Got RESP3 blob-error.";
|
||||
case error::incompatible_size: return "Aggregate container has incompatible size.";
|
||||
case error::not_a_double: return "Not a double.";
|
||||
case error::null: return "Got RESP3 null.";
|
||||
default:
|
||||
BOOST_ASSERT(false);
|
||||
return "Aedis error.";
|
||||
case error::resp3_null: return "Got RESP3 null.";
|
||||
case error::unexpected_server_role: return "Unexpected server role.";
|
||||
case error::ssl_handshake_timeout: return "SSL handshake timeout.";
|
||||
case error::not_connected: return "Not connected.";
|
||||
case error::resp3_handshake_error: return "RESP3 handshake error (HELLO command).";
|
||||
default: BOOST_ASSERT(false); return "Aedis error.";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
boost::system::error_category const& category()
|
||||
auto category() -> boost::system::error_category const&
|
||||
{
|
||||
static error_category_impl instance;
|
||||
return instance;
|
||||
@@ -53,7 +57,7 @@ boost::system::error_category const& category()
|
||||
|
||||
} // detail
|
||||
|
||||
boost::system::error_code make_error_code(error e)
|
||||
auto make_error_code(error e) -> boost::system::error_code
|
||||
{
|
||||
return boost::system::error_code{static_cast<int>(e), detail::category()};
|
||||
}
|
||||
29
include/aedis/operation.hpp
Normal file
29
include/aedis/operation.hpp
Normal file
@@ -0,0 +1,29 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#ifndef AEDIS_OPERATION_HPP
|
||||
#define AEDIS_OPERATION_HPP
|
||||
|
||||
namespace aedis {
|
||||
|
||||
/** \brief Connection operations that can be cancelled.
|
||||
* \ingroup high-level-api
|
||||
*
|
||||
* The operations listed below can be passed to the
|
||||
* `aedis::connection::cancel` member function.
|
||||
*/
|
||||
enum class operation {
|
||||
/// Refers to `connection::async_exec` operations.
|
||||
exec,
|
||||
/// Refers to `connection::async_run` operations.
|
||||
run,
|
||||
/// Refers to `connection::async_receive` operations.
|
||||
receive,
|
||||
};
|
||||
|
||||
} // aedis
|
||||
|
||||
#endif // AEDIS_OPERATION_HPP
|
||||
@@ -20,52 +20,57 @@
|
||||
#include <aedis/resp3/read.hpp>
|
||||
#include <aedis/resp3/request.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
namespace detail {
|
||||
|
||||
#include <boost/asio/yield.hpp>
|
||||
|
||||
namespace aedis::resp3::detail {
|
||||
|
||||
template <
|
||||
class AsyncStream,
|
||||
class Adapter,
|
||||
class DynamicBuffer
|
||||
>
|
||||
struct exec_op {
|
||||
AsyncStream* socket;
|
||||
request const* req;
|
||||
AsyncStream* socket = nullptr;
|
||||
request const* req = nullptr;
|
||||
Adapter adapter;
|
||||
DynamicBuffer dbuf;
|
||||
boost::asio::coroutine coro;
|
||||
DynamicBuffer dbuf{};
|
||||
std::size_t n_cmds = 0;
|
||||
std::size_t size = 0;
|
||||
boost::asio::coroutine coro{};
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, boost::system::error_code ec = {}
|
||||
, std::size_t n = 0)
|
||||
{
|
||||
reenter (coro)
|
||||
reenter (coro) for (;;)
|
||||
{
|
||||
yield
|
||||
boost::asio::async_write(
|
||||
*socket,
|
||||
boost::asio::buffer(req->payload()),
|
||||
std::move(self));
|
||||
if (req) {
|
||||
yield
|
||||
boost::asio::async_write(
|
||||
*socket,
|
||||
boost::asio::buffer(req->payload()),
|
||||
std::move(self));
|
||||
AEDIS_CHECK_OP1();
|
||||
|
||||
if (ec) {
|
||||
self.complete(ec, 0);
|
||||
return;
|
||||
if (n_cmds == 0) {
|
||||
return self.complete({}, n);
|
||||
}
|
||||
|
||||
req = nullptr;
|
||||
}
|
||||
|
||||
yield resp3::async_read(*socket, dbuf, adapter, std::move(self));
|
||||
self.complete(ec, n);
|
||||
AEDIS_CHECK_OP1();
|
||||
|
||||
size += n;
|
||||
if (--n_cmds == 0) {
|
||||
return self.complete(ec, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#include <boost/asio/unyield.hpp>
|
||||
|
||||
} // detail
|
||||
|
||||
template <
|
||||
class AsyncStream,
|
||||
class Adapter,
|
||||
@@ -83,25 +88,22 @@ auto async_exec(
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code, std::size_t)
|
||||
>(detail::exec_op<AsyncStream, Adapter, DynamicBuffer>
|
||||
{&socket, &req, adapter, dbuf}, token, socket);
|
||||
{&socket, &req, adapter, dbuf, req.size()}, token, socket);
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
|
||||
#include <boost/asio/yield.hpp>
|
||||
|
||||
template <
|
||||
class AsyncStream,
|
||||
class Timer,
|
||||
class Adapter,
|
||||
class DynamicBuffer
|
||||
>
|
||||
struct exec_with_timeout_op {
|
||||
AsyncStream* socket;
|
||||
boost::asio::steady_timer* timer;
|
||||
request const* req;
|
||||
AsyncStream* socket = nullptr;
|
||||
Timer* timer = nullptr;
|
||||
request const* req = nullptr;
|
||||
Adapter adapter;
|
||||
DynamicBuffer dbuf;
|
||||
boost::asio::coroutine coro;
|
||||
DynamicBuffer dbuf{};
|
||||
boost::asio::coroutine coro{};
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
@@ -114,50 +116,45 @@ struct exec_with_timeout_op {
|
||||
{
|
||||
yield
|
||||
boost::asio::experimental::make_parallel_group(
|
||||
[this](auto token) { return resp3::async_exec(*socket, *req, adapter, dbuf, token);},
|
||||
[this](auto token) { return detail::async_exec(*socket, *req, adapter, dbuf, token);},
|
||||
[this](auto token) { return timer->async_wait(token);}
|
||||
).async_wait(
|
||||
boost::asio::experimental::wait_for_one(),
|
||||
std::move(self));
|
||||
|
||||
switch (order[0]) {
|
||||
case 0:
|
||||
{
|
||||
if (ec1) {
|
||||
self.complete(ec1, 0);
|
||||
return;
|
||||
}
|
||||
} break;
|
||||
if (is_cancelled(self)) {
|
||||
self.complete(boost::asio::error::operation_aborted, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (order[0]) {
|
||||
case 0: self.complete(ec1, n); break;
|
||||
case 1:
|
||||
{
|
||||
if (!ec2) {
|
||||
self.complete(aedis::error::idle_timeout, 0);
|
||||
return;
|
||||
if (ec2) {
|
||||
self.complete(ec2, 0);
|
||||
} else {
|
||||
self.complete(aedis::error::exec_timeout, 0);
|
||||
}
|
||||
|
||||
} break;
|
||||
|
||||
default: BOOST_ASSERT(false);
|
||||
}
|
||||
|
||||
self.complete({}, n);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#include <boost/asio/unyield.hpp>
|
||||
|
||||
} // detail
|
||||
|
||||
template <
|
||||
class AsyncStream,
|
||||
class Timer,
|
||||
class Adapter,
|
||||
class DynamicBuffer,
|
||||
class CompletionToken = boost::asio::default_completion_token_t<typename AsyncStream::executor_type>
|
||||
>
|
||||
auto async_exec(
|
||||
AsyncStream& socket,
|
||||
boost::asio::steady_timer& timer,
|
||||
Timer& timer,
|
||||
request const& req,
|
||||
Adapter adapter,
|
||||
DynamicBuffer dbuf,
|
||||
@@ -166,11 +163,11 @@ auto async_exec(
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code, std::size_t)
|
||||
>(detail::exec_with_timeout_op<AsyncStream, Adapter, DynamicBuffer>
|
||||
>(detail::exec_with_timeout_op<AsyncStream, Timer, Adapter, DynamicBuffer>
|
||||
{&socket, &timer, &req, adapter, dbuf}, token, socket, timer);
|
||||
}
|
||||
|
||||
} // resp3
|
||||
} // aedis
|
||||
} // aedis::resp3::detail
|
||||
|
||||
#include <boost/asio/unyield.hpp>
|
||||
#endif // AEDIS_RESP3_EXEC_HPP
|
||||
@@ -10,21 +10,16 @@
|
||||
#include <aedis/resp3/detail/parser.hpp>
|
||||
#include <aedis/resp3/type.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
namespace detail {
|
||||
namespace aedis::resp3::detail {
|
||||
|
||||
std::size_t
|
||||
parse_uint(char const* data, std::size_t size, boost::system::error_code& ec)
|
||||
auto parse_uint(char const* data, std::size_t size, boost::system::error_code& ec) -> std::size_t
|
||||
{
|
||||
static constexpr boost::spirit::x3::uint_parser<std::size_t, 10> p{};
|
||||
std::size_t ret;
|
||||
std::size_t ret = 0;
|
||||
if (!parse(data, data + size, p, ret))
|
||||
ec = error::not_a_number;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // detail
|
||||
} // resp3
|
||||
} // aedis
|
||||
} // aedis::resp3::detail
|
||||
@@ -7,6 +7,7 @@
|
||||
#ifndef AEDIS_RESP3_PARSER_HPP
|
||||
#define AEDIS_RESP3_PARSER_HPP
|
||||
|
||||
#include <array>
|
||||
#include <limits>
|
||||
#include <system_error>
|
||||
|
||||
@@ -16,11 +17,9 @@
|
||||
#include <aedis/error.hpp>
|
||||
#include <aedis/resp3/node.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
namespace detail {
|
||||
namespace aedis::resp3::detail {
|
||||
|
||||
std::size_t parse_uint(char const* data, std::size_t size, boost::system::error_code& ec);
|
||||
auto parse_uint(char const* data, std::size_t size, boost::system::error_code& ec) -> std::size_t;
|
||||
|
||||
template <class ResponseAdapter>
|
||||
class parser {
|
||||
@@ -38,7 +37,7 @@ private:
|
||||
// The parser supports up to 5 levels of nested structures. The
|
||||
// first element in the sizes stack is a sentinel and must be
|
||||
// different from 1.
|
||||
std::size_t sizes_[max_embedded_depth + 1] = {1};
|
||||
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)();
|
||||
@@ -48,15 +47,15 @@ private:
|
||||
type bulk_ = type::invalid;
|
||||
|
||||
public:
|
||||
parser(ResponseAdapter adapter)
|
||||
explicit parser(ResponseAdapter adapter)
|
||||
: adapter_{adapter}
|
||||
{
|
||||
sizes_[0] = 2; // The sentinel must be more than 1.
|
||||
}
|
||||
|
||||
// Returns the number of bytes that have been consumed.
|
||||
std::size_t
|
||||
consume(char const* data, std::size_t n, boost::system::error_code& ec)
|
||||
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;
|
||||
@@ -99,7 +98,7 @@ public:
|
||||
case type::verbatim_string:
|
||||
case type::blob_string:
|
||||
{
|
||||
if (*(data + 1) == '?') {
|
||||
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
|
||||
@@ -121,7 +120,7 @@ public:
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (*(data + 1) != 'f' && *(data + 1) != 't') {
|
||||
if (data[1] != 'f' && data[1] != 't') {
|
||||
ec = error::unexpected_bool_value;
|
||||
return 0;
|
||||
}
|
||||
@@ -150,7 +149,7 @@ public:
|
||||
case type::simple_error:
|
||||
case type::simple_string:
|
||||
{
|
||||
adapter_({t, 1, depth_, {data + 1, n - 3}}, ec);
|
||||
adapter_({t, 1, depth_, {&data[1], n - 3}}, ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
|
||||
@@ -208,19 +207,17 @@ public:
|
||||
}
|
||||
|
||||
// Returns true when the parser is done with the current message.
|
||||
auto done() const noexcept
|
||||
[[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.
|
||||
auto bulk() const noexcept { return bulk_; }
|
||||
[[nodiscard]] auto bulk() const noexcept { return bulk_; }
|
||||
|
||||
// The length expected in the the next bulk.
|
||||
auto bulk_length() const noexcept { return bulk_length_; }
|
||||
[[nodiscard]] auto bulk_length() const noexcept { return bulk_length_; }
|
||||
};
|
||||
|
||||
} // detail
|
||||
} // resp3
|
||||
} // aedis
|
||||
} // detail::resp3::aedis
|
||||
|
||||
#endif // AEDIS_RESP3_PARSER_HPP
|
||||
@@ -13,17 +13,44 @@
|
||||
#include <boost/asio/coroutine.hpp>
|
||||
#include <boost/core/ignore_unused.hpp>
|
||||
#include <boost/utility/string_view.hpp>
|
||||
|
||||
#include <aedis/resp3/detail/parser.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
namespace detail {
|
||||
|
||||
#include <boost/asio/yield.hpp>
|
||||
|
||||
namespace aedis::detail
|
||||
{
|
||||
template <class T>
|
||||
auto is_cancelled(T const& self)
|
||||
{
|
||||
return self.get_cancellation_state().cancelled() != boost::asio::cancellation_type_t::none;
|
||||
}
|
||||
}
|
||||
|
||||
#define AEDIS_CHECK_OP0(X)\
|
||||
if (ec || aedis::detail::is_cancelled(self)) {\
|
||||
X;\
|
||||
self.complete(!!ec ? ec : boost::asio::error::operation_aborted);\
|
||||
return;\
|
||||
}
|
||||
|
||||
#define AEDIS_CHECK_OP1(X)\
|
||||
if (ec || aedis::detail::is_cancelled(self)) {\
|
||||
X;\
|
||||
self.complete(!!ec ? ec : boost::asio::error::operation_aborted, {});\
|
||||
return;\
|
||||
}
|
||||
|
||||
namespace aedis::resp3::detail {
|
||||
|
||||
struct ignore_response {
|
||||
void operator()(node<boost::string_view>, boost::system::error_code&) { }
|
||||
void operator()(node<boost::string_view> nd, boost::system::error_code& ec)
|
||||
{
|
||||
switch (nd.data_type) {
|
||||
case resp3::type::simple_error: ec = error::resp3_simple_error; return;
|
||||
case resp3::type::blob_error: ec = error::resp3_blob_error; return;
|
||||
default: return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <
|
||||
@@ -35,16 +62,15 @@ private:
|
||||
AsyncReadStream& stream_;
|
||||
DynamicBuffer buf_;
|
||||
parser<ResponseAdapter> parser_;
|
||||
std::size_t consumed_;
|
||||
std::size_t buffer_size_;
|
||||
boost::asio::coroutine coro_;
|
||||
std::size_t consumed_ = 0;
|
||||
std::size_t buffer_size_ = 0;
|
||||
boost::asio::coroutine coro_{};
|
||||
|
||||
public:
|
||||
parse_op(AsyncReadStream& stream, DynamicBuffer buf, ResponseAdapter adapter)
|
||||
: stream_ {stream}
|
||||
, buf_ {buf}
|
||||
, parser_ {adapter}
|
||||
, consumed_{0}
|
||||
, buf_ {std::move(buf)}
|
||||
, parser_ {std::move(adapter)}
|
||||
{ }
|
||||
|
||||
template <class Self>
|
||||
@@ -56,11 +82,7 @@ public:
|
||||
if (parser_.bulk() == type::invalid) {
|
||||
yield
|
||||
boost::asio::async_read_until(stream_, buf_, "\r\n", std::move(self));
|
||||
|
||||
if (ec) {
|
||||
self.complete(ec, 0);
|
||||
return;
|
||||
}
|
||||
AEDIS_CHECK_OP1();
|
||||
} else {
|
||||
// On a bulk read we can't read until delimiter since the
|
||||
// payload may contain the delimiter itself so we have to
|
||||
@@ -79,18 +101,14 @@ public:
|
||||
buf_.data(buffer_size_, parser_.bulk_length() + 2 - buffer_size_),
|
||||
boost::asio::transfer_all(),
|
||||
std::move(self));
|
||||
|
||||
if (ec) {
|
||||
self.complete(ec, 0);
|
||||
return;
|
||||
}
|
||||
AEDIS_CHECK_OP1();
|
||||
}
|
||||
|
||||
n = parser_.bulk_length() + 2;
|
||||
BOOST_ASSERT(buf_.size() >= n);
|
||||
}
|
||||
|
||||
n = parser_.consume((char const*)buf_.data(0, n).data(), n, ec);
|
||||
n = parser_.consume(static_cast<char const*>(buf_.data(0, n).data()), n, ec);
|
||||
if (ec) {
|
||||
self.complete(ec, 0);
|
||||
return;
|
||||
@@ -106,10 +124,7 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
} // aedis::resp3::detail
|
||||
|
||||
#include <boost/asio/unyield.hpp>
|
||||
|
||||
} // detail
|
||||
} // resp3
|
||||
} // aedis
|
||||
|
||||
#endif // AEDIS_RESP3_READ_OPS_HPP
|
||||
@@ -4,11 +4,11 @@
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <aedis/command.hpp>
|
||||
#include <aedis/resp3/request.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace aedis::resp3::detail {
|
||||
|
||||
bool has_push_response(boost::string_view cmd)
|
||||
auto has_push_response(boost::string_view cmd) -> bool
|
||||
{
|
||||
if (cmd == "SUBSCRIBE") return true;
|
||||
if (cmd == "PSUBSCRIBE") return true;
|
||||
@@ -16,4 +16,4 @@ bool has_push_response(boost::string_view cmd)
|
||||
return false;
|
||||
}
|
||||
|
||||
} // aedis
|
||||
} // aedis::resp3::detail
|
||||
@@ -7,41 +7,38 @@
|
||||
#include <boost/assert.hpp>
|
||||
#include <aedis/resp3/type.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
namespace aedis::resp3 {
|
||||
|
||||
char const* to_string(type t)
|
||||
auto to_string(type t) -> char const*
|
||||
{
|
||||
static char const* table[] =
|
||||
{ "array"
|
||||
, "push"
|
||||
, "set"
|
||||
, "map"
|
||||
, "attribute"
|
||||
, "simple_string"
|
||||
, "simple_error"
|
||||
, "number"
|
||||
, "doublean"
|
||||
, "boolean"
|
||||
, "big_number"
|
||||
, "null"
|
||||
, "blob_error"
|
||||
, "verbatim_string"
|
||||
, "blob_string"
|
||||
, "streamed_string_part"
|
||||
, "invalid"
|
||||
};
|
||||
|
||||
return table[static_cast<int>(t)];
|
||||
switch (t) {
|
||||
case type::array: return "array";
|
||||
case type::push: return "push";
|
||||
case type::set: return "set";
|
||||
case type::map: return "map";
|
||||
case type::attribute: return "attribute";
|
||||
case type::simple_string: return "simple_string";
|
||||
case type::simple_error: return "simple_error";
|
||||
case type::number: return "number";
|
||||
case type::doublean: return "doublean";
|
||||
case type::boolean: return "boolean";
|
||||
case type::big_number: return "big_number";
|
||||
case type::null: return "null";
|
||||
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_part: return "streamed_string_part";
|
||||
default: return "invalid";
|
||||
}
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, type t)
|
||||
auto operator<<(std::ostream& os, type t) -> std::ostream&
|
||||
{
|
||||
os << to_string(t);
|
||||
return os;
|
||||
}
|
||||
|
||||
bool is_aggregate(type t)
|
||||
auto is_aggregate(type t) -> bool
|
||||
{
|
||||
switch (t) {
|
||||
case type::array:
|
||||
@@ -53,7 +50,7 @@ bool is_aggregate(type t)
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t element_multiplicity(type t)
|
||||
auto element_multiplicity(type t) -> std::size_t
|
||||
{
|
||||
switch (t) {
|
||||
case type::map:
|
||||
@@ -62,7 +59,7 @@ std::size_t element_multiplicity(type t)
|
||||
}
|
||||
}
|
||||
|
||||
char to_code(type t)
|
||||
auto to_code(type t) -> char
|
||||
{
|
||||
switch (t) {
|
||||
case type::blob_error: return '!';
|
||||
@@ -82,13 +79,11 @@ char to_code(type t)
|
||||
case type::attribute: return '|';
|
||||
case type::map: return '%';
|
||||
|
||||
default:
|
||||
BOOST_ASSERT(false);
|
||||
return ' ';
|
||||
default: BOOST_ASSERT(false); return ' ';
|
||||
}
|
||||
}
|
||||
|
||||
type to_type(char c)
|
||||
auto to_type(char c) -> type
|
||||
{
|
||||
switch (c) {
|
||||
case '!': return type::blob_error;
|
||||
@@ -111,5 +106,4 @@ type to_type(char c)
|
||||
}
|
||||
}
|
||||
|
||||
} // resp3
|
||||
} // aedis
|
||||
} // aedis::resp3
|
||||
@@ -12,11 +12,10 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
namespace aedis::resp3 {
|
||||
|
||||
/** \brief A node in the response tree.
|
||||
* \ingroup any
|
||||
* \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).
|
||||
@@ -27,25 +26,25 @@ namespace resp3 {
|
||||
template <class String>
|
||||
struct node {
|
||||
/// The RESP3 type of the data in this node.
|
||||
resp3::type data_type;
|
||||
type data_type = type::invalid;
|
||||
|
||||
/// The number of elements of an aggregate.
|
||||
std::size_t aggregate_size;
|
||||
std::size_t aggregate_size{};
|
||||
|
||||
/// The depth of this node in the response tree.
|
||||
std::size_t depth;
|
||||
std::size_t depth{};
|
||||
|
||||
/// The actual data. For aggregate types this is usually empty.
|
||||
String value;
|
||||
String value{};
|
||||
};
|
||||
|
||||
/** \brief Converts the node to a string.
|
||||
* \ingroup any
|
||||
/** @brief Converts the node to a string.
|
||||
* @relates node
|
||||
*
|
||||
* \param in The node object.
|
||||
* @param in The node object.
|
||||
*/
|
||||
template <class String>
|
||||
std::string to_string(node<String> const& in)
|
||||
auto to_string(node<String> const& in)
|
||||
{
|
||||
std::string out;
|
||||
out += std::to_string(in.depth);
|
||||
@@ -60,11 +59,14 @@ std::string to_string(node<String> const& in)
|
||||
return out;
|
||||
}
|
||||
|
||||
/** \brief Compares a node for equality.
|
||||
* \ingroup any
|
||||
/** @brief Compares a node for equality.
|
||||
* @relates node
|
||||
*
|
||||
* @param a Left hand side node object.
|
||||
* @param b Right hand side node object.
|
||||
*/
|
||||
template <class String>
|
||||
bool operator==(node<String> const& a, node<String> const& b)
|
||||
auto operator==(node<String> const& a, node<String> const& b)
|
||||
{
|
||||
return a.aggregate_size == b.aggregate_size
|
||||
&& a.depth == b.depth
|
||||
@@ -72,19 +74,21 @@ bool operator==(node<String> const& a, node<String> const& b)
|
||||
&& a.value == b.value;
|
||||
};
|
||||
|
||||
/** \brief Writes the node string to the stream.
|
||||
* \ingroup any
|
||||
/** @brief Writes the node string to the stream.
|
||||
* @relates node
|
||||
*
|
||||
* NOTE: Binary data is not converted to text.
|
||||
* @param os Output stream.
|
||||
* @param node Node object.
|
||||
*
|
||||
* \remark Binary data is not converted to text.
|
||||
*/
|
||||
template <class String>
|
||||
std::ostream& operator<<(std::ostream& os, node<String> const& o)
|
||||
auto operator<<(std::ostream& os, node<String> const& node) -> std::ostream&
|
||||
{
|
||||
os << to_string(o);
|
||||
os << to_string(node);
|
||||
return os;
|
||||
}
|
||||
|
||||
} // adapter
|
||||
} // aedis
|
||||
} // aedis::resp3
|
||||
|
||||
#endif // AEDIS_RESP3_NODE_HPP
|
||||
@@ -15,12 +15,10 @@
|
||||
#include <boost/asio/compose.hpp>
|
||||
#include <boost/asio/async_result.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
namespace aedis::resp3 {
|
||||
|
||||
/** \internal
|
||||
* \brief Reads a complete response to a command sychronously.
|
||||
* \ingroup any
|
||||
/** \brief Reads a complete response to a command sychronously.
|
||||
* \ingroup low-level-api
|
||||
*
|
||||
* This function reads a complete response to a command or a
|
||||
* server push synchronously. For example
|
||||
@@ -41,7 +39,7 @@ namespace resp3 {
|
||||
*
|
||||
* \param stream The stream from which to read e.g. a tcp socket.
|
||||
* \param buf Dynamic buffer (version 2).
|
||||
* \param adapter The response adapter, see more on \ref low-level-responses.
|
||||
* \param adapter The response adapter.
|
||||
* \param ec If an error occurs, it will be assigned to this paramter.
|
||||
* \returns The number of bytes that have been consumed from the dynamic buffer.
|
||||
*
|
||||
@@ -54,12 +52,12 @@ template <
|
||||
class DynamicBuffer,
|
||||
class ResponseAdapter
|
||||
>
|
||||
std::size_t
|
||||
auto
|
||||
read(
|
||||
SyncReadStream& stream,
|
||||
DynamicBuffer buf,
|
||||
ResponseAdapter adapter,
|
||||
boost::system::error_code& ec)
|
||||
boost::system::error_code& ec) -> std::size_t
|
||||
{
|
||||
detail::parser<ResponseAdapter> p {adapter};
|
||||
std::size_t n = 0;
|
||||
@@ -70,10 +68,6 @@ read(
|
||||
if (ec)
|
||||
return 0;
|
||||
|
||||
if (n < 3) {
|
||||
ec = error::unexpected_read_size;
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
auto const s = buf.size();
|
||||
auto const l = p.bulk_length();
|
||||
@@ -83,15 +77,10 @@ read(
|
||||
n = boost::asio::read(stream, buf.data(s, to_read), ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
|
||||
if (n < to_read) {
|
||||
ec = error::unexpected_read_size;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto const* data = (char const*) buf.data(0, n).data();
|
||||
auto const* data = static_cast<char const*>(buf.data(0, n).data());
|
||||
n = p.consume(data, n, ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
@@ -103,9 +92,8 @@ read(
|
||||
return consumed;
|
||||
}
|
||||
|
||||
/** \internal
|
||||
* \brief Reads a complete response to a command sychronously.
|
||||
* \ingroup any
|
||||
/** \brief Reads a complete response to a command sychronously.
|
||||
* \ingroup low-level-api
|
||||
*
|
||||
* Same as the error_code overload but throws on error.
|
||||
*/
|
||||
@@ -113,7 +101,7 @@ template<
|
||||
class SyncReadStream,
|
||||
class DynamicBuffer,
|
||||
class ResponseAdapter = detail::ignore_response>
|
||||
std::size_t
|
||||
auto
|
||||
read(
|
||||
SyncReadStream& stream,
|
||||
DynamicBuffer buf,
|
||||
@@ -128,9 +116,8 @@ read(
|
||||
return n;
|
||||
}
|
||||
|
||||
/** \internal
|
||||
* \brief Reads a complete response to a Redis command asynchronously.
|
||||
* \ingroup any
|
||||
/** \brief Reads a complete response to a Redis command asynchronously.
|
||||
* \ingroup low-level-api
|
||||
*
|
||||
* This function reads a complete response to a command or a
|
||||
* server push asynchronously. For example
|
||||
@@ -152,7 +139,7 @@ read(
|
||||
*
|
||||
* \param stream The stream from which to read e.g. a tcp socket.
|
||||
* \param buffer Dynamic buffer (version 2).
|
||||
* \param adapter The response adapter, see more on \ref low-level-responses.
|
||||
* \param adapter The response adapter.
|
||||
* \param token The completion token.
|
||||
*
|
||||
* The completion handler will receive as a parameter the total
|
||||
@@ -188,7 +175,6 @@ auto async_read(
|
||||
stream);
|
||||
}
|
||||
|
||||
} // resp3
|
||||
} // aedis
|
||||
} // aedis::resp3
|
||||
|
||||
#endif // AEDIS_RESP3_READ_HPP
|
||||
395
include/aedis/resp3/request.hpp
Normal file
395
include/aedis/resp3/request.hpp
Normal file
@@ -0,0 +1,395 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#ifndef AEDIS_RESP3_REQUEST_HPP
|
||||
#define AEDIS_RESP3_REQUEST_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.
|
||||
//
|
||||
// NOTE: For some commands like hset it would be a good idea to assert
|
||||
// the value type is a pair.
|
||||
|
||||
namespace aedis::resp3 {
|
||||
|
||||
constexpr char const* separator = "\r\n";
|
||||
|
||||
/** @brief Adds a bulk to the request.
|
||||
* @relates request
|
||||
*
|
||||
* This function is useful in serialization of your own data
|
||||
* structures in a request. For example
|
||||
*
|
||||
* @code
|
||||
* void to_bulk(std::string& to, mystruct const& obj)
|
||||
* {
|
||||
* auto const str = // Convert obj to a string.
|
||||
* resp3::to_bulk(to, str);
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* @param to Storage on which data will be copied into.
|
||||
* @param data Data that will be serialized and stored in @c to.
|
||||
*
|
||||
* See more in @ref serialization.
|
||||
*/
|
||||
template <class Request>
|
||||
void to_bulk(Request& to, boost::string_view data)
|
||||
{
|
||||
auto const str = std::to_string(data.size());
|
||||
|
||||
to += to_code(type::blob_string);
|
||||
to.append(std::cbegin(str), std::cend(str));
|
||||
to += separator;
|
||||
to.append(std::cbegin(data), std::cend(data));
|
||||
to += separator;
|
||||
}
|
||||
|
||||
template <class Request, class T, typename = typename std::enable_if<std::is_integral<T>::value>::type>
|
||||
void to_bulk(Request& to, T n)
|
||||
{
|
||||
auto const s = std::to_string(n);
|
||||
to_bulk(to, boost::string_view{s});
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
|
||||
auto has_push_response(boost::string_view cmd) -> bool;
|
||||
|
||||
template <class T>
|
||||
struct add_bulk_impl {
|
||||
template <class Request>
|
||||
static void add(Request& to, T const& from)
|
||||
{
|
||||
using namespace aedis::resp3;
|
||||
to_bulk(to, from);
|
||||
}
|
||||
};
|
||||
|
||||
template <class U, class V>
|
||||
struct add_bulk_impl<std::pair<U, V>> {
|
||||
template <class Request>
|
||||
static void add(Request& to, std::pair<U, V> const& from)
|
||||
{
|
||||
using namespace aedis::resp3;
|
||||
to_bulk(to, from.first);
|
||||
to_bulk(to, from.second);
|
||||
}
|
||||
};
|
||||
|
||||
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)
|
||||
{
|
||||
auto const str = std::to_string(size);
|
||||
|
||||
to += to_code(t);
|
||||
to.append(std::cbegin(str), std::cend(str));
|
||||
to += separator;
|
||||
}
|
||||
|
||||
template <class Request, class T>
|
||||
void add_bulk(Request& to, T const& data)
|
||||
{
|
||||
detail::add_bulk_impl<T>::add(to, data);
|
||||
}
|
||||
|
||||
template <class>
|
||||
struct bulk_counter;
|
||||
|
||||
template <class>
|
||||
struct bulk_counter {
|
||||
static constexpr auto size = 1U;
|
||||
};
|
||||
|
||||
template <class T, class U>
|
||||
struct bulk_counter<std::pair<T, U>> {
|
||||
static constexpr auto size = 2U;
|
||||
};
|
||||
|
||||
template <class Request>
|
||||
void add_blob(Request& to, boost::string_view blob)
|
||||
{
|
||||
to.append(std::cbegin(blob), std::cend(blob));
|
||||
to += separator;
|
||||
}
|
||||
|
||||
template <class Request>
|
||||
void add_separator(Request& to)
|
||||
{
|
||||
to += separator;
|
||||
}
|
||||
} // detail
|
||||
|
||||
/** \brief Creates Redis requests.
|
||||
* \ingroup high-level-api
|
||||
*
|
||||
* A request is composed of one or more Redis commands and is
|
||||
* referred to in the redis documentation as a pipeline, see
|
||||
* https://redis.io/topics/pipelining. For example
|
||||
*
|
||||
* @code
|
||||
* request r;
|
||||
* r.push("HELLO", 3);
|
||||
* r.push("FLUSHALL");
|
||||
* r.push("PING");
|
||||
* r.push("PING", "key");
|
||||
* r.push("QUIT");
|
||||
* @endcode
|
||||
*
|
||||
* \remarks
|
||||
*
|
||||
* \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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
bool cancel_on_connection_lost = false;
|
||||
|
||||
/** \brief If true this request will be coalesced with other requests,
|
||||
* see https://redis.io/topics/pipelining. If false, this
|
||||
* request will be sent individually.
|
||||
*/
|
||||
bool coalesce = true;
|
||||
|
||||
/** \brief If set to true, requests started with
|
||||
* `aedis::connection::async_exec` will fail if the call happens
|
||||
* before the connection with Redis was stablished.
|
||||
*/
|
||||
bool cancel_if_not_connected = false;
|
||||
|
||||
/** \brief If true, the implementation will resend this
|
||||
* request if it remained unresponded when
|
||||
* `aedis::connection::async_run` completed. Has effect only if
|
||||
* cancel_on_connection_lost is true.
|
||||
*/
|
||||
bool retry = true;
|
||||
};
|
||||
|
||||
/** \brief Constructor
|
||||
*
|
||||
* \param cfg Configuration options.
|
||||
* \param resource Memory resource.
|
||||
*/
|
||||
explicit
|
||||
request(config cfg = config{false, true, false, true},
|
||||
std::pmr::memory_resource* resource = std::pmr::get_default_resource())
|
||||
: payload_(resource), cfg_{cfg}
|
||||
{}
|
||||
|
||||
|
||||
//// Returns the number of commands contained in this request.
|
||||
[[nodiscard]] auto size() const noexcept -> std::size_t { return commands_;};
|
||||
|
||||
// Returns the request payload.
|
||||
[[nodiscard]] auto payload() const noexcept -> auto const& { return payload_;}
|
||||
|
||||
/// Clears the request preserving allocated memory.
|
||||
void clear()
|
||||
{
|
||||
payload_.clear();
|
||||
commands_ = 0;
|
||||
}
|
||||
|
||||
/** @brief Appends a new command to the end of the request.
|
||||
*
|
||||
* For example
|
||||
*
|
||||
* \code
|
||||
* request req;
|
||||
* req.push("SET", "key", "some string", "EX", "2");
|
||||
* \endcode
|
||||
*
|
||||
* will add the \c set command with value "some string" and an
|
||||
* expiration of 2 seconds.
|
||||
*
|
||||
* \param cmd The command e.g redis or sentinel command.
|
||||
* \param args Command arguments.
|
||||
*/
|
||||
template <class... Ts>
|
||||
void push(boost::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...));
|
||||
|
||||
if (!detail::has_push_response(cmd))
|
||||
++commands_;
|
||||
}
|
||||
|
||||
/** @brief Appends a new command to the end of the request.
|
||||
*
|
||||
* This overload is useful for commands that have a key and have a
|
||||
* dynamic range of arguments. For example
|
||||
*
|
||||
* @code
|
||||
* std::map<std::string, std::string> map
|
||||
* { {"key1", "value1"}
|
||||
* , {"key2", "value2"}
|
||||
* , {"key3", "value3"}
|
||||
* };
|
||||
*
|
||||
* request req;
|
||||
* req.push_range("HSET", "key", std::cbegin(map), std::cend(map));
|
||||
* @endcode
|
||||
*
|
||||
* \param cmd The command e.g. Redis or Sentinel command.
|
||||
* \param key The command key.
|
||||
* \param begin Iterator to the begin of the range.
|
||||
* \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,
|
||||
typename std::iterator_traits<ForwardIterator>::value_type * = nullptr)
|
||||
{
|
||||
using value_type = typename std::iterator_traits<ForwardIterator>::value_type;
|
||||
using resp3::type;
|
||||
|
||||
if (begin == end)
|
||||
return;
|
||||
|
||||
auto constexpr size = detail::bulk_counter<value_type>::size;
|
||||
auto const distance = std::distance(begin, end);
|
||||
detail::add_header(payload_, type::array, 2 + size * distance);
|
||||
detail::add_bulk(payload_, cmd);
|
||||
detail::add_bulk(payload_, key);
|
||||
|
||||
for (; begin != end; ++begin)
|
||||
detail::add_bulk(payload_, *begin);
|
||||
|
||||
if (!detail::has_push_response(cmd))
|
||||
++commands_;
|
||||
}
|
||||
|
||||
/** @brief Appends a new command to the end of the request.
|
||||
*
|
||||
* This overload is useful for commands that have a dynamic number
|
||||
* of arguments and don't have a key. For example
|
||||
*
|
||||
* \code
|
||||
* std::set<std::string> channels
|
||||
* { "channel1" , "channel2" , "channel3" }
|
||||
*
|
||||
* request req;
|
||||
* req.push("SUBSCRIBE", std::cbegin(channels), std::cend(channels));
|
||||
* \endcode
|
||||
*
|
||||
* \param cmd The Redis command
|
||||
* \param begin Iterator to the begin of the range.
|
||||
* \param end Iterator to the end of the range.
|
||||
*/
|
||||
template <class ForwardIterator>
|
||||
void push_range(boost::string_view cmd, ForwardIterator begin, ForwardIterator end,
|
||||
typename std::iterator_traits<ForwardIterator>::value_type * = nullptr)
|
||||
{
|
||||
using value_type = typename std::iterator_traits<ForwardIterator>::value_type;
|
||||
using resp3::type;
|
||||
|
||||
if (begin == end)
|
||||
return;
|
||||
|
||||
auto constexpr size = detail::bulk_counter<value_type>::size;
|
||||
auto const distance = std::distance(begin, end);
|
||||
detail::add_header(payload_, type::array, 1 + size * distance);
|
||||
detail::add_bulk(payload_, cmd);
|
||||
|
||||
for (; begin != end; ++begin)
|
||||
detail::add_bulk(payload_, *begin);
|
||||
|
||||
if (!detail::has_push_response(cmd))
|
||||
++commands_;
|
||||
}
|
||||
|
||||
/** @brief Appends a new command to the end of the request.
|
||||
*
|
||||
* Equivalent to the overload taking a range (i.e. send_range2).
|
||||
*
|
||||
* \param cmd Redis command.
|
||||
* \param key Redis key.
|
||||
* \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,
|
||||
decltype(std::begin(range)) * = nullptr)
|
||||
{
|
||||
using std::begin;
|
||||
using std::end;
|
||||
push_range(cmd, key, begin(range), end(range));
|
||||
}
|
||||
|
||||
/** @brief Appends a new command to the end of the request.
|
||||
*
|
||||
* Equivalent to the overload taking a range (i.e. send_range2).
|
||||
*
|
||||
* \param cmd Redis command.
|
||||
* \param range Range to send e.g. and \c std::map.
|
||||
*/
|
||||
template <class Range>
|
||||
void push_range(boost::string_view cmd, Range const& range,
|
||||
decltype(std::begin(range)) * = nullptr)
|
||||
{
|
||||
using std::begin;
|
||||
using std::end;
|
||||
push_range(cmd, begin(range), end(range));
|
||||
}
|
||||
|
||||
/// Calls std::string::reserve on the internal storage.
|
||||
void reserve(std::size_t new_cap = 0)
|
||||
{ payload_.reserve(new_cap); }
|
||||
|
||||
/// Returns a const reference to the config object.
|
||||
[[nodiscard]] auto get_config() const noexcept -> auto const& {return cfg_; }
|
||||
|
||||
/// Returns a reference to the config object.
|
||||
[[nodiscard]] auto get_config() noexcept -> auto& {return cfg_; }
|
||||
|
||||
private:
|
||||
std::pmr::string payload_;
|
||||
std::size_t commands_ = 0;
|
||||
config cfg_;
|
||||
};
|
||||
|
||||
} // aedis::resp3
|
||||
|
||||
#endif // AEDIS_RESP3_SERIALIZER_HPP
|
||||
@@ -11,11 +11,10 @@
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
namespace aedis::resp3 {
|
||||
|
||||
/** \brief RESP3 data types.
|
||||
\ingroup any
|
||||
\ingroup high-level-api
|
||||
|
||||
The RESP3 specification can be found at https://github.com/redis/redis-specifications/blob/master/protocol/RESP3.md.
|
||||
*/
|
||||
@@ -57,33 +56,32 @@ enum class type
|
||||
};
|
||||
|
||||
/** \brief Converts the data type to a string.
|
||||
* \ingroup any
|
||||
* \ingroup high-level-api
|
||||
* \param t RESP3 type.
|
||||
*/
|
||||
char const* to_string(type t);
|
||||
auto to_string(type t) -> char const*;
|
||||
|
||||
/** \brief Writes the type to the output stream.
|
||||
* \ingroup any
|
||||
* \ingroup high-level-api
|
||||
* \param os Output stream.
|
||||
* \param t RESP3 type.
|
||||
*/
|
||||
std::ostream& operator<<(std::ostream& os, type t);
|
||||
auto operator<<(std::ostream& os, type t) -> std::ostream&;
|
||||
|
||||
/* Checks whether the data type is an aggregate.
|
||||
*/
|
||||
bool is_aggregate(type t);
|
||||
auto is_aggregate(type t) -> bool;
|
||||
|
||||
// For map and attribute data types this function returns 2. All
|
||||
// other types have value 1.
|
||||
std::size_t element_multiplicity(type t);
|
||||
auto element_multiplicity(type t) -> std::size_t;
|
||||
|
||||
// Returns the wire code of a given type.
|
||||
char to_code(type t);
|
||||
auto to_code(type t) -> char;
|
||||
|
||||
// Converts a wire-format RESP3 type (char) to a resp3 type.
|
||||
type to_type(char c);
|
||||
auto to_type(char c) -> type;
|
||||
|
||||
} // resp3
|
||||
} // aedis
|
||||
} // aedis::resp3
|
||||
|
||||
#endif // AEDIS_RESP3_TYPE_HPP
|
||||
@@ -9,14 +9,19 @@
|
||||
|
||||
#include <boost/asio/write.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
namespace aedis::resp3 {
|
||||
|
||||
/** \brief Writes a request synchronously.
|
||||
* \ingroup low-level-api
|
||||
*
|
||||
* \param stream Stream to write the request to.
|
||||
* \param req Request to write.
|
||||
*/
|
||||
template<
|
||||
class SyncWriteStream,
|
||||
class Request
|
||||
>
|
||||
std::size_t write(SyncWriteStream& stream, Request const& req)
|
||||
auto write(SyncWriteStream& stream, Request const& req)
|
||||
{
|
||||
return boost::asio::write(stream, boost::asio::buffer(req.payload()));
|
||||
}
|
||||
@@ -25,7 +30,7 @@ template<
|
||||
class SyncWriteStream,
|
||||
class Request
|
||||
>
|
||||
std::size_t write(
|
||||
auto write(
|
||||
SyncWriteStream& stream,
|
||||
Request const& req,
|
||||
boost::system::error_code& ec)
|
||||
@@ -33,6 +38,13 @@ std::size_t write(
|
||||
return boost::asio::write(stream, boost::asio::buffer(req.payload()), ec);
|
||||
}
|
||||
|
||||
/** \brief Writes a request asynchronously.
|
||||
* \ingroup low-level-api
|
||||
*
|
||||
* \param stream Stream to write the request to.
|
||||
* \param req Request to write.
|
||||
* \param token Asio completion token.
|
||||
*/
|
||||
template<
|
||||
class AsyncWriteStream,
|
||||
class Request,
|
||||
@@ -47,7 +59,6 @@ auto async_write(
|
||||
return boost::asio::async_write(stream, boost::asio::buffer(req.payload()), token);
|
||||
}
|
||||
|
||||
} // resp3
|
||||
} // aedis
|
||||
} // aedis::resp3
|
||||
|
||||
#endif // AEDIS_RESP3_WRITE_HPP
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
#include <aedis/impl/error.ipp>
|
||||
#include <aedis/impl/command.ipp>
|
||||
#include <aedis/impl/endpoint.ipp>
|
||||
#include <aedis/resp3/impl/request.ipp>
|
||||
#include <aedis/resp3/impl/type.ipp>
|
||||
#include <aedis/resp3/detail/impl/parser.ipp>
|
||||
186
include/aedis/ssl/connection.hpp
Normal file
186
include/aedis/ssl/connection.hpp
Normal file
@@ -0,0 +1,186 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#ifndef AEDIS_SSL_CONNECTION_HPP
|
||||
#define AEDIS_SSL_CONNECTION_HPP
|
||||
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <aedis/detail/connection_base.hpp>
|
||||
#include <aedis/ssl/detail/connection_ops.hpp>
|
||||
|
||||
namespace aedis::ssl {
|
||||
|
||||
template <class>
|
||||
class connection;
|
||||
|
||||
/** \brief A SSL 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.
|
||||
*
|
||||
* @tparam AsyncReadWriteStream A stream that supports reading and
|
||||
* writing.
|
||||
*
|
||||
*/
|
||||
template <class AsyncReadWriteStream>
|
||||
class connection<boost::asio::ssl::stream<AsyncReadWriteStream>> :
|
||||
private aedis::detail::connection_base<
|
||||
typename boost::asio::ssl::stream<AsyncReadWriteStream>::executor_type,
|
||||
connection<boost::asio::ssl::stream<AsyncReadWriteStream>>> {
|
||||
public:
|
||||
/// Type of the next layer
|
||||
using next_layer_type = boost::asio::ssl::stream<AsyncReadWriteStream>;
|
||||
|
||||
/// Executor type.
|
||||
using executor_type = typename next_layer_type::executor_type;
|
||||
using base_type = aedis::detail::connection_base<executor_type, connection<boost::asio::ssl::stream<AsyncReadWriteStream>>>;
|
||||
|
||||
/** \brief Connection configuration parameters.
|
||||
*/
|
||||
struct timeouts {
|
||||
/// Timeout of the resolve operation.
|
||||
std::chrono::steady_clock::duration resolve_timeout = std::chrono::seconds{10};
|
||||
|
||||
/// Timeout of the connect operation.
|
||||
std::chrono::steady_clock::duration connect_timeout = std::chrono::seconds{10};
|
||||
|
||||
/// Timeout of the ssl handshake operation.
|
||||
std::chrono::steady_clock::duration handshake_timeout = std::chrono::seconds{10};
|
||||
|
||||
/// Timeout of the resp3 handshake operation.
|
||||
std::chrono::steady_clock::duration resp3_handshake_timeout = std::chrono::seconds{2};
|
||||
|
||||
/// Time interval of ping operations.
|
||||
std::chrono::steady_clock::duration ping_interval = std::chrono::seconds{1};
|
||||
};
|
||||
|
||||
/// Constructor
|
||||
explicit
|
||||
connection(
|
||||
executor_type ex,
|
||||
boost::asio::ssl::context& ctx,
|
||||
std::pmr::memory_resource* resource = std::pmr::get_default_resource())
|
||||
: base_type{ex, resource}
|
||||
, stream_{ex, ctx}
|
||||
{
|
||||
}
|
||||
|
||||
/// Constructor
|
||||
explicit
|
||||
connection(
|
||||
boost::asio::io_context& ioc,
|
||||
boost::asio::ssl::context& ctx,
|
||||
std::pmr::memory_resource* resource = std::pmr::get_default_resource())
|
||||
: connection(ioc.get_executor(), ctx, resource)
|
||||
{ }
|
||||
|
||||
/// Returns the associated executor.
|
||||
auto get_executor() {return stream_.get_executor();}
|
||||
|
||||
/// Reset the underlying stream.
|
||||
void reset_stream(boost::asio::ssl::context& ctx)
|
||||
{
|
||||
stream_ = next_layer_type{stream_.get_executor(), ctx};
|
||||
}
|
||||
|
||||
/// Returns a reference to the next layer.
|
||||
auto& next_layer() noexcept { return stream_; }
|
||||
|
||||
/// Returns a const reference to the next layer.
|
||||
auto const& next_layer() const noexcept { return stream_; }
|
||||
|
||||
/** @brief Establishes a connection with the Redis server asynchronously.
|
||||
*
|
||||
* See aedis::connection::async_run for more information.
|
||||
*/
|
||||
template <class CompletionToken = boost::asio::default_completion_token_t<executor_type>>
|
||||
auto
|
||||
async_run(
|
||||
endpoint ep,
|
||||
timeouts ts = timeouts{},
|
||||
CompletionToken token = CompletionToken{})
|
||||
{
|
||||
return base_type::async_run(ep, ts, std::move(token));
|
||||
}
|
||||
|
||||
/** @brief Executes a command on the Redis server asynchronously.
|
||||
*
|
||||
* See aedis::connection::async_exec for more information.
|
||||
*/
|
||||
template <
|
||||
class Adapter = aedis::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{})
|
||||
{
|
||||
return base_type::async_exec(req, adapter, std::move(token));
|
||||
}
|
||||
|
||||
/** @brief Receives server side pushes asynchronously.
|
||||
*
|
||||
* See aedis::connection::async_receive for detailed information.
|
||||
*/
|
||||
template <
|
||||
class Adapter = aedis::detail::response_traits<void>::adapter_type,
|
||||
class CompletionToken = boost::asio::default_completion_token_t<executor_type>>
|
||||
auto async_receive(
|
||||
Adapter adapter = adapt(),
|
||||
CompletionToken token = CompletionToken{})
|
||||
{
|
||||
return base_type::async_receive(adapter, std::move(token));
|
||||
}
|
||||
|
||||
/** @brief Cancel operations.
|
||||
*
|
||||
* See aedis::connection::cancel for more information.
|
||||
*/
|
||||
auto cancel(operation op) -> std::size_t
|
||||
{ return base_type::cancel(op); }
|
||||
|
||||
private:
|
||||
using this_type = connection<next_layer_type>;
|
||||
|
||||
template <class, class> friend class aedis::detail::connection_base;
|
||||
template <class, class> friend struct aedis::detail::exec_op;
|
||||
template <class, class> friend struct detail::ssl_connect_with_timeout_op;
|
||||
template <class, class> friend struct aedis::detail::run_op;
|
||||
template <class> friend struct aedis::detail::writer_op;
|
||||
template <class> friend struct aedis::detail::check_idle_op;
|
||||
template <class> friend struct aedis::detail::reader_op;
|
||||
template <class, class> friend struct aedis::detail::exec_read_op;
|
||||
template <class> friend struct aedis::detail::ping_op;
|
||||
|
||||
auto& lowest_layer() noexcept { return stream_.lowest_layer(); }
|
||||
auto is_open() const noexcept { return stream_.next_layer().is_open(); }
|
||||
void close() { stream_.next_layer().close(); }
|
||||
|
||||
template <class Timer, class CompletionToken>
|
||||
auto
|
||||
async_connect(
|
||||
boost::asio::ip::tcp::resolver::results_type const& endpoints,
|
||||
timeouts ts,
|
||||
Timer& timer,
|
||||
CompletionToken&& token)
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code)
|
||||
>(detail::ssl_connect_with_timeout_op<this_type, Timer>{this, &endpoints, ts, &timer}, token, stream_);
|
||||
}
|
||||
|
||||
next_layer_type stream_;
|
||||
};
|
||||
|
||||
} // aedis::ssl
|
||||
|
||||
#endif // AEDIS_SSL_CONNECTION_HPP
|
||||
113
include/aedis/ssl/detail/connection_ops.hpp
Normal file
113
include/aedis/ssl/detail/connection_ops.hpp
Normal file
@@ -0,0 +1,113 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#ifndef AEDIS_SSL_CONNECTION_OPS_HPP
|
||||
#define AEDIS_SSL_CONNECTION_OPS_HPP
|
||||
|
||||
#include <array>
|
||||
|
||||
#include <boost/assert.hpp>
|
||||
#include <boost/system.hpp>
|
||||
#include <boost/asio/experimental/parallel_group.hpp>
|
||||
#include <boost/asio/yield.hpp>
|
||||
|
||||
namespace aedis::ssl::detail
|
||||
{
|
||||
|
||||
template <class Stream>
|
||||
struct handshake_op {
|
||||
Stream* stream;
|
||||
aedis::detail::conn_timer_t<typename Stream::executor_type>* timer;
|
||||
boost::asio::coroutine coro{};
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, std::array<std::size_t, 2> order = {}
|
||||
, boost::system::error_code ec1 = {}
|
||||
, boost::system::error_code ec2 = {})
|
||||
{
|
||||
reenter (coro)
|
||||
{
|
||||
yield
|
||||
boost::asio::experimental::make_parallel_group(
|
||||
[this](auto token)
|
||||
{
|
||||
return stream->async_handshake(boost::asio::ssl::stream_base::client, token);
|
||||
},
|
||||
[this](auto token) { return timer->async_wait(token);}
|
||||
).async_wait(
|
||||
boost::asio::experimental::wait_for_one(),
|
||||
std::move(self));
|
||||
|
||||
if (is_cancelled(self)) {
|
||||
self.complete(boost::asio::error::operation_aborted);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (order[0]) {
|
||||
case 0: self.complete(ec1); return;
|
||||
case 1:
|
||||
{
|
||||
BOOST_ASSERT_MSG(!ec2, "handshake_op: Incompatible state.");
|
||||
self.complete(error::ssl_handshake_timeout);
|
||||
return;
|
||||
}
|
||||
|
||||
default: BOOST_ASSERT(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <
|
||||
class Stream,
|
||||
class CompletionToken
|
||||
>
|
||||
auto async_handshake(
|
||||
Stream& stream,
|
||||
aedis::detail::conn_timer_t<typename Stream::executor_type>& timer,
|
||||
CompletionToken&& token)
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code)
|
||||
>(handshake_op<Stream>{&stream, &timer}, token, stream, timer);
|
||||
}
|
||||
|
||||
template <class Conn, class Timer>
|
||||
struct ssl_connect_with_timeout_op {
|
||||
Conn* conn = nullptr;
|
||||
boost::asio::ip::tcp::resolver::results_type const* endpoints = nullptr;
|
||||
typename Conn::timeouts ts;
|
||||
Timer* timer = nullptr;
|
||||
boost::asio::coroutine coro{};
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, boost::system::error_code ec = {}
|
||||
, boost::asio::ip::tcp::endpoint const& = {})
|
||||
{
|
||||
reenter (coro)
|
||||
{
|
||||
timer->expires_after(ts.connect_timeout);
|
||||
yield
|
||||
aedis::detail::async_connect(
|
||||
conn->lowest_layer(), *timer, *endpoints, std::move(self));
|
||||
AEDIS_CHECK_OP0();
|
||||
|
||||
timer->expires_after(ts.handshake_timeout);
|
||||
yield
|
||||
async_handshake(conn->next_layer(), *timer, std::move(self));
|
||||
AEDIS_CHECK_OP0();
|
||||
self.complete({});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // aedis::ssl::detail
|
||||
|
||||
#include <boost/asio/unyield.hpp>
|
||||
#endif // AEDIS_SSL_CONNECTION_OPS_HPP
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,303 +0,0 @@
|
||||
# ===========================================================================
|
||||
# https://www.gnu.org/software/autoconf-archive/ax_boost_base.html
|
||||
# ===========================================================================
|
||||
#
|
||||
# SYNOPSIS
|
||||
#
|
||||
# AX_BOOST_BASE([MINIMUM-VERSION], [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])
|
||||
#
|
||||
# DESCRIPTION
|
||||
#
|
||||
# Test for the Boost C++ libraries of a particular version (or newer)
|
||||
#
|
||||
# If no path to the installed boost library is given the macro searchs
|
||||
# under /usr, /usr/local, /opt and /opt/local and evaluates the
|
||||
# $BOOST_ROOT environment variable. Further documentation is available at
|
||||
# <http://randspringer.de/boost/index.html>.
|
||||
#
|
||||
# This macro calls:
|
||||
#
|
||||
# AC_SUBST(BOOST_CPPFLAGS) / AC_SUBST(BOOST_LDFLAGS)
|
||||
#
|
||||
# And sets:
|
||||
#
|
||||
# HAVE_BOOST
|
||||
#
|
||||
# LICENSE
|
||||
#
|
||||
# Copyright (c) 2008 Thomas Porschberg <thomas@randspringer.de>
|
||||
# Copyright (c) 2009 Peter Adolphs
|
||||
#
|
||||
# Copying and distribution of this file, with or without modification, are
|
||||
# permitted in any medium without royalty provided the copyright notice
|
||||
# and this notice are preserved. This file is offered as-is, without any
|
||||
# warranty.
|
||||
|
||||
#serial 49
|
||||
|
||||
# example boost program (need to pass version)
|
||||
m4_define([_AX_BOOST_BASE_PROGRAM],
|
||||
[AC_LANG_PROGRAM([[
|
||||
#include <boost/version.hpp>
|
||||
]],[[
|
||||
(void) ((void)sizeof(char[1 - 2*!!((BOOST_VERSION) < ($1))]));
|
||||
]])])
|
||||
|
||||
AC_DEFUN([AX_BOOST_BASE],
|
||||
[
|
||||
AC_ARG_WITH([boost],
|
||||
[AS_HELP_STRING([--with-boost@<:@=ARG@:>@],
|
||||
[use Boost library from a standard location (ARG=yes),
|
||||
from the specified location (ARG=<path>),
|
||||
or disable it (ARG=no)
|
||||
@<:@ARG=yes@:>@ ])],
|
||||
[
|
||||
AS_CASE([$withval],
|
||||
[no],[want_boost="no";_AX_BOOST_BASE_boost_path=""],
|
||||
[yes],[want_boost="yes";_AX_BOOST_BASE_boost_path=""],
|
||||
[want_boost="yes";_AX_BOOST_BASE_boost_path="$withval"])
|
||||
],
|
||||
[want_boost="yes"])
|
||||
|
||||
|
||||
AC_ARG_WITH([boost-libdir],
|
||||
[AS_HELP_STRING([--with-boost-libdir=LIB_DIR],
|
||||
[Force given directory for boost libraries.
|
||||
Note that this will override library path detection,
|
||||
so use this parameter only if default library detection fails
|
||||
and you know exactly where your boost libraries are located.])],
|
||||
[
|
||||
AS_IF([test -d "$withval"],
|
||||
[_AX_BOOST_BASE_boost_lib_path="$withval"],
|
||||
[AC_MSG_ERROR([--with-boost-libdir expected directory name])])
|
||||
],
|
||||
[_AX_BOOST_BASE_boost_lib_path=""])
|
||||
|
||||
BOOST_LDFLAGS=""
|
||||
BOOST_CPPFLAGS=""
|
||||
AS_IF([test "x$want_boost" = "xyes"],
|
||||
[_AX_BOOST_BASE_RUNDETECT([$1],[$2],[$3])])
|
||||
AC_SUBST(BOOST_CPPFLAGS)
|
||||
AC_SUBST(BOOST_LDFLAGS)
|
||||
])
|
||||
|
||||
|
||||
# convert a version string in $2 to numeric and affect to polymorphic var $1
|
||||
AC_DEFUN([_AX_BOOST_BASE_TONUMERICVERSION],[
|
||||
AS_IF([test "x$2" = "x"],[_AX_BOOST_BASE_TONUMERICVERSION_req="1.20.0"],[_AX_BOOST_BASE_TONUMERICVERSION_req="$2"])
|
||||
_AX_BOOST_BASE_TONUMERICVERSION_req_shorten=`expr $_AX_BOOST_BASE_TONUMERICVERSION_req : '\([[0-9]]*\.[[0-9]]*\)'`
|
||||
_AX_BOOST_BASE_TONUMERICVERSION_req_major=`expr $_AX_BOOST_BASE_TONUMERICVERSION_req : '\([[0-9]]*\)'`
|
||||
AS_IF([test "x$_AX_BOOST_BASE_TONUMERICVERSION_req_major" = "x"],
|
||||
[AC_MSG_ERROR([You should at least specify libboost major version])])
|
||||
_AX_BOOST_BASE_TONUMERICVERSION_req_minor=`expr $_AX_BOOST_BASE_TONUMERICVERSION_req : '[[0-9]]*\.\([[0-9]]*\)'`
|
||||
AS_IF([test "x$_AX_BOOST_BASE_TONUMERICVERSION_req_minor" = "x"],
|
||||
[_AX_BOOST_BASE_TONUMERICVERSION_req_minor="0"])
|
||||
_AX_BOOST_BASE_TONUMERICVERSION_req_sub_minor=`expr $_AX_BOOST_BASE_TONUMERICVERSION_req : '[[0-9]]*\.[[0-9]]*\.\([[0-9]]*\)'`
|
||||
AS_IF([test "X$_AX_BOOST_BASE_TONUMERICVERSION_req_sub_minor" = "X"],
|
||||
[_AX_BOOST_BASE_TONUMERICVERSION_req_sub_minor="0"])
|
||||
_AX_BOOST_BASE_TONUMERICVERSION_RET=`expr $_AX_BOOST_BASE_TONUMERICVERSION_req_major \* 100000 \+ $_AX_BOOST_BASE_TONUMERICVERSION_req_minor \* 100 \+ $_AX_BOOST_BASE_TONUMERICVERSION_req_sub_minor`
|
||||
AS_VAR_SET($1,$_AX_BOOST_BASE_TONUMERICVERSION_RET)
|
||||
])
|
||||
|
||||
dnl Run the detection of boost should be run only if $want_boost
|
||||
AC_DEFUN([_AX_BOOST_BASE_RUNDETECT],[
|
||||
_AX_BOOST_BASE_TONUMERICVERSION(WANT_BOOST_VERSION,[$1])
|
||||
succeeded=no
|
||||
|
||||
|
||||
AC_REQUIRE([AC_CANONICAL_HOST])
|
||||
dnl On 64-bit systems check for system libraries in both lib64 and lib.
|
||||
dnl The former is specified by FHS, but e.g. Debian does not adhere to
|
||||
dnl this (as it rises problems for generic multi-arch support).
|
||||
dnl The last entry in the list is chosen by default when no libraries
|
||||
dnl are found, e.g. when only header-only libraries are installed!
|
||||
AS_CASE([${host_cpu}],
|
||||
[x86_64],[libsubdirs="lib64 libx32 lib lib64"],
|
||||
[mips*64*],[libsubdirs="lib64 lib32 lib lib64"],
|
||||
[ppc64|powerpc64|s390x|sparc64|aarch64|ppc64le|powerpc64le|riscv64|e2k],[libsubdirs="lib64 lib lib64"],
|
||||
[libsubdirs="lib"]
|
||||
)
|
||||
|
||||
dnl allow for real multi-arch paths e.g. /usr/lib/x86_64-linux-gnu. Give
|
||||
dnl them priority over the other paths since, if libs are found there, they
|
||||
dnl are almost assuredly the ones desired.
|
||||
AS_CASE([${host_cpu}],
|
||||
[i?86],[multiarch_libsubdir="lib/i386-${host_os}"],
|
||||
[armv7l],[multiarch_libsubdir="lib/arm-${host_os}"],
|
||||
[multiarch_libsubdir="lib/${host_cpu}-${host_os}"]
|
||||
)
|
||||
|
||||
dnl first we check the system location for boost libraries
|
||||
dnl this location ist chosen if boost libraries are installed with the --layout=system option
|
||||
dnl or if you install boost with RPM
|
||||
AS_IF([test "x$_AX_BOOST_BASE_boost_path" != "x"],[
|
||||
AC_MSG_CHECKING([for boostlib >= $1 ($WANT_BOOST_VERSION) includes in "$_AX_BOOST_BASE_boost_path/include"])
|
||||
AS_IF([test -d "$_AX_BOOST_BASE_boost_path/include" && test -r "$_AX_BOOST_BASE_boost_path/include"],[
|
||||
AC_MSG_RESULT([yes])
|
||||
BOOST_CPPFLAGS="-I$_AX_BOOST_BASE_boost_path/include"
|
||||
for _AX_BOOST_BASE_boost_path_tmp in $multiarch_libsubdir $libsubdirs; do
|
||||
AC_MSG_CHECKING([for boostlib >= $1 ($WANT_BOOST_VERSION) lib path in "$_AX_BOOST_BASE_boost_path/$_AX_BOOST_BASE_boost_path_tmp"])
|
||||
AS_IF([test -d "$_AX_BOOST_BASE_boost_path/$_AX_BOOST_BASE_boost_path_tmp" && test -r "$_AX_BOOST_BASE_boost_path/$_AX_BOOST_BASE_boost_path_tmp" ],[
|
||||
AC_MSG_RESULT([yes])
|
||||
BOOST_LDFLAGS="-L$_AX_BOOST_BASE_boost_path/$_AX_BOOST_BASE_boost_path_tmp";
|
||||
break;
|
||||
],
|
||||
[AC_MSG_RESULT([no])])
|
||||
done],[
|
||||
AC_MSG_RESULT([no])])
|
||||
],[
|
||||
if test X"$cross_compiling" = Xyes; then
|
||||
search_libsubdirs=$multiarch_libsubdir
|
||||
else
|
||||
search_libsubdirs="$multiarch_libsubdir $libsubdirs"
|
||||
fi
|
||||
for _AX_BOOST_BASE_boost_path_tmp in /usr /usr/local /opt /opt/local ; do
|
||||
if test -d "$_AX_BOOST_BASE_boost_path_tmp/include/boost" && test -r "$_AX_BOOST_BASE_boost_path_tmp/include/boost" ; then
|
||||
for libsubdir in $search_libsubdirs ; do
|
||||
if ls "$_AX_BOOST_BASE_boost_path_tmp/$libsubdir/libboost_"* >/dev/null 2>&1 ; then break; fi
|
||||
done
|
||||
BOOST_LDFLAGS="-L$_AX_BOOST_BASE_boost_path_tmp/$libsubdir"
|
||||
BOOST_CPPFLAGS="-I$_AX_BOOST_BASE_boost_path_tmp/include"
|
||||
break;
|
||||
fi
|
||||
done
|
||||
])
|
||||
|
||||
dnl overwrite ld flags if we have required special directory with
|
||||
dnl --with-boost-libdir parameter
|
||||
AS_IF([test "x$_AX_BOOST_BASE_boost_lib_path" != "x"],
|
||||
[BOOST_LDFLAGS="-L$_AX_BOOST_BASE_boost_lib_path"])
|
||||
|
||||
AC_MSG_CHECKING([for boostlib >= $1 ($WANT_BOOST_VERSION)])
|
||||
CPPFLAGS_SAVED="$CPPFLAGS"
|
||||
CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS"
|
||||
export CPPFLAGS
|
||||
|
||||
LDFLAGS_SAVED="$LDFLAGS"
|
||||
LDFLAGS="$LDFLAGS $BOOST_LDFLAGS"
|
||||
export LDFLAGS
|
||||
|
||||
AC_REQUIRE([AC_PROG_CXX])
|
||||
AC_LANG_PUSH(C++)
|
||||
AC_COMPILE_IFELSE([_AX_BOOST_BASE_PROGRAM($WANT_BOOST_VERSION)],[
|
||||
AC_MSG_RESULT(yes)
|
||||
succeeded=yes
|
||||
found_system=yes
|
||||
],[
|
||||
])
|
||||
AC_LANG_POP([C++])
|
||||
|
||||
|
||||
|
||||
dnl if we found no boost with system layout we search for boost libraries
|
||||
dnl built and installed without the --layout=system option or for a staged(not installed) version
|
||||
if test "x$succeeded" != "xyes" ; then
|
||||
CPPFLAGS="$CPPFLAGS_SAVED"
|
||||
LDFLAGS="$LDFLAGS_SAVED"
|
||||
BOOST_CPPFLAGS=
|
||||
if test -z "$_AX_BOOST_BASE_boost_lib_path" ; then
|
||||
BOOST_LDFLAGS=
|
||||
fi
|
||||
_version=0
|
||||
if test -n "$_AX_BOOST_BASE_boost_path" ; then
|
||||
if test -d "$_AX_BOOST_BASE_boost_path" && test -r "$_AX_BOOST_BASE_boost_path"; then
|
||||
for i in `ls -d $_AX_BOOST_BASE_boost_path/include/boost-* 2>/dev/null`; do
|
||||
_version_tmp=`echo $i | sed "s#$_AX_BOOST_BASE_boost_path##" | sed 's/\/include\/boost-//' | sed 's/_/./'`
|
||||
V_CHECK=`expr $_version_tmp \> $_version`
|
||||
if test "x$V_CHECK" = "x1" ; then
|
||||
_version=$_version_tmp
|
||||
fi
|
||||
VERSION_UNDERSCORE=`echo $_version | sed 's/\./_/'`
|
||||
BOOST_CPPFLAGS="-I$_AX_BOOST_BASE_boost_path/include/boost-$VERSION_UNDERSCORE"
|
||||
done
|
||||
dnl if nothing found search for layout used in Windows distributions
|
||||
if test -z "$BOOST_CPPFLAGS"; then
|
||||
if test -d "$_AX_BOOST_BASE_boost_path/boost" && test -r "$_AX_BOOST_BASE_boost_path/boost"; then
|
||||
BOOST_CPPFLAGS="-I$_AX_BOOST_BASE_boost_path"
|
||||
fi
|
||||
fi
|
||||
dnl if we found something and BOOST_LDFLAGS was unset before
|
||||
dnl (because "$_AX_BOOST_BASE_boost_lib_path" = ""), set it here.
|
||||
if test -n "$BOOST_CPPFLAGS" && test -z "$BOOST_LDFLAGS"; then
|
||||
for libsubdir in $libsubdirs ; do
|
||||
if ls "$_AX_BOOST_BASE_boost_path/$libsubdir/libboost_"* >/dev/null 2>&1 ; then break; fi
|
||||
done
|
||||
BOOST_LDFLAGS="-L$_AX_BOOST_BASE_boost_path/$libsubdir"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
if test "x$cross_compiling" != "xyes" ; then
|
||||
for _AX_BOOST_BASE_boost_path in /usr /usr/local /opt /opt/local ; do
|
||||
if test -d "$_AX_BOOST_BASE_boost_path" && test -r "$_AX_BOOST_BASE_boost_path" ; then
|
||||
for i in `ls -d $_AX_BOOST_BASE_boost_path/include/boost-* 2>/dev/null`; do
|
||||
_version_tmp=`echo $i | sed "s#$_AX_BOOST_BASE_boost_path##" | sed 's/\/include\/boost-//' | sed 's/_/./'`
|
||||
V_CHECK=`expr $_version_tmp \> $_version`
|
||||
if test "x$V_CHECK" = "x1" ; then
|
||||
_version=$_version_tmp
|
||||
best_path=$_AX_BOOST_BASE_boost_path
|
||||
fi
|
||||
done
|
||||
fi
|
||||
done
|
||||
|
||||
VERSION_UNDERSCORE=`echo $_version | sed 's/\./_/'`
|
||||
BOOST_CPPFLAGS="-I$best_path/include/boost-$VERSION_UNDERSCORE"
|
||||
if test -z "$_AX_BOOST_BASE_boost_lib_path" ; then
|
||||
for libsubdir in $libsubdirs ; do
|
||||
if ls "$best_path/$libsubdir/libboost_"* >/dev/null 2>&1 ; then break; fi
|
||||
done
|
||||
BOOST_LDFLAGS="-L$best_path/$libsubdir"
|
||||
fi
|
||||
fi
|
||||
|
||||
if test -n "$BOOST_ROOT" ; then
|
||||
for libsubdir in $libsubdirs ; do
|
||||
if ls "$BOOST_ROOT/stage/$libsubdir/libboost_"* >/dev/null 2>&1 ; then break; fi
|
||||
done
|
||||
if test -d "$BOOST_ROOT" && test -r "$BOOST_ROOT" && test -d "$BOOST_ROOT/stage/$libsubdir" && test -r "$BOOST_ROOT/stage/$libsubdir"; then
|
||||
version_dir=`expr //$BOOST_ROOT : '.*/\(.*\)'`
|
||||
stage_version=`echo $version_dir | sed 's/boost_//' | sed 's/_/./g'`
|
||||
stage_version_shorten=`expr $stage_version : '\([[0-9]]*\.[[0-9]]*\)'`
|
||||
V_CHECK=`expr $stage_version_shorten \>\= $_version`
|
||||
if test "x$V_CHECK" = "x1" && test -z "$_AX_BOOST_BASE_boost_lib_path" ; then
|
||||
AC_MSG_NOTICE(We will use a staged boost library from $BOOST_ROOT)
|
||||
BOOST_CPPFLAGS="-I$BOOST_ROOT"
|
||||
BOOST_LDFLAGS="-L$BOOST_ROOT/stage/$libsubdir"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS"
|
||||
export CPPFLAGS
|
||||
LDFLAGS="$LDFLAGS $BOOST_LDFLAGS"
|
||||
export LDFLAGS
|
||||
|
||||
AC_LANG_PUSH(C++)
|
||||
AC_COMPILE_IFELSE([_AX_BOOST_BASE_PROGRAM($WANT_BOOST_VERSION)],[
|
||||
AC_MSG_RESULT(yes)
|
||||
succeeded=yes
|
||||
found_system=yes
|
||||
],[
|
||||
])
|
||||
AC_LANG_POP([C++])
|
||||
fi
|
||||
|
||||
if test "x$succeeded" != "xyes" ; then
|
||||
if test "x$_version" = "x0" ; then
|
||||
AC_MSG_NOTICE([[We could not detect the boost libraries (version $1 or higher). If you have a staged boost library (still not installed) please specify \$BOOST_ROOT in your environment and do not give a PATH to --with-boost option. If you are sure you have boost installed, then check your version number looking in <boost/version.hpp>. See http://randspringer.de/boost for more documentation.]])
|
||||
else
|
||||
AC_MSG_NOTICE([Your boost libraries seems to old (version $_version).])
|
||||
fi
|
||||
# execute ACTION-IF-NOT-FOUND (if present):
|
||||
ifelse([$3], , :, [$3])
|
||||
else
|
||||
AC_DEFINE(HAVE_BOOST,,[define if the Boost library is available])
|
||||
# execute ACTION-IF-FOUND (if present):
|
||||
ifelse([$2], , :, [$2])
|
||||
fi
|
||||
|
||||
CPPFLAGS="$CPPFLAGS_SAVED"
|
||||
LDFLAGS="$LDFLAGS_SAVED"
|
||||
|
||||
])
|
||||
@@ -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)
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <stdlib.h>
|
||||
|
||||
template <class T>
|
||||
void expect_eq(T const& a, T const& b, std::string const& msg = "")
|
||||
{
|
||||
if (a == b) {
|
||||
if (!msg.empty())
|
||||
std::cout << "Success: " << msg << std::endl;
|
||||
} else {
|
||||
std::cout << "Error: " << msg << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void expect_neq(T const& a, T const& b, std::string const& msg = "")
|
||||
{
|
||||
if (a != b) {
|
||||
if (!msg.empty())
|
||||
std::cout << "Success: " << msg << std::endl;
|
||||
} else {
|
||||
std::cout << "Error: " << msg << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void expect_error(boost::system::error_code a, T expected = {}, std::string const& msg = "")
|
||||
{
|
||||
if (a == expected) {
|
||||
if (a)
|
||||
std::cout << "Success: " << a.message() << " (" << a.category().name() << ") " << msg << std::endl;
|
||||
} else {
|
||||
std::cout << "Error: " << a.message() << " (" << a.category().name() << ") " << msg << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
inline
|
||||
void expect_no_error(boost::system::error_code ec, std::string const& msg)
|
||||
{
|
||||
expect_error(ec, boost::system::error_code{}, msg);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void check_empty(T const& t)
|
||||
{
|
||||
if (t.empty()) {
|
||||
//std::cout << "Success: " << std::endl;
|
||||
} else {
|
||||
std::cout << "Error: Not empty" << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
164
tests/conn_connect.cpp
Normal file
164
tests/conn_connect.cpp
Normal file
@@ -0,0 +1,164 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/system/errc.hpp>
|
||||
|
||||
#define BOOST_TEST_MODULE low level
|
||||
#include <boost/test/included/unit_test.hpp>
|
||||
|
||||
#include <aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
namespace net = boost::asio;
|
||||
|
||||
using connection = aedis::connection<>;
|
||||
using endpoint = aedis::endpoint;
|
||||
using error_code = boost::system::error_code;
|
||||
|
||||
bool is_host_not_found(error_code ec)
|
||||
{
|
||||
if (ec == net::error::netdb_errors::host_not_found) return true;
|
||||
if (ec == net::error::netdb_errors::host_not_found_try_again) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
error_code test_async_run(endpoint ep, connection::timeouts cfg = {})
|
||||
{
|
||||
net::io_context ioc;
|
||||
connection db{ioc};
|
||||
error_code ret;
|
||||
db.async_run(ep, cfg, [&](auto ec) { ret = ec; });
|
||||
ioc.run();
|
||||
return ret;
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(resolve_bad_host)
|
||||
{
|
||||
std::cout << boost::unit_test::framework::current_test_case().p_name << std::endl;
|
||||
|
||||
endpoint ep;
|
||||
ep.host = "Atibaia";
|
||||
ep.port = "6379";
|
||||
|
||||
connection::timeouts cfg;
|
||||
cfg.resolve_timeout = std::chrono::seconds{100};
|
||||
auto const ec = test_async_run(ep, cfg);
|
||||
|
||||
BOOST_TEST(is_host_not_found(ec));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(resolve_with_timeout)
|
||||
{
|
||||
std::cout << boost::unit_test::framework::current_test_case().p_name << std::endl;
|
||||
|
||||
endpoint ep;
|
||||
ep.host = "Atibaia";
|
||||
ep.port = "6379";
|
||||
|
||||
connection::timeouts cfg;
|
||||
// Low-enough to cause a timeout always.
|
||||
cfg.resolve_timeout = std::chrono::milliseconds{1};
|
||||
auto const ec = test_async_run(ep, cfg);
|
||||
BOOST_CHECK_EQUAL(ec, aedis::error::resolve_timeout);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(connect_bad_port)
|
||||
{
|
||||
std::cout << boost::unit_test::framework::current_test_case().p_name << std::endl;
|
||||
|
||||
endpoint ep;
|
||||
ep.host = "127.0.0.1";
|
||||
ep.port = "1";
|
||||
|
||||
connection::timeouts cfg;
|
||||
cfg.connect_timeout = std::chrono::seconds{100};
|
||||
auto const ec = test_async_run(ep, cfg);
|
||||
BOOST_CHECK_EQUAL(ec, net::error::basic_errors::connection_refused);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(connect_with_timeout)
|
||||
{
|
||||
std::cout << boost::unit_test::framework::current_test_case().p_name << std::endl;
|
||||
|
||||
endpoint ep;
|
||||
ep.host = "example.com";
|
||||
ep.port = "1";
|
||||
|
||||
connection::timeouts cfg;
|
||||
cfg.connect_timeout = std::chrono::milliseconds{1};
|
||||
auto const ec = test_async_run(ep, cfg);
|
||||
BOOST_CHECK_EQUAL(ec, aedis::error::connect_timeout);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(bad_hello_response)
|
||||
{
|
||||
std::cout << boost::unit_test::framework::current_test_case().p_name << std::endl;
|
||||
|
||||
// Succeeds with the tcp connection but fails the hello.
|
||||
endpoint ep;
|
||||
ep.host = "google.com";
|
||||
ep.port = "80";
|
||||
|
||||
auto const ec = test_async_run(ep);
|
||||
BOOST_CHECK_EQUAL(ec, aedis::error::invalid_data_type);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(plain_conn_on_tls_endpoint)
|
||||
{
|
||||
std::cout << boost::unit_test::framework::current_test_case().p_name << std::endl;
|
||||
|
||||
endpoint ep;
|
||||
ep.host = "google.com";
|
||||
ep.port = "443";
|
||||
|
||||
auto const ec = test_async_run(ep);
|
||||
BOOST_TEST(!!ec);
|
||||
}
|
||||
|
||||
auto auth_fail_error(boost::system::error_code ec)
|
||||
{
|
||||
return ec == aedis::error::resp3_handshake_error ||
|
||||
ec == aedis::error::exec_timeout;
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(auth_fail)
|
||||
{
|
||||
std::cout << boost::unit_test::framework::current_test_case().p_name << std::endl;
|
||||
|
||||
// Should cause an error in the authentication as our redis server
|
||||
// has no authentication configured.
|
||||
endpoint ep;
|
||||
ep.host = "127.0.0.1";
|
||||
ep.port = "6379";
|
||||
ep.username = "caboclo-do-mato";
|
||||
ep.password = "jabuticaba";
|
||||
|
||||
auto const ec = test_async_run(ep);
|
||||
BOOST_TEST(auth_fail_error(ec));
|
||||
}
|
||||
|
||||
auto wrong_role_error(boost::system::error_code ec)
|
||||
{
|
||||
return ec == aedis::error::unexpected_server_role ||
|
||||
ec == aedis::error::exec_timeout;
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(wrong_role)
|
||||
{
|
||||
std::cout << boost::unit_test::framework::current_test_case().p_name << std::endl;
|
||||
|
||||
// Should cause an error in the authentication as our redis server
|
||||
// has no authentication configured.
|
||||
endpoint ep;
|
||||
ep.host = "127.0.0.1";
|
||||
ep.port = "6379";
|
||||
ep.role = "errado";
|
||||
|
||||
auto const ec = test_async_run(ep);
|
||||
BOOST_TEST(wrong_role_error(ec));
|
||||
}
|
||||
92
tests/conn_echo_stress.cpp
Normal file
92
tests/conn_echo_stress.cpp
Normal file
@@ -0,0 +1,92 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <boost/asio.hpp>
|
||||
#ifdef BOOST_ASIO_HAS_CO_AWAIT
|
||||
#include <boost/system/errc.hpp>
|
||||
|
||||
#define BOOST_TEST_MODULE low level
|
||||
#include <boost/test/included/unit_test.hpp>
|
||||
|
||||
#include <aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
namespace net = boost::asio;
|
||||
|
||||
using aedis::resp3::request;
|
||||
using aedis::operation;
|
||||
using aedis::adapt;
|
||||
using connection = aedis::connection<>;
|
||||
using endpoint = aedis::endpoint;
|
||||
using error_code = boost::system::error_code;
|
||||
|
||||
#include <boost/asio/experimental/awaitable_operators.hpp>
|
||||
using namespace net::experimental::awaitable_operators;
|
||||
|
||||
net::awaitable<void> push_consumer(std::shared_ptr<connection> conn, int expected)
|
||||
{
|
||||
int c = 0;
|
||||
for (;;) {
|
||||
co_await conn->async_receive(adapt(), net::use_awaitable);
|
||||
if (++c == expected)
|
||||
break;
|
||||
}
|
||||
|
||||
request req;
|
||||
req.push("QUIT");
|
||||
co_await conn->async_exec(req, adapt(), net::use_awaitable);
|
||||
}
|
||||
|
||||
auto echo_session(std::shared_ptr<connection> conn, std::string id, int n) -> net::awaitable<void>
|
||||
{
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
|
||||
request req;
|
||||
std::tuple<std::string> resp;
|
||||
|
||||
for (auto i = 0; i < n; ++i) {
|
||||
auto const msg = id + "/" + std::to_string(i);
|
||||
//std::cout << msg << std::endl;
|
||||
req.push("PING", msg);
|
||||
req.push("SUBSCRIBE", "channel");
|
||||
boost::system::error_code ec;
|
||||
co_await conn->async_exec(req, adapt(resp), net::redirect_error(net::use_awaitable, ec));
|
||||
BOOST_TEST(!ec);
|
||||
BOOST_CHECK_EQUAL(msg, std::get<0>(resp));
|
||||
req.clear();
|
||||
std::get<0>(resp).clear();
|
||||
}
|
||||
}
|
||||
|
||||
auto async_echo_stress() -> net::awaitable<void>
|
||||
{
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
auto conn = std::make_shared<connection>(ex);
|
||||
|
||||
int const sessions = 1000;
|
||||
int const msgs = 100;
|
||||
int total = sessions * msgs;
|
||||
|
||||
net::co_spawn(ex, push_consumer(conn, total), net::detached);
|
||||
|
||||
for (int i = 0; i < sessions; ++i)
|
||||
net::co_spawn(ex, echo_session(conn, std::to_string(i), msgs), net::detached);
|
||||
|
||||
endpoint ep{"127.0.0.1", "6379"};
|
||||
co_await conn->async_run(ep, {}, net::use_awaitable);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(echo_stress)
|
||||
{
|
||||
net::io_context ioc;
|
||||
net::co_spawn(ioc.get_executor(), async_echo_stress(), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
#else
|
||||
int main(){}
|
||||
#endif
|
||||
91
tests/conn_exec.cpp
Normal file
91
tests/conn_exec.cpp
Normal file
@@ -0,0 +1,91 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/system/errc.hpp>
|
||||
|
||||
#define BOOST_TEST_MODULE low level
|
||||
#include <boost/test/included/unit_test.hpp>
|
||||
|
||||
#include <aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
namespace net = boost::asio;
|
||||
|
||||
using aedis::resp3::request;
|
||||
using aedis::adapt;
|
||||
using connection = aedis::connection<>;
|
||||
using endpoint = aedis::endpoint;
|
||||
using error_code = boost::system::error_code;
|
||||
|
||||
#ifdef BOOST_ASIO_HAS_CO_AWAIT
|
||||
#include <boost/asio/experimental/awaitable_operators.hpp>
|
||||
using namespace net::experimental::awaitable_operators;
|
||||
#endif
|
||||
|
||||
BOOST_AUTO_TEST_CASE(wrong_response_data_type)
|
||||
{
|
||||
request req;
|
||||
req.push("QUIT");
|
||||
|
||||
// Wrong data type.
|
||||
std::tuple<int> resp;
|
||||
net::io_context ioc;
|
||||
auto db = std::make_shared<connection>(ioc);
|
||||
db->async_exec(req, adapt(resp), [](auto ec, auto){
|
||||
BOOST_CHECK_EQUAL(ec, aedis::error::not_a_number);
|
||||
});
|
||||
db->async_run({"127.0.0.1", "6379"}, {}, [](auto ec){
|
||||
BOOST_CHECK_EQUAL(ec, boost::asio::error::basic_errors::operation_aborted);
|
||||
});
|
||||
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(cancel_request_if_not_connected)
|
||||
{
|
||||
request req;
|
||||
req.get_config().cancel_if_not_connected = true;
|
||||
req.push("PING");
|
||||
|
||||
net::io_context ioc;
|
||||
auto db = std::make_shared<connection>(ioc);
|
||||
db->async_exec(req, adapt(), [](auto ec, auto){
|
||||
BOOST_CHECK_EQUAL(ec, aedis::error::not_connected);
|
||||
});
|
||||
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(request_retry)
|
||||
{
|
||||
request req1;
|
||||
req1.get_config().cancel_on_connection_lost = true;
|
||||
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;
|
||||
auto db = std::make_shared<connection>(ioc);
|
||||
|
||||
db->async_exec(req1, adapt(), [](auto ec, auto){
|
||||
BOOST_TEST(!ec);
|
||||
});
|
||||
|
||||
db->async_exec(req2, adapt(), [](auto ec, auto){
|
||||
BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled);
|
||||
});
|
||||
|
||||
db->async_run({"127.0.0.1", "6379"}, {}, [](auto ec){
|
||||
BOOST_CHECK_EQUAL(ec, aedis::error::idle_timeout);
|
||||
});
|
||||
|
||||
ioc.run();
|
||||
}
|
||||
142
tests/conn_exec_cancel.cpp
Normal file
142
tests/conn_exec_cancel.cpp
Normal file
@@ -0,0 +1,142 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <boost/asio.hpp>
|
||||
#ifdef BOOST_ASIO_HAS_CO_AWAIT
|
||||
#include <boost/system/errc.hpp>
|
||||
|
||||
#define BOOST_TEST_MODULE low level
|
||||
#include <boost/test/included/unit_test.hpp>
|
||||
|
||||
#include <aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
namespace net = boost::asio;
|
||||
|
||||
using aedis::resp3::request;
|
||||
using aedis::operation;
|
||||
using aedis::adapt;
|
||||
using connection = aedis::connection<>;
|
||||
using endpoint = aedis::endpoint;
|
||||
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) -> net::awaitable<void>
|
||||
{
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
|
||||
connection::timeouts tms;
|
||||
tms.ping_interval = std::chrono::seconds{10};
|
||||
endpoint ep{"127.0.0.1", "6379"};
|
||||
boost::system::error_code ec;
|
||||
co_await conn->async_run(ep, tms, net::redirect_error(net::use_awaitable, ec));
|
||||
BOOST_TEST(!ec);
|
||||
}
|
||||
|
||||
auto async_cancel_exec(std::shared_ptr<connection> conn) -> net::awaitable<void>
|
||||
{
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
|
||||
net::steady_timer st{ex};
|
||||
st.expires_after(std::chrono::seconds{1});
|
||||
|
||||
boost::system::error_code ec1;
|
||||
|
||||
request req1;
|
||||
req1.get_config().coalesce = false;
|
||||
req1.push("BLPOP", "any", 3);
|
||||
|
||||
// Should not be canceled.
|
||||
conn->async_exec(req1, adapt(), [](auto ec, auto){
|
||||
BOOST_TEST(!ec);
|
||||
});
|
||||
|
||||
request req2;
|
||||
req2.get_config().coalesce = false;
|
||||
req2.push("PING", "second");
|
||||
|
||||
// Should be canceled.
|
||||
conn->async_exec(req1, adapt(), [](auto ec, auto){
|
||||
BOOST_CHECK_EQUAL(ec, boost::asio::error::basic_errors::operation_aborted);
|
||||
});
|
||||
|
||||
// Will complete while BLPOP is pending.
|
||||
co_await st.async_wait(net::redirect_error(net::use_awaitable, ec1));
|
||||
conn->cancel(operation::exec);
|
||||
|
||||
BOOST_TEST(!ec1);
|
||||
|
||||
request req3;
|
||||
req3.push("QUIT");
|
||||
|
||||
// Test whether the connection remains usable after a call to
|
||||
// cancel(exec).
|
||||
co_await conn->async_exec(req3, adapt(), net::redirect_error(net::use_awaitable, ec1));
|
||||
|
||||
BOOST_TEST(!ec1);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(cancel_exec_with_timer)
|
||||
{
|
||||
std::cout << boost::unit_test::framework::current_test_case().p_name << std::endl;
|
||||
net::io_context ioc;
|
||||
auto conn = std::make_shared<connection>(ioc);
|
||||
net::co_spawn(ioc.get_executor(), async_run(conn), net::detached);
|
||||
net::co_spawn(ioc.get_executor(), async_cancel_exec(conn), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
auto async_ignore_cancel_of_written_req(std::shared_ptr<connection> conn) -> net::awaitable<void>
|
||||
{
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
|
||||
net::steady_timer st{ex};
|
||||
st.expires_after(std::chrono::seconds{1});
|
||||
|
||||
net::steady_timer st2{ex};
|
||||
st2.expires_after(std::chrono::seconds{3});
|
||||
|
||||
boost::system::error_code ec1, ec2, ec3;
|
||||
|
||||
request req1; // Will be cancelled after it has been written.
|
||||
req1.get_config().coalesce = false;
|
||||
req1.push("BLPOP", "any", 3);
|
||||
|
||||
request req2; // Will be cancelled.
|
||||
req2.push("PING");
|
||||
|
||||
co_await (
|
||||
conn->async_exec(req1, adapt(), net::redirect_error(net::use_awaitable, ec1)) ||
|
||||
conn->async_exec(req2, adapt(), net::redirect_error(net::use_awaitable, ec2)) ||
|
||||
st.async_wait(net::redirect_error(net::use_awaitable, ec3))
|
||||
);
|
||||
|
||||
BOOST_TEST(!ec1);
|
||||
BOOST_CHECK_EQUAL(ec2, boost::asio::error::basic_errors::operation_aborted);
|
||||
BOOST_TEST(!ec3);
|
||||
|
||||
request req3;
|
||||
req3.push("PING");
|
||||
req3.push("QUIT");
|
||||
co_await conn->async_exec(req3, adapt(), net::redirect_error(net::use_awaitable, ec1));
|
||||
BOOST_TEST(!ec1);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(ignore_cancel_of_written_req)
|
||||
{
|
||||
std::cout << boost::unit_test::framework::current_test_case().p_name << std::endl;
|
||||
net::io_context ioc;
|
||||
auto conn = std::make_shared<connection>(ioc);
|
||||
net::co_spawn(ioc.get_executor(), async_run(conn), net::detached);
|
||||
net::co_spawn(ioc.get_executor(), async_ignore_cancel_of_written_req(conn), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
#else
|
||||
int main(){}
|
||||
#endif
|
||||
330
tests/conn_push.cpp
Normal file
330
tests/conn_push.cpp
Normal file
@@ -0,0 +1,330 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/system/errc.hpp>
|
||||
#include <boost/asio/experimental/as_tuple.hpp>
|
||||
|
||||
#define BOOST_TEST_MODULE low level
|
||||
#include <boost/test/included/unit_test.hpp>
|
||||
|
||||
#include <aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
namespace net = boost::asio;
|
||||
|
||||
using aedis::resp3::request;
|
||||
using aedis::adapt;
|
||||
using aedis::endpoint;
|
||||
using aedis::operation;
|
||||
using connection = aedis::connection<>;
|
||||
using error_code = boost::system::error_code;
|
||||
using net::experimental::as_tuple;
|
||||
|
||||
BOOST_AUTO_TEST_CASE(push_filtered_out)
|
||||
{
|
||||
net::io_context ioc;
|
||||
auto conn = std::make_shared<connection>(ioc);
|
||||
|
||||
request req;
|
||||
req.push("PING");
|
||||
req.push("SUBSCRIBE", "channel");
|
||||
req.push("QUIT");
|
||||
|
||||
std::tuple<std::string, std::string> resp;
|
||||
conn->async_exec(req, adapt(resp), [](auto ec, auto){
|
||||
BOOST_TEST(!ec);
|
||||
});
|
||||
|
||||
conn->async_receive(adapt(), [](auto ec, auto){
|
||||
BOOST_TEST(!ec);
|
||||
});
|
||||
|
||||
conn->async_run({"127.0.0.1", "6379"}, {}, [conn](auto ec){
|
||||
BOOST_TEST(!ec);
|
||||
});
|
||||
|
||||
ioc.run();
|
||||
|
||||
BOOST_CHECK_EQUAL(std::get<0>(resp), "PONG");
|
||||
BOOST_CHECK_EQUAL(std::get<1>(resp), "OK");
|
||||
}
|
||||
|
||||
// Checks whether we get idle timeout when no push reader is set.
|
||||
void test_missing_push_reader1(bool coalesce)
|
||||
{
|
||||
net::io_context ioc;
|
||||
auto conn = std::make_shared<connection>(ioc);
|
||||
|
||||
request req{{false, coalesce}};
|
||||
req.get_config().cancel_on_connection_lost = true;
|
||||
req.push("SUBSCRIBE", "channel");
|
||||
|
||||
conn->async_exec(req, adapt(), [](auto ec, auto){
|
||||
BOOST_TEST(!ec);
|
||||
});
|
||||
|
||||
conn->async_run({"127.0.0.1", "6379"}, {}, [conn](auto ec){
|
||||
BOOST_CHECK_EQUAL(ec, aedis::error::idle_timeout);
|
||||
});
|
||||
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
void test_missing_push_reader2(request const& req)
|
||||
{
|
||||
net::io_context ioc;
|
||||
auto conn = std::make_shared<connection>(ioc);
|
||||
|
||||
conn->async_exec(req, adapt(), [](auto ec, auto){
|
||||
BOOST_TEST(!ec);
|
||||
});
|
||||
|
||||
conn->async_run({"127.0.0.1", "6379"}, {}, [](auto ec){
|
||||
BOOST_CHECK_EQUAL(ec, aedis::error::idle_timeout);
|
||||
});
|
||||
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
#ifdef BOOST_ASIO_HAS_CO_AWAIT
|
||||
net::awaitable<void> push_consumer1(std::shared_ptr<connection> conn, bool& push_received)
|
||||
{
|
||||
{
|
||||
auto [ec, ev] = co_await conn->async_receive(adapt(), as_tuple(net::use_awaitable));
|
||||
BOOST_TEST(!ec);
|
||||
}
|
||||
|
||||
{
|
||||
auto [ec, ev] = co_await conn->async_receive(adapt(), as_tuple(net::use_awaitable));
|
||||
BOOST_CHECK_EQUAL(ec, boost::asio::experimental::channel_errc::channel_cancelled);
|
||||
}
|
||||
|
||||
push_received = true;
|
||||
}
|
||||
|
||||
struct adapter_error {
|
||||
void
|
||||
operator()(
|
||||
std::size_t, aedis::resp3::node<boost::string_view> const&, boost::system::error_code& ec)
|
||||
{
|
||||
ec = aedis::error::incompatible_size;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
auto get_supported_response_size() const noexcept
|
||||
{ return static_cast<std::size_t>(-1);}
|
||||
|
||||
[[nodiscard]]
|
||||
auto get_max_read_size(std::size_t) const noexcept
|
||||
{ return static_cast<std::size_t>(-1);}
|
||||
};
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_push_adapter)
|
||||
{
|
||||
net::io_context ioc;
|
||||
auto conn = std::make_shared<connection>(ioc);
|
||||
|
||||
request req;
|
||||
req.push("PING");
|
||||
req.push("SUBSCRIBE", "channel");
|
||||
req.push("PING");
|
||||
|
||||
conn->async_receive(adapter_error{}, [](auto ec, auto) {
|
||||
BOOST_CHECK_EQUAL(ec, aedis::error::incompatible_size);
|
||||
});
|
||||
|
||||
conn->async_exec(req, adapt(), [](auto ec, auto){
|
||||
BOOST_CHECK_EQUAL(ec, boost::asio::experimental::error::channel_errors::channel_cancelled);
|
||||
});
|
||||
|
||||
conn->async_run({"127.0.0.1", "6379"}, {}, [](auto ec){
|
||||
BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled);
|
||||
});
|
||||
|
||||
ioc.run();
|
||||
|
||||
// TODO: Reset the ioc reconnect and send a quit to ensure
|
||||
// reconnection is possible after an error.
|
||||
}
|
||||
|
||||
void test_push_is_received1(bool coalesce)
|
||||
{
|
||||
net::io_context ioc;
|
||||
auto conn = std::make_shared<connection>(ioc);
|
||||
|
||||
request req{{false, coalesce}};
|
||||
req.push("SUBSCRIBE", "channel");
|
||||
req.push("QUIT");
|
||||
|
||||
conn->async_exec(req, adapt(), [](auto ec, auto){
|
||||
BOOST_TEST(!ec);
|
||||
});
|
||||
|
||||
conn->async_run({"127.0.0.1", "6379"}, {}, [conn](auto ec){
|
||||
BOOST_TEST(!ec);
|
||||
conn->cancel(operation::receive);
|
||||
});
|
||||
|
||||
bool push_received = false;
|
||||
net::co_spawn(
|
||||
ioc.get_executor(),
|
||||
push_consumer1(conn, push_received),
|
||||
net::detached);
|
||||
|
||||
ioc.run();
|
||||
|
||||
BOOST_TEST(push_received);
|
||||
}
|
||||
|
||||
void test_push_is_received2(bool coalesce)
|
||||
{
|
||||
request req1{{false, coalesce}};
|
||||
req1.push("PING", "Message1");
|
||||
|
||||
request req2{{false, coalesce}};
|
||||
req2.push("SUBSCRIBE", "channel");
|
||||
|
||||
request req3{{false, coalesce}};
|
||||
req3.push("PING", "Message2");
|
||||
req3.push("QUIT");
|
||||
|
||||
net::io_context ioc;
|
||||
|
||||
auto conn = std::make_shared<connection>(ioc);
|
||||
|
||||
auto handler =[](auto ec, auto...)
|
||||
{
|
||||
BOOST_TEST(!ec);
|
||||
};
|
||||
|
||||
conn->async_exec(req1, adapt(), handler);
|
||||
conn->async_exec(req2, adapt(), handler);
|
||||
conn->async_exec(req3, adapt(), handler);
|
||||
|
||||
endpoint ep{"127.0.0.1", "6379"};
|
||||
conn->async_run(ep, {}, [conn](auto ec) {
|
||||
BOOST_TEST(!ec);
|
||||
conn->cancel(operation::receive);
|
||||
});
|
||||
|
||||
bool push_received = false;
|
||||
net::co_spawn(
|
||||
ioc.get_executor(),
|
||||
push_consumer1(conn, push_received),
|
||||
net::detached);
|
||||
|
||||
ioc.run();
|
||||
|
||||
BOOST_TEST(push_received);
|
||||
}
|
||||
|
||||
net::awaitable<void> push_consumer3(std::shared_ptr<connection> conn)
|
||||
{
|
||||
for (;;)
|
||||
co_await conn->async_receive(adapt(), net::use_awaitable);
|
||||
}
|
||||
|
||||
// Test many subscribe requests.
|
||||
void test_push_many_subscribes(bool coalesce)
|
||||
{
|
||||
request req0{{false, coalesce}};
|
||||
req0.push("HELLO", 3);
|
||||
|
||||
request req1{{false, coalesce}};
|
||||
req1.push("PING", "Message1");
|
||||
|
||||
request req2{{false, coalesce}};
|
||||
req2.push("SUBSCRIBE", "channel");
|
||||
|
||||
request req3{{false, coalesce}};
|
||||
req3.push("QUIT");
|
||||
|
||||
auto handler =[](auto ec, auto...)
|
||||
{
|
||||
BOOST_TEST(!ec);
|
||||
};
|
||||
|
||||
net::io_context ioc;
|
||||
auto conn = std::make_shared<connection>(ioc);
|
||||
conn->async_exec(req0, adapt(), handler);
|
||||
conn->async_exec(req1, adapt(), handler);
|
||||
conn->async_exec(req2, adapt(), handler);
|
||||
conn->async_exec(req2, adapt(), handler);
|
||||
conn->async_exec(req1, adapt(), handler);
|
||||
conn->async_exec(req2, adapt(), handler);
|
||||
conn->async_exec(req1, adapt(), handler);
|
||||
conn->async_exec(req2, adapt(), handler);
|
||||
conn->async_exec(req2, adapt(), handler);
|
||||
conn->async_exec(req1, adapt(), handler);
|
||||
conn->async_exec(req2, adapt(), handler);
|
||||
conn->async_exec(req3, adapt(), handler);
|
||||
|
||||
endpoint ep{"127.0.0.1", "6379"};
|
||||
conn->async_run(ep, {}, [conn](auto ec) {
|
||||
BOOST_TEST(!ec);
|
||||
conn->cancel(operation::receive);
|
||||
});
|
||||
|
||||
net::co_spawn(ioc.get_executor(), push_consumer3(conn), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(push_received1)
|
||||
{
|
||||
test_push_is_received1(true);
|
||||
test_push_is_received1(false);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(push_received2)
|
||||
{
|
||||
test_push_is_received2(true);
|
||||
test_push_is_received2(false);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(many_subscribers)
|
||||
{
|
||||
test_push_many_subscribes(true);
|
||||
test_push_many_subscribes(false);
|
||||
}
|
||||
#endif
|
||||
|
||||
BOOST_AUTO_TEST_CASE(missing_reader1_coalesce)
|
||||
{
|
||||
test_missing_push_reader1(true);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(missing_reader1_no_coalesce)
|
||||
{
|
||||
test_missing_push_reader1(false);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(missing_reader2a)
|
||||
{
|
||||
request req1{{false}};
|
||||
req1.push("PING", "Message");
|
||||
req1.push("SUBSCRIBE"); // Wrong command synthax.
|
||||
|
||||
req1.get_config().coalesce = true;
|
||||
test_missing_push_reader2(req1);
|
||||
|
||||
req1.get_config().coalesce = false;
|
||||
test_missing_push_reader2(req1);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(missing_reader2b)
|
||||
{
|
||||
request req2{{false}};
|
||||
req2.push("SUBSCRIBE"); // Wrong command syntax.
|
||||
|
||||
req2.get_config().coalesce = true;
|
||||
test_missing_push_reader2(req2);
|
||||
|
||||
req2.get_config().coalesce = false;
|
||||
test_missing_push_reader2(req2);
|
||||
}
|
||||
|
||||
85
tests/conn_quit.cpp
Normal file
85
tests/conn_quit.cpp
Normal file
@@ -0,0 +1,85 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/system/errc.hpp>
|
||||
|
||||
#define BOOST_TEST_MODULE low level
|
||||
#include <boost/test/included/unit_test.hpp>
|
||||
|
||||
#include <aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
namespace net = boost::asio;
|
||||
|
||||
using aedis::adapt;
|
||||
using aedis::endpoint;
|
||||
using aedis::resp3::request;
|
||||
using connection = aedis::connection<>;
|
||||
using error_code = boost::system::error_code;
|
||||
using operation = aedis::operation;
|
||||
|
||||
// Test if quit causes async_run to exit.
|
||||
BOOST_AUTO_TEST_CASE(test_quit_no_coalesce)
|
||||
{
|
||||
net::io_context ioc;
|
||||
auto conn = std::make_shared<connection>(ioc);
|
||||
|
||||
request req1{{false, false}};
|
||||
req1.push("PING");
|
||||
|
||||
request req2{{false, false}};
|
||||
req2.push("QUIT");
|
||||
|
||||
conn->async_exec(req1, adapt(), [](auto ec, auto){
|
||||
BOOST_TEST(!ec);
|
||||
});
|
||||
conn->async_exec(req2, 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(req1, adapt(), [](auto ec, auto){
|
||||
BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled);
|
||||
});
|
||||
conn->async_exec(req1, adapt(), [](auto ec, auto){
|
||||
BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled);
|
||||
});
|
||||
|
||||
endpoint ep{"127.0.0.1", "6379"};
|
||||
conn->async_run(ep, {}, [conn](auto ec){
|
||||
BOOST_TEST(!ec);
|
||||
conn->cancel(operation::exec);
|
||||
});
|
||||
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
void test_quit2(bool coalesce)
|
||||
{
|
||||
request req{{false, coalesce}};
|
||||
req.push("QUIT");
|
||||
|
||||
net::io_context ioc;
|
||||
auto conn = std::make_shared<connection>(ioc);
|
||||
conn->async_exec(req, adapt(), [](auto ec, auto) {
|
||||
BOOST_TEST(!ec);
|
||||
});
|
||||
|
||||
conn->async_run({"127.0.0.1", "6379"}, {}, [](auto ec) {
|
||||
BOOST_TEST(!ec);
|
||||
});
|
||||
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_quit)
|
||||
{
|
||||
test_quit2(true);
|
||||
test_quit2(false);
|
||||
}
|
||||
57
tests/conn_quit_coalesce.cpp
Normal file
57
tests/conn_quit_coalesce.cpp
Normal file
@@ -0,0 +1,57 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/system/errc.hpp>
|
||||
|
||||
#define BOOST_TEST_MODULE low level
|
||||
#include <boost/test/included/unit_test.hpp>
|
||||
|
||||
#include <aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
namespace net = boost::asio;
|
||||
|
||||
using aedis::adapt;
|
||||
using aedis::endpoint;
|
||||
using aedis::resp3::request;
|
||||
using connection = aedis::connection<>;
|
||||
using error_code = boost::system::error_code;
|
||||
using operation = aedis::operation;
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_quit_coalesce)
|
||||
{
|
||||
net::io_context ioc;
|
||||
auto db = std::make_shared<connection>(ioc);
|
||||
|
||||
request req1{{false, true}};
|
||||
req1.push("PING");
|
||||
|
||||
request req2{{false, true}};
|
||||
req2.push("QUIT");
|
||||
|
||||
db->async_exec(req1, adapt(), [](auto ec, auto){
|
||||
BOOST_TEST(!ec);
|
||||
});
|
||||
db->async_exec(req2, adapt(), [](auto ec, auto){
|
||||
BOOST_TEST(!ec);
|
||||
});
|
||||
db->async_exec(req1, adapt(), [](auto ec, auto){
|
||||
BOOST_CHECK_EQUAL(ec, net::error::misc_errors::eof);
|
||||
});
|
||||
db->async_exec(req1, adapt(), [](auto ec, auto){
|
||||
BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled);
|
||||
});
|
||||
|
||||
endpoint ep{"127.0.0.1", "6379"};
|
||||
db->async_run(ep, {}, [db](auto ec){
|
||||
BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled);
|
||||
db->cancel(operation::exec);
|
||||
});
|
||||
|
||||
ioc.run();
|
||||
}
|
||||
102
tests/conn_reconnect.cpp
Normal file
102
tests/conn_reconnect.cpp
Normal file
@@ -0,0 +1,102 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <boost/asio.hpp>
|
||||
#ifdef BOOST_ASIO_HAS_CO_AWAIT
|
||||
|
||||
#define BOOST_TEST_MODULE low level
|
||||
#include <boost/test/included/unit_test.hpp>
|
||||
|
||||
#include <aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
namespace net = boost::asio;
|
||||
|
||||
using aedis::resp3::request;
|
||||
using aedis::adapt;
|
||||
using connection = aedis::connection<>;
|
||||
using endpoint = aedis::endpoint;
|
||||
using error_code = boost::system::error_code;
|
||||
|
||||
#include <boost/asio/experimental/awaitable_operators.hpp>
|
||||
using namespace boost::asio::experimental::awaitable_operators;
|
||||
|
||||
net::awaitable<void> test_reconnect_impl(std::shared_ptr<connection> db)
|
||||
{
|
||||
request req;
|
||||
req.push("QUIT");
|
||||
|
||||
int i = 0;
|
||||
endpoint ep{"127.0.0.1", "6379"};
|
||||
for (; i < 5; ++i) {
|
||||
boost::system::error_code ec1, ec2;
|
||||
co_await (
|
||||
db->async_exec(req, adapt(), net::redirect_error(net::use_awaitable, ec1)) &&
|
||||
db->async_run(ep, {}, net::redirect_error(net::use_awaitable, ec2))
|
||||
);
|
||||
|
||||
BOOST_TEST(!ec1);
|
||||
BOOST_TEST(!ec2);
|
||||
db->reset_stream();
|
||||
}
|
||||
|
||||
BOOST_CHECK_EQUAL(i, 5);
|
||||
co_return;
|
||||
}
|
||||
|
||||
// Test whether the client works after a reconnect.
|
||||
BOOST_AUTO_TEST_CASE(test_reconnect)
|
||||
{
|
||||
std::cout << boost::unit_test::framework::current_test_case().p_name << std::endl;
|
||||
net::io_context ioc;
|
||||
auto db = std::make_shared<connection>(ioc);
|
||||
net::co_spawn(ioc, test_reconnect_impl(db), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
auto async_test_reconnect_timeout() -> net::awaitable<void>
|
||||
{
|
||||
auto conn = std::make_shared<connection>(co_await net::this_coro::executor);
|
||||
endpoint ep{"127.0.0.1", "6379"};
|
||||
boost::system::error_code ec1, ec2;
|
||||
|
||||
request req1;
|
||||
req1.get_config().cancel_if_not_connected = false;
|
||||
req1.get_config().cancel_on_connection_lost = true;
|
||||
req1.push("CLIENT", "PAUSE", 7000);
|
||||
|
||||
co_await (
|
||||
conn->async_exec(req1, adapt(), net::redirect_error(net::use_awaitable, ec1)) &&
|
||||
conn->async_run(ep, {}, net::redirect_error(net::use_awaitable, ec2))
|
||||
);
|
||||
|
||||
BOOST_TEST(!ec1);
|
||||
BOOST_CHECK_EQUAL(ec2, aedis::error::idle_timeout);
|
||||
|
||||
request req2;
|
||||
req2.get_config().cancel_if_not_connected = false;
|
||||
req2.get_config().cancel_on_connection_lost = true;
|
||||
req2.push("QUIT");
|
||||
|
||||
co_await (
|
||||
conn->async_exec(req1, adapt(), net::redirect_error(net::use_awaitable, ec1)) &&
|
||||
conn->async_run(ep, {}, net::redirect_error(net::use_awaitable, ec2))
|
||||
);
|
||||
|
||||
BOOST_CHECK_EQUAL(ec1, boost::system::errc::errc_t::operation_canceled);
|
||||
BOOST_CHECK_EQUAL(ec2, aedis::error::exec_timeout);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_reconnect_and_idle)
|
||||
{
|
||||
net::io_context ioc;
|
||||
net::co_spawn(ioc, async_test_reconnect_timeout(), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
#else
|
||||
int main(){}
|
||||
#endif
|
||||
189
tests/conn_run_cancel.cpp
Normal file
189
tests/conn_run_cancel.cpp
Normal file
@@ -0,0 +1,189 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <boost/asio.hpp>
|
||||
#ifdef BOOST_ASIO_HAS_CO_AWAIT
|
||||
#include <boost/system/errc.hpp>
|
||||
#include <boost/asio/experimental/as_tuple.hpp>
|
||||
|
||||
#define BOOST_TEST_MODULE low level
|
||||
#include <boost/test/included/unit_test.hpp>
|
||||
|
||||
#include <aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
namespace net = boost::asio;
|
||||
|
||||
using aedis::resp3::request;
|
||||
using aedis::operation;
|
||||
using aedis::adapt;
|
||||
using connection = aedis::connection<>;
|
||||
using endpoint = aedis::endpoint;
|
||||
using error_code = boost::system::error_code;
|
||||
using net::experimental::as_tuple;
|
||||
|
||||
#include <boost/asio/experimental/awaitable_operators.hpp>
|
||||
using namespace net::experimental::awaitable_operators;
|
||||
|
||||
auto async_cancel_run_with_timer() -> net::awaitable<void>
|
||||
{
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
auto conn = std::make_shared<connection>(ex);
|
||||
net::steady_timer st{ex};
|
||||
st.expires_after(std::chrono::seconds{1});
|
||||
|
||||
endpoint ep{"127.0.0.1", "6379"};
|
||||
boost::system::error_code ec1, ec2;
|
||||
co_await (
|
||||
conn->async_run(ep, {}, net::redirect_error(net::use_awaitable, ec1)) ||
|
||||
st.async_wait(net::redirect_error(net::use_awaitable, ec2))
|
||||
);
|
||||
|
||||
BOOST_CHECK_EQUAL(ec1, boost::asio::error::basic_errors::operation_aborted);
|
||||
BOOST_TEST(!ec2);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(cancel_run_with_timer)
|
||||
{
|
||||
net::io_context ioc;
|
||||
net::co_spawn(ioc.get_executor(), async_cancel_run_with_timer(), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
net::awaitable<void>
|
||||
async_check_cancellation_not_missed(
|
||||
std::shared_ptr<connection> conn,
|
||||
int n,
|
||||
std::chrono::milliseconds ms)
|
||||
{
|
||||
net::steady_timer timer{co_await net::this_coro::executor};
|
||||
|
||||
connection::timeouts tms;
|
||||
tms.resolve_timeout = std::chrono::seconds{10};
|
||||
tms.connect_timeout = std::chrono::seconds{10};
|
||||
tms.resp3_handshake_timeout = std::chrono::seconds{2};
|
||||
tms.ping_interval = std::chrono::seconds{1};
|
||||
|
||||
endpoint ep{"127.0.0.1", "6379"};
|
||||
|
||||
for (auto i = 0; i < n; ++i) {
|
||||
timer.expires_after(ms);
|
||||
boost::system::error_code ec1, ec2;
|
||||
co_await (
|
||||
conn->async_run(ep, {}, net::redirect_error(net::use_awaitable, ec1)) ||
|
||||
timer.async_wait(net::redirect_error(net::use_awaitable, ec2))
|
||||
);
|
||||
BOOST_CHECK_EQUAL(ec1, boost::asio::error::basic_errors::operation_aborted);
|
||||
std::cout << "Counter: " << i << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
// See PR #29
|
||||
BOOST_AUTO_TEST_CASE(check_implicit_cancel_not_missed_0)
|
||||
{
|
||||
net::io_context ioc;
|
||||
auto conn = std::make_shared<connection>(ioc);
|
||||
net::co_spawn(ioc, async_check_cancellation_not_missed(conn, 10, std::chrono::milliseconds{0}), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(check_implicit_cancel_not_missed_2)
|
||||
{
|
||||
net::io_context ioc;
|
||||
auto conn = std::make_shared<connection>(ioc);
|
||||
net::co_spawn(ioc, async_check_cancellation_not_missed(conn, 20, std::chrono::milliseconds{2}), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(check_implicit_cancel_not_missed_8)
|
||||
{
|
||||
net::io_context ioc;
|
||||
auto conn = std::make_shared<connection>(ioc);
|
||||
net::co_spawn(ioc, async_check_cancellation_not_missed(conn, 20, std::chrono::milliseconds{8}), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(check_implicit_cancel_not_missed_16)
|
||||
{
|
||||
net::io_context ioc;
|
||||
auto conn = std::make_shared<connection>(ioc);
|
||||
net::co_spawn(ioc, async_check_cancellation_not_missed(conn, 20, std::chrono::milliseconds{16}), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(check_implicit_cancel_not_missed_32)
|
||||
{
|
||||
net::io_context ioc;
|
||||
auto conn = std::make_shared<connection>(ioc);
|
||||
net::co_spawn(ioc, async_check_cancellation_not_missed(conn, 20, std::chrono::milliseconds{32}), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(check_implicit_cancel_not_missed_64)
|
||||
{
|
||||
net::io_context ioc;
|
||||
auto conn = std::make_shared<connection>(ioc);
|
||||
net::co_spawn(ioc, async_check_cancellation_not_missed(conn, 20, std::chrono::milliseconds{64}), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(check_implicit_cancel_not_missed_128)
|
||||
{
|
||||
net::io_context ioc;
|
||||
auto conn = std::make_shared<connection>(ioc);
|
||||
net::co_spawn(ioc, async_check_cancellation_not_missed(conn, 20, std::chrono::milliseconds{128}), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(check_implicit_cancel_not_missed_256)
|
||||
{
|
||||
net::io_context ioc;
|
||||
auto conn = std::make_shared<connection>(ioc);
|
||||
net::co_spawn(ioc, async_check_cancellation_not_missed(conn, 20, std::chrono::milliseconds{256}), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(check_implicit_cancel_not_missed_512)
|
||||
{
|
||||
net::io_context ioc;
|
||||
auto conn = std::make_shared<connection>(ioc);
|
||||
net::co_spawn(ioc, async_check_cancellation_not_missed(conn, 20, std::chrono::milliseconds{512}), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(check_implicit_cancel_not_missed_1024)
|
||||
{
|
||||
net::io_context ioc;
|
||||
auto conn = std::make_shared<connection>(ioc);
|
||||
net::co_spawn(ioc, async_check_cancellation_not_missed(conn, 20, std::chrono::milliseconds{1024}), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(reset_before_run_completes)
|
||||
{
|
||||
net::io_context ioc;
|
||||
auto conn = std::make_shared<connection>(ioc);
|
||||
|
||||
// Sends a ping just as a means of waiting until we are connected.
|
||||
request req;
|
||||
req.push("PING");
|
||||
|
||||
conn->async_exec(req, adapt(), [conn](auto ec, auto){
|
||||
BOOST_TEST(!ec);
|
||||
conn->reset_stream();
|
||||
});
|
||||
|
||||
conn->async_run({"127.0.0.1", "6379"}, {}, [conn](auto ec){
|
||||
BOOST_CHECK_EQUAL(ec, net::error::operation_aborted);
|
||||
});
|
||||
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
#else
|
||||
int main(){}
|
||||
#endif
|
||||
80
tests/conn_tls.cpp
Normal file
80
tests/conn_tls.cpp
Normal file
@@ -0,0 +1,80 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/asio/ssl.hpp>
|
||||
|
||||
#define BOOST_TEST_MODULE low level
|
||||
#include <boost/test/included/unit_test.hpp>
|
||||
|
||||
#include <aedis.hpp>
|
||||
#include <aedis/ssl/connection.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
namespace net = boost::asio;
|
||||
|
||||
using aedis::adapt;
|
||||
using connection = aedis::ssl::connection<net::ssl::stream<net::ip::tcp::socket>>;
|
||||
using endpoint = aedis::endpoint;
|
||||
|
||||
bool verify_certificate(bool, net::ssl::verify_context&)
|
||||
{
|
||||
std::cout << "set_verify_callback" << std::endl;
|
||||
return true;
|
||||
}
|
||||
|
||||
boost::system::error_code hello_fail(endpoint ep)
|
||||
{
|
||||
net::io_context ioc;
|
||||
|
||||
net::ssl::context ctx{net::ssl::context::sslv23};
|
||||
auto conn = std::make_shared<connection>(ioc.get_executor(), ctx);
|
||||
conn->next_layer().set_verify_mode(net::ssl::verify_peer);
|
||||
conn->next_layer().set_verify_callback(verify_certificate);
|
||||
boost::system::error_code ret;
|
||||
conn->async_run(ep, {}, [&](auto ec) {
|
||||
ret = ec;
|
||||
});
|
||||
|
||||
ioc.run();
|
||||
return ret;
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_tls_handshake_fail)
|
||||
{
|
||||
std::cout << boost::unit_test::framework::current_test_case().p_name << std::endl;
|
||||
|
||||
endpoint ep;
|
||||
ep.host = "google.com";
|
||||
ep.port = "80";
|
||||
auto const ec = hello_fail(ep);
|
||||
BOOST_TEST(!!ec);
|
||||
std::cout << "-----> " << ec.message() << std::endl;
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_tls_handshake_fail2)
|
||||
{
|
||||
std::cout << boost::unit_test::framework::current_test_case().p_name << std::endl;
|
||||
|
||||
endpoint ep;
|
||||
ep.host = "127.0.0.1";
|
||||
ep.port = "6379";
|
||||
auto const ec = hello_fail(ep);
|
||||
BOOST_CHECK_EQUAL(ec, aedis::error::ssl_handshake_timeout);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_hello_fail)
|
||||
{
|
||||
std::cout << boost::unit_test::framework::current_test_case().p_name << std::endl;
|
||||
|
||||
endpoint ep;
|
||||
ep.host = "google.com";
|
||||
ep.port = "443";
|
||||
auto const ec = hello_fail(ep);
|
||||
BOOST_CHECK_EQUAL(ec, aedis::error::invalid_data_type);
|
||||
}
|
||||
|
||||
@@ -1,391 +0,0 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
|
||||
//#define BOOST_ASIO_ENABLE_HANDLER_TRACKING
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/system/errc.hpp>
|
||||
#include <boost/asio/experimental/as_tuple.hpp>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
#include "check.hpp"
|
||||
|
||||
//std::cout << "aaaa " << ec.message() << " " << cmd << " " << n << std::endl;
|
||||
|
||||
namespace net = boost::asio;
|
||||
namespace resp3 = aedis::resp3;
|
||||
|
||||
using aedis::resp3::request;
|
||||
using connection = aedis::connection<>;
|
||||
using error_code = boost::system::error_code;
|
||||
using net::experimental::as_tuple;
|
||||
using tcp = net::ip::tcp;
|
||||
using boost::system::error_code;
|
||||
|
||||
auto print_read = [](auto cmd, auto n)
|
||||
{
|
||||
std::cout << cmd << ": " << n << std::endl;
|
||||
};
|
||||
|
||||
//----------------------------------------------------------------
|
||||
|
||||
void test_resolve()
|
||||
{
|
||||
connection::config cfg;
|
||||
cfg.resolve_timeout = std::chrono::seconds{100};
|
||||
|
||||
net::io_context ioc;
|
||||
connection db{ioc, cfg};
|
||||
db.async_run("Atibaia", "6379", [](auto ec) {
|
||||
expect_error(ec, net::error::netdb_errors::host_not_found, "test_resolve");
|
||||
});
|
||||
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------
|
||||
|
||||
void test_connect()
|
||||
{
|
||||
connection::config cfg;
|
||||
cfg.connect_timeout = std::chrono::seconds{100};
|
||||
|
||||
net::io_context ioc;
|
||||
connection db{ioc, cfg};
|
||||
db.async_run("127.0.0.1", "1", [](auto ec) {
|
||||
expect_error(ec, net::error::basic_errors::connection_refused, "test_connect");
|
||||
});
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------
|
||||
|
||||
// Test if quit causes async_run to exit.
|
||||
void test_quit1()
|
||||
{
|
||||
net::io_context ioc;
|
||||
auto db = std::make_shared<connection>(ioc);
|
||||
|
||||
request req;
|
||||
req.push("HELLO", 3);
|
||||
req.push("QUIT");
|
||||
db->async_exec(req, aedis::adapt(), [](auto ec, auto r){
|
||||
expect_no_error(ec, "test_quit1");
|
||||
});
|
||||
|
||||
db->async_run("127.0.0.1", "6379", [](auto ec){
|
||||
expect_error(ec, net::error::misc_errors::eof, "test_quit1");
|
||||
});
|
||||
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
void test_quit2()
|
||||
{
|
||||
request req;
|
||||
req.push("HELLO", 3);
|
||||
req.push("QUIT");
|
||||
|
||||
net::io_context ioc;
|
||||
auto db = std::make_shared<connection>(ioc);
|
||||
db->async_exec("127.0.0.1", "6379", req, aedis::adapt(), [](auto ec, auto n){
|
||||
expect_error(ec, net::error::misc_errors::eof, "test_quit2");
|
||||
});
|
||||
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------
|
||||
|
||||
net::awaitable<void>
|
||||
push_consumer1(std::shared_ptr<connection> db)
|
||||
{
|
||||
{
|
||||
auto [ec, n] = co_await db->async_read_push(aedis::adapt(), as_tuple(net::use_awaitable));
|
||||
expect_no_error(ec, "push_consumer1");
|
||||
}
|
||||
|
||||
{
|
||||
auto [ec, n] = co_await db->async_read_push(aedis::adapt(), as_tuple(net::use_awaitable));
|
||||
expect_error(ec, boost::asio::experimental::channel_errc::channel_cancelled, "push_consumer1");
|
||||
}
|
||||
}
|
||||
|
||||
// Tests whether a push is indeed delivered.
|
||||
void test_push1()
|
||||
{
|
||||
net::io_context ioc;
|
||||
auto db = std::make_shared<connection>(ioc);
|
||||
|
||||
request req;
|
||||
req.push("HELLO", 3);
|
||||
req.push("SUBSCRIBE", "channel");
|
||||
req.push("QUIT");
|
||||
|
||||
db->async_exec("127.0.0.1", "6379", req, aedis::adapt(), [](auto ec, auto r){
|
||||
expect_error(ec, net::error::misc_errors::eof, "test_push1");
|
||||
});
|
||||
|
||||
net::co_spawn(ioc.get_executor(), push_consumer1(db), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------
|
||||
|
||||
net::awaitable<void> run5(std::shared_ptr<connection> db)
|
||||
{
|
||||
{
|
||||
request req;
|
||||
req.push("QUIT");
|
||||
db->async_exec(req, aedis::adapt(), [](auto ec, auto n){
|
||||
expect_no_error(ec, "test_quit1");
|
||||
});
|
||||
|
||||
auto [ec] = co_await db->async_run("127.0.0.1", "6379", as_tuple(net::use_awaitable));
|
||||
expect_error(ec, net::error::misc_errors::eof, "run5a");
|
||||
}
|
||||
|
||||
{
|
||||
request req;
|
||||
req.push("QUIT");
|
||||
db->async_exec(req, aedis::adapt(), [](auto ec, auto n){
|
||||
expect_no_error(ec, "test_quit1");
|
||||
});
|
||||
|
||||
auto [ec] = co_await db->async_run("127.0.0.1", "6379", as_tuple(net::use_awaitable));
|
||||
expect_error(ec, net::error::misc_errors::eof, "run5a");
|
||||
}
|
||||
|
||||
co_return;
|
||||
}
|
||||
|
||||
// Test whether the client works after a reconnect.
|
||||
void test_reconnect()
|
||||
{
|
||||
net::io_context ioc;
|
||||
auto db = std::make_shared<connection>(ioc.get_executor());
|
||||
|
||||
net::co_spawn(ioc, run5(db), net::detached);
|
||||
ioc.run();
|
||||
std::cout << "Success: test_reconnect()" << std::endl;
|
||||
}
|
||||
|
||||
// Checks whether we get idle timeout when no push reader is set.
|
||||
void test_no_push_reader1()
|
||||
{
|
||||
connection::config cfg;
|
||||
|
||||
net::io_context ioc;
|
||||
auto db = std::make_shared<connection>(ioc, cfg);
|
||||
|
||||
request req;
|
||||
req.push("HELLO", 3);
|
||||
req.push("SUBSCRIBE", "channel");
|
||||
|
||||
db->async_exec("127.0.0.1", "6379", req, aedis::adapt(), [](auto ec, auto r){
|
||||
expect_error(ec, aedis::error::idle_timeout, "test_no_push_reader1");
|
||||
});
|
||||
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
void test_no_push_reader2()
|
||||
{
|
||||
connection::config cfg;
|
||||
|
||||
net::io_context ioc;
|
||||
auto db = std::make_shared<connection>(ioc, cfg);
|
||||
|
||||
request req; // Wrong command syntax.
|
||||
req.push("HELLO", 3);
|
||||
req.push("SUBSCRIBE");
|
||||
|
||||
db->async_exec("127.0.0.1", "6379", req, aedis::adapt(), [](auto ec, auto r){
|
||||
expect_error(ec, aedis::error::idle_timeout, "test_no_push_reader2");
|
||||
});
|
||||
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
void test_no_push_reader3()
|
||||
{
|
||||
connection::config cfg;
|
||||
|
||||
net::io_context ioc;
|
||||
auto db = std::make_shared<connection>(ioc, cfg);
|
||||
|
||||
request req; // Wrong command synthax.
|
||||
req.push("HELLO", 3);
|
||||
req.push("PING", "Message");
|
||||
req.push("SUBSCRIBE");
|
||||
|
||||
db->async_exec("127.0.0.1", "6379", req, aedis::adapt(), [](auto ec, auto r){
|
||||
expect_error(ec, aedis::error::idle_timeout, "test_no_push_reader3");
|
||||
});
|
||||
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
void test_idle()
|
||||
{
|
||||
connection::config cfg;
|
||||
cfg.resolve_timeout = std::chrono::seconds{1};
|
||||
cfg.connect_timeout = std::chrono::seconds{1};
|
||||
cfg.ping_interval = std::chrono::seconds{1};
|
||||
|
||||
net::io_context ioc;
|
||||
auto db = std::make_shared<connection>(ioc, cfg);
|
||||
|
||||
request req;
|
||||
req.push("HELLO", 3);
|
||||
req.push("CLIENT", "PAUSE", 5000);
|
||||
|
||||
db->async_exec(req, aedis::adapt(), [](auto ec, auto r){
|
||||
expect_no_error(ec, "test_idle");
|
||||
});
|
||||
|
||||
db->async_run("127.0.0.1", "6379", [](auto ec){
|
||||
expect_error(ec, aedis::error::idle_timeout, "test_idle");
|
||||
});
|
||||
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
auto handler =[](auto ec, auto...)
|
||||
{ std::cout << ec.message() << std::endl; };
|
||||
|
||||
void test_push2()
|
||||
{
|
||||
request req1;
|
||||
req1.push("HELLO", 3);
|
||||
req1.push("PING", "Message1");
|
||||
|
||||
request req2;
|
||||
req2.push("SUBSCRIBE", "channel");
|
||||
|
||||
request req3;
|
||||
req3.push("PING", "Message2");
|
||||
req3.push("QUIT");
|
||||
|
||||
std::tuple<std::string, std::string> resp;
|
||||
|
||||
net::io_context ioc;
|
||||
connection db{ioc};
|
||||
db.async_exec(req1, aedis::adapt(resp), handler);
|
||||
db.async_exec(req2, aedis::adapt(resp), handler);
|
||||
db.async_exec(req3, aedis::adapt(resp), handler);
|
||||
db.async_run("127.0.0.1", "6379", [&db](auto ec, auto...) {
|
||||
std::cout << ec.message() << std::endl;
|
||||
db.cancel_requests();
|
||||
});
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
net::awaitable<void>
|
||||
push_consumer3(std::shared_ptr<connection> db)
|
||||
{
|
||||
for (;;)
|
||||
co_await db->async_read_push(aedis::adapt(), net::use_awaitable);
|
||||
}
|
||||
|
||||
void test_push3()
|
||||
{
|
||||
request req1;
|
||||
req1.push("HELLO", 3);
|
||||
req1.push("PING", "Message1");
|
||||
|
||||
request req2;
|
||||
req2.push("SUBSCRIBE", "channel");
|
||||
|
||||
request req3;
|
||||
req3.push("QUIT");
|
||||
|
||||
net::io_context ioc;
|
||||
auto db = std::make_shared<connection>(ioc);
|
||||
db->async_exec(req1, aedis::adapt(), handler);
|
||||
db->async_exec(req2, aedis::adapt(), handler);
|
||||
db->async_exec(req2, aedis::adapt(), handler);
|
||||
db->async_exec(req1, aedis::adapt(), handler);
|
||||
db->async_exec(req2, aedis::adapt(), handler);
|
||||
db->async_exec(req1, aedis::adapt(), handler);
|
||||
db->async_exec(req2, aedis::adapt(), handler);
|
||||
db->async_exec(req2, aedis::adapt(), handler);
|
||||
db->async_exec(req1, aedis::adapt(), handler);
|
||||
db->async_exec(req2, aedis::adapt(), handler);
|
||||
db->async_exec(req3, aedis::adapt(), handler);
|
||||
db->async_run("127.0.0.1", "6379", [db](auto ec, auto...) {
|
||||
db->cancel_requests();
|
||||
});
|
||||
net::co_spawn(ioc.get_executor(), push_consumer3(db), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
void test_exec_while_processing()
|
||||
{
|
||||
request req1;
|
||||
req1.push("HELLO", 3);
|
||||
req1.push("PING", "Message1");
|
||||
|
||||
request req2;
|
||||
req2.push("SUBSCRIBE", "channel");
|
||||
|
||||
request req3;
|
||||
req3.push("QUIT");
|
||||
|
||||
net::io_context ioc;
|
||||
auto db = std::make_shared<connection>(ioc);
|
||||
|
||||
db->async_exec(req1, aedis::adapt(), [db, &req1](auto ec, auto) {
|
||||
db->async_exec(req1, aedis::adapt(), handler);
|
||||
});
|
||||
|
||||
db->async_exec(req1, aedis::adapt(), [db, &req2](auto ec, auto) {
|
||||
db->async_exec(req2, aedis::adapt(), handler);
|
||||
});
|
||||
|
||||
db->async_exec(req2, aedis::adapt(), [db, &req2](auto ec, auto) {
|
||||
db->async_exec(req2, aedis::adapt(), handler);
|
||||
});
|
||||
|
||||
db->async_exec(req1, aedis::adapt(), [db, &req1](auto ec, auto) {
|
||||
db->async_exec(req1, aedis::adapt(), handler);
|
||||
});
|
||||
|
||||
db->async_exec(req2, aedis::adapt(), [db, &req3](auto ec, auto) {
|
||||
db->async_exec(req3, aedis::adapt(), handler);
|
||||
});
|
||||
|
||||
db->async_run("127.0.0.1", "6379", [db](auto ec, auto...) {
|
||||
db->cancel_requests();
|
||||
});
|
||||
net::co_spawn(ioc.get_executor(), push_consumer3(db), net::detached);
|
||||
ioc.run();
|
||||
std::cout << "Success: test_exec_while_processing()" << std::endl;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
test_resolve();
|
||||
test_connect();
|
||||
test_quit1();
|
||||
test_quit2();
|
||||
test_push1();
|
||||
test_push2();
|
||||
test_push3();
|
||||
test_no_push_reader1();
|
||||
test_no_push_reader2();
|
||||
test_no_push_reader3();
|
||||
test_reconnect();
|
||||
test_exec_while_processing();
|
||||
|
||||
// Must come last as it send a client pause.
|
||||
test_idle();
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <map>
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
|
||||
#include <boost/system/errc.hpp>
|
||||
#include <boost/asio/awaitable.hpp>
|
||||
@@ -15,19 +16,28 @@
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <boost/asio/co_spawn.hpp>
|
||||
#include <boost/beast/_experimental/test/stream.hpp>
|
||||
#define BOOST_TEST_MODULE low level
|
||||
#include <boost/test/included/unit_test.hpp>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
#include <aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
#include "check.hpp"
|
||||
#include "config.h"
|
||||
// TODO: Test with empty strings.
|
||||
|
||||
|
||||
namespace std
|
||||
{
|
||||
auto operator==(aedis::ignore, aedis::ignore) noexcept {return true;}
|
||||
auto operator!=(aedis::ignore, aedis::ignore) noexcept {return false;}
|
||||
}
|
||||
|
||||
namespace net = boost::asio;
|
||||
namespace resp3 = aedis::resp3;
|
||||
|
||||
using test_stream = boost::beast::test::stream;
|
||||
using aedis::adapter::adapt;
|
||||
using aedis::adapter::adapt2;
|
||||
using node_type = aedis::resp3::node<std::string>;
|
||||
using vec_node_type = std::vector<node_type>;
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
|
||||
@@ -36,23 +46,31 @@ struct expect {
|
||||
std::string in;
|
||||
Result expected;
|
||||
std::string name;
|
||||
boost::system::error_code ec;
|
||||
boost::system::error_code ec{};
|
||||
};
|
||||
|
||||
template <class Result>
|
||||
auto make_expected(std::string in, Result expected, std::string name, boost::system::error_code ec = {})
|
||||
{
|
||||
return expect<Result>{in, expected, name, ec};
|
||||
}
|
||||
|
||||
template <class Result>
|
||||
void test_sync(net::any_io_executor ex, expect<Result> e)
|
||||
{
|
||||
std::cout << e.name << std::endl;
|
||||
std::string rbuffer;
|
||||
test_stream ts {ex};
|
||||
ts.append(e.in);
|
||||
Result result;
|
||||
boost::system::error_code ec;
|
||||
resp3::read(ts, net::dynamic_buffer(rbuffer), adapt(result), ec);
|
||||
expect_error(ec, e.ec);
|
||||
resp3::read(ts, net::dynamic_buffer(rbuffer), adapt2(result), ec);
|
||||
BOOST_CHECK_EQUAL(ec, e.ec);
|
||||
if (e.ec)
|
||||
return;
|
||||
check_empty(rbuffer);
|
||||
expect_eq(result, e.expected, e.name);
|
||||
BOOST_TEST(rbuffer.empty());
|
||||
auto const res = result == e.expected;
|
||||
BOOST_TEST(res);
|
||||
}
|
||||
|
||||
template <class Result>
|
||||
@@ -74,19 +92,20 @@ public:
|
||||
void run()
|
||||
{
|
||||
auto self = this->shared_from_this();
|
||||
auto f = [self](auto ec, auto n)
|
||||
auto f = [self](auto ec, auto)
|
||||
{
|
||||
expect_error(ec, self->data_.ec);
|
||||
BOOST_CHECK_EQUAL(ec, self->data_.ec);
|
||||
if (self->data_.ec)
|
||||
return;
|
||||
check_empty(self->rbuffer_);
|
||||
expect_eq(self->result_, self->data_.expected, self->data_.name);
|
||||
BOOST_TEST(self->rbuffer_.empty());
|
||||
auto const res = self->result_ == self->data_.expected;
|
||||
BOOST_TEST(res);
|
||||
};
|
||||
|
||||
resp3::async_read(
|
||||
ts_,
|
||||
net::dynamic_buffer(rbuffer_),
|
||||
adapt(result_),
|
||||
adapt2(result_),
|
||||
f);
|
||||
}
|
||||
};
|
||||
@@ -97,123 +116,55 @@ void test_async(net::any_io_executor ex, expect<Result> e)
|
||||
std::make_shared<async_test<Result>>(ex, e)->run();
|
||||
}
|
||||
|
||||
void test_number(net::io_context& ioc)
|
||||
{
|
||||
boost::optional<int> ok;
|
||||
ok = 11;
|
||||
|
||||
// Success
|
||||
auto const in01 = expect<node_type>{":3\r\n", node_type{resp3::type::number, 1UL, 0UL, {"3"}}, "number.node (positive)"};
|
||||
auto const in02 = expect<node_type>{":-3\r\n", node_type{resp3::type::number, 1UL, 0UL, {"-3"}}, "number.node (negative)"};
|
||||
auto const in03 = expect<int>{":11\r\n", int{11}, "number.int"};
|
||||
auto const in04 = expect<boost::optional<int>>{":11\r\n", ok, "number.optional.int"};
|
||||
auto const in05 = expect<std::tuple<int>>{"*1\r\n:11\r\n", std::tuple<int>{11}, "number.tuple.int"};
|
||||
auto const in06 = expect<boost::optional<int>>{"%11\r\n", boost::optional<int>{}, "number.optional.int", aedis::make_error_code(aedis::error::expects_simple_type)};
|
||||
auto const in07 = expect<std::set<std::string>>{":11\r\n", std::set<std::string>{}, "number.optional.int", aedis::make_error_code(aedis::error::expects_set_type)};
|
||||
auto const in08 = expect<std::unordered_set<std::string>>{":11\r\n", std::unordered_set<std::string>{}, "number.optional.int", aedis::make_error_code(aedis::error::expects_set_type)};
|
||||
auto const in09 = expect<std::map<std::string, std::string>>{":11\r\n", std::map<std::string, std::string>{}, "number.optional.int", aedis::make_error_code(aedis::error::expects_map_type)};
|
||||
auto const in10 = expect<std::unordered_map<std::string, std::string>>{":11\r\n", std::unordered_map<std::string, std::string>{}, "number.optional.int", aedis::make_error_code(aedis::error::expects_map_type)};
|
||||
auto const in11 = expect<std::list<std::string>>{":11\r\n", std::list<std::string>{}, "number.optional.int", aedis::make_error_code(aedis::error::expects_aggregate_type)};
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
|
||||
test_sync(ex, in01);
|
||||
test_sync(ex, in02);
|
||||
test_sync(ex, in03);
|
||||
test_sync(ex, in04);
|
||||
test_sync(ex, in05);
|
||||
test_sync(ex, in06);
|
||||
test_sync(ex, in07);
|
||||
test_sync(ex, in08);
|
||||
test_sync(ex, in09);
|
||||
test_sync(ex, in10);
|
||||
test_sync(ex, in11);
|
||||
|
||||
test_async(ex, in01);
|
||||
test_async(ex, in02);
|
||||
test_async(ex, in03);
|
||||
test_async(ex, in04);
|
||||
test_async(ex, in05);
|
||||
test_async(ex, in06);
|
||||
test_async(ex, in07);
|
||||
test_async(ex, in08);
|
||||
test_async(ex, in09);
|
||||
test_async(ex, in10);
|
||||
test_async(ex, in11);
|
||||
}
|
||||
|
||||
void test_bool(net::io_context& ioc)
|
||||
{
|
||||
boost::optional<bool> ok;
|
||||
ok = true;
|
||||
|
||||
// Success.
|
||||
auto const in08 = expect<node_type>{"#f\r\n", node_type{resp3::type::boolean, 1UL, 0UL, {"f"}}, "bool.node (false)"};
|
||||
auto const in09 = expect<node_type>{"#t\r\n", node_type{resp3::type::boolean, 1UL, 0UL, {"t"}}, "bool.node (true)"};
|
||||
auto const in10 = expect<bool>{"#t\r\n", bool{true}, "bool.bool (true)"};
|
||||
auto const in11 = expect<bool>{"#f\r\n", bool{false}, "bool.bool (true)"};
|
||||
auto const in13 = expect<boost::optional<bool>>{"#t\r\n", ok, "optional.int"};
|
||||
|
||||
// Error
|
||||
auto const in01 = expect<boost::optional<bool>>{"#11\r\n", boost::optional<bool>{}, "bool.error", aedis::make_error_code(aedis::error::unexpected_bool_value)};
|
||||
auto const in03 = expect<std::set<int>>{"#t\r\n", std::set<int>{}, "bool.error", aedis::make_error_code(aedis::error::expects_set_type)};
|
||||
auto const in04 = expect<std::unordered_set<int>>{"#t\r\n", std::unordered_set<int>{}, "bool.error", aedis::make_error_code(aedis::error::expects_set_type)};
|
||||
auto const in05 = expect<std::map<int, int>>{"#t\r\n", std::map<int, int>{}, "bool.error", aedis::make_error_code(aedis::error::expects_map_type)};
|
||||
auto const in06 = expect<std::unordered_map<int, int>>{"#t\r\n", std::unordered_map<int, int>{}, "bool.error", aedis::make_error_code(aedis::error::expects_map_type)};
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
|
||||
test_sync(ex, in01);
|
||||
test_sync(ex, in03);
|
||||
test_sync(ex, in04);
|
||||
test_sync(ex, in05);
|
||||
test_sync(ex, in06);
|
||||
test_sync(ex, in08);
|
||||
test_sync(ex, in09);
|
||||
test_sync(ex, in10);
|
||||
test_sync(ex, in11);
|
||||
|
||||
test_async(ex, in01);
|
||||
test_async(ex, in03);
|
||||
test_async(ex, in04);
|
||||
test_async(ex, in05);
|
||||
test_async(ex, in06);
|
||||
test_async(ex, in08);
|
||||
test_async(ex, in09);
|
||||
test_async(ex, in10);
|
||||
test_async(ex, in11);
|
||||
}
|
||||
|
||||
void test_streamed_string(net::io_context& ioc)
|
||||
{
|
||||
std::string const wire = "$?\r\n;4\r\nHell\r\n;5\r\no wor\r\n;1\r\nd\r\n;0\r\n";
|
||||
|
||||
std::vector<node_type> e1a
|
||||
{ {aedis::resp3::type::streamed_string_part, 1, 1, "Hell"}
|
||||
, {aedis::resp3::type::streamed_string_part, 1, 1, "o wor"}
|
||||
, {aedis::resp3::type::streamed_string_part, 1, 1, "d"}
|
||||
, {aedis::resp3::type::streamed_string_part, 1, 1, ""}
|
||||
};
|
||||
|
||||
std::vector<node_type> e1b { {resp3::type::streamed_string_part, 1UL, 1UL, {}} };
|
||||
|
||||
auto const in01 = expect<std::vector<node_type>>{wire, e1a, "streamed_string.node"};
|
||||
auto const in03 = expect<std::string>{wire, std::string{"Hello word"}, "streamed_string.string"};
|
||||
auto const in02 = expect<std::vector<node_type>>{"$?\r\n;0\r\n", e1b, "streamed_string.node.empty"};
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
|
||||
test_sync(ex, in01);
|
||||
test_sync(ex, in02);
|
||||
test_sync(ex, in03);
|
||||
|
||||
test_async(ex, in01);
|
||||
test_async(ex, in02);
|
||||
test_async(ex, in03);
|
||||
}
|
||||
|
||||
void test_push(net::io_context& ioc)
|
||||
std::optional<int> op_int_ok = 11;
|
||||
std::optional<bool> op_bool_ok = true;
|
||||
|
||||
std::string const streamed_string_wire = "$?\r\n;4\r\nHell\r\n;5\r\no wor\r\n;1\r\nd\r\n;0\r\n";
|
||||
std::string const streamed_string_wire_error = "$?\r\n;b\r\nHell\r\n;5\r\no wor\r\n;1\r\nd\r\n;0\r\n";
|
||||
// TODO: Test a streamed string that is not finished with a string of
|
||||
// size 0 but other command comes in.
|
||||
std::vector<node_type> streamed_string_e1
|
||||
{ {aedis::resp3::type::streamed_string_part, 1, 1, "Hell"}
|
||||
, {aedis::resp3::type::streamed_string_part, 1, 1, "o wor"}
|
||||
, {aedis::resp3::type::streamed_string_part, 1, 1, "d"}
|
||||
, {aedis::resp3::type::streamed_string_part, 1, 1, ""}
|
||||
};
|
||||
|
||||
std::vector<node_type> streamed_string_e2 { {resp3::type::streamed_string_part, 1UL, 1UL, {}} };
|
||||
|
||||
|
||||
#define NUMBER_TEST_CONDITIONS(test) \
|
||||
test(ex, make_expected("#11\r\n", std::optional<bool>{}, "bool.error", aedis::error::unexpected_bool_value)); \
|
||||
test(ex, make_expected("#f\r\n", bool{false}, "bool.bool (true)")); \
|
||||
test(ex, make_expected("#f\r\n", node_type{resp3::type::boolean, 1UL, 0UL, {"f"}}, "bool.node (false)")); \
|
||||
test(ex, make_expected("#t\r\n", bool{true}, "bool.bool (true)")); \
|
||||
test(ex, make_expected("#t\r\n", node_type{resp3::type::boolean, 1UL, 0UL, {"t"}}, "bool.node (true)")); \
|
||||
test(ex, make_expected("#t\r\n", op_bool_ok, "optional.int")); \
|
||||
test(ex, make_expected("#t\r\n", std::map<int, int>{}, "bool.error", aedis::error::expects_resp3_map)); \
|
||||
test(ex, make_expected("#t\r\n", std::set<int>{}, "bool.error", aedis::error::expects_resp3_set)); \
|
||||
test(ex, make_expected("#t\r\n", std::unordered_map<int, int>{}, "bool.error", aedis::error::expects_resp3_map)); \
|
||||
test(ex, make_expected("#t\r\n", std::unordered_set<int>{}, "bool.error", aedis::error::expects_resp3_set)); \
|
||||
test(ex, make_expected("$?\r\n;0\r\n", streamed_string_e2, "streamed_string.node.empty")); \
|
||||
test(ex, make_expected("%11\r\n", std::optional<int>{}, "number.optional.int.error", aedis::error::expects_resp3_simple_type));; \
|
||||
test(ex, make_expected("*1\r\n:11\r\n", std::tuple<int>{11}, "number.tuple.int")); \
|
||||
test(ex, make_expected(":-3\r\n", node_type{resp3::type::number, 1UL, 0UL, {"-3"}}, "number.node (negative)")); \
|
||||
test(ex, make_expected(":11\r\n", int{11}, "number.int")); \
|
||||
test(ex, make_expected(":11\r\n", op_int_ok, "number.optional.int")); \
|
||||
test(ex, make_expected(":11\r\n", std::list<std::string>{}, "number.optional.int", aedis::error::expects_resp3_aggregate)); \
|
||||
test(ex, make_expected(":11\r\n", std::map<std::string, std::string>{}, "number.optional.int", aedis::error::expects_resp3_map)); \
|
||||
test(ex, make_expected(":11\r\n", std::set<std::string>{}, "number.optional.int", aedis::error::expects_resp3_set)); \
|
||||
test(ex, make_expected(":11\r\n", std::unordered_map<std::string, std::string>{}, "number.optional.int", aedis::error::expects_resp3_map)); \
|
||||
test(ex, make_expected(":11\r\n", std::unordered_set<std::string>{}, "number.optional.int", aedis::error::expects_resp3_set)); \
|
||||
test(ex, make_expected(":3\r\n", node_type{resp3::type::number, 1UL, 0UL, {"3"}}, "number.node (positive)")); \
|
||||
test(ex, make_expected("_\r\n", int{0}, "number.int.error.null", aedis::error::resp3_null)); \
|
||||
test(ex, make_expected(streamed_string_wire, std::string{"Hello word"}, "streamed_string.string")); \
|
||||
test(ex, make_expected(streamed_string_wire, int{}, "streamed_string.string", aedis::error::not_a_number)); \
|
||||
test(ex, make_expected(streamed_string_wire, streamed_string_e1, "streamed_string.node")); \
|
||||
test(ex, make_expected(streamed_string_wire_error, std::string{}, "streamed_string.error", aedis::error::not_a_number)); \
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_push)
|
||||
{
|
||||
net::io_context ioc;
|
||||
std::string const wire = ">4\r\n+pubsub\r\n+message\r\n+some-channel\r\n+some message\r\n";
|
||||
|
||||
std::vector<node_type> e1a
|
||||
@@ -236,19 +187,22 @@ void test_push(net::io_context& ioc)
|
||||
|
||||
test_async(ex, in01);
|
||||
test_async(ex, in02);
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
void test_map(net::io_context& ioc)
|
||||
BOOST_AUTO_TEST_CASE(test_map)
|
||||
{
|
||||
net::io_context ioc;
|
||||
using map_type = std::map<std::string, std::string>;
|
||||
using mmap_type = std::multimap<std::string, std::string>;
|
||||
using umap_type = std::unordered_map<std::string, std::string>;
|
||||
using mumap_type = std::unordered_multimap<std::string, std::string>;
|
||||
using vec_type = std::vector<std::string>;
|
||||
using op_map_type = boost::optional<std::map<std::string, std::string>>;
|
||||
using op_vec_type = boost::optional<std::vector<std::string>>;
|
||||
using op_map_type = std::optional<std::map<std::string, std::string>>;
|
||||
using op_vec_type = std::optional<std::vector<std::string>>;
|
||||
using tuple_type = std::tuple<std::string, std::string, std::string, std::string, std::string, std::string, std::string, std::string>;
|
||||
|
||||
std::string const wire2 = "*3\r\n$2\r\n11\r\n$2\r\n22\r\n$1\r\n3\r\n";
|
||||
std::string const wire = "%4\r\n$4\r\nkey1\r\n$6\r\nvalue1\r\n$4\r\nkey2\r\n$6\r\nvalue2\r\n$4\r\nkey3\r\n$6\r\nvalue3\r\n$4\r\nkey3\r\n$6\r\nvalue3\r\n";
|
||||
|
||||
std::vector<node_type> expected_1a
|
||||
@@ -319,8 +273,10 @@ void test_map(net::io_context& ioc)
|
||||
auto const in07 = expect<op_map_type>{wire, expected_1d, "map.optional.map"};
|
||||
auto const in08 = expect<op_vec_type>{wire, expected_1e, "map.optional.vector"};
|
||||
auto const in09 = expect<std::tuple<op_map_type>>{"*1\r\n" + wire, std::tuple<op_map_type>{expected_1d}, "map.transaction.optional.map"};
|
||||
auto const in10 = expect<int>{"%11\r\n", int{}, "map.invalid.int", aedis::make_error_code(aedis::error::expects_simple_type)};
|
||||
auto const in10 = expect<int>{"%11\r\n", int{}, "map.invalid.int", aedis::error::expects_resp3_simple_type};
|
||||
auto const in11 = expect<tuple_type>{wire, e1f, "map.tuple"};
|
||||
auto const in12 = expect<map_type>{wire2, map_type{}, "map.error", aedis::error::expects_resp3_map};
|
||||
auto const in13 = expect<map_type>{"_\r\n", map_type{}, "map.null", aedis::error::resp3_null};
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
|
||||
@@ -330,11 +286,14 @@ void test_map(net::io_context& ioc)
|
||||
test_sync(ex, in03);
|
||||
test_sync(ex, in04);
|
||||
test_sync(ex, in05);
|
||||
test_sync(ex, in06);
|
||||
test_sync(ex, in07);
|
||||
test_sync(ex, in08);
|
||||
test_sync(ex, in09);
|
||||
test_sync(ex, in00);
|
||||
test_sync(ex, in10);
|
||||
test_sync(ex, in11);
|
||||
test_sync(ex, in12);
|
||||
test_sync(ex, in13);
|
||||
|
||||
test_async(ex, in00);
|
||||
test_async(ex, in01);
|
||||
@@ -342,11 +301,15 @@ void test_map(net::io_context& ioc)
|
||||
test_async(ex, in03);
|
||||
test_async(ex, in04);
|
||||
test_async(ex, in05);
|
||||
test_async(ex, in06);
|
||||
test_async(ex, in07);
|
||||
test_async(ex, in08);
|
||||
test_async(ex, in09);
|
||||
test_async(ex, in00);
|
||||
test_async(ex, in10);
|
||||
test_async(ex, in11);
|
||||
test_async(ex, in12);
|
||||
test_async(ex, in13);
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
void test_attribute(net::io_context& ioc)
|
||||
@@ -377,9 +340,16 @@ void test_attribute(net::io_context& ioc)
|
||||
test_async(ex, in02);
|
||||
}
|
||||
|
||||
void test_array(net::io_context& ioc)
|
||||
BOOST_AUTO_TEST_CASE(test_array)
|
||||
{
|
||||
using tuple_type = std::tuple<int, int>;
|
||||
using array_type = std::array<int, 3>;
|
||||
using array_type2 = std::array<int, 1>;
|
||||
|
||||
net::io_context ioc;
|
||||
char const* wire = "*3\r\n$2\r\n11\r\n$2\r\n22\r\n$1\r\n3\r\n";
|
||||
char const* wire_nested = "*1\r\n*1\r\n$2\r\nab\r\n";
|
||||
char const* wire_nested2 = "*1\r\n*1\r\n*1\r\n*1\r\n*1\r\n*1\r\na\r\n";
|
||||
|
||||
std::vector<node_type> e1a
|
||||
{ {resp3::type::array, 3UL, 0UL, {}}
|
||||
@@ -392,7 +362,7 @@ void test_array(net::io_context& ioc)
|
||||
std::vector<std::string> const e1c{"11", "22", "3"};
|
||||
std::vector<std::string> const e1d{};
|
||||
std::vector<node_type> const e1e{{resp3::type::array, 0UL, 0UL, {}}};
|
||||
std::array<int, 3> const e1f{11, 22, 3};
|
||||
array_type const e1f{11, 22, 3};
|
||||
std::list<int> const e1g{11, 22, 3};
|
||||
std::deque<int> const e1h{11, 22, 3};
|
||||
|
||||
@@ -401,9 +371,17 @@ void test_array(net::io_context& ioc)
|
||||
auto const in03 = expect<std::vector<node_type>>{"*0\r\n", e1e, "array.node.empty"};
|
||||
auto const in04 = expect<std::vector<std::string>>{"*0\r\n", e1d, "array.string.empty"};
|
||||
auto const in05 = expect<std::vector<std::string>>{wire, e1c, "array.string"};
|
||||
auto const in06 = expect<std::array<int, 3>>{wire, e1f, "array.array"};
|
||||
auto const in06 = expect<array_type>{wire, e1f, "array.array"};
|
||||
auto const in07 = expect<std::list<int>>{wire, e1g, "array.list"};
|
||||
auto const in08 = expect<std::deque<int>>{wire, e1h, "array.deque"};
|
||||
auto const in09 = expect<std::vector<int>>{"_\r\n", std::vector<int>{}, "array.vector", aedis::error::resp3_null};
|
||||
auto const in10 = expect<std::list<int>>{"_\r\n", std::list<int>{}, "array.list", aedis::error::resp3_null};
|
||||
auto const in11 = expect<array_type>{"_\r\n", array_type{}, "array.null", aedis::error::resp3_null};
|
||||
auto const in12 = expect<tuple_type>{wire, tuple_type{}, "array.list", aedis::error::incompatible_size};
|
||||
auto const in13 = expect<array_type2>{wire_nested, array_type2{}, "array.nested", aedis::error::nested_aggregate_not_supported};
|
||||
auto const in14 = expect<array_type2>{wire, array_type2{}, "array.null", aedis::error::incompatible_size};
|
||||
auto const in15 = expect<array_type2>{":3\r\n", array_type2{}, "array.array", aedis::error::expects_resp3_aggregate};
|
||||
auto const in16 = expect<vec_node_type>{wire_nested2, vec_node_type{}, "array.depth.exceeds", aedis::error::exceeeds_max_nested_depth};
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
|
||||
@@ -415,6 +393,14 @@ void test_array(net::io_context& ioc)
|
||||
test_sync(ex, in06);
|
||||
test_sync(ex, in07);
|
||||
test_sync(ex, in08);
|
||||
test_sync(ex, in09);
|
||||
test_sync(ex, in10);
|
||||
test_sync(ex, in11);
|
||||
test_sync(ex, in12);
|
||||
test_sync(ex, in13);
|
||||
test_sync(ex, in14);
|
||||
test_sync(ex, in15);
|
||||
test_sync(ex, in16);
|
||||
|
||||
test_async(ex, in01);
|
||||
test_async(ex, in02);
|
||||
@@ -424,18 +410,31 @@ void test_array(net::io_context& ioc)
|
||||
test_async(ex, in06);
|
||||
test_async(ex, in07);
|
||||
test_async(ex, in08);
|
||||
test_async(ex, in09);
|
||||
test_async(ex, in10);
|
||||
test_async(ex, in11);
|
||||
test_async(ex, in12);
|
||||
test_async(ex, in13);
|
||||
test_async(ex, in14);
|
||||
test_async(ex, in15);
|
||||
test_async(ex, in16);
|
||||
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
void test_set(net::io_context& ioc)
|
||||
BOOST_AUTO_TEST_CASE(test_set)
|
||||
{
|
||||
net::io_context ioc;
|
||||
using set_type = std::set<std::string>;
|
||||
using mset_type = std::multiset<std::string>;
|
||||
using uset_type = std::unordered_set<std::string>;
|
||||
using muset_type = std::unordered_multiset<std::string>;
|
||||
using vec_type = std::vector<std::string>;
|
||||
using op_vec_type = boost::optional<std::vector<std::string>>;
|
||||
using op_vec_type = std::optional<std::vector<std::string>>;
|
||||
|
||||
std::string const wire2 = "*3\r\n$2\r\n11\r\n$2\r\n22\r\n$1\r\n3\r\n";
|
||||
std::string const wire = "~6\r\n+orange\r\n+apple\r\n+one\r\n+two\r\n+three\r\n+orange\r\n";
|
||||
|
||||
std::vector<node_type> const expected1a
|
||||
{ {resp3::type::set, 6UL, 0UL, {}}
|
||||
, {resp3::type::simple_string, 1UL, 1UL, {"orange"}}
|
||||
@@ -462,6 +461,7 @@ void test_set(net::io_context& ioc)
|
||||
auto const in06 = expect<uset_type>{wire, e1c, "set.unordered_set"};
|
||||
auto const in07 = expect<muset_type>{wire, e1g, "set.unordered_multiset"};
|
||||
auto const in08 = expect<std::tuple<uset_type>>{"*1\r\n" + wire, std::tuple<uset_type>{e1c}, "set.tuple"};
|
||||
auto const in09 = expect<set_type>{wire2, set_type{}, "set.error", aedis::error::expects_resp3_set};
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
|
||||
@@ -474,6 +474,7 @@ void test_set(net::io_context& ioc)
|
||||
test_sync(ex, in06);
|
||||
test_sync(ex, in07);
|
||||
test_sync(ex, in08);
|
||||
test_sync(ex, in09);
|
||||
|
||||
test_async(ex, in00);
|
||||
test_async(ex, in01);
|
||||
@@ -484,24 +485,32 @@ void test_set(net::io_context& ioc)
|
||||
test_async(ex, in06);
|
||||
test_async(ex, in07);
|
||||
test_async(ex, in08);
|
||||
test_async(ex, in09);
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
void test_simple_error(net::io_context& ioc)
|
||||
BOOST_AUTO_TEST_CASE(test_simple_error)
|
||||
{
|
||||
auto const in01 = expect<node_type>{"-Error\r\n", node_type{resp3::type::simple_error, 1UL, 0UL, {"Error"}}, "simple_error.node", aedis::make_error_code(aedis::error::simple_error)};
|
||||
auto const in02 = expect<node_type>{"-\r\n", node_type{resp3::type::simple_error, 1UL, 0UL, {""}}, "simple_error.node.empty", aedis::make_error_code(aedis::error::simple_error)};
|
||||
net::io_context ioc;
|
||||
auto const in01 = expect<node_type>{"-Error\r\n", node_type{resp3::type::simple_error, 1UL, 0UL, {"Error"}}, "simple_error.node", aedis::error::resp3_simple_error};
|
||||
auto const in02 = expect<node_type>{"-\r\n", node_type{resp3::type::simple_error, 1UL, 0UL, {""}}, "simple_error.node.empty", aedis::error::resp3_simple_error};
|
||||
auto const in03 = expect<aedis::ignore>{"-Error\r\n", aedis::ignore{}, "simple_error.not.ignore.error", aedis::error::resp3_simple_error};
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
|
||||
test_sync(ex, in01);
|
||||
test_sync(ex, in02);
|
||||
test_sync(ex, in03);
|
||||
|
||||
test_async(ex, in01);
|
||||
test_async(ex, in02);
|
||||
test_async(ex, in03);
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
void test_blob_string(net::io_context& ioc)
|
||||
BOOST_AUTO_TEST_CASE(test_blob_string)
|
||||
{
|
||||
net::io_context ioc;
|
||||
std::string str(100000, 'a');
|
||||
str[1000] = '\r';
|
||||
str[1001] = '\n';
|
||||
@@ -529,102 +538,17 @@ void test_blob_string(net::io_context& ioc)
|
||||
test_async(ex, in02);
|
||||
test_async(ex, in03);
|
||||
test_async(ex, in04);
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
void test_double(net::io_context& ioc)
|
||||
BOOST_AUTO_TEST_CASE(test_double)
|
||||
{
|
||||
// TODO: Add test for double.
|
||||
net::io_context ioc;
|
||||
auto const in01 = expect<node_type>{",1.23\r\n", node_type{resp3::type::doublean, 1UL, 0UL, {"1.23"}}, "double.node"};
|
||||
auto const in02 = expect<node_type>{",inf\r\n", node_type{resp3::type::doublean, 1UL, 0UL, {"inf"}}, "double.node (inf)"};
|
||||
auto const in03 = expect<node_type>{",-inf\r\n", node_type{resp3::type::doublean, 1UL, 0UL, {"-inf"}}, "double.node (-inf)"};
|
||||
auto const in04 = expect<double>{",1.23\r\n", double{1.23}, "double.double"};
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
|
||||
test_sync(ex, in01);
|
||||
test_sync(ex, in02);
|
||||
test_sync(ex, in03);
|
||||
test_sync(ex, in04);
|
||||
|
||||
test_async(ex, in01);
|
||||
test_async(ex, in02);
|
||||
test_async(ex, in03);
|
||||
test_async(ex, in04);
|
||||
}
|
||||
|
||||
void test_blob_error(net::io_context& ioc)
|
||||
{
|
||||
auto const in01 = expect<node_type>{"!21\r\nSYNTAX invalid syntax\r\n", node_type{resp3::type::blob_error, 1UL, 0UL, {"SYNTAX invalid syntax"}}, "blob_error", aedis::make_error_code(aedis::error::blob_error)};
|
||||
auto const in02 = expect<node_type>{"!0\r\n\r\n", node_type{resp3::type::blob_error, 1UL, 0UL, {}}, "blob_error.empty", aedis::make_error_code(aedis::error::blob_error)};
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
|
||||
test_sync(ex, in01);
|
||||
test_sync(ex, in02);
|
||||
|
||||
test_async(ex, in01);
|
||||
test_async(ex, in02);
|
||||
}
|
||||
|
||||
void test_verbatim_string(net::io_context& ioc)
|
||||
{
|
||||
auto const in01 = expect<node_type>{"=15\r\ntxt:Some string\r\n", node_type{resp3::type::verbatim_string, 1UL, 0UL, {"txt:Some string"}}, "verbatim_string"};
|
||||
auto const in02 = expect<node_type>{"=0\r\n\r\n", node_type{resp3::type::verbatim_string, 1UL, 0UL, {}}, "verbatim_string.empty"};
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
|
||||
test_sync(ex, in01);
|
||||
test_sync(ex, in02);
|
||||
|
||||
test_async(ex, in01);
|
||||
test_async(ex, in02);
|
||||
}
|
||||
|
||||
void test_big_number(net::io_context& ioc)
|
||||
{
|
||||
auto const in01 = expect<node_type>{"(3492890328409238509324850943850943825024385\r\n", node_type{resp3::type::big_number, 1UL, 0UL, {"3492890328409238509324850943850943825024385"}}, "big_number.node"};
|
||||
auto const in02 = expect<int>{"(\r\n", int{}, "big_number.error (empty field)", aedis::make_error_code(aedis::error::empty_field)};
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
|
||||
test_sync(ex, in01);
|
||||
test_sync(ex, in02);
|
||||
|
||||
test_async(ex, in01);
|
||||
test_async(ex, in02);
|
||||
}
|
||||
|
||||
void test_simple_string(net::io_context& ioc)
|
||||
{
|
||||
boost::optional<std::string> ok1, ok2;
|
||||
ok1 = "OK";
|
||||
ok2 = "";
|
||||
|
||||
auto in00 = expect<std::string>{"+OK\r\n", std::string{"OK"}, "simple_string.string"};
|
||||
auto in01 = expect<std::string>{"+\r\n", std::string{""}, "simple_string.string.empty"};
|
||||
auto in02 = expect<boost::optional<std::string>>{"+OK\r\n", boost::optional<std::string>{"OK"}, "simple_string.optional"};
|
||||
auto in03 = expect<boost::optional<std::string>>{"+\r\n", boost::optional<std::string>{""}, "simple_string.optional.empty"};
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
|
||||
test_sync(ex, in00);
|
||||
test_sync(ex, in01);
|
||||
test_sync(ex, in02);
|
||||
test_sync(ex, in03);
|
||||
|
||||
test_async(ex, in00);
|
||||
test_async(ex, in01);
|
||||
test_async(ex, in02);
|
||||
test_async(ex, in03);
|
||||
}
|
||||
|
||||
void test_resp3(net::io_context& ioc)
|
||||
{
|
||||
auto const in01 = expect<int>{"s11\r\n", int{}, "number.error", aedis::make_error_code(aedis::error::invalid_data_type)};
|
||||
auto const in02 = expect<int>{":adf\r\n", int{11}, "number.int", aedis::make_error_code(aedis::error::not_a_number)};
|
||||
auto const in03 = expect<int>{":\r\n", int{}, "number.error (empty field)", aedis::make_error_code(aedis::error::empty_field)};
|
||||
auto const in04 = expect<boost::optional<bool>>{"#\r\n", boost::optional<bool>{}, "bool.error", aedis::make_error_code(aedis::error::empty_field)};
|
||||
auto const in05 = expect<std::string>{",\r\n", std::string{}, "double.error (empty field)", aedis::make_error_code(aedis::error::empty_field)};
|
||||
auto const in05 = expect<double>{",er\r\n", double{0}, "double.double", aedis::error::not_a_double};
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
|
||||
@@ -639,19 +563,138 @@ void test_resp3(net::io_context& ioc)
|
||||
test_async(ex, in03);
|
||||
test_async(ex, in04);
|
||||
test_async(ex, in05);
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
void test_null(net::io_context& ioc)
|
||||
BOOST_AUTO_TEST_CASE(test_blob_error)
|
||||
{
|
||||
using op_type_01 = boost::optional<bool>;
|
||||
using op_type_02 = boost::optional<int>;
|
||||
using op_type_03 = boost::optional<std::string>;
|
||||
using op_type_04 = boost::optional<std::vector<std::string>>;
|
||||
using op_type_05 = boost::optional<std::list<std::string>>;
|
||||
using op_type_06 = boost::optional<std::map<std::string, std::string>>;
|
||||
using op_type_07 = boost::optional<std::unordered_map<std::string, std::string>>;
|
||||
using op_type_08 = boost::optional<std::set<std::string>>;
|
||||
using op_type_09 = boost::optional<std::unordered_set<std::string>>;
|
||||
net::io_context ioc;
|
||||
auto const in01 = expect<node_type>{"!21\r\nSYNTAX invalid syntax\r\n", node_type{resp3::type::blob_error, 1UL, 0UL, {"SYNTAX invalid syntax"}}, "blob_error", aedis::error::resp3_blob_error};
|
||||
auto const in02 = expect<node_type>{"!0\r\n\r\n", node_type{resp3::type::blob_error, 1UL, 0UL, {}}, "blob_error.empty", aedis::error::resp3_blob_error};
|
||||
auto const in03 = expect<aedis::ignore>{"!3\r\nfoo\r\n", aedis::ignore{}, "blob_error.ignore.adapter.error", aedis::error::resp3_blob_error};
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
|
||||
test_sync(ex, in01);
|
||||
test_sync(ex, in02);
|
||||
test_sync(ex, in03);
|
||||
|
||||
test_async(ex, in01);
|
||||
test_async(ex, in02);
|
||||
test_async(ex, in03);
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_verbatim_string)
|
||||
{
|
||||
net::io_context ioc;
|
||||
auto const in01 = expect<node_type>{"=15\r\ntxt:Some string\r\n", node_type{resp3::type::verbatim_string, 1UL, 0UL, {"txt:Some string"}}, "verbatim_string"};
|
||||
auto const in02 = expect<node_type>{"=0\r\n\r\n", node_type{resp3::type::verbatim_string, 1UL, 0UL, {}}, "verbatim_string.empty"};
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
|
||||
test_sync(ex, in01);
|
||||
test_sync(ex, in02);
|
||||
|
||||
test_async(ex, in01);
|
||||
test_async(ex, in02);
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_big_number)
|
||||
{
|
||||
net::io_context ioc;
|
||||
auto const in01 = expect<node_type>{"(3492890328409238509324850943850943825024385\r\n", node_type{resp3::type::big_number, 1UL, 0UL, {"3492890328409238509324850943850943825024385"}}, "big_number.node"};
|
||||
auto const in02 = expect<int>{"(\r\n", int{}, "big_number.error (empty field)", aedis::error::empty_field};
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
|
||||
test_sync(ex, in01);
|
||||
test_sync(ex, in02);
|
||||
|
||||
test_async(ex, in01);
|
||||
test_async(ex, in02);
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_simple_string)
|
||||
{
|
||||
net::io_context ioc;
|
||||
std::optional<std::string> ok1, ok2;
|
||||
ok1 = "OK";
|
||||
ok2 = "";
|
||||
|
||||
auto in00 = expect<std::string>{"+OK\r\n", std::string{"OK"}, "simple_string.string"};
|
||||
auto in01 = expect<std::string>{"+\r\n", std::string{""}, "simple_string.string.empty"};
|
||||
auto in02 = expect<std::optional<std::string>>{"+OK\r\n", std::optional<std::string>{"OK"}, "simple_string.optional"};
|
||||
auto in03 = expect<std::optional<std::string>>{"+\r\n", std::optional<std::string>{""}, "simple_string.optional.empty"};
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
|
||||
test_sync(ex, in00);
|
||||
test_sync(ex, in01);
|
||||
test_sync(ex, in02);
|
||||
test_sync(ex, in03);
|
||||
|
||||
test_async(ex, in00);
|
||||
test_async(ex, in01);
|
||||
test_async(ex, in02);
|
||||
test_async(ex, in03);
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_resp3)
|
||||
{
|
||||
using map_type = std::map<std::string, std::string>;
|
||||
std::string const wire_map = "%rt\r\n$4\r\nkey1\r\n$6\r\nvalue1\r\n$4\r\nkey2\r\n$6\r\nvalue2\r\n";
|
||||
|
||||
std::string const wire_ssp = "$?\r\n;d\r\nHell\r\n;5\r\no wor\r\n;1\r\nd\r\n;0\r\n";
|
||||
|
||||
net::io_context ioc;
|
||||
auto const in01 = expect<int>{"s11\r\n", int{}, "number.error", aedis::error::invalid_data_type};
|
||||
auto const in02 = expect<int>{":adf\r\n", int{11}, "number.int", aedis::error::not_a_number};
|
||||
auto const in03 = expect<map_type>{wire_map, map_type{}, "map.error", aedis::error::not_a_number};
|
||||
auto const in04 = expect<std::string>{wire_ssp, std::string{}, "streamed_string_part.error", aedis::error::not_a_number};
|
||||
auto const in05 = expect<std::string>{"$l\r\nhh\r\n", std::string{}, "blob_string.error", aedis::error::not_a_number};
|
||||
auto const in06 = expect<int>{":\r\n", int{}, "number.error (empty field)", aedis::error::empty_field};
|
||||
auto const in07 = expect<std::optional<bool>>{"#\r\n", std::optional<bool>{}, "bool.error", aedis::error::empty_field};
|
||||
auto const in08 = expect<std::string>{",\r\n", std::string{}, "double.error (empty field)", aedis::error::empty_field};
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
|
||||
test_sync(ex, in01);
|
||||
test_sync(ex, in02);
|
||||
test_sync(ex, in03);
|
||||
test_sync(ex, in04);
|
||||
test_sync(ex, in05);
|
||||
test_sync(ex, in06);
|
||||
test_sync(ex, in07);
|
||||
test_sync(ex, in08);
|
||||
|
||||
test_async(ex, in01);
|
||||
test_async(ex, in02);
|
||||
test_async(ex, in03);
|
||||
test_async(ex, in04);
|
||||
test_async(ex, in05);
|
||||
test_async(ex, in06);
|
||||
test_async(ex, in07);
|
||||
test_async(ex, in08);
|
||||
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_null)
|
||||
{
|
||||
net::io_context ioc;
|
||||
using op_type_01 = std::optional<bool>;
|
||||
using op_type_02 = std::optional<int>;
|
||||
using op_type_03 = std::optional<std::string>;
|
||||
using op_type_04 = std::optional<std::vector<std::string>>;
|
||||
using op_type_05 = std::optional<std::list<std::string>>;
|
||||
using op_type_06 = std::optional<std::map<std::string, std::string>>;
|
||||
using op_type_07 = std::optional<std::unordered_map<std::string, std::string>>;
|
||||
using op_type_08 = std::optional<std::set<std::string>>;
|
||||
using op_type_09 = std::optional<std::unordered_set<std::string>>;
|
||||
|
||||
auto const in01 = expect<op_type_01>{"_\r\n", op_type_01{}, "null.optional.bool"};
|
||||
auto const in02 = expect<op_type_02>{"_\r\n", op_type_02{}, "null.optional.int"};
|
||||
@@ -684,34 +727,168 @@ void test_null(net::io_context& ioc)
|
||||
test_async(ex, in07);
|
||||
test_async(ex, in08);
|
||||
test_async(ex, in09);
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
int main()
|
||||
BOOST_AUTO_TEST_CASE(ignore_adapter_simple_error)
|
||||
{
|
||||
net::io_context ioc {1};
|
||||
net::io_context ioc;
|
||||
std::string rbuffer;
|
||||
|
||||
// Simple types.
|
||||
test_simple_string(ioc);
|
||||
test_simple_error(ioc);
|
||||
test_blob_string(ioc);
|
||||
test_blob_error(ioc);
|
||||
test_number(ioc);
|
||||
test_double(ioc);
|
||||
test_bool(ioc);
|
||||
test_null(ioc);
|
||||
test_big_number(ioc);
|
||||
test_verbatim_string(ioc);
|
||||
boost::system::error_code ec;
|
||||
|
||||
// Aggregates.
|
||||
test_array(ioc);
|
||||
test_set(ioc);
|
||||
test_map(ioc);
|
||||
test_push(ioc);
|
||||
test_streamed_string(ioc);
|
||||
test_stream ts {ioc};
|
||||
ts.append("-Error\r\n");
|
||||
resp3::read(ts, net::dynamic_buffer(rbuffer), adapt2(), ec);
|
||||
BOOST_CHECK_EQUAL(ec, aedis::error::resp3_simple_error);
|
||||
BOOST_TEST(!rbuffer.empty());
|
||||
}
|
||||
|
||||
// RESP3
|
||||
test_resp3(ioc);
|
||||
BOOST_AUTO_TEST_CASE(ignore_adapter_blob_error)
|
||||
{
|
||||
net::io_context ioc;
|
||||
std::string rbuffer;
|
||||
boost::system::error_code ec;
|
||||
|
||||
test_stream ts {ioc};
|
||||
ts.append("!21\r\nSYNTAX invalid syntax\r\n");
|
||||
resp3::read(ts, net::dynamic_buffer(rbuffer), adapt2(), ec);
|
||||
BOOST_CHECK_EQUAL(ec, aedis::error::resp3_blob_error);
|
||||
BOOST_TEST(!rbuffer.empty());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(ignore_adapter_no_error)
|
||||
{
|
||||
net::io_context ioc;
|
||||
std::string rbuffer;
|
||||
boost::system::error_code ec;
|
||||
test_stream ts {ioc};
|
||||
ts.append(":10\r\n");
|
||||
resp3::read(ts, net::dynamic_buffer(rbuffer), adapt2(), ec);
|
||||
BOOST_TEST(!ec);
|
||||
BOOST_TEST(rbuffer.empty());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(all_tests)
|
||||
{
|
||||
net::io_context ioc;
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
|
||||
#define TEST test_sync
|
||||
NUMBER_TEST_CONDITIONS(TEST)
|
||||
#undef TEST
|
||||
|
||||
#define TEST test_async
|
||||
NUMBER_TEST_CONDITIONS(TEST)
|
||||
#undef TEST
|
||||
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------------
|
||||
void check_error(char const* name, aedis::error ev)
|
||||
{
|
||||
auto const ec = aedis::make_error_code(ev);
|
||||
auto const& cat = ec.category();
|
||||
BOOST_TEST(std::string(ec.category().name()) == name);
|
||||
BOOST_TEST(!ec.message().empty());
|
||||
BOOST_TEST(cat.equivalent(
|
||||
static_cast<std::underlying_type<aedis::error>::type>(ev),
|
||||
ec.category().default_error_condition(
|
||||
static_cast<std::underlying_type<aedis::error>::type>(ev))));
|
||||
BOOST_TEST(cat.equivalent(ec,
|
||||
static_cast<std::underlying_type<aedis::error>::type>(ev)));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(error)
|
||||
{
|
||||
check_error("aedis", aedis::error::resolve_timeout);
|
||||
check_error("aedis", aedis::error::resolve_timeout);
|
||||
check_error("aedis", aedis::error::connect_timeout);
|
||||
check_error("aedis", aedis::error::idle_timeout);
|
||||
check_error("aedis", aedis::error::exec_timeout);
|
||||
check_error("aedis", aedis::error::invalid_data_type);
|
||||
check_error("aedis", aedis::error::not_a_number);
|
||||
check_error("aedis", aedis::error::exceeeds_max_nested_depth);
|
||||
check_error("aedis", aedis::error::unexpected_bool_value);
|
||||
check_error("aedis", aedis::error::empty_field);
|
||||
check_error("aedis", aedis::error::expects_resp3_simple_type);
|
||||
check_error("aedis", aedis::error::expects_resp3_aggregate);
|
||||
check_error("aedis", aedis::error::expects_resp3_map);
|
||||
check_error("aedis", aedis::error::expects_resp3_set);
|
||||
check_error("aedis", aedis::error::nested_aggregate_not_supported);
|
||||
check_error("aedis", aedis::error::resp3_simple_error);
|
||||
check_error("aedis", aedis::error::resp3_blob_error);
|
||||
check_error("aedis", aedis::error::incompatible_size);
|
||||
check_error("aedis", aedis::error::not_a_double);
|
||||
check_error("aedis", aedis::error::resp3_null);
|
||||
check_error("aedis", aedis::error::unexpected_server_role);
|
||||
check_error("aedis", aedis::error::not_connected);
|
||||
check_error("aedis", aedis::error::resp3_handshake_error);
|
||||
}
|
||||
|
||||
std::string get_type_as_str(aedis::resp3::type t)
|
||||
{
|
||||
std::ostringstream ss;
|
||||
ss << t;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(type_string)
|
||||
{
|
||||
BOOST_TEST(!get_type_as_str(aedis::resp3::type::array).empty());
|
||||
BOOST_TEST(!get_type_as_str(aedis::resp3::type::push).empty());
|
||||
BOOST_TEST(!get_type_as_str(aedis::resp3::type::set).empty());
|
||||
BOOST_TEST(!get_type_as_str(aedis::resp3::type::map).empty());
|
||||
BOOST_TEST(!get_type_as_str(aedis::resp3::type::attribute).empty());
|
||||
BOOST_TEST(!get_type_as_str(aedis::resp3::type::simple_string).empty());
|
||||
BOOST_TEST(!get_type_as_str(aedis::resp3::type::simple_error).empty());
|
||||
BOOST_TEST(!get_type_as_str(aedis::resp3::type::number).empty());
|
||||
BOOST_TEST(!get_type_as_str(aedis::resp3::type::doublean).empty());
|
||||
BOOST_TEST(!get_type_as_str(aedis::resp3::type::boolean).empty());
|
||||
BOOST_TEST(!get_type_as_str(aedis::resp3::type::big_number).empty());
|
||||
BOOST_TEST(!get_type_as_str(aedis::resp3::type::null).empty());
|
||||
BOOST_TEST(!get_type_as_str(aedis::resp3::type::blob_error).empty());
|
||||
BOOST_TEST(!get_type_as_str(aedis::resp3::type::verbatim_string).empty());
|
||||
BOOST_TEST(!get_type_as_str(aedis::resp3::type::blob_string).empty());
|
||||
BOOST_TEST(!get_type_as_str(aedis::resp3::type::streamed_string_part).empty());
|
||||
BOOST_TEST(!get_type_as_str(aedis::resp3::type::invalid).empty());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(type_convert)
|
||||
{
|
||||
using aedis::resp3::to_code;
|
||||
using aedis::resp3::to_type;
|
||||
using aedis::resp3::type;
|
||||
|
||||
#define CHECK_CASE(A) BOOST_CHECK_EQUAL(to_type(to_code(type::A)), type::A);
|
||||
CHECK_CASE(array);
|
||||
CHECK_CASE(push);
|
||||
CHECK_CASE(set);
|
||||
CHECK_CASE(map);
|
||||
CHECK_CASE(attribute);
|
||||
CHECK_CASE(simple_string);
|
||||
CHECK_CASE(simple_error);
|
||||
CHECK_CASE(number);
|
||||
CHECK_CASE(doublean);
|
||||
CHECK_CASE(boolean);
|
||||
CHECK_CASE(big_number);
|
||||
CHECK_CASE(null);
|
||||
CHECK_CASE(blob_error);
|
||||
CHECK_CASE(verbatim_string);
|
||||
CHECK_CASE(blob_string);
|
||||
CHECK_CASE(streamed_string_part);
|
||||
#undef CHECK_CASE
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(adapter)
|
||||
{
|
||||
using aedis::adapt;
|
||||
|
||||
std::string s;
|
||||
auto resp = std::tie(s, std::ignore);
|
||||
auto f = adapt(resp);
|
||||
(void)f;
|
||||
}
|
||||
|
||||
|
||||
57
tests/request.cpp
Normal file
57
tests/request.cpp
Normal file
@@ -0,0 +1,57 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <memory_resource>
|
||||
|
||||
#define BOOST_TEST_MODULE low level
|
||||
#include <boost/test/included/unit_test.hpp>
|
||||
|
||||
#include <aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
using aedis::resp3::request;
|
||||
|
||||
// TODO: Serialization.
|
||||
|
||||
BOOST_AUTO_TEST_CASE(single_arg_allocator)
|
||||
{
|
||||
char buf[4096];
|
||||
std::pmr::monotonic_buffer_resource resource{buf, 4096};
|
||||
request req1{{}, &resource};
|
||||
req1.push("PING");
|
||||
BOOST_CHECK_EQUAL(req1.payload(), std::pmr::string{"*1\r\n$4\r\nPING\r\n"});
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(arg_int)
|
||||
{
|
||||
request req;
|
||||
req.push("PING", 42);
|
||||
BOOST_CHECK_EQUAL(req.payload(), std::pmr::string{"*2\r\n$4\r\nPING\r\n$2\r\n42\r\n"});
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(multiple_args)
|
||||
{
|
||||
char const* res = "*5\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n$2\r\nEX\r\n$1\r\n2\r\n";
|
||||
request req;
|
||||
req.push("SET", "key", "value", "EX", "2");
|
||||
BOOST_CHECK_EQUAL(req.payload(), std::pmr::string{res});
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(container_and_range)
|
||||
{
|
||||
std::map<std::string, std::string> in{{"key1", "value1"}, {"key2", "value2"}};
|
||||
|
||||
char const* res = "*6\r\n$4\r\nHSET\r\n$3\r\nkey\r\n$4\r\nkey1\r\n$6\r\nvalue1\r\n$4\r\nkey2\r\n$6\r\nvalue2\r\n";
|
||||
|
||||
request req1;
|
||||
req1.push_range("HSET", "key", in);
|
||||
BOOST_CHECK_EQUAL(req1.payload(), std::pmr::string{res});
|
||||
|
||||
request req2;
|
||||
req2.push_range("HSET", "key", std::cbegin(in), std::cend(in));
|
||||
BOOST_CHECK_EQUAL(req2.payload(), std::pmr::string{res});
|
||||
}
|
||||
Reference in New Issue
Block a user