2
0
mirror of https://github.com/boostorg/redis.git synced 2026-01-25 06:32:08 +00:00

Compare commits

...

118 Commits

Author SHA1 Message Date
Marcelo Zimbres
a4d2bb983d Merge branch 'master' into develop 2024-05-01 16:44:39 +02:00
Ruben Perez
30ba3c3eb6 Leftovers cleanup 2024-04-03 16:02:55 +02:00
Ruben Perez
90f3d6cd92 docker-compose without build 2024-04-03 16:02:55 +02:00
Ruben Perez
ef6dea1666 Missing package & env var 2024-04-03 16:02:55 +02:00
Ruben Perez
90b001a54e Correct volume mount 2024-04-03 16:02:55 +02:00
Ruben Perez
ec1ca876eb Incorrect directory for docker-compose 2024-04-03 16:02:55 +02:00
Ruben Perez
1b90346b7c Removed interactive flag 2024-04-03 16:02:55 +02:00
Ruben Perez
8f46d1eaa9 Removed need 2024-04-03 16:02:55 +02:00
Ruben Perez
a4e9a60f34 Removed external service 2024-04-03 16:02:55 +02:00
Ruben Perez
e9b16a3140 Removed sudo 2024-04-03 16:02:55 +02:00
Ruben Perez
ced4f9bd02 Container service 2024-04-03 16:02:55 +02:00
Ruben Perez
43878d68a3 missing sudo 2024-04-03 16:02:55 +02:00
Ruben Perez
29892f2837 Attempt to run without container 2024-04-03 16:02:55 +02:00
Ruben Perez
b71dff6dd0 Missing ports in docker-compose 2024-04-03 16:02:55 +02:00
Ruben Perez
a9204fcc91 Include cleanup 2024-04-03 16:02:55 +02:00
Ruben Perez
1788ebc80b make instead of ninja 2024-04-03 16:02:55 +02:00
Ruben Perez
5656571aa4 Missing packages 2024-04-03 16:02:55 +02:00
Ruben Perez
aa6622ffa2 Missing git 2024-04-03 16:02:55 +02:00
Ruben Perez
f459d5d89b ca-certificates 2024-04-03 16:02:55 +02:00
Ruben Perez
35276b6acb Docker compose with build step 2024-04-03 16:02:55 +02:00
Ruben Perez
122ffb20b2 Permission change 2024-04-03 16:02:55 +02:00
Ruben Perez
1f4b709b21 Missing ca-certificates 2024-04-03 16:02:55 +02:00
Ruben Perez
7d52065a87 Add git 2024-04-03 16:02:55 +02:00
Ruben Perez
f0d92c16a5 Debugging compose 2024-04-03 16:02:55 +02:00
Ruben Perez
1df18258d8 Missing backslashes in ci.yml 2024-04-03 16:02:55 +02:00
Ruben Perez
71c60a5a89 sudo removal 2024-04-03 16:02:55 +02:00
Ruben Perez
098fbd68d2 CI with Docker compose 2024-04-03 16:02:55 +02:00
Ruben Perez
4f2b12adbc Remove debug statement in test_conn_tls 2024-04-03 16:02:55 +02:00
Ruben Perez
e7cec45cb2 Test TLS files 2024-04-03 16:02:55 +02:00
Ruben Perez
b19067cfed get_server_hostname utility function 2024-04-03 16:02:55 +02:00
Ruben Perez
0af1c2e73d Fixed unused variable warning in connection_base 2024-04-03 16:02:55 +02:00
Ruben Perez
da48368d53 Restored TLS tests & rework 2024-04-03 16:02:55 +02:00
Marcelo Zimbres
b6e1280075 Fixes narrowing conversion.
NOTE: I had to disable the TLS tests because I shotdown the server I was
running on my domain occase.de. Once this ticket is merged I will open a
new one to fix that and reenable the tests.
2024-03-20 23:08:15 +01:00
Marcelo Zimbres
5d553f5d71 Some fixes in the article about the costs of async abstractions [skip ci] 2024-03-20 23:08:15 +01:00
Marcelo Zimbres
5c46b62958 Fixes narrowing conversion.
NOTE: I had to disable the TLS tests because I shotdown the server I was
running on my domain occase.de. Once this ticket is merged I will open a
new one to fix that and reenable the tests.
2024-03-20 14:58:06 +01:00
Marcelo Zimbres
6cde6eab44 Some fixes in the article about the costs of async abstractions [skip ci] 2024-03-20 14:58:06 +01:00
Marcelo Zimbres
78792199ef Adds endian to the list of dependencies 2024-02-17 21:34:01 +01:00
Marcelo Zimbres
f5793ac9bc Refactors add_hello and adds unit tests. 2024-02-17 21:34:01 +01:00
Marcelo Zimbres
dfc2bd1ac2 Fixes issue 181. 2024-02-17 21:34:01 +01:00
Marcelo Zimbres
0445e74fa3 Fixes the CMake file. 2024-01-21 21:52:31 +01:00
Marcelo Zimbres
234f961e87 Provides a way of passing a custom ssl context to the connection. 2024-01-21 21:52:31 +01:00
Marcelo Zimbres
8bb0004188 Adds missing ssl-context getters. 2024-01-21 21:52:31 +01:00
Marcelo Zimbres
4257b2eaec In-tree cmake builds instead of FindBoost. 2024-01-21 21:52:31 +01:00
Marcelo Zimbres
96da11a2cc Article about the costs of async abstractions. 2024-01-21 21:52:31 +01:00
Marcelo Zimbres
3861c5de74 Accepts as valid responses to staged requests.
Before these changes the request had to be marked as written in order to
interpret incoming responses as belonging to that request. On fast
networks however, like on localhost and underload the responses might
arrive before the write operation completed.
2024-01-21 21:52:31 +01:00
Marcelo
168ee6148a Merge pull request #162 from boostorg/develop
Merge latest css improvements into master
2023-12-14 23:22:03 +01:00
Marcelo Zimbres
723e72797f Output test error. 2023-12-02 20:23:16 +01:00
Marcelo
7caea928af Merge pull request #167 from boostorg/165-support-containers-in-ci
165 support containers in ci
2023-11-12 15:08:42 +01:00
Marcelo Zimbres
71b9a4f428 Reduces the number of messages in the stress test. 2023-11-12 13:00:43 +01:00
Marcelo Zimbres
d89a976729 Build gcc11 builds in container. 2023-11-11 14:12:33 +01:00
Marcelo Zimbres
154d0b106d Obtains the Redis host from env variables. 2023-11-04 12:41:53 +01:00
Marcelo Zimbres
2b12525206 Adds container to matrix. 2023-10-30 22:59:04 +01:00
Marcelo Zimbres
0bcbf6d4e4 Fix the build of boost dependencies. 2023-10-28 09:29:51 +02:00
Marcelo
6389daa783 Merge pull request #161 from anarthal/develop
Docs styles minor tweaks
2023-10-11 12:19:16 +02:00
Ruben Perez
ab2d6cdea8 Docs styles minor tweaks
Magnifying glass showing twice.
Sidebar scrolling with the content.
Header not being hidden correctly for small devices.

close #160
2023-10-11 11:57:43 +02:00
Marcelo
63ce40e365 Merge pull request #159 from anarthal/feature/158-boost-docs-formatting-problems
Fixed CSS formatting for Boost docs
2023-10-10 23:10:37 +02:00
Ruben Perez
f2a005a8c4 Fixed CSS formatting for Boost docs 2023-10-10 22:36:09 +02:00
Marcelo Zimbres
0c06be66de Fixes Boost.Redis version.
[skip ci]
2023-10-08 10:04:54 +02:00
Marcelo
0380e643ed Merge pull request #157 from boostorg/develop
Latest develop changes
2023-10-08 09:10:07 +02:00
Marcelo
ff734694ab Merge pull request #156 from boostorg/prepare_for_first_boost_release
Prepare for first boost release
2023-10-07 21:51:28 +02:00
Marcelo Zimbres
548e3d4cb6 Updates the copywrite notice. 2023-10-07 16:44:17 +02:00
Marcelo Zimbres
66b632b13d Small fixes in the docs. 2023-10-07 16:40:13 +02:00
Marcelo
11c9c1b787 Merge pull request #155 from anarthal/feature/118-boost-integration
118 boost integration
2023-10-05 22:42:31 +02:00
Ruben Perez
d6f9e435c7 Revert "Fixed libc++ link flags"
This reverts commit 199fb6c261.
2023-10-05 16:55:53 +02:00
Ruben Perez
9a7816dbf4 switched to default installation of openssl 2023-10-05 16:22:59 +02:00
Ruben Perez
199fb6c261 Fixed libc++ link flags 2023-10-05 16:19:24 +02:00
Ruben Perez
4d30d1e0c0 split cmake_test 2023-10-05 16:10:25 +02:00
Ruben Perez
92be6d958f Reduced ci.py verbosity 2023-10-05 16:08:49 +02:00
Ruben Perez
14d3c0232e Removed unnecessary checks fom jamfile 2023-10-05 16:08:32 +02:00
Ruben Perez
7412b37e08 choco => vcpkg 2023-10-05 16:03:32 +02:00
Ruben Perez
60ba5b62af Missing packages in coverage build 2023-10-05 14:13:19 +02:00
Ruben Perez
0303ae0dbc Simplified & documented Jamfile 2023-10-05 13:47:14 +02:00
Ruben Perez
ea6c5536c1 CMAKE_BUILD_PARALLEL_LEVEL for coverage 2023-10-05 13:26:00 +02:00
Ruben Perez
d386b30c3a Simplified ci.py 2023-10-05 13:25:49 +02:00
Ruben Perez
2951acc80f Merge branch 'feature/118-boost-integration' of github.com:anarthal/boost-redis into feature/118-boost-integration 2023-10-05 12:53:52 +02:00
Ruben Perez
faf15fe7e8 Initial coverage workflow 2023-10-05 12:45:35 +02:00
Ruben Perez
7f3f8b0c13 Relaxed cxx17 requirement in Jamfile 2023-10-05 12:33:49 +02:00
Ruben Perez
f37e514961 Link error fix in win b2 2023-10-05 12:02:47 +02:00
Ruben Perez
b7b4f8f449 OpenSSL win fix in CI 2023-10-05 12:02:31 +02:00
Ruben Perez
686cb306ea README now states Boost requirements 2023-10-04 19:13:50 +02:00
Ruben Perez
fcbe2c431c Canonical project name 2023-10-04 19:10:24 +02:00
Ruben Perez
a7b3fbdd9a Protect min/max 2023-10-04 19:06:31 +02:00
Ruben Perez
5ea0d3c467 Fixed OPENSSL_ROOT on win 2023-10-04 19:06:13 +02:00
Ruben Perez
2cd487784b Attempt to solve b2 openssl problem in win 2023-10-04 18:58:07 +02:00
Ruben Perez
b41e2704a1 choco no progress 2023-10-04 18:50:51 +02:00
Ruben Perez
765f0d45e8 Improved CI build names 2023-10-04 18:48:37 +02:00
Ruben Perez
84c8649d66 Bad b2 command 2023-10-04 18:48:29 +02:00
Ruben Perez
0bf4e76981 B2 CI 2023-10-04 18:41:48 +02:00
Ruben Perez
1d329df81b test jamfile 2023-10-04 18:31:47 +02:00
Ruben Perez
56f7d5af69 examples => example 2023-10-04 17:47:03 +02:00
Ruben Perez
d0c3b3f7ee generator fix 2023-10-04 17:38:50 +02:00
Ruben Perez
87ebc6cf4a protobuf fix 2023-10-04 17:38:34 +02:00
Ruben Perez
ffc35e8e3e copytree and cxxstd 2023-10-04 17:23:48 +02:00
Ruben Perez
a02837ab33 Explicit Python & typos 2023-10-04 17:12:11 +02:00
Ruben Perez
4a39a0d20a Toolset 2023-10-04 17:07:26 +02:00
Ruben Perez
56d9a2778f Typo fix 2023-10-04 12:50:41 +02:00
Ruben Perez
c732f33b48 New CI 2023-10-04 12:49:13 +02:00
Ruben Perez
221016f1c9 subdir tests 2023-10-04 12:29:59 +02:00
Ruben Perez
cb9fdba0a4 New cmakes 2023-10-04 11:28:55 +02:00
Ruben Perez
1c96a60709 ci.py first version 2023-10-03 23:09:34 +02:00
Ruben Perez
b66d067af8 tests => test 2023-10-03 23:08:59 +02:00
Ruben Perez
bc08a8d411 Trigger CI 2023-10-03 21:04:43 +02:00
Ruben Perez
53ef947cf3 Doc install and redirection 2023-10-03 18:59:21 +02:00
Ruben Perez
ecfe51c7ae Doc fixes 2023-10-03 17:27:31 +02:00
Ruben Perez
be20c0d48c Docs via b2 2023-10-03 16:51:05 +02:00
Ruben Perez
d5031c3f69 libraries.json 2023-10-02 17:17:44 +02:00
Marcelo
6748f7682a Merge pull request #153 from boostorg/152-enable-reading-server-pushes-in-batches
152 enable reading server pushes in batches
2023-09-10 22:28:28 +02:00
Marcelo Zimbres
2a4936a9e1 Implements batch reads for server pushes. 2023-09-10 12:05:37 +02:00
Marcelo Zimbres
4547e1ac07 First steps with using adapters to process a generic_response. 2023-09-04 14:00:12 +02:00
Marcelo
44a608c0ba Merge pull request #151 from boostorg/150-remove-resp3read-and-resp3async_read
Removes resp3::async_read.
2023-09-02 14:52:36 +02:00
Marcelo Zimbres
1ed8e0182c Removes resp3::async_read. 2023-09-02 13:05:06 +02:00
Marcelo
d8cf431dc2 Merge pull request #149 from boostorg/144-implement-connection-usage-information
Adds connection usage information.
2023-08-30 09:30:30 +02:00
Marcelo Zimbres
401dd24419 Adds connection usage information. 2023-08-29 16:31:23 +02:00
Marcelo
509635f222 Merge pull request #145 from boostorg/138-use-stdfunction-to-type-erase-the-adapter
Uses std::function to type erase the response adapter
2023-08-26 15:39:49 +02:00
Marcelo Zimbres
4fbd0c6853 Progreeses with the adapter type erasure. 2023-08-26 13:09:48 +02:00
Marcelo
b8899ecdc7 Merge pull request #143 from mrichmon/develop
Fix cmake find_package
2023-08-22 08:26:07 +02:00
Michael Richmond
7d09040646 Bump version number 2023-08-21 16:42:41 -07:00
Michael Richmond
0de26fb0ce Fix out of date filename 2023-08-21 16:42:17 -07:00
113 changed files with 4165 additions and 4689 deletions

View File

@@ -7,7 +7,7 @@ codecov:
ignore:
- "benchmarks/cpp/asio/*"
- "examples/*"
- "example/*"
- "tests/*"
- "/usr/*"
- "**/boost/*"

View File

@@ -1,10 +1,18 @@
# CI script to verify that CMake and B2 builds work.
# B2 builds include only tests that don't require a DB server, to avoid race conditions.
# CMake tests include the actual project tests and all the CMake integration workflows
# recommended by Boost.CI.
# Windows CMake jobs build the code but don't run the tests,
# since we don't have a way to set up a Redis server on Windows (yet).
# Subcommands are implemented by the tools/ci.py script in a platform-independent manner.
name: CI
on: [push, pull_request]
jobs:
windows:
name: "${{matrix.generator}} ${{matrix.toolset}} Boost ${{matrix.boost_version}} ${{matrix.build_type}} ${{matrix.name_args}}"
windows-cmake:
name: "CMake ${{matrix.toolset}} ${{matrix.build-type}} C++${{matrix.cxxstd}}"
runs-on: ${{matrix.os}}
defaults:
run:
@@ -12,84 +20,109 @@ jobs:
strategy:
fail-fast: false
matrix:
boost_version: ["1.81.0"]
os: [windows-2019, windows-2022]
toolset: [v142, v143]
build_type: [Release]
generator: ["Visual Studio 16 2019", "Visual Studio 17 2022"]
config_args: [""]
build_args: [""]
name_args: [""]
exclude:
- { os: windows-2019, toolset: v143 }
- { os: windows-2019, generator: "Visual Studio 17 2022" }
- { os: windows-2022, generator: "Visual Studio 16 2019" }
# The following combinations are not available through install-boost
- { boost_version: "1.81.0", toolset: v143 }
include:
- { toolset: msvc-14.2, os: windows-2019, generator: "Visual Studio 16 2019", cxxstd: '17', build-type: 'Debug', build-shared-libs: 1 }
- { toolset: msvc-14.2, os: windows-2019, generator: "Visual Studio 16 2019", cxxstd: '17', build-type: 'Release', build-shared-libs: 0 }
- { toolset: msvc-14.3, os: windows-2022, generator: "Visual Studio 17 2022", cxxstd: '20', build-type: 'Debug', build-shared-libs: 0 }
- { toolset: msvc-14.3, os: windows-2022, generator: "Visual Studio 17 2022", cxxstd: '20', build-type: 'Release', build-shared-libs: 1 }
env:
CMAKE_BUILD_PARALLEL_LEVEL: 4
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
uses: actions/checkout@v4
- name: Add boost toolset to environment
if: contains(fromJson('["1.81.0"]'), matrix.boost_version)
run: echo BOOST_TOOLSET=$(echo "msvc") >> $GITHUB_ENV
- name: Setup Boost
run: python3 tools/ci.py setup-boost --source-dir=$(pwd)
# The platform_version passed to boost-install determines the msvc toolset version for which static libs are installed.
- name: Add boost platform version to environment
- name: Build a Boost distribution using B2
run: |
declare -A toolset_to_platform_version=( [v142]="2019" [v143]="2022" )
key=$(echo "${{matrix.toolset}}")
echo BOOST_PLATFORM_VERSION="${toolset_to_platform_version[$key]}" >> $GITHUB_ENV
python3 tools/ci.py build-b2-distro \
--toolset ${{ matrix.toolset }}
- name: Add boost install path to environment
run: echo BOOST_INSTALL_PATH="${GITHUB_WORKSPACE}/boost-${{matrix.boost_version}}${BOOST_TOOLSET}${BOOST_PLATFORM_VERSION}" >> $GITHUB_ENV
- name: Add build type configuration to environment
run: echo BUILD_CONFIG_ARG="--config ${{matrix.build_type}}" >> $GITHUB_ENV
- name: Cache Boost installation
id: cache-boost
uses: actions/cache@v3
with:
path: ${{env.BOOST_INSTALL_PATH}}
key: ${{matrix.boost_version}}${{env.BOOST_TOOLSET}}${{env.BOOST_PLATFORM_VERSION}}
- name: Install Boost
if: steps.cache-boost.outputs.cache-hit != 'true'
uses: MarkusJx/install-boost@v2.4.1
with:
boost_version: ${{matrix.boost_version}}
toolset: ${{env.BOOST_TOOLSET}}
boost_install_dir: ${{env.BOOST_INSTALL_PATH}}
platform_version: ${{env.BOOST_PLATFORM_VERSION}}
arch: null
- name: Install openssl
run: choco install openssl
- name: Create build directory
run: mkdir build
- name: Configure
working-directory: build
- name: Build a Boost distribution using CMake
run: |
cmake -T "${{matrix.toolset}}" \
-G "${{matrix.generator}}" \
${{matrix.config_args}} \
${BOOST_COMPILER_ARG}\
"${GITHUB_WORKSPACE}"
env:
BOOST_ROOT: ${{env.BOOST_INSTALL_PATH}}/boost
- name: Build
working-directory: build
python3 tools/ci.py build-cmake-distro \
--build-type ${{ matrix.build-type }} \
--cxxstd ${{ matrix.cxxstd }} \
--toolset ${{ matrix.toolset }} \
--generator "${{ matrix.generator }}" \
--build-shared-libs ${{ matrix.build-shared-libs }}
- name: Build the project tests
run: |
cmake --build . ${BUILD_CONFIG_ARG} ${{matrix.build_args}}
python3 tools/ci.py build-cmake-standalone-tests \
--build-type ${{ matrix.build-type }} \
--cxxstd ${{ matrix.cxxstd }} \
--toolset ${{ matrix.toolset }} \
--generator "${{ matrix.generator }}" \
--build-shared-libs ${{ matrix.build-shared-libs }}
posix:
# # TODO: re-enable this when a Redis server is available for this job
# - name: Run the project tests
# run: |
# python3 tools/ci.py run-cmake-standalone-tests \
# --build-type ${{ matrix.build-type }}
- name: Run add_subdirectory tests
run: |
python3 tools/ci.py run-cmake-add-subdirectory-tests \
--build-type ${{ matrix.build-type }} \
--cxxstd ${{ matrix.cxxstd }} \
--toolset ${{ matrix.toolset }} \
--generator "${{ matrix.generator }}" \
--build-shared-libs ${{ matrix.build-shared-libs }}
- name: Run find_package tests with the built cmake distribution
run: |
python3 tools/ci.py run-cmake-find-package-tests \
--build-type ${{ matrix.build-type }} \
--cxxstd ${{ matrix.cxxstd }} \
--toolset ${{ matrix.toolset }} \
--generator "${{ matrix.generator }}" \
--build-shared-libs ${{ matrix.build-shared-libs }}
- name: Run find_package tests with the built b2 distribution
run: |
python3 tools/ci.py run-cmake-b2-find-package-tests \
--build-type ${{ matrix.build-type }} \
--cxxstd ${{ matrix.cxxstd }} \
--toolset ${{ matrix.toolset }} \
--generator "${{ matrix.generator }}" \
--build-shared-libs ${{ matrix.build-shared-libs }}
windows-b2:
name: "B2 ${{matrix.toolset}}"
runs-on: ${{matrix.os}}
defaults:
run:
shell: bash
strategy:
fail-fast: false
matrix:
include:
- { toolset: msvc-14.2, os: windows-2019 }
- { toolset: msvc-14.3, os: windows-2022 }
env:
OPENSSL_ROOT: "C:\\Program Files\\OpenSSL"
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup user-config.jam
run: cp tools/user-config.jam "${HOMEDRIVE}${HOMEPATH}/"
- name: Setup Boost
run: python3 tools/ci.py setup-boost --source-dir=$(pwd)
- name: Build and run project tests using B2
run: |
python3 tools/ci.py run-b2-tests \
--toolset ${{ matrix.toolset }} \
--cxxstd 17,20 \
--variant debug,release
posix-cmake:
name: "CMake ${{ matrix.toolset }} ${{ matrix.cxxstd }} ${{ matrix.build-type }} ${{ matrix.cxxflags }}"
defaults:
run:
shell: bash
@@ -98,39 +131,183 @@ jobs:
fail-fast: false
matrix:
include:
- { toolset: gcc, compiler: g++-11, install: g++-11, os: ubuntu-22.04, cxxflags: '-std=c++17', ldflags: '' }
- { toolset: gcc, compiler: g++-11, install: g++-11, os: ubuntu-22.04, cxxflags: '-std=c++20', ldflags: '' }
- { toolset: clang, compiler: clang++-11, install: clang-11, os: ubuntu-22.04, cxxflags: '-std=c++17', ldflags: '' }
- { toolset: clang, compiler: clang++-11, install: clang-11, os: ubuntu-22.04, cxxflags: '-std=c++20', ldflags: '' }
- { toolset: clang, compiler: clang++-13, install: clang-13, os: ubuntu-22.04, cxxflags: '-std=c++17', ldflags: '' }
- { toolset: clang, compiler: clang++-13, install: clang-13, os: ubuntu-22.04, cxxflags: '-std=c++20', ldflags: '' }
- { toolset: clang, compiler: clang++-14, install: 'clang-14 libc++-14-dev libc++abi-14-dev', os: ubuntu-22.04, cxxflags: '-std=c++17 -stdlib=libc++', ldflags: '-lc++' }
- { toolset: clang, compiler: clang++-14, install: 'clang-14 libc++-14-dev libc++abi-14-dev', os: ubuntu-22.04, cxxflags: '-std=c++20 -stdlib=libc++', ldflags: '-lc++' }
- toolset: gcc-11
install: g++-11
os: ubuntu-latest
cxxstd: '17'
build-type: 'Debug'
ldflags: ''
- toolset: gcc-11
install: g++-11
os: ubuntu-latest
cxxstd: '20'
build-type: 'Release'
ldflags: ''
- toolset: clang-11
install: clang-11
os: ubuntu-latest
cxxstd: '17'
build-type: 'Debug'
ldflags: ''
- toolset: clang-11
install: clang-11
os: ubuntu-latest
cxxstd: '20'
build-type: 'Debug'
ldflags: ''
- toolset: clang-13
install: clang-13
os: ubuntu-latest
cxxstd: '17'
build-type: 'Release'
ldflags: ''
- toolset: clang-13
install: clang-13
os: ubuntu-latest
cxxstd: '20'
build-type: 'Release'
ldflags: ''
- toolset: clang-14
install: 'clang-14 libc++-14-dev libc++abi-14-dev'
os: ubuntu-latest
cxxstd: '17'
build-type: 'Debug'
cxxflags: '-stdlib=libc++'
ldflags: '-lc++'
- toolset: clang-14
install: 'clang-14 libc++-14-dev libc++abi-14-dev'
os: ubuntu-latest
cxxstd: '20'
build-type: 'Release'
cxxflags: '-stdlib=libc++'
ldflags: '-lc++'
runs-on: ${{ matrix.os }}
env:
CXXFLAGS: -g -O0 ${{matrix.cxxflags}} -Wall -Wextra
CXXFLAGS: ${{matrix.cxxflags}} -Wall -Wextra
LDFLAGS: ${{matrix.ldflags}}
CMAKE_BUILD_PARALLEL_LEVEL: 4
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install CMake
run: sudo apt-get -y install cmake
- name: Install protobuf
run: sudo apt-get -y install protobuf-compiler
- name: Install compiler
run: sudo apt-get install -y ${{ matrix.install }}
- name: Install Redis
run: sudo apt-get install -y redis-server
- name: Install boost
uses: MarkusJx/install-boost@v2.4.1
id: install-boost
with:
boost_version: 1.81.0
platform_version: 22.04
- name: Run CMake
uses: actions/checkout@v4
- name: Set up the required containers
run: |
BOOST_ROOT=${{steps.install-boost.outputs.BOOST_ROOT}} cmake -DCMAKE_CXX_COMPILER="${{matrix.compiler}}" -DCMAKE_CXX_FLAGS="${{env.CXXFLAGS}}" -DCMAKE_EXE_LINKER_FLAGS="${{env.LDFLAGS}}"
- name: Build
run: make
- name: Check
run: ctest --output-on-failure
docker compose -f tools/docker-compose.yml up -d --wait || (docker compose logs; exit 1)
- name: Install dependencies
run: |
docker exec builder apt-get update
docker exec builder apt-get -y --no-install-recommends install \
git \
g++ \
libssl-dev \
make \
ca-certificates \
cmake \
protobuf-compiler \
python3 \
${{ matrix.install }}
- name: Setup Boost
run: docker exec builder /boost-redis/tools/ci.py setup-boost --source-dir=/boost-redis
- name: Build a Boost distribution using B2
run: |
docker exec builder /boost-redis/tools/ci.py build-b2-distro \
--toolset ${{ matrix.toolset }}
- name: Build a Boost distribution using CMake
run: |
docker exec builder /boost-redis/tools/ci.py build-cmake-distro \
--build-type ${{ matrix.build-type }} \
--cxxstd ${{ matrix.cxxstd }} \
--toolset ${{ matrix.toolset }}
- name: Build the project tests
run: |
docker exec builder /boost-redis/tools/ci.py build-cmake-standalone-tests \
--build-type ${{ matrix.build-type }} \
--cxxstd ${{ matrix.cxxstd }} \
--toolset ${{ matrix.toolset }}
- name: Run the project tests
run: |
docker exec builder /boost-redis/tools/ci.py run-cmake-standalone-tests \
--build-type ${{ matrix.build-type }}
- name: Run add_subdirectory tests
run: |
docker exec builder /boost-redis/tools/ci.py run-cmake-add-subdirectory-tests \
--build-type ${{ matrix.build-type }} \
--cxxstd ${{ matrix.cxxstd }} \
--toolset ${{ matrix.toolset }}
- name: Run find_package tests with the built cmake distribution
run: |
docker exec builder /boost-redis/tools/ci.py run-cmake-find-package-tests \
--build-type ${{ matrix.build-type }} \
--cxxstd ${{ matrix.cxxstd }} \
--toolset ${{ matrix.toolset }}
- name: Run find_package tests with the built b2 distribution
run: |
docker exec builder /boost-redis/tools/ci.py run-cmake-b2-find-package-tests \
--build-type ${{ matrix.build-type }} \
--cxxstd ${{ matrix.cxxstd }} \
--toolset ${{ matrix.toolset }}
posix-b2:
name: "B2 ${{ matrix.toolset }}"
defaults:
run:
shell: bash
strategy:
fail-fast: false
matrix:
include:
- toolset: gcc-11
install: g++-11
cxxstd: "11,17,20" # Having C++11 shouldn't break the build
os: ubuntu-latest
container: ubuntu:22.04
- toolset: clang-14
install: clang-14
os: ubuntu-latest
container: ubuntu:22.04
cxxstd: "17,20"
runs-on: ${{ matrix.os }}
container: ${{matrix.container}}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup container environment
if: matrix.container
run: |
apt-get update
apt-get -y install sudo python3 git g++ libssl-dev
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get -y install python3 ${{ matrix.install }}
- name: Setup Boost
run: ./tools/ci.py setup-boost --source-dir=$(pwd)
- name: Build and run project tests using B2
run: |
python3 tools/ci.py run-b2-tests \
--toolset ${{ matrix.toolset }} \
--cxxstd ${{ matrix.cxxstd }} \
--variant debug,release

View File

@@ -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

View File

@@ -1,264 +1,142 @@
cmake_minimum_required(VERSION 3.14)
#set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CMAKE_COMMAND} -E time")
cmake_minimum_required(VERSION 3.8...3.20)
# determine whether it's main/root project
# or being built under another project.
if (NOT DEFINED BOOST_REDIS_MAIN_PROJECT)
set(BOOST_REDIS_MAIN_PROJECT OFF)
if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
set(BOOST_REDIS_MAIN_PROJECT ON)
endif()
set(BOOST_REDIS_MAIN_PROJECT OFF)
if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
set(BOOST_REDIS_MAIN_PROJECT ON)
endif()
endif()
project(
boost_redis
VERSION 1.4.1
DESCRIPTION "A redis client library"
HOMEPAGE_URL "https://boostorg.github.io/redis/"
LANGUAGES CXX
)
option(BOOST_REDIS_INSTALL "Generate install targets." ${BOOST_REDIS_MAIN_PROJECT})
option(BOOST_REDIS_TESTS "Build tests." ${BOOST_REDIS_MAIN_PROJECT})
option(BOOST_REDIS_EXAMPLES "Build examples." ${BOOST_REDIS_MAIN_PROJECT})
option(BOOST_REDIS_BENCHMARKS "Build benchmarks." ${BOOST_REDIS_MAIN_PROJECT})
option(BOOST_REDIS_DOC "Generate documentations." ${BOOST_REDIS_MAIN_PROJECT})
project(boost_redis VERSION "${BOOST_SUPERPROJECT_VERSION}" LANGUAGES CXX)
# Library
add_library(boost_redis INTERFACE)
add_library(Boost::redis ALIAS boost_redis)
target_include_directories(boost_redis INTERFACE
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)
target_link_libraries(
boost_redis
INTERFACE
Boost::asio
Boost::assert
Boost::config
Boost::core
Boost::mp11
Boost::system
Boost::utility
)
target_include_directories(boost_redis INTERFACE include)
target_compile_features(boost_redis INTERFACE cxx_std_17)
# Asio bases C++ feature detection on __cplusplus. Make MSVC
# define it correctly
if (MSVC)
target_compile_options(boost_redis INTERFACE /Zc:__cplusplus)
endif()
# Dependencies
if (BOOST_REDIS_MAIN_PROJECT)
# TODO: Understand why we have to list all dependencies below
# instead of
#set(BOOST_INCLUDE_LIBRARIES redis)
#set(BOOST_EXCLUDE_LIBRARIES redis)
#add_subdirectory(../.. boostorg/boost EXCLUDE_FROM_ALL)
find_package(Boost 1.80 REQUIRED)
set(deps
system
assert
config
throw_exception
asio
variant2
mp11
winapi
predef
align
context
core
coroutine
static_assert
pool
date_time
smart_ptr
exception
integer
move
type_traits
algorithm
utility
io
lexical_cast
numeric/conversion
mpl
range
tokenizer
tuple
array
bind
concept_check
function
iterator
regex
unordered
preprocessor
container
conversion
container_hash
detail
optional
function_types
fusion
intrusive
describe
typeof
functional
test
json
endian
)
include_directories(${Boost_INCLUDE_DIRS})
foreach(dep IN LISTS deps)
add_subdirectory(../${dep} boostorg/${dep})
endforeach()
find_package(OpenSSL REQUIRED)
include_directories(include)
# Common
#=======================================================================
add_library(boost_redis_project_options INTERFACE)
target_link_libraries(boost_redis_project_options INTERFACE OpenSSL::Crypto OpenSSL::SSL)
if (MSVC)
target_compile_options(boost_redis_project_options INTERFACE /bigobj)
target_compile_definitions(boost_redis_project_options INTERFACE _WIN32_WINNT=0x0601)
endif()
add_library(boost_redis_src STATIC examples/boost_redis.cpp)
target_compile_features(boost_redis_src PRIVATE cxx_std_17)
target_link_libraries(boost_redis_src PRIVATE boost_redis_project_options)
# Executables
#=======================================================================
if (BOOST_REDIS_BENCHMARKS)
add_library(benchmarks_options INTERFACE)
target_link_libraries(benchmarks_options INTERFACE boost_redis_src)
target_link_libraries(benchmarks_options INTERFACE boost_redis_project_options)
target_compile_features(benchmarks_options INTERFACE cxx_std_20)
add_executable(echo_server_client benchmarks/cpp/asio/echo_server_client.cpp)
target_link_libraries(echo_server_client PRIVATE benchmarks_options)
add_executable(echo_server_direct benchmarks/cpp/asio/echo_server_direct.cpp)
target_link_libraries(echo_server_direct PRIVATE benchmarks_options)
endif()
if (BOOST_REDIS_EXAMPLES)
add_library(examples_main STATIC examples/main.cpp)
target_compile_features(examples_main PRIVATE cxx_std_20)
target_link_libraries(examples_main PRIVATE boost_redis_project_options)
macro(make_example EXAMPLE_NAME STANDARD)
add_executable(${EXAMPLE_NAME} examples/${EXAMPLE_NAME}.cpp)
target_link_libraries(${EXAMPLE_NAME} PRIVATE boost_redis_src)
target_link_libraries(${EXAMPLE_NAME} PRIVATE boost_redis_project_options)
target_compile_features(${EXAMPLE_NAME} PRIVATE cxx_std_${STANDARD})
if (${STANDARD} STREQUAL "20")
target_link_libraries(${EXAMPLE_NAME} PRIVATE examples_main)
endif()
endmacro()
macro(make_testable_example EXAMPLE_NAME STANDARD)
make_example(${EXAMPLE_NAME} ${STANDARD})
add_test(${EXAMPLE_NAME} ${EXAMPLE_NAME})
endmacro()
make_testable_example(cpp17_intro 17)
make_testable_example(cpp17_intro_sync 17)
make_testable_example(cpp20_intro 20)
make_testable_example(cpp20_containers 20)
make_testable_example(cpp20_json 20)
make_testable_example(cpp20_intro_tls 20)
make_example(cpp20_subscriber 20)
make_example(cpp20_streams 20)
make_example(cpp20_echo_server 20)
make_example(cpp20_resolve_with_sentinel 20)
# We test the protobuf example only on gcc.
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
find_package(Protobuf)
if (Protobuf_FOUND)
protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS examples/person.proto)
make_testable_example(cpp20_protobuf 20)
target_sources(cpp20_protobuf PUBLIC ${PROTO_SRCS} ${PROTO_HDRS})
target_link_libraries(cpp20_protobuf PRIVATE ${Protobuf_LIBRARIES})
target_include_directories(cpp20_protobuf PUBLIC ${Protobuf_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR})
endif()
find_package(Threads REQUIRED)
find_package(OpenSSL REQUIRED)
target_link_libraries(boost_redis
INTERFACE
Boost::system
Boost::asio
Threads::Threads
OpenSSL::Crypto
OpenSSL::SSL
)
else()
# If we're in the superproject or called from add_subdirectory,
# Boost dependencies should be already available.
# If other dependencies are not found, we bail out
find_package(Threads)
if(NOT Threads_FOUND)
message(STATUS "Boost.Redis has been disabled, because the required package Threads hasn't been found")
return()
endif()
find_package(OpenSSL)
if(NOT OpenSSL_FOUND)
message(STATUS "Boost.Redis has been disabled, because the required package OpenSSL hasn't been found")
return()
endif()
if (NOT MSVC)
make_example(cpp20_chat_room 20)
endif()
endif()
if (BOOST_REDIS_TESTS)
enable_testing()
add_library(tests_common STATIC tests/common.cpp)
target_compile_features(tests_common PRIVATE cxx_std_17)
target_link_libraries(tests_common PRIVATE boost_redis_project_options)
macro(make_test TEST_NAME STANDARD)
add_executable(${TEST_NAME} tests/${TEST_NAME}.cpp)
target_link_libraries(${TEST_NAME} PRIVATE boost_redis_src tests_common)
target_link_libraries(${TEST_NAME} PRIVATE boost_redis_project_options)
target_compile_features(${TEST_NAME} PRIVATE cxx_std_${STANDARD})
add_test(${TEST_NAME} ${TEST_NAME})
endmacro()
make_test(test_conn_quit 17)
make_test(test_conn_tls 17)
make_test(test_low_level 17)
make_test(test_conn_exec_retry 17)
make_test(test_conn_exec_error 17)
make_test(test_request 17)
make_test(test_run 17)
make_test(test_low_level_sync 17)
make_test(test_low_level_sync_sans_io 17)
make_test(test_conn_check_health 17)
make_test(test_conn_exec 20)
make_test(test_conn_push 20)
make_test(test_conn_reconnect 20)
make_test(test_conn_exec_cancel 20)
make_test(test_conn_exec_cancel2 20)
make_test(test_conn_echo_stress 20)
make_test(test_low_level_async 20)
make_test(test_conn_run_cancel 20)
make_test(test_issue_50 20)
endif()
# Install
#=======================================================================
if (BOOST_REDIS_INSTALL)
install(TARGETS boost_redis
EXPORT boost_redis
PUBLIC_HEADER DESTINATION include COMPONENT Development
)
include(CMakePackageConfigHelpers)
configure_package_config_file(
"${PROJECT_SOURCE_DIR}/cmake/BoostRedisConfig.cmake.in"
"${PROJECT_BINARY_DIR}/BoostRedisConfig.cmake"
INSTALL_DESTINATION lib/cmake/boost/redis
)
install(EXPORT boost_redis DESTINATION lib/cmake/boost/redis)
install(FILES "${PROJECT_BINARY_DIR}/BoostRedisConfigVersion.cmake"
"${PROJECT_BINARY_DIR}/BoostRedisConfig.cmake"
DESTINATION lib/cmake/boost/redis)
install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/ DESTINATION include)
include(CMakePackageConfigHelpers)
write_basic_package_version_file(
"${PROJECT_BINARY_DIR}/BoostRedisConfigVersion.cmake"
COMPATIBILITY AnyNewerVersion
)
include(CPack)
endif()
# Doxygen
#=======================================================================
if (BOOST_REDIS_DOC)
set(DOXYGEN_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/doc")
configure_file(doc/Doxyfile.in doc/Doxyfile @ONLY)
add_custom_target(
doc
COMMAND doxygen "${PROJECT_BINARY_DIR}/doc/Doxyfile"
COMMENT "Building documentation using Doxygen"
WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}"
VERBATIM
# This is generated by boostdep
target_link_libraries(boost_redis
INTERFACE
Boost::asio
Boost::assert
Boost::core
Boost::mp11
Boost::system
Boost::throw_exception
Threads::Threads
OpenSSL::Crypto
OpenSSL::SSL
)
endif()
# Coverage
#=======================================================================
# Enable testing. If we're being called from the superproject, this has already been done
if (BOOST_REDIS_MAIN_PROJECT)
include(CTest)
endif()
set(
COVERAGE_TRACE_COMMAND
lcov --capture
-output-file "${PROJECT_BINARY_DIR}/coverage.info"
--directory "${PROJECT_BINARY_DIR}"
--include "${PROJECT_SOURCE_DIR}/include/*"
)
# Most tests require a running Redis server, so we only run them if we're the main project
if(BOOST_REDIS_MAIN_PROJECT AND BUILD_TESTING)
# Tests and common utilities
add_subdirectory(test)
set(
COVERAGE_HTML_COMMAND
genhtml --legend -f -q
"${PROJECT_BINARY_DIR}/coverage.info"
--prefix "${PROJECT_SOURCE_DIR}"
--output-directory "${PROJECT_BINARY_DIR}/coverage_html"
)
add_custom_target(
coverage
COMMAND ${COVERAGE_TRACE_COMMAND}
COMMAND ${COVERAGE_HTML_COMMAND}
COMMENT "Generating coverage report"
VERBATIM
)
# TODO
#=======================================================================
#.PHONY: bench
#bench:
# pdflatex --jobname=echo-f0 benchmarks/benchmarks.tex
# pdflatex --jobname=echo-f1 benchmarks/benchmarks.tex
# pdftoppm {input.pdf} {output.file} -png
# Benchmarks. Build them with tests to prevent code rotting
add_subdirectory(benchmarks)
# Examples
add_subdirectory(example)
endif()

View File

@@ -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"] }

View File

@@ -1,15 +1,13 @@
# boost_redis
# Boost.Redis
Boost.Redis is a high-level [Redis](https://redis.io/) client library built on top of
[Boost.Asio](https://www.boost.org/doc/libs/release/doc/html/boost_asio.html)
that implements Redis plain text protocol
that implements the Redis protocol
[RESP3](https://github.com/redis/redis-specifications/blob/master/protocol/RESP3.md).
It can multiplex any number of client
requests, responses, and server pushes onto a single active socket
connection to the Redis server. The requirements for using Boost.Redis are
The requirements for using Boost.Redis are:
* Boost 1.81 or greater.
* C++17 minimum.
* Boost. The library is included in Boost distributions starting with 1.84.
* C++17 or higher.
* Redis 6 or higher (must support RESP3).
* Gcc (10, 11, 12), Clang (11, 13, 14) and Visual Studio (16 2019, 17 2022).
* Have basic-level knowledge about [Redis](https://redis.io/docs/)
@@ -29,7 +27,7 @@ examples and tests cmake is supported, for example
```cpp
# Linux
$ BOOST_ROOT=/opt/boost_1_81_0 cmake --preset g++-11
$ BOOST_ROOT=/opt/boost_1_84_0 cmake --preset g++-11
# Windows
$ cmake -G "Visual Studio 17 2022" -A x64 -B bin64 -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake
@@ -82,8 +80,9 @@ them are
* [Client-side caching](https://redis.io/docs/manual/client-side-caching/)
The connection class supports server pushes by means of the
`boost::redis::connection::async_receive` function, the coroutine shows how
to used it
`boost::redis::connection::async_receive` function, which can be
called in the same connection that is being used to execute commands.
The coroutine below shows how to used it
```cpp
auto
@@ -92,6 +91,9 @@ receiver(std::shared_ptr<connection> conn) -> net::awaitable<void>
request req;
req.push("SUBSCRIBE", "channel");
generic_response resp;
conn->set_receive_response(resp);
// Loop while reconnection is enabled
while (conn->will_reconnect()) {
@@ -99,7 +101,7 @@ receiver(std::shared_ptr<connection> conn) -> net::awaitable<void>
co_await conn->async_exec(req, ignore, net::deferred);
// Loop reading Redis pushes.
for (generic_response resp;;) {
for (;;) {
error_code ec;
co_await conn->async_receive(resp, net::redirect_error(net::use_awaitable, ec));
if (ec)
@@ -108,7 +110,7 @@ receiver(std::shared_ptr<connection> conn) -> net::awaitable<void>
// Use the response resp in some way and then clear it.
...
resp.value().clear();
consume_one(resp);
}
}
}
@@ -674,7 +676,69 @@ https://lists.boost.org/Archives/boost/2023/01/253944.php.
## Changelog
### develop (incorporates changes to conform the boost review and more)
### Boost 1.85
* ([Issue 170](https://github.com/boostorg/redis/issues/170))
Under load and on low-latency networks it is possible to start
receiving responses before the write operation completed and while
the request is still marked as staged and not written. This messes
up with the heuristics that classifies responses as unsolicied or
not.
* ([Issue 168](https://github.com/boostorg/redis/issues/168)).
Provides a way of passing a custom SSL context to the connection.
The design here differs from that of Boost.Beast and Boost.MySql
since in Boost.Redis the connection owns the context instead of only
storing a reference to a user provided one. This is ok so because
apps need only one connection for their entire application, which
makes the overhead of one ssl-context per connection negligible.
* ([Issue 181](https://github.com/boostorg/redis/issues/181)).
See a detailed description of this bug in
[this](https://github.com/boostorg/redis/issues/181#issuecomment-1913346983)
comment.
* ([Issue 182](https://github.com/boostorg/redis/issues/182)).
Sets `"default"` as the default value of `config::username`. This
makes it simpler to use the `requirepass` configuration in Redis.
* ([Issue 189](https://github.com/boostorg/redis/issues/189)).
Fixes narrowing convertion by using `std::size_t` instead of
`std::uint64_t` for the sizes of bulks and aggregates. The code
relies now on `std::from_chars` returning an error if a value
greater than 32 is received on platforms on which the size
of`std::size_t` is 32.
### Boost 1.84 (First release in Boost)
* Deprecates the `async_receive` overload that takes a response. Users
should now first call `set_receive_response` to avoid constantly and
unnecessarily setting the same response.
* Uses `std::function` to type erase the response adapter. This change
should not influence users in any way but allowed important
simplification in the connections internals. This resulted in
massive performance improvement.
* The connection has a new member `get_usage()` that returns the
connection usage information, such as number of bytes written,
received etc.
* There are massive performance improvements in the consuming of
server pushes which are now communicated with an `asio::channel` and
therefore can be buffered which avoids blocking the socket read-loop.
Batch reads are also supported by means of `channel.try_send` and
buffered messages can be consumed synchronously with
`connection::receive`. The function `boost::redis::cancel_one` has
been added to simplify processing multiple server pushes contained
in the same `generic_response`. *IMPORTANT*: These changes may
result in more than one push in the response when
`connection::async_receive` resumes. The user must therefore be
careful when calling `resp.clear()`: either ensure that all message
have been processed or just use `consume_one`.
### v1.4.2 (incorporates changes to conform the boost review and more)
* Adds `boost::redis::config::database_index` to make it possible to
choose a database before starting running commands e.g. after an

20
benchmarks/CMakeLists.txt Normal file
View 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

View File

@@ -1,4 +0,0 @@
@PACKAGE_INIT@
include("${CMAKE_CURRENT_LIST_DIR}/Aedis.cmake")
check_required_components("@PROJECT_NAME@")

File diff suppressed because it is too large Load Diff

92
doc/Jamfile Normal file
View File

@@ -0,0 +1,92 @@
project redis/doc ;
import doxygen ;
import path ;
import sequence ;
# All paths must be absolute to work well with the Doxygen rules.
path-constant this_dir : . ;
path-constant target_dir : html ;
path-constant redis_root_dir : .. ;
path-constant include_dir : ../include ;
path-constant examples_dir : ../example ;
path-constant readme : ../README.md ;
path-constant layout_file : DoxygenLayout.xml ;
path-constant header : header.html ;
path-constant footer : footer.html ;
local stylesheet_files = [ path.glob $(this_dir) : *.css ] ;
local includes = [ path.glob-tree $(include_dir) : *.hpp *.cpp ] ;
local examples = [ path.glob-tree $(examples_dir) : *.hpp *.cpp ] ;
# If passed directly, several HTML_EXTRA_STYLESHEET tags are generated,
# which is not correct.
local stylesheet_arg = [ sequence.join "\"$(stylesheet_files)\"" : " " ] ;
# The doxygen rule requires the target name to end in .html to generate HTML files
doxygen doc.html
:
$(includes) $(examples) $(readme)
:
<doxygen:param>"PROJECT_NAME=Boost.Redis"
<doxygen:param>PROJECT_NUMBER="1.84.0"
<doxygen:param>PROJECT_BRIEF="A redis client library"
<doxygen:param>"STRIP_FROM_PATH=\"$(redis_root_dir)\""
<doxygen:param>"STRIP_FROM_INC_PATH=\"$(include_dir)\""
<doxygen:param>BUILTIN_STL_SUPPORT=YES
<doxygen:param>INLINE_SIMPLE_STRUCTS=YES
<doxygen:param>HIDE_UNDOC_MEMBERS=YES
<doxygen:param>HIDE_UNDOC_CLASSES=YES
<doxygen:param>SHOW_HEADERFILE=YES
<doxygen:param>SORT_BRIEF_DOCS=YES
<doxygen:param>SORT_MEMBERS_CTORS_1ST=YES
<doxygen:param>SHOW_FILES=NO
<doxygen:param>SHOW_NAMESPACES=NO
<doxygen:param>"LAYOUT_FILE=\"$(layout_file)\""
<doxygen:param>WARN_IF_INCOMPLETE_DOC=YES
<doxygen:param>FILE_PATTERNS="*.hpp *.cpp"
<doxygen:param>EXCLUDE_SYMBOLS=std
<doxygen:param>"USE_MDFILE_AS_MAINPAGE=\"$(readme)\""
<doxygen:param>SOURCE_BROWSER=YES
<doxygen:param>"HTML_HEADER=\"$(header)\""
<doxygen:param>"HTML_FOOTER=\"$(footer)\""
<doxygen:param>"HTML_EXTRA_STYLESHEET=$(stylesheet_arg)"
<doxygen:param>HTML_TIMESTAMP=YES
<doxygen:param>GENERATE_TREEVIEW=YES
<doxygen:param>FULL_SIDEBAR=YES
<doxygen:param>DISABLE_INDEX=YES
<doxygen:param>ENUM_VALUES_PER_LINE=0
<doxygen:param>OBFUSCATE_EMAILS=YES
<doxygen:param>USE_MATHJAX=YES
<doxygen:param>MATHJAX_VERSION=MathJax_2
<doxygen:param>MATHJAX_RELPATH="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/"
<doxygen:param>MACRO_EXPANSION=YES
<doxygen:param>HAVE_DOT=NO
<doxygen:param>CLASS_GRAPH=NO
<doxygen:param>DIRECTORY_GRAPH=NO
;
explicit doc.html ;
# The doxygen rule only informs b2 about the main HTML file, and not about
# all the doc directory that gets generated. Using the install rule copies
# only a single file, which is incorrect. This is a workaround to copy
# the generated docs to the doc/html directory, where they should be.
make copyhtml.tag : doc.html : @copy_html_dir ;
explicit copyhtml.tag ;
actions copy_html_dir
{
rm -rf $(target_dir)
mkdir -p $(target_dir)
cp -r $(<:D)/html/doc/* $(target_dir)/
echo "Stamped" > "$(<)"
}
# These are used to inform the build system of the
# means to build the integrated and stand-alone docs.
alias boostdoc ;
explicit boostdoc ;
alias boostrelease : copyhtml.tag ;
explicit boostrelease ;

View File

@@ -32,80 +32,104 @@ html {
* Make sure it is wide enough to contain the page title (logo + title + version)
*/
--side-nav-fixed-width: 335px;
--menu-display: none;
--top-height: 120px;
--toc-sticky-top: -25px;
--toc-max-height: calc(100vh - 2 * var(--spacing-medium) - 25px);
}
#projectname {
white-space: nowrap;
}
#page-wrapper {
height: calc(100vh - 100px);
display: flex;
flex-direction: column;
}
@media screen and (min-width: 768px) {
#content-wrapper {
display: flex;
flex-direction: row;
min-height: 0;
}
#doc-content {
overflow-y: scroll;
flex: 1;
height: auto !important;
}
@media (min-width: 768px) {
html {
--searchbar-background: var(--page-background-color);
}
#side-nav {
#sidebar-wrapper {
display: flex;
flex-direction: column;
min-width: var(--side-nav-fixed-width);
max-width: var(--side-nav-fixed-width);
top: var(--top-height);
overflow: visible;
max-width: var(--side-nav-fixed-width);
background-color: var(--side-nav-background);
border-right: 1px solid rgb(222, 222, 222);
}
#nav-tree, #side-nav {
height: calc(100vh - var(--top-height)) !important;
#search-box-wrapper {
display: flex;
flex-direction: row;
padding-left: 1em;
padding-right: 1em;
}
#MSearchBox {
flex: 1;
display: flex;
padding-left: 1em;
padding-right: 1em;
}
#MSearchBox .left {
display: flex;
flex: 1;
position: static;
align-items: center;
justify-content: flex-start;
width: auto;
height: auto;
}
#MSearchBox .right {
display: none;
}
#MSearchSelect {
padding-left: 0.75em;
left: auto;
background-repeat: no-repeat;
}
#MSearchField {
flex: 1;
position: static;
width: auto;
height: auto;
}
#nav-tree {
padding: 0;
height: auto !important;
}
#nav-sync {
display: none;
}
#top {
display: block;
border-bottom: none;
height: var(--top-height);
margin-bottom: calc(0px - var(--top-height));
max-width: var(--side-nav-fixed-width);
overflow: hidden;
background: var(--side-nav-background);
}
#main-nav {
float: left;
padding-right: 0;
}
.ui-resizable-handle {
cursor: default;
width: 1px !important;
box-shadow: 0 calc(-2 * var(--top-height)) 0 0 var(--separator-color);
}
#nav-path {
position: fixed;
right: 0;
left: var(--side-nav-fixed-width);
bottom: 0;
width: auto;
}
#doc-content {
height: calc(100vh - 31px) !important;
padding-bottom: calc(3 * var(--spacing-large));
padding-top: calc(var(--top-height) - 80px);
box-sizing: border-box;
margin-left: var(--side-nav-fixed-width) !important;
}
#MSearchBox {
width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium)));
}
#MSearchField {
width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium)) - 65px);
}
#MSearchResultsWindow {
@@ -113,3 +137,9 @@ html {
right: auto;
}
}
@media (max-width: 768px) {
#sidebar-wrapper {
display: none;
}
}

View File

@@ -552,25 +552,6 @@ a.anchor {
margin-top: 0;
}
/* until Doxygen 1.9.4 */
.left img#MSearchSelect {
left: 0;
user-select: none;
padding-left: 8px;
}
/* Doxygen 1.9.5 */
.left span#MSearchSelect {
left: 0;
user-select: none;
margin-left: 8px;
padding: 0;
}
.left #MSearchSelect[src$=".png"] {
padding-left: 0
}
.SelectionMark {
user-select: none;
}
@@ -614,9 +595,7 @@ a.anchor {
#MSearchField {
font-size: var(--navigation-font-size);
height: calc(var(--searchbar-height) - 2px);
background: transparent;
width: calc(var(--searchbar-width) - 64px);
}
.MSearchBoxActive #MSearchField {

19
doc/footer.html Normal file
View File

@@ -0,0 +1,19 @@
<!-- HTML footer for doxygen 1.9.1-->
<!-- start footer part -->
</div> <!-- close #content-wrapper -->
<!--BEGIN GENERATE_TREEVIEW-->
<div id="nav-path" class="navpath"><!-- id is needed for treeview function! -->
<ul>
$navpath
<li class="footer">$generatedby <a href="https://www.doxygen.org/index.html"><img class="footer" src="$relpath^doxygen.svg" width="104" height="31" alt="doxygen"/></a> $doxygenversion </li>
</ul>
</div>
<!--END GENERATE_TREEVIEW-->
<!--BEGIN !GENERATE_TREEVIEW-->
<hr class="footer"/><address class="footer"><small>
$generatedby&#160;<a href="https://www.doxygen.org/index.html"><img class="footer" src="$relpath^doxygen.svg" width="104" height="31" alt="doxygen"/></a> $doxygenversion
</small></address>
<!--END !GENERATE_TREEVIEW-->
</div> <!-- #page-wrapper -->
</body>
</html>

61
doc/header.html Normal file
View File

@@ -0,0 +1,61 @@
<!-- HTML header for doxygen 1.9.1-->
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=9"/>
<meta name="generator" content="Doxygen $doxygenversion"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<!--BEGIN PROJECT_NAME--><title>$projectname: $title</title><!--END PROJECT_NAME-->
<!--BEGIN !PROJECT_NAME--><title>$title</title><!--END !PROJECT_NAME-->
<link href="$relpath^tabs.css" rel="stylesheet" type="text/css"/>
<script type="text/javascript" src="$relpath^jquery.js"></script>
<script type="text/javascript" src="$relpath^dynsections.js"></script>
$treeview
$search
$mathjax
<link href="$relpath^$stylesheet" rel="stylesheet" type="text/css" />
$extrastylesheet
</head>
<body>
<div id="page-wrapper">
<div id="content-wrapper">
<div id="sidebar-wrapper">
<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
<!--BEGIN TITLEAREA-->
<div id="titlearea">
<table cellspacing="0" cellpadding="0">
<tbody>
<tr style="height: 56px;">
<!--BEGIN PROJECT_LOGO-->
<td id="projectlogo"><img alt="Logo" src="$relpath^$projectlogo"/></td>
<!--END PROJECT_LOGO-->
<!--BEGIN PROJECT_NAME-->
<td id="projectalign" style="padding-left: 0.5em;">
<div id="projectname">$projectname
<!--BEGIN PROJECT_NUMBER-->&#160;<span id="projectnumber">$projectnumber</span><!--END PROJECT_NUMBER-->
</div>
<!--BEGIN PROJECT_BRIEF--><div id="projectbrief">$projectbrief</div><!--END PROJECT_BRIEF-->
</td>
<!--END PROJECT_NAME-->
<!--BEGIN !PROJECT_NAME-->
<!--BEGIN PROJECT_BRIEF-->
<td style="padding-left: 0.5em;">
<div id="projectbrief">$projectbrief</div>
</td>
<!--END PROJECT_BRIEF-->
<!--END !PROJECT_NAME-->
<!--BEGIN DISABLE_INDEX-->
<!--END DISABLE_INDEX-->
</tr>
</tbody>
</table>
</div>
<!--BEGIN SEARCHENGINE-->
<div id="search-box-wrapper">
$searchbox
</div>
<!--END SEARCHENGINE-->
<!--END TITLEAREA-->
<!-- end header part -->

View File

@@ -0,0 +1,671 @@
# On the costs of asynchronous abstractions
The biggest force behind the evolution of
[Boost.Redis](https://github.com/boostorg/redis) was my struggling in
coming up with a high-level connection abstraction that was capable of
multiplexing Redis commands from independent sources while
concurrently handling server pushes. This journey taught me many
important lessons, many of which are related to the design and
performance of asynchronous programs based on Boost.Asio.
In this article I will share some of the lessons learned, specially
those related to the performance costs of _abstractions_ such as
`async_read_until` that tend to overschedule into the event-loop. In
this context I will also briefly comment on how the topics discussed
here influenced my views on the proposed
[P2300](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2300r7.html)
(a.k.a. Senders and Receivers), which is likely to become the basis of
networking in upcoming C++ standards.
Although the analysis presented in this article uses the Redis communication
protocol for illustration I expect it to be useful in general since
[RESP3](https://github.com/antirez/RESP3/blob/master/spec.md) shares
many similarities with other widely used protocols such as HTTP.
## Parsing `\r\n`-delimited messages
The Redis server communicates with its clients by exchanging data
serialized in
[RESP3](https://github.com/antirez/RESP3/blob/master/spec.md) format.
Among the data types supported by this specification, the
`\r\n`-delimited messages are some of the most frequent in a typical
session. The table below shows some examples
Command | Response | Wire format | RESP3 name
---------|----------|---------------|---------------------
PING | PONG | `+PONG\r\n` | simple-string
INCR | 42 | `:42\r\n` | number
GET | null | `_\r\n` | null
Redis also supports command pipelines, which provide a way of
optimizing round-trip times by batching commands. A pipeline composed
by the commands shown in the previous table look like this
```
| Sent in a |
| single write |
+--------+ | | +-------+
| | --------> PING + INCR + GET --------> | |
| | | |
| Client | | Redis |
| | | |
| | <-------- "+PONG\r\n:42\r\n_\r\n" <-------- | |
+--------+ |<------>|<---->|<-->| +-------+
| |
| Responses |
```
Messages that use delimiters are so common in networking that a
facility called `async_read_until` for reading them incrementally from
a socket is already part of Boost.Asio. The coroutine below uses it to
print message contents to the screen
```cpp
awaitable<void> parse_resp3_simple_msgs(tcp::socket socket)
{
for (std::string buffer;;) {
auto n = co_await async_read_until(socket, dynamic_buffer(buffer), "\r\n");
std::cout << buffer.substr(1, n - 3) << std::endl;
// Consume the buffer.
buffer.erase(0, n);
}
}
```
If we pay attention to the buffer content as it is parsed by the code
above we can see it is rotated fairly often, for example
```
"+PONG\r\n:100\r\n+OK\r\n_\r\n"
":100\r\n+OK\r\n_\r\n"
"+OK\r\n_\r\n"
"_\r\n"
""
```
When I first realized these, apparently excessive, buffer rotations I
was concerned they would impact the performance of Boost.Redis in a
severe way. To measure the magnitude of this impact I came up with an
experimental implementation of Asio's `dynamic_buffer` that consumed
the buffer less eagerly than the `std::string::erase` function used
above. For that, the implementation increased a buffer offset up
to a certain threshold and only then triggered a (larger) rotation.
This is illustrated in the diagram below
```
|<---- offset threshold ---->|
| |
"+PONG\r\n:100\r\n+OK\r\n_\r\n+PONG\r\n"
| # Initial offset
"+PONG\r\n:100\r\n+OK\r\n_\r\n+PONG\r\n"
|<------>| # After 1st message
"+PONG\r\n:100\r\n+OK\r\n_\r\n+PONG\r\n"
|<-------------->| # After 2nd message
"+PONG\r\n:100\r\n+OK\r\n_\r\n+PONG\r\n"
|<--------------------->| # After 3rd message
"+PONG\r\n:100\r\n+OK\r\n_\r\n+PONG\r\n"
|<-------------------------->| # Threshold crossed after the 4th message
"+PONG\r\n"
| # After rotation
```
After comparing the performance differences between the two versions I
was surprised there wasn't any! But that was also very suspicious
since some RESP3 aggregate types contain a considerable number of
separators. For example, a map with two pairs `[(key1, value1),
(key2, value2)]` encoded in RESP3 requires ten rotations in total
```
"%2\r\n$4\r\nkey1\r\n$6\r\nvalue1\r\n$4\r\nkey2\r\n$6\r\nvalue2\r\n"
"$4\r\nkey1\r\n$6\r\nvalue1\r\n$4\r\nkey2\r\n$6\r\nvalue2\r\n"
"key1\r\n$6\r\nvalue1\r\n$4\r\nkey2\r\n$6\r\nvalue2\r\n"
"$6\r\nvalue1\r\n$4\r\nkey2\r\n$6\r\nvalue2\r\n"
...
```
It was evident something more costly was shadowing the buffer
rotations. But it couldn't be the search for the separator since it
performs equivalently to rotations. It is also easy to show that the
overhead is not related to any IO operation since the problem persists
if the buffer is never consumed (which causes the function to be
called with the same string repeatedly). Once these two factors
are removed from the table, we are driven into the conclusion that
calling `async_read_until` has an intrinsic cost, let us see what
that is.
### Async operations that complete synchronously considered harmful
Assume the scenario described earlier where `async_read_until` is used
to parse multiple `\r\n`-delimited messages. The following is a
detailed description of what happens behind the scenes
1. `async_read_until` calls `socket.async_read_some` repeatedly
until the separator `\r\n` shows up in the buffer
```
"<read1>" # Read 1: needs more data.
"<read1><read2>" # Read 2: needs more data.
"<read1><read2>" # Read 3: needs more data.
"<read1><read2><read3>" # Read 4: needs more data.
"<read1><read2><read3>\r\n<bonus bytes>" # separator found, done.
```
2. The last call to `socket.async_read_some` happens to read past
the separator `\r\n` (depicted as `<bonus bytes>` above),
resulting in bonus (maybe incomplete) messages in the buffer
```
| 1st async_read_some | 2nd async_read_some |
| | |
"+message content here \r\n:100\r\n+OK\r\n_\r\n+incomplete respo"
| | | |
| Message wanted |<-- bonus msgs --->|<--incomplete-->|
| | msg |
| | |
| |<---------- bonus bytes ----------->|
```
3. The buffer is consumed and `async_read_until` is called again.
However, since the buffer already contains the next message this
is an IO-less call
```
":100\r\n+OK\r\n_\r\n+not enough byt"
| | |
| No IO required | Need more |
| to parse these | data |
| messages. | |
```
The fact that step 3. doesn't perform any IO implies the operation can
complete synchronously, but because this is an asynchronous function
Boost.Asio by default won't call the continuation before the
function returns. The implementation must therefore enqueue it for
execution, as depicted below
```
OP5 ---> OP4 ---> OP3 ---> OP2 ---> OP1 # Reschedules the continuation
|
OP1 schedules its continuation |
+-----------------------------------+
|
|
OP6 ---> OP5 ---> OP4 ---> OP3 ---> OP2 # Reschedules the continuation
|
OP2 schedules its continuation |
+-----------------------------------+
|
|
OP7 ---> OP6 ---> OP5 ---> OP4 ---> OP3
```
When summed up, the excessive rescheduling of continuations lead to
performance degradation at scale. But since this is an event-loop
there is no way around rescheduling as doing otherwise would mean
allowing a task to monopolize the event-loop, preventing other tasks
from making progress. The best that can be done is to avoid
_overscheduling_, so let us determine how much rescheduling is too
much.
## The intrinsic latency of an event-loop
An event-loop is a design pattern originally used to handle events
external to the application, such as GUIs, networking and other forms
of IO. If we take this literally, it becomes evident that the way
`async_read_until` works is incompatible with an event-loop since
_searching for the separator_ is not an external event and as such
should not have to be enqueued for execution.
Once we constrain ourselves to events that have an external origin,
such as anything related to IO and including any form of IPC, the
scheduling overhead is reduced considerably since the latency
of the transport layer eclipses whatever time it takes to schedule the
continuation, for example, according to
[these](https://www.boost.org/doc/libs/develop/libs/cobalt/doc/html/index.html#posting_to_an_executor)
benchmarks, the time it takes to schedule a task in the
`asio::io_context ` is approximately `50ns`.
To give the reader an idea about the magnitude of this number, if
rescheduling alone were to account for 1% of the runtime of an app
that uses asynchronous IO to move around data in chunks of size 128kb,
then this app would have a throughput of approximately 24Gbs. At such
high throughput multiple other factors kick in before any scheduling
overhead even starts to manifest.
It is therefore safe to say that only asynchronous operations that
don't perform or are not bound to any IO are ever likely to
overschedule in the sense described above. Those cases can be usually
avoided, this is what worked for Boost.Redis
1. `async_read_until` was replaced with calls to
`socket.async_read_some` and an incremental parser that does not
do any IO.
2. Channel `try_` functions are used to check if send and receive
operations can be called without suspension. For example,
`try_send` before `async_send` and `try_receive` before
`async_receive` ([see also](https://github.com/chriskohlhoff/asio/commit/fe4fd7acf145335eeefdd19708483c46caeb45e5)
`try_send_via_dispatch` for a more aggressive optimization).
3. Coalescing of individual requests into a single payload to reduce
the number of necessary writes on the socket, this is only
possible because Redis supports pipelining (good protocols
help!).
4. Increased the socket read sizes to 4kb to reduce the number of
reads (which is outweighed by the costs of rotating data in the
buffer).
5. Dropped the `resp3::async_read` abstraction. When I started
developing Boost.Redis there was convincing precedent for having
a `resp3::async_read` function to read complete RESP3 messages
from a socket
Name | Description
---------------------------------------|-------------------
`asio::ip::tcp::async_read` | Reads `n` bytes from a stream.
`beast::http::async_read` | Reads a complete HTTP message.
`beast::websocket::stream::async_read` | Reads a complete Websocket message.
`redis::async_read` | Reads a complete RESP3 message.
It turns out however that this function is also vulnerable to
immediate completions since in command pipelines multiple
responses show up in the buffer after a call to
`socket.async_read_some`. When that happens each call to
`resp3::async_read` is IO-less.
Sometimes it is not possible to avoid asynchronous operations that
complete synchronously, in the following sections we will see how to
favor throughput over fairness in Boost.Asio.
### Calling the continuation inline
In Boost.Asio it is possible to customize how an algorithm executes
the continuation when an immediate completion occurs, this includes
the ability of calling it inline, thereby avoiding the costs of
excessive rescheduling. Here is how it works
```cpp
// (default) The continuation is enqueued for execution, regardless of
// whether it is immediate or not.
async_read_until(socket, buffer, "\r\n", continuation);
// Immediate completions are executed in exec2 (otherwise equal to the
// version above). The completion is called inline if exec2 is the
// same executor that is running the operation.
async_read_until(socket, buffer, "\r\n", bind_immediate_executor(exec2, completion));
```
To compare the performance of both cases I have written a small
function that calls `async_read_until` in a loop with a buffer that is
never consumed so that all completions are immediate. The version
below uses the default behaviour
```cpp
void read_safe(tcp::socket& s, std::string& buffer)
{
auto continuation = [&s, &buffer](auto ec, auto n)
{
read_safe(s, buffer); // Recursive call
};
// This won't cause stack exhaustion because the continuation is
// not called inline but posted in the event loop.
async_read_until(s, dynamic_buffer(buffer), "\r\n", continuation);
}
```
To optimize away some of the rescheduling the version below uses the
`bind_immediate_executor` customization to call the continuation
reentrantly and then breaks the stack from time to time to avoid
exhausting it
```cpp
void read_reentrant(tcp::socket& s, std::string& buffer)
{
auto cont = [&](auto, auto)
{
read_reentrant(s, buffer); // Recursive call
};
// Breaks the callstack after 16 inline calls.
if (counter % 16 == 0) {
post(s.get_executor(), [cont](){cont({}, 0);});
return;
}
// Continuation called reentrantly.
async_read_until(s, dynamic_buffer(buffer), "\r\n",
bind_immediate_executor(s.get_executor(), cont));
}
```
The diagram below shows what the reentrant chain of calls in the code
above look like from the event-loop point of view
```
OP5 ---> OP4 ---> OP3 ---> OP2 ---> OP1a # Completes immediately
|
|
... |
OP1b # Completes immediately
|
Waiting for OP5 to |
reschedule its |
continuation OP1c # Completes immediately
|
|
... |
OP1d # Break the call-stack
|
+-----------------------------------+
|
OP6 ---> OP5 ---> OP4 ---> OP3 ---> OP2
```
Unsurprisingly, the reentrant code is 3x faster than the one that
relies on the default behaviour (don't forget that this is a best case
scenario, in the general case not all completions are immediate).
Although faster, this strategy has some downsides
- The overall operation is not as fast as possible since it still
has to reschedule from time to time to break the call stack. The
less it reschedules the higher the risk of exhausting it.
- It is too easy to forget to break the stack. For example, the
programmer might decide to branch somewhere into another chain of
asynchronous calls that also use this strategy. To avoid
exhaustion all such branches would have to be safeguarded with a
manual rescheduling i.e. `post`.
- Requires additional layers of complexity such as
`bind_immediate_executor` in addition to `bind_executor`.
- Non-compliat with more strict
[guidelines](https://en.wikipedia.org/wiki/The_Power_of_10:_Rules_for_Developing_Safety-Critical_Code)
that prohibits reentrat code.
- There is no simple way of choosing the maximum allowed number of
reentrant calls for each function in a way that covers different
use cases and users. Library writers and users would be tempted
into using a small value reducing the performance advantage.
- If the socket is always ready for reading the task will
monopolize IO for up to `16` interactions which might cause
stutter in unrelated tasks as depicted below
```
Unfairness
+----+----+----+ +----+----+----+ +----+----+----+
Socket-1 | | | | | | | | | | | |
+----+----+----+----+----+----+----+----+----+----+----+----+
Socket-2 | | | | | |
+----+ +----+ +----+
```
From the aesthetic point of view the code above is also unpleasant as
it breaks the function asynchronous contract by injecting a reentrant
behaviour. It gives me the same kind of feeling I have about
[recursive
mutexes](http://www.zaval.org/resources/library/butenhof1.html).
Note: It is worth mentioning here that a similar
[strategy](https://github.com/NVIDIA/stdexec/blob/6f23dd5b1d523541ce28af32fc2603403ebd36ed/include/exec/trampoline_scheduler.hpp#L52)
is used to break the call stack of repeating algorithms in
[stdexec](https://github.com/NVIDIA/stdexec), but in this time
based on
[P2300](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2300r7.html)
and not on Boost.Asio.
### Coroutine tail-calls
In the previous section we have seen how to avoid overscheduling by
instructing the asynchronous operation to call the completion inline
on immediate completion. It turns out however that coroutine support
for _tail-calls_ provide a way to completely sidestep this problem.
This feature is described by
[Lewis Baker](https://lewissbaker.github.io/2020/05/11/understanding_symmetric_transfer)
as follows
> A tail-call is one where the current stack-frame is popped before
> the call and the current functions return address becomes the
> return-address for the callee. ie. the callee will return directly
> the the [sic] caller of this function.
This means (at least in principle) that a library capable of using
tail-calls when an immediate completion occurs neither has to
reschedule the continuation nor call it inline. To test how this
feature compares to the other styles I have used Boost.Cobalt. The
code looks as follows
```cpp
// Warning: risks unfairness and starvation of other tasks.
task<void> read_until_unfair()
{
for (int i = 0; i != repeat; ++i) {
co_await async_read_until(s, dynamic_buffer(buffer), "\r\n", cobalt::use_op);
}
}
```
The result of this comparison as listed in the table below
Time/s | Style | Configuration | Library
-------|-----------|-----------------------------|-------------
1,0 | Coroutine | `await_ready` optimization | Boost.Cobalt
4.8 | Callback | Reentant | Boost.Asio
10.3 | Coroutine | `use_op` | Boost.Cobalt
14.9 | Callback | Regular | Boost.Asio
15.6 | Coroutine | `asio::deferred` | Boost.Asio
As the reader can see, `cobalt::use_op` ranks 3rd and is considerably
faster (10.3 vs 15.6) than the Asio equivalent that uses
default-rescheduling. However, by trading rescheduling with tail-calls
the code above can now monopolize the event-loop, resulting in
unfairness if the socket happens to receive data at a higher rate
than other tasks. If by chance data is received continuously
on a socket that is always ready for reading, other tasks will starve
```
Starvation
+----+----+----+----+----+----+----+----+----+----+----+----+
Socket-1 | | | | | | | | | | | | |
+----+----+----+----+----+----+----+----+----+----+----+----+
Socket-2 Starving ...
```
To avoid this problem the programmer is forced to reschedule from time
to time, in the same way we did for the reentrant calls
```cpp
task<void> read_until_fair()
{
for (int i = 0; i != repeat; ++i) {
if (repeat % 16 == 0) {
// Reschedules to address unfairness and starvation of
// other tasks.
co_await post(cobalt::use_op);
continue;
}
co_await async_read_until(s, dynamic_buffer(buffer), "\r\n", cobalt::use_op);
}
}
```
Delegating fairness-safety to applications is a dangerous game.
This is a
[problem](https://tokio.rs/blog/2020-04-preemption) the Tokio
community had to deal with before Tokio runtime started enforcing
rescheduling (after 256 successful operations)
> If data is received faster than it can be processed, it is possible
> that more data will have already been received by the time the
> processing of a data chunk completes. In this case, .await will
> never yield control back to the scheduler, other tasks will not be
> scheduled, resulting in starvation and large latency variance.
> Currently, the answer to this problem is that the user of Tokio is
> responsible for adding yield points in both the application and
> libraries. In practice, very few actually do this and end up being
> vulnerable to this sort of problem.
### Safety in P2300 (Senders and Receivers)
As of this writing, the C++ standards committee (WG21) has been
pursuing the standardization of a networking library for almost 20
years. One of the biggest obstacles that prevented it from happening
was a disagreement on what the _asynchronous model_ that underlies
networking should look like. Until 2021 that model was basically
Boost.Asio _executors_, but in this
[poll](https://www.reddit.com/r/cpp/comments/q6tgod/c_committee_polling_results_for_asynchronous/)
the committee decided to abandon that front and concentrate efforts on
the new [P2300](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2300r7.html)
proposal, also known as _senders and receivers_. The decision was
quite [abrupt](https://isocpp.org/files/papers/P2464R0.html)
> The original plan about a week earlier than the actual writing of
> this paper was to write a paper that makes a case for standardizing
> the Networking TS.
and opinions turned out to be very strong against Boost.Asio (see
[this](https://api.csswg.org/bikeshed/?force=1&url=https://raw.githubusercontent.com/brycelelbach/wg21_p2459_2022_january_library_evolution_poll_outcomes/main/2022_january_library_evolution_poll_outcomes.bs)
for how each voter backed their vote)
> The whole concept is completely useless, there's no composed code
> you can write with it.
The part of that debate that interests us most here is stated in
[P2471](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p2471r1.pdf),
that compares Boost.Asio with P2300
> Yes, default rescheduling each operation and default not
> rescheduling each operation, is a poor trade off. IMO both options
> are poor. The one good option that I know of that can prevent stack
> exhaustion is first-class tail-recursion in library or language
> ASIO has chosen to require that every async operation must schedule
> the completion on a scheduler (every read, every write, etc..).
> sender/receiver has not decided to
> require that the completion be scheduled.
> This is why I consider tail-call the only good solution. Scheduling
> solutions are all inferior (give thanks to Lewis for this shift in
> my understanding :) ).
Although tail-calls solve the problem of stack-exhaustion as we have
seen above, it makes the code vulnerable to unfairness and starvation
and therefore it is not an alternative to default-rescheduling as the
quotation above is implying. To deal with the lack of
default-rescheduling, libraries and applications built on top of P2300
have to address the aforementioned problems, layer after layer. For
example,
[stdexec](https://github.com/NVIDIA/stdexec) has invented something
called
_[trampoline-scheduler](https://github.com/NVIDIA/stdexec/blob/e7cd275273525dbc693f4bf5f6dc4d4181b639e4/include/exec/trampoline_scheduler.hpp)_
to protect repeating algorithms such as `repeat_effect_until` from
exhausting the stack. This construct however is built around
reentracy, allowing
[sixteen](https://github.com/NVIDIA/stdexec/blob/83cdb92d316e8b3bca1357e2cf49fc39e9bed403/include/exec/trampoline_scheduler.hpp#L52)
levels of inline calls by default. While in Boost.Asio it is possible to use
reentracy as an optimization for a corner cases, here it is made its
_modus operandi_, the downsides of this approach have already been stated in a
previous section so I won't repeat it here.
Also the fact that a special scheduler is needed by specific
algorithms is a problem on its own since it contradicts one of the
main selling points of P2300 which is that of being _generic_. For
example, [P2464R0](https://isocpp.org/files/papers/P2464R0.html) uses
the code below as an example
```cpp
void
run_that_io_operation(
scheduler auto sched,
sender_of<network_buffer> auto wrapping_continuation)
{
// snip
}
```
and states
> I have no idea what the sched's concrete type is. I have no idea
> what the wrapping_continuation's concrete type is. They're none of
> my business, ...
Hence, by being generic, the algorithms built on top of P2300 are also
unsafe (against stack-exhaustion, unfairness and starvation). Otherwise,
if library writers require a specific scheduler to ensure safety, then
the algorithms become automatically non-generic, pick your poison!
The proposers of P2300 claim that it doesn't address safety because it
should be seen as the low-level building blocks of asynchronous
programming and that its the role of higher-level libraries, to deal
with that. This claim however does not hold since, as we have just
seen, Boost.Asio also provides those building blocks but does so in a
safe way. In fact during the whole development of Boost.Redis I never
had to think about these kinds of problems because safety is built
from the ground up.
### Avoiding coroutine suspension with `await_ready`
Now let us get back to the first place in the table above, which uses
the `await_ready` optimization from Boost.Cobalt. This API provides
users with the ability to avoid coroutine suspension altogether in
case the separator is already present in the buffer. It works by
defining a `struct` with the following interface
```cpp
struct read_until : cobalt::op<error_code, std::size_t> {
...
void ready(cobalt::handler<error_code, std::size_t> handler) override
{
// Search for the separator in buffer and call the handler if found
}
void initiate(cobalt::completion_handler<error_code, std::size_t> complete) override
{
// Regular call to async_read_until.
async_read_until(socket, buffer, delim, std::move(complete));
}
};
```
and the code that uses it
```cpp
for (int i = 0; i != repeat; ++i) {
co_await read_until(socket, dynamic_buffer(buffer));
}
```
In essence, what the code above does is to skip a call to
`async_read_unil` by first checking with the ready function whether
the forthcoming operation is going to complete immediately. The
nice thing about it is that the programmer can use this optimization
only when a performance bottleneck is detected, without planing for it
in advance. The drawback however is that it requires reimplementing
the search for the separator in the body of the `ready` function,
defeating the purpose of using `async_read_until` in first place as
(again) it would have been simpler to reformulate the operation in
terms of `socket.async_read_some` directly.
## Acknowledgements
Thanks to Klemens Morgenstern for answering questions about
Boost.Cobalt.

52
example/CMakeLists.txt Normal file
View File

@@ -0,0 +1,52 @@
add_library(examples_main STATIC main.cpp)
target_compile_features(examples_main PRIVATE cxx_std_20)
target_link_libraries(examples_main PRIVATE boost_redis_project_options)
macro(make_example EXAMPLE_NAME STANDARD)
add_executable(${EXAMPLE_NAME} ${EXAMPLE_NAME}.cpp)
target_link_libraries(${EXAMPLE_NAME} PRIVATE boost_redis_src)
target_link_libraries(${EXAMPLE_NAME} PRIVATE boost_redis_project_options)
target_compile_features(${EXAMPLE_NAME} PRIVATE cxx_std_${STANDARD})
if (${STANDARD} STREQUAL "20")
target_link_libraries(${EXAMPLE_NAME} PRIVATE examples_main)
endif()
if (${EXAMPLE_NAME} STREQUAL "cpp20_json")
target_link_libraries(${EXAMPLE_NAME} PRIVATE Boost::json Boost::container_hash)
endif()
endmacro()
macro(make_testable_example EXAMPLE_NAME STANDARD)
make_example(${EXAMPLE_NAME} ${STANDARD})
if (BOOST_REDIS_INTEGRATION_TESTS)
add_test(${EXAMPLE_NAME} ${EXAMPLE_NAME})
endif()
endmacro()
make_testable_example(cpp17_intro 17)
make_testable_example(cpp17_intro_sync 17)
make_testable_example(cpp20_intro 20)
make_testable_example(cpp20_containers 20)
make_testable_example(cpp20_json 20)
make_testable_example(cpp20_intro_tls 20)
make_example(cpp20_subscriber 20)
make_example(cpp20_streams 20)
make_example(cpp20_echo_server 20)
make_example(cpp20_resolve_with_sentinel 20)
# We test the protobuf example only on gcc.
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
find_package(Protobuf)
if (Protobuf_FOUND)
protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS person.proto)
make_testable_example(cpp20_protobuf 20)
target_sources(cpp20_protobuf PUBLIC ${PROTO_SRCS} ${PROTO_HDRS})
target_link_libraries(cpp20_protobuf PRIVATE ${Protobuf_LIBRARIES})
target_include_directories(cpp20_protobuf PUBLIC ${Protobuf_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR})
endif()
endif()
if (NOT MSVC)
make_example(cpp20_chat_room 20)
endif()

View File

@@ -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)

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);

View File

@@ -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();

View File

@@ -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;

View File

@@ -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;

View File

@@ -15,15 +15,13 @@
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
#define BOOST_JSON_NO_LIB
#define BOOST_CONTAINER_NO_LIB
#include <boost/json/serialize.hpp>
#include <boost/json/parse.hpp>
#include <boost/json/value_from.hpp>
#include <boost/json/value_to.hpp>
#include <boost/redis/resp3/serialization.hpp>
#include <boost/json/src.hpp>
namespace net = boost::asio;
namespace asio = boost::asio;
using namespace boost::describe;
using boost::redis::request;
using boost::redis::response;
@@ -41,18 +39,18 @@ struct user {
// The type must be described for serialization to work.
BOOST_DESCRIBE_STRUCT(user, (), (name, age, country))
// Boost.Redis customization points (examples/json.hpp)
// Boost.Redis customization points (example/json.hpp)
void boost_redis_to_bulk(std::string& to, user const& u)
{ boost::redis::resp3::boost_redis_to_bulk(to, boost::json::serialize(boost::json::value_from(u))); }
void boost_redis_from_bulk(user& u, std::string_view sv, boost::system::error_code&)
{ u = boost::json::value_to<user>(boost::json::parse(sv)); }
auto co_main(config cfg) -> net::awaitable<void>
auto co_main(config cfg) -> asio::awaitable<void>
{
auto ex = co_await net::this_coro::executor;
auto ex = co_await asio::this_coro::executor;
auto conn = std::make_shared<connection>(ex);
conn->async_run(cfg, {}, net::consign(net::detached, conn));
conn->async_run(cfg, {}, asio::consign(asio::detached, conn));
// user object that will be stored in Redis in json format.
user const u{"Joao", "58", "Brazil"};
@@ -64,7 +62,7 @@ auto co_main(config cfg) -> net::awaitable<void>
response<ignore_t, user> resp;
co_await conn->async_exec(req, resp, net::deferred);
co_await conn->async_exec(req, resp, asio::deferred);
conn->cancel();
// Prints the first ping

View File

@@ -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

View File

@@ -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.

View File

@@ -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();

View File

@@ -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);
});

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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");

View File

@@ -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]]

View File

@@ -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;

View File

@@ -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)

View File

@@ -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)

View File

@@ -1,4 +1,4 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
@@ -38,7 +38,7 @@ struct config {
* [HELLO](https://redis.io/commands/hello/) command. If left
* empty `HELLO` will be sent without authentication parameters.
*/
std::string username;
std::string username = "default";
/** @brief Password passed to the
* [HELLO](https://redis.io/commands/hello/) command. If left

View File

@@ -1,4 +1,4 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
@@ -86,13 +86,19 @@ public:
using other = basic_connection<Executor1>;
};
/// Contructs from an executor.
/** @brief Constructor
*
* @param ex Executor on which connection operation will run.
* @param ctx SSL context.
* @param max_read_size Maximum read size that is passed to
* the internal `asio::dynamic_buffer` constructor.
*/
explicit
basic_connection(
executor_type ex,
asio::ssl::context::method method = asio::ssl::context::tls_client,
asio::ssl::context ctx = asio::ssl::context{asio::ssl::context::tlsv12_client},
std::size_t max_read_size = (std::numeric_limits<std::size_t>::max)())
: impl_{ex, method, max_read_size}
: impl_{ex, std::move(ctx), max_read_size}
, timer_{ex}
{ }
@@ -100,9 +106,9 @@ public:
explicit
basic_connection(
asio::io_context& ioc,
asio::ssl::context::method method = asio::ssl::context::tls_client,
asio::ssl::context ctx = asio::ssl::context{asio::ssl::context::tlsv12_client},
std::size_t max_read_size = (std::numeric_limits<std::size_t>::max)())
: basic_connection(ioc.get_executor(), method, max_read_size)
: basic_connection(ioc.get_executor(), std::move(ctx), max_read_size)
{ }
/** @brief Starts underlying connection operations.
@@ -171,7 +177,6 @@ public:
* To cancel an ongoing receive operation apps should call
* `connection::cancel(operation::receive)`.
*
* @param response Response object.
* @param token Completion token.
*
* For an example see cpp20_subscriber.cpp. The completion token must
@@ -184,10 +189,32 @@ public:
* Where the second parameter is the size of the push received in
* bytes.
*/
template <class CompletionToken = asio::default_completion_token_t<executor_type>>
auto async_receive(CompletionToken token = CompletionToken{})
{ return impl_.async_receive(std::move(token)); }
/** @brief Receives server pushes synchronously without blocking.
*
* Receives a server push synchronously by calling `try_receive` on
* the underlying channel. If the operation fails because
* `try_receive` returns `false`, `ec` will be set to
* `boost::redis::error::sync_receive_push_failed`.
*
* @param ec Contains the error if any occurred.
*
* @returns The number of bytes read from the socket.
*/
std::size_t receive(system::error_code& ec)
{
return impl_.receive(ec);
}
template <
class Response = ignore_t,
class CompletionToken = asio::default_completion_token_t<executor_type>
>
[[deprecated("Set the response with set_receive_response and use the other overload.")]]
auto
async_receive(
Response& response,
@@ -242,7 +269,6 @@ public:
* @li operation::all: Cancels all operations listed above.
*
* @param op: The operation to be cancelled.
* @returns The number of operations that have been canceled.
*/
void cancel(operation op = operation::all)
{
@@ -266,10 +292,6 @@ public:
auto const& get_ssl_context() const noexcept
{ return impl_.get_ssl_context();}
/// Returns the ssl context.
auto& get_ssl_context() noexcept
{ return impl_.get_ssl_context();}
/// Resets the underlying stream.
void reset_stream()
{ impl_.reset_stream(); }
@@ -282,6 +304,15 @@ public:
auto const& next_layer() const noexcept
{ return impl_.next_layer(); }
/// Sets the response object of `async_receive` operations.
template <class Response>
void set_receive_response(Response& response)
{ impl_.set_receive_response(response); }
/// Returns connection usage information.
usage get_usage() const noexcept
{ return impl_.get_usage(); }
private:
using timer_type =
asio::basic_waitable_timer<
@@ -314,14 +345,14 @@ public:
explicit
connection(
executor_type ex,
asio::ssl::context::method method = asio::ssl::context::tls_client,
asio::ssl::context ctx = asio::ssl::context{asio::ssl::context::tlsv12_client},
std::size_t max_read_size = (std::numeric_limits<std::size_t>::max)());
/// Contructs from a context.
explicit
connection(
asio::io_context& ioc,
asio::ssl::context::method method = asio::ssl::context::tls_client,
asio::ssl::context ctx = asio::ssl::context{asio::ssl::context::tlsv12_client},
std::size_t max_read_size = (std::numeric_limits<std::size_t>::max)());
/// Returns the underlying executor.
@@ -342,11 +373,23 @@ public:
/// Calls `boost::redis::basic_connection::async_receive`.
template <class Response, class CompletionToken>
[[deprecated("Set the response with set_receive_response and use the other overload.")]]
auto async_receive(Response& response, CompletionToken token)
{
return impl_.async_receive(response, std::move(token));
}
/// Calls `boost::redis::basic_connection::async_receive`.
template <class CompletionToken>
auto async_receive(CompletionToken token)
{ return impl_.async_receive(std::move(token)); }
/// Calls `boost::redis::basic_connection::receive`.
std::size_t receive(system::error_code& ec)
{
return impl_.receive(ec);
}
/// Calls `boost::redis::basic_connection::async_exec`.
template <class Response, class CompletionToken>
auto async_exec(request const& req, Response& resp, CompletionToken token)
@@ -373,6 +416,19 @@ public:
void reset_stream()
{ impl_.reset_stream();}
/// Sets the response object of `async_receive` operations.
template <class Response>
void set_receive_response(Response& response)
{ impl_.set_receive_response(response); }
/// Returns connection usage information.
usage get_usage() const noexcept
{ return impl_.get_usage(); }
/// Returns the ssl context.
auto const& get_ssl_context() const noexcept
{ return impl_.get_ssl_context();}
private:
void
async_run_impl(

File diff suppressed because it is too large Load Diff

View File

@@ -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)

View File

@@ -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)

View File

@@ -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_;

View File

@@ -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)

View File

@@ -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

View File

@@ -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)

View File

@@ -1,4 +1,4 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
@@ -30,6 +30,8 @@
namespace boost::redis::detail
{
void push_hello(config const& cfg, request& req);
template <class Runner, class Connection, class Logger>
struct hello_op {
Runner* runner_ = nullptr;
@@ -42,16 +44,20 @@ struct hello_op {
{
BOOST_ASIO_CORO_REENTER (coro_)
{
runner_->hello_req_.clear();
if (runner_->hello_resp_.has_value())
runner_->hello_resp_.value().clear();
runner_->add_hello();
BOOST_ASIO_CORO_YIELD
conn_->async_exec(runner_->hello_req_, runner_->hello_resp_, std::move(self));
logger_.on_hello(ec, runner_->hello_resp_);
BOOST_REDIS_CHECK_OP0(conn_->cancel(operation::run);)
self.complete(ec);
if (ec || runner_->has_error_in_response() || is_cancelled(self)) {
logger_.trace("hello-op: error/canceled. Exiting ...");
conn_->cancel(operation::run);
self.complete(!!ec ? ec : asio::error::operation_aborted);
return;
}
self.complete({});
}
}
};
@@ -84,12 +90,14 @@ public:
BOOST_ASIO_CORO_YIELD
asio::experimental::make_parallel_group(
[this](auto token) { return runner_->async_run_all(*conn_, logger_, token); },
[this](auto token) { return runner_->health_checker_.async_check_health(*conn_, token); },
[this](auto token) { return runner_->health_checker_.async_check_health(*conn_, logger_, token); },
[this](auto token) { return runner_->async_hello(*conn_, logger_, token); }
).async_wait(
asio::experimental::wait_for_all(),
std::move(self));
logger_.on_runner(ec0, ec1, ec2);
if (is_cancelled(self)) {
self.complete(asio::error::operation_aborted);
return;
@@ -223,17 +231,27 @@ private:
void add_hello()
{
if (!cfg_.username.empty() && !cfg_.password.empty() && !cfg_.clientname.empty())
hello_req_.push("HELLO", "3", "AUTH", cfg_.username, cfg_.password, "SETNAME", cfg_.clientname);
else if (cfg_.username.empty() && cfg_.password.empty() && cfg_.clientname.empty())
hello_req_.push("HELLO", "3");
else if (cfg_.clientname.empty())
hello_req_.push("HELLO", "3", "AUTH", cfg_.username, cfg_.password);
else
hello_req_.push("HELLO", "3", "SETNAME", cfg_.clientname);
hello_req_.clear();
if (hello_resp_.has_value())
hello_resp_.value().clear();
push_hello(cfg_, hello_req_);
}
if (cfg_.database_index)
hello_req_.push("SELECT", cfg_.database_index.value());
bool has_error_in_response() const noexcept
{
if (!hello_resp_.has_value())
return true;
auto f = [](auto const& e)
{
switch (e.data_type) {
case resp3::type::simple_error:
case resp3::type::blob_error: return true;
default: return false;
}
};
return std::any_of(std::cbegin(hello_resp_.value()), std::cend(hello_resp_.value()), f);
}
resolver_type resv_;

View File

@@ -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)

View File

@@ -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

View File

@@ -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)

View File

@@ -1,4 +1,4 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
@@ -10,16 +10,16 @@ namespace boost::redis {
connection::connection(
executor_type ex,
asio::ssl::context::method method,
asio::ssl::context ctx,
std::size_t max_read_size)
: impl_{ex, method, max_read_size}
: impl_{ex, std::move(ctx), max_read_size}
{ }
connection::connection(
asio::io_context& ioc,
asio::ssl::context::method method,
asio::ssl::context ctx,
std::size_t max_read_size)
: impl_{ioc.get_executor(), method, max_read_size}
: impl_{ioc.get_executor(), std::move(ctx), max_read_size}
{ }
void

View File

@@ -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.";
}
}

View File

@@ -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)

View File

@@ -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

View File

@@ -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)

View 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

View File

@@ -0,0 +1,27 @@
/* Copyright (c) 2018-2024 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#include <boost/redis/detail/runner.hpp>
namespace boost::redis::detail
{
void push_hello(config const& cfg, request& req)
{
if (!cfg.username.empty() && !cfg.password.empty() && !cfg.clientname.empty())
req.push("HELLO", "3", "AUTH", cfg.username, cfg.password, "SETNAME", cfg.clientname);
else if (cfg.password.empty() && cfg.clientname.empty())
req.push("HELLO", "3");
else if (cfg.clientname.empty())
req.push("HELLO", "3", "AUTH", cfg.username, cfg.password);
else
req.push("HELLO", "3", "SETNAME", cfg.clientname);
if (cfg.database_index && cfg.database_index.value() != 0)
req.push("SELECT", cfg.database_index.value());
}
} // boost::redis::detail

View File

@@ -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_;

View File

@@ -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)

View File

@@ -1,4 +1,4 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
@@ -47,31 +47,31 @@ class request {
public:
/// Request configuration options.
struct config {
/** \brief If `true`
* `boost::redis::connection::async_exec` will complete with error if the
* connection is lost. Affects only requests that haven't been
* sent yet.
/** \brief If `true` calls to `connection::async_exec` will
* complete with error if the connection is lost while the
* request hasn't been sent yet.
*/
bool cancel_on_connection_lost = true;
/** \brief If `true` the request will complete with
* boost::redis::error::not_connected if `async_exec` is called before
* the connection with Redis was established.
/** \brief If `true` `connection::async_exec` will complete with
* `boost::redis::error::not_connected` if the call happens
* before the connection with Redis was established.
*/
bool cancel_if_not_connected = false;
/** \brief If `false` `boost::redis::connection::async_exec` will not
/** \brief If `false` `connection::async_exec` will not
* automatically cancel this request if the connection is lost.
* Affects only requests that have been written to the socket
* but remained unresponded when `boost::redis::connection::async_run`
* completed.
* but remained unresponded when
* `boost::redis::connection::async_run` completed.
*/
bool cancel_if_unresponded = true;
/** \brief If this request has a `HELLO` command and this flag is
* `true`, the `boost::redis::connection` will move it to the front of
* the queue of awaiting requests. This makes it possible to
* send `HELLO` and authenticate before other commands are sent.
/** \brief If this request has a `HELLO` command and this flag
* is `true`, the `boost::redis::connection` will move it to the
* front of the queue of awaiting requests. This makes it
* possible to send `HELLO` and authenticate before other
* commands are sent.
*/
bool hello_with_priority = true;
};
@@ -84,8 +84,12 @@ public:
request(config cfg = config{true, false, true, true})
: cfg_{cfg} {}
//// Returns the number of responses expected for this request.
[[nodiscard]] auto get_expected_responses() const noexcept -> std::size_t
{ return expected_responses_;};
//// Returns the number of commands contained in this request.
[[nodiscard]] auto size() const noexcept -> std::size_t
[[nodiscard]] auto get_commands() const noexcept -> std::size_t
{ return commands_;};
[[nodiscard]] auto payload() const noexcept -> std::string_view
@@ -99,6 +103,7 @@ public:
{
payload_.clear();
commands_ = 0;
expected_responses_ = 0;
has_hello_priority_ = false;
}
@@ -303,8 +308,10 @@ public:
private:
void check_cmd(std::string_view cmd)
{
++commands_;
if (!detail::has_response(cmd))
++commands_;
++expected_responses_;
if (cmd == "HELLO")
has_hello_priority_ = cfg_.hello_with_priority;
@@ -313,6 +320,7 @@ private:
config cfg_;
std::string payload_;
std::size_t commands_ = 0;
std::size_t expected_responses_ = 0;
bool has_hello_priority_ = false;
};

View File

@@ -1,4 +1,4 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2024 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
@@ -9,10 +9,11 @@
#include <boost/assert.hpp>
#include <charconv>
#include <limits>
namespace boost::redis::resp3 {
void to_int(int_type& i, std::string_view sv, system::error_code& ec)
void to_int(std::size_t& i, std::string_view sv, system::error_code& ec)
{
auto const res = std::from_chars(sv.data(), sv.data() + std::size(sv), i);
if (res.ec != std::errc())
@@ -21,6 +22,16 @@ void to_int(int_type& i, std::string_view sv, system::error_code& ec)
parser::parser()
{
reset();
}
void parser::reset()
{
depth_ = 0;
sizes_ = {{1}};
bulk_length_ = (std::numeric_limits<std::size_t>::max)();
bulk_ = type::invalid;
consumed_ = 0;
sizes_[0] = 2; // The sentinel must be more than 1.
}
@@ -178,7 +189,7 @@ parser::consume_impl(
case type::attribute:
case type::map:
{
int_type l = -1;
std::size_t l = -1;
to_int(l, elem, ec);
if (ec)
return {};

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -1,4 +1,4 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2024 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
@@ -10,15 +10,12 @@
#include <boost/redis/resp3/node.hpp>
#include <boost/system/error_code.hpp>
#include <array>
#include <limits>
#include <string_view>
#include <cstdint>
#include <optional>
namespace boost::redis::resp3 {
using int_type = std::uint64_t;
class parser {
public:
using node_type = basic_node<std::string_view>;
@@ -31,22 +28,22 @@ private:
// The current depth. Simple data types will have depth 0, whereas
// the elements of aggregates will have depth 1. Embedded types
// will have increasing depth.
std::size_t depth_ = 0;
std::size_t depth_;
// The parser supports up to 5 levels of nested structures. The
// first element in the sizes stack is a sentinel and must be
// different from 1.
std::array<std::size_t, max_embedded_depth + 1> sizes_ = {{1}};
std::array<std::size_t, max_embedded_depth + 1> sizes_;
// Contains the length expected in the next bulk read.
int_type bulk_length_ = (std::numeric_limits<unsigned long>::max)();
std::size_t bulk_length_;
// The type of the next bulk. Contains type::invalid if no bulk is
// expected.
type bulk_ = type::invalid;
type bulk_;
// The number of bytes consumed from the buffer.
std::size_t consumed_ = 0;
std::size_t consumed_;
// Returns the number of bytes that have been consumed.
auto consume_impl(type t, std::string_view elem, system::error_code& ec) -> node_type;
@@ -71,8 +68,13 @@ public:
auto get_consumed() const noexcept -> std::size_t;
auto consume(std::string_view view, system::error_code& ec) noexcept -> result;
void reset();
};
// Returns false if more data is needed. If true is returned the
// parser is either done or an error occured, that can be checked on
// ec.
template <class Adapter>
bool
parse(

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -1,4 +1,4 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
@@ -9,6 +9,8 @@
#include <boost/redis/impl/request.ipp>
#include <boost/redis/impl/ignore.ipp>
#include <boost/redis/impl/connection.ipp>
#include <boost/redis/impl/response.ipp>
#include <boost/redis/impl/runner.ipp>
#include <boost/redis/resp3/impl/type.ipp>
#include <boost/redis/resp3/impl/parser.ipp>
#include <boost/redis/resp3/impl/serialization.ipp>

View 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
View 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&nbsp;(C)&nbsp;2023&nbsp;Marcelo&nbsp;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
View File

@@ -0,0 +1,16 @@
{
"key": "redis",
"name": "Redis",
"authors": [
"Marcelo Zimbres Silva"
],
"description": "Redis async client library built on top of Boost.Asio.",
"category": [
"Concurrent",
"IO"
],
"maintainers": [
"Marcelo Zimbres Silva <mzimbres@gmail.com>"
],
"cxxstd": "17"
}

76
test/CMakeLists.txt Normal file
View File

@@ -0,0 +1,76 @@
# Common utilities
add_library(boost_redis_project_options INTERFACE)
target_link_libraries(boost_redis_project_options INTERFACE boost_redis)
if (MSVC)
target_compile_options(boost_redis_project_options INTERFACE /bigobj)
target_compile_definitions(boost_redis_project_options INTERFACE _WIN32_WINNT=0x0601)
endif()
add_library(boost_redis_src STATIC boost_redis.cpp)
target_compile_features(boost_redis_src PRIVATE cxx_std_17)
target_link_libraries(boost_redis_src PRIVATE boost_redis_project_options)
# Test utils
add_library(boost_redis_tests_common STATIC common.cpp)
target_compile_features(boost_redis_tests_common PRIVATE cxx_std_17)
target_link_libraries(boost_redis_tests_common PRIVATE boost_redis_project_options)
macro(make_test TEST_NAME STANDARD)
set(EXE_NAME "boost_redis_${TEST_NAME}")
add_executable(${EXE_NAME} ${TEST_NAME}.cpp)
target_link_libraries(${EXE_NAME} PRIVATE
boost_redis_src
boost_redis_tests_common
boost_redis_project_options
Boost::unit_test_framework
)
target_compile_features(${EXE_NAME} PRIVATE cxx_std_${STANDARD})
add_test(${EXE_NAME} ${EXE_NAME})
endmacro()
make_test(test_conn_quit 17)
# TODO: Configure a Redis server with TLS in the CI and reenable this test.
#make_test(test_conn_tls 17)
make_test(test_low_level 17)
make_test(test_conn_exec_retry 17)
make_test(test_conn_exec_error 17)
make_test(test_request 17)
make_test(test_run 17)
make_test(test_low_level_sync_sans_io 17)
make_test(test_conn_check_health 17)
make_test(test_conn_exec 20)
make_test(test_conn_push 20)
make_test(test_conn_reconnect 20)
make_test(test_conn_exec_cancel 20)
make_test(test_conn_exec_cancel2 20)
make_test(test_conn_echo_stress 20)
make_test(test_conn_run_cancel 20)
make_test(test_issue_50 20)
make_test(test_issue_181 17)
# Coverage
set(
COVERAGE_TRACE_COMMAND
lcov --capture
-output-file "${PROJECT_BINARY_DIR}/coverage.info"
--directory "${PROJECT_BINARY_DIR}"
--include "${PROJECT_SOURCE_DIR}/include/*"
)
set(
COVERAGE_HTML_COMMAND
genhtml --legend -f -q
"${PROJECT_BINARY_DIR}/coverage.info"
--prefix "${PROJECT_SOURCE_DIR}"
--output-directory "${PROJECT_BINARY_DIR}/coverage_html"
)
add_custom_target(
coverage
COMMAND ${COVERAGE_TRACE_COMMAND}
COMMAND ${COVERAGE_HTML_COMMAND}
COMMENT "Generating coverage report"
VERBATIM
)

62
test/Jamfile Normal file
View 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)
;
}

View 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)

View 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());
}

View 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>)

View 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());
}

View File

@@ -0,0 +1,16 @@
cmake_minimum_required(VERSION 3.5...3.22)
project(cmake_subdir_test LANGUAGES CXX)
set(BOOST_INCLUDE_LIBRARIES redis)
# Build our dependencies, so the targets Boost::xxx are defined
add_subdirectory(../../../.. boostorg/boost)
add_executable(main main.cpp)
target_link_libraries(main PRIVATE Boost::redis)
include(CTest)
add_test(main main)
add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure -C $<CONFIG>)

View 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());
}

View File

@@ -1,10 +1,9 @@
#include "common.hpp"
#include <iostream>
#include <cstdlib>
#include <boost/asio/consign.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/test/unit_test.hpp>
namespace net = boost::asio;
struct run_callback {
@@ -15,7 +14,6 @@ struct run_callback {
void operator()(boost::system::error_code const& ec) const
{
std::cout << "async_run: " << ec.message() << std::endl;
//BOOST_CHECK_EQUAL(ec, expected);
conn->cancel(op);
}
};
@@ -31,6 +29,32 @@ run(
conn->async_run(cfg, {l}, run_callback{conn, op, ec});
}
static std::string safe_getenv(const char* name, const char* default_value)
{
// MSVC doesn't like getenv
#ifdef BOOST_MSVC
#pragma warning(push)
#pragma warning(disable : 4996)
#endif
const char* res = std::getenv(name);
#ifdef BOOST_MSVC
#pragma warning(pop)
#endif
return res ? res : default_value;
}
std::string get_server_hostname()
{
return safe_getenv("BOOST_REDIS_TEST_SERVER", "localhost");
}
boost::redis::config make_test_config()
{
boost::redis::config cfg;
cfg.addr.host = get_server_hostname();
return cfg;
}
#ifdef BOOST_ASIO_HAS_CO_AWAIT
auto start(net::awaitable<void> op) -> int
{
@@ -50,5 +74,4 @@ auto start(net::awaitable<void> op) -> int
return 1;
}
#endif // BOOST_ASIO_HAS_CO_AWAIT

View File

@@ -15,11 +15,14 @@ auto redir(boost::system::error_code& ec)
auto start(boost::asio::awaitable<void> op) -> int;
#endif // BOOST_ASIO_HAS_CO_AWAIT
boost::redis::config make_test_config();
std::string get_server_hostname();
void
run(
std::shared_ptr<boost::redis::connection> conn,
boost::redis::config cfg = {},
boost::redis::config cfg = make_test_config(),
boost::system::error_code ec = boost::asio::error::operation_aborted,
boost::redis::operation op = boost::redis::operation::receive,
boost::redis::logger::level l = boost::redis::logger::level::info);
boost::redis::logger::level l = boost::redis::logger::level::disabled);

View File

@@ -5,6 +5,7 @@
*/
#include <boost/redis/connection.hpp>
#include <boost/redis/response.hpp>
#include <boost/system/errc.hpp>
#define BOOST_TEST_MODULE check-health
#include <boost/test/included/unit_test.hpp>
@@ -20,13 +21,10 @@ using boost::redis::request;
using boost::redis::ignore;
using boost::redis::operation;
using boost::redis::generic_response;
using boost::redis::logger;
using redis::config;
using boost::redis::consume_one;
// TODO: Test cancel(health_check)
std::chrono::seconds const interval{1};
struct push_callback {
connection* conn1;
connection* conn2;
@@ -39,9 +37,8 @@ struct push_callback {
{
BOOST_ASIO_CORO_REENTER (coro) for (;;)
{
resp2->value().clear();
BOOST_ASIO_CORO_YIELD
conn2->async_receive(*resp2, *this);
conn2->async_receive(*this);
if (ec) {
std::clog << "Exiting." << std::endl;
return;
@@ -50,6 +47,7 @@ struct push_callback {
BOOST_TEST(resp2->has_value());
BOOST_TEST(!resp2->value().empty());
std::clog << "Event> " << resp2->value().front().value << std::endl;
consume_one(*resp2);
++i;
@@ -73,14 +71,12 @@ struct push_callback {
BOOST_AUTO_TEST_CASE(check_health)
{
net::io_context ioc;
connection conn1{ioc};
request req1;
req1.push("CLIENT", "PAUSE", "10000", "ALL");
config cfg1;
auto cfg1 = make_test_config();
cfg1.health_check_id = "conn1";
cfg1.reconnect_wait_interval = std::chrono::seconds::zero();
error_code res1;
@@ -95,7 +91,7 @@ BOOST_AUTO_TEST_CASE(check_health)
// sending MONITOR. I will therefore open a second connection.
connection conn2{ioc};
config cfg2;
auto cfg2 = make_test_config();
cfg2.health_check_id = "conn2";
error_code res2;
conn2.async_run(cfg2, {}, [&](auto ec){
@@ -106,6 +102,7 @@ BOOST_AUTO_TEST_CASE(check_health)
request req2;
req2.push("MONITOR");
generic_response resp2;
conn2.set_receive_response(resp2);
conn2.async_exec(req2, ignore, [](auto ec, auto) {
std::cout << "async_exec: " << std::endl;
@@ -113,7 +110,7 @@ BOOST_AUTO_TEST_CASE(check_health)
});
//--------------------------------
push_callback{&conn1, &conn2, &resp2, &req1}(); // Starts reading pushes.
ioc.run();

View File

@@ -24,14 +24,40 @@ using boost::redis::response;
using boost::redis::ignore;
using boost::redis::ignore_t;
using boost::redis::logger;
using boost::redis::config;
using boost::redis::connection;
using boost::redis::usage;
using boost::redis::error;
std::ostream& operator<<(std::ostream& os, usage const& u)
{
os
<< "Commands sent: " << u.commands_sent << "\n"
<< "Bytes sent: " << u.bytes_sent << "\n"
<< "Responses received: " << u.responses_received << "\n"
<< "Pushes received: " << u.pushes_received << "\n"
<< "Response bytes received: " << u.response_bytes_received << "\n"
<< "Push bytes received: " << u.push_bytes_received;
return os;
}
auto push_consumer(std::shared_ptr<connection> conn, int expected) -> net::awaitable<void>
{
int c = 0;
for (;;) {
co_await conn->async_receive(ignore, net::use_awaitable);
for (error_code ec;;) {
conn->receive(ec);
if (ec == error::sync_receive_push_failed) {
ec = {};
co_await conn->async_receive(redirect_error(net::use_awaitable, ec));
} else if (!ec) {
//std::cout << "Skipping suspension." << std::endl;
}
if (ec) {
BOOST_TEST(false);
std::cout << "push_consumer error: " << ec.message() << std::endl;
co_return;
}
if (++c == expected)
break;
}
@@ -43,37 +69,16 @@ auto
echo_session(
std::shared_ptr<connection> conn,
std::shared_ptr<request> pubs,
std::string id,
int n) -> net::awaitable<void>
{
auto ex = co_await net::this_coro::executor;
request req;
response<ignore_t, std::string, ignore_t> resp;
for (auto i = 0; i < n; ++i) {
auto const msg = id + "/" + std::to_string(i);
//std::cout << msg << std::endl;
req.push("HELLO", 3); // Just to mess around.
req.push("PING", msg);
req.push("PING", "lsls"); // TODO: Change to HELLO after fixing issue 105.
boost::system::error_code ec;
co_await conn->async_exec(req, resp, redir(ec));
BOOST_REQUIRE_EQUAL(ec, boost::system::error_code{});
BOOST_REQUIRE_EQUAL(msg, std::get<1>(resp).value());
req.clear();
std::get<1>(resp).value().clear();
for (auto i = 0; i < n; ++i)
co_await conn->async_exec(*pubs, ignore, net::deferred);
}
}
auto async_echo_stress() -> net::awaitable<void>
auto async_echo_stress(std::shared_ptr<connection> conn) -> net::awaitable<void>
{
auto ex = co_await net::this_coro::executor;
auto conn = std::make_shared<connection>(ex);
config cfg;
auto cfg = make_test_config();
cfg.health_check_interval = std::chrono::seconds::zero();
run(conn, cfg,
boost::asio::error::operation_aborted,
@@ -86,19 +91,20 @@ auto async_echo_stress() -> net::awaitable<void>
// Number of coroutines that will send pings sharing the same
// connection to redis.
int const sessions = 500;
int const sessions = 150;
// The number of pings that will be sent by each session.
int const msgs = 1000;
int const msgs = 200;
// The number of publishes that will be sent by each session with
// each message.
int const n_pubs = 10;
int const n_pubs = 25;
// This is the total number of pushes we will receive.
int total_pushes = sessions * msgs * n_pubs + 1;
auto pubs = std::make_shared<request>();
pubs->push("PING");
for (int i = 0; i < n_pubs; ++i)
pubs->push("PUBLISH", "channel", "payload");
@@ -107,14 +113,20 @@ auto async_echo_stress() -> net::awaitable<void>
net::co_spawn(ex, push_consumer(conn, total_pushes), net::detached);
for (int i = 0; i < sessions; ++i)
net::co_spawn(ex, echo_session(conn, pubs, std::to_string(i), msgs), net::detached);
net::co_spawn(ex, echo_session(conn, pubs, msgs), net::detached);
}
BOOST_AUTO_TEST_CASE(echo_stress)
{
net::io_context ioc;
net::co_spawn(ioc, async_echo_stress(), net::detached);
auto conn = std::make_shared<connection>(ioc);
net::co_spawn(ioc, async_echo_stress(conn), net::detached);
ioc.run();
std::cout
<< "-------------------\n"
<< conn->get_usage()
<< std::endl;
}
#else

View File

@@ -6,6 +6,7 @@
#include <boost/redis/connection.hpp>
#include <boost/system/errc.hpp>
#include <boost/asio/detached.hpp>
#define BOOST_TEST_MODULE conn-exec
#include <boost/test/included/unit_test.hpp>
#include <iostream>
@@ -17,13 +18,13 @@
// container.
namespace net = boost::asio;
using boost::redis::config;
using boost::redis::connection;
using boost::redis::request;
using boost::redis::response;
using boost::redis::generic_response;
using boost::redis::ignore;
using boost::redis::operation;
using boost::redis::config;
using boost::redis::request;
using boost::redis::response;
// Sends three requests where one of them has a hello with a priority
// set, which means it should be executed first.
@@ -53,7 +54,7 @@ BOOST_AUTO_TEST_CASE(hello_priority)
conn->async_exec(req1, ignore, [&](auto ec, auto){
// Second callback to the called.
std::cout << "req1" << std::endl;
BOOST_CHECK_EQUAL(ec, boost::system::error_code{});
BOOST_TEST(!ec);
BOOST_TEST(!seen2);
BOOST_TEST(seen3);
seen1 = true;
@@ -62,7 +63,7 @@ BOOST_AUTO_TEST_CASE(hello_priority)
conn->async_exec(req2, ignore, [&](auto ec, auto){
// Last callback to the called.
std::cout << "req2" << std::endl;
BOOST_CHECK_EQUAL(ec, boost::system::error_code{});
BOOST_TEST(!ec);
BOOST_TEST(seen1);
BOOST_TEST(seen3);
seen2 = true;
@@ -73,7 +74,7 @@ BOOST_AUTO_TEST_CASE(hello_priority)
conn->async_exec(req3, ignore, [&](auto ec, auto){
// Callback that will be called first.
std::cout << "req3" << std::endl;
BOOST_CHECK_EQUAL(ec, boost::system::error_code{});
BOOST_TEST(!ec);
BOOST_TEST(!seen1);
BOOST_TEST(!seen2);
seen3 = true;
@@ -122,7 +123,7 @@ BOOST_AUTO_TEST_CASE(cancel_request_if_not_connected)
BOOST_AUTO_TEST_CASE(correct_database)
{
config cfg;
auto cfg = make_test_config();
cfg.database_index = 2;
net::io_context ioc;
@@ -154,3 +155,36 @@ BOOST_AUTO_TEST_CASE(correct_database)
BOOST_CHECK_EQUAL(cfg.database_index.value(), index);
}
BOOST_AUTO_TEST_CASE(large_number_of_concurrent_requests_issue_170)
{
// See https://github.com/boostorg/redis/issues/170
std::string payload;
payload.resize(1024);
std::fill(std::begin(payload), std::end(payload), 'A');
net::io_context ioc;
auto conn = std::make_shared<connection>(ioc);
auto cfg = make_test_config();
cfg.health_check_interval = std::chrono::seconds(0);
conn->async_run(cfg, {}, net::detached);
int counter = 0;
int const repeat = 8000;
for (int i = 0; i < repeat; ++i) {
auto req = std::make_shared<request>();
req->push("PING", payload);
conn->async_exec(*req, ignore, [req, &counter, conn](auto ec, auto) {
BOOST_TEST(!ec);
if (++counter == repeat)
conn->cancel();
});
}
ioc.run();
BOOST_CHECK_EQUAL(counter, repeat);
}

View File

@@ -29,7 +29,6 @@ using boost::redis::response;
using boost::redis::generic_response;
using boost::redis::ignore;
using boost::redis::ignore_t;
using boost::redis::config;
using boost::redis::logger;
using boost::redis::connection;
using namespace std::chrono_literals;
@@ -39,8 +38,8 @@ auto implicit_cancel_of_req_written() -> net::awaitable<void>
auto ex = co_await net::this_coro::executor;
auto conn = std::make_shared<connection>(ex);
config cfg;
cfg.health_check_interval = std::chrono::seconds{0};
auto cfg = make_test_config();
cfg.health_check_interval = std::chrono::seconds::zero();
run(conn, cfg);
// See NOTE1.
@@ -106,7 +105,7 @@ BOOST_AUTO_TEST_CASE(test_cancel_of_req_written_on_run_canceled)
conn->async_exec(req0, ignore, c0);
config cfg;
auto cfg = make_test_config();
cfg.health_check_interval = std::chrono::seconds{5};
run(conn);

View File

@@ -25,7 +25,6 @@ using boost::redis::ignore_t;
using boost::redis::error;
using boost::redis::logger;
using boost::redis::operation;
using redis::config;
using namespace std::chrono_literals;
BOOST_AUTO_TEST_CASE(no_ignore_error)
@@ -242,6 +241,8 @@ BOOST_AUTO_TEST_CASE(subscriber_wrong_syntax)
conn->async_exec(req1, ignore, c1);
generic_response gresp;
conn->set_receive_response(gresp);
auto c3 = [&](auto ec, auto)
{
std::cout << "async_receive" << std::endl;
@@ -254,7 +255,7 @@ BOOST_AUTO_TEST_CASE(subscriber_wrong_syntax)
conn->cancel(operation::reconnection);
};
conn->async_receive(gresp, c3);
conn->async_receive(c3);
run(conn);

View File

@@ -57,12 +57,12 @@ BOOST_AUTO_TEST_CASE(request_retry_false)
auto c2 = [&](auto ec, auto){
std::cout << "c2" << std::endl;
BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled);
BOOST_CHECK_EQUAL(ec, boost::asio::error::operation_aborted);
};
auto c1 = [&](auto ec, auto){
std::cout << "c1" << std::endl;
BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled);
BOOST_CHECK_EQUAL(ec, boost::asio::error::operation_aborted);
};
auto c0 = [&](auto ec, auto){
@@ -74,7 +74,7 @@ BOOST_AUTO_TEST_CASE(request_retry_false)
conn->async_exec(req0, ignore, c0);
config cfg;
auto cfg = make_test_config();
cfg.health_check_interval = 5s;
run(conn);
@@ -137,7 +137,7 @@ BOOST_AUTO_TEST_CASE(request_retry_true)
conn->async_exec(req0, ignore, c0);
config cfg;
auto cfg = make_test_config();
cfg.health_check_interval = 5s;
conn->async_run(cfg, {}, [&](auto ec){
std::cout << ec.message() << std::endl;

View File

@@ -26,7 +26,7 @@ using boost::redis::request;
using boost::redis::response;
using boost::redis::ignore;
using boost::redis::ignore_t;
using redis::config;
using boost::system::error_code;
using boost::redis::logger;
using namespace std::chrono_literals;
@@ -49,7 +49,7 @@ BOOST_AUTO_TEST_CASE(receives_push_waiting_resps)
auto c3 =[](auto ec, auto...)
{
BOOST_TEST(!!ec);
std::cout << "c3: " << ec.message() << std::endl;
};
auto c2 =[&, conn](auto ec, auto...)
@@ -66,15 +66,14 @@ BOOST_AUTO_TEST_CASE(receives_push_waiting_resps)
conn->async_exec(req1, ignore, c1);
run(conn, {}, {});
run(conn, make_test_config(), {});
bool push_received = false;
conn->async_receive(ignore, [&, conn](auto ec, auto){
conn->async_receive([&, conn](auto ec, auto){
std::cout << "async_receive" << std::endl;
BOOST_TEST(!ec);
conn->cancel(operation::run);
conn->cancel(operation::reconnection);
push_received = true;
conn->cancel();
});
ioc.run();
@@ -87,29 +86,45 @@ BOOST_AUTO_TEST_CASE(push_received1)
net::io_context ioc;
auto conn = std::make_shared<connection>(ioc);
// Trick: Uses SUBSCRIBE because this command has no response or
// better said, its response is a server push, which is what we
// want to test. We send two because we want to test both
// async_receive and receive.
request req;
//req.push("HELLO", 3);
req.push("SUBSCRIBE", "channel");
req.push("SUBSCRIBE", "channel1");
req.push("SUBSCRIBE", "channel2");
conn->async_exec(req, ignore, [conn](auto ec, auto){
std::cout << "async_exec" << std::endl;
BOOST_TEST(!ec);
});
run(conn);
bool push_async_received = false;
conn->async_receive([&, conn](auto ec, auto){
std::cout << "(1) async_receive" << std::endl;
bool push_received = false;
conn->async_receive(ignore, [&, conn](auto ec, auto){
std::cout << "async_receive" << std::endl;
BOOST_TEST(!ec);
conn->cancel(operation::run);
conn->cancel(operation::reconnection);
push_received = true;
push_async_received = true;
// Receives the second push synchronously.
error_code ec2;
std::size_t res = 0;
res = conn->receive(ec2);
BOOST_TEST(!ec2);
BOOST_TEST(res != std::size_t(0));
// Tries to receive a third push synchronously.
ec2 = {};
res = conn->receive(ec2);
BOOST_CHECK_EQUAL(ec2, boost::redis::make_error_code(boost::redis::error::sync_receive_push_failed));
conn->cancel();
});
run(conn);
ioc.run();
BOOST_TEST(push_received);
BOOST_TEST(push_async_received);
}
BOOST_AUTO_TEST_CASE(push_filtered_out)
@@ -128,7 +143,7 @@ BOOST_AUTO_TEST_CASE(push_filtered_out)
BOOST_TEST(!ec);
});
conn->async_receive(ignore, [conn](auto ec, auto){
conn->async_receive([&, conn](auto ec, auto){
BOOST_TEST(!ec);
conn->cancel(operation::reconnection);
});
@@ -146,12 +161,12 @@ net::awaitable<void>
push_consumer1(std::shared_ptr<connection> conn, bool& push_received)
{
{
auto [ec, ev] = co_await conn->async_receive(ignore, as_tuple(net::use_awaitable));
auto [ec, ev] = co_await conn->async_receive(as_tuple(net::use_awaitable));
BOOST_TEST(!ec);
}
{
auto [ec, ev] = co_await conn->async_receive(ignore, as_tuple(net::use_awaitable));
auto [ec, ev] = co_await conn->async_receive(as_tuple(net::use_awaitable));
BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled);
}
@@ -190,8 +205,10 @@ BOOST_AUTO_TEST_CASE(test_push_adapter)
req.push("SUBSCRIBE", "channel");
req.push("PING");
conn->async_receive(error_tag_obj, [conn](auto ec, auto) {
BOOST_CHECK_EQUAL(ec, boost::redis::error::incompatible_size);
conn->set_receive_response(error_tag_obj);
conn->async_receive([&, conn](auto ec, auto) {
BOOST_CHECK_EQUAL(ec, boost::asio::experimental::error::channel_cancelled);
conn->cancel(operation::reconnection);
});
@@ -199,7 +216,10 @@ BOOST_AUTO_TEST_CASE(test_push_adapter)
BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled);
});
run(conn);
auto cfg = make_test_config();
conn->async_run(cfg, {}, [](auto ec){
BOOST_CHECK_EQUAL(ec, boost::redis::error::incompatible_size);
});
ioc.run();
@@ -210,7 +230,7 @@ BOOST_AUTO_TEST_CASE(test_push_adapter)
net::awaitable<void> push_consumer3(std::shared_ptr<connection> conn)
{
for (;;) {
co_await conn->async_receive(ignore, net::use_awaitable);
co_await conn->async_receive(net::use_awaitable);
}
}
@@ -237,9 +257,8 @@ BOOST_AUTO_TEST_CASE(many_subscribers)
auto c11 =[&](auto ec, auto...)
{
std::cout << "quit sent" << std::endl;
std::cout << "quit sent: " << ec.message() << std::endl;
conn->cancel(operation::reconnection);
BOOST_TEST(!ec);
};
auto c10 =[&](auto ec, auto...)
{
@@ -299,7 +318,7 @@ BOOST_AUTO_TEST_CASE(many_subscribers)
conn->async_exec(req0, ignore, c0);
run(conn, {}, {});
run(conn, make_test_config(), {});
net::co_spawn(ioc.get_executor(), push_consumer3(conn), net::detached);
ioc.run();

View File

@@ -18,7 +18,6 @@ using boost::redis::operation;
using boost::redis::request;
using boost::redis::response;
using boost::redis::ignore;
using boost::redis::config;
using namespace std::chrono_literals;
BOOST_AUTO_TEST_CASE(test_eof_no_error)
@@ -62,7 +61,7 @@ BOOST_AUTO_TEST_CASE(test_async_run_exits)
auto c3 = [](auto ec, auto)
{
std::clog << "c3: " << ec.message() << std::endl;
BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled);
BOOST_CHECK_EQUAL(ec, boost::asio::error::operation_aborted);
};
auto c2 = [&](auto ec, auto)
@@ -83,7 +82,7 @@ BOOST_AUTO_TEST_CASE(test_async_run_exits)
// The healthy checker should not be the cause of async_run
// completing, so we disable.
config cfg;
auto cfg = make_test_config();
cfg.health_check_interval = 0s;
cfg.reconnect_wait_interval = 0s;
run(conn, cfg);

View File

@@ -19,7 +19,6 @@ using boost::system::error_code;
using boost::redis::request;
using boost::redis::response;
using boost::redis::ignore;
using boost::redis::config;
using boost::redis::logger;
using boost::redis::operation;
using boost::redis::connection;
@@ -40,7 +39,7 @@ net::awaitable<void> test_reconnect_impl()
int i = 0;
for (; i < 5; ++i) {
error_code ec1, ec2;
config cfg;
auto cfg = make_test_config();
logger l;
co_await conn->async_exec(req, ignore, net::redirect_error(net::use_awaitable, ec1));
//BOOST_TEST(!ec);
@@ -76,7 +75,7 @@ auto async_test_reconnect_timeout() -> net::awaitable<void>
req1.push("BLPOP", "any", 0);
st.expires_after(std::chrono::seconds{1});
config cfg;
auto cfg = make_test_config();
co_await (
conn->async_exec(req1, ignore, redir(ec1)) ||
st.async_wait(redir(ec3))
@@ -100,7 +99,7 @@ auto async_test_reconnect_timeout() -> net::awaitable<void>
std::cout << "ccc" << std::endl;
BOOST_CHECK_EQUAL(ec1, boost::system::errc::errc_t::operation_canceled);
BOOST_CHECK_EQUAL(ec1, boost::asio::error::operation_aborted);
}
BOOST_AUTO_TEST_CASE(test_reconnect_and_idle)

View File

@@ -20,7 +20,6 @@
namespace net = boost::asio;
using boost::redis::operation;
using boost::redis::config;
using boost::redis::connection;
using boost::system::error_code;
using net::experimental::as_tuple;
@@ -41,7 +40,7 @@ auto async_cancel_run_with_timer() -> net::awaitable<void>
st.expires_after(1s);
error_code ec1, ec2;
config cfg;
auto cfg = make_test_config();
logger l;
co_await (conn.async_run(cfg, l, redir(ec1)) || st.async_wait(redir(ec2)));
@@ -67,7 +66,7 @@ async_check_cancellation_not_missed(int n, std::chrono::milliseconds ms) -> net:
for (auto i = 0; i < n; ++i) {
timer.expires_after(ms);
error_code ec1, ec2;
config cfg;
auto cfg = make_test_config();
logger l;
co_await (conn.async_run(cfg, l, redir(ec1)) || timer.async_wait(redir(ec2)));
BOOST_CHECK_EQUAL(ec1, boost::asio::error::operation_aborted);

126
test/test_conn_tls.cpp Normal file
View File

@@ -0,0 +1,126 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#include <boost/asio/ssl/host_name_verification.hpp>
#include <boost/system/error_code.hpp>
#include <boost/redis/connection.hpp>
#define BOOST_TEST_MODULE conn-tls
#include <boost/test/included/unit_test.hpp>
#include "common.hpp"
namespace net = boost::asio;
using connection = boost::redis::connection;
using boost::redis::request;
using boost::redis::response;
using boost::redis::config;
using boost::system::error_code;
// CA certificate that signed the test server's certificate.
// This is a self-signed CA created for testing purposes.
// This must match tools/tls/ca.crt contents
static constexpr const char* ca_certificate = R"%(-----BEGIN CERTIFICATE-----
MIIFSzCCAzOgAwIBAgIUNd7VUuGK4+ylzCOrmeckg2+TqX8wDQYJKoZIhvcNAQEL
BQAwNTETMBEGA1UECgwKUmVkaXMgVGVzdDEeMBwGA1UEAwwVQ2VydGlmaWNhdGUg
QXV0aG9yaXR5MB4XDTI0MDMzMTE0MjUyM1oXDTM0MDMyOTE0MjUyM1owNTETMBEG
A1UECgwKUmVkaXMgVGVzdDEeMBwGA1UEAwwVQ2VydGlmaWNhdGUgQXV0aG9yaXR5
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA5AMV5V66wt+MM4+oCzH0
xPi++j23p8AOa0o3dxNd4tm5y++gAdKfoxj7oh32ZuYHA5V+sGNEalN/b3GlKXMm
ThdVPSwqOQduny19wrb126ZeQXCfqwgSZQ+rgzaIYpw8/GRRuLDunmsdaR2eiptp
dbv6g6P/aIF6P9mfuekwCC9KBCV6ftqOEnzulNLVw4JjY0rKB9NZqONKVMfWpNyC
zJLCkGmza7BOpybhloZIxGJz033yCjDvIQr9GUWsA5rU9LdUiL+F1W0pWkIel1qo
Evo0EIl3+EOcSSzETI7NPHgnSzNau39ZShV4UBj2lw0DWeNcobeMBQ8ItmqEU6V0
gCEqfUnt10bGIDdmV3D5FKPgvhFvEjQULnblLeLDQ6XDFf+xbGEVjvTzVkLjvyKm
H2D+SKw2O+eDU/0+xhpAf+QsWlm6pmvKWjXI5wK1rh2yssBK2pmY3LuuZCdGrvXb
KX4j/4S9qMr43Hmyoyz0gE5I5rplqot8TvT9O/JsgQYd9fYSvdB+HbqAlJzpBZFl
xbVBXxl0AlDFwQtNMX5ylEQPvYVDKA1M+DTqRTgQKctTfccwvovY3YMV7m5YoODZ
ya2YSBRfQim6VsC+QPYs7p2dk1larIoMMaTaU02oMY+qT2d/eyhWKBv5W9LuowTQ
bWa3ZhWN8lXriPgJOQnZ6iUCAwEAAaNTMFEwHQYDVR0OBBYEFCpEPlClLrgu1zFN
Fmas5G4ybNRJMB8GA1UdIwQYMBaAFCpEPlClLrgu1zFNFmas5G4ybNRJMA8GA1Ud
EwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIBAFLl1NZHp0NT5Av4GKmsJFeI
cJOgcIygjR4SBGDAxyPqVpZk0x1q64gJsfOe1ARyI4olQPqO08FZMeB+VBYuqR3S
fEVQZz2FT5U7IVAEZwWHOcWkrrVpEZC6PZktYJ7Yqju6+ic93inoPrHhGNZ5XA/Y
GSfwriWkyWm2SOk35ChFH67MbPWmve8CRAXRmrOCByXwXF87wdqVYZUvH9xDe6WU
snFWXVHr2NA7Re8ZIGp7yJOwwW+CZagepNCPUDwnI0fWOahtOTzonIjq8bfgTZPx
2e7lBuAr9tVMpoeyUytVOlNJDojZAtKOpfMwhAG8ydhk+78aK07VVbnSYVhv7ctU
kkkldqP/S3lBlWo44oOxenwLc9vDQNh64py7eQTD7Qv+TjqAG0ljHIDbVqlkQsgR
pQsu7keG9O1xASSTLZVZN2/alNewpqE/eFRfPM3mtUiTiIZvSxiQnWQMbKofAZH5
HwhVli4RKWRWPqpof4GFNkB8XwfBE+gdlFuWtyg0oRyV3sJ6Zn7E+lUpbQX4CFx3
97vekaFNBchNYMcP3TZ9LwxTx1xOWZ5HHrHyzASG3uz2rqwAsEmdRbmK03KfEQyQ
YpNY718btZ1D6lLino9VMgzaPhUs79bHC64O4ncl7hRclK9qa3KLQdCG1cbIR7G0
2XVYrfsnPHX0CsPDIy7L
-----END CERTIFICATE-----)%";
static config make_tls_config()
{
config cfg;
cfg.use_ssl = true;
cfg.addr.host = get_server_hostname();
cfg.addr.port = "6380";
return cfg;
}
BOOST_AUTO_TEST_CASE(ping_internal_ssl_context)
{
auto const cfg = make_tls_config();
std::string const in = "Kabuf";
request req;
req.push("PING", in);
response<std::string> resp;
net::io_context ioc;
connection conn{ioc};
// The custom server uses a certificate signed by a CA
// that is not trusted by default - skip verification.
conn.next_layer().set_verify_mode(net::ssl::verify_none);
conn.async_exec(req, resp, [&](error_code ec, auto) {
BOOST_TEST(ec == std::error_code());
conn.cancel();
});
conn.async_run(cfg, {}, [](auto) { });
ioc.run();
BOOST_CHECK_EQUAL(in, std::get<0>(resp).value());
}
BOOST_AUTO_TEST_CASE(ping_custom_ssl_context)
{
auto const cfg = make_tls_config();
std::string const in = "Kabuf";
request req;
req.push("PING", in);
response<std::string> resp;
net::io_context ioc;
net::ssl::context ctx{boost::asio::ssl::context::tls_client};
// Configure the SSL context to trust the CA that signed the server's certificate.
// The test certificate uses "redis" as its common name, regardless of the actual server's hostname
ctx.add_certificate_authority(net::const_buffer(ca_certificate, std::strlen(ca_certificate)));
ctx.set_verify_mode(net::ssl::verify_peer);
ctx.set_verify_callback(net::ssl::host_name_verification("redis"));
connection conn{ioc, std::move(ctx)};
conn.async_exec(req, resp, [&](auto ec, auto) {
BOOST_TEST(ec == std::error_code());
conn.cancel();
});
conn.async_run(cfg, {}, [](auto) { });
ioc.run();
BOOST_CHECK_EQUAL(in, std::get<0>(resp).value());
}

64
test/test_issue_181.cpp Normal file
View File

@@ -0,0 +1,64 @@
/* Copyright (c) 2018-2024 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#include <boost/redis/connection.hpp>
#include <boost/redis/logger.hpp>
#include <boost/asio/awaitable.hpp>
#include <boost/asio/use_awaitable.hpp>
#define BOOST_TEST_MODULE conn-quit
#include <boost/test/included/unit_test.hpp>
#include <chrono>
#include <iostream>
#include <memory>
#include <optional>
#include <string>
#include "common.hpp"
namespace net = boost::asio;
using boost::redis::request;
using boost::redis::request;
using boost::redis::response;
using boost::redis::ignore;
using boost::redis::logger;
using boost::redis::config;
using boost::redis::operation;
using boost::redis::connection;
using boost::system::error_code;
using namespace std::chrono_literals;
BOOST_AUTO_TEST_CASE(issue_181)
{
using connection_base = boost::redis::detail::connection_base<net::any_io_executor>;
auto const level = boost::redis::logger::level::debug;
net::io_context ioc;
auto ctx = net::ssl::context{net::ssl::context::tlsv12_client};
connection_base conn{ioc.get_executor(), std::move(ctx), 1000000};
net::steady_timer timer{ioc};
timer.expires_after(std::chrono::seconds{1});
auto run_cont = [&](auto ec){
std::cout << "async_run1: " << ec.message() << std::endl;
};
auto cfg = make_test_config();
cfg.health_check_interval = std::chrono::seconds{0};
cfg.reconnect_wait_interval = std::chrono::seconds{0};
conn.async_run(cfg, boost::redis::logger{level}, run_cont);
BOOST_TEST(!conn.run_is_canceled());
// Uses a timer to wait some time until run has been called.
auto timer_cont = [&](auto ec){
std::cout << "timer_cont: " << ec.message() << std::endl;
BOOST_TEST(!conn.run_is_canceled());
conn.cancel(operation::run);
BOOST_TEST(conn.run_is_canceled());
};
timer.async_wait(timer_cont);
ioc.run();
}

View File

@@ -19,6 +19,7 @@
#include <boost/test/included/unit_test.hpp>
#include <tuple>
#include <iostream>
#include "common.hpp"
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
@@ -47,11 +48,15 @@ receiver(std::shared_ptr<connection> conn) -> net::awaitable<void>
for (;;) {
std::cout << "aaaa" << std::endl;
error_code ec;
co_await conn->async_receive(ignore, redirect_error(use_awaitable, ec));
if (ec)
co_await conn->async_receive(redirect_error(use_awaitable, ec));
if (ec) {
std::cout << "Error in async_receive" << std::endl;
break;
}
}
}
std::cout << "Exiting the receiver." << std::endl;
}
auto
@@ -82,13 +87,14 @@ periodic_task(std::shared_ptr<connection> conn) -> net::awaitable<void>
conn->cancel(operation::reconnection);
}
auto co_main(config cfg) -> net::awaitable<void>
auto co_main(config) -> net::awaitable<void>
{
auto ex = co_await net::this_coro::executor;
auto conn = std::make_shared<connection>(ex);
net::co_spawn(ex, receiver(conn), net::detached);
net::co_spawn(ex, periodic_task(conn), net::detached);
auto cfg = make_test_config();
conn->async_run(cfg, {}, net::consign(net::detached, conn));
}

View File

@@ -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());
}

View File

@@ -0,0 +1,90 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#include <boost/redis/detail/runner.hpp>
#include <boost/redis/resp3/serialization.hpp>
#include <boost/redis/adapter/adapt.hpp>
#define BOOST_TEST_MODULE conn-quit
#include <boost/test/included/unit_test.hpp>
#include <string>
#include <iostream>
using boost::redis::request;
using boost::redis::config;
using boost::redis::detail::push_hello;
using boost::redis::adapter::adapt2;
using boost::redis::adapter::result;
using boost::redis::resp3::detail::deserialize;
BOOST_AUTO_TEST_CASE(low_level_sync_sans_io)
{
try {
result<std::set<std::string>> resp;
char const* wire = "~6\r\n+orange\r\n+apple\r\n+one\r\n+two\r\n+three\r\n+orange\r\n";
deserialize(wire, adapt2(resp));
for (auto const& e: resp.value())
std::cout << e << std::endl;
} catch (std::exception const& e) {
std::cerr << e.what() << std::endl;
exit(EXIT_FAILURE);
}
}
BOOST_AUTO_TEST_CASE(config_to_hello)
{
config cfg;
cfg.clientname = "";
request req;
push_hello(cfg, req);
std::string_view const expected = "*2\r\n$5\r\nHELLO\r\n$1\r\n3\r\n";
BOOST_CHECK_EQUAL(req.payload(), expected);
}
BOOST_AUTO_TEST_CASE(config_to_hello_with_select)
{
config cfg;
cfg.clientname = "";
cfg.database_index = 10;
request req;
push_hello(cfg, req);
std::string_view const expected =
"*2\r\n$5\r\nHELLO\r\n$1\r\n3\r\n"
"*2\r\n$6\r\nSELECT\r\n$2\r\n10\r\n";
BOOST_CHECK_EQUAL(req.payload(), expected);
}
BOOST_AUTO_TEST_CASE(config_to_hello_cmd_clientname)
{
config cfg;
request req;
push_hello(cfg, req);
std::string_view const expected = "*4\r\n$5\r\nHELLO\r\n$1\r\n3\r\n$7\r\nSETNAME\r\n$11\r\nBoost.Redis\r\n";
BOOST_CHECK_EQUAL(req.payload(), expected);
}
BOOST_AUTO_TEST_CASE(config_to_hello_cmd_auth)
{
config cfg;
cfg.clientname = "";
cfg.username = "foo";
cfg.password = "bar";
request req;
push_hello(cfg, req);
std::string_view const expected = "*5\r\n$5\r\nHELLO\r\n$1\r\n3\r\n$4\r\nAUTH\r\n$3\r\nfoo\r\n$3\r\nbar\r\n";
BOOST_CHECK_EQUAL(req.payload(), expected);
}

Some files were not shown because too many files have changed in this diff Show More