mirror of
https://github.com/boostorg/redis.git
synced 2026-01-26 06:52:09 +00:00
Compare commits
60 Commits
v1.4.2
...
boost-1.84
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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/*"
|
||||
|
||||
292
.github/workflows/ci.yml
vendored
292
.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
|
||||
|
||||
- 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@v3
|
||||
|
||||
- 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,100 @@ 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-22.04, cxxstd: '17', build-type: 'Debug', ldflags: '' }
|
||||
- { toolset: gcc-11, install: g++-11, os: ubuntu-22.04, cxxstd: '20', build-type: 'Release', ldflags: '' }
|
||||
- { toolset: clang-11, install: clang-11, os: ubuntu-22.04, cxxstd: '17', build-type: 'Debug', ldflags: '' }
|
||||
- { toolset: clang-11, install: clang-11, os: ubuntu-22.04, cxxstd: '20', build-type: 'Debug', ldflags: '' }
|
||||
- { toolset: clang-13, install: clang-13, os: ubuntu-22.04, cxxstd: '17', build-type: 'Release', ldflags: '' }
|
||||
- { toolset: clang-13, install: clang-13, os: ubuntu-22.04, cxxstd: '20', build-type: 'Release', ldflags: '' }
|
||||
- { toolset: clang-14, install: 'clang-14 libc++-14-dev libc++abi-14-dev', os: ubuntu-22.04, 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-22.04, 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
|
||||
|
||||
- name: Install dependencies
|
||||
run: sudo apt-get -y install cmake protobuf-compiler redis-server python3 ${{ matrix.install }}
|
||||
|
||||
- name: Setup Boost
|
||||
run: ./tools/ci.py setup-boost --source-dir=$(pwd)
|
||||
|
||||
- name: Build a Boost distribution using B2
|
||||
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
|
||||
./tools/ci.py build-b2-distro \
|
||||
--toolset ${{ matrix.toolset }}
|
||||
|
||||
- name: Build a Boost distribution using CMake
|
||||
run: |
|
||||
./tools/ci.py build-cmake-distro \
|
||||
--build-type ${{ matrix.build-type }} \
|
||||
--cxxstd ${{ matrix.cxxstd }} \
|
||||
--toolset ${{ matrix.toolset }}
|
||||
|
||||
- name: Build the project tests
|
||||
run: |
|
||||
./tools/ci.py build-cmake-standalone-tests \
|
||||
--build-type ${{ matrix.build-type }} \
|
||||
--cxxstd ${{ matrix.cxxstd }} \
|
||||
--toolset ${{ matrix.toolset }}
|
||||
|
||||
- name: Run the project tests
|
||||
run: |
|
||||
./tools/ci.py run-cmake-standalone-tests \
|
||||
--build-type ${{ matrix.build-type }}
|
||||
|
||||
- name: Run add_subdirectory tests
|
||||
run: |
|
||||
./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: |
|
||||
./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: |
|
||||
./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
|
||||
- { toolset: clang-14, install: clang-14, cxxstd: "17,20" }
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install dependencies
|
||||
run: 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
|
||||
|
||||
304
CMakeLists.txt
304
CMakeLists.txt
@@ -1,264 +1,78 @@
|
||||
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()
|
||||
|
||||
find_package(Boost 1.80 REQUIRED)
|
||||
|
||||
include_directories(${Boost_INCLUDE_DIRS})
|
||||
|
||||
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()
|
||||
# Dependencies
|
||||
if (BOOST_REDIS_MAIN_PROJECT)
|
||||
# If we're the root project, error if a dependency is not found
|
||||
find_package(Boost 1.83 REQUIRED COMPONENTS headers)
|
||||
find_package(Threads REQUIRED)
|
||||
find_package(OpenSSL REQUIRED)
|
||||
target_link_libraries(boost_redis
|
||||
INTERFACE
|
||||
Boost::headers
|
||||
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"] }
|
||||
|
||||
54
README.md
54
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/)
|
||||
@@ -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,35 @@ https://lists.boost.org/Archives/boost/2023/01/253944.php.
|
||||
|
||||
## Changelog
|
||||
|
||||
### develop (incorporates changes to conform the boost review and more)
|
||||
### develop
|
||||
|
||||
* 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
87
doc/Jamfile
Normal file
87
doc/Jamfile
Normal file
@@ -0,0 +1,87 @@
|
||||
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 ;
|
||||
|
||||
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.4.2"
|
||||
<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_EXTRA_STYLESHEET=$(stylesheet_arg)"
|
||||
<doxygen:param>HTML_TIMESTAMP=YES
|
||||
<doxygen:param>GENERATE_TREEVIEW=YES
|
||||
<doxygen:param>FULL_SIDEBAR=NO
|
||||
<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 ;
|
||||
49
example/CMakeLists.txt
Normal file
49
example/CMakeLists.txt
Normal file
@@ -0,0 +1,49 @@
|
||||
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()
|
||||
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;
|
||||
@@ -23,7 +23,7 @@
|
||||
#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 +41,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 +64,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)
|
||||
|
||||
@@ -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)
|
||||
@@ -171,7 +171,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 +183,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 +263,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)
|
||||
{
|
||||
@@ -282,6 +302,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<
|
||||
@@ -342,11 +371,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 +414,15 @@ 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(); }
|
||||
|
||||
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)
|
||||
@@ -50,8 +50,15 @@ struct hello_op {
|
||||
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 +91,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;
|
||||
@@ -232,10 +241,27 @@ private:
|
||||
else
|
||||
hello_req_.push("HELLO", "3", "SETNAME", cfg_.clientname);
|
||||
|
||||
if (cfg_.database_index)
|
||||
if (cfg_.database_index && cfg_.database_index.value() != 0)
|
||||
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_;
|
||||
connector_type ctor_;
|
||||
handshaker_type hsher_;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <boost/assert.hpp>
|
||||
|
||||
#include <charconv>
|
||||
#include <limits>
|
||||
|
||||
namespace boost::redis::resp3 {
|
||||
|
||||
@@ -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<unsigned long>::max)();
|
||||
bulk_ = type::invalid;
|
||||
consumed_ = 0;
|
||||
sizes_[0] = 2; // The sentinel must be more than 1.
|
||||
}
|
||||
|
||||
|
||||
@@ -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-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
@@ -10,7 +10,6 @@
|
||||
#include <boost/redis/resp3/node.hpp>
|
||||
#include <boost/system/error_code.hpp>
|
||||
#include <array>
|
||||
#include <limits>
|
||||
#include <string_view>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
@@ -31,22 +30,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)();
|
||||
int_type 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 +70,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,7 @@
|
||||
#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/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"
|
||||
}
|
||||
73
test/CMakeLists.txt
Normal file
73
test/CMakeLists.txt
Normal file
@@ -0,0 +1,73 @@
|
||||
|
||||
# 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
|
||||
)
|
||||
target_compile_features(${EXE_NAME} PRIVATE cxx_std_${STANDARD})
|
||||
add_test(${EXE_NAME} ${EXE_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_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)
|
||||
|
||||
# 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());
|
||||
}
|
||||
79
test/cmake_subdir_test/CMakeLists.txt
Normal file
79
test/cmake_subdir_test/CMakeLists.txt
Normal file
@@ -0,0 +1,79 @@
|
||||
cmake_minimum_required(VERSION 3.5...3.22)
|
||||
|
||||
project(cmake_subdir_test LANGUAGES CXX)
|
||||
|
||||
# Generated by boostdep --brief redis
|
||||
set(_DEPENDENCIES
|
||||
# Primary dependencies
|
||||
asio
|
||||
assert
|
||||
core
|
||||
mp11
|
||||
system
|
||||
throw_exception
|
||||
|
||||
# Secondary dependencies
|
||||
align
|
||||
array
|
||||
bind
|
||||
chrono
|
||||
config
|
||||
context
|
||||
coroutine
|
||||
date_time
|
||||
exception
|
||||
"function"
|
||||
regex
|
||||
smart_ptr
|
||||
type_traits
|
||||
utility
|
||||
static_assert
|
||||
variant2
|
||||
winapi
|
||||
integer
|
||||
move
|
||||
mpl
|
||||
predef
|
||||
ratio
|
||||
typeof
|
||||
pool
|
||||
algorithm
|
||||
io
|
||||
lexical_cast
|
||||
numeric/conversion
|
||||
range
|
||||
tokenizer
|
||||
tuple
|
||||
preprocessor
|
||||
concept_check
|
||||
container_hash
|
||||
iterator
|
||||
unordered
|
||||
describe
|
||||
container
|
||||
conversion
|
||||
detail
|
||||
optional
|
||||
rational
|
||||
intrusive
|
||||
function_types
|
||||
fusion
|
||||
functional
|
||||
)
|
||||
|
||||
# Build our dependencies, so the targets Boost::xxx are defined
|
||||
set(_BOOST_ROOT ../../../..)
|
||||
foreach(_DEPENDENCY IN LISTS _DEPENDENCIES)
|
||||
add_subdirectory(${_BOOST_ROOT}/libs/${_DEPENDENCY} boostorg/${_DEPENDENCY})
|
||||
endforeach()
|
||||
|
||||
# Build our project
|
||||
add_subdirectory(${_BOOST_ROOT}/libs/redis boostorg/redis)
|
||||
|
||||
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());
|
||||
}
|
||||
@@ -3,7 +3,6 @@
|
||||
#include <boost/asio/consign.hpp>
|
||||
#include <boost/asio/co_spawn.hpp>
|
||||
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
namespace net = boost::asio;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
@@ -21,5 +21,5 @@ run(
|
||||
boost::redis::config cfg = {},
|
||||
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,12 +21,11 @@ using boost::redis::request;
|
||||
using boost::redis::ignore;
|
||||
using boost::redis::operation;
|
||||
using boost::redis::generic_response;
|
||||
using boost::redis::logger;
|
||||
using boost::redis::consume_one;
|
||||
using redis::config;
|
||||
|
||||
// TODO: Test cancel(health_check)
|
||||
|
||||
std::chrono::seconds const interval{1};
|
||||
|
||||
struct push_callback {
|
||||
connection* conn1;
|
||||
@@ -39,9 +39,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 +49,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;
|
||||
|
||||
@@ -106,6 +106,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 +114,7 @@ BOOST_AUTO_TEST_CASE(check_health)
|
||||
});
|
||||
|
||||
//--------------------------------
|
||||
|
||||
|
||||
push_callback{&conn1, &conn2, &resp2, &req1}(); // Starts reading pushes.
|
||||
|
||||
ioc.run();
|
||||
@@ -26,12 +26,39 @@ 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,36 +70,15 @@ 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;
|
||||
cfg.health_check_interval = std::chrono::seconds::zero();
|
||||
run(conn, cfg,
|
||||
@@ -86,19 +92,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 = 1000;
|
||||
|
||||
// The number of pings that will be sent by each session.
|
||||
int const msgs = 1000;
|
||||
int const msgs = 500;
|
||||
|
||||
// The number of publishes that will be sent by each session with
|
||||
// each message.
|
||||
int const n_pubs = 10;
|
||||
int const n_pubs = 100;
|
||||
|
||||
// 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 +114,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
|
||||
@@ -53,7 +53,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 +62,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 +73,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;
|
||||
@@ -242,6 +242,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 +256,7 @@ BOOST_AUTO_TEST_CASE(subscriber_wrong_syntax)
|
||||
conn->cancel(operation::reconnection);
|
||||
};
|
||||
|
||||
conn->async_receive(gresp, c3);
|
||||
conn->async_receive(c3);
|
||||
|
||||
run(conn);
|
||||
|
||||
@@ -26,6 +26,7 @@ using boost::redis::request;
|
||||
using boost::redis::response;
|
||||
using boost::redis::ignore;
|
||||
using boost::redis::ignore_t;
|
||||
using boost::system::error_code;
|
||||
using redis::config;
|
||||
using boost::redis::logger;
|
||||
using namespace std::chrono_literals;
|
||||
@@ -49,7 +50,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...)
|
||||
@@ -69,12 +70,11 @@ BOOST_AUTO_TEST_CASE(receives_push_waiting_resps)
|
||||
run(conn, {}, {});
|
||||
|
||||
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 +87,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 +144,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 +162,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 +206,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 +217,9 @@ BOOST_AUTO_TEST_CASE(test_push_adapter)
|
||||
BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled);
|
||||
});
|
||||
|
||||
run(conn);
|
||||
conn->async_run({}, {}, [](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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ using boost::redis::request;
|
||||
using boost::redis::response;
|
||||
using boost::redis::config;
|
||||
using boost::redis::operation;
|
||||
using boost::system::error_code;
|
||||
|
||||
bool verify_certificate(bool, net::ssl::verify_context&)
|
||||
{
|
||||
@@ -32,6 +33,7 @@ BOOST_AUTO_TEST_CASE(ping)
|
||||
cfg.password = "aedis";
|
||||
cfg.addr.host = "db.occase.de";
|
||||
cfg.addr.port = "6380";
|
||||
//cfg.health_check_interval = std::chrono::seconds{0};
|
||||
|
||||
std::string const in = "Kabuf";
|
||||
|
||||
@@ -55,6 +57,44 @@ BOOST_AUTO_TEST_CASE(ping)
|
||||
ioc.run();
|
||||
|
||||
BOOST_CHECK_EQUAL(in, std::get<0>(resp).value());
|
||||
std::cout << "===============================" << std::endl;
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(acl_does_not_allow_select)
|
||||
{
|
||||
config cfg;
|
||||
cfg.use_ssl = true;
|
||||
cfg.username = "aedis";
|
||||
cfg.password = "aedis";
|
||||
cfg.addr.host = "db.occase.de";
|
||||
cfg.addr.port = "6380";
|
||||
cfg.database_index = 22;
|
||||
cfg.reconnect_wait_interval = std::chrono::seconds::zero();
|
||||
|
||||
std::string const in = "Kabuf";
|
||||
|
||||
request req;
|
||||
req.push("PING", in);
|
||||
|
||||
response<std::string> resp;
|
||||
|
||||
net::io_context ioc;
|
||||
connection conn{ioc};
|
||||
conn.next_layer().set_verify_mode(net::ssl::verify_peer);
|
||||
conn.next_layer().set_verify_callback(verify_certificate);
|
||||
|
||||
conn.async_exec(req, resp, [&](auto, auto) {
|
||||
// TODO: We should not need this cancel here because
|
||||
// reconnect was disabled.
|
||||
conn.cancel();
|
||||
});
|
||||
|
||||
|
||||
error_code ec2;
|
||||
conn.async_run(cfg, {}, [&](auto ec) {
|
||||
ec2 = ec;
|
||||
});
|
||||
|
||||
ioc.run();
|
||||
|
||||
BOOST_TEST(!!ec2);
|
||||
}
|
||||
@@ -47,11 +47,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
|
||||
@@ -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());
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <boost/redis/connection.hpp>
|
||||
#include <boost/redis/adapter/adapt.hpp>
|
||||
#include <boost/redis/detail/read.hpp>
|
||||
#include <boost/redis/detail/write.hpp>
|
||||
#include <boost/asio/use_awaitable.hpp>
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <boost/asio/co_spawn.hpp>
|
||||
#include <boost/asio/connect.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#define BOOST_TEST_MODULE conn-tls
|
||||
#include <boost/test/included/unit_test.hpp>
|
||||
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
|
||||
namespace net = boost::asio;
|
||||
namespace redis = boost::redis;
|
||||
using resolver = net::use_awaitable_t<>::as_default_on_t<net::ip::tcp::resolver>;
|
||||
using tcp_socket = net::use_awaitable_t<>::as_default_on_t<net::ip::tcp::socket>;
|
||||
using boost::redis::adapter::adapt2;
|
||||
using net::ip::tcp;
|
||||
using boost::redis::request;
|
||||
using boost::redis::adapter::result;
|
||||
using redis::config;
|
||||
|
||||
auto co_main(config cfg) -> net::awaitable<void>
|
||||
{
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
|
||||
resolver resv{ex};
|
||||
auto const addrs = co_await resv.async_resolve(cfg.addr.host, cfg.addr.port);
|
||||
tcp_socket socket{ex};
|
||||
co_await net::async_connect(socket, addrs);
|
||||
|
||||
// Creates the request and writes to the socket.
|
||||
request req;
|
||||
req.push("HELLO", 3);
|
||||
req.push("PING", "Hello world");
|
||||
req.push("QUIT");
|
||||
co_await redis::detail::async_write(socket, req);
|
||||
|
||||
// Responses
|
||||
std::string buffer;
|
||||
result<std::string> resp;
|
||||
|
||||
std::size_t consumed = 0;
|
||||
// Reads the responses to all commands in the request.
|
||||
auto dbuf = net::dynamic_buffer(buffer);
|
||||
consumed = co_await redis::detail::async_read(socket, dbuf);
|
||||
dbuf.consume(consumed);
|
||||
consumed = co_await redis::detail::async_read(socket, dbuf, adapt2(resp));
|
||||
dbuf.consume(consumed);
|
||||
consumed = co_await redis::detail::async_read(socket, dbuf);
|
||||
dbuf.consume(consumed);
|
||||
|
||||
std::cout << "Ping: " << resp.value() << std::endl;
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(low_level_async)
|
||||
{
|
||||
net::io_context ioc;
|
||||
net::co_spawn(ioc, std::move(co_main({})), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
#else // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
|
||||
BOOST_AUTO_TEST_CASE(low_level_async)
|
||||
{
|
||||
}
|
||||
|
||||
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
|
||||
@@ -1,61 +0,0 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <boost/redis/detail/read.hpp>
|
||||
#include <boost/redis/detail/write.hpp>
|
||||
#include <boost/redis/adapter/adapt.hpp>
|
||||
#include <boost/asio/connect.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#define BOOST_TEST_MODULE conn-quit
|
||||
#include <boost/test/included/unit_test.hpp>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
|
||||
namespace net = boost::asio;
|
||||
namespace redis = boost::redis;
|
||||
using boost::redis::adapter::adapt2;
|
||||
using boost::redis::request;
|
||||
using boost::redis::adapter::result;
|
||||
|
||||
BOOST_AUTO_TEST_CASE(low_level_sync)
|
||||
{
|
||||
try {
|
||||
std::string const host = "127.0.0.1";
|
||||
std::string const port = "6379";
|
||||
|
||||
net::io_context ioc;
|
||||
net::ip::tcp::resolver resv{ioc};
|
||||
auto const res = resv.resolve(host, port);
|
||||
net::ip::tcp::socket socket{ioc};
|
||||
net::connect(socket, res);
|
||||
|
||||
// Creates the request and writes to the socket.
|
||||
request req;
|
||||
req.push("HELLO", 3);
|
||||
req.push("PING", "Hello world");
|
||||
req.push("QUIT");
|
||||
redis::detail::write(socket, req);
|
||||
|
||||
std::string buffer;
|
||||
result<std::string> resp;
|
||||
|
||||
std::size_t consumed = 0;
|
||||
// Reads the responses to all commands in the request.
|
||||
auto dbuf = net::dynamic_buffer(buffer);
|
||||
consumed = redis::detail::read(socket, dbuf);
|
||||
dbuf.consume(consumed);
|
||||
consumed = redis::detail::read(socket, dbuf, adapt2(resp));
|
||||
dbuf.consume(consumed);
|
||||
consumed = redis::detail::read(socket, dbuf);
|
||||
dbuf.consume(consumed);
|
||||
|
||||
std::cout << "Ping: " << resp.value() << std::endl;
|
||||
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
377
tools/ci.py
Executable file
377
tools/ci.py
Executable file
@@ -0,0 +1,377 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
# Contains commands that are invoked by the CI scripts.
|
||||
# Having this as a Python file makes it platform-independent.
|
||||
|
||||
from pathlib import Path
|
||||
from typing import List, Union
|
||||
import subprocess
|
||||
import os
|
||||
import stat
|
||||
from shutil import rmtree, copytree, ignore_patterns
|
||||
import argparse
|
||||
|
||||
|
||||
# Variables
|
||||
_is_windows = os.name == 'nt'
|
||||
_home = Path(os.path.expanduser('~'))
|
||||
_boost_root = _home.joinpath('boost-root')
|
||||
_b2_distro = _home.joinpath('boost-b2-distro')
|
||||
_cmake_distro = _home.joinpath('boost-cmake-distro')
|
||||
_b2_command = str(_boost_root.joinpath('b2'))
|
||||
|
||||
|
||||
# Utilities
|
||||
def _run(args: List[str]) -> None:
|
||||
print('+ ', args, flush=True)
|
||||
subprocess.run(args, check=True)
|
||||
|
||||
|
||||
def _mkdir_and_cd(path: Path) -> None:
|
||||
os.makedirs(str(path), exist_ok=True)
|
||||
os.chdir(str(path))
|
||||
|
||||
|
||||
def _cmake_bool(value: bool) -> str:
|
||||
return 'ON' if value else 'OFF'
|
||||
|
||||
|
||||
def _remove_readonly(func, path, _):
|
||||
os.chmod(path, stat.S_IWRITE)
|
||||
func(path)
|
||||
|
||||
|
||||
# Parses a string into a boolean (for command-line parsing)
|
||||
def _str2bool(v: Union[bool, str]) -> bool:
|
||||
if isinstance(v, bool):
|
||||
return v
|
||||
elif v == '1':
|
||||
return True
|
||||
elif v == '0':
|
||||
return False
|
||||
else:
|
||||
raise argparse.ArgumentTypeError('Boolean value expected.')
|
||||
|
||||
|
||||
# Transforms a b2-like toolset into a compiler command suitable
|
||||
# to be passed to CMAKE_CXX_COMPILER
|
||||
def _compiler_from_toolset(toolset: str) -> str:
|
||||
if toolset.startswith('gcc'):
|
||||
return toolset.replace('gcc', 'g++')
|
||||
elif toolset.startswith('clang'):
|
||||
return toolset.replace('clang', 'clang++')
|
||||
elif toolset.startswith('msvc'):
|
||||
return 'cl'
|
||||
else:
|
||||
return toolset
|
||||
|
||||
|
||||
# If we're on the master branch, we should use the Boost superproject master branch.
|
||||
# Otherwise, use the superproject develop branch.
|
||||
def _deduce_boost_branch() -> str:
|
||||
# Are we in GitHub Actions?
|
||||
if os.environ.get('GITHUB_ACTIONS') is not None:
|
||||
ci = 'GitHub Actions'
|
||||
ref = os.environ.get('GITHUB_BASE_REF', '') or os.environ.get('GITHUB_REF', '')
|
||||
res = 'master' if ref == 'master' or ref.endswith('/master') else 'develop'
|
||||
elif os.environ.get('DRONE') is not None:
|
||||
ref = os.environ.get('DRONE_BRANCH', '')
|
||||
ci = 'Drone'
|
||||
res = 'master' if ref == 'master' else 'develop'
|
||||
else:
|
||||
ci = 'Unknown'
|
||||
ref = ''
|
||||
res = 'develop'
|
||||
|
||||
print('+ Found CI {}, ref={}, deduced branch {}'.format(ci, ref, res))
|
||||
|
||||
return res
|
||||
|
||||
|
||||
# Gets Boost (develop or master, depending on the CI branch we're operating on),
|
||||
# with the required dependencies, and leaves it at _boost_root. Places our library,
|
||||
# located under source_dir, under $BOOST_ROOT/libs. Also runs the bootstrap script so b2 is usable.
|
||||
def _setup_boost(
|
||||
source_dir: Path
|
||||
) -> None:
|
||||
assert source_dir.is_absolute()
|
||||
assert not _boost_root.exists()
|
||||
lib_dir = _boost_root.joinpath('libs', 'redis')
|
||||
branch = _deduce_boost_branch()
|
||||
|
||||
# Clone Boost
|
||||
_run(['git', 'clone', '-b', branch, '--depth', '1', 'https://github.com/boostorg/boost.git', str(_boost_root)])
|
||||
os.chdir(str(_boost_root))
|
||||
|
||||
# Put our library inside boost root
|
||||
if lib_dir.exists():
|
||||
rmtree(str(lib_dir), onerror=_remove_readonly)
|
||||
copytree(
|
||||
str(source_dir),
|
||||
str(lib_dir),
|
||||
ignore=ignore_patterns('__build*__', '.git')
|
||||
)
|
||||
|
||||
# Install Boost dependencies
|
||||
_run(["git", "config", "submodule.fetchJobs", "8"])
|
||||
_run(["git", "submodule", "update", "-q", "--init", "tools/boostdep"])
|
||||
_run(["python", "tools/boostdep/depinst/depinst.py", "--include", "example", "redis"])
|
||||
|
||||
# Bootstrap
|
||||
if _is_windows:
|
||||
_run(['cmd', '/q', '/c', 'bootstrap.bat'])
|
||||
else:
|
||||
_run(['bash', 'bootstrap.sh'])
|
||||
_run([_b2_command, 'headers', '-d0'])
|
||||
|
||||
|
||||
# Builds a Boost distribution using ./b2 install, and places it into _b2_distro.
|
||||
# This emulates a regular Boost distribution, like the ones in releases
|
||||
def _build_b2_distro(
|
||||
toolset: str
|
||||
):
|
||||
os.chdir(str(_boost_root))
|
||||
_run([
|
||||
_b2_command,
|
||||
'--prefix={}'.format(_b2_distro),
|
||||
'--with-system',
|
||||
'toolset={}'.format(toolset),
|
||||
'-d0',
|
||||
'install'
|
||||
])
|
||||
|
||||
|
||||
# Builds a Boost distribution using cmake, and places it into _cmake_distro.
|
||||
# It includes only our library and any dependency.
|
||||
def _build_cmake_distro(
|
||||
generator: str,
|
||||
build_type: str,
|
||||
cxxstd: str,
|
||||
toolset: str,
|
||||
build_shared_libs: bool = False
|
||||
):
|
||||
_mkdir_and_cd(_boost_root.joinpath('__build_cmake_test__'))
|
||||
_run([
|
||||
'cmake',
|
||||
'-G',
|
||||
generator,
|
||||
'-DBUILD_TESTING=ON',
|
||||
'-DCMAKE_CXX_COMPILER={}'.format(_compiler_from_toolset(toolset)),
|
||||
'-DCMAKE_BUILD_TYPE={}'.format(build_type),
|
||||
'-DCMAKE_CXX_STANDARD={}'.format(cxxstd),
|
||||
'-DBOOST_INCLUDE_LIBRARIES=redis',
|
||||
'-DBUILD_SHARED_LIBS={}'.format(_cmake_bool(build_shared_libs)),
|
||||
'-DCMAKE_INSTALL_PREFIX={}'.format(_cmake_distro),
|
||||
'-DBUILD_TESTING=ON',
|
||||
'-DBoost_VERBOSE=ON',
|
||||
'-DCMAKE_INSTALL_MESSAGE=NEVER',
|
||||
'..'
|
||||
])
|
||||
_run(['cmake', '--build', '.', '--target', 'tests', '--config', build_type])
|
||||
_run(['ctest', '--output-on-failure', '--build-config', build_type])
|
||||
_run(['cmake', '--build', '.', '--target', 'install', '--config', build_type])
|
||||
|
||||
|
||||
# Builds our CMake tests as a standalone project
|
||||
# (BOOST_REDIS_MAIN_PROJECT is ON) and we find_package Boost.
|
||||
# This ensures that all our test suite is run.
|
||||
def _build_cmake_standalone_tests(
|
||||
generator: str,
|
||||
build_type: str,
|
||||
cxxstd: str,
|
||||
toolset: str,
|
||||
build_shared_libs: bool = False
|
||||
):
|
||||
_mkdir_and_cd(_boost_root.joinpath('libs', 'redis', '__build_standalone__'))
|
||||
_run([
|
||||
'cmake',
|
||||
'-DBUILD_TESTING=ON',
|
||||
'-DCMAKE_CXX_COMPILER={}'.format(_compiler_from_toolset(toolset)),
|
||||
'-DCMAKE_PREFIX_PATH={}'.format(_b2_distro),
|
||||
'-DCMAKE_BUILD_TYPE={}'.format(build_type),
|
||||
'-DBUILD_SHARED_LIBS={}'.format(_cmake_bool(build_shared_libs)),
|
||||
'-DCMAKE_CXX_STANDARD={}'.format(cxxstd),
|
||||
'-G',
|
||||
generator,
|
||||
'..'
|
||||
])
|
||||
_run(['cmake', '--build', '.'])
|
||||
|
||||
|
||||
# Runs the tests built in the previous step
|
||||
def _run_cmake_standalone_tests(
|
||||
build_type: str
|
||||
):
|
||||
os.chdir(str(_boost_root.joinpath('libs', 'redis', '__build_standalone__')))
|
||||
_run(['ctest', '--output-on-failure', '--build-config', build_type, '--no-tests=error'])
|
||||
|
||||
|
||||
# Tests that the library can be consumed using add_subdirectory()
|
||||
def _run_cmake_add_subdirectory_tests(
|
||||
generator: str,
|
||||
build_type: str,
|
||||
cxxstd: str,
|
||||
toolset: str,
|
||||
build_shared_libs: bool = False
|
||||
):
|
||||
test_folder = _boost_root.joinpath('libs', 'redis', 'test', 'cmake_subdir_test', '__build')
|
||||
_mkdir_and_cd(test_folder)
|
||||
_run([
|
||||
'cmake',
|
||||
'-G',
|
||||
generator,
|
||||
'-DCMAKE_CXX_COMPILER={}'.format(_compiler_from_toolset(toolset)),
|
||||
'-DBUILD_TESTING=ON',
|
||||
'-DCMAKE_BUILD_TYPE={}'.format(build_type),
|
||||
'-DBUILD_SHARED_LIBS={}'.format(_cmake_bool(build_shared_libs)),
|
||||
'-DCMAKE_CXX_STANDARD={}'.format(cxxstd),
|
||||
'..'
|
||||
])
|
||||
_run(['cmake', '--build', '.', '--config', build_type])
|
||||
_run(['ctest', '--output-on-failure', '--build-config', build_type, '--no-tests=error'])
|
||||
|
||||
|
||||
# Tests that the library can be consumed using find_package on a distro built by cmake
|
||||
def _run_cmake_find_package_tests(
|
||||
generator: str,
|
||||
build_type: str,
|
||||
cxxstd: str,
|
||||
toolset: str,
|
||||
build_shared_libs: bool = False
|
||||
):
|
||||
_mkdir_and_cd(_boost_root.joinpath('libs', 'redis', 'test', 'cmake_install_test', '__build'))
|
||||
_run([
|
||||
'cmake',
|
||||
'-G',
|
||||
generator,
|
||||
'-DCMAKE_CXX_COMPILER={}'.format(_compiler_from_toolset(toolset)),
|
||||
'-DBUILD_TESTING=ON',
|
||||
'-DCMAKE_BUILD_TYPE={}'.format(build_type),
|
||||
'-DBUILD_SHARED_LIBS={}'.format(_cmake_bool(build_shared_libs)),
|
||||
'-DCMAKE_CXX_STANDARD={}'.format(cxxstd),
|
||||
'-DCMAKE_PREFIX_PATH={}'.format(_cmake_distro),
|
||||
'..'
|
||||
])
|
||||
_run(['cmake', '--build', '.', '--config', build_type])
|
||||
_run(['ctest', '--output-on-failure', '--build-config', build_type, '--no-tests=error'])
|
||||
|
||||
|
||||
# Tests that the library can be consumed using find_package on a distro built by b2
|
||||
def _run_cmake_b2_find_package_tests(
|
||||
generator: str,
|
||||
build_type: str,
|
||||
cxxstd: str,
|
||||
toolset: str,
|
||||
build_shared_libs: bool = False
|
||||
):
|
||||
_mkdir_and_cd(_boost_root.joinpath('libs', 'redis', 'test', 'cmake_b2_test', '__build'))
|
||||
_run([
|
||||
'cmake',
|
||||
'-G',
|
||||
generator,
|
||||
'-DCMAKE_CXX_COMPILER={}'.format(_compiler_from_toolset(toolset)),
|
||||
'-DBUILD_TESTING=ON',
|
||||
'-DCMAKE_PREFIX_PATH={}'.format(_b2_distro),
|
||||
'-DCMAKE_BUILD_TYPE={}'.format(build_type),
|
||||
'-DBUILD_SHARED_LIBS={}'.format(_cmake_bool(build_shared_libs)),
|
||||
'-DCMAKE_CXX_STANDARD={}'.format(cxxstd),
|
||||
'-DBUILD_TESTING=ON',
|
||||
'..'
|
||||
])
|
||||
_run(['cmake', '--build', '.', '--config', build_type])
|
||||
_run(['ctest', '--output-on-failure', '--build-config', build_type, '--no-tests=error'])
|
||||
|
||||
|
||||
# Builds and runs the library tests using b2
|
||||
def _run_b2_tests(
|
||||
variant: str,
|
||||
cxxstd: str,
|
||||
toolset: str
|
||||
):
|
||||
os.chdir(str(_boost_root))
|
||||
_run([
|
||||
_b2_command,
|
||||
'--abbreviate-paths',
|
||||
'toolset={}'.format(toolset),
|
||||
'cxxstd={}'.format(cxxstd),
|
||||
'variant={}'.format(variant),
|
||||
'-j4',
|
||||
'libs/redis/test'
|
||||
])
|
||||
|
||||
|
||||
def main():
|
||||
# Command line parsing
|
||||
parser = argparse.ArgumentParser()
|
||||
subparsers = parser.add_subparsers()
|
||||
|
||||
subp = subparsers.add_parser('setup-boost')
|
||||
subp.add_argument('--source-dir', type=Path, required=True)
|
||||
subp.set_defaults(func=_setup_boost)
|
||||
|
||||
subp = subparsers.add_parser('build-b2-distro')
|
||||
subp.add_argument('--toolset', default='gcc')
|
||||
subp.set_defaults(func=_build_b2_distro)
|
||||
|
||||
subp = subparsers.add_parser('build-cmake-distro')
|
||||
subp.add_argument('--generator', default='Unix Makefiles')
|
||||
subp.add_argument('--build-type', default='Debug')
|
||||
subp.add_argument('--cxxstd', default='20')
|
||||
subp.add_argument('--toolset', default='gcc')
|
||||
subp.add_argument('--build-shared-libs', type=_str2bool, default=False)
|
||||
subp.set_defaults(func=_build_cmake_distro)
|
||||
|
||||
subp = subparsers.add_parser('build-cmake-standalone-tests')
|
||||
subp.add_argument('--generator', default='Unix Makefiles')
|
||||
subp.add_argument('--build-type', default='Debug')
|
||||
subp.add_argument('--cxxstd', default='20')
|
||||
subp.add_argument('--toolset', default='gcc')
|
||||
subp.add_argument('--build-shared-libs', type=_str2bool, default=False)
|
||||
subp.set_defaults(func=_build_cmake_standalone_tests)
|
||||
|
||||
subp = subparsers.add_parser('run-cmake-standalone-tests')
|
||||
subp.add_argument('--build-type', default='Debug')
|
||||
subp.set_defaults(func=_run_cmake_standalone_tests)
|
||||
|
||||
subp = subparsers.add_parser('run-cmake-add-subdirectory-tests')
|
||||
subp.add_argument('--generator', default='Unix Makefiles')
|
||||
subp.add_argument('--build-type', default='Debug')
|
||||
subp.add_argument('--cxxstd', default='20')
|
||||
subp.add_argument('--toolset', default='gcc')
|
||||
subp.add_argument('--build-shared-libs', type=_str2bool, default=False)
|
||||
subp.set_defaults(func=_run_cmake_add_subdirectory_tests)
|
||||
|
||||
subp = subparsers.add_parser('run-cmake-find-package-tests')
|
||||
subp.add_argument('--generator', default='Unix Makefiles')
|
||||
subp.add_argument('--build-type', default='Debug')
|
||||
subp.add_argument('--cxxstd', default='20')
|
||||
subp.add_argument('--toolset', default='gcc')
|
||||
subp.add_argument('--build-shared-libs', type=_str2bool, default=False)
|
||||
subp.set_defaults(func=_run_cmake_find_package_tests)
|
||||
|
||||
subp = subparsers.add_parser('run-cmake-b2-find-package-tests')
|
||||
subp.add_argument('--generator', default='Unix Makefiles')
|
||||
subp.add_argument('--build-type', default='Debug')
|
||||
subp.add_argument('--cxxstd', default='20')
|
||||
subp.add_argument('--toolset', default='gcc')
|
||||
subp.add_argument('--build-shared-libs', type=_str2bool, default=False)
|
||||
subp.set_defaults(func=_run_cmake_b2_find_package_tests)
|
||||
|
||||
subp = subparsers.add_parser('run-b2-tests')
|
||||
subp.add_argument('--variant', default='debug,release')
|
||||
subp.add_argument('--cxxstd', default='17,20')
|
||||
subp.add_argument('--toolset', default='gcc')
|
||||
subp.set_defaults(func=_run_b2_tests)
|
||||
|
||||
# Actually parse the arguments
|
||||
args = parser.parse_args()
|
||||
|
||||
# Invoke the relevant function (as defined by the func default), with
|
||||
# the command-line arguments the user passed us (we need to get rid
|
||||
# of the func property to match function signatures)
|
||||
# This approach is recommended by Python's argparse docs
|
||||
args.func(**{k: v for k, v in vars(args).items() if k != 'func'})
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
12
tools/user-config.jam
Normal file
12
tools/user-config.jam
Normal file
@@ -0,0 +1,12 @@
|
||||
# Used on CI. This is required on Windows to make b2 find openssl
|
||||
|
||||
import os ;
|
||||
|
||||
local OPENSSL_ROOT = [ os.environ OPENSSL_ROOT ] ;
|
||||
|
||||
using openssl : :
|
||||
<include>$(OPENSSL_ROOT)/include
|
||||
<search>$(OPENSSL_ROOT)/lib
|
||||
<ssl-name>libssl
|
||||
<crypto-name>libcrypto
|
||||
;
|
||||
Reference in New Issue
Block a user