mirror of
https://github.com/boostorg/redis.git
synced 2026-01-25 06:32:08 +00:00
Compare commits
118 Commits
v1.4.2
...
boost-1.86
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a4d2bb983d | ||
|
|
30ba3c3eb6 | ||
|
|
90f3d6cd92 | ||
|
|
ef6dea1666 | ||
|
|
90b001a54e | ||
|
|
ec1ca876eb | ||
|
|
1b90346b7c | ||
|
|
8f46d1eaa9 | ||
|
|
a4e9a60f34 | ||
|
|
e9b16a3140 | ||
|
|
ced4f9bd02 | ||
|
|
43878d68a3 | ||
|
|
29892f2837 | ||
|
|
b71dff6dd0 | ||
|
|
a9204fcc91 | ||
|
|
1788ebc80b | ||
|
|
5656571aa4 | ||
|
|
aa6622ffa2 | ||
|
|
f459d5d89b | ||
|
|
35276b6acb | ||
|
|
122ffb20b2 | ||
|
|
1f4b709b21 | ||
|
|
7d52065a87 | ||
|
|
f0d92c16a5 | ||
|
|
1df18258d8 | ||
|
|
71c60a5a89 | ||
|
|
098fbd68d2 | ||
|
|
4f2b12adbc | ||
|
|
e7cec45cb2 | ||
|
|
b19067cfed | ||
|
|
0af1c2e73d | ||
|
|
da48368d53 | ||
|
|
b6e1280075 | ||
|
|
5d553f5d71 | ||
|
|
5c46b62958 | ||
|
|
6cde6eab44 | ||
|
|
78792199ef | ||
|
|
f5793ac9bc | ||
|
|
dfc2bd1ac2 | ||
|
|
0445e74fa3 | ||
|
|
234f961e87 | ||
|
|
8bb0004188 | ||
|
|
4257b2eaec | ||
|
|
96da11a2cc | ||
|
|
3861c5de74 | ||
|
|
168ee6148a | ||
|
|
723e72797f | ||
|
|
7caea928af | ||
|
|
71b9a4f428 | ||
|
|
d89a976729 | ||
|
|
154d0b106d | ||
|
|
2b12525206 | ||
|
|
0bcbf6d4e4 | ||
|
|
6389daa783 | ||
|
|
ab2d6cdea8 | ||
|
|
63ce40e365 | ||
|
|
f2a005a8c4 | ||
|
|
0c06be66de | ||
|
|
0380e643ed | ||
|
|
ff734694ab | ||
|
|
548e3d4cb6 | ||
|
|
66b632b13d | ||
|
|
11c9c1b787 | ||
|
|
d6f9e435c7 | ||
|
|
9a7816dbf4 | ||
|
|
199fb6c261 | ||
|
|
4d30d1e0c0 | ||
|
|
92be6d958f | ||
|
|
14d3c0232e | ||
|
|
7412b37e08 | ||
|
|
60ba5b62af | ||
|
|
0303ae0dbc | ||
|
|
ea6c5536c1 | ||
|
|
d386b30c3a | ||
|
|
2951acc80f | ||
|
|
faf15fe7e8 | ||
|
|
7f3f8b0c13 | ||
|
|
f37e514961 | ||
|
|
b7b4f8f449 | ||
|
|
686cb306ea | ||
|
|
fcbe2c431c | ||
|
|
a7b3fbdd9a | ||
|
|
5ea0d3c467 | ||
|
|
2cd487784b | ||
|
|
b41e2704a1 | ||
|
|
765f0d45e8 | ||
|
|
84c8649d66 | ||
|
|
0bf4e76981 | ||
|
|
1d329df81b | ||
|
|
56f7d5af69 | ||
|
|
d0c3b3f7ee | ||
|
|
87ebc6cf4a | ||
|
|
ffc35e8e3e | ||
|
|
a02837ab33 | ||
|
|
4a39a0d20a | ||
|
|
56d9a2778f | ||
|
|
c732f33b48 | ||
|
|
221016f1c9 | ||
|
|
cb9fdba0a4 | ||
|
|
1c96a60709 | ||
|
|
b66d067af8 | ||
|
|
bc08a8d411 | ||
|
|
53ef947cf3 | ||
|
|
ecfe51c7ae | ||
|
|
be20c0d48c | ||
|
|
d5031c3f69 | ||
|
|
6748f7682a | ||
|
|
2a4936a9e1 | ||
|
|
4547e1ac07 | ||
|
|
44a608c0ba | ||
|
|
1ed8e0182c | ||
|
|
d8cf431dc2 | ||
|
|
401dd24419 | ||
|
|
509635f222 | ||
|
|
4fbd0c6853 | ||
|
|
b8899ecdc7 | ||
|
|
7d09040646 | ||
|
|
0de26fb0ce |
@@ -7,7 +7,7 @@ codecov:
|
||||
|
||||
ignore:
|
||||
- "benchmarks/cpp/asio/*"
|
||||
- "examples/*"
|
||||
- "example/*"
|
||||
- "tests/*"
|
||||
- "/usr/*"
|
||||
- "**/boost/*"
|
||||
|
||||
379
.github/workflows/ci.yml
vendored
379
.github/workflows/ci.yml
vendored
@@ -1,10 +1,18 @@
|
||||
# CI script to verify that CMake and B2 builds work.
|
||||
# B2 builds include only tests that don't require a DB server, to avoid race conditions.
|
||||
# CMake tests include the actual project tests and all the CMake integration workflows
|
||||
# recommended by Boost.CI.
|
||||
# Windows CMake jobs build the code but don't run the tests,
|
||||
# since we don't have a way to set up a Redis server on Windows (yet).
|
||||
# Subcommands are implemented by the tools/ci.py script in a platform-independent manner.
|
||||
|
||||
name: CI
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
windows:
|
||||
name: "${{matrix.generator}} ${{matrix.toolset}} Boost ${{matrix.boost_version}} ${{matrix.build_type}} ${{matrix.name_args}}"
|
||||
windows-cmake:
|
||||
name: "CMake ${{matrix.toolset}} ${{matrix.build-type}} C++${{matrix.cxxstd}}"
|
||||
runs-on: ${{matrix.os}}
|
||||
defaults:
|
||||
run:
|
||||
@@ -12,84 +20,109 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
boost_version: ["1.81.0"]
|
||||
os: [windows-2019, windows-2022]
|
||||
toolset: [v142, v143]
|
||||
build_type: [Release]
|
||||
generator: ["Visual Studio 16 2019", "Visual Studio 17 2022"]
|
||||
config_args: [""]
|
||||
build_args: [""]
|
||||
name_args: [""]
|
||||
exclude:
|
||||
- { os: windows-2019, toolset: v143 }
|
||||
- { os: windows-2019, generator: "Visual Studio 17 2022" }
|
||||
- { os: windows-2022, generator: "Visual Studio 16 2019" }
|
||||
# The following combinations are not available through install-boost
|
||||
- { boost_version: "1.81.0", toolset: v143 }
|
||||
|
||||
include:
|
||||
- { toolset: msvc-14.2, os: windows-2019, generator: "Visual Studio 16 2019", cxxstd: '17', build-type: 'Debug', build-shared-libs: 1 }
|
||||
- { toolset: msvc-14.2, os: windows-2019, generator: "Visual Studio 16 2019", cxxstd: '17', build-type: 'Release', build-shared-libs: 0 }
|
||||
- { toolset: msvc-14.3, os: windows-2022, generator: "Visual Studio 17 2022", cxxstd: '20', build-type: 'Debug', build-shared-libs: 0 }
|
||||
- { toolset: msvc-14.3, os: windows-2022, generator: "Visual Studio 17 2022", cxxstd: '20', build-type: 'Release', build-shared-libs: 1 }
|
||||
env:
|
||||
CMAKE_BUILD_PARALLEL_LEVEL: 4
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Add boost toolset to environment
|
||||
if: contains(fromJson('["1.81.0"]'), matrix.boost_version)
|
||||
run: echo BOOST_TOOLSET=$(echo "msvc") >> $GITHUB_ENV
|
||||
- name: Setup Boost
|
||||
run: python3 tools/ci.py setup-boost --source-dir=$(pwd)
|
||||
|
||||
# The platform_version passed to boost-install determines the msvc toolset version for which static libs are installed.
|
||||
- name: Add boost platform version to environment
|
||||
- name: Build a Boost distribution using B2
|
||||
run: |
|
||||
declare -A toolset_to_platform_version=( [v142]="2019" [v143]="2022" )
|
||||
key=$(echo "${{matrix.toolset}}")
|
||||
echo BOOST_PLATFORM_VERSION="${toolset_to_platform_version[$key]}" >> $GITHUB_ENV
|
||||
python3 tools/ci.py build-b2-distro \
|
||||
--toolset ${{ matrix.toolset }}
|
||||
|
||||
- name: Add boost install path to environment
|
||||
run: echo BOOST_INSTALL_PATH="${GITHUB_WORKSPACE}/boost-${{matrix.boost_version}}${BOOST_TOOLSET}${BOOST_PLATFORM_VERSION}" >> $GITHUB_ENV
|
||||
|
||||
- name: Add build type configuration to environment
|
||||
run: echo BUILD_CONFIG_ARG="--config ${{matrix.build_type}}" >> $GITHUB_ENV
|
||||
|
||||
- name: Cache Boost installation
|
||||
id: cache-boost
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{env.BOOST_INSTALL_PATH}}
|
||||
key: ${{matrix.boost_version}}${{env.BOOST_TOOLSET}}${{env.BOOST_PLATFORM_VERSION}}
|
||||
|
||||
- name: Install Boost
|
||||
if: steps.cache-boost.outputs.cache-hit != 'true'
|
||||
uses: MarkusJx/install-boost@v2.4.1
|
||||
with:
|
||||
boost_version: ${{matrix.boost_version}}
|
||||
toolset: ${{env.BOOST_TOOLSET}}
|
||||
boost_install_dir: ${{env.BOOST_INSTALL_PATH}}
|
||||
platform_version: ${{env.BOOST_PLATFORM_VERSION}}
|
||||
arch: null
|
||||
|
||||
- name: Install openssl
|
||||
run: choco install openssl
|
||||
|
||||
- name: Create build directory
|
||||
run: mkdir build
|
||||
|
||||
- name: Configure
|
||||
working-directory: build
|
||||
- name: Build a Boost distribution using CMake
|
||||
run: |
|
||||
cmake -T "${{matrix.toolset}}" \
|
||||
-G "${{matrix.generator}}" \
|
||||
${{matrix.config_args}} \
|
||||
${BOOST_COMPILER_ARG}\
|
||||
"${GITHUB_WORKSPACE}"
|
||||
env:
|
||||
BOOST_ROOT: ${{env.BOOST_INSTALL_PATH}}/boost
|
||||
|
||||
- name: Build
|
||||
working-directory: build
|
||||
python3 tools/ci.py build-cmake-distro \
|
||||
--build-type ${{ matrix.build-type }} \
|
||||
--cxxstd ${{ matrix.cxxstd }} \
|
||||
--toolset ${{ matrix.toolset }} \
|
||||
--generator "${{ matrix.generator }}" \
|
||||
--build-shared-libs ${{ matrix.build-shared-libs }}
|
||||
|
||||
- name: Build the project tests
|
||||
run: |
|
||||
cmake --build . ${BUILD_CONFIG_ARG} ${{matrix.build_args}}
|
||||
python3 tools/ci.py build-cmake-standalone-tests \
|
||||
--build-type ${{ matrix.build-type }} \
|
||||
--cxxstd ${{ matrix.cxxstd }} \
|
||||
--toolset ${{ matrix.toolset }} \
|
||||
--generator "${{ matrix.generator }}" \
|
||||
--build-shared-libs ${{ matrix.build-shared-libs }}
|
||||
|
||||
posix:
|
||||
# # TODO: re-enable this when a Redis server is available for this job
|
||||
# - name: Run the project tests
|
||||
# run: |
|
||||
# python3 tools/ci.py run-cmake-standalone-tests \
|
||||
# --build-type ${{ matrix.build-type }}
|
||||
|
||||
- name: Run add_subdirectory tests
|
||||
run: |
|
||||
python3 tools/ci.py run-cmake-add-subdirectory-tests \
|
||||
--build-type ${{ matrix.build-type }} \
|
||||
--cxxstd ${{ matrix.cxxstd }} \
|
||||
--toolset ${{ matrix.toolset }} \
|
||||
--generator "${{ matrix.generator }}" \
|
||||
--build-shared-libs ${{ matrix.build-shared-libs }}
|
||||
|
||||
- name: Run find_package tests with the built cmake distribution
|
||||
run: |
|
||||
python3 tools/ci.py run-cmake-find-package-tests \
|
||||
--build-type ${{ matrix.build-type }} \
|
||||
--cxxstd ${{ matrix.cxxstd }} \
|
||||
--toolset ${{ matrix.toolset }} \
|
||||
--generator "${{ matrix.generator }}" \
|
||||
--build-shared-libs ${{ matrix.build-shared-libs }}
|
||||
|
||||
- name: Run find_package tests with the built b2 distribution
|
||||
run: |
|
||||
python3 tools/ci.py run-cmake-b2-find-package-tests \
|
||||
--build-type ${{ matrix.build-type }} \
|
||||
--cxxstd ${{ matrix.cxxstd }} \
|
||||
--toolset ${{ matrix.toolset }} \
|
||||
--generator "${{ matrix.generator }}" \
|
||||
--build-shared-libs ${{ matrix.build-shared-libs }}
|
||||
|
||||
windows-b2:
|
||||
name: "B2 ${{matrix.toolset}}"
|
||||
runs-on: ${{matrix.os}}
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- { toolset: msvc-14.2, os: windows-2019 }
|
||||
- { toolset: msvc-14.3, os: windows-2022 }
|
||||
env:
|
||||
OPENSSL_ROOT: "C:\\Program Files\\OpenSSL"
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup user-config.jam
|
||||
run: cp tools/user-config.jam "${HOMEDRIVE}${HOMEPATH}/"
|
||||
|
||||
- name: Setup Boost
|
||||
run: python3 tools/ci.py setup-boost --source-dir=$(pwd)
|
||||
|
||||
- name: Build and run project tests using B2
|
||||
run: |
|
||||
python3 tools/ci.py run-b2-tests \
|
||||
--toolset ${{ matrix.toolset }} \
|
||||
--cxxstd 17,20 \
|
||||
--variant debug,release
|
||||
|
||||
posix-cmake:
|
||||
name: "CMake ${{ matrix.toolset }} ${{ matrix.cxxstd }} ${{ matrix.build-type }} ${{ matrix.cxxflags }}"
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
@@ -98,39 +131,183 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- { toolset: gcc, compiler: g++-11, install: g++-11, os: ubuntu-22.04, cxxflags: '-std=c++17', ldflags: '' }
|
||||
- { toolset: gcc, compiler: g++-11, install: g++-11, os: ubuntu-22.04, cxxflags: '-std=c++20', ldflags: '' }
|
||||
- { toolset: clang, compiler: clang++-11, install: clang-11, os: ubuntu-22.04, cxxflags: '-std=c++17', ldflags: '' }
|
||||
- { toolset: clang, compiler: clang++-11, install: clang-11, os: ubuntu-22.04, cxxflags: '-std=c++20', ldflags: '' }
|
||||
- { toolset: clang, compiler: clang++-13, install: clang-13, os: ubuntu-22.04, cxxflags: '-std=c++17', ldflags: '' }
|
||||
- { toolset: clang, compiler: clang++-13, install: clang-13, os: ubuntu-22.04, cxxflags: '-std=c++20', ldflags: '' }
|
||||
- { toolset: clang, compiler: clang++-14, install: 'clang-14 libc++-14-dev libc++abi-14-dev', os: ubuntu-22.04, cxxflags: '-std=c++17 -stdlib=libc++', ldflags: '-lc++' }
|
||||
- { toolset: clang, compiler: clang++-14, install: 'clang-14 libc++-14-dev libc++abi-14-dev', os: ubuntu-22.04, cxxflags: '-std=c++20 -stdlib=libc++', ldflags: '-lc++' }
|
||||
- toolset: gcc-11
|
||||
install: g++-11
|
||||
os: ubuntu-latest
|
||||
cxxstd: '17'
|
||||
build-type: 'Debug'
|
||||
ldflags: ''
|
||||
|
||||
- toolset: gcc-11
|
||||
install: g++-11
|
||||
os: ubuntu-latest
|
||||
cxxstd: '20'
|
||||
build-type: 'Release'
|
||||
ldflags: ''
|
||||
|
||||
- toolset: clang-11
|
||||
install: clang-11
|
||||
os: ubuntu-latest
|
||||
cxxstd: '17'
|
||||
build-type: 'Debug'
|
||||
ldflags: ''
|
||||
|
||||
- toolset: clang-11
|
||||
install: clang-11
|
||||
os: ubuntu-latest
|
||||
cxxstd: '20'
|
||||
build-type: 'Debug'
|
||||
ldflags: ''
|
||||
|
||||
- toolset: clang-13
|
||||
install: clang-13
|
||||
os: ubuntu-latest
|
||||
cxxstd: '17'
|
||||
build-type: 'Release'
|
||||
ldflags: ''
|
||||
|
||||
- toolset: clang-13
|
||||
install: clang-13
|
||||
os: ubuntu-latest
|
||||
cxxstd: '20'
|
||||
build-type: 'Release'
|
||||
ldflags: ''
|
||||
|
||||
- toolset: clang-14
|
||||
install: 'clang-14 libc++-14-dev libc++abi-14-dev'
|
||||
os: ubuntu-latest
|
||||
cxxstd: '17'
|
||||
build-type: 'Debug'
|
||||
cxxflags: '-stdlib=libc++'
|
||||
ldflags: '-lc++'
|
||||
|
||||
- toolset: clang-14
|
||||
install: 'clang-14 libc++-14-dev libc++abi-14-dev'
|
||||
os: ubuntu-latest
|
||||
cxxstd: '20'
|
||||
build-type: 'Release'
|
||||
cxxflags: '-stdlib=libc++'
|
||||
ldflags: '-lc++'
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
CXXFLAGS: -g -O0 ${{matrix.cxxflags}} -Wall -Wextra
|
||||
CXXFLAGS: ${{matrix.cxxflags}} -Wall -Wextra
|
||||
LDFLAGS: ${{matrix.ldflags}}
|
||||
CMAKE_BUILD_PARALLEL_LEVEL: 4
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Install CMake
|
||||
run: sudo apt-get -y install cmake
|
||||
- name: Install protobuf
|
||||
run: sudo apt-get -y install protobuf-compiler
|
||||
- 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.4.1
|
||||
id: install-boost
|
||||
with:
|
||||
boost_version: 1.81.0
|
||||
platform_version: 22.04
|
||||
- name: Run CMake
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up the required containers
|
||||
run: |
|
||||
BOOST_ROOT=${{steps.install-boost.outputs.BOOST_ROOT}} cmake -DCMAKE_CXX_COMPILER="${{matrix.compiler}}" -DCMAKE_CXX_FLAGS="${{env.CXXFLAGS}}" -DCMAKE_EXE_LINKER_FLAGS="${{env.LDFLAGS}}"
|
||||
- name: Build
|
||||
run: make
|
||||
- name: Check
|
||||
run: ctest --output-on-failure
|
||||
docker compose -f tools/docker-compose.yml up -d --wait || (docker compose logs; exit 1)
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
docker exec builder apt-get update
|
||||
docker exec builder apt-get -y --no-install-recommends install \
|
||||
git \
|
||||
g++ \
|
||||
libssl-dev \
|
||||
make \
|
||||
ca-certificates \
|
||||
cmake \
|
||||
protobuf-compiler \
|
||||
python3 \
|
||||
${{ matrix.install }}
|
||||
|
||||
- name: Setup Boost
|
||||
run: docker exec builder /boost-redis/tools/ci.py setup-boost --source-dir=/boost-redis
|
||||
|
||||
- name: Build a Boost distribution using B2
|
||||
run: |
|
||||
docker exec builder /boost-redis/tools/ci.py build-b2-distro \
|
||||
--toolset ${{ matrix.toolset }}
|
||||
|
||||
- name: Build a Boost distribution using CMake
|
||||
run: |
|
||||
docker exec builder /boost-redis/tools/ci.py build-cmake-distro \
|
||||
--build-type ${{ matrix.build-type }} \
|
||||
--cxxstd ${{ matrix.cxxstd }} \
|
||||
--toolset ${{ matrix.toolset }}
|
||||
|
||||
- name: Build the project tests
|
||||
run: |
|
||||
docker exec builder /boost-redis/tools/ci.py build-cmake-standalone-tests \
|
||||
--build-type ${{ matrix.build-type }} \
|
||||
--cxxstd ${{ matrix.cxxstd }} \
|
||||
--toolset ${{ matrix.toolset }}
|
||||
|
||||
- name: Run the project tests
|
||||
run: |
|
||||
docker exec builder /boost-redis/tools/ci.py run-cmake-standalone-tests \
|
||||
--build-type ${{ matrix.build-type }}
|
||||
|
||||
- name: Run add_subdirectory tests
|
||||
run: |
|
||||
docker exec builder /boost-redis/tools/ci.py run-cmake-add-subdirectory-tests \
|
||||
--build-type ${{ matrix.build-type }} \
|
||||
--cxxstd ${{ matrix.cxxstd }} \
|
||||
--toolset ${{ matrix.toolset }}
|
||||
|
||||
- name: Run find_package tests with the built cmake distribution
|
||||
run: |
|
||||
docker exec builder /boost-redis/tools/ci.py run-cmake-find-package-tests \
|
||||
--build-type ${{ matrix.build-type }} \
|
||||
--cxxstd ${{ matrix.cxxstd }} \
|
||||
--toolset ${{ matrix.toolset }}
|
||||
|
||||
- name: Run find_package tests with the built b2 distribution
|
||||
run: |
|
||||
docker exec builder /boost-redis/tools/ci.py run-cmake-b2-find-package-tests \
|
||||
--build-type ${{ matrix.build-type }} \
|
||||
--cxxstd ${{ matrix.cxxstd }} \
|
||||
--toolset ${{ matrix.toolset }}
|
||||
|
||||
posix-b2:
|
||||
name: "B2 ${{ matrix.toolset }}"
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- toolset: gcc-11
|
||||
install: g++-11
|
||||
cxxstd: "11,17,20" # Having C++11 shouldn't break the build
|
||||
os: ubuntu-latest
|
||||
container: ubuntu:22.04
|
||||
- toolset: clang-14
|
||||
install: clang-14
|
||||
os: ubuntu-latest
|
||||
container: ubuntu:22.04
|
||||
cxxstd: "17,20"
|
||||
runs-on: ${{ matrix.os }}
|
||||
container: ${{matrix.container}}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup container environment
|
||||
if: matrix.container
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get -y install sudo python3 git g++ libssl-dev
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install python3 ${{ matrix.install }}
|
||||
|
||||
- name: Setup Boost
|
||||
run: ./tools/ci.py setup-boost --source-dir=$(pwd)
|
||||
|
||||
- name: Build and run project tests using B2
|
||||
run: |
|
||||
python3 tools/ci.py run-b2-tests \
|
||||
--toolset ${{ matrix.toolset }} \
|
||||
--cxxstd ${{ matrix.cxxstd }} \
|
||||
--variant debug,release
|
||||
|
||||
40
.github/workflows/coverage.yml
vendored
40
.github/workflows/coverage.yml
vendored
@@ -4,6 +4,7 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- develop
|
||||
|
||||
jobs:
|
||||
posix:
|
||||
defaults:
|
||||
@@ -15,33 +16,36 @@ jobs:
|
||||
CXX: g++-11
|
||||
CXXFLAGS: -g -O0 -std=c++20 --coverage -fkeep-inline-functions -fkeep-static-functions
|
||||
LDFLAGS: --coverage
|
||||
CMAKE_BUILD_PARALLEL_LEVEL: 4
|
||||
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.4.1
|
||||
id: install-boost
|
||||
with:
|
||||
boost_version: 1.81.0
|
||||
platform_version: 22.04
|
||||
|
||||
- name: Install dependencies
|
||||
run: sudo apt-get --no-install-recommends -y install cmake lcov g++-11 redis-server python3 libgd-perl
|
||||
|
||||
- name: Setup Boost
|
||||
run: ./tools/ci.py setup-boost --source-dir=$(pwd)
|
||||
|
||||
- name: Build Boost
|
||||
run: ./tools/ci.py build-b2-distro --toolset=gcc-11
|
||||
|
||||
# Having our library there confuses the coverage reports
|
||||
- name: Remove Boost.Redis from the b2 distro
|
||||
run: rm -rf ~/boost-b2-distro/include/boost/redis
|
||||
|
||||
- name: Run CMake
|
||||
run: |
|
||||
BOOST_ROOT=${{steps.install-boost.outputs.BOOST_ROOT}} cmake --preset coverage .
|
||||
run: cmake -DCMAKE_PREFIX_PATH=$HOME/boost-b2-distro --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"
|
||||
|
||||
bash <(curl -s https://codecov.io/bash) -f ./build/coverage/coverage.info
|
||||
|
||||
362
CMakeLists.txt
362
CMakeLists.txt
@@ -1,264 +1,142 @@
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
|
||||
#set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CMAKE_COMMAND} -E time")
|
||||
cmake_minimum_required(VERSION 3.8...3.20)
|
||||
|
||||
# determine whether it's main/root project
|
||||
# or being built under another project.
|
||||
if (NOT DEFINED BOOST_REDIS_MAIN_PROJECT)
|
||||
set(BOOST_REDIS_MAIN_PROJECT OFF)
|
||||
if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
|
||||
set(BOOST_REDIS_MAIN_PROJECT ON)
|
||||
endif()
|
||||
set(BOOST_REDIS_MAIN_PROJECT OFF)
|
||||
if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
|
||||
set(BOOST_REDIS_MAIN_PROJECT ON)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
project(
|
||||
boost_redis
|
||||
VERSION 1.4.1
|
||||
DESCRIPTION "A redis client library"
|
||||
HOMEPAGE_URL "https://boostorg.github.io/redis/"
|
||||
LANGUAGES CXX
|
||||
)
|
||||
|
||||
option(BOOST_REDIS_INSTALL "Generate install targets." ${BOOST_REDIS_MAIN_PROJECT})
|
||||
option(BOOST_REDIS_TESTS "Build tests." ${BOOST_REDIS_MAIN_PROJECT})
|
||||
option(BOOST_REDIS_EXAMPLES "Build examples." ${BOOST_REDIS_MAIN_PROJECT})
|
||||
option(BOOST_REDIS_BENCHMARKS "Build benchmarks." ${BOOST_REDIS_MAIN_PROJECT})
|
||||
option(BOOST_REDIS_DOC "Generate documentations." ${BOOST_REDIS_MAIN_PROJECT})
|
||||
project(boost_redis VERSION "${BOOST_SUPERPROJECT_VERSION}" LANGUAGES CXX)
|
||||
|
||||
# Library
|
||||
add_library(boost_redis INTERFACE)
|
||||
add_library(Boost::redis ALIAS boost_redis)
|
||||
target_include_directories(boost_redis INTERFACE
|
||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
|
||||
$<INSTALL_INTERFACE:include>
|
||||
)
|
||||
|
||||
target_link_libraries(
|
||||
boost_redis
|
||||
INTERFACE
|
||||
Boost::asio
|
||||
Boost::assert
|
||||
Boost::config
|
||||
Boost::core
|
||||
Boost::mp11
|
||||
Boost::system
|
||||
Boost::utility
|
||||
)
|
||||
|
||||
target_include_directories(boost_redis INTERFACE include)
|
||||
target_compile_features(boost_redis INTERFACE cxx_std_17)
|
||||
|
||||
# Asio bases C++ feature detection on __cplusplus. Make MSVC
|
||||
# define it correctly
|
||||
if (MSVC)
|
||||
target_compile_options(boost_redis INTERFACE /Zc:__cplusplus)
|
||||
endif()
|
||||
# Dependencies
|
||||
if (BOOST_REDIS_MAIN_PROJECT)
|
||||
# TODO: Understand why we have to list all dependencies below
|
||||
# instead of
|
||||
#set(BOOST_INCLUDE_LIBRARIES redis)
|
||||
#set(BOOST_EXCLUDE_LIBRARIES redis)
|
||||
#add_subdirectory(../.. boostorg/boost EXCLUDE_FROM_ALL)
|
||||
|
||||
find_package(Boost 1.80 REQUIRED)
|
||||
set(deps
|
||||
system
|
||||
assert
|
||||
config
|
||||
throw_exception
|
||||
asio
|
||||
variant2
|
||||
mp11
|
||||
winapi
|
||||
predef
|
||||
align
|
||||
context
|
||||
core
|
||||
coroutine
|
||||
static_assert
|
||||
pool
|
||||
date_time
|
||||
smart_ptr
|
||||
exception
|
||||
integer
|
||||
move
|
||||
type_traits
|
||||
algorithm
|
||||
utility
|
||||
io
|
||||
lexical_cast
|
||||
numeric/conversion
|
||||
mpl
|
||||
range
|
||||
tokenizer
|
||||
tuple
|
||||
array
|
||||
bind
|
||||
concept_check
|
||||
function
|
||||
iterator
|
||||
regex
|
||||
unordered
|
||||
preprocessor
|
||||
container
|
||||
conversion
|
||||
container_hash
|
||||
detail
|
||||
optional
|
||||
function_types
|
||||
fusion
|
||||
intrusive
|
||||
describe
|
||||
typeof
|
||||
functional
|
||||
test
|
||||
json
|
||||
endian
|
||||
)
|
||||
|
||||
include_directories(${Boost_INCLUDE_DIRS})
|
||||
foreach(dep IN LISTS deps)
|
||||
add_subdirectory(../${dep} boostorg/${dep})
|
||||
endforeach()
|
||||
|
||||
find_package(OpenSSL REQUIRED)
|
||||
|
||||
include_directories(include)
|
||||
|
||||
# Common
|
||||
#=======================================================================
|
||||
|
||||
add_library(boost_redis_project_options INTERFACE)
|
||||
target_link_libraries(boost_redis_project_options INTERFACE OpenSSL::Crypto OpenSSL::SSL)
|
||||
if (MSVC)
|
||||
target_compile_options(boost_redis_project_options INTERFACE /bigobj)
|
||||
target_compile_definitions(boost_redis_project_options INTERFACE _WIN32_WINNT=0x0601)
|
||||
endif()
|
||||
|
||||
add_library(boost_redis_src STATIC examples/boost_redis.cpp)
|
||||
target_compile_features(boost_redis_src PRIVATE cxx_std_17)
|
||||
target_link_libraries(boost_redis_src PRIVATE boost_redis_project_options)
|
||||
|
||||
# Executables
|
||||
#=======================================================================
|
||||
|
||||
if (BOOST_REDIS_BENCHMARKS)
|
||||
add_library(benchmarks_options INTERFACE)
|
||||
target_link_libraries(benchmarks_options INTERFACE boost_redis_src)
|
||||
target_link_libraries(benchmarks_options INTERFACE boost_redis_project_options)
|
||||
target_compile_features(benchmarks_options INTERFACE cxx_std_20)
|
||||
|
||||
add_executable(echo_server_client benchmarks/cpp/asio/echo_server_client.cpp)
|
||||
target_link_libraries(echo_server_client PRIVATE benchmarks_options)
|
||||
|
||||
add_executable(echo_server_direct benchmarks/cpp/asio/echo_server_direct.cpp)
|
||||
target_link_libraries(echo_server_direct PRIVATE benchmarks_options)
|
||||
endif()
|
||||
|
||||
if (BOOST_REDIS_EXAMPLES)
|
||||
add_library(examples_main STATIC examples/main.cpp)
|
||||
target_compile_features(examples_main PRIVATE cxx_std_20)
|
||||
target_link_libraries(examples_main PRIVATE boost_redis_project_options)
|
||||
|
||||
macro(make_example EXAMPLE_NAME STANDARD)
|
||||
add_executable(${EXAMPLE_NAME} examples/${EXAMPLE_NAME}.cpp)
|
||||
target_link_libraries(${EXAMPLE_NAME} PRIVATE boost_redis_src)
|
||||
target_link_libraries(${EXAMPLE_NAME} PRIVATE boost_redis_project_options)
|
||||
target_compile_features(${EXAMPLE_NAME} PRIVATE cxx_std_${STANDARD})
|
||||
if (${STANDARD} STREQUAL "20")
|
||||
target_link_libraries(${EXAMPLE_NAME} PRIVATE examples_main)
|
||||
endif()
|
||||
endmacro()
|
||||
|
||||
macro(make_testable_example EXAMPLE_NAME STANDARD)
|
||||
make_example(${EXAMPLE_NAME} ${STANDARD})
|
||||
add_test(${EXAMPLE_NAME} ${EXAMPLE_NAME})
|
||||
endmacro()
|
||||
|
||||
make_testable_example(cpp17_intro 17)
|
||||
make_testable_example(cpp17_intro_sync 17)
|
||||
|
||||
make_testable_example(cpp20_intro 20)
|
||||
make_testable_example(cpp20_containers 20)
|
||||
make_testable_example(cpp20_json 20)
|
||||
make_testable_example(cpp20_intro_tls 20)
|
||||
|
||||
make_example(cpp20_subscriber 20)
|
||||
make_example(cpp20_streams 20)
|
||||
make_example(cpp20_echo_server 20)
|
||||
make_example(cpp20_resolve_with_sentinel 20)
|
||||
|
||||
# We test the protobuf example only on gcc.
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
find_package(Protobuf)
|
||||
if (Protobuf_FOUND)
|
||||
protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS examples/person.proto)
|
||||
make_testable_example(cpp20_protobuf 20)
|
||||
target_sources(cpp20_protobuf PUBLIC ${PROTO_SRCS} ${PROTO_HDRS})
|
||||
target_link_libraries(cpp20_protobuf PRIVATE ${Protobuf_LIBRARIES})
|
||||
target_include_directories(cpp20_protobuf PUBLIC ${Protobuf_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR})
|
||||
endif()
|
||||
find_package(Threads REQUIRED)
|
||||
find_package(OpenSSL REQUIRED)
|
||||
target_link_libraries(boost_redis
|
||||
INTERFACE
|
||||
Boost::system
|
||||
Boost::asio
|
||||
Threads::Threads
|
||||
OpenSSL::Crypto
|
||||
OpenSSL::SSL
|
||||
)
|
||||
else()
|
||||
# If we're in the superproject or called from add_subdirectory,
|
||||
# Boost dependencies should be already available.
|
||||
# If other dependencies are not found, we bail out
|
||||
find_package(Threads)
|
||||
if(NOT Threads_FOUND)
|
||||
message(STATUS "Boost.Redis has been disabled, because the required package Threads hasn't been found")
|
||||
return()
|
||||
endif()
|
||||
find_package(OpenSSL)
|
||||
if(NOT OpenSSL_FOUND)
|
||||
message(STATUS "Boost.Redis has been disabled, because the required package OpenSSL hasn't been found")
|
||||
return()
|
||||
endif()
|
||||
|
||||
if (NOT MSVC)
|
||||
make_example(cpp20_chat_room 20)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (BOOST_REDIS_TESTS)
|
||||
enable_testing()
|
||||
|
||||
add_library(tests_common STATIC tests/common.cpp)
|
||||
target_compile_features(tests_common PRIVATE cxx_std_17)
|
||||
target_link_libraries(tests_common PRIVATE boost_redis_project_options)
|
||||
|
||||
macro(make_test TEST_NAME STANDARD)
|
||||
add_executable(${TEST_NAME} tests/${TEST_NAME}.cpp)
|
||||
target_link_libraries(${TEST_NAME} PRIVATE boost_redis_src tests_common)
|
||||
target_link_libraries(${TEST_NAME} PRIVATE boost_redis_project_options)
|
||||
target_compile_features(${TEST_NAME} PRIVATE cxx_std_${STANDARD})
|
||||
add_test(${TEST_NAME} ${TEST_NAME})
|
||||
endmacro()
|
||||
|
||||
make_test(test_conn_quit 17)
|
||||
make_test(test_conn_tls 17)
|
||||
make_test(test_low_level 17)
|
||||
make_test(test_conn_exec_retry 17)
|
||||
make_test(test_conn_exec_error 17)
|
||||
make_test(test_request 17)
|
||||
make_test(test_run 17)
|
||||
make_test(test_low_level_sync 17)
|
||||
make_test(test_low_level_sync_sans_io 17)
|
||||
make_test(test_conn_check_health 17)
|
||||
|
||||
make_test(test_conn_exec 20)
|
||||
make_test(test_conn_push 20)
|
||||
make_test(test_conn_reconnect 20)
|
||||
make_test(test_conn_exec_cancel 20)
|
||||
make_test(test_conn_exec_cancel2 20)
|
||||
make_test(test_conn_echo_stress 20)
|
||||
make_test(test_low_level_async 20)
|
||||
make_test(test_conn_run_cancel 20)
|
||||
make_test(test_issue_50 20)
|
||||
endif()
|
||||
|
||||
# Install
|
||||
#=======================================================================
|
||||
|
||||
if (BOOST_REDIS_INSTALL)
|
||||
install(TARGETS boost_redis
|
||||
EXPORT boost_redis
|
||||
PUBLIC_HEADER DESTINATION include COMPONENT Development
|
||||
)
|
||||
|
||||
include(CMakePackageConfigHelpers)
|
||||
|
||||
configure_package_config_file(
|
||||
"${PROJECT_SOURCE_DIR}/cmake/BoostRedisConfig.cmake.in"
|
||||
"${PROJECT_BINARY_DIR}/BoostRedisConfig.cmake"
|
||||
INSTALL_DESTINATION lib/cmake/boost/redis
|
||||
)
|
||||
|
||||
install(EXPORT boost_redis DESTINATION lib/cmake/boost/redis)
|
||||
install(FILES "${PROJECT_BINARY_DIR}/BoostRedisConfigVersion.cmake"
|
||||
"${PROJECT_BINARY_DIR}/BoostRedisConfig.cmake"
|
||||
DESTINATION lib/cmake/boost/redis)
|
||||
|
||||
install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/ DESTINATION include)
|
||||
|
||||
include(CMakePackageConfigHelpers)
|
||||
write_basic_package_version_file(
|
||||
"${PROJECT_BINARY_DIR}/BoostRedisConfigVersion.cmake"
|
||||
COMPATIBILITY AnyNewerVersion
|
||||
)
|
||||
|
||||
include(CPack)
|
||||
endif()
|
||||
|
||||
# Doxygen
|
||||
#=======================================================================
|
||||
|
||||
if (BOOST_REDIS_DOC)
|
||||
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
|
||||
# This is generated by boostdep
|
||||
target_link_libraries(boost_redis
|
||||
INTERFACE
|
||||
Boost::asio
|
||||
Boost::assert
|
||||
Boost::core
|
||||
Boost::mp11
|
||||
Boost::system
|
||||
Boost::throw_exception
|
||||
Threads::Threads
|
||||
OpenSSL::Crypto
|
||||
OpenSSL::SSL
|
||||
)
|
||||
endif()
|
||||
|
||||
# Coverage
|
||||
#=======================================================================
|
||||
# Enable testing. If we're being called from the superproject, this has already been done
|
||||
if (BOOST_REDIS_MAIN_PROJECT)
|
||||
include(CTest)
|
||||
endif()
|
||||
|
||||
set(
|
||||
COVERAGE_TRACE_COMMAND
|
||||
lcov --capture
|
||||
-output-file "${PROJECT_BINARY_DIR}/coverage.info"
|
||||
--directory "${PROJECT_BINARY_DIR}"
|
||||
--include "${PROJECT_SOURCE_DIR}/include/*"
|
||||
)
|
||||
# Most tests require a running Redis server, so we only run them if we're the main project
|
||||
if(BOOST_REDIS_MAIN_PROJECT AND BUILD_TESTING)
|
||||
# Tests and common utilities
|
||||
add_subdirectory(test)
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
# TODO
|
||||
#=======================================================================
|
||||
|
||||
#.PHONY: bench
|
||||
#bench:
|
||||
# pdflatex --jobname=echo-f0 benchmarks/benchmarks.tex
|
||||
# pdflatex --jobname=echo-f1 benchmarks/benchmarks.tex
|
||||
# pdftoppm {input.pdf} {output.file} -png
|
||||
# Benchmarks. Build them with tests to prevent code rotting
|
||||
add_subdirectory(benchmarks)
|
||||
|
||||
# Examples
|
||||
add_subdirectory(example)
|
||||
endif()
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"warnings": {
|
||||
"dev": true,
|
||||
"deprecated": true,
|
||||
"uninitialized": true,
|
||||
"uninitialized": false,
|
||||
"unusedCli": true,
|
||||
"systemVars": false
|
||||
},
|
||||
@@ -52,8 +52,7 @@
|
||||
"CMAKE_CXX_COMPILER": "g++-11",
|
||||
"CMAKE_SHARED_LINKER_FLAGS": "-fsanitize=address",
|
||||
"CMAKE_CXX_STANDARD_REQUIRED": "ON",
|
||||
"PROJECT_BINARY_DIR": "${sourceDir}/build/g++-11",
|
||||
"DOXYGEN_OUTPUT_DIRECTORY": "${sourceDir}/build/g++-11/doc/"
|
||||
"PROJECT_BINARY_DIR": "${sourceDir}/build/g++-11"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -69,8 +68,7 @@
|
||||
"CMAKE_CXX_COMPILER": "g++-11",
|
||||
"CMAKE_SHARED_LINKER_FLAGS": "",
|
||||
"CMAKE_CXX_STANDARD_REQUIRED": "ON",
|
||||
"PROJECT_BINARY_DIR": "${sourceDir}/build/g++-11-release",
|
||||
"DOXYGEN_OUTPUT_DIRECTORY": "${sourceDir}/build/g++-11-release/doc/"
|
||||
"PROJECT_BINARY_DIR": "${sourceDir}/build/g++-11-release"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -86,8 +84,23 @@
|
||||
"CMAKE_CXX_COMPILER": "clang++-13",
|
||||
"CMAKE_SHARED_LINKER_FLAGS": "-fsanitize=address",
|
||||
"CMAKE_CXX_STANDARD_REQUIRED": "ON",
|
||||
"PROJECT_BINARY_DIR": "${sourceDir}/build/clang++-13",
|
||||
"DOXYGEN_OUTPUT_DIRECTORY": "${sourceDir}/build/clang++-13/doc/"
|
||||
"PROJECT_BINARY_DIR": "${sourceDir}/build/clang++-13"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "clang++-14",
|
||||
"generator": "Unix Makefiles",
|
||||
"hidden": false,
|
||||
"inherits": ["cmake-pedantic"],
|
||||
"binaryDir": "${sourceDir}/build/clang++-14",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Debug",
|
||||
"CMAKE_CXX_EXTENSIONS": "OFF",
|
||||
"CMAKE_CXX_FLAGS": "-Wall -Wextra -fsanitize=address",
|
||||
"CMAKE_CXX_COMPILER": "clang++-14",
|
||||
"CMAKE_SHARED_LINKER_FLAGS": "-fsanitize=address",
|
||||
"CMAKE_CXX_STANDARD_REQUIRED": "ON",
|
||||
"PROJECT_BINARY_DIR": "${sourceDir}/build/clang++-14"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -104,8 +117,7 @@
|
||||
"CMAKE_CXX_COMPILER": "clang++-14",
|
||||
"CMAKE_SHARED_LINKER_FLAGS": "",
|
||||
"CMAKE_CXX_STANDARD_REQUIRED": "ON",
|
||||
"PROJECT_BINARY_DIR": "${sourceDir}/build/libc++-14-cpp17",
|
||||
"DOXYGEN_OUTPUT_DIRECTORY": "${sourceDir}/build/libc++-14-cpp17/doc/"
|
||||
"PROJECT_BINARY_DIR": "${sourceDir}/build/libc++-14-cpp17"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -122,8 +134,7 @@
|
||||
"CMAKE_CXX_COMPILER": "clang++-14",
|
||||
"CMAKE_SHARED_LINKER_FLAGS": "",
|
||||
"CMAKE_CXX_STANDARD_REQUIRED": "ON",
|
||||
"PROJECT_BINARY_DIR": "${sourceDir}/build/libc++-14-cpp20",
|
||||
"DOXYGEN_OUTPUT_DIRECTORY": "${sourceDir}/build/libc++-14-cpp20/doc/"
|
||||
"PROJECT_BINARY_DIR": "${sourceDir}/build/libc++-14-cpp20"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -143,6 +154,7 @@
|
||||
{ "name": "g++-11", "configurePreset": "g++-11" },
|
||||
{ "name": "g++-11-release", "configurePreset": "g++-11-release" },
|
||||
{ "name": "clang++-13", "configurePreset": "clang++-13" },
|
||||
{ "name": "clang++-14", "configurePreset": "clang++-14" },
|
||||
{ "name": "libc++-14-cpp17", "configurePreset": "libc++-14-cpp17" },
|
||||
{ "name": "libc++-14-cpp20", "configurePreset": "libc++-14-cpp20" },
|
||||
{ "name": "clang-tidy", "configurePreset": "clang-tidy" }
|
||||
@@ -158,6 +170,7 @@
|
||||
{ "name": "g++-11", "configurePreset": "g++-11", "inherits": ["test"] },
|
||||
{ "name": "g++-11-release", "configurePreset": "g++-11-release", "inherits": ["test"] },
|
||||
{ "name": "clang++-13", "configurePreset": "clang++-13", "inherits": ["test"] },
|
||||
{ "name": "clang++-14", "configurePreset": "clang++-14", "inherits": ["test"] },
|
||||
{ "name": "libc++-14-cpp17", "configurePreset": "libc++-14-cpp17", "inherits": ["test"] },
|
||||
{ "name": "libc++-14-cpp20", "configurePreset": "libc++-14-cpp20", "inherits": ["test"] },
|
||||
{ "name": "clang-tidy", "configurePreset": "clang-tidy", "inherits": ["test"] }
|
||||
|
||||
90
README.md
90
README.md
@@ -1,15 +1,13 @@
|
||||
# boost_redis
|
||||
# Boost.Redis
|
||||
|
||||
Boost.Redis is a high-level [Redis](https://redis.io/) client library built on top of
|
||||
[Boost.Asio](https://www.boost.org/doc/libs/release/doc/html/boost_asio.html)
|
||||
that implements Redis plain text protocol
|
||||
that implements the Redis protocol
|
||||
[RESP3](https://github.com/redis/redis-specifications/blob/master/protocol/RESP3.md).
|
||||
It can multiplex any number of client
|
||||
requests, responses, and server pushes onto a single active socket
|
||||
connection to the Redis server. The requirements for using Boost.Redis are
|
||||
The requirements for using Boost.Redis are:
|
||||
|
||||
* Boost 1.81 or greater.
|
||||
* C++17 minimum.
|
||||
* Boost. The library is included in Boost distributions starting with 1.84.
|
||||
* C++17 or higher.
|
||||
* Redis 6 or higher (must support RESP3).
|
||||
* Gcc (10, 11, 12), Clang (11, 13, 14) and Visual Studio (16 2019, 17 2022).
|
||||
* Have basic-level knowledge about [Redis](https://redis.io/docs/)
|
||||
@@ -29,7 +27,7 @@ examples and tests cmake is supported, for example
|
||||
|
||||
```cpp
|
||||
# Linux
|
||||
$ BOOST_ROOT=/opt/boost_1_81_0 cmake --preset g++-11
|
||||
$ BOOST_ROOT=/opt/boost_1_84_0 cmake --preset g++-11
|
||||
|
||||
# Windows
|
||||
$ cmake -G "Visual Studio 17 2022" -A x64 -B bin64 -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake
|
||||
@@ -82,8 +80,9 @@ them are
|
||||
* [Client-side caching](https://redis.io/docs/manual/client-side-caching/)
|
||||
|
||||
The connection class supports server pushes by means of the
|
||||
`boost::redis::connection::async_receive` function, the coroutine shows how
|
||||
to used it
|
||||
`boost::redis::connection::async_receive` function, which can be
|
||||
called in the same connection that is being used to execute commands.
|
||||
The coroutine below shows how to used it
|
||||
|
||||
```cpp
|
||||
auto
|
||||
@@ -92,6 +91,9 @@ receiver(std::shared_ptr<connection> conn) -> net::awaitable<void>
|
||||
request req;
|
||||
req.push("SUBSCRIBE", "channel");
|
||||
|
||||
generic_response resp;
|
||||
conn->set_receive_response(resp);
|
||||
|
||||
// Loop while reconnection is enabled
|
||||
while (conn->will_reconnect()) {
|
||||
|
||||
@@ -99,7 +101,7 @@ receiver(std::shared_ptr<connection> conn) -> net::awaitable<void>
|
||||
co_await conn->async_exec(req, ignore, net::deferred);
|
||||
|
||||
// Loop reading Redis pushes.
|
||||
for (generic_response resp;;) {
|
||||
for (;;) {
|
||||
error_code ec;
|
||||
co_await conn->async_receive(resp, net::redirect_error(net::use_awaitable, ec));
|
||||
if (ec)
|
||||
@@ -108,7 +110,7 @@ receiver(std::shared_ptr<connection> conn) -> net::awaitable<void>
|
||||
// Use the response resp in some way and then clear it.
|
||||
...
|
||||
|
||||
resp.value().clear();
|
||||
consume_one(resp);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -674,7 +676,69 @@ https://lists.boost.org/Archives/boost/2023/01/253944.php.
|
||||
|
||||
## Changelog
|
||||
|
||||
### develop (incorporates changes to conform the boost review and more)
|
||||
### Boost 1.85
|
||||
|
||||
* ([Issue 170](https://github.com/boostorg/redis/issues/170))
|
||||
Under load and on low-latency networks it is possible to start
|
||||
receiving responses before the write operation completed and while
|
||||
the request is still marked as staged and not written. This messes
|
||||
up with the heuristics that classifies responses as unsolicied or
|
||||
not.
|
||||
|
||||
* ([Issue 168](https://github.com/boostorg/redis/issues/168)).
|
||||
Provides a way of passing a custom SSL context to the connection.
|
||||
The design here differs from that of Boost.Beast and Boost.MySql
|
||||
since in Boost.Redis the connection owns the context instead of only
|
||||
storing a reference to a user provided one. This is ok so because
|
||||
apps need only one connection for their entire application, which
|
||||
makes the overhead of one ssl-context per connection negligible.
|
||||
|
||||
* ([Issue 181](https://github.com/boostorg/redis/issues/181)).
|
||||
See a detailed description of this bug in
|
||||
[this](https://github.com/boostorg/redis/issues/181#issuecomment-1913346983)
|
||||
comment.
|
||||
|
||||
* ([Issue 182](https://github.com/boostorg/redis/issues/182)).
|
||||
Sets `"default"` as the default value of `config::username`. This
|
||||
makes it simpler to use the `requirepass` configuration in Redis.
|
||||
|
||||
* ([Issue 189](https://github.com/boostorg/redis/issues/189)).
|
||||
Fixes narrowing convertion by using `std::size_t` instead of
|
||||
`std::uint64_t` for the sizes of bulks and aggregates. The code
|
||||
relies now on `std::from_chars` returning an error if a value
|
||||
greater than 32 is received on platforms on which the size
|
||||
of`std::size_t` is 32.
|
||||
|
||||
|
||||
### Boost 1.84 (First release in Boost)
|
||||
|
||||
* Deprecates the `async_receive` overload that takes a response. Users
|
||||
should now first call `set_receive_response` to avoid constantly and
|
||||
unnecessarily setting the same response.
|
||||
|
||||
* Uses `std::function` to type erase the response adapter. This change
|
||||
should not influence users in any way but allowed important
|
||||
simplification in the connections internals. This resulted in
|
||||
massive performance improvement.
|
||||
|
||||
* The connection has a new member `get_usage()` that returns the
|
||||
connection usage information, such as number of bytes written,
|
||||
received etc.
|
||||
|
||||
* There are massive performance improvements in the consuming of
|
||||
server pushes which are now communicated with an `asio::channel` and
|
||||
therefore can be buffered which avoids blocking the socket read-loop.
|
||||
Batch reads are also supported by means of `channel.try_send` and
|
||||
buffered messages can be consumed synchronously with
|
||||
`connection::receive`. The function `boost::redis::cancel_one` has
|
||||
been added to simplify processing multiple server pushes contained
|
||||
in the same `generic_response`. *IMPORTANT*: These changes may
|
||||
result in more than one push in the response when
|
||||
`connection::async_receive` resumes. The user must therefore be
|
||||
careful when calling `resp.clear()`: either ensure that all message
|
||||
have been processed or just use `consume_one`.
|
||||
|
||||
### v1.4.2 (incorporates changes to conform the boost review and more)
|
||||
|
||||
* Adds `boost::redis::config::database_index` to make it possible to
|
||||
choose a database before starting running commands e.g. after an
|
||||
|
||||
20
benchmarks/CMakeLists.txt
Normal file
20
benchmarks/CMakeLists.txt
Normal file
@@ -0,0 +1,20 @@
|
||||
|
||||
add_library(benchmarks_options INTERFACE)
|
||||
target_link_libraries(benchmarks_options INTERFACE boost_redis_src)
|
||||
target_link_libraries(benchmarks_options INTERFACE boost_redis_project_options)
|
||||
target_compile_features(benchmarks_options INTERFACE cxx_std_20)
|
||||
|
||||
add_executable(echo_server_client cpp/asio/echo_server_client.cpp)
|
||||
target_link_libraries(echo_server_client PRIVATE benchmarks_options)
|
||||
|
||||
add_executable(echo_server_direct cpp/asio/echo_server_direct.cpp)
|
||||
target_link_libraries(echo_server_direct PRIVATE benchmarks_options)
|
||||
|
||||
# TODO
|
||||
#=======================================================================
|
||||
|
||||
#.PHONY: bench
|
||||
#bench:
|
||||
# pdflatex --jobname=echo-f0 benchmarks/benchmarks.tex
|
||||
# pdflatex --jobname=echo-f1 benchmarks/benchmarks.tex
|
||||
# pdftoppm {input.pdf} {output.file} -png
|
||||
@@ -1,4 +0,0 @@
|
||||
@PACKAGE_INIT@
|
||||
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/Aedis.cmake")
|
||||
check_required_components("@PROJECT_NAME@")
|
||||
2690
doc/Doxyfile.in
2690
doc/Doxyfile.in
File diff suppressed because it is too large
Load Diff
92
doc/Jamfile
Normal file
92
doc/Jamfile
Normal file
@@ -0,0 +1,92 @@
|
||||
project redis/doc ;
|
||||
|
||||
import doxygen ;
|
||||
import path ;
|
||||
import sequence ;
|
||||
|
||||
# All paths must be absolute to work well with the Doxygen rules.
|
||||
path-constant this_dir : . ;
|
||||
path-constant target_dir : html ;
|
||||
path-constant redis_root_dir : .. ;
|
||||
path-constant include_dir : ../include ;
|
||||
path-constant examples_dir : ../example ;
|
||||
path-constant readme : ../README.md ;
|
||||
path-constant layout_file : DoxygenLayout.xml ;
|
||||
path-constant header : header.html ;
|
||||
path-constant footer : footer.html ;
|
||||
|
||||
local stylesheet_files = [ path.glob $(this_dir) : *.css ] ;
|
||||
local includes = [ path.glob-tree $(include_dir) : *.hpp *.cpp ] ;
|
||||
local examples = [ path.glob-tree $(examples_dir) : *.hpp *.cpp ] ;
|
||||
|
||||
# If passed directly, several HTML_EXTRA_STYLESHEET tags are generated,
|
||||
# which is not correct.
|
||||
local stylesheet_arg = [ sequence.join "\"$(stylesheet_files)\"" : " " ] ;
|
||||
|
||||
# The doxygen rule requires the target name to end in .html to generate HTML files
|
||||
doxygen doc.html
|
||||
:
|
||||
$(includes) $(examples) $(readme)
|
||||
:
|
||||
<doxygen:param>"PROJECT_NAME=Boost.Redis"
|
||||
<doxygen:param>PROJECT_NUMBER="1.84.0"
|
||||
<doxygen:param>PROJECT_BRIEF="A redis client library"
|
||||
<doxygen:param>"STRIP_FROM_PATH=\"$(redis_root_dir)\""
|
||||
<doxygen:param>"STRIP_FROM_INC_PATH=\"$(include_dir)\""
|
||||
<doxygen:param>BUILTIN_STL_SUPPORT=YES
|
||||
<doxygen:param>INLINE_SIMPLE_STRUCTS=YES
|
||||
<doxygen:param>HIDE_UNDOC_MEMBERS=YES
|
||||
<doxygen:param>HIDE_UNDOC_CLASSES=YES
|
||||
<doxygen:param>SHOW_HEADERFILE=YES
|
||||
<doxygen:param>SORT_BRIEF_DOCS=YES
|
||||
<doxygen:param>SORT_MEMBERS_CTORS_1ST=YES
|
||||
<doxygen:param>SHOW_FILES=NO
|
||||
<doxygen:param>SHOW_NAMESPACES=NO
|
||||
<doxygen:param>"LAYOUT_FILE=\"$(layout_file)\""
|
||||
<doxygen:param>WARN_IF_INCOMPLETE_DOC=YES
|
||||
<doxygen:param>FILE_PATTERNS="*.hpp *.cpp"
|
||||
<doxygen:param>EXCLUDE_SYMBOLS=std
|
||||
<doxygen:param>"USE_MDFILE_AS_MAINPAGE=\"$(readme)\""
|
||||
<doxygen:param>SOURCE_BROWSER=YES
|
||||
<doxygen:param>"HTML_HEADER=\"$(header)\""
|
||||
<doxygen:param>"HTML_FOOTER=\"$(footer)\""
|
||||
<doxygen:param>"HTML_EXTRA_STYLESHEET=$(stylesheet_arg)"
|
||||
<doxygen:param>HTML_TIMESTAMP=YES
|
||||
<doxygen:param>GENERATE_TREEVIEW=YES
|
||||
<doxygen:param>FULL_SIDEBAR=YES
|
||||
<doxygen:param>DISABLE_INDEX=YES
|
||||
<doxygen:param>ENUM_VALUES_PER_LINE=0
|
||||
<doxygen:param>OBFUSCATE_EMAILS=YES
|
||||
<doxygen:param>USE_MATHJAX=YES
|
||||
<doxygen:param>MATHJAX_VERSION=MathJax_2
|
||||
<doxygen:param>MATHJAX_RELPATH="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/"
|
||||
<doxygen:param>MACRO_EXPANSION=YES
|
||||
<doxygen:param>HAVE_DOT=NO
|
||||
<doxygen:param>CLASS_GRAPH=NO
|
||||
<doxygen:param>DIRECTORY_GRAPH=NO
|
||||
;
|
||||
|
||||
explicit doc.html ;
|
||||
|
||||
# The doxygen rule only informs b2 about the main HTML file, and not about
|
||||
# all the doc directory that gets generated. Using the install rule copies
|
||||
# only a single file, which is incorrect. This is a workaround to copy
|
||||
# the generated docs to the doc/html directory, where they should be.
|
||||
make copyhtml.tag : doc.html : @copy_html_dir ;
|
||||
explicit copyhtml.tag ;
|
||||
actions copy_html_dir
|
||||
{
|
||||
rm -rf $(target_dir)
|
||||
mkdir -p $(target_dir)
|
||||
cp -r $(<:D)/html/doc/* $(target_dir)/
|
||||
echo "Stamped" > "$(<)"
|
||||
}
|
||||
|
||||
# These are used to inform the build system of the
|
||||
# means to build the integrated and stand-alone docs.
|
||||
|
||||
alias boostdoc ;
|
||||
explicit boostdoc ;
|
||||
|
||||
alias boostrelease : copyhtml.tag ;
|
||||
explicit boostrelease ;
|
||||
@@ -32,80 +32,104 @@ html {
|
||||
* Make sure it is wide enough to contain the page title (logo + title + version)
|
||||
*/
|
||||
--side-nav-fixed-width: 335px;
|
||||
--menu-display: none;
|
||||
|
||||
--top-height: 120px;
|
||||
--toc-sticky-top: -25px;
|
||||
--toc-max-height: calc(100vh - 2 * var(--spacing-medium) - 25px);
|
||||
}
|
||||
|
||||
#projectname {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#page-wrapper {
|
||||
height: calc(100vh - 100px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
#content-wrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
#doc-content {
|
||||
overflow-y: scroll;
|
||||
flex: 1;
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
html {
|
||||
--searchbar-background: var(--page-background-color);
|
||||
}
|
||||
|
||||
#side-nav {
|
||||
#sidebar-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: var(--side-nav-fixed-width);
|
||||
max-width: var(--side-nav-fixed-width);
|
||||
top: var(--top-height);
|
||||
overflow: visible;
|
||||
max-width: var(--side-nav-fixed-width);
|
||||
background-color: var(--side-nav-background);
|
||||
border-right: 1px solid rgb(222, 222, 222);
|
||||
}
|
||||
|
||||
#nav-tree, #side-nav {
|
||||
height: calc(100vh - var(--top-height)) !important;
|
||||
#search-box-wrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding-left: 1em;
|
||||
padding-right: 1em;
|
||||
}
|
||||
|
||||
#MSearchBox {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
padding-left: 1em;
|
||||
padding-right: 1em;
|
||||
}
|
||||
|
||||
|
||||
#MSearchBox .left {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
position: static;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
width: auto;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
#MSearchBox .right {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#MSearchSelect {
|
||||
padding-left: 0.75em;
|
||||
left: auto;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
#MSearchField {
|
||||
flex: 1;
|
||||
position: static;
|
||||
width: auto;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
#nav-tree {
|
||||
padding: 0;
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
#nav-sync {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#top {
|
||||
display: block;
|
||||
border-bottom: none;
|
||||
height: var(--top-height);
|
||||
margin-bottom: calc(0px - var(--top-height));
|
||||
max-width: var(--side-nav-fixed-width);
|
||||
overflow: hidden;
|
||||
background: var(--side-nav-background);
|
||||
}
|
||||
#main-nav {
|
||||
float: left;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.ui-resizable-handle {
|
||||
cursor: default;
|
||||
width: 1px !important;
|
||||
box-shadow: 0 calc(-2 * var(--top-height)) 0 0 var(--separator-color);
|
||||
}
|
||||
|
||||
#nav-path {
|
||||
position: fixed;
|
||||
right: 0;
|
||||
left: var(--side-nav-fixed-width);
|
||||
bottom: 0;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
#doc-content {
|
||||
height: calc(100vh - 31px) !important;
|
||||
padding-bottom: calc(3 * var(--spacing-large));
|
||||
padding-top: calc(var(--top-height) - 80px);
|
||||
box-sizing: border-box;
|
||||
margin-left: var(--side-nav-fixed-width) !important;
|
||||
}
|
||||
|
||||
#MSearchBox {
|
||||
width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium)));
|
||||
}
|
||||
|
||||
#MSearchField {
|
||||
width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium)) - 65px);
|
||||
}
|
||||
|
||||
#MSearchResultsWindow {
|
||||
@@ -113,3 +137,9 @@ html {
|
||||
right: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
#sidebar-wrapper {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -552,25 +552,6 @@ a.anchor {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
/* until Doxygen 1.9.4 */
|
||||
.left img#MSearchSelect {
|
||||
left: 0;
|
||||
user-select: none;
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
/* Doxygen 1.9.5 */
|
||||
.left span#MSearchSelect {
|
||||
left: 0;
|
||||
user-select: none;
|
||||
margin-left: 8px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.left #MSearchSelect[src$=".png"] {
|
||||
padding-left: 0
|
||||
}
|
||||
|
||||
.SelectionMark {
|
||||
user-select: none;
|
||||
}
|
||||
@@ -614,9 +595,7 @@ a.anchor {
|
||||
|
||||
#MSearchField {
|
||||
font-size: var(--navigation-font-size);
|
||||
height: calc(var(--searchbar-height) - 2px);
|
||||
background: transparent;
|
||||
width: calc(var(--searchbar-width) - 64px);
|
||||
}
|
||||
|
||||
.MSearchBoxActive #MSearchField {
|
||||
|
||||
19
doc/footer.html
Normal file
19
doc/footer.html
Normal file
@@ -0,0 +1,19 @@
|
||||
<!-- HTML footer for doxygen 1.9.1-->
|
||||
<!-- start footer part -->
|
||||
</div> <!-- close #content-wrapper -->
|
||||
<!--BEGIN GENERATE_TREEVIEW-->
|
||||
<div id="nav-path" class="navpath"><!-- id is needed for treeview function! -->
|
||||
<ul>
|
||||
$navpath
|
||||
<li class="footer">$generatedby <a href="https://www.doxygen.org/index.html"><img class="footer" src="$relpath^doxygen.svg" width="104" height="31" alt="doxygen"/></a> $doxygenversion </li>
|
||||
</ul>
|
||||
</div>
|
||||
<!--END GENERATE_TREEVIEW-->
|
||||
<!--BEGIN !GENERATE_TREEVIEW-->
|
||||
<hr class="footer"/><address class="footer"><small>
|
||||
$generatedby <a href="https://www.doxygen.org/index.html"><img class="footer" src="$relpath^doxygen.svg" width="104" height="31" alt="doxygen"/></a> $doxygenversion
|
||||
</small></address>
|
||||
<!--END !GENERATE_TREEVIEW-->
|
||||
</div> <!-- #page-wrapper -->
|
||||
</body>
|
||||
</html>
|
||||
61
doc/header.html
Normal file
61
doc/header.html
Normal file
@@ -0,0 +1,61 @@
|
||||
<!-- HTML header for doxygen 1.9.1-->
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=9"/>
|
||||
<meta name="generator" content="Doxygen $doxygenversion"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<!--BEGIN PROJECT_NAME--><title>$projectname: $title</title><!--END PROJECT_NAME-->
|
||||
<!--BEGIN !PROJECT_NAME--><title>$title</title><!--END !PROJECT_NAME-->
|
||||
<link href="$relpath^tabs.css" rel="stylesheet" type="text/css"/>
|
||||
<script type="text/javascript" src="$relpath^jquery.js"></script>
|
||||
<script type="text/javascript" src="$relpath^dynsections.js"></script>
|
||||
$treeview
|
||||
$search
|
||||
$mathjax
|
||||
<link href="$relpath^$stylesheet" rel="stylesheet" type="text/css" />
|
||||
$extrastylesheet
|
||||
</head>
|
||||
<body>
|
||||
<div id="page-wrapper">
|
||||
<div id="content-wrapper">
|
||||
<div id="sidebar-wrapper">
|
||||
<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
|
||||
|
||||
<!--BEGIN TITLEAREA-->
|
||||
<div id="titlearea">
|
||||
<table cellspacing="0" cellpadding="0">
|
||||
<tbody>
|
||||
<tr style="height: 56px;">
|
||||
<!--BEGIN PROJECT_LOGO-->
|
||||
<td id="projectlogo"><img alt="Logo" src="$relpath^$projectlogo"/></td>
|
||||
<!--END PROJECT_LOGO-->
|
||||
<!--BEGIN PROJECT_NAME-->
|
||||
<td id="projectalign" style="padding-left: 0.5em;">
|
||||
<div id="projectname">$projectname
|
||||
<!--BEGIN PROJECT_NUMBER--> <span id="projectnumber">$projectnumber</span><!--END PROJECT_NUMBER-->
|
||||
</div>
|
||||
<!--BEGIN PROJECT_BRIEF--><div id="projectbrief">$projectbrief</div><!--END PROJECT_BRIEF-->
|
||||
</td>
|
||||
<!--END PROJECT_NAME-->
|
||||
<!--BEGIN !PROJECT_NAME-->
|
||||
<!--BEGIN PROJECT_BRIEF-->
|
||||
<td style="padding-left: 0.5em;">
|
||||
<div id="projectbrief">$projectbrief</div>
|
||||
</td>
|
||||
<!--END PROJECT_BRIEF-->
|
||||
<!--END !PROJECT_NAME-->
|
||||
<!--BEGIN DISABLE_INDEX-->
|
||||
<!--END DISABLE_INDEX-->
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--BEGIN SEARCHENGINE-->
|
||||
<div id="search-box-wrapper">
|
||||
$searchbox
|
||||
</div>
|
||||
<!--END SEARCHENGINE-->
|
||||
<!--END TITLEAREA-->
|
||||
<!-- end header part -->
|
||||
671
doc/on-the-costs-of-async-abstractions.md
Normal file
671
doc/on-the-costs-of-async-abstractions.md
Normal file
@@ -0,0 +1,671 @@
|
||||
# On the costs of asynchronous abstractions
|
||||
|
||||
The biggest force behind the evolution of
|
||||
[Boost.Redis](https://github.com/boostorg/redis) was my struggling in
|
||||
coming up with a high-level connection abstraction that was capable of
|
||||
multiplexing Redis commands from independent sources while
|
||||
concurrently handling server pushes. This journey taught me many
|
||||
important lessons, many of which are related to the design and
|
||||
performance of asynchronous programs based on Boost.Asio.
|
||||
|
||||
In this article I will share some of the lessons learned, specially
|
||||
those related to the performance costs of _abstractions_ such as
|
||||
`async_read_until` that tend to overschedule into the event-loop. In
|
||||
this context I will also briefly comment on how the topics discussed
|
||||
here influenced my views on the proposed
|
||||
[P2300](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2300r7.html)
|
||||
(a.k.a. Senders and Receivers), which is likely to become the basis of
|
||||
networking in upcoming C++ standards.
|
||||
|
||||
Although the analysis presented in this article uses the Redis communication
|
||||
protocol for illustration I expect it to be useful in general since
|
||||
[RESP3](https://github.com/antirez/RESP3/blob/master/spec.md) shares
|
||||
many similarities with other widely used protocols such as HTTP.
|
||||
|
||||
## Parsing `\r\n`-delimited messages
|
||||
|
||||
The Redis server communicates with its clients by exchanging data
|
||||
serialized in
|
||||
[RESP3](https://github.com/antirez/RESP3/blob/master/spec.md) format.
|
||||
Among the data types supported by this specification, the
|
||||
`\r\n`-delimited messages are some of the most frequent in a typical
|
||||
session. The table below shows some examples
|
||||
|
||||
Command | Response | Wire format | RESP3 name
|
||||
---------|----------|---------------|---------------------
|
||||
PING | PONG | `+PONG\r\n` | simple-string
|
||||
INCR | 42 | `:42\r\n` | number
|
||||
GET | null | `_\r\n` | null
|
||||
|
||||
Redis also supports command pipelines, which provide a way of
|
||||
optimizing round-trip times by batching commands. A pipeline composed
|
||||
by the commands shown in the previous table look like this
|
||||
|
||||
```
|
||||
| Sent in a |
|
||||
| single write |
|
||||
+--------+ | | +-------+
|
||||
| | --------> PING + INCR + GET --------> | |
|
||||
| | | |
|
||||
| Client | | Redis |
|
||||
| | | |
|
||||
| | <-------- "+PONG\r\n:42\r\n_\r\n" <-------- | |
|
||||
+--------+ |<------>|<---->|<-->| +-------+
|
||||
| |
|
||||
| Responses |
|
||||
```
|
||||
|
||||
Messages that use delimiters are so common in networking that a
|
||||
facility called `async_read_until` for reading them incrementally from
|
||||
a socket is already part of Boost.Asio. The coroutine below uses it to
|
||||
print message contents to the screen
|
||||
|
||||
```cpp
|
||||
awaitable<void> parse_resp3_simple_msgs(tcp::socket socket)
|
||||
{
|
||||
for (std::string buffer;;) {
|
||||
auto n = co_await async_read_until(socket, dynamic_buffer(buffer), "\r\n");
|
||||
|
||||
std::cout << buffer.substr(1, n - 3) << std::endl;
|
||||
|
||||
// Consume the buffer.
|
||||
buffer.erase(0, n);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If we pay attention to the buffer content as it is parsed by the code
|
||||
above we can see it is rotated fairly often, for example
|
||||
|
||||
```
|
||||
"+PONG\r\n:100\r\n+OK\r\n_\r\n"
|
||||
":100\r\n+OK\r\n_\r\n"
|
||||
"+OK\r\n_\r\n"
|
||||
"_\r\n"
|
||||
""
|
||||
```
|
||||
|
||||
When I first realized these, apparently excessive, buffer rotations I
|
||||
was concerned they would impact the performance of Boost.Redis in a
|
||||
severe way. To measure the magnitude of this impact I came up with an
|
||||
experimental implementation of Asio's `dynamic_buffer` that consumed
|
||||
the buffer less eagerly than the `std::string::erase` function used
|
||||
above. For that, the implementation increased a buffer offset up
|
||||
to a certain threshold and only then triggered a (larger) rotation.
|
||||
This is illustrated in the diagram below
|
||||
|
||||
```
|
||||
|<---- offset threshold ---->|
|
||||
| |
|
||||
"+PONG\r\n:100\r\n+OK\r\n_\r\n+PONG\r\n"
|
||||
| # Initial offset
|
||||
|
||||
"+PONG\r\n:100\r\n+OK\r\n_\r\n+PONG\r\n"
|
||||
|<------>| # After 1st message
|
||||
|
||||
"+PONG\r\n:100\r\n+OK\r\n_\r\n+PONG\r\n"
|
||||
|<-------------->| # After 2nd message
|
||||
|
||||
"+PONG\r\n:100\r\n+OK\r\n_\r\n+PONG\r\n"
|
||||
|<--------------------->| # After 3rd message
|
||||
|
||||
"+PONG\r\n:100\r\n+OK\r\n_\r\n+PONG\r\n"
|
||||
|<-------------------------->| # Threshold crossed after the 4th message
|
||||
|
||||
"+PONG\r\n"
|
||||
| # After rotation
|
||||
```
|
||||
|
||||
After comparing the performance differences between the two versions I
|
||||
was surprised there wasn't any! But that was also very suspicious
|
||||
since some RESP3 aggregate types contain a considerable number of
|
||||
separators. For example, a map with two pairs `[(key1, value1),
|
||||
(key2, value2)]` encoded in RESP3 requires ten rotations in total
|
||||
|
||||
```
|
||||
"%2\r\n$4\r\nkey1\r\n$6\r\nvalue1\r\n$4\r\nkey2\r\n$6\r\nvalue2\r\n"
|
||||
"$4\r\nkey1\r\n$6\r\nvalue1\r\n$4\r\nkey2\r\n$6\r\nvalue2\r\n"
|
||||
"key1\r\n$6\r\nvalue1\r\n$4\r\nkey2\r\n$6\r\nvalue2\r\n"
|
||||
"$6\r\nvalue1\r\n$4\r\nkey2\r\n$6\r\nvalue2\r\n"
|
||||
...
|
||||
```
|
||||
|
||||
It was evident something more costly was shadowing the buffer
|
||||
rotations. But it couldn't be the search for the separator since it
|
||||
performs equivalently to rotations. It is also easy to show that the
|
||||
overhead is not related to any IO operation since the problem persists
|
||||
if the buffer is never consumed (which causes the function to be
|
||||
called with the same string repeatedly). Once these two factors
|
||||
are removed from the table, we are driven into the conclusion that
|
||||
calling `async_read_until` has an intrinsic cost, let us see what
|
||||
that is.
|
||||
|
||||
### Async operations that complete synchronously considered harmful
|
||||
|
||||
Assume the scenario described earlier where `async_read_until` is used
|
||||
to parse multiple `\r\n`-delimited messages. The following is a
|
||||
detailed description of what happens behind the scenes
|
||||
|
||||
1. `async_read_until` calls `socket.async_read_some` repeatedly
|
||||
until the separator `\r\n` shows up in the buffer
|
||||
|
||||
```
|
||||
"<read1>" # Read 1: needs more data.
|
||||
"<read1><read2>" # Read 2: needs more data.
|
||||
"<read1><read2>" # Read 3: needs more data.
|
||||
"<read1><read2><read3>" # Read 4: needs more data.
|
||||
"<read1><read2><read3>\r\n<bonus bytes>" # separator found, done.
|
||||
```
|
||||
|
||||
2. The last call to `socket.async_read_some` happens to read past
|
||||
the separator `\r\n` (depicted as `<bonus bytes>` above),
|
||||
resulting in bonus (maybe incomplete) messages in the buffer
|
||||
|
||||
```
|
||||
| 1st async_read_some | 2nd async_read_some |
|
||||
| | |
|
||||
"+message content here \r\n:100\r\n+OK\r\n_\r\n+incomplete respo"
|
||||
| | | |
|
||||
| Message wanted |<-- bonus msgs --->|<--incomplete-->|
|
||||
| | msg |
|
||||
| | |
|
||||
| |<---------- bonus bytes ----------->|
|
||||
```
|
||||
|
||||
3. The buffer is consumed and `async_read_until` is called again.
|
||||
However, since the buffer already contains the next message this
|
||||
is an IO-less call
|
||||
|
||||
```
|
||||
":100\r\n+OK\r\n_\r\n+not enough byt"
|
||||
| | |
|
||||
| No IO required | Need more |
|
||||
| to parse these | data |
|
||||
| messages. | |
|
||||
```
|
||||
|
||||
The fact that step 3. doesn't perform any IO implies the operation can
|
||||
complete synchronously, but because this is an asynchronous function
|
||||
Boost.Asio by default won't call the continuation before the
|
||||
function returns. The implementation must therefore enqueue it for
|
||||
execution, as depicted below
|
||||
|
||||
```
|
||||
OP5 ---> OP4 ---> OP3 ---> OP2 ---> OP1 # Reschedules the continuation
|
||||
|
|
||||
OP1 schedules its continuation |
|
||||
+-----------------------------------+
|
||||
|
|
||||
|
|
||||
OP6 ---> OP5 ---> OP4 ---> OP3 ---> OP2 # Reschedules the continuation
|
||||
|
|
||||
OP2 schedules its continuation |
|
||||
+-----------------------------------+
|
||||
|
|
||||
|
|
||||
OP7 ---> OP6 ---> OP5 ---> OP4 ---> OP3
|
||||
```
|
||||
|
||||
When summed up, the excessive rescheduling of continuations lead to
|
||||
performance degradation at scale. But since this is an event-loop
|
||||
there is no way around rescheduling as doing otherwise would mean
|
||||
allowing a task to monopolize the event-loop, preventing other tasks
|
||||
from making progress. The best that can be done is to avoid
|
||||
_overscheduling_, so let us determine how much rescheduling is too
|
||||
much.
|
||||
|
||||
## The intrinsic latency of an event-loop
|
||||
|
||||
An event-loop is a design pattern originally used to handle events
|
||||
external to the application, such as GUIs, networking and other forms
|
||||
of IO. If we take this literally, it becomes evident that the way
|
||||
`async_read_until` works is incompatible with an event-loop since
|
||||
_searching for the separator_ is not an external event and as such
|
||||
should not have to be enqueued for execution.
|
||||
|
||||
Once we constrain ourselves to events that have an external origin,
|
||||
such as anything related to IO and including any form of IPC, the
|
||||
scheduling overhead is reduced considerably since the latency
|
||||
of the transport layer eclipses whatever time it takes to schedule the
|
||||
continuation, for example, according to
|
||||
[these](https://www.boost.org/doc/libs/develop/libs/cobalt/doc/html/index.html#posting_to_an_executor)
|
||||
benchmarks, the time it takes to schedule a task in the
|
||||
`asio::io_context ` is approximately `50ns`.
|
||||
|
||||
To give the reader an idea about the magnitude of this number, if
|
||||
rescheduling alone were to account for 1% of the runtime of an app
|
||||
that uses asynchronous IO to move around data in chunks of size 128kb,
|
||||
then this app would have a throughput of approximately 24Gbs. At such
|
||||
high throughput multiple other factors kick in before any scheduling
|
||||
overhead even starts to manifest.
|
||||
|
||||
It is therefore safe to say that only asynchronous operations that
|
||||
don't perform or are not bound to any IO are ever likely to
|
||||
overschedule in the sense described above. Those cases can be usually
|
||||
avoided, this is what worked for Boost.Redis
|
||||
|
||||
1. `async_read_until` was replaced with calls to
|
||||
`socket.async_read_some` and an incremental parser that does not
|
||||
do any IO.
|
||||
|
||||
2. Channel `try_` functions are used to check if send and receive
|
||||
operations can be called without suspension. For example,
|
||||
`try_send` before `async_send` and `try_receive` before
|
||||
`async_receive` ([see also](https://github.com/chriskohlhoff/asio/commit/fe4fd7acf145335eeefdd19708483c46caeb45e5)
|
||||
`try_send_via_dispatch` for a more aggressive optimization).
|
||||
|
||||
3. Coalescing of individual requests into a single payload to reduce
|
||||
the number of necessary writes on the socket, this is only
|
||||
possible because Redis supports pipelining (good protocols
|
||||
help!).
|
||||
|
||||
4. Increased the socket read sizes to 4kb to reduce the number of
|
||||
reads (which is outweighed by the costs of rotating data in the
|
||||
buffer).
|
||||
|
||||
5. Dropped the `resp3::async_read` abstraction. When I started
|
||||
developing Boost.Redis there was convincing precedent for having
|
||||
a `resp3::async_read` function to read complete RESP3 messages
|
||||
from a socket
|
||||
|
||||
Name | Description
|
||||
---------------------------------------|-------------------
|
||||
`asio::ip::tcp::async_read` | Reads `n` bytes from a stream.
|
||||
`beast::http::async_read` | Reads a complete HTTP message.
|
||||
`beast::websocket::stream::async_read` | Reads a complete Websocket message.
|
||||
`redis::async_read` | Reads a complete RESP3 message.
|
||||
|
||||
It turns out however that this function is also vulnerable to
|
||||
immediate completions since in command pipelines multiple
|
||||
responses show up in the buffer after a call to
|
||||
`socket.async_read_some`. When that happens each call to
|
||||
`resp3::async_read` is IO-less.
|
||||
|
||||
Sometimes it is not possible to avoid asynchronous operations that
|
||||
complete synchronously, in the following sections we will see how to
|
||||
favor throughput over fairness in Boost.Asio.
|
||||
|
||||
### Calling the continuation inline
|
||||
|
||||
In Boost.Asio it is possible to customize how an algorithm executes
|
||||
the continuation when an immediate completion occurs, this includes
|
||||
the ability of calling it inline, thereby avoiding the costs of
|
||||
excessive rescheduling. Here is how it works
|
||||
|
||||
```cpp
|
||||
// (default) The continuation is enqueued for execution, regardless of
|
||||
// whether it is immediate or not.
|
||||
async_read_until(socket, buffer, "\r\n", continuation);
|
||||
|
||||
// Immediate completions are executed in exec2 (otherwise equal to the
|
||||
// version above). The completion is called inline if exec2 is the
|
||||
// same executor that is running the operation.
|
||||
async_read_until(socket, buffer, "\r\n", bind_immediate_executor(exec2, completion));
|
||||
```
|
||||
|
||||
To compare the performance of both cases I have written a small
|
||||
function that calls `async_read_until` in a loop with a buffer that is
|
||||
never consumed so that all completions are immediate. The version
|
||||
below uses the default behaviour
|
||||
|
||||
```cpp
|
||||
void read_safe(tcp::socket& s, std::string& buffer)
|
||||
{
|
||||
auto continuation = [&s, &buffer](auto ec, auto n)
|
||||
{
|
||||
read_safe(s, buffer); // Recursive call
|
||||
};
|
||||
|
||||
// This won't cause stack exhaustion because the continuation is
|
||||
// not called inline but posted in the event loop.
|
||||
async_read_until(s, dynamic_buffer(buffer), "\r\n", continuation);
|
||||
}
|
||||
```
|
||||
|
||||
To optimize away some of the rescheduling the version below uses the
|
||||
`bind_immediate_executor` customization to call the continuation
|
||||
reentrantly and then breaks the stack from time to time to avoid
|
||||
exhausting it
|
||||
|
||||
```cpp
|
||||
void read_reentrant(tcp::socket& s, std::string& buffer)
|
||||
{
|
||||
auto cont = [&](auto, auto)
|
||||
{
|
||||
read_reentrant(s, buffer); // Recursive call
|
||||
};
|
||||
|
||||
// Breaks the callstack after 16 inline calls.
|
||||
if (counter % 16 == 0) {
|
||||
post(s.get_executor(), [cont](){cont({}, 0);});
|
||||
return;
|
||||
}
|
||||
|
||||
// Continuation called reentrantly.
|
||||
async_read_until(s, dynamic_buffer(buffer), "\r\n",
|
||||
bind_immediate_executor(s.get_executor(), cont));
|
||||
}
|
||||
```
|
||||
|
||||
The diagram below shows what the reentrant chain of calls in the code
|
||||
above look like from the event-loop point of view
|
||||
|
||||
```
|
||||
OP5 ---> OP4 ---> OP3 ---> OP2 ---> OP1a # Completes immediately
|
||||
|
|
||||
|
|
||||
... |
|
||||
OP1b # Completes immediately
|
||||
|
|
||||
Waiting for OP5 to |
|
||||
reschedule its |
|
||||
continuation OP1c # Completes immediately
|
||||
|
|
||||
|
|
||||
... |
|
||||
OP1d # Break the call-stack
|
||||
|
|
||||
+-----------------------------------+
|
||||
|
|
||||
OP6 ---> OP5 ---> OP4 ---> OP3 ---> OP2
|
||||
```
|
||||
|
||||
Unsurprisingly, the reentrant code is 3x faster than the one that
|
||||
relies on the default behaviour (don't forget that this is a best case
|
||||
scenario, in the general case not all completions are immediate).
|
||||
Although faster, this strategy has some downsides
|
||||
|
||||
- The overall operation is not as fast as possible since it still
|
||||
has to reschedule from time to time to break the call stack. The
|
||||
less it reschedules the higher the risk of exhausting it.
|
||||
|
||||
- It is too easy to forget to break the stack. For example, the
|
||||
programmer might decide to branch somewhere into another chain of
|
||||
asynchronous calls that also use this strategy. To avoid
|
||||
exhaustion all such branches would have to be safeguarded with a
|
||||
manual rescheduling i.e. `post`.
|
||||
|
||||
- Requires additional layers of complexity such as
|
||||
`bind_immediate_executor` in addition to `bind_executor`.
|
||||
|
||||
- Non-compliat with more strict
|
||||
[guidelines](https://en.wikipedia.org/wiki/The_Power_of_10:_Rules_for_Developing_Safety-Critical_Code)
|
||||
that prohibits reentrat code.
|
||||
|
||||
- There is no simple way of choosing the maximum allowed number of
|
||||
reentrant calls for each function in a way that covers different
|
||||
use cases and users. Library writers and users would be tempted
|
||||
into using a small value reducing the performance advantage.
|
||||
|
||||
- If the socket is always ready for reading the task will
|
||||
monopolize IO for up to `16` interactions which might cause
|
||||
stutter in unrelated tasks as depicted below
|
||||
|
||||
```
|
||||
Unfairness
|
||||
|
||||
+----+----+----+ +----+----+----+ +----+----+----+
|
||||
Socket-1 | | | | | | | | | | | |
|
||||
+----+----+----+----+----+----+----+----+----+----+----+----+
|
||||
Socket-2 | | | | | |
|
||||
+----+ +----+ +----+
|
||||
```
|
||||
|
||||
From the aesthetic point of view the code above is also unpleasant as
|
||||
it breaks the function asynchronous contract by injecting a reentrant
|
||||
behaviour. It gives me the same kind of feeling I have about
|
||||
[recursive
|
||||
mutexes](http://www.zaval.org/resources/library/butenhof1.html).
|
||||
|
||||
Note: It is worth mentioning here that a similar
|
||||
[strategy](https://github.com/NVIDIA/stdexec/blob/6f23dd5b1d523541ce28af32fc2603403ebd36ed/include/exec/trampoline_scheduler.hpp#L52)
|
||||
is used to break the call stack of repeating algorithms in
|
||||
[stdexec](https://github.com/NVIDIA/stdexec), but in this time
|
||||
based on
|
||||
[P2300](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2300r7.html)
|
||||
and not on Boost.Asio.
|
||||
|
||||
### Coroutine tail-calls
|
||||
|
||||
In the previous section we have seen how to avoid overscheduling by
|
||||
instructing the asynchronous operation to call the completion inline
|
||||
on immediate completion. It turns out however that coroutine support
|
||||
for _tail-calls_ provide a way to completely sidestep this problem.
|
||||
This feature is described by
|
||||
[Lewis Baker](https://lewissbaker.github.io/2020/05/11/understanding_symmetric_transfer)
|
||||
as follows
|
||||
|
||||
> A tail-call is one where the current stack-frame is popped before
|
||||
> the call and the current function’s return address becomes the
|
||||
> return-address for the callee. ie. the callee will return directly
|
||||
> the the [sic] caller of this function.
|
||||
|
||||
This means (at least in principle) that a library capable of using
|
||||
tail-calls when an immediate completion occurs neither has to
|
||||
reschedule the continuation nor call it inline. To test how this
|
||||
feature compares to the other styles I have used Boost.Cobalt. The
|
||||
code looks as follows
|
||||
|
||||
```cpp
|
||||
// Warning: risks unfairness and starvation of other tasks.
|
||||
task<void> read_until_unfair()
|
||||
{
|
||||
for (int i = 0; i != repeat; ++i) {
|
||||
co_await async_read_until(s, dynamic_buffer(buffer), "\r\n", cobalt::use_op);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The result of this comparison as listed in the table below
|
||||
|
||||
Time/s | Style | Configuration | Library
|
||||
-------|-----------|-----------------------------|-------------
|
||||
1,0 | Coroutine | `await_ready` optimization | Boost.Cobalt
|
||||
4.8 | Callback | Reentant | Boost.Asio
|
||||
10.3 | Coroutine | `use_op` | Boost.Cobalt
|
||||
14.9 | Callback | Regular | Boost.Asio
|
||||
15.6 | Coroutine | `asio::deferred` | Boost.Asio
|
||||
|
||||
As the reader can see, `cobalt::use_op` ranks 3rd and is considerably
|
||||
faster (10.3 vs 15.6) than the Asio equivalent that uses
|
||||
default-rescheduling. However, by trading rescheduling with tail-calls
|
||||
the code above can now monopolize the event-loop, resulting in
|
||||
unfairness if the socket happens to receive data at a higher rate
|
||||
than other tasks. If by chance data is received continuously
|
||||
on a socket that is always ready for reading, other tasks will starve
|
||||
|
||||
```
|
||||
Starvation
|
||||
|
||||
+----+----+----+----+----+----+----+----+----+----+----+----+
|
||||
Socket-1 | | | | | | | | | | | | |
|
||||
+----+----+----+----+----+----+----+----+----+----+----+----+
|
||||
|
||||
Socket-2 Starving ...
|
||||
|
||||
```
|
||||
|
||||
To avoid this problem the programmer is forced to reschedule from time
|
||||
to time, in the same way we did for the reentrant calls
|
||||
|
||||
```cpp
|
||||
task<void> read_until_fair()
|
||||
{
|
||||
for (int i = 0; i != repeat; ++i) {
|
||||
if (repeat % 16 == 0) {
|
||||
// Reschedules to address unfairness and starvation of
|
||||
// other tasks.
|
||||
co_await post(cobalt::use_op);
|
||||
continue;
|
||||
}
|
||||
|
||||
co_await async_read_until(s, dynamic_buffer(buffer), "\r\n", cobalt::use_op);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Delegating fairness-safety to applications is a dangerous game.
|
||||
This is a
|
||||
[problem](https://tokio.rs/blog/2020-04-preemption) the Tokio
|
||||
community had to deal with before Tokio runtime started enforcing
|
||||
rescheduling (after 256 successful operations)
|
||||
|
||||
> If data is received faster than it can be processed, it is possible
|
||||
> that more data will have already been received by the time the
|
||||
> processing of a data chunk completes. In this case, .await will
|
||||
> never yield control back to the scheduler, other tasks will not be
|
||||
> scheduled, resulting in starvation and large latency variance.
|
||||
|
||||
> Currently, the answer to this problem is that the user of Tokio is
|
||||
> responsible for adding yield points in both the application and
|
||||
> libraries. In practice, very few actually do this and end up being
|
||||
> vulnerable to this sort of problem.
|
||||
|
||||
### Safety in P2300 (Senders and Receivers)
|
||||
|
||||
As of this writing, the C++ standards committee (WG21) has been
|
||||
pursuing the standardization of a networking library for almost 20
|
||||
years. One of the biggest obstacles that prevented it from happening
|
||||
was a disagreement on what the _asynchronous model_ that underlies
|
||||
networking should look like. Until 2021 that model was basically
|
||||
Boost.Asio _executors_, but in this
|
||||
[poll](https://www.reddit.com/r/cpp/comments/q6tgod/c_committee_polling_results_for_asynchronous/)
|
||||
the committee decided to abandon that front and concentrate efforts on
|
||||
the new [P2300](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2300r7.html)
|
||||
proposal, also known as _senders and receivers_. The decision was
|
||||
quite [abrupt](https://isocpp.org/files/papers/P2464R0.html)
|
||||
|
||||
> The original plan about a week earlier than the actual writing of
|
||||
> this paper was to write a paper that makes a case for standardizing
|
||||
> the Networking TS.
|
||||
|
||||
and opinions turned out to be very strong against Boost.Asio (see
|
||||
[this](https://api.csswg.org/bikeshed/?force=1&url=https://raw.githubusercontent.com/brycelelbach/wg21_p2459_2022_january_library_evolution_poll_outcomes/main/2022_january_library_evolution_poll_outcomes.bs)
|
||||
for how each voter backed their vote)
|
||||
|
||||
> The whole concept is completely useless, there's no composed code
|
||||
> you can write with it.
|
||||
|
||||
The part of that debate that interests us most here is stated in
|
||||
[P2471](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p2471r1.pdf),
|
||||
that compares Boost.Asio with P2300
|
||||
|
||||
> Yes, default rescheduling each operation and default not
|
||||
> rescheduling each operation, is a poor trade off. IMO both options
|
||||
> are poor. The one good option that I know of that can prevent stack
|
||||
> exhaustion is first-class tail-recursion in library or language
|
||||
|
||||
> ASIO has chosen to require that every async operation must schedule
|
||||
> the completion on a scheduler (every read, every write, etc..).
|
||||
|
||||
> sender/receiver has not decided to
|
||||
> require that the completion be scheduled.
|
||||
|
||||
> This is why I consider tail-call the only good solution. Scheduling
|
||||
> solutions are all inferior (give thanks to Lewis for this shift in
|
||||
> my understanding :) ).
|
||||
|
||||
Although tail-calls solve the problem of stack-exhaustion as we have
|
||||
seen above, it makes the code vulnerable to unfairness and starvation
|
||||
and therefore it is not an alternative to default-rescheduling as the
|
||||
quotation above is implying. To deal with the lack of
|
||||
default-rescheduling, libraries and applications built on top of P2300
|
||||
have to address the aforementioned problems, layer after layer. For
|
||||
example,
|
||||
[stdexec](https://github.com/NVIDIA/stdexec) has invented something
|
||||
called
|
||||
_[trampoline-scheduler](https://github.com/NVIDIA/stdexec/blob/e7cd275273525dbc693f4bf5f6dc4d4181b639e4/include/exec/trampoline_scheduler.hpp)_
|
||||
to protect repeating algorithms such as `repeat_effect_until` from
|
||||
exhausting the stack. This construct however is built around
|
||||
reentracy, allowing
|
||||
[sixteen](https://github.com/NVIDIA/stdexec/blob/83cdb92d316e8b3bca1357e2cf49fc39e9bed403/include/exec/trampoline_scheduler.hpp#L52)
|
||||
levels of inline calls by default. While in Boost.Asio it is possible to use
|
||||
reentracy as an optimization for a corner cases, here it is made its
|
||||
_modus operandi_, the downsides of this approach have already been stated in a
|
||||
previous section so I won't repeat it here.
|
||||
|
||||
Also the fact that a special scheduler is needed by specific
|
||||
algorithms is a problem on its own since it contradicts one of the
|
||||
main selling points of P2300 which is that of being _generic_. For
|
||||
example, [P2464R0](https://isocpp.org/files/papers/P2464R0.html) uses
|
||||
the code below as an example
|
||||
|
||||
```cpp
|
||||
void
|
||||
run_that_io_operation(
|
||||
scheduler auto sched,
|
||||
sender_of<network_buffer> auto wrapping_continuation)
|
||||
{
|
||||
// snip
|
||||
}
|
||||
```
|
||||
|
||||
and states
|
||||
|
||||
> I have no idea what the sched's concrete type is. I have no idea
|
||||
> what the wrapping_continuation's concrete type is. They're none of
|
||||
> my business, ...
|
||||
|
||||
Hence, by being generic, the algorithms built on top of P2300 are also
|
||||
unsafe (against stack-exhaustion, unfairness and starvation). Otherwise,
|
||||
if library writers require a specific scheduler to ensure safety, then
|
||||
the algorithms become automatically non-generic, pick your poison!
|
||||
|
||||
The proposers of P2300 claim that it doesn't address safety because it
|
||||
should be seen as the low-level building blocks of asynchronous
|
||||
programming and that its the role of higher-level libraries, to deal
|
||||
with that. This claim however does not hold since, as we have just
|
||||
seen, Boost.Asio also provides those building blocks but does so in a
|
||||
safe way. In fact during the whole development of Boost.Redis I never
|
||||
had to think about these kinds of problems because safety is built
|
||||
from the ground up.
|
||||
|
||||
### Avoiding coroutine suspension with `await_ready`
|
||||
|
||||
Now let us get back to the first place in the table above, which uses
|
||||
the `await_ready` optimization from Boost.Cobalt. This API provides
|
||||
users with the ability to avoid coroutine suspension altogether in
|
||||
case the separator is already present in the buffer. It works by
|
||||
defining a `struct` with the following interface
|
||||
|
||||
```cpp
|
||||
struct read_until : cobalt::op<error_code, std::size_t> {
|
||||
...
|
||||
|
||||
void ready(cobalt::handler<error_code, std::size_t> handler) override
|
||||
{
|
||||
// Search for the separator in buffer and call the handler if found
|
||||
}
|
||||
|
||||
void initiate(cobalt::completion_handler<error_code, std::size_t> complete) override
|
||||
{
|
||||
// Regular call to async_read_until.
|
||||
async_read_until(socket, buffer, delim, std::move(complete));
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
and the code that uses it
|
||||
|
||||
```cpp
|
||||
for (int i = 0; i != repeat; ++i) {
|
||||
co_await read_until(socket, dynamic_buffer(buffer));
|
||||
}
|
||||
```
|
||||
|
||||
In essence, what the code above does is to skip a call to
|
||||
`async_read_unil` by first checking with the ready function whether
|
||||
the forthcoming operation is going to complete immediately. The
|
||||
nice thing about it is that the programmer can use this optimization
|
||||
only when a performance bottleneck is detected, without planing for it
|
||||
in advance. The drawback however is that it requires reimplementing
|
||||
the search for the separator in the body of the `ready` function,
|
||||
defeating the purpose of using `async_read_until` in first place as
|
||||
(again) it would have been simpler to reformulate the operation in
|
||||
terms of `socket.async_read_some` directly.
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
Thanks to Klemens Morgenstern for answering questions about
|
||||
Boost.Cobalt.
|
||||
|
||||
52
example/CMakeLists.txt
Normal file
52
example/CMakeLists.txt
Normal file
@@ -0,0 +1,52 @@
|
||||
add_library(examples_main STATIC main.cpp)
|
||||
target_compile_features(examples_main PRIVATE cxx_std_20)
|
||||
target_link_libraries(examples_main PRIVATE boost_redis_project_options)
|
||||
|
||||
macro(make_example EXAMPLE_NAME STANDARD)
|
||||
add_executable(${EXAMPLE_NAME} ${EXAMPLE_NAME}.cpp)
|
||||
target_link_libraries(${EXAMPLE_NAME} PRIVATE boost_redis_src)
|
||||
target_link_libraries(${EXAMPLE_NAME} PRIVATE boost_redis_project_options)
|
||||
target_compile_features(${EXAMPLE_NAME} PRIVATE cxx_std_${STANDARD})
|
||||
if (${STANDARD} STREQUAL "20")
|
||||
target_link_libraries(${EXAMPLE_NAME} PRIVATE examples_main)
|
||||
endif()
|
||||
if (${EXAMPLE_NAME} STREQUAL "cpp20_json")
|
||||
target_link_libraries(${EXAMPLE_NAME} PRIVATE Boost::json Boost::container_hash)
|
||||
endif()
|
||||
endmacro()
|
||||
|
||||
macro(make_testable_example EXAMPLE_NAME STANDARD)
|
||||
make_example(${EXAMPLE_NAME} ${STANDARD})
|
||||
if (BOOST_REDIS_INTEGRATION_TESTS)
|
||||
add_test(${EXAMPLE_NAME} ${EXAMPLE_NAME})
|
||||
endif()
|
||||
endmacro()
|
||||
|
||||
make_testable_example(cpp17_intro 17)
|
||||
make_testable_example(cpp17_intro_sync 17)
|
||||
|
||||
make_testable_example(cpp20_intro 20)
|
||||
make_testable_example(cpp20_containers 20)
|
||||
make_testable_example(cpp20_json 20)
|
||||
make_testable_example(cpp20_intro_tls 20)
|
||||
|
||||
make_example(cpp20_subscriber 20)
|
||||
make_example(cpp20_streams 20)
|
||||
make_example(cpp20_echo_server 20)
|
||||
make_example(cpp20_resolve_with_sentinel 20)
|
||||
|
||||
# We test the protobuf example only on gcc.
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
find_package(Protobuf)
|
||||
if (Protobuf_FOUND)
|
||||
protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS person.proto)
|
||||
make_testable_example(cpp20_protobuf 20)
|
||||
target_sources(cpp20_protobuf PUBLIC ${PROTO_SRCS} ${PROTO_HDRS})
|
||||
target_link_libraries(cpp20_protobuf PRIVATE ${Protobuf_LIBRARIES})
|
||||
target_include_directories(cpp20_protobuf PUBLIC ${Protobuf_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (NOT MSVC)
|
||||
make_example(cpp20_chat_room 20)
|
||||
endif()
|
||||
@@ -8,7 +8,7 @@
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <iostream>
|
||||
|
||||
namespace net = boost::asio;
|
||||
namespace asio = boost::asio;
|
||||
using boost::redis::connection;
|
||||
using boost::redis::request;
|
||||
using boost::redis::response;
|
||||
@@ -29,10 +29,10 @@ auto main(int argc, char * argv[]) -> int
|
||||
|
||||
response<std::string> resp;
|
||||
|
||||
net::io_context ioc;
|
||||
asio::io_context ioc;
|
||||
connection conn{ioc};
|
||||
|
||||
conn.async_run(cfg, {}, net::detached);
|
||||
conn.async_run(cfg, {}, asio::detached);
|
||||
|
||||
conn.async_exec(req, resp, [&](auto ec, auto) {
|
||||
if (!ec)
|
||||
@@ -9,7 +9,6 @@
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
|
||||
namespace net = boost::asio;
|
||||
using boost::redis::sync_connection;
|
||||
using boost::redis::request;
|
||||
using boost::redis::response;
|
||||
@@ -17,16 +17,23 @@
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
#if defined(BOOST_ASIO_HAS_POSIX_STREAM_DESCRIPTOR)
|
||||
|
||||
namespace net = boost::asio;
|
||||
using stream_descriptor = net::deferred_t::as_default_on_t<net::posix::stream_descriptor>;
|
||||
using signal_set = net::deferred_t::as_default_on_t<net::signal_set>;
|
||||
using boost::redis::request;
|
||||
using boost::redis::generic_response;
|
||||
namespace asio = boost::asio;
|
||||
using stream_descriptor = asio::deferred_t::as_default_on_t<asio::posix::stream_descriptor>;
|
||||
using signal_set = asio::deferred_t::as_default_on_t<asio::signal_set>;
|
||||
using boost::asio::async_read_until;
|
||||
using boost::asio::awaitable;
|
||||
using boost::asio::co_spawn;
|
||||
using boost::asio::consign;
|
||||
using boost::asio::deferred;
|
||||
using boost::asio::detached;
|
||||
using boost::asio::dynamic_buffer;
|
||||
using boost::asio::redirect_error;
|
||||
using boost::asio::use_awaitable;
|
||||
using boost::redis::config;
|
||||
using boost::redis::connection;
|
||||
using boost::redis::generic_response;
|
||||
using boost::redis::ignore;
|
||||
using net::redirect_error;
|
||||
using net::use_awaitable;
|
||||
using boost::redis::request;
|
||||
using boost::system::error_code;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
@@ -34,20 +41,22 @@ using namespace std::chrono_literals;
|
||||
// terminals and type messages to stdin.
|
||||
|
||||
auto
|
||||
receiver(std::shared_ptr<connection> conn) -> net::awaitable<void>
|
||||
receiver(std::shared_ptr<connection> conn) -> awaitable<void>
|
||||
{
|
||||
request req;
|
||||
req.push("SUBSCRIBE", "channel");
|
||||
|
||||
generic_response resp;
|
||||
conn->set_receive_response(resp);
|
||||
|
||||
while (conn->will_reconnect()) {
|
||||
|
||||
// Subscribe to channels.
|
||||
co_await conn->async_exec(req, ignore, net::deferred);
|
||||
co_await conn->async_exec(req, ignore, deferred);
|
||||
|
||||
// Loop reading Redis push messages.
|
||||
for (generic_response resp;;) {
|
||||
error_code ec;
|
||||
co_await conn->async_receive(resp, redirect_error(use_awaitable, ec));
|
||||
for (error_code ec;;) {
|
||||
co_await conn->async_receive(redirect_error(use_awaitable, ec));
|
||||
if (ec)
|
||||
break; // Connection lost, break so we can reconnect to channels.
|
||||
std::cout
|
||||
@@ -61,27 +70,27 @@ receiver(std::shared_ptr<connection> conn) -> net::awaitable<void>
|
||||
}
|
||||
|
||||
// Publishes stdin messages to a Redis channel.
|
||||
auto publisher(std::shared_ptr<stream_descriptor> in, std::shared_ptr<connection> conn) -> net::awaitable<void>
|
||||
auto publisher(std::shared_ptr<stream_descriptor> in, std::shared_ptr<connection> conn) -> awaitable<void>
|
||||
{
|
||||
for (std::string msg;;) {
|
||||
auto n = co_await net::async_read_until(*in, net::dynamic_buffer(msg, 1024), "\n");
|
||||
auto n = co_await async_read_until(*in, dynamic_buffer(msg, 1024), "\n");
|
||||
request req;
|
||||
req.push("PUBLISH", "channel", msg);
|
||||
co_await conn->async_exec(req, ignore, net::deferred);
|
||||
co_await conn->async_exec(req, ignore, deferred);
|
||||
msg.erase(0, n);
|
||||
}
|
||||
}
|
||||
|
||||
// Called from the main function (see main.cpp)
|
||||
auto co_main(config cfg) -> net::awaitable<void>
|
||||
auto co_main(config cfg) -> awaitable<void>
|
||||
{
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
auto ex = co_await asio::this_coro::executor;
|
||||
auto conn = std::make_shared<connection>(ex);
|
||||
auto stream = std::make_shared<stream_descriptor>(ex, ::dup(STDIN_FILENO));
|
||||
|
||||
net::co_spawn(ex, receiver(conn), net::detached);
|
||||
net::co_spawn(ex, publisher(stream, conn), net::detached);
|
||||
conn->async_run(cfg, {}, net::consign(net::detached, conn));
|
||||
co_spawn(ex, receiver(conn), detached);
|
||||
co_spawn(ex, publisher(stream, conn), detached);
|
||||
conn->async_run(cfg, {}, consign(detached, conn));
|
||||
|
||||
signal_set sig_set{ex, SIGINT, SIGTERM};
|
||||
co_await sig_set.async_wait();
|
||||
@@ -90,7 +99,7 @@ auto co_main(config cfg) -> net::awaitable<void>
|
||||
}
|
||||
|
||||
#else // defined(BOOST_ASIO_HAS_POSIX_STREAM_DESCRIPTOR)
|
||||
auto co_main(config const&) -> net::awaitable<void>
|
||||
auto co_main(config const&) -> awaitable<void>
|
||||
{
|
||||
std::cout << "Requires support for posix streams." << std::endl;
|
||||
co_return;
|
||||
@@ -14,13 +14,17 @@
|
||||
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
|
||||
namespace net = boost::asio;
|
||||
namespace asio = boost::asio;
|
||||
using boost::redis::request;
|
||||
using boost::redis::response;
|
||||
using boost::redis::ignore_t;
|
||||
using boost::redis::ignore;
|
||||
using boost::redis::config;
|
||||
using boost::redis::connection;
|
||||
using boost::asio::awaitable;
|
||||
using boost::asio::deferred;
|
||||
using boost::asio::detached;
|
||||
using boost::asio::consign;
|
||||
|
||||
void print(std::map<std::string, std::string> const& cont)
|
||||
{
|
||||
@@ -35,7 +39,7 @@ void print(std::vector<int> const& cont)
|
||||
}
|
||||
|
||||
// Stores the content of some STL containers in Redis.
|
||||
auto store(std::shared_ptr<connection> conn) -> net::awaitable<void>
|
||||
auto store(std::shared_ptr<connection> conn) -> awaitable<void>
|
||||
{
|
||||
std::vector<int> vec
|
||||
{1, 2, 3, 4, 5, 6};
|
||||
@@ -47,10 +51,10 @@ auto store(std::shared_ptr<connection> conn) -> net::awaitable<void>
|
||||
req.push_range("RPUSH", "rpush-key", vec);
|
||||
req.push_range("HSET", "hset-key", map);
|
||||
|
||||
co_await conn->async_exec(req, ignore, net::deferred);
|
||||
co_await conn->async_exec(req, ignore, deferred);
|
||||
}
|
||||
|
||||
auto hgetall(std::shared_ptr<connection> conn) -> net::awaitable<void>
|
||||
auto hgetall(std::shared_ptr<connection> conn) -> awaitable<void>
|
||||
{
|
||||
// A request contains multiple commands.
|
||||
request req;
|
||||
@@ -60,13 +64,13 @@ auto hgetall(std::shared_ptr<connection> conn) -> net::awaitable<void>
|
||||
response<std::map<std::string, std::string>> resp;
|
||||
|
||||
// Executes the request and reads the response.
|
||||
co_await conn->async_exec(req, resp, net::deferred);
|
||||
co_await conn->async_exec(req, resp, deferred);
|
||||
|
||||
print(std::get<0>(resp).value());
|
||||
}
|
||||
|
||||
// Retrieves in a transaction.
|
||||
auto transaction(std::shared_ptr<connection> conn) -> net::awaitable<void>
|
||||
auto transaction(std::shared_ptr<connection> conn) -> awaitable<void>
|
||||
{
|
||||
request req;
|
||||
req.push("MULTI");
|
||||
@@ -81,17 +85,17 @@ auto transaction(std::shared_ptr<connection> conn) -> net::awaitable<void>
|
||||
response<std::optional<std::vector<int>>, std::optional<std::map<std::string, std::string>>> // exec
|
||||
> resp;
|
||||
|
||||
co_await conn->async_exec(req, resp, net::deferred);
|
||||
co_await conn->async_exec(req, resp, deferred);
|
||||
|
||||
print(std::get<0>(std::get<3>(resp).value()).value().value());
|
||||
print(std::get<1>(std::get<3>(resp).value()).value().value());
|
||||
}
|
||||
|
||||
// Called from the main function (see main.cpp)
|
||||
net::awaitable<void> co_main(config cfg)
|
||||
awaitable<void> co_main(config cfg)
|
||||
{
|
||||
auto conn = std::make_shared<connection>(co_await net::this_coro::executor);
|
||||
conn->async_run(cfg, {}, net::consign(net::detached, conn));
|
||||
auto conn = std::make_shared<connection>(co_await asio::this_coro::executor);
|
||||
conn->async_run(cfg, {}, consign(detached, conn));
|
||||
|
||||
co_await store(conn);
|
||||
co_await transaction(conn);
|
||||
@@ -14,10 +14,10 @@
|
||||
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
|
||||
namespace net = boost::asio;
|
||||
using tcp_socket = net::deferred_t::as_default_on_t<net::ip::tcp::socket>;
|
||||
using tcp_acceptor = net::deferred_t::as_default_on_t<net::ip::tcp::acceptor>;
|
||||
using signal_set = net::deferred_t::as_default_on_t<net::signal_set>;
|
||||
namespace asio = boost::asio;
|
||||
using tcp_socket = asio::deferred_t::as_default_on_t<asio::ip::tcp::socket>;
|
||||
using tcp_acceptor = asio::deferred_t::as_default_on_t<asio::ip::tcp::acceptor>;
|
||||
using signal_set = asio::deferred_t::as_default_on_t<asio::signal_set>;
|
||||
using boost::redis::request;
|
||||
using boost::redis::response;
|
||||
using boost::redis::config;
|
||||
@@ -25,16 +25,16 @@ using boost::system::error_code;
|
||||
using boost::redis::connection;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
auto echo_server_session(tcp_socket socket, std::shared_ptr<connection> conn) -> net::awaitable<void>
|
||||
auto echo_server_session(tcp_socket socket, std::shared_ptr<connection> conn) -> asio::awaitable<void>
|
||||
{
|
||||
request req;
|
||||
response<std::string> resp;
|
||||
|
||||
for (std::string buffer;;) {
|
||||
auto n = co_await net::async_read_until(socket, net::dynamic_buffer(buffer, 1024), "\n");
|
||||
auto n = co_await asio::async_read_until(socket, asio::dynamic_buffer(buffer, 1024), "\n");
|
||||
req.push("PING", buffer);
|
||||
co_await conn->async_exec(req, resp, net::deferred);
|
||||
co_await net::async_write(socket, net::buffer(std::get<0>(resp).value()));
|
||||
co_await conn->async_exec(req, resp, asio::deferred);
|
||||
co_await asio::async_write(socket, asio::buffer(std::get<0>(resp).value()));
|
||||
std::get<0>(resp).value().clear();
|
||||
req.clear();
|
||||
buffer.erase(0, n);
|
||||
@@ -42,25 +42,25 @@ auto echo_server_session(tcp_socket socket, std::shared_ptr<connection> conn) ->
|
||||
}
|
||||
|
||||
// Listens for tcp connections.
|
||||
auto listener(std::shared_ptr<connection> conn) -> net::awaitable<void>
|
||||
auto listener(std::shared_ptr<connection> conn) -> asio::awaitable<void>
|
||||
{
|
||||
try {
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
tcp_acceptor acc(ex, {net::ip::tcp::v4(), 55555});
|
||||
auto ex = co_await asio::this_coro::executor;
|
||||
tcp_acceptor acc(ex, {asio::ip::tcp::v4(), 55555});
|
||||
for (;;)
|
||||
net::co_spawn(ex, echo_server_session(co_await acc.async_accept(), conn), net::detached);
|
||||
asio::co_spawn(ex, echo_server_session(co_await acc.async_accept(), conn), asio::detached);
|
||||
} catch (std::exception const& e) {
|
||||
std::clog << "Listener: " << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
// Called from the main function (see main.cpp)
|
||||
auto co_main(config cfg) -> net::awaitable<void>
|
||||
auto co_main(config cfg) -> asio::awaitable<void>
|
||||
{
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
auto ex = co_await asio::this_coro::executor;
|
||||
auto conn = std::make_shared<connection>(ex);
|
||||
net::co_spawn(ex, listener(conn), net::detached);
|
||||
conn->async_run(cfg, {}, net::consign(net::detached, conn));
|
||||
asio::co_spawn(ex, listener(conn), asio::detached);
|
||||
conn->async_run(cfg, {}, asio::consign(asio::detached, conn));
|
||||
|
||||
signal_set sig_set(ex, SIGINT, SIGTERM);
|
||||
co_await sig_set.async_wait();
|
||||
@@ -13,17 +13,17 @@
|
||||
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
|
||||
namespace net = boost::asio;
|
||||
namespace asio = boost::asio;
|
||||
using boost::redis::request;
|
||||
using boost::redis::response;
|
||||
using boost::redis::config;
|
||||
using boost::redis::connection;
|
||||
|
||||
// Called from the main function (see main.cpp)
|
||||
auto co_main(config cfg) -> net::awaitable<void>
|
||||
auto co_main(config cfg) -> asio::awaitable<void>
|
||||
{
|
||||
auto conn = std::make_shared<connection>(co_await net::this_coro::executor);
|
||||
conn->async_run(cfg, {}, net::consign(net::detached, conn));
|
||||
auto conn = std::make_shared<connection>(co_await asio::this_coro::executor);
|
||||
conn->async_run(cfg, {}, asio::consign(asio::detached, conn));
|
||||
|
||||
// A request containing only a ping command.
|
||||
request req;
|
||||
@@ -33,7 +33,7 @@ auto co_main(config cfg) -> net::awaitable<void>
|
||||
response<std::string> resp;
|
||||
|
||||
// Executes the request.
|
||||
co_await conn->async_exec(req, resp, net::deferred);
|
||||
co_await conn->async_exec(req, resp, asio::deferred);
|
||||
conn->cancel();
|
||||
|
||||
std::cout << "PING: " << std::get<0>(resp).value() << std::endl;
|
||||
@@ -13,20 +13,20 @@
|
||||
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
|
||||
namespace net = boost::asio;
|
||||
namespace asio = boost::asio;
|
||||
using boost::redis::request;
|
||||
using boost::redis::response;
|
||||
using boost::redis::config;
|
||||
using boost::redis::logger;
|
||||
using boost::redis::connection;
|
||||
|
||||
auto verify_certificate(bool, net::ssl::verify_context&) -> bool
|
||||
auto verify_certificate(bool, asio::ssl::verify_context&) -> bool
|
||||
{
|
||||
std::cout << "set_verify_callback" << std::endl;
|
||||
return true;
|
||||
}
|
||||
|
||||
auto co_main(config cfg) -> net::awaitable<void>
|
||||
auto co_main(config cfg) -> asio::awaitable<void>
|
||||
{
|
||||
cfg.use_ssl = true;
|
||||
cfg.username = "aedis";
|
||||
@@ -34,18 +34,18 @@ auto co_main(config cfg) -> net::awaitable<void>
|
||||
cfg.addr.host = "db.occase.de";
|
||||
cfg.addr.port = "6380";
|
||||
|
||||
auto conn = std::make_shared<connection>(co_await net::this_coro::executor);
|
||||
conn->async_run(cfg, {}, net::consign(net::detached, conn));
|
||||
auto conn = std::make_shared<connection>(co_await asio::this_coro::executor);
|
||||
conn->async_run(cfg, {}, asio::consign(asio::detached, conn));
|
||||
|
||||
request req;
|
||||
req.push("PING");
|
||||
|
||||
response<std::string> resp;
|
||||
|
||||
conn->next_layer().set_verify_mode(net::ssl::verify_peer);
|
||||
conn->next_layer().set_verify_mode(asio::ssl::verify_peer);
|
||||
conn->next_layer().set_verify_callback(verify_certificate);
|
||||
|
||||
co_await conn->async_exec(req, resp, net::deferred);
|
||||
co_await conn->async_exec(req, resp, asio::deferred);
|
||||
conn->cancel();
|
||||
|
||||
std::cout << "Response: " << std::get<0>(resp).value() << std::endl;
|
||||
@@ -15,15 +15,13 @@
|
||||
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
|
||||
#define BOOST_JSON_NO_LIB
|
||||
#define BOOST_CONTAINER_NO_LIB
|
||||
#include <boost/json/serialize.hpp>
|
||||
#include <boost/json/parse.hpp>
|
||||
#include <boost/json/value_from.hpp>
|
||||
#include <boost/json/value_to.hpp>
|
||||
#include <boost/redis/resp3/serialization.hpp>
|
||||
#include <boost/json/src.hpp>
|
||||
|
||||
namespace net = boost::asio;
|
||||
namespace asio = boost::asio;
|
||||
using namespace boost::describe;
|
||||
using boost::redis::request;
|
||||
using boost::redis::response;
|
||||
@@ -41,18 +39,18 @@ struct user {
|
||||
// The type must be described for serialization to work.
|
||||
BOOST_DESCRIBE_STRUCT(user, (), (name, age, country))
|
||||
|
||||
// Boost.Redis customization points (examples/json.hpp)
|
||||
// Boost.Redis customization points (example/json.hpp)
|
||||
void boost_redis_to_bulk(std::string& to, user const& u)
|
||||
{ boost::redis::resp3::boost_redis_to_bulk(to, boost::json::serialize(boost::json::value_from(u))); }
|
||||
|
||||
void boost_redis_from_bulk(user& u, std::string_view sv, boost::system::error_code&)
|
||||
{ u = boost::json::value_to<user>(boost::json::parse(sv)); }
|
||||
|
||||
auto co_main(config cfg) -> net::awaitable<void>
|
||||
auto co_main(config cfg) -> asio::awaitable<void>
|
||||
{
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
auto ex = co_await asio::this_coro::executor;
|
||||
auto conn = std::make_shared<connection>(ex);
|
||||
conn->async_run(cfg, {}, net::consign(net::detached, conn));
|
||||
conn->async_run(cfg, {}, asio::consign(asio::detached, conn));
|
||||
|
||||
// user object that will be stored in Redis in json format.
|
||||
user const u{"Joao", "58", "Brazil"};
|
||||
@@ -64,7 +62,7 @@ auto co_main(config cfg) -> net::awaitable<void>
|
||||
|
||||
response<ignore_t, user> resp;
|
||||
|
||||
co_await conn->async_exec(req, resp, net::deferred);
|
||||
co_await conn->async_exec(req, resp, asio::deferred);
|
||||
conn->cancel();
|
||||
|
||||
// Prints the first ping
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
|
||||
namespace net = boost::asio;
|
||||
namespace asio = boost::asio;
|
||||
using boost::redis::request;
|
||||
using boost::redis::response;
|
||||
using boost::redis::operation;
|
||||
@@ -27,10 +27,10 @@ using boost::redis::ignore_t;
|
||||
using boost::redis::config;
|
||||
using boost::redis::connection;
|
||||
|
||||
// The protobuf type described in examples/person.proto
|
||||
// The protobuf type described in example/person.proto
|
||||
using tutorial::person;
|
||||
|
||||
// Boost.Redis customization points (examples/protobuf.hpp)
|
||||
// Boost.Redis customization points (example/protobuf.hpp)
|
||||
namespace tutorial
|
||||
{
|
||||
|
||||
@@ -58,11 +58,11 @@ void boost_redis_from_bulk(person& u, std::string_view sv, boost::system::error_
|
||||
using tutorial::boost_redis_to_bulk;
|
||||
using tutorial::boost_redis_from_bulk;
|
||||
|
||||
net::awaitable<void> co_main(config cfg)
|
||||
asio::awaitable<void> co_main(config cfg)
|
||||
{
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
auto ex = co_await asio::this_coro::executor;
|
||||
auto conn = std::make_shared<connection>(ex);
|
||||
conn->async_run(cfg, {}, net::consign(net::detached, conn));
|
||||
conn->async_run(cfg, {}, asio::consign(asio::detached, conn));
|
||||
|
||||
person p;
|
||||
p.set_name("Louis");
|
||||
@@ -76,7 +76,7 @@ net::awaitable<void> co_main(config cfg)
|
||||
response<ignore_t, person> resp;
|
||||
|
||||
// Sends the request and receives the response.
|
||||
co_await conn->async_exec(req, resp, net::deferred);
|
||||
co_await conn->async_exec(req, resp, asio::deferred);
|
||||
conn->cancel();
|
||||
|
||||
std::cout
|
||||
@@ -12,8 +12,8 @@
|
||||
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
|
||||
namespace net = boost::asio;
|
||||
using endpoints = net::ip::tcp::resolver::results_type;
|
||||
namespace asio = boost::asio;
|
||||
using endpoints = asio::ip::tcp::resolver::results_type;
|
||||
using boost::redis::request;
|
||||
using boost::redis::response;
|
||||
using boost::redis::ignore_t;
|
||||
@@ -22,18 +22,18 @@ using boost::redis::address;
|
||||
using boost::redis::connection;
|
||||
|
||||
auto redir(boost::system::error_code& ec)
|
||||
{ return net::redirect_error(net::use_awaitable, ec); }
|
||||
{ return asio::redirect_error(asio::use_awaitable, ec); }
|
||||
|
||||
// For more info see
|
||||
// - https://redis.io/docs/manual/sentinel.
|
||||
// - https://redis.io/docs/reference/sentinel-clients.
|
||||
auto resolve_master_address(std::vector<address> const& addresses) -> net::awaitable<address>
|
||||
auto resolve_master_address(std::vector<address> const& addresses) -> asio::awaitable<address>
|
||||
{
|
||||
request req;
|
||||
req.push("SENTINEL", "get-master-addr-by-name", "mymaster");
|
||||
req.push("QUIT");
|
||||
|
||||
auto conn = std::make_shared<connection>(co_await net::this_coro::executor);
|
||||
auto conn = std::make_shared<connection>(co_await asio::this_coro::executor);
|
||||
|
||||
response<std::optional<std::array<std::string, 2>>, ignore_t> resp;
|
||||
for (auto addr : addresses) {
|
||||
@@ -43,7 +43,7 @@ auto resolve_master_address(std::vector<address> const& addresses) -> net::await
|
||||
// TODO: async_run and async_exec should be lauched in
|
||||
// parallel here so we can wait for async_run completion
|
||||
// before eventually calling it again.
|
||||
conn->async_run(cfg, {}, net::consign(net::detached, conn));
|
||||
conn->async_run(cfg, {}, asio::consign(asio::detached, conn));
|
||||
co_await conn->async_exec(req, resp, redir(ec));
|
||||
conn->cancel();
|
||||
conn->reset_stream();
|
||||
@@ -54,7 +54,7 @@ auto resolve_master_address(std::vector<address> const& addresses) -> net::await
|
||||
co_return address{};
|
||||
}
|
||||
|
||||
auto co_main(config cfg) -> net::awaitable<void>
|
||||
auto co_main(config cfg) -> asio::awaitable<void>
|
||||
{
|
||||
// A list of sentinel addresses from which only one is responsive.
|
||||
// This simulates sentinels that are down.
|
||||
@@ -18,16 +18,18 @@
|
||||
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
|
||||
namespace net = boost::asio;
|
||||
namespace asio = boost::asio;
|
||||
using namespace std::chrono_literals;
|
||||
using boost::redis::request;
|
||||
using boost::redis::generic_response;
|
||||
using boost::redis::consume_one;
|
||||
using boost::redis::logger;
|
||||
using boost::redis::config;
|
||||
using boost::redis::ignore;
|
||||
using boost::redis::error;
|
||||
using boost::system::error_code;
|
||||
using boost::redis::connection;
|
||||
using signal_set = net::deferred_t::as_default_on_t<net::signal_set>;
|
||||
using signal_set = asio::deferred_t::as_default_on_t<asio::signal_set>;
|
||||
|
||||
/* This example will subscribe and read pushes indefinitely.
|
||||
*
|
||||
@@ -47,39 +49,49 @@ using signal_set = net::deferred_t::as_default_on_t<net::signal_set>;
|
||||
|
||||
// Receives server pushes.
|
||||
auto
|
||||
receiver(std::shared_ptr<connection> conn) -> net::awaitable<void>
|
||||
receiver(std::shared_ptr<connection> conn) -> asio::awaitable<void>
|
||||
{
|
||||
request req;
|
||||
req.push("SUBSCRIBE", "channel");
|
||||
|
||||
generic_response resp;
|
||||
conn->set_receive_response(resp);
|
||||
|
||||
// Loop while reconnection is enabled
|
||||
while (conn->will_reconnect()) {
|
||||
|
||||
// Reconnect to channels.
|
||||
co_await conn->async_exec(req, ignore, net::deferred);
|
||||
// Reconnect to the channels.
|
||||
co_await conn->async_exec(req, ignore, asio::deferred);
|
||||
|
||||
// Loop reading Redis pushs messages.
|
||||
for (generic_response resp;;) {
|
||||
error_code ec;
|
||||
co_await conn->async_receive(resp, net::redirect_error(net::use_awaitable, ec));
|
||||
for (error_code ec;;) {
|
||||
// First tries to read any buffered pushes.
|
||||
conn->receive(ec);
|
||||
if (ec == error::sync_receive_push_failed) {
|
||||
ec = {};
|
||||
co_await conn->async_receive(asio::redirect_error(asio::use_awaitable, ec));
|
||||
}
|
||||
|
||||
if (ec)
|
||||
break; // Connection lost, break so we can reconnect to channels.
|
||||
|
||||
std::cout
|
||||
<< resp.value().at(1).value
|
||||
<< " " << resp.value().at(2).value
|
||||
<< " " << resp.value().at(3).value
|
||||
<< std::endl;
|
||||
resp.value().clear();
|
||||
|
||||
consume_one(resp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto co_main(config cfg) -> net::awaitable<void>
|
||||
auto co_main(config cfg) -> asio::awaitable<void>
|
||||
{
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
auto ex = co_await asio::this_coro::executor;
|
||||
auto conn = std::make_shared<connection>(ex);
|
||||
net::co_spawn(ex, receiver(conn), net::detached);
|
||||
conn->async_run(cfg, {}, net::consign(net::detached, conn));
|
||||
asio::co_spawn(ex, receiver(conn), asio::detached);
|
||||
conn->async_run(cfg, {}, asio::consign(asio::detached, conn));
|
||||
|
||||
signal_set sig_set(ex, SIGINT, SIGTERM);
|
||||
co_await sig_set.async_wait();
|
||||
@@ -11,13 +11,13 @@
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <iostream>
|
||||
|
||||
namespace net = boost::asio;
|
||||
namespace asio = boost::asio;
|
||||
using boost::redis::config;
|
||||
using boost::redis::logger;
|
||||
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
|
||||
extern net::awaitable<void> co_main(config);
|
||||
extern asio::awaitable<void> co_main(config);
|
||||
|
||||
auto main(int argc, char * argv[]) -> int
|
||||
{
|
||||
@@ -29,8 +29,8 @@ auto main(int argc, char * argv[]) -> int
|
||||
cfg.addr.port = argv[2];
|
||||
}
|
||||
|
||||
net::io_context ioc;
|
||||
net::co_spawn(ioc, std::move(co_main(cfg)), [](std::exception_ptr p) {
|
||||
asio::io_context ioc;
|
||||
asio::co_spawn(ioc, co_main(cfg), [](std::exception_ptr p) {
|
||||
if (p)
|
||||
std::rethrow_exception(p);
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
@@ -92,7 +92,8 @@ private:
|
||||
|
||||
public:
|
||||
explicit general_aggregate(Result* c = nullptr): result_(c) {}
|
||||
void operator()(resp3::basic_node<std::string_view> const& nd, system::error_code&)
|
||||
template <class String>
|
||||
void operator()(resp3::basic_node<String> const& nd, system::error_code&)
|
||||
{
|
||||
BOOST_ASSERT_MSG(!!result_, "Unexpected null pointer");
|
||||
switch (nd.data_type) {
|
||||
@@ -114,7 +115,8 @@ private:
|
||||
public:
|
||||
explicit general_simple(Node* t = nullptr) : result_(t) {}
|
||||
|
||||
void operator()(resp3::basic_node<std::string_view> const& nd, system::error_code&)
|
||||
template <class String>
|
||||
void operator()(resp3::basic_node<String> const& nd, system::error_code&)
|
||||
{
|
||||
BOOST_ASSERT_MSG(!!result_, "Unexpected null pointer");
|
||||
switch (nd.data_type) {
|
||||
@@ -136,11 +138,8 @@ class simple_impl {
|
||||
public:
|
||||
void on_value_available(Result&) {}
|
||||
|
||||
void
|
||||
operator()(
|
||||
Result& result,
|
||||
resp3::basic_node<std::string_view> const& n,
|
||||
system::error_code& ec)
|
||||
template <class String>
|
||||
void operator()(Result& result, resp3::basic_node<String> const& n, system::error_code& ec)
|
||||
{
|
||||
if (is_aggregate(n.data_type)) {
|
||||
ec = redis::error::expects_resp3_simple_type;
|
||||
@@ -160,11 +159,8 @@ public:
|
||||
void on_value_available(Result& result)
|
||||
{ hint_ = std::end(result); }
|
||||
|
||||
void
|
||||
operator()(
|
||||
Result& result,
|
||||
resp3::basic_node<std::string_view> const& nd,
|
||||
system::error_code& ec)
|
||||
template <class String>
|
||||
void operator()(Result& result, resp3::basic_node<String> const& nd, system::error_code& ec)
|
||||
{
|
||||
if (is_aggregate(nd.data_type)) {
|
||||
if (nd.data_type != resp3::type::set)
|
||||
@@ -195,11 +191,8 @@ public:
|
||||
void on_value_available(Result& result)
|
||||
{ current_ = std::end(result); }
|
||||
|
||||
void
|
||||
operator()(
|
||||
Result& result,
|
||||
resp3::basic_node<std::string_view> const& nd,
|
||||
system::error_code& ec)
|
||||
template <class String>
|
||||
void operator()(Result& result, resp3::basic_node<String> const& nd, system::error_code& ec)
|
||||
{
|
||||
if (is_aggregate(nd.data_type)) {
|
||||
if (element_multiplicity(nd.data_type) != 2)
|
||||
@@ -233,11 +226,8 @@ class vector_impl {
|
||||
public:
|
||||
void on_value_available(Result& ) { }
|
||||
|
||||
void
|
||||
operator()(
|
||||
Result& result,
|
||||
resp3::basic_node<std::string_view> const& nd,
|
||||
system::error_code& ec)
|
||||
template <class String>
|
||||
void operator()(Result& result, resp3::basic_node<String> const& nd, system::error_code& ec)
|
||||
{
|
||||
if (is_aggregate(nd.data_type)) {
|
||||
auto const m = element_multiplicity(nd.data_type);
|
||||
@@ -257,11 +247,8 @@ private:
|
||||
public:
|
||||
void on_value_available(Result& ) { }
|
||||
|
||||
void
|
||||
operator()(
|
||||
Result& result,
|
||||
resp3::basic_node<std::string_view> const& nd,
|
||||
system::error_code& ec)
|
||||
template <class String>
|
||||
void operator()(Result& result, resp3::basic_node<String> const& nd, system::error_code& ec)
|
||||
{
|
||||
if (is_aggregate(nd.data_type)) {
|
||||
if (i_ != -1) {
|
||||
@@ -292,11 +279,8 @@ struct list_impl {
|
||||
|
||||
void on_value_available(Result& ) { }
|
||||
|
||||
void
|
||||
operator()(
|
||||
Result& result,
|
||||
resp3::basic_node<std::string_view> const& nd,
|
||||
system::error_code& ec)
|
||||
template <class String>
|
||||
void operator()(Result& result, resp3::basic_node<String> const& nd, system::error_code& ec)
|
||||
{
|
||||
if (!is_aggregate(nd.data_type)) {
|
||||
BOOST_ASSERT(nd.aggregate_size == 1);
|
||||
@@ -365,7 +349,8 @@ private:
|
||||
response_type* result_;
|
||||
typename impl_map<Result>::type impl_;
|
||||
|
||||
bool set_if_resp3_error(resp3::basic_node<std::string_view> const& nd) noexcept
|
||||
template <class String>
|
||||
bool set_if_resp3_error(resp3::basic_node<String> const& nd) noexcept
|
||||
{
|
||||
switch (nd.data_type) {
|
||||
case resp3::type::null:
|
||||
@@ -387,10 +372,8 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
operator()(
|
||||
resp3::basic_node<std::string_view> const& nd,
|
||||
system::error_code& ec)
|
||||
template <class String>
|
||||
void operator()(resp3::basic_node<String> const& nd, system::error_code& ec)
|
||||
{
|
||||
BOOST_ASSERT_MSG(!!result_, "Unexpected null pointer");
|
||||
|
||||
@@ -414,7 +397,8 @@ private:
|
||||
response_type* result_;
|
||||
typename impl_map<T>::type impl_{};
|
||||
|
||||
bool set_if_resp3_error(resp3::basic_node<std::string_view> const& nd) noexcept
|
||||
template <class String>
|
||||
bool set_if_resp3_error(resp3::basic_node<String> const& nd) noexcept
|
||||
{
|
||||
switch (nd.data_type) {
|
||||
case resp3::type::blob_error:
|
||||
@@ -429,9 +413,10 @@ private:
|
||||
public:
|
||||
explicit wrapper(response_type* o = nullptr) : result_(o) {}
|
||||
|
||||
template <class String>
|
||||
void
|
||||
operator()(
|
||||
resp3::basic_node<std::string_view> const& nd,
|
||||
resp3::basic_node<String> const& nd,
|
||||
system::error_code& ec)
|
||||
{
|
||||
BOOST_ASSERT_MSG(!!result_, "Unexpected null pointer");
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
@@ -23,8 +23,8 @@ namespace boost::redis::adapter::detail
|
||||
|
||||
class ignore_adapter {
|
||||
public:
|
||||
void
|
||||
operator()(std::size_t, resp3::basic_node<std::string_view> const& nd, system::error_code& ec)
|
||||
template <class String>
|
||||
void operator()(std::size_t, resp3::basic_node<String> const& nd, system::error_code& ec)
|
||||
{
|
||||
switch (nd.data_type) {
|
||||
case resp3::type::simple_error: ec = redis::error::resp3_simple_error; break;
|
||||
@@ -59,11 +59,8 @@ public:
|
||||
auto get_supported_response_size() const noexcept
|
||||
{ return size;}
|
||||
|
||||
void
|
||||
operator()(
|
||||
std::size_t i,
|
||||
resp3::basic_node<std::string_view> const& nd,
|
||||
system::error_code& ec)
|
||||
template <class String>
|
||||
void operator()(std::size_t i, resp3::basic_node<String> const& nd, system::error_code& ec)
|
||||
{
|
||||
using std::visit;
|
||||
// I am usure whether this should be an error or an assertion.
|
||||
@@ -88,11 +85,8 @@ public:
|
||||
get_supported_response_size() const noexcept
|
||||
{ return static_cast<std::size_t>(-1);}
|
||||
|
||||
void
|
||||
operator()(
|
||||
std::size_t,
|
||||
resp3::basic_node<std::string_view> const& nd,
|
||||
system::error_code& ec)
|
||||
template <class String>
|
||||
void operator()(std::size_t, resp3::basic_node<String> const& nd, system::error_code& ec)
|
||||
{
|
||||
adapter_(nd, ec);
|
||||
}
|
||||
@@ -142,7 +136,8 @@ class wrapper {
|
||||
public:
|
||||
explicit wrapper(Adapter adapter) : adapter_{adapter} {}
|
||||
|
||||
void operator()(resp3::basic_node<std::string_view> const& nd, system::error_code& ec)
|
||||
template <class String>
|
||||
void operator()(resp3::basic_node<String> const& nd, system::error_code& ec)
|
||||
{ return adapter_(0, nd, ec); }
|
||||
|
||||
[[nodiscard]]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
@@ -116,7 +116,8 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void count(resp3::basic_node<std::string_view> const& nd)
|
||||
template <class String>
|
||||
void count(resp3::basic_node<String> const& nd)
|
||||
{
|
||||
if (nd.depth == 1) {
|
||||
if (is_aggregate(nd.data_type))
|
||||
@@ -131,7 +132,8 @@ public:
|
||||
++i_;
|
||||
}
|
||||
|
||||
void operator()(resp3::basic_node<std::string_view> const& nd, system::error_code& ec)
|
||||
template <class String>
|
||||
void operator()(resp3::basic_node<String> const& nd, system::error_code& ec)
|
||||
{
|
||||
using std::visit;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
@@ -38,7 +38,7 @@ struct config {
|
||||
* [HELLO](https://redis.io/commands/hello/) command. If left
|
||||
* empty `HELLO` will be sent without authentication parameters.
|
||||
*/
|
||||
std::string username;
|
||||
std::string username = "default";
|
||||
|
||||
/** @brief Password passed to the
|
||||
* [HELLO](https://redis.io/commands/hello/) command. If left
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
@@ -86,13 +86,19 @@ public:
|
||||
using other = basic_connection<Executor1>;
|
||||
};
|
||||
|
||||
/// Contructs from an executor.
|
||||
/** @brief Constructor
|
||||
*
|
||||
* @param ex Executor on which connection operation will run.
|
||||
* @param ctx SSL context.
|
||||
* @param max_read_size Maximum read size that is passed to
|
||||
* the internal `asio::dynamic_buffer` constructor.
|
||||
*/
|
||||
explicit
|
||||
basic_connection(
|
||||
executor_type ex,
|
||||
asio::ssl::context::method method = asio::ssl::context::tls_client,
|
||||
asio::ssl::context ctx = asio::ssl::context{asio::ssl::context::tlsv12_client},
|
||||
std::size_t max_read_size = (std::numeric_limits<std::size_t>::max)())
|
||||
: impl_{ex, method, max_read_size}
|
||||
: impl_{ex, std::move(ctx), max_read_size}
|
||||
, timer_{ex}
|
||||
{ }
|
||||
|
||||
@@ -100,9 +106,9 @@ public:
|
||||
explicit
|
||||
basic_connection(
|
||||
asio::io_context& ioc,
|
||||
asio::ssl::context::method method = asio::ssl::context::tls_client,
|
||||
asio::ssl::context ctx = asio::ssl::context{asio::ssl::context::tlsv12_client},
|
||||
std::size_t max_read_size = (std::numeric_limits<std::size_t>::max)())
|
||||
: basic_connection(ioc.get_executor(), method, max_read_size)
|
||||
: basic_connection(ioc.get_executor(), std::move(ctx), max_read_size)
|
||||
{ }
|
||||
|
||||
/** @brief Starts underlying connection operations.
|
||||
@@ -171,7 +177,6 @@ public:
|
||||
* To cancel an ongoing receive operation apps should call
|
||||
* `connection::cancel(operation::receive)`.
|
||||
*
|
||||
* @param response Response object.
|
||||
* @param token Completion token.
|
||||
*
|
||||
* For an example see cpp20_subscriber.cpp. The completion token must
|
||||
@@ -184,10 +189,32 @@ public:
|
||||
* Where the second parameter is the size of the push received in
|
||||
* bytes.
|
||||
*/
|
||||
template <class CompletionToken = asio::default_completion_token_t<executor_type>>
|
||||
auto async_receive(CompletionToken token = CompletionToken{})
|
||||
{ return impl_.async_receive(std::move(token)); }
|
||||
|
||||
|
||||
/** @brief Receives server pushes synchronously without blocking.
|
||||
*
|
||||
* Receives a server push synchronously by calling `try_receive` on
|
||||
* the underlying channel. If the operation fails because
|
||||
* `try_receive` returns `false`, `ec` will be set to
|
||||
* `boost::redis::error::sync_receive_push_failed`.
|
||||
*
|
||||
* @param ec Contains the error if any occurred.
|
||||
*
|
||||
* @returns The number of bytes read from the socket.
|
||||
*/
|
||||
std::size_t receive(system::error_code& ec)
|
||||
{
|
||||
return impl_.receive(ec);
|
||||
}
|
||||
|
||||
template <
|
||||
class Response = ignore_t,
|
||||
class CompletionToken = asio::default_completion_token_t<executor_type>
|
||||
>
|
||||
[[deprecated("Set the response with set_receive_response and use the other overload.")]]
|
||||
auto
|
||||
async_receive(
|
||||
Response& response,
|
||||
@@ -242,7 +269,6 @@ public:
|
||||
* @li operation::all: Cancels all operations listed above.
|
||||
*
|
||||
* @param op: The operation to be cancelled.
|
||||
* @returns The number of operations that have been canceled.
|
||||
*/
|
||||
void cancel(operation op = operation::all)
|
||||
{
|
||||
@@ -266,10 +292,6 @@ public:
|
||||
auto const& get_ssl_context() const noexcept
|
||||
{ return impl_.get_ssl_context();}
|
||||
|
||||
/// Returns the ssl context.
|
||||
auto& get_ssl_context() noexcept
|
||||
{ return impl_.get_ssl_context();}
|
||||
|
||||
/// Resets the underlying stream.
|
||||
void reset_stream()
|
||||
{ impl_.reset_stream(); }
|
||||
@@ -282,6 +304,15 @@ public:
|
||||
auto const& next_layer() const noexcept
|
||||
{ return impl_.next_layer(); }
|
||||
|
||||
/// Sets the response object of `async_receive` operations.
|
||||
template <class Response>
|
||||
void set_receive_response(Response& response)
|
||||
{ impl_.set_receive_response(response); }
|
||||
|
||||
/// Returns connection usage information.
|
||||
usage get_usage() const noexcept
|
||||
{ return impl_.get_usage(); }
|
||||
|
||||
private:
|
||||
using timer_type =
|
||||
asio::basic_waitable_timer<
|
||||
@@ -314,14 +345,14 @@ public:
|
||||
explicit
|
||||
connection(
|
||||
executor_type ex,
|
||||
asio::ssl::context::method method = asio::ssl::context::tls_client,
|
||||
asio::ssl::context ctx = asio::ssl::context{asio::ssl::context::tlsv12_client},
|
||||
std::size_t max_read_size = (std::numeric_limits<std::size_t>::max)());
|
||||
|
||||
/// Contructs from a context.
|
||||
explicit
|
||||
connection(
|
||||
asio::io_context& ioc,
|
||||
asio::ssl::context::method method = asio::ssl::context::tls_client,
|
||||
asio::ssl::context ctx = asio::ssl::context{asio::ssl::context::tlsv12_client},
|
||||
std::size_t max_read_size = (std::numeric_limits<std::size_t>::max)());
|
||||
|
||||
/// Returns the underlying executor.
|
||||
@@ -342,11 +373,23 @@ public:
|
||||
|
||||
/// Calls `boost::redis::basic_connection::async_receive`.
|
||||
template <class Response, class CompletionToken>
|
||||
[[deprecated("Set the response with set_receive_response and use the other overload.")]]
|
||||
auto async_receive(Response& response, CompletionToken token)
|
||||
{
|
||||
return impl_.async_receive(response, std::move(token));
|
||||
}
|
||||
|
||||
/// Calls `boost::redis::basic_connection::async_receive`.
|
||||
template <class CompletionToken>
|
||||
auto async_receive(CompletionToken token)
|
||||
{ return impl_.async_receive(std::move(token)); }
|
||||
|
||||
/// Calls `boost::redis::basic_connection::receive`.
|
||||
std::size_t receive(system::error_code& ec)
|
||||
{
|
||||
return impl_.receive(ec);
|
||||
}
|
||||
|
||||
/// Calls `boost::redis::basic_connection::async_exec`.
|
||||
template <class Response, class CompletionToken>
|
||||
auto async_exec(request const& req, Response& resp, CompletionToken token)
|
||||
@@ -373,6 +416,19 @@ public:
|
||||
void reset_stream()
|
||||
{ impl_.reset_stream();}
|
||||
|
||||
/// Sets the response object of `async_receive` operations.
|
||||
template <class Response>
|
||||
void set_receive_response(Response& response)
|
||||
{ impl_.set_receive_response(response); }
|
||||
|
||||
/// Returns connection usage information.
|
||||
usage get_usage() const noexcept
|
||||
{ return impl_.get_usage(); }
|
||||
|
||||
/// Returns the ssl context.
|
||||
auto const& get_ssl_context() const noexcept
|
||||
{ return impl_.get_ssl_context();}
|
||||
|
||||
private:
|
||||
void
|
||||
async_run_impl(
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
@@ -24,11 +24,12 @@
|
||||
|
||||
namespace boost::redis::detail {
|
||||
|
||||
template <class HealthChecker, class Connection>
|
||||
template <class HealthChecker, class Connection, class Logger>
|
||||
class ping_op {
|
||||
public:
|
||||
HealthChecker* checker_ = nullptr;
|
||||
Connection* conn_ = nullptr;
|
||||
Logger logger_;
|
||||
asio::coroutine coro_{};
|
||||
|
||||
template <class Self>
|
||||
@@ -37,28 +38,39 @@ public:
|
||||
BOOST_ASIO_CORO_REENTER (coro_) for (;;)
|
||||
{
|
||||
if (checker_->checker_has_exited_) {
|
||||
logger_.trace("ping_op: checker has exited. Exiting ...");
|
||||
self.complete({});
|
||||
return;
|
||||
}
|
||||
|
||||
BOOST_ASIO_CORO_YIELD
|
||||
conn_->async_exec(checker_->req_, checker_->resp_, std::move(self));
|
||||
BOOST_REDIS_CHECK_OP0(checker_->wait_timer_.cancel();)
|
||||
if (ec || is_cancelled(self)) {
|
||||
logger_.trace("ping_op: error/cancelled (1).");
|
||||
checker_->wait_timer_.cancel();
|
||||
self.complete(!!ec ? ec : asio::error::operation_aborted);
|
||||
return;
|
||||
}
|
||||
|
||||
// Wait before pinging again.
|
||||
checker_->ping_timer_.expires_after(checker_->ping_interval_);
|
||||
BOOST_ASIO_CORO_YIELD
|
||||
checker_->ping_timer_.async_wait(std::move(self));
|
||||
BOOST_REDIS_CHECK_OP0(;)
|
||||
if (ec || is_cancelled(self)) {
|
||||
logger_.trace("ping_op: error/cancelled (2).");
|
||||
self.complete(!!ec ? ec : asio::error::operation_aborted);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class HealthChecker, class Connection>
|
||||
template <class HealthChecker, class Connection, class Logger>
|
||||
class check_timeout_op {
|
||||
public:
|
||||
HealthChecker* checker_ = nullptr;
|
||||
Connection* conn_ = nullptr;
|
||||
Logger logger_;
|
||||
asio::coroutine coro_{};
|
||||
|
||||
template <class Self>
|
||||
@@ -69,14 +81,20 @@ public:
|
||||
checker_->wait_timer_.expires_after(2 * checker_->ping_interval_);
|
||||
BOOST_ASIO_CORO_YIELD
|
||||
checker_->wait_timer_.async_wait(std::move(self));
|
||||
BOOST_REDIS_CHECK_OP0(;)
|
||||
if (ec || is_cancelled(self)) {
|
||||
logger_.trace("check-timeout-op: error/canceled. Exiting ...");
|
||||
self.complete(!!ec ? ec : asio::error::operation_aborted);
|
||||
return;
|
||||
}
|
||||
|
||||
if (checker_->resp_.has_error()) {
|
||||
logger_.trace("check-timeout-op: Response error. Exiting ...");
|
||||
self.complete({});
|
||||
return;
|
||||
}
|
||||
|
||||
if (checker_->resp_.value().empty()) {
|
||||
logger_.trace("check-timeout-op: Response has no value. Exiting ...");
|
||||
checker_->ping_timer_.cancel();
|
||||
conn_->cancel(operation::run);
|
||||
checker_->checker_has_exited_ = true;
|
||||
@@ -91,11 +109,12 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
template <class HealthChecker, class Connection>
|
||||
template <class HealthChecker, class Connection, class Logger>
|
||||
class check_health_op {
|
||||
public:
|
||||
HealthChecker* checker_ = nullptr;
|
||||
Connection* conn_ = nullptr;
|
||||
Logger logger_;
|
||||
asio::coroutine coro_{};
|
||||
|
||||
template <class Self>
|
||||
@@ -109,6 +128,7 @@ public:
|
||||
BOOST_ASIO_CORO_REENTER (coro_)
|
||||
{
|
||||
if (checker_->ping_interval_ == std::chrono::seconds::zero()) {
|
||||
logger_.trace("check-health-op: timeout disabled.");
|
||||
BOOST_ASIO_CORO_YIELD
|
||||
asio::post(std::move(self));
|
||||
self.complete({});
|
||||
@@ -117,13 +137,16 @@ public:
|
||||
|
||||
BOOST_ASIO_CORO_YIELD
|
||||
asio::experimental::make_parallel_group(
|
||||
[this](auto token) { return checker_->async_ping(*conn_, token); },
|
||||
[this](auto token) { return checker_->async_check_timeout(*conn_, token);}
|
||||
[this](auto token) { return checker_->async_ping(*conn_, logger_, token); },
|
||||
[this](auto token) { return checker_->async_check_timeout(*conn_, logger_, token);}
|
||||
).async_wait(
|
||||
asio::experimental::wait_for_one(),
|
||||
std::move(self));
|
||||
|
||||
logger_.on_check_health(ec1, ec2);
|
||||
|
||||
if (is_cancelled(self)) {
|
||||
logger_.trace("check-health-op: canceled. Exiting ...");
|
||||
self.complete(asio::error::operation_aborted);
|
||||
return;
|
||||
}
|
||||
@@ -163,15 +186,20 @@ public:
|
||||
|
||||
template <
|
||||
class Connection,
|
||||
class Logger,
|
||||
class CompletionToken = asio::default_completion_token_t<Executor>
|
||||
>
|
||||
auto async_check_health(Connection& conn, CompletionToken token = CompletionToken{})
|
||||
auto
|
||||
async_check_health(
|
||||
Connection& conn,
|
||||
Logger l,
|
||||
CompletionToken token = CompletionToken{})
|
||||
{
|
||||
checker_has_exited_ = false;
|
||||
return asio::async_compose
|
||||
< CompletionToken
|
||||
, void(system::error_code)
|
||||
>(check_health_op<health_checker, Connection>{this, &conn}, token, conn);
|
||||
>(check_health_op<health_checker, Connection, Logger>{this, &conn, l}, token, conn);
|
||||
}
|
||||
|
||||
std::size_t cancel(operation op)
|
||||
@@ -189,27 +217,27 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
template <class Connection, class CompletionToken>
|
||||
auto async_ping(Connection& conn, CompletionToken token)
|
||||
template <class Connection, class Logger, class CompletionToken>
|
||||
auto async_ping(Connection& conn, Logger l, CompletionToken token)
|
||||
{
|
||||
return asio::async_compose
|
||||
< CompletionToken
|
||||
, void(system::error_code)
|
||||
>(ping_op<health_checker, Connection>{this, &conn}, token, conn, ping_timer_);
|
||||
>(ping_op<health_checker, Connection, Logger>{this, &conn, l}, token, conn, ping_timer_);
|
||||
}
|
||||
|
||||
template <class Connection, class CompletionToken>
|
||||
auto async_check_timeout(Connection& conn, CompletionToken token)
|
||||
template <class Connection, class Logger, class CompletionToken>
|
||||
auto async_check_timeout(Connection& conn, Logger l, CompletionToken token)
|
||||
{
|
||||
return asio::async_compose
|
||||
< CompletionToken
|
||||
, void(system::error_code)
|
||||
>(check_timeout_op<health_checker, Connection>{this, &conn}, token, conn, wait_timer_);
|
||||
>(check_timeout_op<health_checker, Connection, Logger>{this, &conn, l}, token, conn, wait_timer_);
|
||||
}
|
||||
|
||||
template <class, class> friend class ping_op;
|
||||
template <class, class> friend class check_timeout_op;
|
||||
template <class, class> friend class check_health_op;
|
||||
template <class, class, class> friend class ping_op;
|
||||
template <class, class, class> friend class check_timeout_op;
|
||||
template <class, class, class> friend class check_health_op;
|
||||
|
||||
timer_type ping_timer_;
|
||||
timer_type wait_timer_;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
|
||||
@@ -1,291 +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 BOOST_REDIS_READ_HPP
|
||||
#define BOOST_REDIS_READ_HPP
|
||||
|
||||
#include <boost/redis/resp3/type.hpp>
|
||||
#include <boost/redis/resp3/parser.hpp>
|
||||
#include <boost/redis/adapter/ignore.hpp>
|
||||
#include <boost/redis/detail/helper.hpp>
|
||||
#include <boost/asio/read.hpp>
|
||||
#include <boost/asio/compose.hpp>
|
||||
#include <boost/asio/coroutine.hpp>
|
||||
#include <boost/asio/post.hpp>
|
||||
|
||||
#include <string_view>
|
||||
#include <limits>
|
||||
|
||||
namespace boost::redis::detail {
|
||||
|
||||
template <class DynamicBuffer>
|
||||
std::string_view buffer_view(DynamicBuffer buf) noexcept
|
||||
{
|
||||
char const* start = static_cast<char const*>(buf.data(0, buf.size()).data());
|
||||
return std::string_view{start, std::size(buf)};
|
||||
}
|
||||
|
||||
template <class AsyncReadStream, class DynamicBuffer>
|
||||
class append_some_op {
|
||||
private:
|
||||
AsyncReadStream& stream_;
|
||||
DynamicBuffer buf_;
|
||||
std::size_t size_ = 0;
|
||||
std::size_t tmp_ = 0;
|
||||
asio::coroutine coro_{};
|
||||
|
||||
public:
|
||||
append_some_op(AsyncReadStream& stream, DynamicBuffer buf, std::size_t size)
|
||||
: stream_ {stream}
|
||||
, buf_ {std::move(buf)}
|
||||
, size_{size}
|
||||
{ }
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, system::error_code ec = {}
|
||||
, std::size_t n = 0)
|
||||
{
|
||||
BOOST_ASIO_CORO_REENTER (coro_)
|
||||
{
|
||||
tmp_ = buf_.size();
|
||||
buf_.grow(size_);
|
||||
|
||||
BOOST_ASIO_CORO_YIELD
|
||||
stream_.async_read_some(buf_.data(tmp_, size_), std::move(self));
|
||||
if (ec) {
|
||||
self.complete(ec, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
buf_.shrink(buf_.size() - tmp_ - n);
|
||||
self.complete({}, n);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class AsyncReadStream, class DynamicBuffer, class CompletionToken>
|
||||
auto
|
||||
async_append_some(
|
||||
AsyncReadStream& stream,
|
||||
DynamicBuffer buffer,
|
||||
std::size_t size,
|
||||
CompletionToken&& token)
|
||||
{
|
||||
return asio::async_compose
|
||||
< CompletionToken
|
||||
, void(system::error_code, std::size_t)
|
||||
>(append_some_op<AsyncReadStream, DynamicBuffer> {stream, buffer, size}, token, stream);
|
||||
}
|
||||
|
||||
template <
|
||||
class AsyncReadStream,
|
||||
class DynamicBuffer,
|
||||
class ResponseAdapter>
|
||||
class parse_op {
|
||||
private:
|
||||
AsyncReadStream& stream_;
|
||||
DynamicBuffer buf_;
|
||||
resp3::parser parser_;
|
||||
ResponseAdapter adapter_;
|
||||
bool needs_rescheduling_ = true;
|
||||
system::error_code ec_;
|
||||
asio::coroutine coro_{};
|
||||
|
||||
static std::size_t const growth = 1024;
|
||||
|
||||
public:
|
||||
parse_op(AsyncReadStream& stream, DynamicBuffer buf, ResponseAdapter adapter)
|
||||
: stream_ {stream}
|
||||
, buf_ {std::move(buf)}
|
||||
, adapter_ {std::move(adapter)}
|
||||
{ }
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, system::error_code ec = {}
|
||||
, std::size_t = 0)
|
||||
{
|
||||
BOOST_ASIO_CORO_REENTER (coro_)
|
||||
{
|
||||
while (!resp3::parse(parser_, buffer_view(buf_), adapter_, ec)) {
|
||||
needs_rescheduling_ = false;
|
||||
BOOST_ASIO_CORO_YIELD
|
||||
async_append_some(
|
||||
stream_, buf_, parser_.get_suggested_buffer_growth(growth),
|
||||
std::move(self));
|
||||
if (ec) {
|
||||
self.complete(ec, 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ec_ = ec;
|
||||
if (needs_rescheduling_) {
|
||||
BOOST_ASIO_CORO_YIELD
|
||||
asio::post(std::move(self));
|
||||
}
|
||||
|
||||
self.complete(ec_, parser_.get_consumed());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** \brief Reads a complete response to a command sychronously.
|
||||
*
|
||||
* This function reads a complete response to a command or a
|
||||
* server push synchronously. For example
|
||||
*
|
||||
* @code
|
||||
* int resp;
|
||||
* std::string buffer;
|
||||
* resp3::read(socket, dynamic_buffer(buffer), adapt(resp));
|
||||
* @endcode
|
||||
*
|
||||
* For a complete example see examples/intro_sync.cpp. This function
|
||||
* is implemented in terms of one or more calls to @c
|
||||
* asio::read_until and @c asio::read functions, and is known as a @a
|
||||
* composed @a operation. Furthermore, the implementation may read
|
||||
* additional bytes from the stream that lie past the end of the
|
||||
* message being read. These additional bytes are stored in the
|
||||
* dynamic buffer, which must be preserved for subsequent reads.
|
||||
*
|
||||
* \param stream The stream from which to read e.g. a tcp socket.
|
||||
* \param buf Dynamic buffer (version 2).
|
||||
* \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.
|
||||
*
|
||||
* \remark This function calls buf.consume() in each chunk of data
|
||||
* after it has been passed to the adapter. Users must not consume
|
||||
* the bytes after it returns.
|
||||
*/
|
||||
template <
|
||||
class SyncReadStream,
|
||||
class DynamicBuffer,
|
||||
class ResponseAdapter
|
||||
>
|
||||
auto
|
||||
read(
|
||||
SyncReadStream& stream,
|
||||
DynamicBuffer buf,
|
||||
ResponseAdapter adapter,
|
||||
system::error_code& ec) -> std::size_t
|
||||
{
|
||||
static std::size_t const growth = 1024;
|
||||
|
||||
resp3::parser parser;
|
||||
while (!parser.done()) {
|
||||
auto const res = parser.consume(detail::buffer_view(buf), ec);
|
||||
if (ec)
|
||||
return 0UL;
|
||||
|
||||
if (!res.has_value()) {
|
||||
auto const size_before = buf.size();
|
||||
buf.grow(parser.get_suggested_buffer_growth(growth));
|
||||
auto const n =
|
||||
stream.read_some(
|
||||
buf.data(size_before, parser.get_suggested_buffer_growth(growth)),
|
||||
ec);
|
||||
if (ec)
|
||||
return 0UL;
|
||||
|
||||
buf.shrink(buf.size() - size_before - n);
|
||||
continue;
|
||||
}
|
||||
|
||||
adapter(res.value(), ec);
|
||||
if (ec)
|
||||
return 0UL;
|
||||
}
|
||||
|
||||
return parser.get_consumed();
|
||||
}
|
||||
|
||||
/** \brief Reads a complete response to a command sychronously.
|
||||
*
|
||||
* Same as the error_code overload but throws on error.
|
||||
*/
|
||||
template<
|
||||
class SyncReadStream,
|
||||
class DynamicBuffer,
|
||||
class ResponseAdapter = adapter::ignore>
|
||||
auto
|
||||
read(
|
||||
SyncReadStream& stream,
|
||||
DynamicBuffer buf,
|
||||
ResponseAdapter adapter = ResponseAdapter{})
|
||||
{
|
||||
system::error_code ec;
|
||||
auto const n = redis::detail::read(stream, buf, adapter, ec);
|
||||
|
||||
if (ec)
|
||||
BOOST_THROW_EXCEPTION(system::system_error{ec});
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
/** \brief Reads a complete response to a Redis command asynchronously.
|
||||
*
|
||||
* This function reads a complete response to a command or a
|
||||
* server push asynchronously. For example
|
||||
*
|
||||
* @code
|
||||
* std::string buffer;
|
||||
* std::set<std::string> resp;
|
||||
* co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(resp));
|
||||
* @endcode
|
||||
*
|
||||
* For a complete example see examples/transaction.cpp. This function
|
||||
* is implemented in terms of one or more calls to @c
|
||||
* asio::async_read_until and @c asio::async_read functions, and is
|
||||
* known as a @a composed @a operation. Furthermore, the
|
||||
* implementation may read additional bytes from the stream that lie
|
||||
* past the end of the message being read. These additional bytes are
|
||||
* stored in the dynamic buffer, which must be preserved for
|
||||
* subsequent reads.
|
||||
*
|
||||
* \param stream The stream from which to read e.g. a tcp socket.
|
||||
* \param buffer Dynamic buffer (version 2).
|
||||
* \param adapter The response adapter.
|
||||
* \param token The completion token.
|
||||
*
|
||||
* The completion handler will receive as a parameter the total
|
||||
* number of bytes transferred from the stream and must have the
|
||||
* following signature
|
||||
*
|
||||
* @code
|
||||
* void(system::error_code, std::size_t);
|
||||
* @endcode
|
||||
*
|
||||
* \remark This function calls buf.consume() in each chunk of data
|
||||
* after it has been passed to the adapter. Users must not consume
|
||||
* the bytes after it returns.
|
||||
*/
|
||||
template <
|
||||
class AsyncReadStream,
|
||||
class DynamicBuffer,
|
||||
class ResponseAdapter = adapter::ignore,
|
||||
class CompletionToken = asio::default_completion_token_t<typename AsyncReadStream::executor_type>
|
||||
>
|
||||
auto async_read(
|
||||
AsyncReadStream& stream,
|
||||
DynamicBuffer buffer,
|
||||
ResponseAdapter adapter = ResponseAdapter{},
|
||||
CompletionToken&& token =
|
||||
asio::default_completion_token_t<typename AsyncReadStream::executor_type>{})
|
||||
{
|
||||
return asio::async_compose
|
||||
< CompletionToken
|
||||
, void(system::error_code, std::size_t)
|
||||
>(parse_op<AsyncReadStream, DynamicBuffer, ResponseAdapter> {stream, buffer, adapter},
|
||||
token,
|
||||
stream);
|
||||
}
|
||||
|
||||
} // boost::redis::detail
|
||||
|
||||
#endif // BOOST_REDIS_READ_HPP
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
@@ -30,6 +30,8 @@
|
||||
namespace boost::redis::detail
|
||||
{
|
||||
|
||||
void push_hello(config const& cfg, request& req);
|
||||
|
||||
template <class Runner, class Connection, class Logger>
|
||||
struct hello_op {
|
||||
Runner* runner_ = nullptr;
|
||||
@@ -42,16 +44,20 @@ struct hello_op {
|
||||
{
|
||||
BOOST_ASIO_CORO_REENTER (coro_)
|
||||
{
|
||||
runner_->hello_req_.clear();
|
||||
if (runner_->hello_resp_.has_value())
|
||||
runner_->hello_resp_.value().clear();
|
||||
runner_->add_hello();
|
||||
|
||||
BOOST_ASIO_CORO_YIELD
|
||||
conn_->async_exec(runner_->hello_req_, runner_->hello_resp_, std::move(self));
|
||||
logger_.on_hello(ec, runner_->hello_resp_);
|
||||
BOOST_REDIS_CHECK_OP0(conn_->cancel(operation::run);)
|
||||
self.complete(ec);
|
||||
|
||||
if (ec || runner_->has_error_in_response() || is_cancelled(self)) {
|
||||
logger_.trace("hello-op: error/canceled. Exiting ...");
|
||||
conn_->cancel(operation::run);
|
||||
self.complete(!!ec ? ec : asio::error::operation_aborted);
|
||||
return;
|
||||
}
|
||||
|
||||
self.complete({});
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -84,12 +90,14 @@ public:
|
||||
BOOST_ASIO_CORO_YIELD
|
||||
asio::experimental::make_parallel_group(
|
||||
[this](auto token) { return runner_->async_run_all(*conn_, logger_, token); },
|
||||
[this](auto token) { return runner_->health_checker_.async_check_health(*conn_, token); },
|
||||
[this](auto token) { return runner_->health_checker_.async_check_health(*conn_, logger_, token); },
|
||||
[this](auto token) { return runner_->async_hello(*conn_, logger_, token); }
|
||||
).async_wait(
|
||||
asio::experimental::wait_for_all(),
|
||||
std::move(self));
|
||||
|
||||
logger_.on_runner(ec0, ec1, ec2);
|
||||
|
||||
if (is_cancelled(self)) {
|
||||
self.complete(asio::error::operation_aborted);
|
||||
return;
|
||||
@@ -223,17 +231,27 @@ private:
|
||||
|
||||
void add_hello()
|
||||
{
|
||||
if (!cfg_.username.empty() && !cfg_.password.empty() && !cfg_.clientname.empty())
|
||||
hello_req_.push("HELLO", "3", "AUTH", cfg_.username, cfg_.password, "SETNAME", cfg_.clientname);
|
||||
else if (cfg_.username.empty() && cfg_.password.empty() && cfg_.clientname.empty())
|
||||
hello_req_.push("HELLO", "3");
|
||||
else if (cfg_.clientname.empty())
|
||||
hello_req_.push("HELLO", "3", "AUTH", cfg_.username, cfg_.password);
|
||||
else
|
||||
hello_req_.push("HELLO", "3", "SETNAME", cfg_.clientname);
|
||||
hello_req_.clear();
|
||||
if (hello_resp_.has_value())
|
||||
hello_resp_.value().clear();
|
||||
push_hello(cfg_, hello_req_);
|
||||
}
|
||||
|
||||
if (cfg_.database_index)
|
||||
hello_req_.push("SELECT", cfg_.database_index.value());
|
||||
bool has_error_in_response() const noexcept
|
||||
{
|
||||
if (!hello_resp_.has_value())
|
||||
return true;
|
||||
|
||||
auto f = [](auto const& e)
|
||||
{
|
||||
switch (e.data_type) {
|
||||
case resp3::type::simple_error:
|
||||
case resp3::type::blob_error: return true;
|
||||
default: return false;
|
||||
}
|
||||
};
|
||||
|
||||
return std::any_of(std::cbegin(hello_resp_.value()), std::cend(hello_resp_.value()), f);
|
||||
}
|
||||
|
||||
resolver_type resv_;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
@@ -75,6 +75,12 @@ enum class error
|
||||
|
||||
/// SSL handshake timeout
|
||||
ssl_handshake_timeout,
|
||||
|
||||
/// Can't receive push synchronously without blocking
|
||||
sync_receive_push_failed,
|
||||
|
||||
/// Incompatible node depth.
|
||||
incompatible_node_depth,
|
||||
};
|
||||
|
||||
/** \internal
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
@@ -10,16 +10,16 @@ namespace boost::redis {
|
||||
|
||||
connection::connection(
|
||||
executor_type ex,
|
||||
asio::ssl::context::method method,
|
||||
asio::ssl::context ctx,
|
||||
std::size_t max_read_size)
|
||||
: impl_{ex, method, max_read_size}
|
||||
: impl_{ex, std::move(ctx), max_read_size}
|
||||
{ }
|
||||
|
||||
connection::connection(
|
||||
asio::io_context& ioc,
|
||||
asio::ssl::context::method method,
|
||||
asio::ssl::context ctx,
|
||||
std::size_t max_read_size)
|
||||
: impl_{ioc.get_executor(), method, max_read_size}
|
||||
: impl_{ioc.get_executor(), std::move(ctx), max_read_size}
|
||||
{ }
|
||||
|
||||
void
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
@@ -41,6 +41,9 @@ struct error_category_impl : system::error_category {
|
||||
case error::resolve_timeout: return "Resolve timeout.";
|
||||
case error::connect_timeout: return "Connect timeout.";
|
||||
case error::pong_timeout: return "Pong timeout.";
|
||||
case error::ssl_handshake_timeout: return "SSL handshake timeout.";
|
||||
case error::sync_receive_push_failed: return "Can't receive server push synchronously without blocking.";
|
||||
case error::incompatible_node_depth: return "Incompatible node depth.";
|
||||
default: BOOST_ASSERT(false); return "Boost.Redis error.";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
@@ -25,7 +25,7 @@ void logger::on_resolve(system::error_code const& ec, asio::ip::tcp::resolver::r
|
||||
|
||||
write_prefix();
|
||||
|
||||
std::clog << "Resolve results: ";
|
||||
std::clog << "run-all-op: resolve addresses ";
|
||||
|
||||
if (ec) {
|
||||
std::clog << ec.message() << std::endl;
|
||||
@@ -51,7 +51,7 @@ void logger::on_connect(system::error_code const& ec, asio::ip::tcp::endpoint co
|
||||
|
||||
write_prefix();
|
||||
|
||||
std::clog << "Connected to endpoint: ";
|
||||
std::clog << "run-all-op: connected to endpoint ";
|
||||
|
||||
if (ec)
|
||||
std::clog << ec.message() << std::endl;
|
||||
@@ -68,7 +68,7 @@ void logger::on_ssl_handshake(system::error_code const& ec)
|
||||
|
||||
write_prefix();
|
||||
|
||||
std::clog << "SSL handshake: " << ec.message() << std::endl;
|
||||
std::clog << "Runner: SSL handshake " << ec.message() << std::endl;
|
||||
}
|
||||
|
||||
void logger::on_connection_lost(system::error_code const& ec)
|
||||
@@ -97,9 +97,38 @@ logger::on_write(
|
||||
write_prefix();
|
||||
|
||||
if (ec)
|
||||
std::clog << "Write: " << ec.message();
|
||||
std::clog << "writer-op: " << ec.message();
|
||||
else
|
||||
std::clog << "Bytes written: " << std::size(payload);
|
||||
std::clog << "writer-op: " << std::size(payload) << " bytes written.";
|
||||
|
||||
std::clog << std::endl;
|
||||
}
|
||||
|
||||
void logger::on_read(system::error_code const& ec, std::size_t n)
|
||||
{
|
||||
if (level_ < level::info)
|
||||
return;
|
||||
|
||||
write_prefix();
|
||||
|
||||
if (ec)
|
||||
std::clog << "reader-op: " << ec.message();
|
||||
else
|
||||
std::clog << "reader-op: " << n << " bytes read.";
|
||||
|
||||
std::clog << std::endl;
|
||||
}
|
||||
|
||||
void logger::on_run(system::error_code const& reader_ec, system::error_code const& writer_ec)
|
||||
{
|
||||
if (level_ < level::info)
|
||||
return;
|
||||
|
||||
write_prefix();
|
||||
|
||||
std::clog << "run-op: "
|
||||
<< reader_ec.message() << " (reader), "
|
||||
<< writer_ec.message() << " (writer)";
|
||||
|
||||
std::clog << std::endl;
|
||||
}
|
||||
@@ -115,14 +144,60 @@ logger::on_hello(
|
||||
write_prefix();
|
||||
|
||||
if (ec) {
|
||||
std::clog << "Hello: " << ec.message();
|
||||
std::clog << "hello-op: " << ec.message();
|
||||
if (resp.has_error())
|
||||
std::clog << " (" << resp.error().diagnostic << ")";
|
||||
} else {
|
||||
std::clog << "Hello: Success";
|
||||
std::clog << "hello-op: Success";
|
||||
}
|
||||
|
||||
std::clog << std::endl;
|
||||
}
|
||||
|
||||
void
|
||||
logger::on_runner(
|
||||
system::error_code const& run_all_ec,
|
||||
system::error_code const& health_check_ec,
|
||||
system::error_code const& hello_ec)
|
||||
{
|
||||
if (level_ < level::info)
|
||||
return;
|
||||
|
||||
write_prefix();
|
||||
|
||||
std::clog << "runner-op: "
|
||||
<< run_all_ec.message() << " (async_run_all), "
|
||||
<< health_check_ec.message() << " (async_health_check) "
|
||||
<< hello_ec.message() << " (async_hello).";
|
||||
|
||||
std::clog << std::endl;
|
||||
}
|
||||
|
||||
void
|
||||
logger::on_check_health(
|
||||
system::error_code const& ping_ec,
|
||||
system::error_code const& timeout_ec)
|
||||
{
|
||||
if (level_ < level::info)
|
||||
return;
|
||||
|
||||
write_prefix();
|
||||
|
||||
std::clog << "check-health-op: "
|
||||
<< ping_ec.message() << " (async_ping), "
|
||||
<< timeout_ec.message() << " (async_check_timeout).";
|
||||
|
||||
std::clog << std::endl;
|
||||
}
|
||||
|
||||
void logger::trace(std::string_view reason)
|
||||
{
|
||||
if (level_ < level::debug)
|
||||
return;
|
||||
|
||||
write_prefix();
|
||||
|
||||
std::clog << reason << std::endl;
|
||||
}
|
||||
|
||||
} // boost::redis
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
|
||||
48
include/boost/redis/impl/response.ipp
Normal file
48
include/boost/redis/impl/response.ipp
Normal file
@@ -0,0 +1,48 @@
|
||||
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <boost/redis/response.hpp>
|
||||
#include <boost/redis/error.hpp>
|
||||
#include <boost/assert.hpp>
|
||||
|
||||
namespace boost::redis
|
||||
{
|
||||
|
||||
void consume_one(generic_response& r, system::error_code& ec)
|
||||
{
|
||||
if (r.has_error())
|
||||
return; // Nothing to consume.
|
||||
|
||||
if (std::empty(r.value()))
|
||||
return; // Nothing to consume.
|
||||
|
||||
auto const depth = r.value().front().depth;
|
||||
|
||||
// To simplify we will refuse to consume any data-type that is not
|
||||
// a root node. I think there is no use for that and it is complex
|
||||
// since it requires updating parent nodes.
|
||||
if (depth != 0) {
|
||||
ec = error::incompatible_node_depth;
|
||||
return;
|
||||
}
|
||||
|
||||
auto f = [depth](auto const& e)
|
||||
{ return e.depth == depth; };
|
||||
|
||||
auto match = std::find_if(std::next(std::cbegin(r.value())), std::cend(r.value()), f);
|
||||
|
||||
r.value().erase(std::cbegin(r.value()), match);
|
||||
}
|
||||
|
||||
void consume_one(generic_response& r)
|
||||
{
|
||||
system::error_code ec;
|
||||
consume_one(r, ec);
|
||||
if (ec)
|
||||
throw system::system_error(ec);
|
||||
}
|
||||
|
||||
} // boost::redis::resp3
|
||||
27
include/boost/redis/impl/runner.ipp
Normal file
27
include/boost/redis/impl/runner.ipp
Normal file
@@ -0,0 +1,27 @@
|
||||
/* Copyright (c) 2018-2024 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <boost/redis/detail/runner.hpp>
|
||||
|
||||
namespace boost::redis::detail
|
||||
{
|
||||
|
||||
void push_hello(config const& cfg, request& req)
|
||||
{
|
||||
if (!cfg.username.empty() && !cfg.password.empty() && !cfg.clientname.empty())
|
||||
req.push("HELLO", "3", "AUTH", cfg.username, cfg.password, "SETNAME", cfg.clientname);
|
||||
else if (cfg.password.empty() && cfg.clientname.empty())
|
||||
req.push("HELLO", "3");
|
||||
else if (cfg.clientname.empty())
|
||||
req.push("HELLO", "3", "AUTH", cfg.username, cfg.password);
|
||||
else
|
||||
req.push("HELLO", "3", "SETNAME", cfg.clientname);
|
||||
|
||||
if (cfg.database_index && cfg.database_index.value() != 0)
|
||||
req.push("SELECT", cfg.database_index.value());
|
||||
}
|
||||
|
||||
} // boost::redis::detail
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
@@ -19,6 +19,10 @@ namespace boost::redis {
|
||||
* @ingroup high-level-api
|
||||
*
|
||||
* The class can be passed to the connection objects to log to `std::clog`
|
||||
*
|
||||
* Notice that currently this class has no stable interface. Users
|
||||
* that don't want any logging can disable it by contructing a logger
|
||||
* with logger::level::emerg to the connection.
|
||||
*/
|
||||
class logger {
|
||||
public:
|
||||
@@ -26,7 +30,11 @@ public:
|
||||
* @ingroup high-level-api
|
||||
*/
|
||||
enum class level
|
||||
{ /// Emergency
|
||||
{
|
||||
/// Disabled
|
||||
disabled,
|
||||
|
||||
/// Emergency
|
||||
emerg,
|
||||
|
||||
/// Alert
|
||||
@@ -56,7 +64,7 @@ public:
|
||||
*
|
||||
* @param l Log level.
|
||||
*/
|
||||
logger(level l = level::info)
|
||||
logger(level l = level::disabled)
|
||||
: level_{l}
|
||||
{}
|
||||
|
||||
@@ -98,6 +106,22 @@ public:
|
||||
*/
|
||||
void on_write(system::error_code const& ec, std::string const& payload);
|
||||
|
||||
/** @brief Called when the read operation completes.
|
||||
* @ingroup high-level-api
|
||||
*
|
||||
* @param ec Error code returned by the read operation.
|
||||
* @param n Number of bytes read.
|
||||
*/
|
||||
void on_read(system::error_code const& ec, std::size_t n);
|
||||
|
||||
/** @brief Called when the run operation completes.
|
||||
* @ingroup high-level-api
|
||||
*
|
||||
* @param reader_ec Error code returned by the read operation.
|
||||
* @param writer_ec Error code returned by the write operation.
|
||||
*/
|
||||
void on_run(system::error_code const& reader_ec, system::error_code const& writer_ec);
|
||||
|
||||
/** @brief Called when the `HELLO` request completes.
|
||||
* @ingroup high-level-api
|
||||
*
|
||||
@@ -116,6 +140,26 @@ public:
|
||||
prefix_ = prefix;
|
||||
}
|
||||
|
||||
/** @brief Called when the runner operation completes.
|
||||
* @ingroup high-level-api
|
||||
*
|
||||
* @param run_all_ec Error code returned by the run_all operation.
|
||||
* @param health_check_ec Error code returned by the health checker operation.
|
||||
* @param hello_ec Error code returned by the health checker operation.
|
||||
*/
|
||||
void
|
||||
on_runner(
|
||||
system::error_code const& run_all_ec,
|
||||
system::error_code const& health_check_ec,
|
||||
system::error_code const& hello_ec);
|
||||
|
||||
void
|
||||
on_check_health(
|
||||
system::error_code const& ping_ec,
|
||||
system::error_code const& check_timeout_ec);
|
||||
|
||||
void trace(std::string_view reason);
|
||||
|
||||
private:
|
||||
void write_prefix();
|
||||
level level_;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
@@ -47,31 +47,31 @@ class request {
|
||||
public:
|
||||
/// Request configuration options.
|
||||
struct config {
|
||||
/** \brief If `true`
|
||||
* `boost::redis::connection::async_exec` will complete with error if the
|
||||
* connection is lost. Affects only requests that haven't been
|
||||
* sent yet.
|
||||
/** \brief If `true` calls to `connection::async_exec` will
|
||||
* complete with error if the connection is lost while the
|
||||
* request hasn't been sent yet.
|
||||
*/
|
||||
bool cancel_on_connection_lost = true;
|
||||
|
||||
/** \brief If `true` the request will complete with
|
||||
* boost::redis::error::not_connected if `async_exec` is called before
|
||||
* the connection with Redis was established.
|
||||
/** \brief If `true` `connection::async_exec` will complete with
|
||||
* `boost::redis::error::not_connected` if the call happens
|
||||
* before the connection with Redis was established.
|
||||
*/
|
||||
bool cancel_if_not_connected = false;
|
||||
|
||||
/** \brief If `false` `boost::redis::connection::async_exec` will not
|
||||
/** \brief If `false` `connection::async_exec` will not
|
||||
* automatically cancel this request if the connection is lost.
|
||||
* Affects only requests that have been written to the socket
|
||||
* but remained unresponded when `boost::redis::connection::async_run`
|
||||
* completed.
|
||||
* but remained unresponded when
|
||||
* `boost::redis::connection::async_run` completed.
|
||||
*/
|
||||
bool cancel_if_unresponded = true;
|
||||
|
||||
/** \brief If this request has a `HELLO` command and this flag is
|
||||
* `true`, the `boost::redis::connection` will move it to the front of
|
||||
* the queue of awaiting requests. This makes it possible to
|
||||
* send `HELLO` and authenticate before other commands are sent.
|
||||
/** \brief If this request has a `HELLO` command and this flag
|
||||
* is `true`, the `boost::redis::connection` will move it to the
|
||||
* front of the queue of awaiting requests. This makes it
|
||||
* possible to send `HELLO` and authenticate before other
|
||||
* commands are sent.
|
||||
*/
|
||||
bool hello_with_priority = true;
|
||||
};
|
||||
@@ -84,8 +84,12 @@ public:
|
||||
request(config cfg = config{true, false, true, true})
|
||||
: cfg_{cfg} {}
|
||||
|
||||
//// Returns the number of responses expected for this request.
|
||||
[[nodiscard]] auto get_expected_responses() const noexcept -> std::size_t
|
||||
{ return expected_responses_;};
|
||||
|
||||
//// Returns the number of commands contained in this request.
|
||||
[[nodiscard]] auto size() const noexcept -> std::size_t
|
||||
[[nodiscard]] auto get_commands() const noexcept -> std::size_t
|
||||
{ return commands_;};
|
||||
|
||||
[[nodiscard]] auto payload() const noexcept -> std::string_view
|
||||
@@ -99,6 +103,7 @@ public:
|
||||
{
|
||||
payload_.clear();
|
||||
commands_ = 0;
|
||||
expected_responses_ = 0;
|
||||
has_hello_priority_ = false;
|
||||
}
|
||||
|
||||
@@ -303,8 +308,10 @@ public:
|
||||
private:
|
||||
void check_cmd(std::string_view cmd)
|
||||
{
|
||||
++commands_;
|
||||
|
||||
if (!detail::has_response(cmd))
|
||||
++commands_;
|
||||
++expected_responses_;
|
||||
|
||||
if (cmd == "HELLO")
|
||||
has_hello_priority_ = cfg_.hello_with_priority;
|
||||
@@ -313,6 +320,7 @@ private:
|
||||
config cfg_;
|
||||
std::string payload_;
|
||||
std::size_t commands_ = 0;
|
||||
std::size_t expected_responses_ = 0;
|
||||
bool has_hello_priority_ = false;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2024 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
@@ -9,10 +9,11 @@
|
||||
#include <boost/assert.hpp>
|
||||
|
||||
#include <charconv>
|
||||
#include <limits>
|
||||
|
||||
namespace boost::redis::resp3 {
|
||||
|
||||
void to_int(int_type& i, std::string_view sv, system::error_code& ec)
|
||||
void to_int(std::size_t& i, std::string_view sv, system::error_code& ec)
|
||||
{
|
||||
auto const res = std::from_chars(sv.data(), sv.data() + std::size(sv), i);
|
||||
if (res.ec != std::errc())
|
||||
@@ -21,6 +22,16 @@ void to_int(int_type& i, std::string_view sv, system::error_code& ec)
|
||||
|
||||
parser::parser()
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
void parser::reset()
|
||||
{
|
||||
depth_ = 0;
|
||||
sizes_ = {{1}};
|
||||
bulk_length_ = (std::numeric_limits<std::size_t>::max)();
|
||||
bulk_ = type::invalid;
|
||||
consumed_ = 0;
|
||||
sizes_[0] = 2; // The sentinel must be more than 1.
|
||||
}
|
||||
|
||||
@@ -178,7 +189,7 @@ parser::consume_impl(
|
||||
case type::attribute:
|
||||
case type::map:
|
||||
{
|
||||
int_type l = -1;
|
||||
std::size_t l = -1;
|
||||
to_int(l, elem, ec);
|
||||
if (ec)
|
||||
return {};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2024 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
@@ -10,15 +10,12 @@
|
||||
#include <boost/redis/resp3/node.hpp>
|
||||
#include <boost/system/error_code.hpp>
|
||||
#include <array>
|
||||
#include <limits>
|
||||
#include <string_view>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
namespace boost::redis::resp3 {
|
||||
|
||||
using int_type = std::uint64_t;
|
||||
|
||||
class parser {
|
||||
public:
|
||||
using node_type = basic_node<std::string_view>;
|
||||
@@ -31,22 +28,22 @@ private:
|
||||
// The current depth. Simple data types will have depth 0, whereas
|
||||
// the elements of aggregates will have depth 1. Embedded types
|
||||
// will have increasing depth.
|
||||
std::size_t depth_ = 0;
|
||||
std::size_t depth_;
|
||||
|
||||
// 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::array<std::size_t, max_embedded_depth + 1> sizes_ = {{1}};
|
||||
std::array<std::size_t, max_embedded_depth + 1> sizes_;
|
||||
|
||||
// Contains the length expected in the next bulk read.
|
||||
int_type bulk_length_ = (std::numeric_limits<unsigned long>::max)();
|
||||
std::size_t bulk_length_;
|
||||
|
||||
// The type of the next bulk. Contains type::invalid if no bulk is
|
||||
// expected.
|
||||
type bulk_ = type::invalid;
|
||||
type bulk_;
|
||||
|
||||
// The number of bytes consumed from the buffer.
|
||||
std::size_t consumed_ = 0;
|
||||
std::size_t consumed_;
|
||||
|
||||
// Returns the number of bytes that have been consumed.
|
||||
auto consume_impl(type t, std::string_view elem, system::error_code& ec) -> node_type;
|
||||
@@ -71,8 +68,13 @@ public:
|
||||
auto get_consumed() const noexcept -> std::size_t;
|
||||
|
||||
auto consume(std::string_view view, system::error_code& ec) noexcept -> result;
|
||||
|
||||
void reset();
|
||||
};
|
||||
|
||||
// Returns false if more data is needed. If true is returned the
|
||||
// parser is either done or an error occured, that can be checked on
|
||||
// ec.
|
||||
template <class Adapter>
|
||||
bool
|
||||
parse(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
@@ -9,12 +9,14 @@
|
||||
|
||||
#include <boost/redis/resp3/node.hpp>
|
||||
#include <boost/redis/adapter/result.hpp>
|
||||
#include <boost/system.hpp>
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
|
||||
namespace boost::redis {
|
||||
namespace boost::redis
|
||||
{
|
||||
|
||||
/** @brief Response with compile-time size.
|
||||
* @ingroup high-level-api
|
||||
@@ -32,6 +34,47 @@ using response = std::tuple<adapter::result<Ts>...>;
|
||||
*/
|
||||
using generic_response = adapter::result<std::vector<resp3::node>>;
|
||||
|
||||
} // boost::redis::resp3
|
||||
/** @brief Consume on response from a generic response
|
||||
*
|
||||
* This function rotates the elements so that the start of the next
|
||||
* response becomes the new front element. For example the output of
|
||||
* the following code
|
||||
*
|
||||
* @code
|
||||
* request req;
|
||||
* req.push("PING", "one");
|
||||
* req.push("PING", "two");
|
||||
* req.push("PING", "three");
|
||||
*
|
||||
* generic_response resp;
|
||||
* co_await conn->async_exec(req, resp, asio::deferred);
|
||||
*
|
||||
* std::cout << "PING: " << resp.value().front().value << std::endl;
|
||||
* consume_one(resp);
|
||||
* std::cout << "PING: " << resp.value().front().value << std::endl;
|
||||
* consume_one(resp);
|
||||
* std::cout << "PING: " << resp.value().front().value << std::endl;
|
||||
* @endcode
|
||||
*
|
||||
* is
|
||||
*
|
||||
* @code
|
||||
* PING: one
|
||||
* PING: two
|
||||
* PING: three
|
||||
* @endcode
|
||||
*
|
||||
* Given that this function rotates elements, it won't be very
|
||||
* efficient for responses with a large number of elements. It was
|
||||
* introduced mainly to deal with buffers server pushes as shown in
|
||||
* the cpp20_subscriber.cpp example. In the future queue-like
|
||||
* responses might be introduced to consume in O(1) operations.
|
||||
*/
|
||||
void consume_one(generic_response& r, system::error_code& ec);
|
||||
|
||||
/// Throwing overload of `consume_one`.
|
||||
void consume_one(generic_response& r);
|
||||
|
||||
} // boost::redis
|
||||
|
||||
#endif // BOOST_REDIS_RESPONSE_HPP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
@@ -9,6 +9,8 @@
|
||||
#include <boost/redis/impl/request.ipp>
|
||||
#include <boost/redis/impl/ignore.ipp>
|
||||
#include <boost/redis/impl/connection.ipp>
|
||||
#include <boost/redis/impl/response.ipp>
|
||||
#include <boost/redis/impl/runner.ipp>
|
||||
#include <boost/redis/resp3/impl/type.ipp>
|
||||
#include <boost/redis/resp3/impl/parser.ipp>
|
||||
#include <boost/redis/resp3/impl/serialization.ipp>
|
||||
|
||||
43
include/boost/redis/usage.hpp
Normal file
43
include/boost/redis/usage.hpp
Normal file
@@ -0,0 +1,43 @@
|
||||
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#ifndef BOOST_REDIS_USAGE_HPP
|
||||
#define BOOST_REDIS_USAGE_HPP
|
||||
|
||||
namespace boost::redis
|
||||
{
|
||||
|
||||
/** @brief Connection usage information.
|
||||
* @ingroup high-level-api
|
||||
*
|
||||
* @note: To simplify the implementation, the commands_sent and
|
||||
* bytes_sent in the struct below are computed just before writing to
|
||||
* the socket, which means on error they might not represent exaclty
|
||||
* what has been received by the Redis server.
|
||||
*/
|
||||
struct usage {
|
||||
/// Number of commands sent.
|
||||
std::size_t commands_sent = 0;
|
||||
|
||||
/// Number of bytes sent.
|
||||
std::size_t bytes_sent = 0;
|
||||
|
||||
/// Number of responses received.
|
||||
std::size_t responses_received = 0;
|
||||
|
||||
/// Number of pushes received.
|
||||
std::size_t pushes_received = 0;
|
||||
|
||||
/// Number of response-bytes received.
|
||||
std::size_t response_bytes_received = 0;
|
||||
|
||||
/// Number of push-bytes received.
|
||||
std::size_t push_bytes_received = 0;
|
||||
};
|
||||
|
||||
} // boost::redis
|
||||
|
||||
#endif // BOOST_REDIS_USAGE_HPP
|
||||
24
index.html
Normal file
24
index.html
Normal file
@@ -0,0 +1,24 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Boost.Redis</title>
|
||||
<meta http-equiv="refresh" content="0; URL=./doc/html/index.html">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
Automatic redirection failed, please go to
|
||||
<a href="./doc/html/index.html">./doc/html/index.html</a>
|
||||
<hr>
|
||||
<tt>
|
||||
Boost.Redis<br>
|
||||
<br>
|
||||
Copyright (C) 2023 Marcelo Zimbres<br>
|
||||
<br>
|
||||
Distributed under the Boost Software License, Version 1.0.
|
||||
(See accompanying file LICENSE_1_0.txt or copy at
|
||||
<a href=http://www.boost.org/LICENSE_1_0.txt>http://www.boost.org/LICENSE_1_0.txt</a>) <br>
|
||||
<br>
|
||||
</tt>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
16
meta/libraries.json
Normal file
16
meta/libraries.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"key": "redis",
|
||||
"name": "Redis",
|
||||
"authors": [
|
||||
"Marcelo Zimbres Silva"
|
||||
],
|
||||
"description": "Redis async client library built on top of Boost.Asio.",
|
||||
"category": [
|
||||
"Concurrent",
|
||||
"IO"
|
||||
],
|
||||
"maintainers": [
|
||||
"Marcelo Zimbres Silva <mzimbres@gmail.com>"
|
||||
],
|
||||
"cxxstd": "17"
|
||||
}
|
||||
76
test/CMakeLists.txt
Normal file
76
test/CMakeLists.txt
Normal file
@@ -0,0 +1,76 @@
|
||||
|
||||
# Common utilities
|
||||
add_library(boost_redis_project_options INTERFACE)
|
||||
target_link_libraries(boost_redis_project_options INTERFACE boost_redis)
|
||||
if (MSVC)
|
||||
target_compile_options(boost_redis_project_options INTERFACE /bigobj)
|
||||
target_compile_definitions(boost_redis_project_options INTERFACE _WIN32_WINNT=0x0601)
|
||||
endif()
|
||||
|
||||
add_library(boost_redis_src STATIC boost_redis.cpp)
|
||||
target_compile_features(boost_redis_src PRIVATE cxx_std_17)
|
||||
target_link_libraries(boost_redis_src PRIVATE boost_redis_project_options)
|
||||
|
||||
# Test utils
|
||||
add_library(boost_redis_tests_common STATIC common.cpp)
|
||||
target_compile_features(boost_redis_tests_common PRIVATE cxx_std_17)
|
||||
target_link_libraries(boost_redis_tests_common PRIVATE boost_redis_project_options)
|
||||
|
||||
macro(make_test TEST_NAME STANDARD)
|
||||
set(EXE_NAME "boost_redis_${TEST_NAME}")
|
||||
add_executable(${EXE_NAME} ${TEST_NAME}.cpp)
|
||||
target_link_libraries(${EXE_NAME} PRIVATE
|
||||
boost_redis_src
|
||||
boost_redis_tests_common
|
||||
boost_redis_project_options
|
||||
Boost::unit_test_framework
|
||||
)
|
||||
target_compile_features(${EXE_NAME} PRIVATE cxx_std_${STANDARD})
|
||||
add_test(${EXE_NAME} ${EXE_NAME})
|
||||
endmacro()
|
||||
|
||||
make_test(test_conn_quit 17)
|
||||
# TODO: Configure a Redis server with TLS in the CI and reenable this test.
|
||||
#make_test(test_conn_tls 17)
|
||||
make_test(test_low_level 17)
|
||||
make_test(test_conn_exec_retry 17)
|
||||
make_test(test_conn_exec_error 17)
|
||||
make_test(test_request 17)
|
||||
make_test(test_run 17)
|
||||
make_test(test_low_level_sync_sans_io 17)
|
||||
make_test(test_conn_check_health 17)
|
||||
|
||||
make_test(test_conn_exec 20)
|
||||
make_test(test_conn_push 20)
|
||||
make_test(test_conn_reconnect 20)
|
||||
make_test(test_conn_exec_cancel 20)
|
||||
make_test(test_conn_exec_cancel2 20)
|
||||
make_test(test_conn_echo_stress 20)
|
||||
make_test(test_conn_run_cancel 20)
|
||||
make_test(test_issue_50 20)
|
||||
make_test(test_issue_181 17)
|
||||
|
||||
# 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
|
||||
)
|
||||
62
test/Jamfile
Normal file
62
test/Jamfile
Normal file
@@ -0,0 +1,62 @@
|
||||
|
||||
import ../config/checks/config : requires ;
|
||||
import ac ;
|
||||
|
||||
# Configure openssl if it hasn't been done yet
|
||||
using openssl ;
|
||||
|
||||
# Use these requirements as both regular and usage requirements across all tests
|
||||
local requirements =
|
||||
<define>BOOST_ASIO_NO_DEPRECATED=1
|
||||
<define>BOOST_ASIO_DISABLE_BOOST_ARRAY=1
|
||||
<define>BOOST_ASIO_DISABLE_BOOST_BIND=1
|
||||
<define>BOOST_ASIO_DISABLE_BOOST_DATE_TIME=1
|
||||
<define>BOOST_ASIO_DISABLE_BOOST_REGEX=1
|
||||
<toolset>msvc:<cxxflags>"/bigobj"
|
||||
<target-os>windows:<define>_WIN32_WINNT=0x0601
|
||||
<include>../include
|
||||
[ requires
|
||||
cxx14_constexpr
|
||||
cxx14_generic_lambdas
|
||||
cxx14_initialized_lambda_captures
|
||||
cxx14_aggregate_nsdmi
|
||||
cxx14_return_type_deduction
|
||||
cxx17_hdr_charconv
|
||||
cxx17_hdr_optional
|
||||
cxx17_hdr_string_view
|
||||
cxx17_hdr_variant
|
||||
cxx17_std_apply
|
||||
cxx17_structured_bindings
|
||||
]
|
||||
[ ac.check-library /openssl//ssl : <library>/openssl//ssl/<link>shared : <build>no ]
|
||||
[ ac.check-library /openssl//crypto : <library>/openssl//crypto/<link>shared : <build>no ]
|
||||
;
|
||||
|
||||
|
||||
# Helper library
|
||||
lib redis_test_common
|
||||
:
|
||||
boost_redis.cpp
|
||||
common.cpp
|
||||
: requirements $(requirements)
|
||||
: usage-requirements $(requirements)
|
||||
;
|
||||
|
||||
# B2 runs tests in parallel, and some tests rely on having exclusive
|
||||
# access to a Redis server, so we only run the ones that don't require a DB server.
|
||||
local tests =
|
||||
test_low_level_sync_sans_io
|
||||
test_low_level
|
||||
test_request
|
||||
test_run
|
||||
;
|
||||
|
||||
# Build and run the tests
|
||||
for local test in $(tests)
|
||||
{
|
||||
run
|
||||
$(test).cpp
|
||||
redis_test_common/<link>static
|
||||
: target-name $(test)
|
||||
;
|
||||
}
|
||||
13
test/cmake_b2_test/CMakeLists.txt
Normal file
13
test/cmake_b2_test/CMakeLists.txt
Normal file
@@ -0,0 +1,13 @@
|
||||
cmake_minimum_required(VERSION 3.5...3.22)
|
||||
|
||||
project(boost_redis_b2_test LANGUAGES CXX)
|
||||
|
||||
find_package(Boost REQUIRED COMPONENTS headers)
|
||||
find_package(Threads REQUIRED)
|
||||
find_package(OpenSSL REQUIRED)
|
||||
|
||||
add_executable(main main.cpp)
|
||||
target_link_libraries(main PRIVATE Boost::headers Threads::Threads OpenSSL::Crypto OpenSSL::SSL)
|
||||
|
||||
include(CTest)
|
||||
add_test(NAME main COMMAND main)
|
||||
15
test/cmake_b2_test/main.cpp
Normal file
15
test/cmake_b2_test/main.cpp
Normal file
@@ -0,0 +1,15 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
|
||||
#include <boost/redis/connection.hpp>
|
||||
#include <boost/redis/src.hpp>
|
||||
|
||||
int main()
|
||||
{
|
||||
boost::redis::connection conn(boost::asio::system_executor{});
|
||||
return static_cast<int>(!conn.will_reconnect());
|
||||
}
|
||||
13
test/cmake_install_test/CMakeLists.txt
Normal file
13
test/cmake_install_test/CMakeLists.txt
Normal file
@@ -0,0 +1,13 @@
|
||||
cmake_minimum_required(VERSION 3.5...3.22)
|
||||
|
||||
project(cmake_install_test LANGUAGES CXX)
|
||||
|
||||
find_package(boost_redis REQUIRED)
|
||||
|
||||
add_executable(main main.cpp)
|
||||
target_link_libraries(main PRIVATE Boost::redis)
|
||||
|
||||
include(CTest)
|
||||
add_test(main main)
|
||||
|
||||
add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure -C $<CONFIG>)
|
||||
15
test/cmake_install_test/main.cpp
Normal file
15
test/cmake_install_test/main.cpp
Normal file
@@ -0,0 +1,15 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
|
||||
#include <boost/redis/connection.hpp>
|
||||
#include <boost/redis/src.hpp>
|
||||
|
||||
int main()
|
||||
{
|
||||
boost::redis::connection conn(boost::asio::system_executor{});
|
||||
return static_cast<int>(!conn.will_reconnect());
|
||||
}
|
||||
16
test/cmake_subdir_test/CMakeLists.txt
Normal file
16
test/cmake_subdir_test/CMakeLists.txt
Normal file
@@ -0,0 +1,16 @@
|
||||
cmake_minimum_required(VERSION 3.5...3.22)
|
||||
|
||||
project(cmake_subdir_test LANGUAGES CXX)
|
||||
|
||||
set(BOOST_INCLUDE_LIBRARIES redis)
|
||||
|
||||
# Build our dependencies, so the targets Boost::xxx are defined
|
||||
add_subdirectory(../../../.. boostorg/boost)
|
||||
|
||||
add_executable(main main.cpp)
|
||||
target_link_libraries(main PRIVATE Boost::redis)
|
||||
|
||||
include(CTest)
|
||||
add_test(main main)
|
||||
|
||||
add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure -C $<CONFIG>)
|
||||
15
test/cmake_subdir_test/main.cpp
Normal file
15
test/cmake_subdir_test/main.cpp
Normal file
@@ -0,0 +1,15 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
|
||||
#include <boost/redis/connection.hpp>
|
||||
#include <boost/redis/src.hpp>
|
||||
|
||||
int main()
|
||||
{
|
||||
boost::redis::connection conn(boost::asio::system_executor{});
|
||||
return static_cast<int>(!conn.will_reconnect());
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
#include "common.hpp"
|
||||
#include <iostream>
|
||||
#include <cstdlib>
|
||||
#include <boost/asio/consign.hpp>
|
||||
#include <boost/asio/co_spawn.hpp>
|
||||
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
namespace net = boost::asio;
|
||||
|
||||
struct run_callback {
|
||||
@@ -15,7 +14,6 @@ struct run_callback {
|
||||
void operator()(boost::system::error_code const& ec) const
|
||||
{
|
||||
std::cout << "async_run: " << ec.message() << std::endl;
|
||||
//BOOST_CHECK_EQUAL(ec, expected);
|
||||
conn->cancel(op);
|
||||
}
|
||||
};
|
||||
@@ -31,6 +29,32 @@ run(
|
||||
conn->async_run(cfg, {l}, run_callback{conn, op, ec});
|
||||
}
|
||||
|
||||
static std::string safe_getenv(const char* name, const char* default_value)
|
||||
{
|
||||
// MSVC doesn't like getenv
|
||||
#ifdef BOOST_MSVC
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4996)
|
||||
#endif
|
||||
const char* res = std::getenv(name);
|
||||
#ifdef BOOST_MSVC
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
return res ? res : default_value;
|
||||
}
|
||||
|
||||
std::string get_server_hostname()
|
||||
{
|
||||
return safe_getenv("BOOST_REDIS_TEST_SERVER", "localhost");
|
||||
}
|
||||
|
||||
boost::redis::config make_test_config()
|
||||
{
|
||||
boost::redis::config cfg;
|
||||
cfg.addr.host = get_server_hostname();
|
||||
return cfg;
|
||||
}
|
||||
|
||||
#ifdef BOOST_ASIO_HAS_CO_AWAIT
|
||||
auto start(net::awaitable<void> op) -> int
|
||||
{
|
||||
@@ -50,5 +74,4 @@ auto start(net::awaitable<void> op) -> int
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
#endif // BOOST_ASIO_HAS_CO_AWAIT
|
||||
@@ -15,11 +15,14 @@ auto redir(boost::system::error_code& ec)
|
||||
auto start(boost::asio::awaitable<void> op) -> int;
|
||||
#endif // BOOST_ASIO_HAS_CO_AWAIT
|
||||
|
||||
boost::redis::config make_test_config();
|
||||
std::string get_server_hostname();
|
||||
|
||||
void
|
||||
run(
|
||||
std::shared_ptr<boost::redis::connection> conn,
|
||||
boost::redis::config cfg = {},
|
||||
boost::redis::config cfg = make_test_config(),
|
||||
boost::system::error_code ec = boost::asio::error::operation_aborted,
|
||||
boost::redis::operation op = boost::redis::operation::receive,
|
||||
boost::redis::logger::level l = boost::redis::logger::level::info);
|
||||
boost::redis::logger::level l = boost::redis::logger::level::disabled);
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
#include <boost/redis/connection.hpp>
|
||||
#include <boost/redis/response.hpp>
|
||||
#include <boost/system/errc.hpp>
|
||||
#define BOOST_TEST_MODULE check-health
|
||||
#include <boost/test/included/unit_test.hpp>
|
||||
@@ -20,13 +21,10 @@ using boost::redis::request;
|
||||
using boost::redis::ignore;
|
||||
using boost::redis::operation;
|
||||
using boost::redis::generic_response;
|
||||
using boost::redis::logger;
|
||||
using redis::config;
|
||||
using boost::redis::consume_one;
|
||||
|
||||
// TODO: Test cancel(health_check)
|
||||
|
||||
std::chrono::seconds const interval{1};
|
||||
|
||||
struct push_callback {
|
||||
connection* conn1;
|
||||
connection* conn2;
|
||||
@@ -39,9 +37,8 @@ struct push_callback {
|
||||
{
|
||||
BOOST_ASIO_CORO_REENTER (coro) for (;;)
|
||||
{
|
||||
resp2->value().clear();
|
||||
BOOST_ASIO_CORO_YIELD
|
||||
conn2->async_receive(*resp2, *this);
|
||||
conn2->async_receive(*this);
|
||||
if (ec) {
|
||||
std::clog << "Exiting." << std::endl;
|
||||
return;
|
||||
@@ -50,6 +47,7 @@ struct push_callback {
|
||||
BOOST_TEST(resp2->has_value());
|
||||
BOOST_TEST(!resp2->value().empty());
|
||||
std::clog << "Event> " << resp2->value().front().value << std::endl;
|
||||
consume_one(*resp2);
|
||||
|
||||
++i;
|
||||
|
||||
@@ -73,14 +71,12 @@ struct push_callback {
|
||||
BOOST_AUTO_TEST_CASE(check_health)
|
||||
{
|
||||
net::io_context ioc;
|
||||
|
||||
|
||||
connection conn1{ioc};
|
||||
|
||||
request req1;
|
||||
req1.push("CLIENT", "PAUSE", "10000", "ALL");
|
||||
|
||||
config cfg1;
|
||||
auto cfg1 = make_test_config();
|
||||
cfg1.health_check_id = "conn1";
|
||||
cfg1.reconnect_wait_interval = std::chrono::seconds::zero();
|
||||
error_code res1;
|
||||
@@ -95,7 +91,7 @@ BOOST_AUTO_TEST_CASE(check_health)
|
||||
// sending MONITOR. I will therefore open a second connection.
|
||||
connection conn2{ioc};
|
||||
|
||||
config cfg2;
|
||||
auto cfg2 = make_test_config();
|
||||
cfg2.health_check_id = "conn2";
|
||||
error_code res2;
|
||||
conn2.async_run(cfg2, {}, [&](auto ec){
|
||||
@@ -106,6 +102,7 @@ BOOST_AUTO_TEST_CASE(check_health)
|
||||
request req2;
|
||||
req2.push("MONITOR");
|
||||
generic_response resp2;
|
||||
conn2.set_receive_response(resp2);
|
||||
|
||||
conn2.async_exec(req2, ignore, [](auto ec, auto) {
|
||||
std::cout << "async_exec: " << std::endl;
|
||||
@@ -113,7 +110,7 @@ BOOST_AUTO_TEST_CASE(check_health)
|
||||
});
|
||||
|
||||
//--------------------------------
|
||||
|
||||
|
||||
push_callback{&conn1, &conn2, &resp2, &req1}(); // Starts reading pushes.
|
||||
|
||||
ioc.run();
|
||||
@@ -24,14 +24,40 @@ using boost::redis::response;
|
||||
using boost::redis::ignore;
|
||||
using boost::redis::ignore_t;
|
||||
using boost::redis::logger;
|
||||
using boost::redis::config;
|
||||
using boost::redis::connection;
|
||||
using boost::redis::usage;
|
||||
using boost::redis::error;
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, usage const& u)
|
||||
{
|
||||
os
|
||||
<< "Commands sent: " << u.commands_sent << "\n"
|
||||
<< "Bytes sent: " << u.bytes_sent << "\n"
|
||||
<< "Responses received: " << u.responses_received << "\n"
|
||||
<< "Pushes received: " << u.pushes_received << "\n"
|
||||
<< "Response bytes received: " << u.response_bytes_received << "\n"
|
||||
<< "Push bytes received: " << u.push_bytes_received;
|
||||
|
||||
return os;
|
||||
}
|
||||
|
||||
auto push_consumer(std::shared_ptr<connection> conn, int expected) -> net::awaitable<void>
|
||||
{
|
||||
int c = 0;
|
||||
for (;;) {
|
||||
co_await conn->async_receive(ignore, net::use_awaitable);
|
||||
for (error_code ec;;) {
|
||||
conn->receive(ec);
|
||||
if (ec == error::sync_receive_push_failed) {
|
||||
ec = {};
|
||||
co_await conn->async_receive(redirect_error(net::use_awaitable, ec));
|
||||
} else if (!ec) {
|
||||
//std::cout << "Skipping suspension." << std::endl;
|
||||
}
|
||||
|
||||
if (ec) {
|
||||
BOOST_TEST(false);
|
||||
std::cout << "push_consumer error: " << ec.message() << std::endl;
|
||||
co_return;
|
||||
}
|
||||
if (++c == expected)
|
||||
break;
|
||||
}
|
||||
@@ -43,37 +69,16 @@ auto
|
||||
echo_session(
|
||||
std::shared_ptr<connection> conn,
|
||||
std::shared_ptr<request> pubs,
|
||||
std::string id,
|
||||
int n) -> net::awaitable<void>
|
||||
{
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
|
||||
request req;
|
||||
response<ignore_t, std::string, ignore_t> resp;
|
||||
|
||||
for (auto i = 0; i < n; ++i) {
|
||||
auto const msg = id + "/" + std::to_string(i);
|
||||
//std::cout << msg << std::endl;
|
||||
req.push("HELLO", 3); // Just to mess around.
|
||||
req.push("PING", msg);
|
||||
req.push("PING", "lsls"); // TODO: Change to HELLO after fixing issue 105.
|
||||
boost::system::error_code ec;
|
||||
co_await conn->async_exec(req, resp, redir(ec));
|
||||
|
||||
BOOST_REQUIRE_EQUAL(ec, boost::system::error_code{});
|
||||
BOOST_REQUIRE_EQUAL(msg, std::get<1>(resp).value());
|
||||
req.clear();
|
||||
std::get<1>(resp).value().clear();
|
||||
|
||||
for (auto i = 0; i < n; ++i)
|
||||
co_await conn->async_exec(*pubs, ignore, net::deferred);
|
||||
}
|
||||
}
|
||||
|
||||
auto async_echo_stress() -> net::awaitable<void>
|
||||
auto async_echo_stress(std::shared_ptr<connection> conn) -> net::awaitable<void>
|
||||
{
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
auto conn = std::make_shared<connection>(ex);
|
||||
config cfg;
|
||||
auto cfg = make_test_config();
|
||||
cfg.health_check_interval = std::chrono::seconds::zero();
|
||||
run(conn, cfg,
|
||||
boost::asio::error::operation_aborted,
|
||||
@@ -86,19 +91,20 @@ auto async_echo_stress() -> net::awaitable<void>
|
||||
|
||||
// Number of coroutines that will send pings sharing the same
|
||||
// connection to redis.
|
||||
int const sessions = 500;
|
||||
int const sessions = 150;
|
||||
|
||||
// The number of pings that will be sent by each session.
|
||||
int const msgs = 1000;
|
||||
int const msgs = 200;
|
||||
|
||||
// The number of publishes that will be sent by each session with
|
||||
// each message.
|
||||
int const n_pubs = 10;
|
||||
int const n_pubs = 25;
|
||||
|
||||
// This is the total number of pushes we will receive.
|
||||
int total_pushes = sessions * msgs * n_pubs + 1;
|
||||
|
||||
auto pubs = std::make_shared<request>();
|
||||
pubs->push("PING");
|
||||
for (int i = 0; i < n_pubs; ++i)
|
||||
pubs->push("PUBLISH", "channel", "payload");
|
||||
|
||||
@@ -107,14 +113,20 @@ auto async_echo_stress() -> net::awaitable<void>
|
||||
net::co_spawn(ex, push_consumer(conn, total_pushes), net::detached);
|
||||
|
||||
for (int i = 0; i < sessions; ++i)
|
||||
net::co_spawn(ex, echo_session(conn, pubs, std::to_string(i), msgs), net::detached);
|
||||
net::co_spawn(ex, echo_session(conn, pubs, msgs), net::detached);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(echo_stress)
|
||||
{
|
||||
net::io_context ioc;
|
||||
net::co_spawn(ioc, async_echo_stress(), net::detached);
|
||||
auto conn = std::make_shared<connection>(ioc);
|
||||
net::co_spawn(ioc, async_echo_stress(conn), net::detached);
|
||||
ioc.run();
|
||||
|
||||
std::cout
|
||||
<< "-------------------\n"
|
||||
<< conn->get_usage()
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
#else
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <boost/redis/connection.hpp>
|
||||
#include <boost/system/errc.hpp>
|
||||
#include <boost/asio/detached.hpp>
|
||||
#define BOOST_TEST_MODULE conn-exec
|
||||
#include <boost/test/included/unit_test.hpp>
|
||||
#include <iostream>
|
||||
@@ -17,13 +18,13 @@
|
||||
// container.
|
||||
|
||||
namespace net = boost::asio;
|
||||
using boost::redis::config;
|
||||
using boost::redis::connection;
|
||||
using boost::redis::request;
|
||||
using boost::redis::response;
|
||||
using boost::redis::generic_response;
|
||||
using boost::redis::ignore;
|
||||
using boost::redis::operation;
|
||||
using boost::redis::config;
|
||||
using boost::redis::request;
|
||||
using boost::redis::response;
|
||||
|
||||
// Sends three requests where one of them has a hello with a priority
|
||||
// set, which means it should be executed first.
|
||||
@@ -53,7 +54,7 @@ BOOST_AUTO_TEST_CASE(hello_priority)
|
||||
conn->async_exec(req1, ignore, [&](auto ec, auto){
|
||||
// Second callback to the called.
|
||||
std::cout << "req1" << std::endl;
|
||||
BOOST_CHECK_EQUAL(ec, boost::system::error_code{});
|
||||
BOOST_TEST(!ec);
|
||||
BOOST_TEST(!seen2);
|
||||
BOOST_TEST(seen3);
|
||||
seen1 = true;
|
||||
@@ -62,7 +63,7 @@ BOOST_AUTO_TEST_CASE(hello_priority)
|
||||
conn->async_exec(req2, ignore, [&](auto ec, auto){
|
||||
// Last callback to the called.
|
||||
std::cout << "req2" << std::endl;
|
||||
BOOST_CHECK_EQUAL(ec, boost::system::error_code{});
|
||||
BOOST_TEST(!ec);
|
||||
BOOST_TEST(seen1);
|
||||
BOOST_TEST(seen3);
|
||||
seen2 = true;
|
||||
@@ -73,7 +74,7 @@ BOOST_AUTO_TEST_CASE(hello_priority)
|
||||
conn->async_exec(req3, ignore, [&](auto ec, auto){
|
||||
// Callback that will be called first.
|
||||
std::cout << "req3" << std::endl;
|
||||
BOOST_CHECK_EQUAL(ec, boost::system::error_code{});
|
||||
BOOST_TEST(!ec);
|
||||
BOOST_TEST(!seen1);
|
||||
BOOST_TEST(!seen2);
|
||||
seen3 = true;
|
||||
@@ -122,7 +123,7 @@ BOOST_AUTO_TEST_CASE(cancel_request_if_not_connected)
|
||||
|
||||
BOOST_AUTO_TEST_CASE(correct_database)
|
||||
{
|
||||
config cfg;
|
||||
auto cfg = make_test_config();
|
||||
cfg.database_index = 2;
|
||||
|
||||
net::io_context ioc;
|
||||
@@ -154,3 +155,36 @@ BOOST_AUTO_TEST_CASE(correct_database)
|
||||
BOOST_CHECK_EQUAL(cfg.database_index.value(), index);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(large_number_of_concurrent_requests_issue_170)
|
||||
{
|
||||
// See https://github.com/boostorg/redis/issues/170
|
||||
|
||||
std::string payload;
|
||||
payload.resize(1024);
|
||||
std::fill(std::begin(payload), std::end(payload), 'A');
|
||||
|
||||
net::io_context ioc;
|
||||
auto conn = std::make_shared<connection>(ioc);
|
||||
|
||||
auto cfg = make_test_config();
|
||||
cfg.health_check_interval = std::chrono::seconds(0);
|
||||
conn->async_run(cfg, {}, net::detached);
|
||||
|
||||
int counter = 0;
|
||||
int const repeat = 8000;
|
||||
|
||||
for (int i = 0; i < repeat; ++i) {
|
||||
auto req = std::make_shared<request>();
|
||||
req->push("PING", payload);
|
||||
conn->async_exec(*req, ignore, [req, &counter, conn](auto ec, auto) {
|
||||
BOOST_TEST(!ec);
|
||||
if (++counter == repeat)
|
||||
conn->cancel();
|
||||
});
|
||||
}
|
||||
|
||||
ioc.run();
|
||||
|
||||
BOOST_CHECK_EQUAL(counter, repeat);
|
||||
}
|
||||
|
||||
@@ -29,7 +29,6 @@ using boost::redis::response;
|
||||
using boost::redis::generic_response;
|
||||
using boost::redis::ignore;
|
||||
using boost::redis::ignore_t;
|
||||
using boost::redis::config;
|
||||
using boost::redis::logger;
|
||||
using boost::redis::connection;
|
||||
using namespace std::chrono_literals;
|
||||
@@ -39,8 +38,8 @@ auto implicit_cancel_of_req_written() -> net::awaitable<void>
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
auto conn = std::make_shared<connection>(ex);
|
||||
|
||||
config cfg;
|
||||
cfg.health_check_interval = std::chrono::seconds{0};
|
||||
auto cfg = make_test_config();
|
||||
cfg.health_check_interval = std::chrono::seconds::zero();
|
||||
run(conn, cfg);
|
||||
|
||||
// See NOTE1.
|
||||
@@ -106,7 +105,7 @@ BOOST_AUTO_TEST_CASE(test_cancel_of_req_written_on_run_canceled)
|
||||
|
||||
conn->async_exec(req0, ignore, c0);
|
||||
|
||||
config cfg;
|
||||
auto cfg = make_test_config();
|
||||
cfg.health_check_interval = std::chrono::seconds{5};
|
||||
run(conn);
|
||||
|
||||
@@ -25,7 +25,6 @@ using boost::redis::ignore_t;
|
||||
using boost::redis::error;
|
||||
using boost::redis::logger;
|
||||
using boost::redis::operation;
|
||||
using redis::config;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
BOOST_AUTO_TEST_CASE(no_ignore_error)
|
||||
@@ -242,6 +241,8 @@ BOOST_AUTO_TEST_CASE(subscriber_wrong_syntax)
|
||||
conn->async_exec(req1, ignore, c1);
|
||||
|
||||
generic_response gresp;
|
||||
conn->set_receive_response(gresp);
|
||||
|
||||
auto c3 = [&](auto ec, auto)
|
||||
{
|
||||
std::cout << "async_receive" << std::endl;
|
||||
@@ -254,7 +255,7 @@ BOOST_AUTO_TEST_CASE(subscriber_wrong_syntax)
|
||||
conn->cancel(operation::reconnection);
|
||||
};
|
||||
|
||||
conn->async_receive(gresp, c3);
|
||||
conn->async_receive(c3);
|
||||
|
||||
run(conn);
|
||||
|
||||
@@ -57,12 +57,12 @@ BOOST_AUTO_TEST_CASE(request_retry_false)
|
||||
|
||||
auto c2 = [&](auto ec, auto){
|
||||
std::cout << "c2" << std::endl;
|
||||
BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled);
|
||||
BOOST_CHECK_EQUAL(ec, boost::asio::error::operation_aborted);
|
||||
};
|
||||
|
||||
auto c1 = [&](auto ec, auto){
|
||||
std::cout << "c1" << std::endl;
|
||||
BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled);
|
||||
BOOST_CHECK_EQUAL(ec, boost::asio::error::operation_aborted);
|
||||
};
|
||||
|
||||
auto c0 = [&](auto ec, auto){
|
||||
@@ -74,7 +74,7 @@ BOOST_AUTO_TEST_CASE(request_retry_false)
|
||||
|
||||
conn->async_exec(req0, ignore, c0);
|
||||
|
||||
config cfg;
|
||||
auto cfg = make_test_config();
|
||||
cfg.health_check_interval = 5s;
|
||||
run(conn);
|
||||
|
||||
@@ -137,7 +137,7 @@ BOOST_AUTO_TEST_CASE(request_retry_true)
|
||||
|
||||
conn->async_exec(req0, ignore, c0);
|
||||
|
||||
config cfg;
|
||||
auto cfg = make_test_config();
|
||||
cfg.health_check_interval = 5s;
|
||||
conn->async_run(cfg, {}, [&](auto ec){
|
||||
std::cout << ec.message() << std::endl;
|
||||
@@ -26,7 +26,7 @@ using boost::redis::request;
|
||||
using boost::redis::response;
|
||||
using boost::redis::ignore;
|
||||
using boost::redis::ignore_t;
|
||||
using redis::config;
|
||||
using boost::system::error_code;
|
||||
using boost::redis::logger;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
@@ -49,7 +49,7 @@ BOOST_AUTO_TEST_CASE(receives_push_waiting_resps)
|
||||
|
||||
auto c3 =[](auto ec, auto...)
|
||||
{
|
||||
BOOST_TEST(!!ec);
|
||||
std::cout << "c3: " << ec.message() << std::endl;
|
||||
};
|
||||
|
||||
auto c2 =[&, conn](auto ec, auto...)
|
||||
@@ -66,15 +66,14 @@ BOOST_AUTO_TEST_CASE(receives_push_waiting_resps)
|
||||
|
||||
conn->async_exec(req1, ignore, c1);
|
||||
|
||||
run(conn, {}, {});
|
||||
run(conn, make_test_config(), {});
|
||||
|
||||
bool push_received = false;
|
||||
conn->async_receive(ignore, [&, conn](auto ec, auto){
|
||||
conn->async_receive([&, conn](auto ec, auto){
|
||||
std::cout << "async_receive" << std::endl;
|
||||
BOOST_TEST(!ec);
|
||||
conn->cancel(operation::run);
|
||||
conn->cancel(operation::reconnection);
|
||||
push_received = true;
|
||||
conn->cancel();
|
||||
});
|
||||
|
||||
ioc.run();
|
||||
@@ -87,29 +86,45 @@ BOOST_AUTO_TEST_CASE(push_received1)
|
||||
net::io_context ioc;
|
||||
auto conn = std::make_shared<connection>(ioc);
|
||||
|
||||
// Trick: Uses SUBSCRIBE because this command has no response or
|
||||
// better said, its response is a server push, which is what we
|
||||
// want to test. We send two because we want to test both
|
||||
// async_receive and receive.
|
||||
request req;
|
||||
//req.push("HELLO", 3);
|
||||
req.push("SUBSCRIBE", "channel");
|
||||
req.push("SUBSCRIBE", "channel1");
|
||||
req.push("SUBSCRIBE", "channel2");
|
||||
|
||||
conn->async_exec(req, ignore, [conn](auto ec, auto){
|
||||
std::cout << "async_exec" << std::endl;
|
||||
BOOST_TEST(!ec);
|
||||
});
|
||||
|
||||
run(conn);
|
||||
bool push_async_received = false;
|
||||
conn->async_receive([&, conn](auto ec, auto){
|
||||
std::cout << "(1) async_receive" << std::endl;
|
||||
|
||||
bool push_received = false;
|
||||
conn->async_receive(ignore, [&, conn](auto ec, auto){
|
||||
std::cout << "async_receive" << std::endl;
|
||||
BOOST_TEST(!ec);
|
||||
conn->cancel(operation::run);
|
||||
conn->cancel(operation::reconnection);
|
||||
push_received = true;
|
||||
push_async_received = true;
|
||||
|
||||
// Receives the second push synchronously.
|
||||
error_code ec2;
|
||||
std::size_t res = 0;
|
||||
res = conn->receive(ec2);
|
||||
BOOST_TEST(!ec2);
|
||||
BOOST_TEST(res != std::size_t(0));
|
||||
|
||||
// Tries to receive a third push synchronously.
|
||||
ec2 = {};
|
||||
res = conn->receive(ec2);
|
||||
BOOST_CHECK_EQUAL(ec2, boost::redis::make_error_code(boost::redis::error::sync_receive_push_failed));
|
||||
|
||||
conn->cancel();
|
||||
});
|
||||
|
||||
run(conn);
|
||||
ioc.run();
|
||||
|
||||
BOOST_TEST(push_received);
|
||||
BOOST_TEST(push_async_received);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(push_filtered_out)
|
||||
@@ -128,7 +143,7 @@ BOOST_AUTO_TEST_CASE(push_filtered_out)
|
||||
BOOST_TEST(!ec);
|
||||
});
|
||||
|
||||
conn->async_receive(ignore, [conn](auto ec, auto){
|
||||
conn->async_receive([&, conn](auto ec, auto){
|
||||
BOOST_TEST(!ec);
|
||||
conn->cancel(operation::reconnection);
|
||||
});
|
||||
@@ -146,12 +161,12 @@ net::awaitable<void>
|
||||
push_consumer1(std::shared_ptr<connection> conn, bool& push_received)
|
||||
{
|
||||
{
|
||||
auto [ec, ev] = co_await conn->async_receive(ignore, as_tuple(net::use_awaitable));
|
||||
auto [ec, ev] = co_await conn->async_receive(as_tuple(net::use_awaitable));
|
||||
BOOST_TEST(!ec);
|
||||
}
|
||||
|
||||
{
|
||||
auto [ec, ev] = co_await conn->async_receive(ignore, as_tuple(net::use_awaitable));
|
||||
auto [ec, ev] = co_await conn->async_receive(as_tuple(net::use_awaitable));
|
||||
BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled);
|
||||
}
|
||||
|
||||
@@ -190,8 +205,10 @@ BOOST_AUTO_TEST_CASE(test_push_adapter)
|
||||
req.push("SUBSCRIBE", "channel");
|
||||
req.push("PING");
|
||||
|
||||
conn->async_receive(error_tag_obj, [conn](auto ec, auto) {
|
||||
BOOST_CHECK_EQUAL(ec, boost::redis::error::incompatible_size);
|
||||
conn->set_receive_response(error_tag_obj);
|
||||
|
||||
conn->async_receive([&, conn](auto ec, auto) {
|
||||
BOOST_CHECK_EQUAL(ec, boost::asio::experimental::error::channel_cancelled);
|
||||
conn->cancel(operation::reconnection);
|
||||
});
|
||||
|
||||
@@ -199,7 +216,10 @@ BOOST_AUTO_TEST_CASE(test_push_adapter)
|
||||
BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled);
|
||||
});
|
||||
|
||||
run(conn);
|
||||
auto cfg = make_test_config();
|
||||
conn->async_run(cfg, {}, [](auto ec){
|
||||
BOOST_CHECK_EQUAL(ec, boost::redis::error::incompatible_size);
|
||||
});
|
||||
|
||||
ioc.run();
|
||||
|
||||
@@ -210,7 +230,7 @@ BOOST_AUTO_TEST_CASE(test_push_adapter)
|
||||
net::awaitable<void> push_consumer3(std::shared_ptr<connection> conn)
|
||||
{
|
||||
for (;;) {
|
||||
co_await conn->async_receive(ignore, net::use_awaitable);
|
||||
co_await conn->async_receive(net::use_awaitable);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,9 +257,8 @@ BOOST_AUTO_TEST_CASE(many_subscribers)
|
||||
|
||||
auto c11 =[&](auto ec, auto...)
|
||||
{
|
||||
std::cout << "quit sent" << std::endl;
|
||||
std::cout << "quit sent: " << ec.message() << std::endl;
|
||||
conn->cancel(operation::reconnection);
|
||||
BOOST_TEST(!ec);
|
||||
};
|
||||
auto c10 =[&](auto ec, auto...)
|
||||
{
|
||||
@@ -299,7 +318,7 @@ BOOST_AUTO_TEST_CASE(many_subscribers)
|
||||
|
||||
conn->async_exec(req0, ignore, c0);
|
||||
|
||||
run(conn, {}, {});
|
||||
run(conn, make_test_config(), {});
|
||||
|
||||
net::co_spawn(ioc.get_executor(), push_consumer3(conn), net::detached);
|
||||
ioc.run();
|
||||
@@ -18,7 +18,6 @@ using boost::redis::operation;
|
||||
using boost::redis::request;
|
||||
using boost::redis::response;
|
||||
using boost::redis::ignore;
|
||||
using boost::redis::config;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_eof_no_error)
|
||||
@@ -62,7 +61,7 @@ BOOST_AUTO_TEST_CASE(test_async_run_exits)
|
||||
auto c3 = [](auto ec, auto)
|
||||
{
|
||||
std::clog << "c3: " << ec.message() << std::endl;
|
||||
BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled);
|
||||
BOOST_CHECK_EQUAL(ec, boost::asio::error::operation_aborted);
|
||||
};
|
||||
|
||||
auto c2 = [&](auto ec, auto)
|
||||
@@ -83,7 +82,7 @@ BOOST_AUTO_TEST_CASE(test_async_run_exits)
|
||||
|
||||
// The healthy checker should not be the cause of async_run
|
||||
// completing, so we disable.
|
||||
config cfg;
|
||||
auto cfg = make_test_config();
|
||||
cfg.health_check_interval = 0s;
|
||||
cfg.reconnect_wait_interval = 0s;
|
||||
run(conn, cfg);
|
||||
@@ -19,7 +19,6 @@ using boost::system::error_code;
|
||||
using boost::redis::request;
|
||||
using boost::redis::response;
|
||||
using boost::redis::ignore;
|
||||
using boost::redis::config;
|
||||
using boost::redis::logger;
|
||||
using boost::redis::operation;
|
||||
using boost::redis::connection;
|
||||
@@ -40,7 +39,7 @@ net::awaitable<void> test_reconnect_impl()
|
||||
int i = 0;
|
||||
for (; i < 5; ++i) {
|
||||
error_code ec1, ec2;
|
||||
config cfg;
|
||||
auto cfg = make_test_config();
|
||||
logger l;
|
||||
co_await conn->async_exec(req, ignore, net::redirect_error(net::use_awaitable, ec1));
|
||||
//BOOST_TEST(!ec);
|
||||
@@ -76,7 +75,7 @@ auto async_test_reconnect_timeout() -> net::awaitable<void>
|
||||
req1.push("BLPOP", "any", 0);
|
||||
|
||||
st.expires_after(std::chrono::seconds{1});
|
||||
config cfg;
|
||||
auto cfg = make_test_config();
|
||||
co_await (
|
||||
conn->async_exec(req1, ignore, redir(ec1)) ||
|
||||
st.async_wait(redir(ec3))
|
||||
@@ -100,7 +99,7 @@ auto async_test_reconnect_timeout() -> net::awaitable<void>
|
||||
|
||||
std::cout << "ccc" << std::endl;
|
||||
|
||||
BOOST_CHECK_EQUAL(ec1, boost::system::errc::errc_t::operation_canceled);
|
||||
BOOST_CHECK_EQUAL(ec1, boost::asio::error::operation_aborted);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_reconnect_and_idle)
|
||||
@@ -20,7 +20,6 @@
|
||||
namespace net = boost::asio;
|
||||
|
||||
using boost::redis::operation;
|
||||
using boost::redis::config;
|
||||
using boost::redis::connection;
|
||||
using boost::system::error_code;
|
||||
using net::experimental::as_tuple;
|
||||
@@ -41,7 +40,7 @@ auto async_cancel_run_with_timer() -> net::awaitable<void>
|
||||
st.expires_after(1s);
|
||||
|
||||
error_code ec1, ec2;
|
||||
config cfg;
|
||||
auto cfg = make_test_config();
|
||||
logger l;
|
||||
co_await (conn.async_run(cfg, l, redir(ec1)) || st.async_wait(redir(ec2)));
|
||||
|
||||
@@ -67,7 +66,7 @@ async_check_cancellation_not_missed(int n, std::chrono::milliseconds ms) -> net:
|
||||
for (auto i = 0; i < n; ++i) {
|
||||
timer.expires_after(ms);
|
||||
error_code ec1, ec2;
|
||||
config cfg;
|
||||
auto cfg = make_test_config();
|
||||
logger l;
|
||||
co_await (conn.async_run(cfg, l, redir(ec1)) || timer.async_wait(redir(ec2)));
|
||||
BOOST_CHECK_EQUAL(ec1, boost::asio::error::operation_aborted);
|
||||
126
test/test_conn_tls.cpp
Normal file
126
test/test_conn_tls.cpp
Normal file
@@ -0,0 +1,126 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <boost/asio/ssl/host_name_verification.hpp>
|
||||
#include <boost/system/error_code.hpp>
|
||||
#include <boost/redis/connection.hpp>
|
||||
#define BOOST_TEST_MODULE conn-tls
|
||||
#include <boost/test/included/unit_test.hpp>
|
||||
#include "common.hpp"
|
||||
|
||||
namespace net = boost::asio;
|
||||
|
||||
using connection = boost::redis::connection;
|
||||
using boost::redis::request;
|
||||
using boost::redis::response;
|
||||
using boost::redis::config;
|
||||
using boost::system::error_code;
|
||||
|
||||
// CA certificate that signed the test server's certificate.
|
||||
// This is a self-signed CA created for testing purposes.
|
||||
// This must match tools/tls/ca.crt contents
|
||||
static constexpr const char* ca_certificate = R"%(-----BEGIN CERTIFICATE-----
|
||||
MIIFSzCCAzOgAwIBAgIUNd7VUuGK4+ylzCOrmeckg2+TqX8wDQYJKoZIhvcNAQEL
|
||||
BQAwNTETMBEGA1UECgwKUmVkaXMgVGVzdDEeMBwGA1UEAwwVQ2VydGlmaWNhdGUg
|
||||
QXV0aG9yaXR5MB4XDTI0MDMzMTE0MjUyM1oXDTM0MDMyOTE0MjUyM1owNTETMBEG
|
||||
A1UECgwKUmVkaXMgVGVzdDEeMBwGA1UEAwwVQ2VydGlmaWNhdGUgQXV0aG9yaXR5
|
||||
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA5AMV5V66wt+MM4+oCzH0
|
||||
xPi++j23p8AOa0o3dxNd4tm5y++gAdKfoxj7oh32ZuYHA5V+sGNEalN/b3GlKXMm
|
||||
ThdVPSwqOQduny19wrb126ZeQXCfqwgSZQ+rgzaIYpw8/GRRuLDunmsdaR2eiptp
|
||||
dbv6g6P/aIF6P9mfuekwCC9KBCV6ftqOEnzulNLVw4JjY0rKB9NZqONKVMfWpNyC
|
||||
zJLCkGmza7BOpybhloZIxGJz033yCjDvIQr9GUWsA5rU9LdUiL+F1W0pWkIel1qo
|
||||
Evo0EIl3+EOcSSzETI7NPHgnSzNau39ZShV4UBj2lw0DWeNcobeMBQ8ItmqEU6V0
|
||||
gCEqfUnt10bGIDdmV3D5FKPgvhFvEjQULnblLeLDQ6XDFf+xbGEVjvTzVkLjvyKm
|
||||
H2D+SKw2O+eDU/0+xhpAf+QsWlm6pmvKWjXI5wK1rh2yssBK2pmY3LuuZCdGrvXb
|
||||
KX4j/4S9qMr43Hmyoyz0gE5I5rplqot8TvT9O/JsgQYd9fYSvdB+HbqAlJzpBZFl
|
||||
xbVBXxl0AlDFwQtNMX5ylEQPvYVDKA1M+DTqRTgQKctTfccwvovY3YMV7m5YoODZ
|
||||
ya2YSBRfQim6VsC+QPYs7p2dk1larIoMMaTaU02oMY+qT2d/eyhWKBv5W9LuowTQ
|
||||
bWa3ZhWN8lXriPgJOQnZ6iUCAwEAAaNTMFEwHQYDVR0OBBYEFCpEPlClLrgu1zFN
|
||||
Fmas5G4ybNRJMB8GA1UdIwQYMBaAFCpEPlClLrgu1zFNFmas5G4ybNRJMA8GA1Ud
|
||||
EwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIBAFLl1NZHp0NT5Av4GKmsJFeI
|
||||
cJOgcIygjR4SBGDAxyPqVpZk0x1q64gJsfOe1ARyI4olQPqO08FZMeB+VBYuqR3S
|
||||
fEVQZz2FT5U7IVAEZwWHOcWkrrVpEZC6PZktYJ7Yqju6+ic93inoPrHhGNZ5XA/Y
|
||||
GSfwriWkyWm2SOk35ChFH67MbPWmve8CRAXRmrOCByXwXF87wdqVYZUvH9xDe6WU
|
||||
snFWXVHr2NA7Re8ZIGp7yJOwwW+CZagepNCPUDwnI0fWOahtOTzonIjq8bfgTZPx
|
||||
2e7lBuAr9tVMpoeyUytVOlNJDojZAtKOpfMwhAG8ydhk+78aK07VVbnSYVhv7ctU
|
||||
kkkldqP/S3lBlWo44oOxenwLc9vDQNh64py7eQTD7Qv+TjqAG0ljHIDbVqlkQsgR
|
||||
pQsu7keG9O1xASSTLZVZN2/alNewpqE/eFRfPM3mtUiTiIZvSxiQnWQMbKofAZH5
|
||||
HwhVli4RKWRWPqpof4GFNkB8XwfBE+gdlFuWtyg0oRyV3sJ6Zn7E+lUpbQX4CFx3
|
||||
97vekaFNBchNYMcP3TZ9LwxTx1xOWZ5HHrHyzASG3uz2rqwAsEmdRbmK03KfEQyQ
|
||||
YpNY718btZ1D6lLino9VMgzaPhUs79bHC64O4ncl7hRclK9qa3KLQdCG1cbIR7G0
|
||||
2XVYrfsnPHX0CsPDIy7L
|
||||
-----END CERTIFICATE-----)%";
|
||||
|
||||
static config make_tls_config()
|
||||
{
|
||||
config cfg;
|
||||
cfg.use_ssl = true;
|
||||
cfg.addr.host = get_server_hostname();
|
||||
cfg.addr.port = "6380";
|
||||
return cfg;
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(ping_internal_ssl_context)
|
||||
{
|
||||
auto const cfg = make_tls_config();
|
||||
std::string const in = "Kabuf";
|
||||
|
||||
request req;
|
||||
req.push("PING", in);
|
||||
|
||||
response<std::string> resp;
|
||||
|
||||
net::io_context ioc;
|
||||
connection conn{ioc};
|
||||
|
||||
// The custom server uses a certificate signed by a CA
|
||||
// that is not trusted by default - skip verification.
|
||||
conn.next_layer().set_verify_mode(net::ssl::verify_none);
|
||||
|
||||
conn.async_exec(req, resp, [&](error_code ec, auto) {
|
||||
BOOST_TEST(ec == std::error_code());
|
||||
conn.cancel();
|
||||
});
|
||||
|
||||
conn.async_run(cfg, {}, [](auto) { });
|
||||
|
||||
ioc.run();
|
||||
|
||||
BOOST_CHECK_EQUAL(in, std::get<0>(resp).value());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(ping_custom_ssl_context)
|
||||
{
|
||||
auto const cfg = make_tls_config();
|
||||
std::string const in = "Kabuf";
|
||||
|
||||
request req;
|
||||
req.push("PING", in);
|
||||
|
||||
response<std::string> resp;
|
||||
|
||||
net::io_context ioc;
|
||||
net::ssl::context ctx{boost::asio::ssl::context::tls_client};
|
||||
|
||||
// Configure the SSL context to trust the CA that signed the server's certificate.
|
||||
// The test certificate uses "redis" as its common name, regardless of the actual server's hostname
|
||||
ctx.add_certificate_authority(net::const_buffer(ca_certificate, std::strlen(ca_certificate)));
|
||||
ctx.set_verify_mode(net::ssl::verify_peer);
|
||||
ctx.set_verify_callback(net::ssl::host_name_verification("redis"));
|
||||
|
||||
connection conn{ioc, std::move(ctx)};
|
||||
|
||||
conn.async_exec(req, resp, [&](auto ec, auto) {
|
||||
BOOST_TEST(ec == std::error_code());
|
||||
conn.cancel();
|
||||
});
|
||||
|
||||
conn.async_run(cfg, {}, [](auto) { });
|
||||
|
||||
ioc.run();
|
||||
|
||||
BOOST_CHECK_EQUAL(in, std::get<0>(resp).value());
|
||||
}
|
||||
64
test/test_issue_181.cpp
Normal file
64
test/test_issue_181.cpp
Normal file
@@ -0,0 +1,64 @@
|
||||
/* Copyright (c) 2018-2024 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <boost/redis/connection.hpp>
|
||||
#include <boost/redis/logger.hpp>
|
||||
#include <boost/asio/awaitable.hpp>
|
||||
#include <boost/asio/use_awaitable.hpp>
|
||||
#define BOOST_TEST_MODULE conn-quit
|
||||
#include <boost/test/included/unit_test.hpp>
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include "common.hpp"
|
||||
|
||||
namespace net = boost::asio;
|
||||
using boost::redis::request;
|
||||
using boost::redis::request;
|
||||
using boost::redis::response;
|
||||
using boost::redis::ignore;
|
||||
using boost::redis::logger;
|
||||
using boost::redis::config;
|
||||
using boost::redis::operation;
|
||||
using boost::redis::connection;
|
||||
using boost::system::error_code;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
BOOST_AUTO_TEST_CASE(issue_181)
|
||||
{
|
||||
using connection_base = boost::redis::detail::connection_base<net::any_io_executor>;
|
||||
|
||||
auto const level = boost::redis::logger::level::debug;
|
||||
net::io_context ioc;
|
||||
auto ctx = net::ssl::context{net::ssl::context::tlsv12_client};
|
||||
connection_base conn{ioc.get_executor(), std::move(ctx), 1000000};
|
||||
net::steady_timer timer{ioc};
|
||||
timer.expires_after(std::chrono::seconds{1});
|
||||
|
||||
auto run_cont = [&](auto ec){
|
||||
std::cout << "async_run1: " << ec.message() << std::endl;
|
||||
};
|
||||
|
||||
auto cfg = make_test_config();
|
||||
cfg.health_check_interval = std::chrono::seconds{0};
|
||||
cfg.reconnect_wait_interval = std::chrono::seconds{0};
|
||||
conn.async_run(cfg, boost::redis::logger{level}, run_cont);
|
||||
BOOST_TEST(!conn.run_is_canceled());
|
||||
|
||||
// Uses a timer to wait some time until run has been called.
|
||||
auto timer_cont = [&](auto ec){
|
||||
std::cout << "timer_cont: " << ec.message() << std::endl;
|
||||
BOOST_TEST(!conn.run_is_canceled());
|
||||
conn.cancel(operation::run);
|
||||
BOOST_TEST(conn.run_is_canceled());
|
||||
};
|
||||
|
||||
timer.async_wait(timer_cont);
|
||||
|
||||
ioc.run();
|
||||
}
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <boost/test/included/unit_test.hpp>
|
||||
#include <tuple>
|
||||
#include <iostream>
|
||||
#include "common.hpp"
|
||||
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
|
||||
@@ -47,11 +48,15 @@ receiver(std::shared_ptr<connection> conn) -> net::awaitable<void>
|
||||
for (;;) {
|
||||
std::cout << "aaaa" << std::endl;
|
||||
error_code ec;
|
||||
co_await conn->async_receive(ignore, redirect_error(use_awaitable, ec));
|
||||
if (ec)
|
||||
co_await conn->async_receive(redirect_error(use_awaitable, ec));
|
||||
if (ec) {
|
||||
std::cout << "Error in async_receive" << std::endl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "Exiting the receiver." << std::endl;
|
||||
}
|
||||
|
||||
auto
|
||||
@@ -82,13 +87,14 @@ periodic_task(std::shared_ptr<connection> conn) -> net::awaitable<void>
|
||||
conn->cancel(operation::reconnection);
|
||||
}
|
||||
|
||||
auto co_main(config cfg) -> net::awaitable<void>
|
||||
auto co_main(config) -> net::awaitable<void>
|
||||
{
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
auto conn = std::make_shared<connection>(ex);
|
||||
|
||||
net::co_spawn(ex, receiver(conn), net::detached);
|
||||
net::co_spawn(ex, periodic_task(conn), net::detached);
|
||||
auto cfg = make_test_config();
|
||||
conn->async_run(cfg, {}, net::consign(net::detached, conn));
|
||||
}
|
||||
|
||||
@@ -7,16 +7,11 @@
|
||||
#include <boost/redis/request.hpp>
|
||||
#include <boost/redis/response.hpp>
|
||||
#include <boost/redis/adapter/adapt.hpp>
|
||||
#include <boost/redis/detail/read.hpp>
|
||||
#include <boost/system/errc.hpp>
|
||||
#include <boost/asio/awaitable.hpp>
|
||||
#include <boost/asio/redirect_error.hpp>
|
||||
#include <boost/asio/use_awaitable.hpp>
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <boost/asio/co_spawn.hpp>
|
||||
#include <boost/beast/_experimental/test/stream.hpp>
|
||||
#include <boost/redis/resp3/parser.hpp>
|
||||
|
||||
#define BOOST_TEST_MODULE low level
|
||||
#include <boost/test/included/unit_test.hpp>
|
||||
|
||||
#include <map>
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
@@ -30,19 +25,22 @@ auto operator==(boost::redis::ignore_t, boost::redis::ignore_t) noexcept {return
|
||||
auto operator!=(boost::redis::ignore_t, boost::redis::ignore_t) noexcept {return false;}
|
||||
}
|
||||
|
||||
namespace net = boost::asio;
|
||||
namespace redis = boost::redis;
|
||||
namespace resp3 = boost::redis::resp3;
|
||||
using boost::system::error_code;
|
||||
using boost::redis::request;
|
||||
using boost::redis::response;
|
||||
using boost::redis::generic_response;
|
||||
using boost::redis::ignore;
|
||||
using boost::redis::ignore_t;
|
||||
using boost::redis::adapter::result;
|
||||
using boost::redis::resp3::parser;
|
||||
using boost::redis::resp3::parse;
|
||||
using boost::redis::consume_one;
|
||||
using boost::redis::error;
|
||||
|
||||
using test_stream = boost::beast::test::stream;
|
||||
using boost::redis::adapter::adapt2;
|
||||
using node_type = result<resp3::node>;
|
||||
using vec_node_type = result<std::vector<resp3::node>>;
|
||||
using vec_type = result<std::vector<std::string>>;
|
||||
using op_vec_type = result<std::optional<std::vector<std::string>>>;
|
||||
|
||||
@@ -82,95 +80,51 @@ template <class Result>
|
||||
struct expect {
|
||||
std::string in;
|
||||
Result expected;
|
||||
boost::system::error_code ec{};
|
||||
error_code ec{};
|
||||
resp3::type error_type = resp3::type::invalid;
|
||||
};
|
||||
|
||||
template <class Result>
|
||||
auto make_expected(std::string in, Result expected, boost::system::error_code ec = {}, resp3::type error_type = resp3::type::invalid)
|
||||
auto make_expected(std::string in, Result expected, error_code ec = {}, resp3::type error_type = resp3::type::invalid)
|
||||
{
|
||||
return expect<Result>{in, expected, ec, error_type};
|
||||
}
|
||||
|
||||
template <class Result>
|
||||
void test_sync(net::any_io_executor ex, expect<Result> e)
|
||||
void test_sync(expect<Result> e)
|
||||
{
|
||||
std::string rbuffer;
|
||||
test_stream ts {ex};
|
||||
ts.append(e.in);
|
||||
parser p;
|
||||
Result result;
|
||||
boost::system::error_code ec;
|
||||
auto dbuf = net::dynamic_buffer(rbuffer);
|
||||
auto const consumed = redis::detail::read(ts, dbuf, adapt2(result), ec);
|
||||
if (e.ec) {
|
||||
auto adapter = adapt2(result);
|
||||
error_code ec;
|
||||
auto const res = parse(p, e.in, adapter, ec);
|
||||
|
||||
BOOST_TEST(res); // None of these tests need more data.
|
||||
|
||||
if (ec) {
|
||||
BOOST_CHECK_EQUAL(ec, e.ec);
|
||||
return;
|
||||
}
|
||||
|
||||
dbuf.consume(consumed);
|
||||
|
||||
BOOST_TEST(!ec);
|
||||
BOOST_TEST(rbuffer.empty());
|
||||
|
||||
if (result.has_value()) {
|
||||
auto const res = result == e.expected;
|
||||
BOOST_TEST(res);
|
||||
BOOST_TEST(bool(result == e.expected));
|
||||
BOOST_CHECK_EQUAL(e.in.size(), p.get_consumed());
|
||||
} else {
|
||||
BOOST_TEST(result.has_error());
|
||||
BOOST_CHECK_EQUAL(result.error().data_type, e.error_type);
|
||||
}
|
||||
}
|
||||
|
||||
template <class Result>
|
||||
class async_test: public std::enable_shared_from_this<async_test<Result>> {
|
||||
private:
|
||||
std::string rbuffer_;
|
||||
test_stream ts_;
|
||||
expect<Result> data_;
|
||||
Result result_;
|
||||
|
||||
public:
|
||||
async_test(net::any_io_executor ex, expect<Result> e)
|
||||
: ts_{ex}
|
||||
, data_{e}
|
||||
{
|
||||
ts_.append(e.in);
|
||||
}
|
||||
|
||||
void run()
|
||||
{
|
||||
auto self = this->shared_from_this();
|
||||
auto f = [self](auto ec, auto)
|
||||
{
|
||||
if (self->data_.ec) {
|
||||
BOOST_CHECK_EQUAL(ec, self->data_.ec);
|
||||
return;
|
||||
}
|
||||
|
||||
BOOST_TEST(!ec);
|
||||
//BOOST_TEST(self->rbuffer_.empty());
|
||||
|
||||
if (self->result_.has_value()) {
|
||||
auto const res = self->result_ == self->data_.expected;
|
||||
BOOST_TEST(res);
|
||||
} else {
|
||||
BOOST_TEST(self->result_.has_error());
|
||||
BOOST_CHECK_EQUAL(self->result_.error().data_type, self->data_.error_type);
|
||||
}
|
||||
};
|
||||
|
||||
redis::detail::async_read(
|
||||
ts_,
|
||||
net::dynamic_buffer(rbuffer_),
|
||||
adapt2(result_),
|
||||
f);
|
||||
}
|
||||
};
|
||||
|
||||
template <class Result>
|
||||
void test_async(net::any_io_executor ex, expect<Result> e)
|
||||
void test_sync2(expect<Result> e)
|
||||
{
|
||||
std::make_shared<async_test<Result>>(ex, e)->run();
|
||||
parser p;
|
||||
Result result;
|
||||
auto adapter = adapt2(result);
|
||||
error_code ec;
|
||||
auto const res = parse(p, e.in, adapter, ec);
|
||||
|
||||
BOOST_TEST(res); // None of these tests need more data.
|
||||
BOOST_CHECK_EQUAL(ec, e.ec);
|
||||
}
|
||||
|
||||
auto make_blob()
|
||||
@@ -202,7 +156,7 @@ result<std::optional<bool>> op_bool_ok = true;
|
||||
|
||||
// TODO: Test a streamed string that is not finished with a string of
|
||||
// size 0 but other command comes in.
|
||||
vec_node_type streamed_string_e1
|
||||
generic_response streamed_string_e1
|
||||
{{ {boost::redis::resp3::type::streamed_string, 0, 1, ""}
|
||||
, {boost::redis::resp3::type::streamed_string_part, 1, 1, "Hell"}
|
||||
, {boost::redis::resp3::type::streamed_string_part, 1, 1, "o wor"}
|
||||
@@ -210,10 +164,10 @@ vec_node_type streamed_string_e1
|
||||
, {boost::redis::resp3::type::streamed_string_part, 1, 1, ""}
|
||||
}};
|
||||
|
||||
vec_node_type streamed_string_e2
|
||||
generic_response streamed_string_e2
|
||||
{{{resp3::type::streamed_string, 0UL, 1UL, {}}, {resp3::type::streamed_string_part, 1UL, 1UL, {}} }};
|
||||
|
||||
vec_node_type const push_e1a
|
||||
generic_response const push_e1a
|
||||
{{ {resp3::type::push, 4UL, 0UL, {}}
|
||||
, {resp3::type::simple_string, 1UL, 1UL, "pubsub"}
|
||||
, {resp3::type::simple_string, 1UL, 1UL, "message"}
|
||||
@@ -221,10 +175,10 @@ vec_node_type const push_e1a
|
||||
, {resp3::type::simple_string, 1UL, 1UL, "some message"}
|
||||
}};
|
||||
|
||||
vec_node_type const push_e1b
|
||||
generic_response const push_e1b
|
||||
{{{resp3::type::push, 0UL, 0UL, {}}}};
|
||||
|
||||
vec_node_type const set_expected1a
|
||||
generic_response const set_expected1a
|
||||
{{{resp3::type::set, 6UL, 0UL, {}}
|
||||
, {resp3::type::simple_string, 1UL, 1UL, {"orange"}}
|
||||
, {resp3::type::simple_string, 1UL, 1UL, {"apple"}}
|
||||
@@ -240,7 +194,7 @@ muset_type const set_e1g{{"apple", "one", "orange", "orange", "three", "two"}};
|
||||
vec_type const set_e1d = {{"orange", "apple", "one", "two", "three", "orange"}};
|
||||
op_vec_type const set_expected_1e = set_e1d;
|
||||
|
||||
vec_node_type const array_e1a
|
||||
generic_response const array_e1a
|
||||
{{ {resp3::type::array, 3UL, 0UL, {}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"11"}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"22"}}
|
||||
@@ -250,12 +204,12 @@ vec_node_type const array_e1a
|
||||
result<std::vector<int>> const array_e1b{{11, 22, 3}};
|
||||
result<std::vector<std::string>> const array_e1c{{"11", "22", "3"}};
|
||||
result<std::vector<std::string>> const array_e1d{};
|
||||
vec_node_type const array_e1e{{{resp3::type::array, 0UL, 0UL, {}}}};
|
||||
generic_response const array_e1e{{{resp3::type::array, 0UL, 0UL, {}}}};
|
||||
array_type const array_e1f{{11, 22, 3}};
|
||||
result<std::list<int>> const array_e1g{{11, 22, 3}};
|
||||
result<std::deque<int>> const array_e1h{{11, 22, 3}};
|
||||
|
||||
vec_node_type const map_expected_1a
|
||||
generic_response const map_expected_1a
|
||||
{{ {resp3::type::map, 4UL, 0UL, {}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"key1"}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"value1"}}
|
||||
@@ -311,7 +265,7 @@ tuple8_type const map_e1f
|
||||
, std::string{"key3"}, std::string{"value3"}
|
||||
};
|
||||
|
||||
vec_node_type const attr_e1a
|
||||
generic_response const attr_e1a
|
||||
{{ {resp3::type::attribute, 1UL, 0UL, {}}
|
||||
, {resp3::type::simple_string, 1UL, 1UL, "key-popularity"}
|
||||
, {resp3::type::map, 2UL, 1UL, {}}
|
||||
@@ -321,7 +275,7 @@ vec_node_type const attr_e1a
|
||||
, {resp3::type::doublean, 1UL, 2UL, "0.0012"}
|
||||
} };
|
||||
|
||||
vec_node_type const attr_e1b
|
||||
generic_response const attr_e1b
|
||||
{{{resp3::type::attribute, 0UL, 0UL, {}} }};
|
||||
|
||||
#define S01a "#11\r\n"
|
||||
@@ -397,173 +351,136 @@ vec_node_type const attr_e1b
|
||||
#define S18d "$0\r\n\r\n"
|
||||
|
||||
#define NUMBER_TEST_CONDITIONS(test) \
|
||||
test(ex, make_expected(S01a, result<std::optional<bool>>{}, boost::redis::error::unexpected_bool_value)); \
|
||||
test(ex, make_expected(S01b, result<bool>{{false}})); \
|
||||
test(ex, make_expected(S01b, node_type{{resp3::type::boolean, 1UL, 0UL, {"f"}}})); \
|
||||
test(ex, make_expected(S01c, result<bool>{{true}})); \
|
||||
test(ex, make_expected(S01c, node_type{{resp3::type::boolean, 1UL, 0UL, {"t"}}})); \
|
||||
test(ex, make_expected(S01c, op_bool_ok)); \
|
||||
test(ex, make_expected(S01c, result<std::map<int, int>>{}, boost::redis::error::expects_resp3_map)); \
|
||||
test(ex, make_expected(S01c, result<std::set<int>>{}, boost::redis::error::expects_resp3_set)); \
|
||||
test(ex, make_expected(S01c, result<std::unordered_map<int, int>>{}, boost::redis::error::expects_resp3_map)); \
|
||||
test(ex, make_expected(S01c, result<std::unordered_set<int>>{}, boost::redis::error::expects_resp3_set)); \
|
||||
test(ex, make_expected(S02a, streamed_string_e2)); \
|
||||
test(ex, make_expected(S03a, result<int>{}, boost::redis::error::expects_resp3_simple_type));\
|
||||
test(ex, make_expected(S03a, result<std::optional<int>>{}, boost::redis::error::expects_resp3_simple_type));; \
|
||||
test(ex, make_expected(S02b, result<int>{}, boost::redis::error::not_a_number)); \
|
||||
test(ex, make_expected(S02b, result<std::string>{std::string{"Hello word"}})); \
|
||||
test(ex, make_expected(S02b, streamed_string_e1)); \
|
||||
test(ex, make_expected(S02c, result<std::string>{}, boost::redis::error::not_a_number)); \
|
||||
test(ex, make_expected(S05a, node_type{{resp3::type::number, 1UL, 0UL, {"-3"}}})); \
|
||||
test(ex, make_expected(S05b, result<int>{11})); \
|
||||
test(ex, make_expected(S05b, op_int_ok)); \
|
||||
test(ex, make_expected(S05b, result<std::list<std::string>>{}, boost::redis::error::expects_resp3_aggregate)); \
|
||||
test(ex, make_expected(S05b, result<std::map<std::string, std::string>>{}, boost::redis::error::expects_resp3_map)); \
|
||||
test(ex, make_expected(S05b, result<std::set<std::string>>{}, boost::redis::error::expects_resp3_set)); \
|
||||
test(ex, make_expected(S05b, result<std::unordered_map<std::string, std::string>>{}, boost::redis::error::expects_resp3_map)); \
|
||||
test(ex, make_expected(S05b, result<std::unordered_set<std::string>>{}, boost::redis::error::expects_resp3_set)); \
|
||||
test(ex, make_expected(s05c, array_type2{}, boost::redis::error::expects_resp3_aggregate));\
|
||||
test(ex, make_expected(s05c, node_type{{resp3::type::number, 1UL, 0UL, {"3"}}}));\
|
||||
test(ex, make_expected(S06a, op_type_01{})); \
|
||||
test(ex, make_expected(S06a, op_type_02{}));\
|
||||
test(ex, make_expected(S06a, op_type_03{}));\
|
||||
test(ex, make_expected(S06a, op_type_04{}));\
|
||||
test(ex, make_expected(S06a, op_type_05{}));\
|
||||
test(ex, make_expected(S06a, op_type_06{}));\
|
||||
test(ex, make_expected(S06a, op_type_07{}));\
|
||||
test(ex, make_expected(S06a, op_type_08{}));\
|
||||
test(ex, make_expected(S06a, op_type_09{}));\
|
||||
test(ex, make_expected(S07a, push_e1a)); \
|
||||
test(ex, make_expected(S07b, push_e1b)); \
|
||||
test(ex, make_expected(S04b, map_type{}, boost::redis::error::expects_resp3_map));\
|
||||
test(ex, make_expected(S03b, map_e1f));\
|
||||
test(ex, make_expected(S03b, map_e1g));\
|
||||
test(ex, make_expected(S03b, map_e1k));\
|
||||
test(ex, make_expected(S03b, map_expected_1a));\
|
||||
test(ex, make_expected(S03b, map_expected_1b));\
|
||||
test(ex, make_expected(S03b, map_expected_1c));\
|
||||
test(ex, make_expected(S03b, map_expected_1d));\
|
||||
test(ex, make_expected(S03b, map_expected_1e));\
|
||||
test(ex, make_expected(S08a, attr_e1a)); \
|
||||
test(ex, make_expected(S08b, attr_e1b)); \
|
||||
test(ex, make_expected(S04e, array_e1a));\
|
||||
test(ex, make_expected(S04e, array_e1b));\
|
||||
test(ex, make_expected(S04e, array_e1c));\
|
||||
test(ex, make_expected(S04e, array_e1f));\
|
||||
test(ex, make_expected(S04e, array_e1g));\
|
||||
test(ex, make_expected(S04e, array_e1h));\
|
||||
test(ex, make_expected(S04e, array_type2{}, boost::redis::error::incompatible_size));\
|
||||
test(ex, make_expected(S04e, tuple_int_2{}, boost::redis::error::incompatible_size));\
|
||||
test(ex, make_expected(S04f, array_type2{}, boost::redis::error::nested_aggregate_not_supported));\
|
||||
test(ex, make_expected(S04g, vec_node_type{}, boost::redis::error::exceeeds_max_nested_depth));\
|
||||
test(ex, make_expected(S04h, array_e1d));\
|
||||
test(ex, make_expected(S04h, array_e1e));\
|
||||
test(ex, make_expected(S04i, set_type{}, boost::redis::error::expects_resp3_set)); \
|
||||
test(ex, make_expected(S09a, set_e1c)); \
|
||||
test(ex, make_expected(S09a, set_e1d)); \
|
||||
test(ex, make_expected(S09a, set_e1f)); \
|
||||
test(ex, make_expected(S09a, set_e1g)); \
|
||||
test(ex, make_expected(S09a, set_expected1a)); \
|
||||
test(ex, make_expected(S09a, set_expected_1e)); \
|
||||
test(ex, make_expected(S09a, set_type{{"apple", "one", "orange", "three", "two"}})); \
|
||||
test(ex, make_expected(S09b, vec_node_type{{{resp3::type::set, 0UL, 0UL, {}}}})); \
|
||||
test(ex, make_expected(S03c, map_type{}));\
|
||||
test(ex, make_expected(S11a, node_type{{resp3::type::doublean, 1UL, 0UL, {"1.23"}}}));\
|
||||
test(ex, make_expected(S11b, node_type{{resp3::type::doublean, 1UL, 0UL, {"inf"}}}));\
|
||||
test(ex, make_expected(S11c, node_type{{resp3::type::doublean, 1UL, 0UL, {"-inf"}}}));\
|
||||
test(ex, make_expected(S11d, result<double>{{1.23}}));\
|
||||
test(ex, make_expected(S11e, result<double>{{0}}, boost::redis::error::not_a_double));\
|
||||
test(ex, make_expected(S13a, node_type{{resp3::type::verbatim_string, 1UL, 0UL, {"txt:Some string"}}}));\
|
||||
test(ex, make_expected(S13b, node_type{{resp3::type::verbatim_string, 1UL, 0UL, {}}}));\
|
||||
test(ex, make_expected(S14a, node_type{{resp3::type::big_number, 1UL, 0UL, {"3492890328409238509324850943850943825024385"}}}));\
|
||||
test(ex, make_expected(S14b, result<int>{}, boost::redis::error::empty_field));\
|
||||
test(ex, make_expected(S15a, result<std::optional<std::string>>{{"OK"}}));\
|
||||
test(ex, make_expected(S15a, result<std::string>{{"OK"}}));\
|
||||
test(ex, make_expected(S15b, result<std::optional<std::string>>{""}));\
|
||||
test(ex, make_expected(S15b, result<std::string>{{""}}));\
|
||||
test(ex, make_expected(S16a, result<int>{}, boost::redis::error::invalid_data_type));\
|
||||
test(ex, make_expected(S05d, result<int>{11}, boost::redis::error::not_a_number));\
|
||||
test(ex, make_expected(S03d, map_type{}, boost::redis::error::not_a_number));\
|
||||
test(ex, make_expected(S02d, result<std::string>{}, boost::redis::error::not_a_number));\
|
||||
test(ex, make_expected(S17a, result<std::string>{}, boost::redis::error::not_a_number));\
|
||||
test(ex, make_expected(S05e, result<int>{}, boost::redis::error::empty_field));\
|
||||
test(ex, make_expected(S01d, result<std::optional<bool>>{}, boost::redis::error::empty_field));\
|
||||
test(ex, make_expected(S11f, result<std::string>{}, boost::redis::error::empty_field));\
|
||||
test(ex, make_expected(S17b, node_type{{resp3::type::blob_string, 1UL, 0UL, {"hh"}}}));\
|
||||
test(ex, make_expected(S18c, node_type{{resp3::type::blob_string, 1UL, 0UL, {"hhaa\aaaa\raaaaa\r\naaaaaaaaaa"}}}));\
|
||||
test(ex, make_expected(S18d, node_type{{resp3::type::blob_string, 1UL, 0UL, {}}}));\
|
||||
test(ex, make_expected(make_blob_string(blob), node_type{{resp3::type::blob_string, 1UL, 0UL, {blob}}}));\
|
||||
test(ex, make_expected(S04a, result<std::vector<int>>{{11}})); \
|
||||
test(ex, make_expected(S04d, result<response<std::unordered_set<std::string>>>{response<std::unordered_set<std::string>>{{set_e1c}}})); \
|
||||
test(ex, make_expected(S04c, result<response<std::map<std::string, std::string>>>{response<std::map<std::string, std::string>>{{map_expected_1b}}}));\
|
||||
test(ex, make_expected(S03b, map_e1l));\
|
||||
test(ex, make_expected(S06a, result<int>{0}, {}, resp3::type::null)); \
|
||||
test(ex, make_expected(S06a, map_type{}, {}, resp3::type::null));\
|
||||
test(ex, make_expected(S06a, array_type{}, {}, resp3::type::null));\
|
||||
test(ex, make_expected(S06a, result<std::list<int>>{}, {}, resp3::type::null));\
|
||||
test(ex, make_expected(S06a, result<std::vector<int>>{}, {}, resp3::type::null));\
|
||||
test(ex, make_expected(S10a, result<ignore_t>{}, boost::redis::error::resp3_simple_error)); \
|
||||
test(ex, make_expected(S10a, node_type{{resp3::type::simple_error, 1UL, 0UL, {"Error"}}}, {}, resp3::type::simple_error)); \
|
||||
test(ex, make_expected(S10b, node_type{{resp3::type::simple_error, 1UL, 0UL, {""}}}, {}, resp3::type::simple_error)); \
|
||||
test(ex, make_expected(S12a, node_type{{resp3::type::blob_error, 1UL, 0UL, {"SYNTAX invalid syntax"}}}, {}, resp3::type::blob_error));\
|
||||
test(ex, make_expected(S12b, node_type{{resp3::type::blob_error, 1UL, 0UL, {}}}, {}, resp3::type::blob_error));\
|
||||
test(ex, make_expected(S12c, result<ignore_t>{}, boost::redis::error::resp3_blob_error));\
|
||||
test(make_expected(S01a, result<std::optional<bool>>{}, boost::redis::error::unexpected_bool_value)); \
|
||||
test(make_expected(S01b, result<bool>{{false}})); \
|
||||
test(make_expected(S01b, node_type{{resp3::type::boolean, 1UL, 0UL, {"f"}}})); \
|
||||
test(make_expected(S01c, result<bool>{{true}})); \
|
||||
test(make_expected(S01c, node_type{{resp3::type::boolean, 1UL, 0UL, {"t"}}})); \
|
||||
test(make_expected(S01c, op_bool_ok)); \
|
||||
test(make_expected(S01c, result<std::map<int, int>>{}, boost::redis::error::expects_resp3_map)); \
|
||||
test(make_expected(S01c, result<std::set<int>>{}, boost::redis::error::expects_resp3_set)); \
|
||||
test(make_expected(S01c, result<std::unordered_map<int, int>>{}, boost::redis::error::expects_resp3_map)); \
|
||||
test(make_expected(S01c, result<std::unordered_set<int>>{}, boost::redis::error::expects_resp3_set)); \
|
||||
test(make_expected(S02a, streamed_string_e2)); \
|
||||
test(make_expected(S03a, result<int>{}, boost::redis::error::expects_resp3_simple_type));\
|
||||
test(make_expected(S03a, result<std::optional<int>>{}, boost::redis::error::expects_resp3_simple_type));; \
|
||||
test(make_expected(S02b, result<int>{}, boost::redis::error::not_a_number)); \
|
||||
test(make_expected(S02b, result<std::string>{std::string{"Hello word"}})); \
|
||||
test(make_expected(S02b, streamed_string_e1)); \
|
||||
test(make_expected(S02c, result<std::string>{}, boost::redis::error::not_a_number)); \
|
||||
test(make_expected(S05a, node_type{{resp3::type::number, 1UL, 0UL, {"-3"}}})); \
|
||||
test(make_expected(S05b, result<int>{11})); \
|
||||
test(make_expected(S05b, op_int_ok)); \
|
||||
test(make_expected(S05b, result<std::list<std::string>>{}, boost::redis::error::expects_resp3_aggregate)); \
|
||||
test(make_expected(S05b, result<std::map<std::string, std::string>>{}, boost::redis::error::expects_resp3_map)); \
|
||||
test(make_expected(S05b, result<std::set<std::string>>{}, boost::redis::error::expects_resp3_set)); \
|
||||
test(make_expected(S05b, result<std::unordered_map<std::string, std::string>>{}, boost::redis::error::expects_resp3_map)); \
|
||||
test(make_expected(S05b, result<std::unordered_set<std::string>>{}, boost::redis::error::expects_resp3_set)); \
|
||||
test(make_expected(s05c, array_type2{}, boost::redis::error::expects_resp3_aggregate));\
|
||||
test(make_expected(s05c, node_type{{resp3::type::number, 1UL, 0UL, {"3"}}}));\
|
||||
test(make_expected(S06a, op_type_01{})); \
|
||||
test(make_expected(S06a, op_type_02{}));\
|
||||
test(make_expected(S06a, op_type_03{}));\
|
||||
test(make_expected(S06a, op_type_04{}));\
|
||||
test(make_expected(S06a, op_type_05{}));\
|
||||
test(make_expected(S06a, op_type_06{}));\
|
||||
test(make_expected(S06a, op_type_07{}));\
|
||||
test(make_expected(S06a, op_type_08{}));\
|
||||
test(make_expected(S06a, op_type_09{}));\
|
||||
test(make_expected(S07a, push_e1a)); \
|
||||
test(make_expected(S07b, push_e1b)); \
|
||||
test(make_expected(S04b, map_type{}, boost::redis::error::expects_resp3_map));\
|
||||
test(make_expected(S03b, map_e1f));\
|
||||
test(make_expected(S03b, map_e1g));\
|
||||
test(make_expected(S03b, map_e1k));\
|
||||
test(make_expected(S03b, map_expected_1a));\
|
||||
test(make_expected(S03b, map_expected_1b));\
|
||||
test(make_expected(S03b, map_expected_1c));\
|
||||
test(make_expected(S03b, map_expected_1d));\
|
||||
test(make_expected(S03b, map_expected_1e));\
|
||||
test(make_expected(S08a, attr_e1a)); \
|
||||
test(make_expected(S08b, attr_e1b)); \
|
||||
test(make_expected(S04e, array_e1a));\
|
||||
test(make_expected(S04e, array_e1b));\
|
||||
test(make_expected(S04e, array_e1c));\
|
||||
test(make_expected(S04e, array_e1f));\
|
||||
test(make_expected(S04e, array_e1g));\
|
||||
test(make_expected(S04e, array_e1h));\
|
||||
test(make_expected(S04e, array_type2{}, boost::redis::error::incompatible_size));\
|
||||
test(make_expected(S04e, tuple_int_2{}, boost::redis::error::incompatible_size));\
|
||||
test(make_expected(S04f, array_type2{}, boost::redis::error::nested_aggregate_not_supported));\
|
||||
test(make_expected(S04g, generic_response{}, boost::redis::error::exceeeds_max_nested_depth));\
|
||||
test(make_expected(S04h, array_e1d));\
|
||||
test(make_expected(S04h, array_e1e));\
|
||||
test(make_expected(S04i, set_type{}, boost::redis::error::expects_resp3_set)); \
|
||||
test(make_expected(S09a, set_e1c)); \
|
||||
test(make_expected(S09a, set_e1d)); \
|
||||
test(make_expected(S09a, set_e1f)); \
|
||||
test(make_expected(S09a, set_e1g)); \
|
||||
test(make_expected(S09a, set_expected1a)); \
|
||||
test(make_expected(S09a, set_expected_1e)); \
|
||||
test(make_expected(S09a, set_type{{"apple", "one", "orange", "three", "two"}})); \
|
||||
test(make_expected(S09b, generic_response{{{resp3::type::set, 0UL, 0UL, {}}}})); \
|
||||
test(make_expected(S03c, map_type{}));\
|
||||
test(make_expected(S11a, node_type{{resp3::type::doublean, 1UL, 0UL, {"1.23"}}}));\
|
||||
test(make_expected(S11b, node_type{{resp3::type::doublean, 1UL, 0UL, {"inf"}}}));\
|
||||
test(make_expected(S11c, node_type{{resp3::type::doublean, 1UL, 0UL, {"-inf"}}}));\
|
||||
test(make_expected(S11d, result<double>{{1.23}}));\
|
||||
test(make_expected(S11e, result<double>{{0}}, boost::redis::error::not_a_double));\
|
||||
test(make_expected(S13a, node_type{{resp3::type::verbatim_string, 1UL, 0UL, {"txt:Some string"}}}));\
|
||||
test(make_expected(S13b, node_type{{resp3::type::verbatim_string, 1UL, 0UL, {}}}));\
|
||||
test(make_expected(S14a, node_type{{resp3::type::big_number, 1UL, 0UL, {"3492890328409238509324850943850943825024385"}}}));\
|
||||
test(make_expected(S14b, result<int>{}, boost::redis::error::empty_field));\
|
||||
test(make_expected(S15a, result<std::optional<std::string>>{{"OK"}}));\
|
||||
test(make_expected(S15a, result<std::string>{{"OK"}}));\
|
||||
test(make_expected(S15b, result<std::optional<std::string>>{""}));\
|
||||
test(make_expected(S15b, result<std::string>{{""}}));\
|
||||
test(make_expected(S16a, result<int>{}, boost::redis::error::invalid_data_type));\
|
||||
test(make_expected(S05d, result<int>{11}, boost::redis::error::not_a_number));\
|
||||
test(make_expected(S03d, map_type{}, boost::redis::error::not_a_number));\
|
||||
test(make_expected(S02d, result<std::string>{}, boost::redis::error::not_a_number));\
|
||||
test(make_expected(S17a, result<std::string>{}, boost::redis::error::not_a_number));\
|
||||
test(make_expected(S05e, result<int>{}, boost::redis::error::empty_field));\
|
||||
test(make_expected(S01d, result<std::optional<bool>>{}, boost::redis::error::empty_field));\
|
||||
test(make_expected(S11f, result<std::string>{}, boost::redis::error::empty_field));\
|
||||
test(make_expected(S17b, node_type{{resp3::type::blob_string, 1UL, 0UL, {"hh"}}}));\
|
||||
test(make_expected(S18c, node_type{{resp3::type::blob_string, 1UL, 0UL, {"hhaa\aaaa\raaaaa\r\naaaaaaaaaa"}}}));\
|
||||
test(make_expected(S18d, node_type{{resp3::type::blob_string, 1UL, 0UL, {}}}));\
|
||||
test(make_expected(make_blob_string(blob), node_type{{resp3::type::blob_string, 1UL, 0UL, {blob}}}));\
|
||||
test(make_expected(S04a, result<std::vector<int>>{{11}})); \
|
||||
test(make_expected(S04d, result<response<std::unordered_set<std::string>>>{response<std::unordered_set<std::string>>{{set_e1c}}})); \
|
||||
test(make_expected(S04c, result<response<std::map<std::string, std::string>>>{response<std::map<std::string, std::string>>{{map_expected_1b}}}));\
|
||||
test(make_expected(S03b, map_e1l));\
|
||||
test(make_expected(S06a, result<int>{0}, {}, resp3::type::null)); \
|
||||
test(make_expected(S06a, map_type{}, {}, resp3::type::null));\
|
||||
test(make_expected(S06a, array_type{}, {}, resp3::type::null));\
|
||||
test(make_expected(S06a, result<std::list<int>>{}, {}, resp3::type::null));\
|
||||
test(make_expected(S06a, result<std::vector<int>>{}, {}, resp3::type::null));\
|
||||
test(make_expected(S10a, result<ignore_t>{}, boost::redis::error::resp3_simple_error)); \
|
||||
test(make_expected(S10a, node_type{{resp3::type::simple_error, 1UL, 0UL, {"Error"}}}, {}, resp3::type::simple_error)); \
|
||||
test(make_expected(S10b, node_type{{resp3::type::simple_error, 1UL, 0UL, {""}}}, {}, resp3::type::simple_error)); \
|
||||
test(make_expected(S12a, node_type{{resp3::type::blob_error, 1UL, 0UL, {"SYNTAX invalid syntax"}}}, {}, resp3::type::blob_error));\
|
||||
test(make_expected(S12b, node_type{{resp3::type::blob_error, 1UL, 0UL, {}}}, {}, resp3::type::blob_error));\
|
||||
test(make_expected(S12c, result<ignore_t>{}, boost::redis::error::resp3_blob_error));\
|
||||
|
||||
BOOST_AUTO_TEST_CASE(parser)
|
||||
BOOST_AUTO_TEST_CASE(sansio)
|
||||
{
|
||||
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();
|
||||
NUMBER_TEST_CONDITIONS(test_sync)
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(ignore_adapter_simple_error)
|
||||
{
|
||||
net::io_context ioc;
|
||||
std::string rbuffer;
|
||||
|
||||
boost::system::error_code ec;
|
||||
|
||||
test_stream ts {ioc};
|
||||
ts.append(S10a);
|
||||
redis::detail::read(ts, net::dynamic_buffer(rbuffer), adapt2(ignore), ec);
|
||||
BOOST_CHECK_EQUAL(ec, boost::redis::error::resp3_simple_error);
|
||||
BOOST_TEST(!rbuffer.empty());
|
||||
test_sync2(make_expected(S10a, ignore, boost::redis::error::resp3_simple_error));
|
||||
}
|
||||
|
||||
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(S12a);
|
||||
redis::detail::read(ts, net::dynamic_buffer(rbuffer), adapt2(ignore), ec);
|
||||
BOOST_CHECK_EQUAL(ec, boost::redis::error::resp3_blob_error);
|
||||
BOOST_TEST(!rbuffer.empty());
|
||||
test_sync2(make_expected(S12a, ignore, boost::redis::error::resp3_blob_error));
|
||||
}
|
||||
|
||||
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(S05b);
|
||||
auto const consumed = redis::detail::read(ts, net::dynamic_buffer(rbuffer), adapt2(ignore), ec);
|
||||
BOOST_TEST(!ec);
|
||||
BOOST_CHECK_EQUAL(rbuffer.size(), consumed);
|
||||
test_sync2(make_expected(S05b, ignore));
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------------
|
||||
@@ -581,7 +498,7 @@ void check_error(char const* name, boost::redis::error ev)
|
||||
static_cast<std::underlying_type<boost::redis::error>::type>(ev)));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(error)
|
||||
BOOST_AUTO_TEST_CASE(cover_error)
|
||||
{
|
||||
check_error("boost.redis", boost::redis::error::invalid_data_type);
|
||||
check_error("boost.redis", boost::redis::error::not_a_number);
|
||||
@@ -599,6 +516,12 @@ BOOST_AUTO_TEST_CASE(error)
|
||||
check_error("boost.redis", boost::redis::error::not_a_double);
|
||||
check_error("boost.redis", boost::redis::error::resp3_null);
|
||||
check_error("boost.redis", boost::redis::error::not_connected);
|
||||
check_error("boost.redis", boost::redis::error::resolve_timeout);
|
||||
check_error("boost.redis", boost::redis::error::connect_timeout);
|
||||
check_error("boost.redis", boost::redis::error::pong_timeout);
|
||||
check_error("boost.redis", boost::redis::error::ssl_handshake_timeout);
|
||||
check_error("boost.redis", boost::redis::error::sync_receive_push_failed);
|
||||
check_error("boost.redis", boost::redis::error::incompatible_node_depth);
|
||||
}
|
||||
|
||||
std::string get_type_as_str(boost::redis::resp3::type t)
|
||||
@@ -660,7 +583,7 @@ BOOST_AUTO_TEST_CASE(adapter)
|
||||
using boost::redis::adapter::boost_redis_adapt;
|
||||
using resp3::type;
|
||||
|
||||
boost::system::error_code ec;
|
||||
error_code ec;
|
||||
|
||||
response<std::string, int, ignore_t> resp;
|
||||
|
||||
@@ -675,3 +598,70 @@ BOOST_AUTO_TEST_CASE(adapter)
|
||||
BOOST_TEST(!ec);
|
||||
}
|
||||
|
||||
// TODO: This was an experiment, I will resume implementing this
|
||||
// later.
|
||||
BOOST_AUTO_TEST_CASE(adapter_as)
|
||||
{
|
||||
result<std::set<std::string>> set;
|
||||
auto adapter = adapt2(set);
|
||||
|
||||
for (auto const& e: set_expected1a.value()) {
|
||||
error_code ec;
|
||||
adapter(e, ec);
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(cancel_one_1)
|
||||
{
|
||||
auto resp = push_e1a;
|
||||
BOOST_TEST(resp.has_value());
|
||||
|
||||
consume_one(resp);
|
||||
BOOST_TEST(resp.value().empty());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(cancel_one_empty)
|
||||
{
|
||||
generic_response resp;
|
||||
BOOST_TEST(resp.has_value());
|
||||
|
||||
consume_one(resp);
|
||||
BOOST_TEST(resp.value().empty());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(cancel_one_has_error)
|
||||
{
|
||||
generic_response resp = boost::redis::adapter::error{resp3::type::simple_string, {}};
|
||||
BOOST_TEST(resp.has_error());
|
||||
|
||||
consume_one(resp);
|
||||
BOOST_TEST(resp.has_error());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(cancel_one_has_does_not_consume_past_the_end)
|
||||
{
|
||||
auto resp = push_e1a;
|
||||
BOOST_TEST(resp.has_value());
|
||||
resp.value().insert(
|
||||
std::cend(resp.value()),
|
||||
std::cbegin(push_e1a.value()),
|
||||
std::cend(push_e1a.value()));
|
||||
|
||||
consume_one(resp);
|
||||
|
||||
BOOST_CHECK_EQUAL(resp.value().size(), push_e1a.value().size());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(cancel_one_incompatible_depth)
|
||||
{
|
||||
auto resp = streamed_string_e1;
|
||||
BOOST_TEST(resp.has_value());
|
||||
|
||||
error_code ec;
|
||||
consume_one(resp, ec);
|
||||
|
||||
error_code expected = error::incompatible_node_depth;
|
||||
BOOST_CHECK_EQUAL(ec, expected);
|
||||
|
||||
BOOST_CHECK_EQUAL(resp.value().size(), push_e1a.value().size());
|
||||
}
|
||||
90
test/test_low_level_sync_sans_io.cpp
Normal file
90
test/test_low_level_sync_sans_io.cpp
Normal file
@@ -0,0 +1,90 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <boost/redis/detail/runner.hpp>
|
||||
#include <boost/redis/resp3/serialization.hpp>
|
||||
#include <boost/redis/adapter/adapt.hpp>
|
||||
#define BOOST_TEST_MODULE conn-quit
|
||||
#include <boost/test/included/unit_test.hpp>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
|
||||
using boost::redis::request;
|
||||
using boost::redis::config;
|
||||
using boost::redis::detail::push_hello;
|
||||
using boost::redis::adapter::adapt2;
|
||||
using boost::redis::adapter::result;
|
||||
using boost::redis::resp3::detail::deserialize;
|
||||
|
||||
BOOST_AUTO_TEST_CASE(low_level_sync_sans_io)
|
||||
{
|
||||
try {
|
||||
result<std::set<std::string>> resp;
|
||||
|
||||
char const* wire = "~6\r\n+orange\r\n+apple\r\n+one\r\n+two\r\n+three\r\n+orange\r\n";
|
||||
deserialize(wire, adapt2(resp));
|
||||
|
||||
for (auto const& e: resp.value())
|
||||
std::cout << e << std::endl;
|
||||
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(config_to_hello)
|
||||
{
|
||||
config cfg;
|
||||
cfg.clientname = "";
|
||||
request req;
|
||||
|
||||
push_hello(cfg, req);
|
||||
|
||||
std::string_view const expected = "*2\r\n$5\r\nHELLO\r\n$1\r\n3\r\n";
|
||||
BOOST_CHECK_EQUAL(req.payload(), expected);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(config_to_hello_with_select)
|
||||
{
|
||||
config cfg;
|
||||
cfg.clientname = "";
|
||||
cfg.database_index = 10;
|
||||
request req;
|
||||
|
||||
push_hello(cfg, req);
|
||||
|
||||
std::string_view const expected =
|
||||
"*2\r\n$5\r\nHELLO\r\n$1\r\n3\r\n"
|
||||
"*2\r\n$6\r\nSELECT\r\n$2\r\n10\r\n";
|
||||
|
||||
BOOST_CHECK_EQUAL(req.payload(), expected);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(config_to_hello_cmd_clientname)
|
||||
{
|
||||
config cfg;
|
||||
request req;
|
||||
|
||||
push_hello(cfg, req);
|
||||
|
||||
std::string_view const expected = "*4\r\n$5\r\nHELLO\r\n$1\r\n3\r\n$7\r\nSETNAME\r\n$11\r\nBoost.Redis\r\n";
|
||||
BOOST_CHECK_EQUAL(req.payload(), expected);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(config_to_hello_cmd_auth)
|
||||
{
|
||||
config cfg;
|
||||
cfg.clientname = "";
|
||||
cfg.username = "foo";
|
||||
cfg.password = "bar";
|
||||
request req;
|
||||
|
||||
push_hello(cfg, req);
|
||||
|
||||
std::string_view const expected = "*5\r\n$5\r\nHELLO\r\n$1\r\n3\r\n$4\r\nAUTH\r\n$3\r\nfoo\r\n$3\r\nbar\r\n";
|
||||
BOOST_CHECK_EQUAL(req.payload(), expected);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user