2
0
mirror of https://github.com/boostorg/redis.git synced 2026-01-20 05:02:12 +00:00

Compare commits

..

36 Commits

Author SHA1 Message Date
Marcelo Zimbres
a76a621b0b More code review changes. 2025-08-02 13:38:19 +02:00
Marcelo Zimbres
16bf57cf33 Add parse event init, node and done. 2025-07-26 22:44:53 +02:00
Anarthal (Rubén Pérez)
88d8f3c0ca Makes all objects in connection have a stable address (#285)
Moves read_buffer memory reservation to the connection's constructor
Makes read_buffer memory reservation size be a power of 2
2025-07-25 22:51:14 +02:00
Marcelo
20ab2c7e70 Merge pull request #283 from boostorg/refactoring_clean_code
Removes async_append_some
2025-07-22 20:51:50 +02:00
Marcelo Zimbres
8ee2213efe Code review changes. 2025-07-20 18:38:02 +02:00
Marcelo Zimbres
97d71d1d6b Removes async_append_some. 2025-07-17 12:37:17 +02:00
Anarthal (Rubén Pérez)
76129bbcb8 Merge pull request #282 from boostorg/develop
Migrates the documentation to Asciidoc/MrDocs (#276)
2025-07-08 21:07:52 +02:00
Anarthal (Rubén Pérez)
adf17f2b3b Migrates the documentation to Asciidoc/MrDocs (#276)
Fixes some typos

close #247
2025-07-02 23:27:33 +02:00
Marcelo
6499818911 Merge pull request #280 from boostorg/develop
Sync master with develop for boost release
2025-07-02 13:27:48 +02:00
Marcelo Zimbres
963ae8d145 Disables msvc-14.2 builds 2025-07-02 13:09:58 +02:00
Anarthal (Rubén Pérez)
fe4ba64a97 Makes the reconnection loop retry on errors (#275)
After an error is encountered during name resolution, connection establishment, SSL handshake, Redis hello, reading or writing, async_run will now keep retrying if the configuration requests it

close #255
2025-06-29 19:02:22 +02:00
Marcelo
b82224700e Merge pull request #274 from boostorg/refactoring_clean_code
Adds a sans-io fsm to the read operation.
2025-06-23 22:40:42 +02:00
Marcelo Zimbres
620b1e9510 Adds a sans-io fsm for read operation. 2025-06-23 22:24:46 +02:00
Anarthal (Rubén Pérez)
f04d97ffa5 Updates the Logger interface to allow extensibility and type erasure (#273)
Removes all the logger::on_xxx functions
Removes the Logger template parameter to async_run
Adds a logger constructor that allows passing a std::function to customize logging behavior
Adds constructors to connection and basic_connection taking a logger
Deprecates config::logger_prefix
Deprecates the async_run overload taking a logger parameter
Deprecates the basic_connection::async_run overload not taking any config object
Deprecates the basic_connection::next_layer_type typedef
Makes the default log level logger::info
Makes the logging thread-safe
Cleans up deprecated functionality from examples
Adds docs on logging
Adds an example on how to integrate spdlog into Boost.Redis logging

close #213
2025-06-23 12:07:21 +02:00
Anarthal (Rubén Pérez)
7304d99bf6 Adds support for UNIX sockets (#272)
close #246
2025-06-20 13:23:39 +02:00
Anarthal (Rubén Pérez)
89a42dbf74 async_exec now uses a sans-io strategy (#250)
async_exec now supports partial and total cancellation when the request is pending
Added docs on async_exec's cancellation support
Added detail::exec_fsm and macros for coroutine emulation
Requests are now removed from the multiplexer when an error is encountered
2025-06-10 20:42:59 +02:00
Anarthal (Rubén Pérez)
a39d130240 Adds a redis_stream class that encapsulates connection, reading and writing (#266)
Deprecates get_ssl_context for connection and basic_connection
Deprecates reset_stream for connection and basic_connection
2025-06-07 19:15:42 +02:00
Anarthal (Rubén Pérez)
6d5b550bb3 Restores the TLS tests in CI (#267)
Renews test certificates
Recovers and refactors test_conn_tls.cpp
Adds a test for TLS reconnection
2025-06-07 18:37:09 +02:00
Anarthal (Rubén Pérez)
4a2085c800 Uses ignore as default for connection::async_exec (#265)
Updates test_conn_echo_stress.cpp to use this default

close #264
2025-06-07 13:21:20 +02:00
Anarthal (Rubén Pérez)
2fc54bc73b Increases the robustness of integration tests (#259)
Updates multiplexer to make requests complete with error_code() rather than error_code(0)
Integration tests now use io_context::run_for to run with a timeout and avoid deadlocks
Tests now use concrete lambdas where generic ones are not required
Tests now use BOOST_TEST with operator== to print values on error
Tests now use anonymous namespaces to detect dead code
Adds run_coroutine_test and removed start from common.hpp
Updates test_reconnect to perform relevant checks
Refactors how test_issue_50 launches its coroutines
Groups tests in CMake as unit or integration
Updates Jamfile tests to contain all unit tests and only unit tests
2025-06-06 12:48:40 +02:00
Anarthal (Rubén Pérez)
0c8c6fcc09 Fixes a memory corruption in logger and adds sanitizer builds to CI (#261)
logger now owns the prefix string
This fixes a use-after-stack-return memory error in async_run
Adds a clang-19 and a gcc-14 build to CI with the address and undefined behavior sanitizer enabled

close #260
2025-06-01 13:40:37 +02:00
Marcelo
479068e778 Merge pull request #258 from boostorg/refactoring_clean_code
Refactors reader_op to simplify sans-io.
2025-05-29 14:52:04 +02:00
Marcelo Zimbres
35d50700b9 Refactors reader_op to simplify sans-io. 2025-05-25 12:04:28 +02:00
Anarthal (Rubén Pérez)
b58e4f94de Marks the next_layer() functions as deprecated (#256)
CI builds now use BOOST_ALLOW_DEPRECATED to prevent warnings
2025-05-25 11:05:07 +02:00
Anarthal (Rubén Pérez)
e8b13bd7a0 Enables -Wall and -Werror in CIs (#254)
Removed warnings in:

* bench/echo_server_client.cpp (parameter shadowing)
* include/boost/redis/adapter/detail/adapters.hpp (unused parameter)
* include/boost/redis/connection.hpp (unused parameter)
* include/boost/redis/resp3/impl/parser.ipp (signed to unsigned conversion)
* test/test_conversions.cpp (signed to unsigned comparison)
* test/test_issue_50.cpp (superfluous move)
* test/test_low_level_sync_sans_io.cpp (signed to unsigned comparison)
2025-05-20 20:30:15 +02:00
Anarthal (Rubén Pérez)
c1c50e3e24 Reconnection no longer causes the writer to perform busy waiting (#253)
Reconnection now uses a separate timer to wait

close #252
2025-05-19 16:46:56 +02:00
Anarthal (Rubén Pérez)
328ad97a79 Adds automatic formatting with clang-format 2025-05-12 13:49:55 +02:00
Marcelo
1060733b84 Merge pull request #248 from boostorg/refactoring_clean_code
Sans-io multiplexing
2025-05-10 20:52:10 +02:00
Marcelo Zimbres
f9d0679be5 Splits the multiplexer out of the connection. 2025-05-04 11:39:34 +02:00
Marcelo
11e54de8f8 Merge pull request #244 from boostorg/develop
Sync master with develop
2025-04-26 20:07:46 +02:00
Marcelo Zimbres
109248d53b Removes unnecessary dynamic buffer. 2025-04-26 19:24:21 +02:00
Marcelo
f7b4ec291e Merge pull request #239 from anarthal/bugfix/238-int-conversions
Responses now admit all integer types again
2025-04-07 22:48:48 +02:00
Ruben Perez
c4e31e5f2f Floating point tests 2025-04-07 11:38:44 +02:00
Ruben Perez
f32da1b0e3 tests for boolean 2025-04-07 11:30:28 +02:00
Ruben Perez
8b98081ccf Fixed the problem 2025-04-07 11:21:56 +02:00
Ruben Perez
77ade122ea Failing test 2025-04-07 11:12:07 +02:00
144 changed files with 10566 additions and 7610 deletions

176
.clang-format Normal file
View File

@@ -0,0 +1,176 @@
---
Language: Cpp
ColumnLimit: 100
IndentWidth: 3
BreakBeforeBraces: Custom
BraceWrapping:
AfterCaseLabel: true
AfterClass: false
AfterControlStatement: false
AfterEnum: true
AfterFunction: true
AfterNamespace: false
AfterStruct: false
AfterUnion: false
AfterExternBlock: false
BeforeCatch: false
BeforeElse: false
IndentBraces: false
SplitEmptyFunction: false
SplitEmptyRecord: false
SplitEmptyNamespace: true
AccessModifierOffset: -3
BinPackArguments: false
BinPackParameters: false
AlignAfterOpenBracket: AlwaysBreak
PointerAlignment: Left
IncludeBlocks: Regroup
IncludeCategories:
- Regex: '^<boost/redis.*>|<boost/redis/.*>'
Priority: -10
SortPriority: 1
- Regex: '^<boost/.*>'
Priority: -8
SortPriority: 3
- Regex: "^<.*"
Priority: -6
SortPriority: 5
- Regex: ".*"
Priority: -5
SortPriority: 4
IndentCaseLabels: true
AllowShortCaseLabelsOnASingleLine: true
AllowAllArgumentsOnNextLine: false
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortIfStatementsOnASingleLine: Never
AllowShortLoopsOnASingleLine: false
AlignArrayOfStructures: Left
DerivePointerAlignment: false
PenaltyBreakAssignment: 2000000
PenaltyBreakBeforeFirstCallParameter: 0
PenaltyBreakOpenParenthesis: 0
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyBreakScopeResolution: 1000000
PenaltyReturnTypeOnItsOwnLine: 200000000
# Defaults (based on Google)
AlignConsecutiveMacros: false
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlines: Left
AlignOperands: true
AlignTrailingComments: true
AllowAllConstructorInitializersOnNextLine: true
AllowShortBlocksOnASingleLine: Never
AllowShortFunctionsOnASingleLine: All
AllowShortLambdasOnASingleLine: Empty
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: ExceptShortType
AlwaysBreakBeforeMultilineStrings: true
AlwaysBreakTemplateDeclarations: Yes
BreakTemplateDeclarations: Leave
BreakBeforeBinaryOperators: None
BreakBeforeInheritanceComma: false
BreakInheritanceList: BeforeColon
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: true
BreakConstructorInitializers: BeforeColon
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: true
CommentPragmas: '(^ IWYU pragma:)|(^\\copydoc )|(^ ?\\n)'
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ConstructorInitializerIndentWidth: 0
ContinuationIndentWidth: 3
Cpp11BracedListStyle: true
DeriveLineEnding: true
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: true
ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
IncludeIsMainRegex: null
IncludeIsMainSourceRegex: ""
IndentGotoLabels: false
IndentPPDirectives: None
IndentWrappedFunctionNames: false
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: false
MacroBlockBegin: ""
MacroBlockEnd: ""
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBinPackProtocolList: Never
ObjCBlockIndentWidth: 2
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
RawStringFormats:
- Language: Cpp
Delimiters:
- cc
- CC
- cpp
- Cpp
- CPP
- "c++"
- "C++"
CanonicalDelimiter: ""
BasedOnStyle: google
- Language: TextProto
Delimiters:
- pb
- PB
- proto
- PROTO
EnclosingFunctions:
- EqualsProto
- EquivToProto
- PARSE_PARTIAL_TEXT_PROTO
- PARSE_TEST_PROTO
- PARSE_TEXT_PROTO
- ParseTextOrDie
- ParseTextProtoOrDie
CanonicalDelimiter: ""
BasedOnStyle: google
ReflowComments: false
SortIncludes: true
SortUsingDeclarations: false
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyBlock: true
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 2
SpacesInAngles: false
SpacesInConditionalStatement: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
SpaceBeforeSquareBrackets: false
Standard: Auto
StatementMacros:
- Q_UNUSED
- QT_REQUIRE_VERSION
TabWidth: 8
UseCRLF: false
UseTab: Never
AlignConsecutiveShortCaseStatements:
Enabled: true
AcrossEmptyLines: false
AcrossComments: true
AlignCaseColons: false
---

View File

@@ -21,8 +21,8 @@ jobs:
fail-fast: false
matrix:
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.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:
@@ -100,7 +100,7 @@ jobs:
fail-fast: false
matrix:
include:
- { toolset: msvc-14.2, os: windows-2019 }
#- { toolset: msvc-14.2, os: windows-2019 }
- { toolset: msvc-14.3, os: windows-2022 }
env:
OPENSSL_ROOT: "C:\\Program Files\\OpenSSL"
@@ -133,49 +133,49 @@ jobs:
include:
- toolset: gcc-11
install: g++-11
os: ubuntu-latest
container: ubuntu:22.04
cxxstd: '17'
build-type: 'Debug'
ldflags: ''
- toolset: gcc-11
install: g++-11
os: ubuntu-latest
container: ubuntu:22.04
cxxstd: '20'
build-type: 'Release'
ldflags: ''
- toolset: clang-11
install: clang-11
os: ubuntu-latest
container: ubuntu:22.04
cxxstd: '17'
build-type: 'Debug'
ldflags: ''
- toolset: clang-11
install: clang-11
os: ubuntu-latest
container: ubuntu:22.04
cxxstd: '20'
build-type: 'Debug'
ldflags: ''
- toolset: clang-13
install: clang-13
os: ubuntu-latest
container: ubuntu:22.04
cxxstd: '17'
build-type: 'Release'
ldflags: ''
- toolset: clang-13
install: clang-13
os: ubuntu-latest
container: ubuntu:22.04
cxxstd: '20'
build-type: 'Release'
ldflags: ''
- toolset: clang-14
install: 'clang-14 libc++-14-dev libc++abi-14-dev'
os: ubuntu-latest
container: ubuntu:22.04
cxxstd: '17'
build-type: 'Debug'
cxxflags: '-stdlib=libc++'
@@ -183,13 +183,36 @@ jobs:
- toolset: clang-14
install: 'clang-14 libc++-14-dev libc++abi-14-dev'
os: ubuntu-latest
container: ubuntu:22.04
cxxstd: '20'
build-type: 'Release'
cxxflags: '-stdlib=libc++'
ldflags: '-lc++'
- toolset: clang-19
install: 'clang-19'
container: ubuntu:24.04
cxxstd: '23'
build-type: 'Debug'
cxxflags: '-fsanitize=address -fsanitize=undefined -fno-sanitize-recover=all'
ldflags: '-fsanitize=address -fsanitize=undefined'
runs-on: ${{ matrix.os }}
- toolset: gcc-14
install: 'g++-14'
container: ubuntu:24.04
cxxstd: '23'
build-type: 'Debug'
cxxflags: '-DBOOST_ASIO_DISABLE_LOCAL_SOCKETS=1' # If a system had no UNIX socket support, we build correctly
- toolset: gcc-14
install: 'g++-14'
container: ubuntu:24.04
cxxstd: '23'
build-type: 'Debug'
cxxflags: '-fsanitize=address -fsanitize=undefined -fno-sanitize-recover=all'
ldflags: '-fsanitize=address -fsanitize=undefined'
runs-on: ubuntu-latest
env:
CXXFLAGS: ${{matrix.cxxflags}} -Wall -Wextra
LDFLAGS: ${{matrix.ldflags}}
@@ -201,7 +224,7 @@ jobs:
- name: Set up the required containers
run: |
docker compose -f tools/docker-compose.yml up -d --wait || (docker compose logs; exit 1)
IMAGE=${{ matrix.container }} docker compose -f tools/docker-compose.yml up -d --wait || (docker compose logs; exit 1)
- name: Install dependencies
run: |
@@ -311,3 +334,24 @@ jobs:
--toolset ${{ matrix.toolset }} \
--cxxstd ${{ matrix.cxxstd }} \
--variant debug,release
# Checks that we don't have any errors in docs
check-docs:
name: Check docs
defaults:
run:
shell: bash
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Boost
run: ./tools/ci.py setup-boost --source-dir=$(pwd)
- name: Build docs
run: |
cd ~/boost-root
./b2 libs/redis/doc
[ -f ~/boost-root/libs/redis/doc/html/index.html ]

1022
README.md

File diff suppressed because it is too large Load Diff

View File

@@ -4,8 +4,9 @@
* accompanying file LICENSE.txt)
*/
#include <iostream>
#include <boost/asio.hpp>
#include <iostream>
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
namespace net = boost::asio;
@@ -26,9 +27,9 @@ auto example(boost::asio::ip::tcp::endpoint ep, std::string msg, int n) -> net::
auto dbuffer = net::dynamic_buffer(buffer);
for (int i = 0; i < n; ++i) {
co_await net::async_write(socket, net::buffer(msg));
auto n = co_await net::async_read_until(socket, dbuffer, "\n");
auto bytes_read = co_await net::async_read_until(socket, dbuffer, "\n");
//std::printf("> %s", buffer.data());
dbuffer.consume(n);
dbuffer.consume(bytes_read);
}
//std::printf("Ok: %s", msg.data());
@@ -62,6 +63,10 @@ int main(int argc, char* argv[])
std::cerr << e.what() << std::endl;
}
}
#else // defined(BOOST_ASIO_HAS_CO_AWAIT)
auto main() -> int {std::cout << "Requires coroutine support." << std::endl; return 1;}
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
#else // defined(BOOST_ASIO_HAS_CO_AWAIT)
auto main() -> int
{
std::cout << "Requires coroutine support." << std::endl;
return 1;
}
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)

View File

@@ -8,9 +8,10 @@
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include <boost/asio.hpp>
#include <cstdio>
#include <iostream>
#include <boost/asio.hpp>
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
namespace net = boost::asio;
@@ -27,37 +28,41 @@ constexpr net::use_awaitable_t<executor_type> use_awaitable;
awaitable_type echo(tcp_socket socket)
{
try {
char data[1024];
for (;;) {
std::size_t n = co_await socket.async_read_some(net::buffer(data), use_awaitable);
co_await async_write(socket, net::buffer(data, n), use_awaitable);
}
} catch (std::exception const&) {
//std::printf("echo Exception: %s\n", e.what());
}
try {
char data[1024];
for (;;) {
std::size_t n = co_await socket.async_read_some(net::buffer(data), use_awaitable);
co_await async_write(socket, net::buffer(data, n), use_awaitable);
}
} catch (std::exception const&) {
//std::printf("echo Exception: %s\n", e.what());
}
}
awaitable_type listener()
{
auto ex = co_await this_coro::executor;
tcp_acceptor acceptor(ex, {tcp::v4(), 55555});
for (;;) {
tcp_socket socket = co_await acceptor.async_accept(use_awaitable);
co_spawn(ex, echo(std::move(socket)), detached);
}
auto ex = co_await this_coro::executor;
tcp_acceptor acceptor(ex, {tcp::v4(), 55555});
for (;;) {
tcp_socket socket = co_await acceptor.async_accept(use_awaitable);
co_spawn(ex, echo(std::move(socket)), detached);
}
}
int main()
{
try {
net::io_context io_context{BOOST_ASIO_CONCURRENCY_HINT_UNSAFE_IO};
co_spawn(io_context, listener(), detached);
io_context.run();
} catch (std::exception const& e) {
std::printf("Exception: %s\n", e.what());
}
try {
net::io_context io_context{BOOST_ASIO_CONCURRENCY_HINT_UNSAFE_IO};
co_spawn(io_context, listener(), detached);
io_context.run();
} catch (std::exception const& e) {
std::printf("Exception: %s\n", e.what());
}
}
#else // defined(BOOST_ASIO_HAS_CO_AWAIT)
auto main() -> int {std::cout << "Requires coroutine support." << std::endl; return 1;}
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
#else // defined(BOOST_ASIO_HAS_CO_AWAIT)
auto main() -> int
{
std::cout << "Requires coroutine support." << std::endl;
return 1;
}
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)

26
doc/CMakeLists.txt Normal file
View File

@@ -0,0 +1,26 @@
#
# Copyright (c) 2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
#
# Distributed under the Boost Software License, Version 1.0. (See accompanying
# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#
# Root CMakeLists.txt so MrDocs knows how to build our code
cmake_minimum_required(VERSION 3.8...3.22)
# Project
project(boost_redis_mrdocs LANGUAGES CXX)
# MrDocs forces CMAKE_EXPORT_COMPILE_COMMANDS=ON, incorrectly
# causing all targets to be dumped to the compilation database.
# Disable this setting so we can set EXPORT_COMPILE_COMMANDS
# only to our target of interest
set(CMAKE_EXPORT_COMPILE_COMMANDS OFF)
# Add Boost
add_subdirectory($ENV{BOOST_SRC_DIR} deps/boost)
# Add the target for mrdocs to analyze
add_executable(mrdocs mrdocs.cpp)
target_link_libraries(mrdocs PRIVATE Boost::redis)
set_target_properties(mrdocs PROPERTIES EXPORT_COMPILE_COMMANDS ON)

View File

@@ -1,226 +0,0 @@
<doxygenlayout version="1.0">
<!-- Generated by doxygen 1.9.1 -->
<!-- Navigation index tabs for HTML output -->
<navindex>
<tab type="mainpage" visible="yes" title="Contents"/>
<tab type="pages" visible="yes" title="" intro=""/>
<tab type="modules" visible="no" title="Reference" intro=""/>
<tab type="namespaces" visible="no" title="">
<tab type="namespacelist" visible="yes" title="" intro=""/>
<tab type="namespacemembers" visible="yes" title="" intro=""/>
</tab>
<tab type="interfaces" visible="no" title="">
<tab type="interfacelist" visible="yes" title="" intro=""/>
<tab type="interfaceindex" visible="$ALPHABETICAL_INDEX" title=""/>
<tab type="interfacehierarchy" visible="yes" title="" intro=""/>
</tab>
<tab type="classes" visible="no" title="">
<tab type="classlist" visible="no" title="" intro=""/>
<tab type="classindex" visible="$ALPHABETICAL_INDEX" title=""/>
<tab type="hierarchy" visible="no" title="" intro=""/>
<tab type="classmembers" visible="no" title="" intro=""/>
</tab>
<tab type="structs" visible="no" title="">
<tab type="structlist" visible="no" title="" intro=""/>
<tab type="structindex" visible="$ALPHABETICAL_INDEX" title=""/>
</tab>
<tab type="exceptions" visible="no" title="">
<tab type="exceptionlist" visible="no" title="" intro=""/>
<tab type="exceptionindex" visible="$ALPHABETICAL_INDEX" title=""/>
<tab type="exceptionhierarchy" visible="yes" title="" intro=""/>
</tab>
<tab type="files" visible="no" title="">
<tab type="filelist" visible="no" title="" intro=""/>
<tab type="globals" visible="no" title="" intro=""/>
</tab>
<tab type="examples" visible="yes" title="" intro=""/>
</navindex>
<!-- Layout definition for a class page -->
<class>
<briefdescription visible="yes"/>
<includes visible="$SHOW_INCLUDE_FILES"/>
<inheritancegraph visible="$CLASS_GRAPH"/>
<collaborationgraph visible="$COLLABORATION_GRAPH"/>
<memberdecl>
<nestedclasses visible="yes" title=""/>
<publictypes title=""/>
<services title=""/>
<interfaces title=""/>
<publicslots title=""/>
<signals title=""/>
<publicmethods title=""/>
<publicstaticmethods title=""/>
<publicattributes title=""/>
<publicstaticattributes title=""/>
<protectedtypes title=""/>
<protectedslots title=""/>
<protectedmethods title=""/>
<protectedstaticmethods title=""/>
<protectedattributes title=""/>
<protectedstaticattributes title=""/>
<packagetypes title=""/>
<packagemethods title=""/>
<packagestaticmethods title=""/>
<packageattributes title=""/>
<packagestaticattributes title=""/>
<properties title=""/>
<events title=""/>
<privatetypes title=""/>
<privateslots title=""/>
<privatemethods title=""/>
<privatestaticmethods title=""/>
<privateattributes title=""/>
<privatestaticattributes title=""/>
<friends title=""/>
<related title="" subtitle=""/>
<membergroups visible="yes"/>
</memberdecl>
<detaileddescription title=""/>
<memberdef>
<inlineclasses title=""/>
<typedefs title=""/>
<enums title=""/>
<services title=""/>
<interfaces title=""/>
<constructors title=""/>
<functions title=""/>
<related title=""/>
<variables title=""/>
<properties title=""/>
<events title=""/>
</memberdef>
<allmemberslink visible="yes"/>
<usedfiles visible="$SHOW_USED_FILES"/>
<authorsection visible="yes"/>
</class>
<!-- Layout definition for a namespace page -->
<namespace>
<briefdescription visible="yes"/>
<memberdecl>
<nestednamespaces visible="yes" title=""/>
<constantgroups visible="yes" title=""/>
<interfaces visible="yes" title=""/>
<classes visible="yes" title=""/>
<structs visible="yes" title=""/>
<exceptions visible="yes" title=""/>
<typedefs title=""/>
<sequences title=""/>
<dictionaries title=""/>
<enums title=""/>
<functions title=""/>
<variables title=""/>
<membergroups visible="yes"/>
</memberdecl>
<detaileddescription title=""/>
<memberdef>
<inlineclasses title=""/>
<typedefs title=""/>
<sequences title=""/>
<dictionaries title=""/>
<enums title=""/>
<functions title=""/>
<variables title=""/>
</memberdef>
<authorsection visible="yes"/>
</namespace>
<!-- Layout definition for a file page -->
<file>
<briefdescription visible="yes"/>
<includes visible="$SHOW_INCLUDE_FILES"/>
<includegraph visible="$INCLUDE_GRAPH"/>
<includedbygraph visible="$INCLUDED_BY_GRAPH"/>
<sourcelink visible="yes"/>
<memberdecl>
<interfaces visible="yes" title=""/>
<classes visible="yes" title=""/>
<structs visible="yes" title=""/>
<exceptions visible="yes" title=""/>
<namespaces visible="yes" title=""/>
<constantgroups visible="yes" title=""/>
<defines title=""/>
<typedefs title=""/>
<sequences title=""/>
<dictionaries title=""/>
<enums title=""/>
<functions title=""/>
<variables title=""/>
<membergroups visible="yes"/>
</memberdecl>
<detaileddescription title=""/>
<memberdef>
<inlineclasses title=""/>
<defines title=""/>
<typedefs title=""/>
<sequences title=""/>
<dictionaries title=""/>
<enums title=""/>
<functions title=""/>
<variables title=""/>
</memberdef>
<authorsection/>
</file>
<!-- Layout definition for a group page -->
<group>
<briefdescription visible="yes"/>
<groupgraph visible="$GROUP_GRAPHS"/>
<memberdecl>
<nestedgroups visible="yes" title=""/>
<dirs visible="yes" title=""/>
<files visible="yes" title=""/>
<namespaces visible="yes" title=""/>
<classes visible="yes" title=""/>
<defines title=""/>
<typedefs title=""/>
<sequences title=""/>
<dictionaries title=""/>
<enums title=""/>
<enumvalues title=""/>
<functions title=""/>
<variables title=""/>
<signals title=""/>
<publicslots title=""/>
<protectedslots title=""/>
<privateslots title=""/>
<events title=""/>
<properties title=""/>
<friends title=""/>
<membergroups visible="yes"/>
</memberdecl>
<detaileddescription title=""/>
<memberdef>
<pagedocs/>
<inlineclasses title=""/>
<defines title=""/>
<typedefs title=""/>
<sequences title=""/>
<dictionaries title=""/>
<enums title=""/>
<enumvalues title=""/>
<functions title=""/>
<variables title=""/>
<signals title=""/>
<publicslots title=""/>
<protectedslots title=""/>
<privateslots title=""/>
<events title=""/>
<properties title=""/>
<friends title=""/>
</memberdef>
<authorsection visible="yes"/>
</group>
<!-- Layout definition for a directory page -->
<directory>
<briefdescription visible="yes"/>
<directorygraph visible="yes"/>
<memberdecl>
<dirs visible="yes"/>
<files visible="yes"/>
</memberdecl>
<detaileddescription title=""/>
</directory>
</doxygenlayout>

View File

@@ -1,85 +1,17 @@
project redis/doc ;
#
# Copyright (c) 2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
#
# Distributed under the Boost Software License, Version 1.0. (See accompanying
# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#
import doxygen ;
import path ;
import sequence ;
# Adapted from Boost.Unordered
make html/index.html : build_antora.sh : @run-script ;
# 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
# Runs the Antora script
actions run-script
{
rm -rf $(target_dir)
mkdir -p $(target_dir)
cp -r $(<:D)/html/doc/* $(target_dir)/
echo "Stamped" > "$(<)"
bash -x $(>)
}
# These are used to inform the build system of the
@@ -88,5 +20,5 @@ actions copy_html_dir
alias boostdoc ;
explicit boostdoc ;
alias boostrelease : copyhtml.tag ;
alias boostrelease : html/index.html ;
explicit boostrelease ;

15
doc/antora.yml Normal file
View File

@@ -0,0 +1,15 @@
#
# Copyright (c) 2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
#
# Distributed under the Boost Software License, Version 1.0. (See accompanying
# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#
name: redis
title: Boost.Redis
version: ~
nav:
- modules/ROOT/nav.adoc
ext:
cpp-reference:
config: doc/mrdocs.yml

19
doc/build_antora.sh Executable file
View File

@@ -0,0 +1,19 @@
#!/bin/bash
#
# Copyright (c) 2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
#
# Distributed under the Boost Software License, Version 1.0. (See accompanying
# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#
set -e
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
cd "$SCRIPT_DIR"
# Required by our CMake.
# Prevents Antora from cloning Boost again
export BOOST_SRC_DIR=$(realpath $SCRIPT_DIR/../../..)
npm ci
npx antora --log-format=pretty redis-playbook.yml

View File

@@ -1,145 +0,0 @@
/**
Doxygen Awesome
https://github.com/jothepro/doxygen-awesome-css
MIT License
Copyright (c) 2021 jothepro
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
html {
/* side nav width. MUST be = `TREEVIEW_WIDTH`.
* Make sure it is wide enough to contain the page title (logo + title + version)
*/
--side-nav-fixed-width: 335px;
}
#projectname {
white-space: nowrap;
}
#page-wrapper {
height: calc(100vh - 100px);
display: flex;
flex-direction: column;
}
#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);
}
#sidebar-wrapper {
display: flex;
flex-direction: column;
min-width: var(--side-nav-fixed-width);
max-width: var(--side-nav-fixed-width);
background-color: var(--side-nav-background);
border-right: 1px solid rgb(222, 222, 222);
}
#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 {
height: auto !important;
}
#nav-sync {
display: none;
}
#top {
display: block;
border-bottom: none;
max-width: var(--side-nav-fixed-width);
background: var(--side-nav-background);
}
.ui-resizable-handle {
cursor: default;
width: 1px !important;
}
#MSearchResultsWindow {
left: var(--spacing-medium) !important;
right: auto;
}
}
@media (max-width: 768px) {
#sidebar-wrapper {
display: none;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,19 +0,0 @@
<!-- 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>

View File

@@ -1,61 +0,0 @@
<!-- 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 -->

10
doc/modules/ROOT/nav.adoc Normal file
View File

@@ -0,0 +1,10 @@
* xref:index.adoc[Introduction]
* xref:requests_responses.adoc[]
* xref:serialization.adoc[]
* xref:logging.adoc[]
* xref:benchmarks.adoc[]
* xref:comparison.adoc[]
* xref:examples.adoc[]
* xref:reference.adoc[Reference]
* xref:acknowledgements.adoc[]
* xref:changelog.adoc[]

View File

@@ -0,0 +1,37 @@
//
// Copyright (c) 2025 Marcelo Zimbres Silva (mzimbres@gmail.com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
= Acknowledgements
Acknowledgement to people that helped shape Boost.Redis
* Richard Hodges (https://github.com/madmongo1[madmongo1]): For very helpful support with Asio, the design of asynchronous programs, etc.
* Vinícius dos Santos Oliveira (https://github.com/vinipsmaker[vinipsmaker]): For useful discussion about how Boost.Redis consumes buffers in the read operation.
* Petr Dannhofer (https://github.com/Eddie-cz[Eddie-cz]): For helping me understand how the `AUTH` and `HELLO` command can influence each other.
* Mohammad Nejati (https://github.com/ashtum[ashtum]): For pointing out scenarios where calls to `async_exec` should fail when the connection is lost.
* Klemens Morgenstern (https://github.com/klemens-morgenstern[klemens-morgenstern]): For useful discussion about timeouts, cancellation, synchronous interfaces and general help with Asio.
* Vinnie Falco (https://github.com/vinniefalco[vinniefalco]): For general suggestions about how to improve the code and the documentation.
* Bram Veldhoen (https://github.com/bveldhoen[bveldhoen]): For contributing a Redis-streams example.
Also many thanks to all individuals that participated in the Boost
review
* Zach Laine: https://lists.boost.org/Archives/boost/2023/01/253883.php
* Vinnie Falco: https://lists.boost.org/Archives/boost/2023/01/253886.php
* Christian Mazakas: https://lists.boost.org/Archives/boost/2023/01/253900.php
* Ruben Perez: https://lists.boost.org/Archives/boost/2023/01/253915.php
* Dmitry Arkhipov: https://lists.boost.org/Archives/boost/2023/01/253925.php
* Alan de Freitas: https://lists.boost.org/Archives/boost/2023/01/253927.php
* Mohammad Nejati: https://lists.boost.org/Archives/boost/2023/01/253929.php
* Sam Hartsfield: https://lists.boost.org/Archives/boost/2023/01/253931.php
* Miguel Portilla: https://lists.boost.org/Archives/boost/2023/01/253935.php
* Robert A.H. Leahy: https://lists.boost.org/Archives/boost/2023/01/253928.php
The Reviews can be found at:
https://lists.boost.org/Archives/boost/2023/01/date.php. The thread
with the ACCEPT from the review manager can be found here:
https://lists.boost.org/Archives/boost/2023/01/253944.php.

View File

@@ -0,0 +1,95 @@
//
// Copyright (c) 2025 Marcelo Zimbres Silva (mzimbres@gmail.com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
= Echo server benchmark
This document benchmarks the performance of TCP echo servers I
implemented in different languages using different Redis clients. The
main motivations for choosing an echo server are
* Simple to implement and does not require expertise level in most languages.
* I/O bound: Echo servers have very low CPU consumption in general
and therefore are excellent to measure how a program handles concurrent requests.
* It simulates very well a typical backend in regard to concurrency.
I also imposed some constraints on the implementations
* It should be simple enough and not require writing too much code.
* Favor the use standard idioms and avoid optimizations that require expert level.
* Avoid the use of complex things like connection and thread pool.
To reproduce these results run one of the echo-server programs in one
terminal and the
https://github.com/boostorg/redis/blob/42880e788bec6020dd018194075a211ad9f339e8/benchmarks/cpp/asio/echo_server_client.cpp[echo-server-client] in another.
== Without Redis
First I tested a pure TCP echo server, i.e. one that sends the messages
directly to the client without interacting with Redis. The result can
be seen below
image::https://boostorg.github.io/redis/tcp-echo-direct.png[]
The tests were performed with a 1000 concurrent TCP connections on the
localhost where latency is 0.07ms on average on my machine. On higher
latency networks the difference among libraries is expected to
decrease.
* I expected Libuv to have similar performance to Asio and Tokio.
* I did expect nodejs to come a little behind given it is is
javascript code. Otherwise I did expect it to have similar
performance to libuv since it is the framework behind it.
* Go did surprise me: faster than nodejs and libuv!
The code used in the benchmarks can be found at
* https://github.com/boostorg/redis/blob/3fb018ccc6138d310ac8b73540391cdd8f2fdad6/benchmarks/cpp/asio/echo_server_direct.cpp[Asio]: A variation of https://github.com/chriskohlhoff/asio/blob/4915cfd8a1653c157a1480162ae5601318553eb8/asio/src/examples/cpp20/coroutines/echo_server.cpp[this Asio example].
* https://github.com/boostorg/redis/tree/835a1decf477b09317f391eddd0727213cdbe12b/benchmarks/c/libuv[Libuv]: Taken from https://github.com/libuv/libuv/blob/06948c6ee502862524f233af4e2c3e4ca876f5f6/docs/code/tcp-echo-server/main.c[this Libuv example].
* https://github.com/boostorg/redis/tree/3fb018ccc6138d310ac8b73540391cdd8f2fdad6/benchmarks/rust/echo_server_direct[Tokio]: Taken from https://docs.rs/tokio/latest/tokio/[here].
* https://github.com/boostorg/redis/tree/3fb018ccc6138d310ac8b73540391cdd8f2fdad6/benchmarks/nodejs/echo_server_direct[Nodejs]
* https://github.com/boostorg/redis/blob/3fb018ccc6138d310ac8b73540391cdd8f2fdad6/benchmarks/go/echo_server_direct.go[Go]
== With Redis
This is similar to the echo server described above but messages are
echoed by Redis and not by the echo-server itself, which acts
as a proxy between the client and the Redis server. The results
can be seen below
image::https://boostorg.github.io/redis/tcp-echo-over-redis.png[]
The tests were performed on a network where latency is 35ms on
average, otherwise it uses the same number of TCP connections
as the previous example.
As the reader can see, the Libuv and the Rust test are not depicted
in the graph, the reasons are
* https://github.com/redis-rs/redis-rs[redis-rs]: This client
comes so far behind that it can't even be represented together
with the other benchmarks without making them look insignificant.
I don't know for sure why it is so slow, I suppose it has
something to do with its lack of automatic
https://redis.io/docs/manual/pipelining/[pipelining] support.
In fact, the more TCP connections I launch the worse its
performance gets.
* Libuv: I left it out because it would require me writing to much
c code. More specifically, I would have to use hiredis and
implement support for pipelines manually.
The code used in the benchmarks can be found at
* https://github.com/boostorg/redis[Boost.Redis]: https://github.com/boostorg/redis/blob/3fb018ccc6138d310ac8b73540391cdd8f2fdad6/examples/echo_server.cpp[code]
* https://github.com/redis/node-redis[node-redis]: https://github.com/boostorg/redis/tree/3fb018ccc6138d310ac8b73540391cdd8f2fdad6/benchmarks/nodejs/echo_server_over_redis[code]
* https://github.com/go-redis/redis[go-redis]: https://github.com/boostorg/redis/blob/3fb018ccc6138d310ac8b73540391cdd8f2fdad6/benchmarks/go/echo_server_over_redis.go[code]
== Conclusion
Redis clients have to support automatic pipelining to have competitive performance. For updates to this document follow https://github.com/boostorg/redis[].

View File

@@ -0,0 +1,397 @@
//
// Copyright (c) 2025 Marcelo Zimbres Silva (mzimbres@gmail.com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
= Changelog
== Boost 1.88
* (Issue https://github.com/boostorg/redis/issues/233[233])
To deal with keys that might not exits in the Redis server, the
library supports `std::optional`, for example
`response<std::optional<std::vector<std::string>>>`. In some cases
however, such as the https://redis.io/docs/latest/commands/mget/[MGET] command,
each element in the vector might be non exiting, now it is possible
to specify a response as `response<std::optional<std::vector<std::optional<std::string>>>>`.
* (Issue https://github.com/boostorg/redis/issues/225[225])
Use `deferred` as the connection default completion token.
* (Issue https://github.com/boostorg/redis/issues/128[128])
Adds a new `async_exec` overload that allows passing response
adapters. This makes it possible to receive Redis responses directly
in custom data structures thereby avoiding unnecessary data copying.
Thanks to Ruben Perez (@anarthal) for implementing this feature.
* There are also other multiple small improvements in this release,
users can refer to the git history for more details.
== Boost 1.87
* (Issue https://github.com/boostorg/redis/issues/205[205])
Improves reaction time to disconnection by using `wait_for_one_error`
instead of `wait_for_all`. The function `connection::async_run` was
also changed to return EOF to the user when that error is received
from the server. That is a breaking change.
* (Issue https://github.com/boostorg/redis/issues/210[210])
Fixes the adapter of empty nested responses.
* (Issues https://github.com/boostorg/redis/issues/211[211] and https://github.com/boostorg/redis/issues/212[212])
Fixes the reconnect loop that would hang under certain conditions,
see the linked issues for more details.
* (Issue https://github.com/boostorg/redis/issues/219[219])
Changes the default log level from `disabled` to `debug`.
== Boost 1.85
* (Issue https://github.com/boostorg/redis/issues/170[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 unsolicited or
not.
* (Issue https://github.com/boostorg/redis/issues/168[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 https://github.com/boostorg/redis/issues/181[181]).
See a detailed description of this bug in
https://github.com/boostorg/redis/issues/181#issuecomment-1913346983[this comment].
* (Issue https://github.com/boostorg/redis/issues/182[182]).
Sets `"default"` as the default value of `config::username`. This
makes it simpler to use the `requirepass` configuration in Redis.
* (Issue https://github.com/boostorg/redis/issues/189[189]).
Fixes narrowing conversion 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
automatic reconnection.
* Massive performance improvement. One of my tests went from
140k req/s to 390k/s. This was possible after a parser
simplification that reduced the number of reschedules and buffer
rotations.
* Adds Redis stream example.
* Renames the project to Boost.Redis and moves the code into namespace
`boost::redis`.
* As pointed out in the reviews the `to_bulk` and `from_bulk` names were too
generic for ADL customization points. They gained the prefix `boost_redis_`.
* Moves `boost::redis::resp3::request` to `boost::redis::request`.
* Adds new typedef `boost::redis::response` that should be used instead of
`std::tuple`.
* Adds new typedef `boost::redis::generic_response` that should be used instead
of `std::vector<resp3::node<std::string>>`.
* Renames `redis::ignore` to `redis::ignore_t`.
* Changes `async_exec` to receive a `redis::response` instead of an adapter,
namely, instead of passing `adapt(resp)` users should pass `resp` directly.
* Introduces `boost::redis::adapter::result` to store responses to commands
including possible resp3 errors without losing the error diagnostic part. To
access values now use `std::get<N>(resp).value()` instead of
`std::get<N>(resp)`.
* Implements full-duplex communication. Before these changes the connection
would wait for a response to arrive before sending the next one. Now requests
are continuously coalesced and written to the socket. `request::coalesce`
became unnecessary and was removed. I could measure significative performance
gains with these changes.
* Improves serialization examples using Boost.Describe to serialize to JSON and protobuf. See
cpp20_json.cpp and cpp20_protobuf.cpp for more details.
* Upgrades to Boost 1.81.0.
* Fixes build with pass:[libc++].
* Adds high-level functionality to the connection classes. For
example, `boost::redis::connection::async_run` will automatically
resolve, connect, reconnect and perform health checks.
== v1.4.0-1
* Renames `retry_on_connection_lost` to `cancel_if_unresponded`. (v1.4.1)
* Removes dependency on Boost.Hana, `boost::string_view`, Boost.Variant2 and Boost.Spirit.
* Fixes build and setup CI on windows.
== v1.3.0-1
* Upgrades to Boost 1.80.0
* Removes automatic sending of the `HELLO` command. This can't be
implemented properly without bloating the connection class. It is
now a user responsibility to send HELLO. Requests that contain it have
priority over other requests and will be moved to the front of the
queue, see `aedis::request::config`
* Automatic name resolving and connecting have been removed from
`aedis::connection::async_run`. Users have to do this step manually
now. The reason for this change is that having them built-in doesn't
offer enough flexibility that is need for boost users.
* Removes healthy checks and idle timeout. This functionality must now
be implemented by users, see the examples. This is
part of making Aedis useful to a larger audience and suitable for
the Boost review process.
* The `aedis::connection` is now using a typeddef to a
`net::ip::tcp::socket` and `aedis::ssl::connection` to
`net::ssl::stream<net::ip::tcp::socket>`. Users that need to use
other stream type must now specialize `aedis::basic_connection`.
* Adds a low level example of async code.
== v1.2.0
* `aedis::adapt` supports now tuples created with `std::tie`.
`aedis::ignore` is now an alias to the type of `std::ignore`.
* Provides allocator support for the internal queue used in the
`aedis::connection` class.
* Changes the behaviour of `async_run` to complete with success if
asio::error::eof is received. This makes it easier to write
composed operations with awaitable operators.
* Adds allocator support in the `aedis::request` (a
contribution from Klemens Morgenstern).
* Renames `aedis::request::push_range2` to `push_range`. The
suffix 2 was used for disambiguation. Klemens fixed it with SFINAE.
* Renames `fail_on_connection_lost` to
`aedis::request::config::cancel_on_connection_lost`. Now, it will
only cause connections to be canceled when `async_run` completes.
* Introduces `aedis::request::config::cancel_if_not_connected` which will
cause a request to be canceled if `async_exec` is called before a
connection has been established.
* Introduces new request flag `aedis::request::config::retry` that if
set to true will cause the request to not be canceled when it was
sent to Redis but remained unresponded after `async_run` completed.
It provides a way to avoid executing commands twice.
* Removes the `aedis::connection::async_run` overload that takes
request and adapter as parameters.
* Changes the way `aedis::adapt()` behaves with
`std::vector<aedis::resp3::node<T>>`. Receiving RESP3 simple errors,
blob errors or null won't causes an error but will be treated as
normal response. It is the user responsibility to check the content
in the vector.
* Fixes a bug in `connection::cancel(operation::exec)`. Now this
call will only cancel non-written requests.
* Implements per-operation implicit cancellation support for
`aedis::connection::async_exec`. The following call will `co_await (conn.async_exec(...) || timer.async_wait(...))`
will cancel the request as long as it has not been written.
* Changes `aedis::connection::async_run` completion signature to
`f(error_code)`. This is how is was in the past, the second
parameter was not helpful.
* Renames `operation::receive_push` to `aedis::operation::receive`.
== v1.1.0-1
* Removes `coalesce_requests` from the `aedis::connection::config`, it
became a request property now, see `aedis::request::config::coalesce`.
* Removes `max_read_size` from the `aedis::connection::config`. The maximum
read size can be specified now as a parameter of the
`aedis::adapt()` function.
* Removes `aedis::sync` class, see intro_sync.cpp for how to perform
synchronous and thread safe calls. This is possible in Boost. 1.80
only as it requires `boost::asio::deferred`.
* Moves from `boost::optional` to `std::optional`. This is part of
moving to pass:[C++17].
* Changes the behaviour of the second `aedis::connection::async_run` overload
so that it always returns an error when the connection is lost.
* Adds TLS support, see intro_tls.cpp.
* Adds an example that shows how to resolve addresses over sentinels,
see subscriber_sentinel.cpp.
* Adds a `aedis::connection::timeouts::resp3_handshake_timeout`. This is
timeout used to send the `HELLO` command.
* Adds `aedis::endpoint` where in addition to host and port, users can
optionally provide username, password and the expected server role
(see `aedis::error::unexpected_server_role`).
* `aedis::connection::async_run` checks whether the server role received in
the hello command is equal to the expected server role specified in
`aedis::endpoint`. To skip this check let the role variable empty.
* Removes reconnect functionality from `aedis::connection`. It
is possible in simple reconnection strategies but bloats the class
in more complex scenarios, for example, with sentinel,
authentication and TLS. This is trivial to implement in a separate
coroutine. As a result the `enum event` and `async_receive_event`
have been removed from the class too.
* Fixes a bug in `connection::async_receive_push` that prevented
passing any response adapter other that `adapt(std::vector<node>)`.
* Changes the behaviour of `aedis::adapt()` that caused RESP3 errors
to be ignored. One consequence of it is that `connection::async_run`
would not exit with failure in servers that required authentication.
* Changes the behaviour of `connection::async_run` that would cause it
to complete with success when an error in the
`connection::async_exec` occurred.
* Ports the buildsystem from autotools to CMake.
== v1.0.0
* Adds experimental cmake support for windows users.
* Adds new class `aedis::sync` that wraps an `aedis::connection` in
a thread-safe and synchronous API. All free functions from the
`sync.hpp` are now member functions of `aedis::sync`.
* Split `aedis::connection::async_receive_event` in two functions, one
to receive events and another for server side pushes, see
`aedis::connection::async_receive_push`.
* Removes collision between `aedis::adapter::adapt` and
`aedis::adapt`.
* Adds `connection::operation` enum to replace `cancel_*` member
functions with a single cancel function that gets the operations
that should be cancelled as argument.
* Bugfix: a bug on reconnect from a state where the `connection` object
had unsent commands. It could cause `async_exec` to never
complete under certain conditions.
* Bugfix: Documentation of `adapt()` functions were missing from
Doxygen.
== v0.3.0
* Adds `experimental::exec` and `receive_event` functions to offer a
thread safe and synchronous way of executing requests across
threads. See `intro_sync.cpp` and `subscriber_sync.cpp` for
examples.
* `connection::async_read_push` was renamed to `async_receive_event`.
* `connection::async_receive_event` is now being used to communicate
internal events to the user, such as resolve, connect, push etc. For
examples see cpp20_subscriber.cpp and `connection::event`.
* The `aedis` directory has been moved to `include` to look more
similar to Boost libraries. Users should now replace `-I/aedis-path`
with `-I/aedis-path/include` in the compiler flags.
* The `AUTH` and `HELLO` commands are now sent automatically. This change was
necessary to implement reconnection. The username and password
used in `AUTH` should be provided by the user on
`connection::config`.
* Adds support for reconnection. See `connection::enable_reconnect`.
* Fixes a bug in the `connection::async_run(host, port)` overload
that was causing crashes on reconnection.
* Fixes the executor usage in the connection class. Before these
changes it was imposing `any_io_executor` on users.
* `connection::async_receiver_event` is not cancelled anymore when
`connection::async_run` exits. This change makes user code simpler.
* `connection::async_exec` with host and port overload has been
removed. Use the other `connection::async_run` overload.
* The host and port parameters from `connection::async_run` have been
move to `connection::config` to better support authentication and
failover.
* Many simplifications in the `chat_room` example.
* Fixes build in clang the compilers and makes some improvements in
the documentation.
== v0.2.0-1
* Fixes a bug that happens on very high load. (v0.2.1)
* Major rewrite of the high-level API. There is no more need to use the low-level API anymore.
* No more callbacks: Sending requests follows the ASIO asynchronous model.
* Support for reconnection: Pending requests are not canceled when a connection is lost and are re-sent when a new one is established.
* The library is not sending HELLO-3 on user behalf anymore. This is important to support AUTH properly.
== v0.1.0-2
* Adds reconnect coroutine in the `echo_server` example. (v0.1.2)
* Corrects `client::async_wait_for_data` with `make_parallel_group` to launch operation. (v0.1.2)
* Improvements in the documentation. (v0.1.2)
* Avoids dynamic memory allocation in the client class after reconnection. (v0.1.2)
* Improves the documentation and adds some features to the high-level client. (v.0.1.1)
* Improvements in the design and documentation.
== v0.0.1
* First release to collect design feedback.

View File

@@ -0,0 +1,121 @@
//
// Copyright (c) 2025 Marcelo Zimbres Silva (mzimbres@gmail.com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
= Comparison with other Redis clients
## Comparison
The main reason for why I started writing Boost.Redis was to have a client
compatible with the Asio asynchronous model. As I made progresses I could
also address what I considered weaknesses in other libraries. Due to
time constraints I won't be able to give a detailed comparison with
each client listed in the
https://redis.io/docs/clients/#cpp[official list].
Instead, I will focus on the most popular pass:[C++] client on github in number of stars, namely:
https://github.com/sewenew/redis-plus-plus[]
### Boost.Redis vs Redis-plus-plus
Before we start it is important to mention some of the things
redis-plus-plus does not support
* The latest version of the communication protocol RESP3. Without that it is impossible to support some important Redis features like client side caching, among other things.
* Coroutines.
* Reading responses directly in user data structures to avoid creating temporaries.
* Error handling with support for error-code.
* Cancellation.
The remaining points will be addressed individually. Let us first
have a look at what sending a command a pipeline and a transaction
look like
[source,cpp]
----
auto redis = Redis("tcp://127.0.0.1:6379");
// Send commands
redis.set("key", "val");
auto val = redis.get("key"); // val is of type OptionalString.
if (val)
std::cout << *val << std::endl;
// Sending pipelines
auto pipe = redis.pipeline();
auto pipe_replies = pipe.set("key", "value")
.get("key")
.rename("key", "new-key")
.rpush("list", {"a", "b", "c"})
.lrange("list", 0, -1)
.exec();
// Parse reply with reply type and index.
auto set_cmd_result = pipe_replies.get<bool>(0);
// ...
// Sending a transaction
auto tx = redis.transaction();
auto tx_replies = tx.incr("num0")
.incr("num1")
.mget({"num0", "num1"})
.exec();
auto incr_result0 = tx_replies.get<long long>(0);
// ...
----
Some of the problems with this API are
* Heterogeneous treatment of commands, pipelines and transaction. This makes auto-pipelining impossible.
* Any Api that sends individual commands has a very restricted scope of usability and should be avoided for performance reasons.
* The API imposes exceptions on users, no error-code overload is provided.
* No way to reuse the buffer for new calls to e.g. redis.get in order to avoid further dynamic memory allocations.
* Error handling of resolve and connection not clear.
According to the documentation, pipelines in redis-plus-plus have
the following characteristics
> +NOTE+: By default, creating a Pipeline object is NOT cheap, since
> it creates a new connection.
This is clearly a downside in the API as pipelines should be the
default way of communicating and not an exception, paying such a
high price for each pipeline imposes a severe cost in performance.
Transactions also suffer from the very same problem:
> +NOTE+: Creating a Transaction object is NOT cheap, since it
> creates a new connection.
In Boost.Redis there is no difference between sending one command, a
pipeline or a transaction because requests are decoupled
from the I/O objects.
> redis-plus-plus also supports async interface, however, async
> support for Transaction and Subscriber is still on the way.
>
> The async interface depends on third-party event library, and so
> far, only libuv is supported.
Async code in redis-plus-plus looks like the following
[source,cpp]
----
auto async_redis = AsyncRedis(opts, pool_opts);
Future<string> ping_res = async_redis.ping();
cout << ping_res.get() << endl;
----
As the reader can see, the async interface is based on futures
which is also known to have a bad performance. The biggest
problem however with this async design is that it makes it
impossible to write asynchronous programs correctly since it
starts an async operation on every command sent instead of
enqueueing a message and triggering a write when it can be sent.
It is also not clear how are pipelines realised with this design
(if at all).

View File

@@ -0,0 +1,27 @@
//
// Copyright (c) 2025 Marcelo Zimbres Silva (mzimbres@gmail.com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
= Examples
The examples below show how to use the features discussed throughout this documentation:
* {site-url}/example/cpp20_intro.cpp[cpp20_intro.cpp]: Does not use awaitable operators.
* {site-url}/example/cpp20_intro_tls.cpp[cpp20_intro_tls.cpp]: Communicates over TLS.
* {site-url}/example/cpp20_unix_sockets.cpp[cpp20_unix_sockets.cpp]: Communicates over UNIX domain sockets.
* {site-url}/example/cpp20_containers.cpp[cpp20_containers.cpp]: Shows how to send and receive STL containers and how to use transactions.
* {site-url}/example/cpp20_json.cpp[cpp20_json.cpp]: Shows how to serialize types using Boost.Json.
* {site-url}/example/cpp20_protobuf.cpp[cpp20_protobuf.cpp]: Shows how to serialize types using protobuf.
* {site-url}/example/cpp20_resolve_with_sentinel.cpp[cpp20_resolve_with_sentinel.cpp]: Shows how to resolve a master address using sentinels.
* {site-url}/example/cpp20_subscriber.cpp[cpp20_subscriber.cpp]: Shows how to implement pubsub with reconnection re-subscription.
* {site-url}/example/cpp20_echo_server.cpp[cpp20_echo_server.cpp]: A simple TCP echo server.
* {site-url}/example/cpp20_chat_room.cpp[cpp20_chat_room.cpp]: A command line chat built on Redis pubsub.
* {site-url}/example/cpp17_intro.cpp[cpp17_intro.cpp]: Uses callbacks and requires pass:[C++17].
* {site-url}/example/cpp17_intro_sync.cpp[cpp17_intro_sync.cpp]: Runs `async_run` in a separate thread and performs synchronous calls to `async_exec`.
* {site-url}/example/cpp17_spdlog.cpp[cpp17_spdlog.cpp]: Shows how to use third-party logging libraries like `spdlog` with Boost.Redis.
The main function used in some async examples has been factored out in
the {site-url}/example/main.cpp[main.cpp] file.

View File

@@ -0,0 +1,143 @@
//
// Copyright (c) 2025 Marcelo Zimbres Silva (mzimbres@gmail.com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
[#intro]
= Introduction
Boost.Redis is a high-level https://redis.io/[Redis] client library built on top of
https://www.boost.org/doc/libs/latest/doc/html/boost_asio.html[Boost.Asio]
that implements the Redis protocol
https://github.com/redis/redis-specifications/blob/master/protocol/RESP3.md[RESP3].
== Requirements
The requirements for using Boost.Redis are:
* Boost 1.84 or higher. Boost.Redis is included in Boost installations since Boost 1.84.
* pass:[C++17] or higher. Supported compilers include gcc 11 and later, clang 11 and later, and Visual Studio 16 (2019) and later.
* Redis 6 or higher (must support RESP3).
* OpenSSL.
The documentation assumes basic-level knowledge about https://redis.io/docs/[Redis] and https://www.boost.org/doc/libs/latest/doc/html/boost_asio.html[Boost.Asio].
== Building the library
To use the library it is necessary to include the following:
[source,cpp]
----
#include <boost/redis/src.hpp>
----
in exactly one source file in your applications. Otherwise, the library is header-only.
Boost.Redis unconditionally requires OpenSSL. Targets using Boost.Redis need to link
to the OpenSSL libraries.
== Tutorial
The code below uses a short-lived connection to
https://redis.io/commands/ping/[ping] a Redis server:
[source,cpp]
----
#include <boost/redis/connection.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/consign.hpp>
#include <boost/asio/detached.hpp>
#include <iostream>
namespace net = boost::asio;
using boost::redis::request;
using boost::redis::response;
using boost::redis::config;
using boost::redis::connection;
auto co_main(config const& cfg) -> net::awaitable<void>
{
auto conn = std::make_shared<connection>(co_await net::this_coro::executor);
conn->async_run(cfg, {}, net::consign(net::detached, conn));
// A request containing only a ping command.
request req;
req.push("PING", "Hello world");
// Response object.
response<std::string> resp;
// Executes the request.
co_await conn->async_exec(req, resp);
conn->cancel();
std::cout << "PING: " << std::get<0>(resp).value() << std::endl;
}
----
The roles played by the `async_run` and `async_exec` functions are:
* xref:reference:boost/redis/basic_connection/async_exec-02.adoc[`connection::async_exec`]: executes the commands contained in the
request and stores the individual responses in the response object. Can
be called from multiple places in your code concurrently.
* xref:reference:boost/redis/basic_connection/async_run-04.adoc[`connection::async_run`]: keeps the connection healthy. It takes care of hostname resolution, session establishment, health-checks, reconnection and coordination of low-level read and write operations. It should be called only once per connection, regardless of the number of requests to execute.
== Server pushes
Redis servers can also send a variety of pushes to the client. Some of
them are:
* https://redis.io/docs/manual/pubsub/[Pubsub messages].
* https://redis.io/docs/manual/keyspace-notifications/[Keyspace notifications].
* https://redis.io/docs/manual/client-side-caching/[Client-side caching].
The connection class supports server pushes by means of the
xref:reference:boost/redis/basic_connection/async_receive.adoc[`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 use it:
[source,cpp]
----
auto
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()) {
// Reconnect to channels.
co_await conn->async_exec(req, ignore);
// Loop reading Redis pushes.
for (;;) {
error_code ec;
co_await conn->async_receive(resp, net::redirect_error(net::use_awaitable, ec));
if (ec)
break; // Connection lost, break so we can reconnect to channels.
// Use the response resp in some way and then clear it.
...
consume_one(resp);
}
}
}
----
== Further reading
Here is a list of topics that you might be interested in:
* xref:requests_responses.adoc[More on requests and responses].
* xref:serialization.adoc[Serializing and parsing into custom types].
* xref:logging.adoc[Configuring logging].
* xref:examples.adoc[Examples].

View File

@@ -0,0 +1,45 @@
//
// Copyright (c) 2025 Marcelo Zimbres Silva (mzimbres@gmail.com),
// Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
= Logging
xref:reference:boost/redis/basic_connection/async_run-04.adoc[`connection::async_run`]
is a complex algorithm, with features like built-in reconnection.
This can make configuration problems, like a misconfigured hostname, difficult to debug -
Boost.Redis will keep retrying to connect to the same hostname over and over.
For this reason, Boost.Redis incorporates a lightweight logging solution, and
*will log some status messages to stderr by default*.
Logging can be customized by passing a
xref:reference:boost/redis/logger.adoc[`logger`] object to the connection's constructor. For example, logging can be disabled by writing:
[source,cpp]
----
asio::io_context ioc;
connection conn {ioc, logger{logger::level::disabled}};
----
Every message logged by the library is attached a
https://en.wikipedia.org/wiki/Syslog#Severity_level[syslog-like severity]
tag (a xref:reference:boost/redis/logger/level.adoc[`logger::level`]).
You can filter messages by severity by creating a `logger` with a specific level:
[source,cpp]
----
asio::io_context ioc;
// Logs to stderr messages with severity >= level::error.
// This will hide all informational output.
connection conn {ioc, logger{logger::level::error}};
----
The `logger` constructor accepts a `std::function<void(logger::level, std::string_view)>`
as second argument. If supplied, Boost.Redis will call this function when logging
instead of printing to stderr. This can be used to integrate third-party logging
libraries. See our {site-url}/example/cpp17_spdlog.cpp[spdlog integration example]
for sample code.

View File

@@ -0,0 +1,90 @@
//
// Copyright (c) 2025 Marcelo Zimbres Silva (mzimbres@gmail.com),
// Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
[#reference]
= Reference
[width=100%,cols="5*"]
|===
| *Connections*
| *Requests and responses*
| *Adapters*
| *RESP3 protocol*
| *Unstable low-level APIs*
|
xref:reference:boost/redis/connection.adoc[`connection`]
xref:reference:boost/redis/basic_connection.adoc[`basic_connection`]
xref:reference:boost/redis/address.adoc[`address`]
xref:reference:boost/redis/config.adoc[`config`]
xref:reference:boost/redis/error.adoc[`error`]
xref:reference:boost/redis/logger.adoc[`logger`]
xref:reference:boost/redis/logger/level.adoc[`logger::level`]
xref:reference:boost/redis/operation.adoc[`operation`]
xref:reference:boost/redis/usage.adoc[`usage`]
|
xref:reference:boost/redis/ignore_t.adoc[`ignore_t`]
xref:reference:boost/redis/ignore.adoc[`ignore`]
xref:reference:boost/redis/request.adoc[`request`]
xref:reference:boost/redis/request/config.adoc[`request::config`]
xref:reference:boost/redis/response.adoc[`response`]
xref:reference:boost/redis/generic_response.adoc[`generic_response`]
xref:reference:boost/redis/consume_one-08.adoc[`consume_one`]
|
xref:reference:boost/redis/adapter/boost_redis_adapt.adoc[`boost_redis_adapt`]
xref:reference:boost/redis/adapter/ignore.adoc[`adapter::ignore`]
xref:reference:boost/redis/adapter/error.adoc[`adapter::error`]
xref:reference:boost/redis/adapter/result.adoc[`adapter::result`]
xref:reference:boost/redis/any_adapter.adoc[`any_adapter`]
|
xref:reference:boost/redis/resp3/basic_node.adoc[`basic_node`]
xref:reference:boost/redis/resp3/node.adoc[`node`]
xref:reference:boost/redis/resp3/node_view.adoc[`node_view`]
xref:reference:boost/redis/resp3/boost_redis_to_bulk-08.adoc[`boost_redis_to_bulk`]
xref:reference:boost/redis/resp3/type.adoc[`type`]
xref:reference:boost/redis/resp3/is_aggregate.adoc[`is_aggregate`]
|
xref:reference:boost/redis/adapter/adapt2.adoc[`adapter::adapt2`]
xref:reference:boost/redis/resp3/parser.adoc[`parser`]
xref:reference:boost/redis/resp3/parse.adoc[`parse`]
|===

View File

@@ -0,0 +1,302 @@
//
// Copyright (c) 2025 Marcelo Zimbres Silva (mzimbres@gmail.com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
= Requests and responses
== Requests
Redis requests are composed of one or more commands. In the
Redis documentation, requests are called
https://redis.io/topics/pipelining[pipelines]. For example:
[source,cpp]
----
// Some example containers.
std::list<std::string> list {...};
std::map<std::string, mystruct> map { ...};
// The request can contain multiple commands.
request req;
// Command with variable length of arguments.
req.push("SET", "key", "some value", "EX", "2");
// Pushes a list.
req.push_range("SUBSCRIBE", list);
// Same as above but as an iterator range.
req.push_range("SUBSCRIBE", std::cbegin(list), std::cend(list));
// Pushes a map.
req.push_range("HSET", "key", map);
----
Sending a request to Redis is performed by
xref:reference:boost/redis/basic_connection/async_exec-02.adoc[`connection::async_exec`]
as already stated. Requests accept a xref:reference:boost/redis/request/config[`boost::redis::request::config`]
object when constructed that dictates how requests are handled in situations like
reconnection. The reader is advised to read it carefully.
## Responses
Boost.Redis uses the following strategy to deal with Redis responses:
* xref:reference:boost/redis/response.adoc[`boost::redis::response`] should be used
when the request's number of commands is known at compile-time.
* xref:reference:boost/redis/generic_response.adoc[`boost::redis::generic_response`] should be
used when the number of commands is dynamic.
For example, the request below has three commands:
[source,cpp]
----
request req;
req.push("PING");
req.push("INCR", "key");
req.push("QUIT");
----
Therefore, its response will also contain three elements.
The following response object can be used:
[source,cpp]
----
response<std::string, int, std::string>
----
The response behaves as a `std::tuple` and must
have as many elements as the request has commands (exceptions below).
It is also necessary that each tuple element is capable of storing the
response to the command it refers to, otherwise an error will occur.
To ignore responses to individual commands in the request use the tag
xref:reference:boost/redis/ignore_t.adoc[`boost::redis::ignore_t`]. For example:
[source,cpp]
----
// Ignore the second and last responses.
response<std::string, ignore_t, std::string, ignore_t>
----
The following table provides the RESP3-types returned by some Redis
commands:
[cols="3*"]
|===
| *Command* | *RESP3 type* | *Documentation*
| `lpush` | Number | https://redis.io/commands/lpush[]
| `lrange` | Array | https://redis.io/commands/lrange[]
| `set` | Simple-string, null or blob-string | https://redis.io/commands/set[]
| `get` | Blob-string | https://redis.io/commands/get[]
| `smembers` | Set | https://redis.io/commands/smembers[]
| `hgetall` | Map | https://redis.io/commands/hgetall[]
|===
To map these RESP3 types into a pass:[C++] data structure use the table below:
[cols="3*"]
|===
| *RESP3 type* | *Possible pass:[C++] type* | *Type*
| Simple-string | `std::string` | Simple
| Simple-error | `std::string` | Simple
| Blob-string | `std::string`, `std::vector` | Simple
| Blob-error | `std::string`, `std::vector` | Simple
| Number | `long long`, `int`, `std::size_t`, `std::string` | Simple
| Double | `double`, `std::string` | Simple
| Null | `std::optional<T>` | Simple
| Array | `std::vector`, `std::list`, `std::array`, `std::deque` | Aggregate
| Map | `std::vector`, `std::map`, `std::unordered_map` | Aggregate
| Set | `std::vector`, `std::set`, `std::unordered_set` | Aggregate
| Push | `std::vector`, `std::map`, `std::unordered_map` | Aggregate
|===
For example, the response to the request
[source,cpp]
----
request req;
req.push("HELLO", 3);
req.push_range("RPUSH", "key1", vec);
req.push_range("HSET", "key2", map);
req.push("LRANGE", "key3", 0, -1);
req.push("HGETALL", "key4");
req.push("QUIT");
----
Can be read in the following response object:
[source,cpp]
----
response<
redis::ignore_t, // hello
int, // rpush
int, // hset
std::vector<T>, // lrange
std::map<U, V>, // hgetall
std::string // quit
> resp;
----
To execute the request and read the response use
xref:reference:boost/redis/basic_connection/async_exec-02.adoc[`async_exec`]:
[source,cpp]
----
co_await conn->async_exec(req, resp);
----
If the intention is to ignore responses altogether, use
xref:reference:boost/redis/ignore.adoc[`ignore`]:
[source,cpp]
----
// Ignores the response
co_await conn->async_exec(req, ignore);
----
Responses that contain nested aggregates or heterogeneous data
types will be given special treatment later in xref:#the-general-case[the general case]. As
of this writing, not all RESP3 types are used by the Redis server,
which means in practice users will be concerned with a reduced
subset of the RESP3 specification.
### Pushes
Commands that have no response, like
* `"SUBSCRIBE"`
* `"PSUBSCRIBE"`
* `"UNSUBSCRIBE"`
must **NOT** be included in the response tuple. For example, the following request
[source,cpp]
----
request req;
req.push("PING");
req.push("SUBSCRIBE", "channel");
req.push("QUIT");
----
must be read in the response object `response<std::string, std::string>`.
### Null
It is not uncommon for apps to access keys that do not exist or that
have already expired in the Redis server. To deal with these use cases,
wrap the type within a `std::optional`:
[source,cpp]
----
response<
std::optional<A>,
std::optional<B>,
...
> resp;
----
Everything else stays the same.
### Transactions
To read responses to transactions we must first observe that Redis
will queue the transaction commands and send their individual
responses as elements of an array. The array itself is the response to
the `EXEC` command. For example, to read the response to this request
[source,cpp]
----
req.push("MULTI");
req.push("GET", "key1");
req.push("LRANGE", "key2", 0, -1);
req.push("HGETALL", "key3");
req.push("EXEC");
----
Use the following response type:
[source,cpp]
----
response<
ignore_t, // multi
ignore_t, // QUEUED
ignore_t, // QUEUED
ignore_t, // QUEUED
response<
std::optional<std::string>, // get
std::optional<std::vector<std::string>>, // lrange
std::optional<std::map<std::string, std::string>> // hgetall
> // exec
> resp;
----
For a complete example, see {site-url}/example/cpp20_containers.cpp[cpp20_containers.cpp].
[#the-general-case]
### The general case
There are cases where responses to Redis
commands won't fit in the model presented above. Some examples are:
* Commands (like `set`) whose responses don't have a fixed
RESP3 type. Expecting an `int` and receiving a blob-string
results in an error.
* RESP3 aggregates that contain nested aggregates can't be read in STL containers.
* Transactions with a dynamic number of commands can't be read in a `response`.
To deal with these cases Boost.Redis provides the xref:reference:boost/redis/resp3/node.adoc[`boost::redis::resp3::node`] type
abstraction, that is the most general form of an element in a
response, be it a simple RESP3 type or the element of an aggregate. It
is defined like:
[source,cpp]
----
template <class String>
struct basic_node {
// The RESP3 type of the data in this node.
type data_type;
// The number of elements of an aggregate (or 1 for simple data).
std::size_t aggregate_size;
// The depth of this node in the response tree.
std::size_t depth;
// The actual data. For aggregate types this is always empty.
String value;
};
----
Any response to a Redis command can be parsed into a
xref:reference:boost/redis/generic_response.adoc[boost::redis::generic_response].
The vector can be seen as a pre-order view of the response tree.
Using it is not different than using other types:
[source,cpp]
----
// Receives any RESP3 simple or aggregate data type.
boost::redis::generic_response resp;
co_await conn->async_exec(req, resp);
----
For example, suppose we want to retrieve a hash data structure
from Redis with `HGETALL`, some of the options are
* `boost::redis::generic_response`: always works.
* `std::vector<std::string>`: efficient and flat, all elements as string.
* `std::map<std::string, std::string>`: efficient if you need the data as a `std::map`.
* `std::map<U, V>`: efficient if you are storing serialized data. Avoids temporaries and requires `boost_redis_from_bulk` for `U` and `V`.
In addition to the above users can also use unordered versions of the
containers. The same reasoning applies to sets e.g. `SMEMBERS`
and other data structures in general.

View File

@@ -0,0 +1,26 @@
//
// Copyright (c) 2025 Marcelo Zimbres Silva (mzimbres@gmail.com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
= Serializing and parsing into custom types
Boost.Redis supports serialization of user defined types by means of
the following customization points
[source,cpp]
----
// Serialize
void boost_redis_to_bulk(std::string& to, mystruct const& obj);
// Deserialize
void boost_redis_from_bulk(mystruct& u, node_view const& node, boost::system::error_code&);
----
These functions are accessed over ADL and therefore they must be
imported in the global namespace by the user. The following examples might be of interest:
* {site-url}/example/cpp20_json.cpp[cpp20_json.cpp]: serializes and parses JSON objects.
* {site-url}/example/cpp20_protobuf.cpp[cpp20_protobuf.cpp]: serializes and parses https://protobuf.dev/[protobuf] objects.

10
doc/mrdocs.cpp Normal file
View File

@@ -0,0 +1,10 @@
//
// Copyright (c) 2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#define BOOST_ALLOW_DEPRECATED // avoid mrdocs errors with the BOOST_DEPRECATED macro
#include <boost/redis.hpp>

31
doc/mrdocs.yml Normal file
View File

@@ -0,0 +1,31 @@
#
# Copyright (c) 2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
#
# Distributed under the Boost Software License, Version 1.0. (See accompanying
# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#
source-root: ../include
compilation-database: ./CMakeLists.txt
include-symbols:
- "boost::redis::**"
exclude-symbols:
- "boost::redis::detail::**"
- "boost::redis::adapter::detail::**"
- "boost::redis::resp3::detail::**"
- "boost::redis::basic_connection::run_is_canceled"
- "boost::redis::basic_connection::this_type"
- "boost::redis::any_adapter::impl_t"
- "boost::redis::any_adapter::fn_type"
- "boost::redis::any_adapter::create_impl"
- "boost::redis::any_adapter::impl_"
- "boost::redis::request::payload"
- "boost::redis::request::has_hello_priority"
see-below:
- "boost::redis::adapter::ignore"
sort-members: false
base-url: https://github.com/boostorg/redis/blob/master/include/
use-system-libc: true
warn-as-error: true
warn-if-undocumented: false
warn-no-paramdoc: false

1955
doc/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

6
doc/package.json Normal file
View File

@@ -0,0 +1,6 @@
{
"dependencies": {
"@cppalliance/antora-cpp-reference-extension": "^0.0.6",
"antora": "^3.1.10"
}
}

45
doc/redis-playbook.yml Normal file
View File

@@ -0,0 +1,45 @@
#
# Copyright (c) 2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
#
# Distributed under the Boost Software License, Version 1.0. (See accompanying
# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#
site:
url: https://github.com/boostorg/redis/blob/master
title: Boost.Redis
robots: allow
start_page: redis::index.adoc
antora:
extensions:
- require: '@cppalliance/antora-cpp-reference-extension'
dependencies:
- name: 'boost'
repo: 'https://github.com/boostorg/boost.git'
tag: 'develop'
variable: 'BOOST_SRC_DIR'
system-env: 'BOOST_SRC_DIR'
asciidoc:
attributes:
# Scrolling problems appear without this
page-pagination: ''
content:
sources:
- url: ..
start_path: doc
ui:
bundle:
url: https://github.com/boostorg/website-v2-docs/releases/download/ui-master/ui-bundle.zip
snapshot: true
output_dir: _
output:
dir: html
runtime:
log:
failure_level: error

View File

@@ -29,6 +29,7 @@ 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_testable_example(cpp20_unix_sockets 20)
make_example(cpp20_subscriber 20)
make_example(cpp20_streams 20)
@@ -50,3 +51,12 @@ endif()
if (NOT MSVC)
make_example(cpp20_chat_room 20)
endif()
# We build and test the spdlog integration example only if the library is found
find_package(spdlog)
if (spdlog_FOUND)
make_testable_example(cpp17_spdlog 17)
target_link_libraries(cpp17_spdlog PRIVATE spdlog::spdlog)
else()
message(STATUS "Skipping the spdlog example because the spdlog package couldn't be found")
endif()

View File

@@ -5,7 +5,9 @@
*/
#include <boost/redis/connection.hpp>
#include <boost/asio/detached.hpp>
#include <iostream>
namespace asio = boost::asio;
@@ -14,7 +16,7 @@ using boost::redis::request;
using boost::redis::response;
using boost::redis::config;
auto main(int argc, char * argv[]) -> int
auto main(int argc, char* argv[]) -> int
{
try {
config cfg;
@@ -32,7 +34,7 @@ auto main(int argc, char * argv[]) -> int
asio::io_context ioc;
connection conn{ioc};
conn.async_run(cfg, {}, asio::detached);
conn.async_run(cfg, asio::detached);
conn.async_exec(req, resp, [&](auto ec, auto) {
if (!ec)
@@ -47,4 +49,3 @@ auto main(int argc, char * argv[]) -> int
return 1;
}
}

View File

@@ -6,15 +6,15 @@
#include "sync_connection.hpp"
#include <string>
#include <iostream>
#include <string>
using boost::redis::sync_connection;
using boost::redis::request;
using boost::redis::response;
using boost::redis::config;
auto main(int argc, char * argv[]) -> int
auto main(int argc, char* argv[]) -> int
{
try {
config cfg;

102
example/cpp17_spdlog.cpp Normal file
View File

@@ -0,0 +1,102 @@
//
// Copyright (c) 2025 Marcelo Zimbres Silva (mzimbres@gmail.com),
// Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include <boost/redis/connection.hpp>
#include <boost/redis/logger.hpp>
#include <boost/asio/detached.hpp>
#include <boost/system/error_code.hpp>
#include <cstddef>
#include <iostream>
#include <spdlog/spdlog.h>
#include <string_view>
namespace asio = boost::asio;
namespace redis = boost::redis;
// Maps a Boost.Redis log level to a spdlog log level
static spdlog::level::level_enum to_spdlog_level(redis::logger::level lvl)
{
switch (lvl) {
// spdlog doesn't include the emerg and alert syslog levels,
// so we convert them to the highest supported level.
// Similarly, notice is similar to info
case redis::logger::level::emerg:
case redis::logger::level::alert:
case redis::logger::level::crit: return spdlog::level::critical;
case redis::logger::level::err: return spdlog::level::err;
case redis::logger::level::warning: return spdlog::level::warn;
case redis::logger::level::notice:
case redis::logger::level::info: return spdlog::level::info;
case redis::logger::level::debug:
default: return spdlog::level::debug;
}
}
// This function glues Boost.Redis logging and spdlog.
// It should have the signature shown here. It will be invoked
// by Boost.Redis whenever a message is to be logged.
static void do_log(redis::logger::level level, std::string_view msg)
{
spdlog::log(to_spdlog_level(level), "(Boost.Redis) {}", msg);
}
auto main(int argc, char* argv[]) -> int
{
if (argc != 3) {
std::cerr << "Usage: " << argv[0] << " <server-host> <server-port>\n";
exit(1);
}
try {
// Create an execution context, required to create any I/O objects
asio::io_context ioc;
// Create a connection to connect to Redis, and pass it a custom logger.
// Boost.Redis will call do_log whenever it needs to log a message.
// Note that the function will only be called for messages with level >= info
// (i.e. filtering is done by Boost.Redis).
redis::connection conn{
ioc,
redis::logger{redis::logger::level::info, do_log}
};
// Configuration to connect to the server
redis::config cfg;
cfg.addr.host = argv[1];
cfg.addr.port = argv[2];
// Run the connection with the specified configuration.
// This will establish the connection and keep it healthy
conn.async_run(cfg, asio::detached);
// Execute a request
redis::request req;
req.push("PING", "Hello world");
redis::response<std::string> resp;
conn.async_exec(req, resp, [&](boost::system::error_code ec, std::size_t /* bytes_read*/) {
if (ec) {
spdlog::error("Request failed: {}", ec.what());
exit(1);
} else {
spdlog::info("PING: {}", std::get<0>(resp).value());
}
conn.cancel();
});
// Actually run our example. Nothing will happen until we call run()
ioc.run();
} catch (std::exception const& e) {
spdlog::error("Error: {}", e.what());
return 1;
}
}

View File

@@ -5,13 +5,15 @@
*/
#include <boost/redis/connection.hpp>
#include <boost/asio/signal_set.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/redirect_error.hpp>
#include <boost/asio/posix/stream_descriptor.hpp>
#include <unistd.h>
#include <boost/asio/redirect_error.hpp>
#include <boost/asio/signal_set.hpp>
#include <iostream>
#include <unistd.h>
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
#if defined(BOOST_ASIO_HAS_POSIX_STREAM_DESCRIPTOR)
@@ -38,8 +40,7 @@ using namespace std::chrono_literals;
// Chat over Redis pubsub. To test, run this program from multiple
// terminals and type messages to stdin.
auto
receiver(std::shared_ptr<connection> conn) -> awaitable<void>
auto receiver(std::shared_ptr<connection> conn) -> awaitable<void>
{
request req;
req.push("SUBSCRIBE", "channel");
@@ -48,7 +49,6 @@ receiver(std::shared_ptr<connection> conn) -> awaitable<void>
conn->set_receive_response(resp);
while (conn->will_reconnect()) {
// Subscribe to channels.
co_await conn->async_exec(req, ignore);
@@ -56,19 +56,17 @@ receiver(std::shared_ptr<connection> conn) -> awaitable<void>
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
<< resp.value().at(1).value
<< " " << resp.value().at(2).value
<< " " << resp.value().at(3).value
<< std::endl;
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();
}
}
}
// Publishes stdin messages to a Redis channel.
auto publisher(std::shared_ptr<stream_descriptor> in, std::shared_ptr<connection> conn) -> 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 async_read_until(*in, dynamic_buffer(msg, 1024), "\n");
@@ -88,7 +86,7 @@ auto co_main(config cfg) -> awaitable<void>
co_spawn(ex, receiver(conn), detached);
co_spawn(ex, publisher(stream, conn), detached);
conn->async_run(cfg, {}, consign(detached, conn));
conn->async_run(cfg, consign(detached, conn));
signal_set sig_set{ex, SIGINT, SIGTERM};
co_await sig_set.async_wait();
@@ -96,11 +94,11 @@ auto co_main(config cfg) -> awaitable<void>
stream->cancel();
}
#else // defined(BOOST_ASIO_HAS_POSIX_STREAM_DESCRIPTOR)
#else // defined(BOOST_ASIO_HAS_POSIX_STREAM_DESCRIPTOR)
auto co_main(config const&) -> awaitable<void>
{
std::cout << "Requires support for posix streams." << std::endl;
co_return;
}
#endif // defined(BOOST_ASIO_HAS_POSIX_STREAM_DESCRIPTOR)
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
#endif // defined(BOOST_ASIO_HAS_POSIX_STREAM_DESCRIPTOR)
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)

View File

@@ -5,11 +5,13 @@
*/
#include <boost/redis/connection.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/detached.hpp>
#include <iostream>
#include <map>
#include <vector>
#include <iostream>
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
@@ -24,38 +26,41 @@ using boost::asio::awaitable;
using boost::asio::detached;
using boost::asio::consign;
template<class T>
template <class T>
std::ostream& operator<<(std::ostream& os, std::optional<T> const& opt)
{
if (opt.has_value())
std::cout << opt.value();
else
std::cout << "null";
if (opt.has_value())
std::cout << opt.value();
else
std::cout << "null";
return os;
return os;
}
void print(std::map<std::string, std::string> const& cont)
{
for (auto const& e: cont)
for (auto const& e : cont)
std::cout << e.first << ": " << e.second << "\n";
}
template <class T>
void print(std::vector<T> const& cont)
{
for (auto const& e: cont) std::cout << e << " ";
for (auto const& e : cont)
std::cout << e << " ";
std::cout << "\n";
}
// Stores the content of some STL containers in Redis.
auto store(std::shared_ptr<connection> conn) -> awaitable<void>
{
std::vector<int> vec
{1, 2, 3, 4, 5, 6};
std::vector<int> vec{1, 2, 3, 4, 5, 6};
std::map<std::string, std::string> map
{{"key1", "value1"}, {"key2", "value2"}, {"key3", "value3"}};
std::map<std::string, std::string> map{
{"key1", "value1"},
{"key2", "value2"},
{"key3", "value3"}
};
request req;
req.push_range("RPUSH", "rpush-key", vec);
@@ -100,22 +105,22 @@ auto transaction(std::shared_ptr<connection> conn) -> awaitable<void>
{
request req;
req.push("MULTI");
req.push("LRANGE", "rpush-key", 0, -1); // Retrieves
req.push("HGETALL", "hset-key"); // Retrieves
req.push("LRANGE", "rpush-key", 0, -1); // Retrieves
req.push("HGETALL", "hset-key"); // Retrieves
req.push("MGET", "key", "non-existing-key");
req.push("EXEC");
response<
ignore_t, // multi
ignore_t, // lrange
ignore_t, // hgetall
ignore_t, // mget
ignore_t, // multi
ignore_t, // lrange
ignore_t, // hgetall
ignore_t, // mget
response<
std::optional<std::vector<int>>,
std::optional<std::map<std::string, std::string>>,
std::optional<std::vector<std::optional<std::string>>>
> // exec
> resp;
std::optional<std::vector<std::optional<std::string>>>> // exec
>
resp;
co_await conn->async_exec(req, resp);
@@ -128,7 +133,7 @@ auto transaction(std::shared_ptr<connection> conn) -> awaitable<void>
awaitable<void> co_main(config cfg)
{
auto conn = std::make_shared<connection>(co_await asio::this_coro::executor);
conn->async_run(cfg, {}, consign(detached, conn));
conn->async_run(cfg, consign(detached, conn));
co_await store(conn);
co_await transaction(conn);
@@ -137,4 +142,4 @@ awaitable<void> co_main(config cfg)
conn->cancel();
}
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)

View File

@@ -5,10 +5,12 @@
*/
#include <boost/redis/connection.hpp>
#include <boost/asio/signal_set.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/redirect_error.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/signal_set.hpp>
#include <iostream>
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
@@ -22,10 +24,8 @@ using boost::system::error_code;
using boost::redis::connection;
using namespace std::chrono_literals;
auto
echo_server_session(
asio::ip::tcp::socket socket,
std::shared_ptr<connection> conn) -> asio::awaitable<void>
auto echo_server_session(asio::ip::tcp::socket socket, std::shared_ptr<connection> conn)
-> asio::awaitable<void>
{
request req;
response<std::string> resp;
@@ -60,11 +60,11 @@ auto co_main(config cfg) -> asio::awaitable<void>
auto ex = co_await asio::this_coro::executor;
auto conn = std::make_shared<connection>(ex);
asio::co_spawn(ex, listener(conn), asio::detached);
conn->async_run(cfg, {}, asio::consign(asio::detached, conn));
conn->async_run(cfg, asio::consign(asio::detached, conn));
signal_set sig_set(ex, SIGINT, SIGTERM);
co_await sig_set.async_wait();
conn->cancel();
}
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)

View File

@@ -5,9 +5,11 @@
*/
#include <boost/redis/connection.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/consign.hpp>
#include <boost/asio/detached.hpp>
#include <iostream>
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
@@ -22,7 +24,7 @@ using boost::redis::connection;
auto co_main(config cfg) -> asio::awaitable<void>
{
auto conn = std::make_shared<connection>(co_await asio::this_coro::executor);
conn->async_run(cfg, {}, asio::consign(asio::detached, conn));
conn->async_run(cfg, asio::consign(asio::detached, conn));
// A request containing only a ping command.
request req;
@@ -38,4 +40,4 @@ auto co_main(config cfg) -> asio::awaitable<void>
std::cout << "PING: " << std::get<0>(resp).value() << std::endl;
}
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)

View File

@@ -5,9 +5,12 @@
*/
#include <boost/redis/connection.hpp>
#include <boost/asio/use_awaitable.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/consign.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/ssl/context.hpp>
#include <boost/asio/use_awaitable.hpp>
#include <iostream>
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
@@ -33,21 +36,22 @@ auto co_main(config cfg) -> asio::awaitable<void>
cfg.addr.host = "db.occase.de";
cfg.addr.port = "6380";
auto conn = std::make_shared<connection>(co_await asio::this_coro::executor);
conn->async_run(cfg, {}, asio::consign(asio::detached, conn));
asio::ssl::context ctx{asio::ssl::context::tlsv12_client};
ctx.set_verify_mode(asio::ssl::verify_peer);
ctx.set_verify_callback(verify_certificate);
auto conn = std::make_shared<connection>(co_await asio::this_coro::executor, std::move(ctx));
conn->async_run(cfg, asio::consign(asio::detached, conn));
request req;
req.push("PING");
response<std::string> resp;
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);
conn->cancel();
std::cout << "Response: " << std::get<0>(resp).value() << std::endl;
}
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)

View File

@@ -5,20 +5,23 @@
*/
#include <boost/redis/connection.hpp>
#include <boost/asio/detached.hpp>
#include <boost/describe.hpp>
#include <boost/asio/consign.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/use_awaitable.hpp>
#include <string>
#include <boost/describe.hpp>
#include <iostream>
#include <string>
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
#include <boost/json/serialize.hpp>
#include <boost/redis/resp3/serialization.hpp>
#include <boost/json/parse.hpp>
#include <boost/json/serialize.hpp>
#include <boost/json/value_from.hpp>
#include <boost/json/value_to.hpp>
#include <boost/redis/resp3/serialization.hpp>
namespace asio = boost::asio;
namespace resp3 = boost::redis::resp3;
@@ -30,7 +33,7 @@ using boost::redis::config;
using boost::redis::connection;
using boost::redis::resp3::node_view;
// Struct that will be stored in Redis using json serialization.
// Struct that will be stored in Redis using json serialization.
struct user {
std::string name;
std::string age;
@@ -46,11 +49,7 @@ void boost_redis_to_bulk(std::string& to, user const& u)
resp3::boost_redis_to_bulk(to, boost::json::serialize(boost::json::value_from(u)));
}
void
boost_redis_from_bulk(
user& u,
node_view const& node,
boost::system::error_code&)
void boost_redis_from_bulk(user& u, node_view const& node, boost::system::error_code&)
{
u = boost::json::value_to<user>(boost::json::parse(node.value));
}
@@ -59,15 +58,15 @@ auto co_main(config cfg) -> asio::awaitable<void>
{
auto ex = co_await asio::this_coro::executor;
auto conn = std::make_shared<connection>(ex);
conn->async_run(cfg, {}, asio::consign(asio::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"};
// Stores and retrieves in the same request.
request req;
req.push("SET", "json-key", u); // Stores in Redis.
req.push("GET", "json-key"); // Retrieves from Redis.
req.push("SET", "json-key", u); // Stores in Redis.
req.push("GET", "json-key"); // Retrieves from Redis.
response<ignore_t, user> resp;
@@ -75,10 +74,9 @@ auto co_main(config cfg) -> asio::awaitable<void>
conn->cancel();
// Prints the first ping
std::cout
<< "Name: " << std::get<1>(resp).value().name << "\n"
<< "Age: " << std::get<1>(resp).value().age << "\n"
<< "Country: " << std::get<1>(resp).value().country << "\n";
std::cout << "Name: " << std::get<1>(resp).value().name << "\n"
<< "Age: " << std::get<1>(resp).value().age << "\n"
<< "Country: " << std::get<1>(resp).value().country << "\n";
}
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)

View File

@@ -6,10 +6,12 @@
#include <boost/redis/connection.hpp>
#include <boost/redis/resp3/serialization.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/consign.hpp>
#include <boost/asio/detached.hpp>
#include <boost/system/errc.hpp>
#include <iostream>
// See the definition in person.proto. This header is automatically
@@ -32,8 +34,7 @@ using boost::redis::resp3::node_view;
using tutorial::person;
// Boost.Redis customization points (example/protobuf.hpp)
namespace tutorial
{
namespace tutorial {
// Below I am using a Boost.Redis to indicate a protobuf error, this
// is ok for an example, users however might want to define their own
@@ -47,18 +48,14 @@ void boost_redis_to_bulk(std::string& to, person const& u)
resp3::boost_redis_to_bulk(to, tmp);
}
void
boost_redis_from_bulk(
person& u,
node_view const& node,
boost::system::error_code& ec)
void boost_redis_from_bulk(person& u, node_view const& node, boost::system::error_code& ec)
{
std::string const tmp {node.value};
std::string const tmp{node.value};
if (!u.ParseFromString(tmp))
ec = boost::redis::error::invalid_data_type;
}
} // tutorial
} // namespace tutorial
using tutorial::boost_redis_to_bulk;
using tutorial::boost_redis_from_bulk;
@@ -67,7 +64,7 @@ asio::awaitable<void> co_main(config cfg)
{
auto ex = co_await asio::this_coro::executor;
auto conn = std::make_shared<connection>(ex);
conn->async_run(cfg, {}, asio::consign(asio::detached, conn));
conn->async_run(cfg, asio::consign(asio::detached, conn));
person p;
p.set_name("Louis");
@@ -84,10 +81,9 @@ asio::awaitable<void> co_main(config cfg)
co_await conn->async_exec(req, resp);
conn->cancel();
std::cout
<< "Name: " << std::get<1>(resp).value().name() << "\n"
<< "Age: " << std::get<1>(resp).value().id() << "\n"
<< "Email: " << std::get<1>(resp).value().email() << "\n";
std::cout << "Name: " << std::get<1>(resp).value().name() << "\n"
<< "Age: " << std::get<1>(resp).value().id() << "\n"
<< "Email: " << std::get<1>(resp).value().email() << "\n";
}
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)

View File

@@ -5,9 +5,11 @@
*/
#include <boost/redis/connection.hpp>
#include <boost/asio/use_awaitable.hpp>
#include <boost/asio/redirect_error.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/redirect_error.hpp>
#include <boost/asio/use_awaitable.hpp>
#include <iostream>
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
@@ -21,8 +23,7 @@ using boost::redis::config;
using boost::redis::address;
using boost::redis::connection;
auto redir(boost::system::error_code& ec)
{ return asio::redirect_error(asio::use_awaitable, ec); }
auto redir(boost::system::error_code& ec) { return asio::redirect_error(asio::use_awaitable, ec); }
// For more info see
// - https://redis.io/docs/manual/sentinel.
@@ -43,12 +44,13 @@ auto resolve_master_address(std::vector<address> const& addresses) -> asio::awai
// 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, {}, asio::consign(asio::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();
if (!ec && std::get<0>(resp))
co_return address{std::get<0>(resp).value().value().at(0), std::get<0>(resp).value().value().at(1)};
co_return address{
std::get<0>(resp).value().value().at(0),
std::get<0>(resp).value().value().at(1)};
}
co_return address{};
@@ -58,18 +60,17 @@ 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.
std::vector<address> const addresses
{ address{"foo", "26379"}
, address{"bar", "26379"}
, cfg.addr
std::vector<address> const addresses{
address{"foo", "26379"},
address{"bar", "26379"},
cfg.addr
};
auto const ep = co_await resolve_master_address(addresses);
std::clog
<< "Host: " << ep.host << "\n"
<< "Port: " << ep.port << "\n"
<< std::flush;
std::clog << "Host: " << ep.host << "\n"
<< "Port: " << ep.port << "\n"
<< std::flush;
}
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)

View File

@@ -5,11 +5,13 @@
*/
#include <boost/redis/connection.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/consign.hpp>
#include <boost/asio/signal_set.hpp>
#include <boost/asio/awaitable.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/consign.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/signal_set.hpp>
#include <iostream>
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
@@ -29,53 +31,51 @@ using net::signal_set;
auto stream_reader(std::shared_ptr<connection> conn) -> net::awaitable<void>
{
std::string redisStreamKey_;
request req;
generic_response resp;
std::string redisStreamKey_;
request req;
generic_response resp;
std::string stream_id{"$"};
std::string const field = "myfield";
std::string stream_id{"$"};
std::string const field = "myfield";
for (;;) {
req.push("XREAD", "BLOCK", "0", "STREAMS", "test-topic", stream_id);
co_await conn->async_exec(req, resp);
for (;;) {
req.push("XREAD", "BLOCK", "0", "STREAMS", "test-topic", stream_id);
co_await conn->async_exec(req, resp);
//std::cout << "Response: ";
//for (auto i = 0UL; i < resp->size(); ++i) {
// std::cout << resp->at(i).value << ", ";
//}
//std::cout << std::endl;
//std::cout << "Response: ";
//for (auto i = 0UL; i < resp->size(); ++i) {
// std::cout << resp->at(i).value << ", ";
//}
//std::cout << std::endl;
// The following approach was taken in order to be able to
// deal with the responses, as generated by redis in the case
// that there are multiple stream 'records' within a single
// generic_response. The nesting and number of values in
// resp.value() are different, depending on the contents
// of the stream in redis. Uncomment the above commented-out
// code for examples while running the XADD command.
// The following approach was taken in order to be able to
// deal with the responses, as generated by redis in the case
// that there are multiple stream 'records' within a single
// generic_response. The nesting and number of values in
// resp.value() are different, depending on the contents
// of the stream in redis. Uncomment the above commented-out
// code for examples while running the XADD command.
std::size_t item_index = 0;
while (item_index < std::size(resp.value())) {
auto const& val = resp.value().at(item_index).value;
std::size_t item_index = 0;
while (item_index < std::size(resp.value())) {
auto const& val = resp.value().at(item_index).value;
if (field.compare(val) == 0) {
// We've hit a myfield field.
// The streamId is located at item_index - 2
// The payload is located at item_index + 1
stream_id = resp.value().at(item_index - 2).value;
std::cout
<< "StreamId: " << stream_id << ", "
<< "MyField: " << resp.value().at(item_index + 1).value
<< std::endl;
++item_index; // We can increase so we don't read this again
}
if (field.compare(val) == 0) {
// We've hit a myfield field.
// The streamId is located at item_index - 2
// The payload is located at item_index + 1
stream_id = resp.value().at(item_index - 2).value;
std::cout << "StreamId: " << stream_id << ", "
<< "MyField: " << resp.value().at(item_index + 1).value << std::endl;
++item_index; // We can increase so we don't read this again
}
++item_index;
}
++item_index;
}
req.clear();
resp.value().clear();
}
req.clear();
resp.value().clear();
}
}
// Run this in another terminal:
@@ -88,10 +88,10 @@ auto co_main(config cfg) -> net::awaitable<void>
// Disable health checks.
cfg.health_check_interval = std::chrono::seconds::zero();
conn->async_run(cfg, {}, net::consign(net::detached, conn));
conn->async_run(cfg, net::consign(net::detached, conn));
signal_set sig_set(ex, SIGINT, SIGTERM);
co_await sig_set.async_wait();
conn->cancel();
}
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)

View File

@@ -6,13 +6,15 @@
#include <boost/redis/connection.hpp>
#include <boost/redis/logger.hpp>
#include <boost/asio/awaitable.hpp>
#include <boost/asio/use_awaitable.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/consign.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/redirect_error.hpp>
#include <boost/asio/signal_set.hpp>
#include <boost/asio/use_awaitable.hpp>
#include <iostream>
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
@@ -47,8 +49,7 @@ using asio::signal_set;
*/
// Receives server pushes.
auto
receiver(std::shared_ptr<connection> conn) -> asio::awaitable<void>
auto receiver(std::shared_ptr<connection> conn) -> asio::awaitable<void>
{
request req;
req.push("SUBSCRIBE", "channel");
@@ -58,7 +59,6 @@ receiver(std::shared_ptr<connection> conn) -> asio::awaitable<void>
// Loop while reconnection is enabled
while (conn->will_reconnect()) {
// Reconnect to the channels.
co_await conn->async_exec(req, ignore);
@@ -72,13 +72,10 @@ receiver(std::shared_ptr<connection> conn) -> asio::awaitable<void>
}
if (ec)
break; // Connection lost, break so we can reconnect to channels.
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;
std::cout << resp.value().at(1).value << " " << resp.value().at(2).value << " "
<< resp.value().at(3).value << std::endl;
consume_one(resp);
}
@@ -90,7 +87,7 @@ auto co_main(config cfg) -> asio::awaitable<void>
auto ex = co_await asio::this_coro::executor;
auto conn = std::make_shared<connection>(ex);
asio::co_spawn(ex, receiver(conn), asio::detached);
conn->async_run(cfg, {}, asio::consign(asio::detached, conn));
conn->async_run(cfg, asio::consign(asio::detached, conn));
signal_set sig_set(ex, SIGINT, SIGTERM);
co_await sig_set.async_wait();
@@ -98,4 +95,4 @@ auto co_main(config cfg) -> asio::awaitable<void>
conn->cancel();
}
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)

View File

@@ -0,0 +1,60 @@
//
// Copyright (c) 2025 Marcelo Zimbres Silva (mzimbres@gmail.com),
// Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include <boost/asio/awaitable.hpp>
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
#include <boost/redis/connection.hpp>
#include <boost/asio/consign.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/this_coro.hpp>
#include <iostream>
namespace asio = boost::asio;
using boost::redis::request;
using boost::redis::response;
using boost::redis::config;
using boost::redis::logger;
using boost::redis::connection;
#ifdef BOOST_ASIO_HAS_LOCAL_SOCKETS
auto co_main(config cfg) -> asio::awaitable<void>
{
// If unix_socket is set to a non-empty string, UNIX domain sockets will be used
// instead of TCP. Set this value to the path where your server is listening.
// UNIX domain socket connections work in the same way as TCP connections.
cfg.unix_socket = "/tmp/redis-socks/redis.sock";
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;
co_await conn->async_exec(req, resp);
conn->cancel();
std::cout << "Response: " << std::get<0>(resp).value() << std::endl;
}
#else
auto co_main(config) -> asio::awaitable<void>
{
std::cout << "Sorry, your system does not support UNIX domain sockets\n";
co_return;
}
#endif
#endif

View File

@@ -4,11 +4,13 @@
* accompanying file LICENSE.txt)
*/
#include <boost/redis/connection.hpp>
#include <boost/redis/config.hpp>
#include <boost/redis/connection.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/use_awaitable.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/use_awaitable.hpp>
#include <iostream>
namespace asio = boost::asio;
@@ -19,7 +21,7 @@ using boost::redis::logger;
extern asio::awaitable<void> co_main(config);
auto main(int argc, char * argv[]) -> int
auto main(int argc, char* argv[]) -> int
{
try {
config cfg;
@@ -42,7 +44,7 @@ auto main(int argc, char * argv[]) -> int
}
}
#else // defined(BOOST_ASIO_HAS_CO_AWAIT)
#else // defined(BOOST_ASIO_HAS_CO_AWAIT)
auto main() -> int
{
@@ -50,4 +52,4 @@ auto main() -> int
return 0;
}
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)

View File

@@ -7,16 +7,17 @@
#include <boost/redis/connection.hpp>
#include <boost/redis/request.hpp>
#include <boost/asio/deferred.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/use_future.hpp>
#include <thread>
#include <chrono>
#include <thread>
using namespace std::chrono_literals;
namespace boost::redis
{
namespace boost::redis {
class sync_connection {
public:
@@ -25,33 +26,32 @@ public:
, conn_{std::make_shared<connection>(ioc_)}
{ }
~sync_connection()
{
thread_.join();
}
~sync_connection() { thread_.join(); }
void run(config cfg)
{
// Starts a thread that will can io_context::run on which the
// connection will run.
thread_ = std::thread{[this, cfg]() {
conn_->async_run(cfg, {}, asio::detached);
conn_->async_run(cfg, asio::detached);
ioc_.run();
}};
}
void stop()
{
asio::dispatch(ioc_, [this]() { conn_->cancel(); });
asio::dispatch(ioc_, [this]() {
conn_->cancel();
});
}
template <class Response>
auto exec(request const& req, Response& resp)
{
asio::dispatch(
conn_->get_executor(),
asio::deferred([this, &req, &resp]() { return conn_->async_exec(req, resp, asio::deferred); }))
(asio::use_future).get();
asio::dispatch(conn_->get_executor(), asio::deferred([this, &req, &resp]() {
return conn_->async_exec(req, resp, asio::deferred);
}))(asio::use_future)
.get();
}
private:
@@ -60,4 +60,4 @@ private:
std::thread thread_;
};
}
} // namespace boost::redis

View File

@@ -8,12 +8,12 @@
#define BOOST_REDIS_HPP
#include <boost/redis/config.hpp>
#include <boost/redis/error.hpp>
#include <boost/redis/connection.hpp>
#include <boost/redis/request.hpp>
#include <boost/redis/response.hpp>
#include <boost/redis/error.hpp>
#include <boost/redis/ignore.hpp>
#include <boost/redis/logger.hpp>
#include <boost/redis/request.hpp>
#include <boost/redis/response.hpp>
/** @defgroup high-level-api Reference
*
@@ -25,4 +25,4 @@
* This page contains the documentation of the Aedis low-level API.
*/
#endif // BOOST_REDIS_HPP
#endif // BOOST_REDIS_HPP

View File

@@ -7,74 +7,40 @@
#ifndef BOOST_REDIS_ADAPTER_ADAPT_HPP
#define BOOST_REDIS_ADAPTER_ADAPT_HPP
#include <boost/redis/resp3/node.hpp>
#include <boost/redis/response.hpp>
#include <boost/redis/adapter/detail/result_traits.hpp>
#include <boost/redis/adapter/detail/response_traits.hpp>
#include <boost/mp11.hpp>
#include <boost/system.hpp>
#include <boost/redis/adapter/detail/result_traits.hpp>
#include <boost/redis/ignore.hpp>
#include <tuple>
#include <limits>
#include <string_view>
#include <variant>
namespace boost::redis::adapter
{
namespace boost::redis::adapter {
/** @brief Adapts a type to be used as a response.
*
* The type T must be either
*
* 1. a response<T1, T2, T3, ...> or
* 2. std::vector<node<String>>
* @li a `response<T1, T2, T3, ...>`
* @li `std::vector<node<String>>`
*
* The types T1, T2, etc can be any STL container, any integer type
* and `std::string`.
*
* @param t Tuple containing the responses.
* @tparam T The response type.
*/
template<class T>
template <class T>
auto boost_redis_adapt(T& t) noexcept
{
return detail::response_traits<T>::adapt(t);
}
/** @brief Adapts user data to read operations.
* @ingroup low-level-api
/** @brief Adapts a type to be used as the response to an individual command.
*
* STL containers, \c resp3::response and built-in types are supported and
* can be used in conjunction with \c std::optional<T>.
*
* Example usage:
*
* @code
* std::unordered_map<std::string, std::string> cont;
* co_await async_read(socket, buffer, adapt(cont));
* @endcode
*
* For a transaction
*
* @code
* sr.push(command::multi);
* sr.push(command::ping, ...);
* sr.push(command::incr, ...);
* sr.push_range(command::rpush, ...);
* sr.push(command::lrange, ...);
* sr.push(command::incr, ...);
* sr.push(command::exec);
*
* co_await async_write(socket, buffer(request));
*
* // Reads the response to a transaction
* resp3::response<std::string, int, int, std::vector<std::string>, int> execs;
* co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(execs));
* @endcode
* It can be used with low-level APIs, like @ref boost::redis::resp3::parser.
*/
template<class T>
template <class T>
auto adapt2(T& t = redis::ignore) noexcept
{ return detail::result_traits<T>::adapt(t); }
{
return detail::result_traits<T>::adapt(t);
}
} // boost::redis::adapter
} // namespace boost::redis::adapter
#endif // BOOST_REDIS_ADAPTER_ADAPT_HPP
#endif // BOOST_REDIS_ADAPTER_ADAPT_HPP

View File

@@ -1,4 +1,4 @@
/* Copyright (c) 2018-2023 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2025 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
@@ -7,10 +7,11 @@
#ifndef BOOST_REDIS_ANY_ADAPTER_HPP
#define BOOST_REDIS_ANY_ADAPTER_HPP
#include <boost/redis/resp3/node.hpp>
#include <boost/redis/adapter/adapt.hpp>
#include <boost/redis/resp3/node.hpp>
#include <boost/system/error_code.hpp>
#include <cstddef>
#include <functional>
#include <string_view>
@@ -18,50 +19,58 @@
namespace boost::redis {
namespace detail {
// Forward decl
template <class Executor>
class basic_connection;
}
/** @brief A type-erased reference to a response.
* @ingroup high-level-api
*
* A type-erased response adapter. It can be executed using @ref connection::async_exec.
* Using this type instead of raw response references enables separate compilation.
*
* Given a response object `resp` that can be passed to `async_exec`, the following two
* statements have the same effect:
* ```
*
* @code
* co_await conn.async_exec(req, resp);
* co_await conn.async_exec(req, any_response(resp));
* ```
* @endcode
*/
class any_adapter
{
using fn_type = std::function<void(std::size_t, resp3::basic_node<std::string_view> const&, system::error_code&)>;
struct impl_t {
fn_type adapt_fn;
std::size_t supported_response_size;
} impl_;
template <class T>
static auto create_impl(T& resp) -> impl_t
{
using namespace boost::redis::adapter;
auto adapter = boost_redis_adapt(resp);
std::size_t size = adapter.get_supported_response_size();
return { std::move(adapter), size };
}
template <class Executor>
friend class basic_connection;
class any_adapter {
public:
/**
/** @brief Parse events that an adapter must support.
*/
enum class parse_event
{
/// Called before the parser starts processing data
init,
/// Called for each and every node of RESP3 data
node,
/// Called when done processing a complete RESP3 message
done
};
/// The type erased implementation type.
using impl_t = std::function<void(parse_event, resp3::node_view const&, system::error_code&)>;
template <class T>
static auto create_impl(T& resp) -> impl_t
{
using namespace boost::redis::adapter;
return [adapter2 = boost_redis_adapt(resp)](
any_adapter::parse_event ev,
resp3::node_view const& nd,
system::error_code& ec) mutable {
switch (ev) {
case parse_event::init: adapter2.on_init(); break;
case parse_event::node: adapter2.on_node(nd, ec); break;
case parse_event::done: adapter2.on_done(); break;
}
};
}
/// Contructs from a type erased adaper
any_adapter(impl_t fn = [](parse_event, resp3::node_view const&, system::error_code&) { })
: impl_{std::move(fn)}
{ }
/**
* @brief Constructor.
*
* Creates a type-erased response adapter from `resp` by calling
@@ -71,10 +80,35 @@ public:
* This object stores a reference to `resp`, which must be kept alive
* while `*this` is being used.
*/
template <class T, class = std::enable_if_t<!std::is_same_v<T, any_adapter>>>
explicit any_adapter(T& resp) : impl_(create_impl(resp)) {}
template <class T, class = std::enable_if_t<!std::is_same_v<T, any_adapter>>>
explicit any_adapter(T& resp)
: impl_(create_impl(resp))
{ }
/// Calls the implementation with the arguments `impl_(parse_event::init, ...);`
void on_init()
{
system::error_code ec;
impl_(parse_event::init, {}, ec);
};
/// Calls the implementation with the arguments `impl_(parse_event::done, ...);`
void on_done()
{
system::error_code ec;
impl_(parse_event::done, {}, ec);
};
/// Calls the implementation with the arguments `impl_(parse_event::node, ...);`
void on_node(resp3::node_view const& nd, system::error_code& ec)
{
impl_(parse_event::node, nd, ec);
};
private:
impl_t impl_;
};
}
} // namespace boost::redis
#endif

View File

@@ -7,26 +7,28 @@
#ifndef BOOST_REDIS_ADAPTER_ADAPTERS_HPP
#define BOOST_REDIS_ADAPTER_ADAPTERS_HPP
#include <boost/redis/error.hpp>
#include <boost/redis/resp3/type.hpp>
#include <boost/redis/resp3/serialization.hpp>
#include <boost/redis/resp3/node.hpp>
#include <boost/redis/adapter/result.hpp>
#include <boost/redis/error.hpp>
#include <boost/redis/resp3/node.hpp>
#include <boost/redis/resp3/serialization.hpp>
#include <boost/redis/resp3/type.hpp>
#include <boost/assert.hpp>
#include <set>
#include <optional>
#include <unordered_set>
#include <forward_list>
#include <system_error>
#include <map>
#include <unordered_map>
#include <list>
#include <deque>
#include <vector>
#include <array>
#include <string_view>
#include <charconv>
#include <deque>
#include <forward_list>
#include <list>
#include <map>
#include <optional>
#include <set>
#include <string_view>
#include <system_error>
#include <type_traits>
#include <unordered_map>
#include <unordered_set>
#include <vector>
// See https://stackoverflow.com/a/31658120/1077832
#ifdef _LIBCPP_VERSION
@@ -34,55 +36,46 @@
#include <cstdlib>
#endif
namespace boost::redis::adapter::detail
{
namespace boost::redis::adapter::detail {
template <class> struct is_integral : std::false_type {};
// Exclude bools, char and charXY_t types
template <class T> struct is_integral_number : std::is_integral<T> { };
template <> struct is_integral_number<bool> : std::false_type { };
template <> struct is_integral_number<char> : std::false_type { };
template <> struct is_integral_number<char16_t> : std::false_type { };
template <> struct is_integral_number<char32_t> : std::false_type { };
template <> struct is_integral_number<wchar_t> : std::false_type { };
#ifdef __cpp_char8_t
template <> struct is_integral_number<char8_t> : std::false_type { };
#endif
template <> struct is_integral<long long int > : std::true_type {};
template <> struct is_integral<unsigned long long int> : std::true_type {};
template <> struct is_integral<int > : std::true_type {};
template<class T, bool = is_integral<T>::value>
template <class T, bool = is_integral_number<T>::value>
struct converter;
template<class T>
template <class T>
struct converter<T, true> {
template <class String>
static void
apply(
T& i,
resp3::basic_node<String> const& node,
system::error_code& ec)
static void apply(T& i, resp3::basic_node<String> const& node, system::error_code& ec)
{
auto const res =
std::from_chars(node.value.data(), node.value.data() + node.value.size(), i);
auto const res = std::from_chars(node.value.data(), node.value.data() + node.value.size(), i);
if (res.ec != std::errc())
ec = redis::error::not_a_number;
}
};
template<>
template <>
struct converter<bool, false> {
template <class String>
static void
apply(
bool& t,
resp3::basic_node<String> const& node,
system::error_code& ec)
static void apply(bool& t, resp3::basic_node<String> const& node, system::error_code&)
{
t = *node.value.data() == 't';
}
};
template<>
template <>
struct converter<double, false> {
template <class String>
static void
apply(
double& d,
resp3::basic_node<String> const& node,
system::error_code& ec)
static void apply(double& d, resp3::basic_node<String> const& node, system::error_code& ec)
{
#ifdef _LIBCPP_VERSION
// The string in node.value is not null terminated and we also
@@ -97,15 +90,14 @@ struct converter<double, false> {
auto const res = std::from_chars(node.value.data(), node.value.data() + node.value.size(), d);
if (res.ec != std::errc())
ec = redis::error::not_a_double;
#endif // _LIBCPP_VERSION
#endif // _LIBCPP_VERSION
}
};
template <class CharT, class Traits, class Allocator>
struct converter<std::basic_string<CharT, Traits, Allocator>, false> {
template <class String>
static void
apply(
static void apply(
std::basic_string<CharT, Traits, Allocator>& s,
resp3::basic_node<String> const& node,
system::error_code&)
@@ -117,11 +109,7 @@ struct converter<std::basic_string<CharT, Traits, Allocator>, false> {
template <class T>
struct from_bulk_impl {
template <class String>
static void
apply(
T& t,
resp3::basic_node<String> const& node,
system::error_code& ec)
static void apply(T& t, resp3::basic_node<String> const& node, system::error_code& ec)
{
converter<T>::apply(t, node, ec);
}
@@ -130,8 +118,7 @@ struct from_bulk_impl {
template <class T>
struct from_bulk_impl<std::optional<T>> {
template <class String>
static void
apply(
static void apply(
std::optional<T>& op,
resp3::basic_node<String> const& node,
system::error_code& ec)
@@ -144,11 +131,7 @@ struct from_bulk_impl<std::optional<T>> {
};
template <class T, class String>
void
boost_redis_from_bulk(
T& t,
resp3::basic_node<String> const& node,
system::error_code& ec)
void boost_redis_from_bulk(T& t, resp3::basic_node<String> const& node, system::error_code& ec)
{
from_bulk_impl<T>::apply(t, node, ec);
}
@@ -161,18 +144,32 @@ private:
Result* result_;
public:
explicit general_aggregate(Result* c = nullptr): result_(c) {}
explicit general_aggregate(Result* c = nullptr)
: result_(c)
{ }
void on_init() { }
void on_done() { }
template <class String>
void operator()(resp3::basic_node<String> const& nd, system::error_code&)
void on_node(resp3::basic_node<String> const& nd, system::error_code&)
{
BOOST_ASSERT_MSG(!!result_, "Unexpected null pointer");
switch (nd.data_type) {
case resp3::type::blob_error:
case resp3::type::simple_error:
*result_ = error{nd.data_type, std::string{std::cbegin(nd.value), std::cend(nd.value)}};
*result_ = error{
nd.data_type,
std::string{std::cbegin(nd.value), std::cend(nd.value)}
};
break;
default:
result_->value().push_back({nd.data_type, nd.aggregate_size, nd.depth, std::string{std::cbegin(nd.value), std::cend(nd.value)}});
result_->value().push_back({
nd.data_type,
nd.aggregate_size,
nd.depth,
std::string{std::cbegin(nd.value), std::cend(nd.value)}
});
}
}
};
@@ -183,16 +180,24 @@ private:
Node* result_;
public:
explicit general_simple(Node* t = nullptr) : result_(t) {}
explicit general_simple(Node* t = nullptr)
: result_(t)
{ }
void on_init() { }
void on_done() { }
template <class String>
void operator()(resp3::basic_node<String> const& nd, system::error_code&)
void on_node(resp3::basic_node<String> const& nd, system::error_code&)
{
BOOST_ASSERT_MSG(!!result_, "Unexpected null pointer");
switch (nd.data_type) {
case resp3::type::blob_error:
case resp3::type::simple_error:
*result_ = error{nd.data_type, std::string{std::cbegin(nd.value), std::cend(nd.value)}};
*result_ = error{
nd.data_type,
std::string{std::cbegin(nd.value), std::cend(nd.value)}
};
break;
default:
result_->value().data_type = nd.data_type;
@@ -206,10 +211,13 @@ public:
template <class Result>
class simple_impl {
public:
void on_value_available(Result&) {}
void on_value_available(Result&) { }
void on_init() { }
void on_done() { }
template <class String>
void operator()(Result& result, resp3::basic_node<String> const& node, system::error_code& ec)
void on_node(Result& result, resp3::basic_node<String> const& node, system::error_code& ec)
{
if (is_aggregate(node.data_type)) {
ec = redis::error::expects_resp3_simple_type;
@@ -226,11 +234,13 @@ private:
typename Result::iterator hint_;
public:
void on_value_available(Result& result)
{ hint_ = std::end(result); }
void on_value_available(Result& result) { hint_ = std::end(result); }
void on_init() { }
void on_done() { }
template <class String>
void operator()(Result& result, resp3::basic_node<String> const& nd, system::error_code& ec)
void on_node(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)
@@ -241,8 +251,8 @@ public:
BOOST_ASSERT(nd.aggregate_size == 1);
if (nd.depth < 1) {
ec = redis::error::expects_resp3_set;
return;
ec = redis::error::expects_resp3_set;
return;
}
typename Result::key_type obj;
@@ -258,23 +268,25 @@ private:
bool on_key_ = true;
public:
void on_value_available(Result& result)
{ current_ = std::end(result); }
void on_value_available(Result& result) { current_ = std::end(result); }
void on_init() { }
void on_done() { }
template <class String>
void operator()(Result& result, resp3::basic_node<String> const& nd, system::error_code& ec)
void on_node(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)
ec = redis::error::expects_resp3_map;
ec = redis::error::expects_resp3_map;
return;
}
BOOST_ASSERT(nd.aggregate_size == 1);
if (nd.depth < 1) {
ec = redis::error::expects_resp3_map;
return;
ec = redis::error::expects_resp3_map;
return;
}
if (on_key_) {
@@ -294,10 +306,13 @@ public:
template <class Result>
class vector_impl {
public:
void on_value_available(Result& ) { }
void on_value_available(Result&) { }
void on_init() { }
void on_done() { }
template <class String>
void operator()(Result& result, resp3::basic_node<String> const& nd, system::error_code& ec)
void on_node(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);
@@ -315,13 +330,16 @@ private:
int i_ = -1;
public:
void on_value_available(Result& ) { }
void on_value_available(Result&) { }
void on_init() { }
void on_done() { }
template <class String>
void operator()(Result& result, resp3::basic_node<String> const& nd, system::error_code& ec)
void on_node(Result& result, resp3::basic_node<String> const& nd, system::error_code& ec)
{
if (is_aggregate(nd.data_type)) {
if (i_ != -1) {
if (i_ != -1) {
ec = redis::error::nested_aggregate_not_supported;
return;
}
@@ -346,21 +364,23 @@ public:
template <class Result>
struct list_impl {
void on_value_available(Result&) { }
void on_value_available(Result& ) { }
void on_init() { }
void on_done() { }
template <class String>
void operator()(Result& result, resp3::basic_node<String> const& nd, system::error_code& ec)
void on_node(Result& result, resp3::basic_node<String> const& nd, system::error_code& ec)
{
if (!is_aggregate(nd.data_type)) {
BOOST_ASSERT(nd.aggregate_size == 1);
if (nd.depth < 1) {
ec = redis::error::expects_resp3_aggregate;
return;
}
BOOST_ASSERT(nd.aggregate_size == 1);
if (nd.depth < 1) {
ec = redis::error::expects_resp3_aggregate;
return;
}
result.push_back({});
boost_redis_from_bulk(result.back(), nd, ec);
result.push_back({});
boost_redis_from_bulk(result.back(), nd, ec);
}
}
};
@@ -368,43 +388,69 @@ struct list_impl {
//---------------------------------------------------
template <class T>
struct impl_map { using type = simple_impl<T>; };
struct impl_map {
using type = simple_impl<T>;
};
template <class Key, class Compare, class Allocator>
struct impl_map<std::set<Key, Compare, Allocator>> { using type = set_impl<std::set<Key, Compare, Allocator>>; };
struct impl_map<std::set<Key, Compare, Allocator>> {
using type = set_impl<std::set<Key, Compare, Allocator>>;
};
template <class Key, class Compare, class Allocator>
struct impl_map<std::multiset<Key, Compare, Allocator>> { using type = set_impl<std::multiset<Key, Compare, Allocator>>; };
struct impl_map<std::multiset<Key, Compare, Allocator>> {
using type = set_impl<std::multiset<Key, Compare, Allocator>>;
};
template <class Key, class Hash, class KeyEqual, class Allocator>
struct impl_map<std::unordered_set<Key, Hash, KeyEqual, Allocator>> { using type = set_impl<std::unordered_set<Key, Hash, KeyEqual, Allocator>>; };
struct impl_map<std::unordered_set<Key, Hash, KeyEqual, Allocator>> {
using type = set_impl<std::unordered_set<Key, Hash, KeyEqual, Allocator>>;
};
template <class Key, class Hash, class KeyEqual, class Allocator>
struct impl_map<std::unordered_multiset<Key, Hash, KeyEqual, Allocator>> { using type = set_impl<std::unordered_multiset<Key, Hash, KeyEqual, Allocator>>; };
struct impl_map<std::unordered_multiset<Key, Hash, KeyEqual, Allocator>> {
using type = set_impl<std::unordered_multiset<Key, Hash, KeyEqual, Allocator>>;
};
template <class Key, class T, class Compare, class Allocator>
struct impl_map<std::map<Key, T, Compare, Allocator>> { using type = map_impl<std::map<Key, T, Compare, Allocator>>; };
struct impl_map<std::map<Key, T, Compare, Allocator>> {
using type = map_impl<std::map<Key, T, Compare, Allocator>>;
};
template <class Key, class T, class Compare, class Allocator>
struct impl_map<std::multimap<Key, T, Compare, Allocator>> { using type = map_impl<std::multimap<Key, T, Compare, Allocator>>; };
struct impl_map<std::multimap<Key, T, Compare, Allocator>> {
using type = map_impl<std::multimap<Key, T, Compare, Allocator>>;
};
template <class Key, class Hash, class KeyEqual, class Allocator>
struct impl_map<std::unordered_map<Key, Hash, KeyEqual, Allocator>> { using type = map_impl<std::unordered_map<Key, Hash, KeyEqual, Allocator>>; };
struct impl_map<std::unordered_map<Key, Hash, KeyEqual, Allocator>> {
using type = map_impl<std::unordered_map<Key, Hash, KeyEqual, Allocator>>;
};
template <class Key, class Hash, class KeyEqual, class Allocator>
struct impl_map<std::unordered_multimap<Key, Hash, KeyEqual, Allocator>> { using type = map_impl<std::unordered_multimap<Key, Hash, KeyEqual, Allocator>>; };
struct impl_map<std::unordered_multimap<Key, Hash, KeyEqual, Allocator>> {
using type = map_impl<std::unordered_multimap<Key, Hash, KeyEqual, Allocator>>;
};
template <class T, class Allocator>
struct impl_map<std::vector<T, Allocator>> { using type = vector_impl<std::vector<T, Allocator>>; };
struct impl_map<std::vector<T, Allocator>> {
using type = vector_impl<std::vector<T, Allocator>>;
};
template <class T, std::size_t N>
struct impl_map<std::array<T, N>> { using type = array_impl<std::array<T, N>>; };
struct impl_map<std::array<T, N>> {
using type = array_impl<std::array<T, N>>;
};
template <class T, class Allocator>
struct impl_map<std::list<T, Allocator>> { using type = list_impl<std::list<T, Allocator>>; };
struct impl_map<std::list<T, Allocator>> {
using type = list_impl<std::list<T, Allocator>>;
};
template <class T, class Allocator>
struct impl_map<std::deque<T, Allocator>> { using type = list_impl<std::deque<T, Allocator>>; };
struct impl_map<std::deque<T, Allocator>> {
using type = list_impl<std::deque<T, Allocator>>;
};
//---------------------------------------------------
@@ -415,6 +461,7 @@ template <class T>
class wrapper<result<T>> {
public:
using response_type = result<T>;
private:
response_type* result_;
typename impl_map<T>::type impl_;
@@ -427,15 +474,18 @@ private:
case resp3::type::null:
case resp3::type::simple_error:
case resp3::type::blob_error:
*result_ = error{nd.data_type, {std::cbegin(nd.value), std::cend(nd.value)}};
*result_ = error{
nd.data_type,
{std::cbegin(nd.value), std::cend(nd.value)}
};
return true;
default:
return false;
default: return false;
}
}
public:
explicit wrapper(response_type* t = nullptr) : result_(t)
explicit wrapper(response_type* t = nullptr)
: result_(t)
{
if (result_) {
result_->value() = T{};
@@ -443,8 +493,11 @@ public:
}
}
void on_init() { impl_.on_init(); }
void on_done() { impl_.on_done(); }
template <class String>
void operator()(resp3::basic_node<String> const& nd, system::error_code& ec)
void on_node(resp3::basic_node<String> const& nd, system::error_code& ec)
{
BOOST_ASSERT_MSG(!!result_, "Unexpected null pointer");
@@ -455,7 +508,7 @@ public:
return;
BOOST_ASSERT(result_);
impl_(result_->value(), nd, ec);
impl_.on_node(result_->value(), nd, ec);
}
};
@@ -475,21 +528,25 @@ private:
switch (nd.data_type) {
case resp3::type::blob_error:
case resp3::type::simple_error:
*result_ = error{nd.data_type, {std::cbegin(nd.value), std::cend(nd.value)}};
*result_ = error{
nd.data_type,
{std::cbegin(nd.value), std::cend(nd.value)}
};
return true;
default:
return false;
default: return false;
}
}
public:
explicit wrapper(response_type* o = nullptr) : result_(o) {}
explicit wrapper(response_type* o = nullptr)
: result_(o)
{ }
void on_init() { impl_.on_init(); }
void on_done() { impl_.on_done(); }
template <class String>
void
operator()(
resp3::basic_node<String> const& nd,
system::error_code& ec)
void on_node(resp3::basic_node<String> const& nd, system::error_code& ec)
{
BOOST_ASSERT_MSG(!!result_, "Unexpected null pointer");
@@ -503,14 +560,14 @@ public:
return;
if (!result_->value().has_value()) {
result_->value() = T{};
impl_.on_value_available(result_->value().value());
result_->value() = T{};
impl_.on_value_available(result_->value().value());
}
impl_(result_->value().value(), nd, ec);
impl_.on_node(result_->value().value(), nd, ec);
}
};
} // boost::redis::adapter::detail
} // namespace boost::redis::adapter::detail
#endif // BOOST_REDIS_ADAPTER_ADAPTERS_HPP
#endif // BOOST_REDIS_ADAPTER_ADAPTERS_HPP

View File

@@ -1,4 +1,4 @@
/* Copyright (c) 2018-2024 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2025 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
@@ -7,37 +7,20 @@
#ifndef BOOST_REDIS_ADAPTER_DETAIL_RESPONSE_TRAITS_HPP
#define BOOST_REDIS_ADAPTER_DETAIL_RESPONSE_TRAITS_HPP
#include <boost/redis/adapter/detail/result_traits.hpp>
#include <boost/redis/ignore.hpp>
#include <boost/redis/resp3/node.hpp>
#include <boost/redis/response.hpp>
#include <boost/redis/adapter/detail/result_traits.hpp>
#include <boost/mp11.hpp>
#include <boost/system.hpp>
#include <tuple>
#include <limits>
#include <string_view>
#include <tuple>
#include <variant>
namespace boost::redis::adapter::detail
{
class ignore_adapter {
public:
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;
case resp3::type::blob_error: ec = redis::error::resp3_blob_error; break;
case resp3::type::null: ec = redis::error::resp3_null; break;
default:;
}
}
[[nodiscard]]
auto get_supported_response_size() const noexcept
{ return static_cast<std::size_t>(-1);}
};
namespace boost::redis::adapter::detail {
template <class Response>
class static_adapter {
@@ -48,47 +31,44 @@ private:
using adapters_array_type = std::array<variant_type, size>;
adapters_array_type adapters_;
std::size_t i_ = 0;
public:
explicit static_adapter(Response& r)
{
assigner<size - 1>::assign(adapters_, r);
}
explicit static_adapter(Response& r) { assigner<size - 1>::assign(adapters_, r); }
[[nodiscard]]
auto get_supported_response_size() const noexcept
{ return size;}
template <class String>
void operator()(std::size_t i, resp3::basic_node<String> const& nd, system::error_code& ec)
void on_init()
{
using std::visit;
// I am usure whether this should be an error or an assertion.
BOOST_ASSERT(i < adapters_.size());
visit([&](auto& arg){arg(nd, ec);}, adapters_.at(i));
visit(
[&](auto& arg) {
arg.on_init();
},
adapters_.at(i_));
}
};
template <class Vector>
class vector_adapter {
private:
using adapter_type = typename result_traits<Vector>::adapter_type;
adapter_type adapter_;
public:
explicit vector_adapter(Vector& v)
: adapter_{internal_adapt(v)}
{ }
[[nodiscard]]
auto
get_supported_response_size() const noexcept
{ return static_cast<std::size_t>(-1);}
void on_done()
{
using std::visit;
visit(
[&](auto& arg) {
arg.on_done();
},
adapters_.at(i_));
i_ += 1;
}
template <class String>
void operator()(std::size_t, resp3::basic_node<String> const& nd, system::error_code& ec)
void on_node(resp3::basic_node<String> const& nd, system::error_code& ec)
{
adapter_(nd, ec);
using std::visit;
// I am usure whether this should be an error or an assertion.
BOOST_ASSERT(i_ < adapters_.size());
visit(
[&](auto& arg) {
arg.on_node(nd, ec);
},
adapters_.at(i_));
}
};
@@ -98,62 +78,35 @@ struct response_traits;
template <>
struct response_traits<ignore_t> {
using response_type = ignore_t;
using adapter_type = detail::ignore_adapter;
using adapter_type = ignore;
static auto adapt(response_type&) noexcept
{ return detail::ignore_adapter{}; }
static auto adapt(response_type&) noexcept { return ignore{}; }
};
template <>
struct response_traits<result<ignore_t>> {
using response_type = result<ignore_t>;
using adapter_type = detail::ignore_adapter;
using adapter_type = ignore;
static auto adapt(response_type&) noexcept
{ return detail::ignore_adapter{}; }
static auto adapt(response_type&) noexcept { return ignore{}; }
};
template <class String, class Allocator>
struct response_traits<result<std::vector<resp3::basic_node<String>, Allocator>>> {
using response_type = result<std::vector<resp3::basic_node<String>, Allocator>>;
using adapter_type = vector_adapter<response_type>;
using adapter_type = general_aggregate<response_type>;
static auto adapt(response_type& v) noexcept
{ return adapter_type{v}; }
static auto adapt(response_type& v) noexcept { return adapter_type{&v}; }
};
template <class ...Ts>
template <class... Ts>
struct response_traits<response<Ts...>> {
using response_type = response<Ts...>;
using adapter_type = static_adapter<response_type>;
static auto adapt(response_type& r) noexcept
{ return adapter_type{r}; }
static auto adapt(response_type& r) noexcept { return adapter_type{r}; }
};
template <class Adapter>
class wrapper {
public:
explicit wrapper(Adapter adapter) : adapter_{adapter} {}
} // namespace boost::redis::adapter::detail
template <class String>
void operator()(resp3::basic_node<String> const& nd, system::error_code& ec)
{ return adapter_(0, nd, ec); }
[[nodiscard]]
auto get_supported_response_size() const noexcept
{ return adapter_.get_supported_response_size();}
private:
Adapter adapter_;
};
template <class Adapter>
auto make_adapter_wrapper(Adapter adapter)
{
return wrapper{adapter};
}
} // boost::redis::adapter::detail
#endif // BOOST_REDIS_ADAPTER_DETAIL_RESPONSE_TRAITS_HPP
#endif // BOOST_REDIS_ADAPTER_DETAIL_RESPONSE_TRAITS_HPP

View File

@@ -7,21 +7,21 @@
#ifndef BOOST_REDIS_ADAPTER_RESPONSE_TRAITS_HPP
#define BOOST_REDIS_ADAPTER_RESPONSE_TRAITS_HPP
#include <boost/redis/error.hpp>
#include <boost/redis/resp3/type.hpp>
#include <boost/redis/ignore.hpp>
#include <boost/redis/adapter/detail/adapters.hpp>
#include <boost/redis/adapter/result.hpp>
#include <boost/redis/adapter/ignore.hpp>
#include <boost/redis/adapter/result.hpp>
#include <boost/redis/error.hpp>
#include <boost/redis/ignore.hpp>
#include <boost/redis/resp3/type.hpp>
#include <boost/mp11.hpp>
#include <vector>
#include <tuple>
#include <string_view>
#include <tuple>
#include <variant>
#include <vector>
namespace boost::redis::adapter::detail
{
namespace boost::redis::adapter::detail {
/* Traits class for response objects.
*
@@ -65,27 +65,29 @@ struct result_traits<result<std::vector<resp3::basic_node<String>, Allocator>>>
template <class T>
using adapter_t = typename result_traits<std::decay_t<T>>::adapter_type;
template<class T>
template <class T>
auto internal_adapt(T& t) noexcept
{ return result_traits<std::decay_t<T>>::adapt(t); }
{
return result_traits<std::decay_t<T>>::adapt(t);
}
template <std::size_t N>
struct assigner {
template <class T1, class T2>
static void assign(T1& dest, T2& from)
{
dest[N].template emplace<N>(internal_adapt(std::get<N>(from)));
assigner<N - 1>::assign(dest, from);
}
template <class T1, class T2>
static void assign(T1& dest, T2& from)
{
dest[N].template emplace<N>(internal_adapt(std::get<N>(from)));
assigner<N - 1>::assign(dest, from);
}
};
template <>
struct assigner<0> {
template <class T1, class T2>
static void assign(T1& dest, T2& from)
{
dest[0].template emplace<0>(internal_adapt(std::get<0>(from)));
}
template <class T1, class T2>
static void assign(T1& dest, T2& from)
{
dest[0].template emplace<0>(internal_adapt(std::get<0>(from)));
}
};
template <class Tuple>
@@ -94,13 +96,9 @@ class static_aggregate_adapter;
template <class Tuple>
class static_aggregate_adapter<result<Tuple>> {
private:
using adapters_array_type =
std::array<
mp11::mp_rename<
mp11::mp_transform<
adapter_t, Tuple>,
std::variant>,
std::tuple_size<Tuple>::value>;
using adapters_array_type = std::array<
mp11::mp_rename<mp11::mp_transform<adapter_t, Tuple>, std::variant>,
std::tuple_size<Tuple>::value>;
// Tuple element we are currently on.
std::size_t i_ = 0;
@@ -134,8 +132,32 @@ public:
}
}
void on_init()
{
using std::visit;
for (auto& adapter : adapters_) {
visit(
[&](auto& arg) {
arg.on_init();
},
adapter);
}
}
void on_done()
{
using std::visit;
for (auto& adapter : adapters_) {
visit(
[&](auto& arg) {
arg.on_done();
},
adapter);
}
}
template <class String>
void operator()(resp3::basic_node<String> const& elem, system::error_code& ec)
void on_node(resp3::basic_node<String> const& elem, system::error_code& ec)
{
using std::visit;
@@ -148,19 +170,22 @@ public:
return;
}
visit([&](auto& arg){arg(elem, ec);}, adapters_[i_]);
visit(
[&](auto& arg) {
arg.on_node(elem, ec);
},
adapters_.at(i_));
count(elem);
}
};
template <class... Ts>
struct result_traits<result<std::tuple<Ts...>>>
{
struct result_traits<result<std::tuple<Ts...>>> {
using response_type = result<std::tuple<Ts...>>;
using adapter_type = static_aggregate_adapter<response_type>;
static auto adapt(response_type& r) noexcept { return adapter_type{&r}; }
};
} // boost::redis::adapter::detail
} // namespace boost::redis::adapter::detail
#endif // BOOST_REDIS_ADAPTER_RESPONSE_TRAITS_HPP
#endif // BOOST_REDIS_ADAPTER_RESPONSE_TRAITS_HPP

View File

@@ -1,4 +1,4 @@
/* Copyright (c) 2018-2024 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2025 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
@@ -7,31 +7,32 @@
#ifndef BOOST_REDIS_ADAPTER_IGNORE_HPP
#define BOOST_REDIS_ADAPTER_IGNORE_HPP
#include <boost/redis/resp3/node.hpp>
#include <boost/redis/error.hpp>
#include <boost/redis/resp3/node.hpp>
#include <boost/system/error_code.hpp>
#include <string>
namespace boost::redis::adapter
{
namespace boost::redis::adapter {
/** @brief An adapter that ignores responses
* @ingroup high-level-api
/** @brief An adapter that ignores responses.
*
* RESP3 errors won't be ignored.
*/
struct ignore {
void operator()(resp3::basic_node<std::string_view> const& nd, system::error_code& ec)
void on_init() { }
void on_done() { }
void on_node(resp3::basic_node<std::string_view> const& nd, system::error_code& ec)
{
switch (nd.data_type) {
case resp3::type::simple_error: ec = redis::error::resp3_simple_error; break;
case resp3::type::blob_error: ec = redis::error::resp3_blob_error; break;
case resp3::type::null: ec = redis::error::resp3_null; break;
default:;
case resp3::type::blob_error: ec = redis::error::resp3_blob_error; break;
case resp3::type::null: ec = redis::error::resp3_null; break;
default: ;
}
}
};
} // boost::redis::adapter
} // namespace boost::redis::adapter
#endif // BOOST_REDIS_ADAPTER_IGNORE_HPP
#endif // BOOST_REDIS_ADAPTER_IGNORE_HPP

View File

@@ -8,17 +8,16 @@
#ifndef BOOST_REDIS_ADAPTER_RESULT_HPP
#define BOOST_REDIS_ADAPTER_RESULT_HPP
#include <boost/redis/resp3/type.hpp>
#include <boost/redis/error.hpp>
#include <boost/redis/resp3/type.hpp>
#include <boost/system/result.hpp>
#include <string>
namespace boost::redis::adapter
{
namespace boost::redis::adapter {
/** @brief Stores any resp3 error
* @ingroup high-level-api
*/
/// Stores any resp3 error.
struct error {
/// RESP3 error data type.
resp3::type data_type = resp3::type::invalid;
@@ -44,38 +43,30 @@ inline bool operator==(error const& a, error const& b)
* @param a Left hand side error object.
* @param b Right hand side error object.
*/
inline bool operator!=(error const& a, error const& b)
{
return !(a == b);
}
inline bool operator!=(error const& a, error const& b) { return !(a == b); }
/** @brief Stores response to individual Redis commands
* @ingroup high-level-api
*/
/// Stores response to individual Redis commands.
template <class Value>
using result = system::result<Value, error>;
BOOST_NORETURN inline void
throw_exception_from_error(error const & e, boost::source_location const &)
/**
* @brief Allows using @ref error with `boost::system::result`.
* @param e The error to throw.
* @relates error
*/
BOOST_NORETURN inline void throw_exception_from_error(error const& e, boost::source_location const&)
{
system::error_code ec;
switch (e.data_type) {
case resp3::type::simple_error:
ec = redis::error::resp3_simple_error;
break;
case resp3::type::blob_error:
ec = redis::error::resp3_blob_error;
break;
case resp3::type::null:
ec = redis::error::resp3_null;
break;
default:
BOOST_ASSERT_MSG(false, "Unexpected data type.");
case resp3::type::simple_error: ec = redis::error::resp3_simple_error; break;
case resp3::type::blob_error: ec = redis::error::resp3_blob_error; break;
case resp3::type::null: ec = redis::error::resp3_null; break;
default: BOOST_ASSERT_MSG(false, "Unexpected data type.");
}
throw system::system_error(ec, e.diagnostic);
}
} // boost::redis::adapter
} // namespace boost::redis::adapter
#endif // BOOST_REDIS_ADAPTER_RESULT_HPP
#endif // BOOST_REDIS_ADAPTER_RESULT_HPP

View File

@@ -7,16 +7,14 @@
#ifndef BOOST_REDIS_CONFIG_HPP
#define BOOST_REDIS_CONFIG_HPP
#include <string>
#include <chrono>
#include <limits>
#include <optional>
#include <string>
namespace boost::redis
{
namespace boost::redis {
/** @brief Address of a Redis server
* @ingroup high-level-api
*/
/// Address of a Redis server.
struct address {
/// Redis host.
std::string host = "127.0.0.1";
@@ -24,62 +22,89 @@ struct address {
std::string port = "6379";
};
/** @brief Configure parameters used by the connection classes
* @ingroup high-level-api
*/
/// Configure parameters used by the connection classes.
struct config {
/// Uses SSL instead of a plain connection.
bool use_ssl = false;
/// Address of the Redis server.
/// For TCP connections, hostname and port of the Redis server.
address addr = address{"127.0.0.1", "6379"};
/** @brief Username passed to the
* [HELLO](https://redis.io/commands/hello/) command. If left
* empty `HELLO` will be sent without authentication parameters.
/**
* @brief The UNIX domain socket path where the server is listening.
*
* If non-empty, communication with the server will happen using
* UNIX domain sockets, and @ref addr will be ignored.
* UNIX domain sockets can't be used with SSL: if `unix_socket` is non-empty,
* @ref use_ssl must be `false`.
*/
std::string unix_socket;
/** @brief Username passed to the `HELLO` command.
* If left empty `HELLO` will be sent without authentication parameters.
*/
std::string username = "default";
/** @brief Password passed to the
* [HELLO](https://redis.io/commands/hello/) command. If left
* `HELLO` command. If left
* empty `HELLO` will be sent without authentication parameters.
*/
std::string password;
/// Client name parameter of the [HELLO](https://redis.io/commands/hello/) command.
/// Client name parameter of the `HELLO` command.
std::string clientname = "Boost.Redis";
/// Database that will be passed to the [SELECT](https://redis.io/commands/hello/) command.
/// Database that will be passed to the `SELECT` command.
std::optional<int> database_index = 0;
/// Message used by the health-checker in `boost::redis::connection::async_run`.
/// Message used by the health-checker in @ref boost::redis::basic_connection::async_run.
std::string health_check_id = "Boost.Redis";
/// Logger prefix, see `boost::redis::logger`.
/**
* @brief (Deprecated) Sets the logger prefix, a string printed before log messages.
*
* Setting a prefix in this struct is deprecated. If you need to change how log messages
* look like, please construct a logger object passing a formatting function, and use that
* logger in connection's constructor. This member will be removed in subsequent releases.
*/
std::string log_prefix = "(Boost.Redis) ";
/// Time the resolve operation is allowed to last.
/// Time span that the resolve operation is allowed to elapse.
std::chrono::steady_clock::duration resolve_timeout = std::chrono::seconds{10};
/// Time the connect operation is allowed to last.
/// Time span that the connect operation is allowed to elapse.
std::chrono::steady_clock::duration connect_timeout = std::chrono::seconds{10};
/// Time the SSL handshake operation is allowed to last.
/// Time span that the SSL handshake operation is allowed to elapse.
std::chrono::steady_clock::duration ssl_handshake_timeout = std::chrono::seconds{10};
/** Health checks interval.
*
* To disable health-checks pass zero as duration.
/** @brief Time span between successive health checks.
* Set to zero to disable health-checks pass zero as duration.
*/
std::chrono::steady_clock::duration health_check_interval = std::chrono::seconds{2};
/** @brief Time waited before trying a reconnection.
*
* To disable reconnection pass zero as duration.
/** @brief Time span to wait between successive connection retries.
* Set to zero to disable reconnection.
*/
std::chrono::steady_clock::duration reconnect_wait_interval = std::chrono::seconds{1};
/** @brief Maximum size of the socket read-buffer in bytes.
*
* Sets a limit on how much data is allowed to be read into the
* read buffer. It can be used to prevent DDOS.
*/
std::size_t max_read_size = (std::numeric_limits<std::size_t>::max)();
/** @brief read_buffer_append_size
*
* The size by which the read buffer grows when more space is
* needed. This can help avoiding some memory allocations. Once the
* maximum size is reached no more memory allocations are made
* since the buffer is reused.
*/
std::size_t read_buffer_append_size = 4096;
};
} // boost::redis
} // namespace boost::redis
#endif // BOOST_REDIS_CONFIG_HPP
#endif // BOOST_REDIS_CONFIG_HPP

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,54 @@
/* Copyright (c) 2018-2025 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#ifndef BOOST_REDIS_CONNECTION_LOGGER_HPP
#define BOOST_REDIS_CONNECTION_LOGGER_HPP
#include <boost/redis/detail/reader_fsm.hpp>
#include <boost/redis/logger.hpp>
#include <boost/redis/response.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/system/error_code.hpp>
#include <string_view>
namespace boost::redis::detail {
// Wraps a logger and a string buffer for re-use, and provides
// utility functions to format the log messages that we use.
// The long-term trend will be moving most of this class to finite state
// machines as we write them
class connection_logger {
logger logger_;
std::string msg_;
public:
connection_logger(logger&& logger) noexcept
: logger_(std::move(logger))
{ }
void reset(logger&& logger) { logger_ = std::move(logger); }
void on_resolve(system::error_code const& ec, asio::ip::tcp::resolver::results_type const& res);
void on_connect(system::error_code const& ec, asio::ip::tcp::endpoint const& ep);
void on_connect(system::error_code const& ec, std::string_view unix_socket_ep);
void on_ssl_handshake(system::error_code const& ec);
void on_write(system::error_code const& ec, std::size_t n);
void on_fsm_resume(reader_fsm::action const& action);
void on_hello(system::error_code const& ec, generic_response const& resp);
void log(logger::level lvl, std::string_view msg);
void log(logger::level lvl, std::string_view op, system::error_code const& ec);
void trace(std::string_view message) { log(logger::level::debug, message); }
void trace(std::string_view op, system::error_code const& ec)
{
log(logger::level::debug, op, ec);
}
};
} // namespace boost::redis::detail
#endif // BOOST_REDIS_LOGGER_HPP

View File

@@ -1,82 +0,0 @@
/* Copyright (c) 2018-2024 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#ifndef BOOST_REDIS_CONNECTOR_HPP
#define BOOST_REDIS_CONNECTOR_HPP
#include <boost/redis/detail/helper.hpp>
#include <boost/redis/error.hpp>
#include <boost/asio/compose.hpp>
#include <boost/asio/connect.hpp>
#include <boost/asio/coroutine.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/cancel_after.hpp>
#include <string>
#include <chrono>
namespace boost::redis::detail
{
template <class Connector, class Stream>
struct connect_op {
Connector* ctor_ = nullptr;
Stream* stream = nullptr;
asio::ip::tcp::resolver::results_type const* res_ = nullptr;
asio::coroutine coro{};
template <class Self>
void operator()( Self& self
, system::error_code const& ec = {}
, asio::ip::tcp::endpoint const& ep= {})
{
BOOST_ASIO_CORO_REENTER (coro)
{
BOOST_ASIO_CORO_YIELD
asio::async_connect(*stream, *res_,
[](system::error_code const&, auto const&) { return true; },
asio::cancel_after(ctor_->timeout_, std::move(self)));
ctor_->endpoint_ = ep;
if (ec == asio::error::operation_aborted) {
self.complete(redis::error::connect_timeout);
} else {
self.complete(ec);
}
}
}
};
class connector {
public:
void set_config(config const& cfg)
{ timeout_ = cfg.connect_timeout; }
template <class Stream, class CompletionToken>
auto
async_connect(
Stream& stream,
asio::ip::tcp::resolver::results_type const& res,
CompletionToken&& token)
{
return asio::async_compose
< CompletionToken
, void(system::error_code)
>(connect_op<connector, Stream>{this, &stream, &res}, token);
}
auto const& endpoint() const noexcept { return endpoint_;}
private:
template <class, class> friend struct connect_op;
std::chrono::steady_clock::duration timeout_ = std::chrono::seconds{2};
asio::ip::tcp::endpoint endpoint_;
};
} // boost::redis::detail
#endif // BOOST_REDIS_CONNECTOR_HPP

View File

@@ -0,0 +1,36 @@
//
// Copyright (c) 2025 Marcelo Zimbres Silva (mzimbres@gmail.com),
// Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_REDIS_DETAIL_COROUTINE_HPP
#define BOOST_REDIS_DETAIL_COROUTINE_HPP
// asio::coroutine uses __COUNTER__ internally, which can trigger
// ODR violations if we use them in header-only code. These manifest as
// extremely hard-to-debug bugs only present in release builds.
// Use this instead when doing coroutines in non-template code.
// Adapted from Boost.MySQL.
// Coroutine state is represented as an integer (resume_point_var).
// Every yield gets assigned a unique value (resume_point_id).
// Yielding sets the next resume point, returns, and sets a case label for re-entering.
// Coroutines need to switch on resume_point_var to re-enter.
// Enclosing this in a scope allows placing the macro inside a brace-less for/while loop
// The empty scope after the case label is required because labels can't be at the end of a compound statement
#define BOOST_REDIS_YIELD(resume_point_var, resume_point_id, ...) \
{ \
resume_point_var = resume_point_id; \
return {__VA_ARGS__}; \
case resume_point_id: \
{ \
} \
}
#define BOOST_REDIS_CORO_INITIAL case 0:
#endif

View File

@@ -0,0 +1,72 @@
//
// Copyright (c) 2025 Marcelo Zimbres Silva (mzimbres@gmail.com),
// Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_REDIS_EXEC_FSM_HPP
#define BOOST_REDIS_EXEC_FSM_HPP
#include <boost/redis/detail/multiplexer.hpp>
#include <boost/asio/cancellation_type.hpp>
#include <boost/system/error_code.hpp>
#include <cstddef>
#include <memory>
// Sans-io algorithm for async_exec, as a finite state machine
namespace boost::redis::detail {
// What should we do next?
enum class exec_action_type
{
setup_cancellation, // Set up the cancellation types supported by the composed operation
immediate, // Invoke asio::async_immediate to avoid re-entrancy problems
done, // Call the final handler
notify_writer, // Notify the writer task
wait_for_response, // Wait to be notified
cancel_run, // Cancel the connection's run operation
};
class exec_action {
exec_action_type type_;
system::error_code ec_;
std::size_t bytes_read_;
public:
exec_action(exec_action_type type) noexcept
: type_{type}
{ }
exec_action(system::error_code ec, std::size_t bytes_read = 0u) noexcept
: type_{exec_action_type::done}
, ec_{ec}
, bytes_read_{bytes_read}
{ }
exec_action_type type() const { return type_; }
system::error_code error() const { return ec_; }
std::size_t bytes_read() const { return bytes_read_; }
};
class exec_fsm {
int resume_point_{0};
multiplexer* mpx_{nullptr};
std::shared_ptr<multiplexer::elem> elem_;
public:
exec_fsm(multiplexer& mpx, std::shared_ptr<multiplexer::elem> elem) noexcept
: mpx_(&mpx)
, elem_(std::move(elem))
{ }
exec_action resume(bool connection_is_open, asio::cancellation_type_t cancel_state);
};
} // namespace boost::redis::detail
#endif // BOOST_REDIS_CONNECTOR_HPP

View File

@@ -7,37 +7,37 @@
#ifndef BOOST_REDIS_HEALTH_CHECKER_HPP
#define BOOST_REDIS_HEALTH_CHECKER_HPP
#include <boost/redis/request.hpp>
#include <boost/redis/response.hpp>
#include <boost/redis/operation.hpp>
#include <boost/redis/adapter/any_adapter.hpp>
#include <boost/redis/config.hpp>
#include <boost/redis/detail/connection_logger.hpp>
#include <boost/redis/operation.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/redis/request.hpp>
#include <boost/redis/response.hpp>
#include <boost/asio/compose.hpp>
#include <boost/asio/consign.hpp>
#include <boost/asio/coroutine.hpp>
#include <boost/asio/post.hpp>
#include <memory>
#include <boost/asio/steady_timer.hpp>
#include <chrono>
namespace boost::redis::detail {
template <class HealthChecker, class Connection, class Logger>
template <class HealthChecker, class ConnectionImpl>
class ping_op {
public:
HealthChecker* checker_ = nullptr;
Connection* conn_ = nullptr;
Logger logger_;
ConnectionImpl* conn_ = nullptr;
asio::coroutine coro_{};
template <class Self>
void operator()(Self& self, system::error_code ec = {}, std::size_t = 0)
{
BOOST_ASIO_CORO_REENTER (coro_) for (;;)
BOOST_ASIO_CORO_REENTER(coro_) for (;;)
{
if (checker_->ping_interval_ == std::chrono::seconds::zero()) {
logger_.trace("ping_op (1): timeout disabled.");
conn_->logger_.trace("ping_op (1): timeout disabled.");
BOOST_ASIO_CORO_YIELD
asio::post(std::move(self));
self.complete({});
@@ -45,15 +45,18 @@ public:
}
if (checker_->checker_has_exited_) {
logger_.trace("ping_op (2): checker has exited.");
conn_->logger_.trace("ping_op (2): checker has exited.");
self.complete({});
return;
}
BOOST_ASIO_CORO_YIELD
conn_->async_exec(checker_->req_, any_adapter(checker_->resp_), std::move(self));
conn_->async_exec(
checker_->req_,
any_adapter{checker_->resp_},
std::move(self));
if (ec) {
logger_.trace("ping_op (3)", ec);
conn_->logger_.trace("ping_op (3)", ec);
checker_->wait_timer_.cancel();
self.complete(ec);
return;
@@ -65,7 +68,7 @@ public:
BOOST_ASIO_CORO_YIELD
checker_->ping_timer_.async_wait(std::move(self));
if (ec) {
logger_.trace("ping_op (4)", ec);
conn_->logger_.trace("ping_op (4)", ec);
self.complete(ec);
return;
}
@@ -73,21 +76,20 @@ public:
}
};
template <class HealthChecker, class Connection, class Logger>
template <class HealthChecker, class Connection>
class check_timeout_op {
public:
HealthChecker* checker_ = nullptr;
Connection* conn_ = nullptr;
Logger logger_;
asio::coroutine coro_{};
template <class Self>
void operator()(Self& self, system::error_code ec = {})
{
BOOST_ASIO_CORO_REENTER (coro_) for (;;)
BOOST_ASIO_CORO_REENTER(coro_) for (;;)
{
if (checker_->ping_interval_ == std::chrono::seconds::zero()) {
logger_.trace("check_timeout_op (1): timeout disabled.");
conn_->logger_.trace("check_timeout_op (1): timeout disabled.");
BOOST_ASIO_CORO_YIELD
asio::post(std::move(self));
self.complete({});
@@ -99,20 +101,20 @@ public:
BOOST_ASIO_CORO_YIELD
checker_->wait_timer_.async_wait(std::move(self));
if (ec) {
logger_.trace("check_timeout_op (2)", ec);
conn_->logger_.trace("check_timeout_op (2)", ec);
self.complete(ec);
return;
}
if (checker_->resp_.has_error()) {
// TODO: Log the error.
logger_.trace("check_timeout_op (3): Response error.");
conn_->logger_.trace("check_timeout_op (3): Response error.");
self.complete({});
return;
}
if (checker_->resp_.value().empty()) {
logger_.trace("check_timeout_op (4): pong timeout.");
conn_->logger_.trace("check_timeout_op (4): pong timeout.");
checker_->ping_timer_.cancel();
conn_->cancel(operation::run);
checker_->checker_has_exited_ = true;
@@ -130,11 +132,10 @@ public:
template <class Executor>
class health_checker {
private:
using timer_type =
asio::basic_waitable_timer<
std::chrono::steady_clock,
asio::wait_traits<std::chrono::steady_clock>,
Executor>;
using timer_type = asio::basic_waitable_timer<
std::chrono::steady_clock,
asio::wait_traits<std::chrono::steady_clock>,
Executor>;
public:
health_checker(Executor ex)
@@ -157,28 +158,30 @@ public:
wait_timer_.cancel();
}
template <class Connection, class Logger, class CompletionToken>
auto async_ping(Connection& conn, Logger l, CompletionToken token)
template <class ConnectionImpl, class CompletionToken>
auto async_ping(ConnectionImpl& conn, CompletionToken token)
{
return asio::async_compose
< CompletionToken
, void(system::error_code)
>(ping_op<health_checker, Connection, Logger>{this, &conn, l}, token, conn, ping_timer_);
return asio::async_compose<CompletionToken, void(system::error_code)>(
ping_op<health_checker, ConnectionImpl>{this, &conn},
token,
conn,
ping_timer_);
}
template <class Connection, class Logger, class CompletionToken>
auto async_check_timeout(Connection& conn, Logger l, CompletionToken token)
template <class Connection, class CompletionToken>
auto async_check_timeout(Connection& conn, CompletionToken token)
{
checker_has_exited_ = false;
return asio::async_compose
< CompletionToken
, void(system::error_code)
>(check_timeout_op<health_checker, Connection, Logger>{this, &conn, l}, token, conn, wait_timer_);
return asio::async_compose<CompletionToken, void(system::error_code)>(
check_timeout_op<health_checker, Connection>{this, &conn},
token,
conn,
wait_timer_);
}
private:
template <class, class, class> friend class ping_op;
template <class, class, class> friend class check_timeout_op;
template <class, class> friend class ping_op;
template <class, class> friend class check_timeout_op;
timer_type ping_timer_;
timer_type wait_timer_;
@@ -188,6 +191,6 @@ private:
bool checker_has_exited_ = false;
};
} // boost::redis::detail
} // namespace boost::redis::detail
#endif // BOOST_REDIS_HEALTH_CHECKER_HPP
#endif // BOOST_REDIS_HEALTH_CHECKER_HPP

View File

@@ -9,8 +9,7 @@
#include <boost/asio/cancellation_type.hpp>
namespace boost::redis::detail
{
namespace boost::redis::detail {
template <class T>
auto is_cancelled(T const& self)
@@ -18,20 +17,18 @@ auto is_cancelled(T const& self)
return self.get_cancellation_state().cancelled() != asio::cancellation_type_t::none;
}
#define BOOST_REDIS_CHECK_OP0(X)\
if (ec || redis::detail::is_cancelled(self)) {\
X\
self.complete(!!ec ? ec : asio::error::operation_aborted);\
return;\
#define BOOST_REDIS_CHECK_OP0(X) \
if (ec || redis::detail::is_cancelled(self)) { \
X self.complete(!!ec ? ec : asio::error::operation_aborted); \
return; \
}
#define BOOST_REDIS_CHECK_OP1(X)\
if (ec || redis::detail::is_cancelled(self)) {\
X\
self.complete(!!ec ? ec : asio::error::operation_aborted, {});\
return;\
#define BOOST_REDIS_CHECK_OP1(X) \
if (ec || redis::detail::is_cancelled(self)) { \
X self.complete(!!ec ? ec : asio::error::operation_aborted, {}); \
return; \
}
} // boost::redis::detail
} // namespace boost::redis::detail
#endif // BOOST_REDIS_HELPER_HPP
#endif // BOOST_REDIS_HELPER_HPP

View File

@@ -0,0 +1,198 @@
/* Copyright (c) 2018-2025 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#ifndef BOOST_REDIS_MULTIPLEXER_HPP
#define BOOST_REDIS_MULTIPLEXER_HPP
#include <boost/redis/adapter/adapt.hpp>
#include <boost/redis/adapter/any_adapter.hpp>
#include <boost/redis/detail/read_buffer.hpp>
#include <boost/redis/resp3/node.hpp>
#include <boost/redis/resp3/parser.hpp>
#include <boost/redis/resp3/type.hpp>
#include <boost/redis/usage.hpp>
#include <boost/system/error_code.hpp>
#include <algorithm>
#include <deque>
#include <functional>
#include <memory>
#include <optional>
#include <string_view>
#include <utility>
namespace boost::redis {
class request;
namespace detail {
using tribool = std::optional<bool>;
class multiplexer {
public:
struct elem {
public:
explicit elem(request const& req, any_adapter adapter);
void set_done_callback(std::function<void()> f) noexcept { done_ = std::move(f); };
auto notify_done() noexcept -> void
{
status_ = status::done;
done_();
}
auto notify_error(system::error_code ec) noexcept -> void;
[[nodiscard]]
auto is_waiting() const noexcept
{
return status_ == status::waiting;
}
[[nodiscard]]
auto is_written() const noexcept
{
return status_ == status::written;
}
[[nodiscard]]
auto is_staged() const noexcept
{
return status_ == status::staged;
}
[[nodiscard]]
bool is_done() const noexcept
{
return status_ == status::done;
}
void mark_written() noexcept { status_ = status::written; }
void mark_staged() noexcept { status_ = status::staged; }
void mark_waiting() noexcept { status_ = status::waiting; }
auto get_error() const -> system::error_code const& { return ec_; }
auto get_request() const -> request const& { return *req_; }
auto get_read_size() const -> std::size_t { return read_size_; }
auto get_remaining_responses() const -> std::size_t { return remaining_responses_; }
auto commit_response(std::size_t read_size) -> void;
auto get_adapter() -> any_adapter& { return adapter_; }
private:
enum class status
{
waiting, // the request hasn't been written yet
staged, // we've issued the write for this request, but it hasn't finished yet
written, // the request has been written successfully
done, // the request has completed and the done callback has been invoked
};
request const* req_;
any_adapter adapter_;
std::function<void()> done_;
// Contains the number of commands that haven't been read yet.
std::size_t remaining_responses_;
status status_;
system::error_code ec_;
std::size_t read_size_;
};
auto remove(std::shared_ptr<elem> const& ptr) -> bool;
[[nodiscard]]
auto prepare_write() -> std::size_t;
// Returns the number of requests that have been released because
// they don't have a response e.g. SUBSCRIBE.
auto commit_write() -> std::size_t;
// If the tribool contains no value more data is needed, otherwise
// if the value is true the message consumed is a push.
[[nodiscard]]
auto consume_next(std::string_view data, system::error_code& ec)
-> std::pair<tribool, std::size_t>;
auto add(std::shared_ptr<elem> const& ptr) -> void;
auto reset() -> void;
[[nodiscard]]
auto const& get_parser() const noexcept
{
return parser_;
}
//[[nodiscard]]
auto cancel_waiting() -> std::size_t;
//[[nodiscard]]
auto cancel_on_conn_lost() -> std::size_t;
[[nodiscard]]
auto get_cancel_run_state() const noexcept -> bool
{
return cancel_run_called_;
}
[[nodiscard]]
auto get_write_buffer() noexcept -> std::string_view
{
return std::string_view{write_buffer_};
}
void set_receive_adapter(any_adapter adapter);
[[nodiscard]]
auto get_usage() const noexcept -> usage
{
return usage_;
}
[[nodiscard]]
auto is_writing() const noexcept -> bool;
private:
[[nodiscard]]
auto is_waiting_response() const noexcept -> bool;
void commit_usage(bool is_push, std::size_t size);
[[nodiscard]]
auto is_next_push(std::string_view data) const noexcept -> bool;
// Releases the number of requests that have been released.
[[nodiscard]]
auto release_push_requests() -> std::size_t;
[[nodiscard]]
tribool consume_next_impl(std::string_view data, system::error_code& ec);
std::string write_buffer_;
std::deque<std::shared_ptr<elem>> reqs_;
resp3::parser parser_{};
bool on_push_ = false;
bool cancel_run_called_ = false;
usage usage_;
any_adapter receive_adapter_;
};
auto make_elem(request const& req, any_adapter adapter) -> std::shared_ptr<multiplexer::elem>;
} // namespace detail
} // namespace boost::redis
#endif // BOOST_REDIS_MULTIPLEXER_HPP

View File

@@ -0,0 +1,65 @@
/* Copyright (c) 2018-2025 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#ifndef BOOST_REDIS_READ_BUFFER_HPP
#define BOOST_REDIS_READ_BUFFER_HPP
#include <boost/core/span.hpp>
#include <boost/system/error_code.hpp>
#include <cstddef>
#include <string_view>
#include <utility>
#include <vector>
namespace boost::redis::detail {
class read_buffer {
public:
using span_type = span<char>;
// See config.hpp for the meaning of these parameters.
struct config {
std::size_t read_buffer_append_size = 4096u;
std::size_t max_read_size = static_cast<std::size_t>(-1);
};
[[nodiscard]]
auto prepare_append() -> system::error_code;
[[nodiscard]]
auto get_append_buffer() noexcept -> span_type;
void commit_append(std::size_t read_size);
[[nodiscard]]
auto get_committed_buffer() const noexcept -> std::string_view;
[[nodiscard]]
auto get_committed_size() const noexcept -> std::size_t;
void clear();
// Consume committed data.
auto consume_committed(std::size_t size) -> std::size_t;
void reserve(std::size_t n);
friend bool operator==(read_buffer const& lhs, read_buffer const& rhs);
friend bool operator!=(read_buffer const& lhs, read_buffer const& rhs);
void set_config(config const& cfg) noexcept { cfg_ = cfg; };
private:
config cfg_ = config{};
std::vector<char> buffer_;
std::size_t append_buf_begin_ = 0;
};
} // namespace boost::redis::detail
#endif // BOOST_REDIS_READ_BUFFER_HPP

View File

@@ -0,0 +1,56 @@
/* Copyright (c) 2018-2025 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#ifndef BOOST_REDIS_READER_FSM_HPP
#define BOOST_REDIS_READER_FSM_HPP
#include <boost/redis/detail/multiplexer.hpp>
#include <boost/asio/cancellation_type.hpp>
#include <boost/system/error_code.hpp>
#include <cstddef>
namespace boost::redis::detail {
class read_buffer;
class reader_fsm {
public:
struct action {
enum class type
{
setup_cancellation,
append_some,
needs_more,
notify_push_receiver,
cancel_run,
done,
};
type type_ = type::setup_cancellation;
std::size_t push_size_ = 0u;
system::error_code ec_ = {};
};
explicit reader_fsm(read_buffer& rbuf, multiplexer& mpx) noexcept;
action resume(
std::size_t bytes_read,
system::error_code ec,
asio::cancellation_type_t /*cancel_state*/);
private:
int resume_point_{0};
read_buffer* read_buffer_ = nullptr;
action action_after_resume_;
action::type next_read_type_ = action::type::append_some;
multiplexer* mpx_ = nullptr;
std::pair<tribool, std::size_t> res_{std::make_pair(std::nullopt, 0)};
};
} // namespace boost::redis::detail
#endif // BOOST_REDIS_READER_FSM_HPP

View File

@@ -0,0 +1,291 @@
/* Copyright (c) 2018-2025 Marcelo Zimbres Silva (mzimbres@gmail.com),
* Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#ifndef BOOST_REDIS_REDIS_STREAM_HPP
#define BOOST_REDIS_REDIS_STREAM_HPP
#include <boost/redis/config.hpp>
#include <boost/redis/detail/connection_logger.hpp>
#include <boost/redis/error.hpp>
#include <boost/asio/basic_waitable_timer.hpp>
#include <boost/asio/cancel_after.hpp>
#include <boost/asio/compose.hpp>
#include <boost/asio/connect.hpp>
#include <boost/asio/coroutine.hpp>
#include <boost/asio/ip/basic_resolver.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/local/stream_protocol.hpp>
#include <boost/asio/ssl/context.hpp>
#include <boost/asio/ssl/stream.hpp>
#include <boost/asio/ssl/stream_base.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/system/error_code.hpp>
#include <utility>
namespace boost {
namespace redis {
namespace detail {
// What transport is redis_stream using?
enum class transport_type
{
tcp, // plaintext TCP
tcp_tls, // TLS over TCP
unix_socket, // UNIX domain sockets
};
template <class Executor>
class redis_stream {
asio::ssl::context ssl_ctx_;
asio::ip::basic_resolver<asio::ip::tcp, Executor> resolv_;
asio::ssl::stream<asio::basic_stream_socket<asio::ip::tcp, Executor>> stream_;
#ifdef BOOST_ASIO_HAS_LOCAL_SOCKETS
asio::basic_stream_socket<asio::local::stream_protocol, Executor> unix_socket_;
#endif
typename asio::steady_timer::template rebind_executor<Executor>::other timer_;
transport_type transport_{transport_type::tcp};
bool ssl_stream_used_{false};
void reset_stream() { stream_ = {resolv_.get_executor(), ssl_ctx_}; }
static transport_type transport_from_config(const config& cfg)
{
if (cfg.unix_socket.empty()) {
if (cfg.use_ssl) {
return transport_type::tcp_tls;
} else {
return transport_type::tcp;
}
} else {
BOOST_ASSERT(!cfg.use_ssl);
return transport_type::unix_socket;
}
}
struct connect_op {
redis_stream& obj;
const config* cfg;
connection_logger* lgr;
asio::coroutine coro{};
// This overload will be used for connects. We only need the endpoint
// for logging, so log it and call the coroutine
template <class Self>
void operator()(
Self& self,
system::error_code ec,
const asio::ip::tcp::endpoint& selected_endpoint)
{
lgr->on_connect(ec, selected_endpoint);
(*this)(self, ec);
}
template <class Self>
void operator()(
Self& self,
system::error_code ec = {},
asio::ip::tcp::resolver::results_type resolver_results = {})
{
BOOST_ASIO_CORO_REENTER(coro)
{
// Record the transport that we will be using
obj.transport_ = transport_from_config(*cfg);
if (obj.transport_ == transport_type::unix_socket) {
#ifdef BOOST_ASIO_HAS_LOCAL_SOCKETS
// Directly connect to the socket
BOOST_ASIO_CORO_YIELD
obj.unix_socket_.async_connect(
cfg->unix_socket,
asio::cancel_after(obj.timer_, cfg->connect_timeout, std::move(self)));
// Log it
lgr->on_connect(ec, cfg->unix_socket);
// If this failed, we can't continue
if (ec) {
self.complete(ec == asio::error::operation_aborted ? error::connect_timeout : ec);
return;
}
#else
BOOST_ASSERT(false);
#endif
} else {
// ssl::stream doesn't support being re-used. If we're to use
// TLS and the stream has been used, re-create it.
// Must be done before anything else is done on the stream
if (cfg->use_ssl && obj.ssl_stream_used_)
obj.reset_stream();
BOOST_ASIO_CORO_YIELD
obj.resolv_.async_resolve(
cfg->addr.host,
cfg->addr.port,
asio::cancel_after(obj.timer_, cfg->resolve_timeout, std::move(self)));
// Log it
lgr->on_resolve(ec, resolver_results);
// If this failed, we can't continue
if (ec) {
self.complete(ec == asio::error::operation_aborted ? error::resolve_timeout : ec);
return;
}
// Connect to the address that the resolver provided us
BOOST_ASIO_CORO_YIELD
asio::async_connect(
obj.stream_.next_layer(),
std::move(resolver_results),
asio::cancel_after(obj.timer_, cfg->connect_timeout, std::move(self)));
// Note: logging is performed in the specialized operator() function.
// If this failed, we can't continue
if (ec) {
self.complete(ec == asio::error::operation_aborted ? error::connect_timeout : ec);
return;
}
if (cfg->use_ssl) {
// Mark the SSL stream as used
obj.ssl_stream_used_ = true;
// If we were configured to use TLS, perform the handshake
BOOST_ASIO_CORO_YIELD
obj.stream_.async_handshake(
asio::ssl::stream_base::client,
asio::cancel_after(obj.timer_, cfg->ssl_handshake_timeout, std::move(self)));
lgr->on_ssl_handshake(ec);
// If this failed, we can't continue
if (ec) {
self.complete(
ec == asio::error::operation_aborted ? error::ssl_handshake_timeout : ec);
return;
}
}
}
// Done
self.complete(system::error_code());
}
}
};
public:
explicit redis_stream(Executor ex, asio::ssl::context&& ssl_ctx)
: ssl_ctx_{std::move(ssl_ctx)}
, resolv_{ex}
, stream_{ex, ssl_ctx_}
#ifdef BOOST_ASIO_HAS_LOCAL_SOCKETS
, unix_socket_{ex}
#endif
, timer_{std::move(ex)}
{ }
// Executor. Required to satisfy the AsyncStream concept
using executor_type = Executor;
executor_type get_executor() noexcept { return resolv_.get_executor(); }
// Accessors
const auto& get_ssl_context() const noexcept { return ssl_ctx_; }
bool is_open() const
{
#ifdef BOOST_ASIO_HAS_LOCAL_SOCKETS
if (transport_ == transport_type::unix_socket)
return unix_socket_.is_open();
#endif
return stream_.next_layer().is_open();
}
auto& next_layer() { return stream_; }
const auto& next_layer() const { return stream_; }
// I/O
template <class CompletionToken>
auto async_connect(const config* cfg, connection_logger* l, CompletionToken&& token)
{
return asio::async_compose<CompletionToken, void(system::error_code)>(
connect_op{*this, cfg, l},
token);
}
// These functions should only be used with callbacks (e.g. within async_compose function bodies)
template <class ConstBufferSequence, class CompletionToken>
void async_write_some(const ConstBufferSequence& buffers, CompletionToken&& token)
{
switch (transport_) {
case transport_type::tcp:
{
stream_.next_layer().async_write_some(buffers, std::forward<CompletionToken>(token));
break;
}
case transport_type::tcp_tls:
{
stream_.async_write_some(buffers, std::forward<CompletionToken>(token));
break;
}
#ifdef BOOST_ASIO_HAS_LOCAL_SOCKETS
case transport_type::unix_socket:
{
unix_socket_.async_write_some(buffers, std::forward<CompletionToken>(token));
break;
}
#endif
default: BOOST_ASSERT(false);
}
}
template <class MutableBufferSequence, class CompletionToken>
void async_read_some(const MutableBufferSequence& buffers, CompletionToken&& token)
{
switch (transport_) {
case transport_type::tcp:
{
return stream_.next_layer().async_read_some(
buffers,
std::forward<CompletionToken>(token));
break;
}
case transport_type::tcp_tls:
{
return stream_.async_read_some(buffers, std::forward<CompletionToken>(token));
break;
}
#ifdef BOOST_ASIO_HAS_LOCAL_SOCKETS
case transport_type::unix_socket:
{
unix_socket_.async_read_some(buffers, std::forward<CompletionToken>(token));
break;
}
#endif
default: BOOST_ASSERT(false);
}
}
// Cleanup
void cancel_resolve() { resolv_.cancel(); }
void close()
{
system::error_code ec;
if (stream_.next_layer().is_open())
stream_.next_layer().close(ec);
#ifdef BOOST_ASIO_HAS_LOCAL_SOCKETS
if (unix_socket_.is_open())
unix_socket_.close(ec);
#endif
}
};
} // namespace detail
} // namespace redis
} // namespace boost
#endif

View File

@@ -1,89 +0,0 @@
/* Copyright (c) 2018-2024 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#ifndef BOOST_REDIS_RESOLVER_HPP
#define BOOST_REDIS_RESOLVER_HPP
#include <boost/redis/config.hpp>
#include <boost/redis/error.hpp>
#include <boost/asio/compose.hpp>
#include <boost/asio/coroutine.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/cancel_after.hpp>
#include <string>
#include <chrono>
namespace boost::redis::detail
{
template <class Resolver>
struct resolve_op {
Resolver* resv_ = nullptr;
asio::coroutine coro{};
template <class Self>
void operator()( Self& self
, system::error_code ec = {}
, asio::ip::tcp::resolver::results_type res = {})
{
BOOST_ASIO_CORO_REENTER (coro)
{
BOOST_ASIO_CORO_YIELD
resv_->resv_.async_resolve(
resv_->addr_.host,
resv_->addr_.port,
asio::cancel_after(resv_->timeout_, std::move(self)));
resv_->results_ = res;
if (ec == asio::error::operation_aborted) {
self.complete(error::resolve_timeout);
} else {
self.complete(ec);
}
}
}
};
template <class Executor>
class resolver {
public:
resolver(Executor ex) : resv_{ex} {}
template <class CompletionToken>
auto async_resolve(CompletionToken&& token)
{
return asio::async_compose
< CompletionToken
, void(system::error_code)
>(resolve_op<resolver>{this}, token, resv_);
}
void cancel()
{ resv_.cancel(); }
auto const& results() const noexcept
{ return results_;}
void set_config(config const& cfg)
{
addr_ = cfg.addr;
timeout_ = cfg.resolve_timeout;
}
private:
using resolver_type = asio::ip::basic_resolver<asio::ip::tcp, Executor>;
template <class> friend struct resolve_op;
resolver_type resv_;
address addr_;
std::chrono::steady_clock::duration timeout_;
asio::ip::tcp::resolver::results_type results_;
};
} // boost::redis::detail
#endif // BOOST_REDIS_RESOLVER_HPP

View File

@@ -8,43 +8,44 @@
#define BOOST_REDIS_RUNNER_HPP
#include <boost/redis/config.hpp>
#include <boost/redis/detail/connection_logger.hpp>
#include <boost/redis/error.hpp>
#include <boost/redis/operation.hpp>
#include <boost/redis/request.hpp>
#include <boost/redis/response.hpp>
#include <boost/redis/error.hpp>
#include <boost/redis/logger.hpp>
#include <boost/redis/operation.hpp>
#include <boost/asio/compose.hpp>
#include <boost/asio/coroutine.hpp>
//#include <boost/asio/ip/tcp.hpp>
#include <string>
#include <memory>
#include <chrono>
namespace boost::redis::detail
{
#include <string>
namespace boost::redis::detail {
void push_hello(config const& cfg, request& req);
// TODO: Can we avoid this whole function whose only purpose is to
// check for an error in the hello response and complete with an error
// so that the parallel group that starts it can exit?
template <class Handshaker, class Connection, class Logger>
template <class Handshaker, class ConnectionImpl>
struct hello_op {
Handshaker* handshaker_ = nullptr;
Connection* conn_ = nullptr;
Logger logger_;
ConnectionImpl* conn_ = nullptr;
asio::coroutine coro_{};
template <class Self>
void operator()(Self& self, system::error_code ec = {}, std::size_t = 0)
{
BOOST_ASIO_CORO_REENTER (coro_)
BOOST_ASIO_CORO_REENTER(coro_)
{
handshaker_->add_hello();
BOOST_ASIO_CORO_YIELD
conn_->async_exec(handshaker_->hello_req_, any_adapter(handshaker_->hello_resp_), std::move(self));
logger_.on_hello(ec, handshaker_->hello_resp_);
conn_->async_exec(
handshaker_->hello_req_,
any_adapter{handshaker_->hello_resp_},
std::move(self));
conn_->logger_.on_hello(ec, handshaker_->hello_resp_);
if (ec) {
conn_->cancel(operation::run);
@@ -66,20 +67,19 @@ struct hello_op {
template <class Executor>
class resp3_handshaker {
public:
void set_config(config const& cfg)
{ cfg_ = cfg; }
void set_config(config const& cfg) { cfg_ = cfg; }
template <class Connection, class Logger, class CompletionToken>
auto async_hello(Connection& conn, Logger l, CompletionToken token)
template <class ConnectionImpl, class CompletionToken>
auto async_hello(ConnectionImpl& conn, CompletionToken token)
{
return asio::async_compose
< CompletionToken
, void(system::error_code)
>(hello_op<resp3_handshaker, Connection, Logger>{this, &conn, l}, token, conn);
return asio::async_compose<CompletionToken, void(system::error_code)>(
hello_op<resp3_handshaker, ConnectionImpl>{this, &conn},
token,
conn);
}
private:
template <class, class, class> friend struct hello_op;
template <class, class> friend struct hello_op;
void add_hello()
{
@@ -94,12 +94,11 @@ private:
if (!hello_resp_.has_value())
return true;
auto f = [](auto const& e)
{
auto f = [](auto const& e) {
switch (e.data_type) {
case resp3::type::simple_error:
case resp3::type::blob_error: return true;
default: return false;
case resp3::type::blob_error: return true;
default: return false;
}
};
@@ -111,6 +110,6 @@ private:
config cfg_;
};
} // boost::redis::detail
} // namespace boost::redis::detail
#endif // BOOST_REDIS_RUNNER_HPP
#endif // BOOST_REDIS_RUNNER_HPP

View File

@@ -1,270 +0,0 @@
/* Copyright (c) 2018-2024 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#ifndef BOOST_REDIS_RUNNER_HPP
#define BOOST_REDIS_RUNNER_HPP
#include <boost/redis/adapter/any_adapter.hpp>
#include <boost/redis/config.hpp>
#include <boost/redis/request.hpp>
#include <boost/redis/response.hpp>
#include <boost/redis/detail/helper.hpp>
#include <boost/redis/error.hpp>
#include <boost/redis/logger.hpp>
#include <boost/redis/operation.hpp>
#include <boost/asio/compose.hpp>
#include <boost/asio/coroutine.hpp>
#include <boost/asio/experimental/parallel_group.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/asio/prepend.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/asio/cancel_after.hpp>
#include <string>
#include <memory>
#include <chrono>
namespace boost::redis::detail
{
void push_hello(config const& cfg, request& req);
// TODO: Can we avoid this whole function whose only purpose is to
// check for an error in the hello response and complete with an error
// so that the parallel group that starts it can exit?
template <class Runner, class Connection, class Logger>
struct hello_op {
Runner* runner_ = nullptr;
Connection* conn_ = nullptr;
Logger logger_;
asio::coroutine coro_{};
template <class Self>
void operator()(Self& self, system::error_code ec = {}, std::size_t = 0)
{
BOOST_ASIO_CORO_REENTER (coro_)
{
runner_->add_hello();
BOOST_ASIO_CORO_YIELD
conn_->async_exec(runner_->hello_req_, any_adapter(runner_->hello_resp_), std::move(self));
logger_.on_hello(ec, runner_->hello_resp_);
if (ec) {
conn_->cancel(operation::run);
self.complete(ec);
return;
}
if (runner_->has_error_in_response()) {
conn_->cancel(operation::run);
self.complete(error::resp3_hello);
return;
}
self.complete({});
}
}
};
template <class Runner, class Connection, class Logger>
class runner_op {
private:
Runner* runner_ = nullptr;
Connection* conn_ = nullptr;
Logger logger_;
asio::coroutine coro_{};
using order_t = std::array<std::size_t, 5>;
public:
runner_op(Runner* runner, Connection* conn, Logger l)
: runner_{runner}
, conn_{conn}
, logger_{l}
{}
template <class Self>
void operator()( Self& self
, order_t order = {}
, system::error_code ec0 = {}
, system::error_code ec1 = {}
, system::error_code ec2 = {}
, system::error_code ec3 = {}
, system::error_code ec4 = {})
{
BOOST_ASIO_CORO_REENTER (coro_) for (;;)
{
BOOST_ASIO_CORO_YIELD
conn_->resv_.async_resolve(asio::prepend(std::move(self), order_t {}));
logger_.on_resolve(ec0, conn_->resv_.results());
if (ec0) {
self.complete(ec0);
return;
}
BOOST_ASIO_CORO_YIELD
conn_->ctor_.async_connect(
conn_->next_layer().next_layer(),
conn_->resv_.results(),
asio::prepend(std::move(self), order_t {}));
logger_.on_connect(ec0, conn_->ctor_.endpoint());
if (ec0) {
self.complete(ec0);
return;
}
if (conn_->use_ssl()) {
BOOST_ASIO_CORO_YIELD
conn_->next_layer().async_handshake(
asio::ssl::stream_base::client,
asio::prepend(
asio::cancel_after(
runner_->cfg_.ssl_handshake_timeout,
std::move(self)
),
order_t {}
)
);
logger_.on_ssl_handshake(ec0);
if (ec0) {
self.complete(ec0);
return;
}
}
conn_->reset();
// Note: Order is important here because the writer might
// trigger an async_write before the async_hello thereby
// causing an authentication problem.
BOOST_ASIO_CORO_YIELD
asio::experimental::make_parallel_group(
[this](auto token) { return runner_->async_hello(*conn_, logger_, token); },
[this](auto token) { return conn_->health_checker_.async_ping(*conn_, logger_, token); },
[this](auto token) { return conn_->health_checker_.async_check_timeout(*conn_, logger_, token);},
[this](auto token) { return conn_->reader(logger_, token);},
[this](auto token) { return conn_->writer(logger_, token);}
).async_wait(
asio::experimental::wait_for_one_error(),
std::move(self));
if (order[0] == 0 && !!ec0) {
self.complete(ec0);
return;
}
if (order[0] == 2 && ec2 == error::pong_timeout) {
self.complete(ec1);
return;
}
// The receive operation must be cancelled because channel
// subscription does not survive a reconnection but requires
// re-subscription.
conn_->cancel(operation::receive);
if (!conn_->will_reconnect()) {
conn_->cancel(operation::reconnection);
self.complete(ec3);
return;
}
// It is safe to use the writer timer here because we are not
// connected.
conn_->writer_timer_.expires_after(conn_->cfg_.reconnect_wait_interval);
BOOST_ASIO_CORO_YIELD
conn_->writer_timer_.async_wait(asio::prepend(std::move(self), order_t {}));
if (ec0) {
self.complete(ec0);
return;
}
if (!conn_->will_reconnect()) {
self.complete(asio::error::operation_aborted);
return;
}
conn_->reset_stream();
}
}
};
template <class Executor>
class runner {
public:
runner(Executor ex, config cfg)
: cfg_{cfg}
{ }
void set_config(config const& cfg)
{
cfg_ = cfg;
}
template <class Connection, class Logger, class CompletionToken>
auto async_run(Connection& conn, Logger l, CompletionToken token)
{
return asio::async_compose
< CompletionToken
, void(system::error_code)
>(runner_op<runner, Connection, Logger>{this, &conn, l}, token, conn);
}
private:
template <class, class, class> friend class runner_op;
template <class, class, class> friend struct hello_op;
template <class Connection, class Logger, class CompletionToken>
auto async_hello(Connection& conn, Logger l, CompletionToken token)
{
return asio::async_compose
< CompletionToken
, void(system::error_code)
>(hello_op<runner, Connection, Logger>{this, &conn, l}, token, conn);
}
void add_hello()
{
hello_req_.clear();
if (hello_resp_.has_value())
hello_resp_.value().clear();
push_hello(cfg_, hello_req_);
}
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);
}
request hello_req_;
generic_response hello_resp_;
config cfg_;
};
} // boost::redis::detail
#endif // BOOST_REDIS_RUNNER_HPP

View File

@@ -7,40 +7,39 @@
#ifndef BOOST_REDIS_WRITE_HPP
#define BOOST_REDIS_WRITE_HPP
#include <boost/asio/write.hpp>
#include <boost/redis/request.hpp>
#include <boost/asio/write.hpp>
namespace boost::redis::detail {
/** \brief Writes a request synchronously.
* \ingroup low-level-api
/** @brief Writes a request synchronously.
*
* \param stream Stream to write the request to.
* \param req Request to write.
* @param stream Stream to write the request to.
* @param req Request to write.
*/
template<class SyncWriteStream>
template <class SyncWriteStream>
auto write(SyncWriteStream& stream, request const& req)
{
return asio::write(stream, asio::buffer(req.payload()));
}
template<class SyncWriteStream>
template <class SyncWriteStream>
auto write(SyncWriteStream& stream, request const& req, system::error_code& ec)
{
return asio::write(stream, asio::buffer(req.payload()), ec);
}
/** \brief Writes a request asynchronously.
* \ingroup low-level-api
/** @brief Writes a request asynchronously.
*
* \param stream Stream to write the request to.
* \param req Request to write.
* \param token Asio completion token.
* @param stream Stream to write the request to.
* @param req Request to write.
* @param token Asio completion token.
*/
template<
template <
class AsyncWriteStream,
class CompletionToken = asio::default_completion_token_t<typename AsyncWriteStream::executor_type>
>
class CompletionToken =
asio::default_completion_token_t<typename AsyncWriteStream::executor_type> >
auto async_write(
AsyncWriteStream& stream,
request const& req,
@@ -50,6 +49,6 @@ auto async_write(
return asio::async_write(stream, asio::buffer(req.payload()), token);
}
} // boost::redis::detail
} // namespace boost::redis::detail
#endif // BOOST_REDIS_WRITE_HPP
#endif // BOOST_REDIS_WRITE_HPP

View File

@@ -11,9 +11,7 @@
namespace boost::redis {
/** \brief Generic errors.
* \ingroup high-level-api
*/
/// Generic errors.
enum class error
{
/// Invalid RESP3 type.
@@ -84,22 +82,31 @@ enum class error
/// Resp3 hello command error
resp3_hello,
/// The configuration specified a UNIX socket address, but UNIX sockets are not supported by the system.
unix_sockets_unsupported,
/// The configuration specified UNIX sockets with SSL, which is not supported.
unix_sockets_ssl_unsupported,
/// Reading data from the socket would exceed the maximum size allowed of the read buffer.
exceeds_maximum_read_buffer_size,
};
/** \internal
* \brief Creates a error_code object from an error.
* \param e Error code.
* \ingroup any
/**
* @brief Creates a error_code object from an error.
*
* @param e Error code.
*/
auto make_error_code(error e) -> system::error_code;
} // boost::redis
} // namespace boost::redis
namespace std {
template<>
struct is_error_code_enum<::boost::redis::error> : std::true_type {};
template <>
struct is_error_code_enum<::boost::redis::error> : std::true_type { };
} // std
} // namespace std
#endif // BOOST_REDIS_ERROR_HPP
#endif // BOOST_REDIS_ERROR_HPP

View File

@@ -13,30 +13,27 @@
#include <tuple>
#include <type_traits>
namespace boost::redis
{
namespace boost::redis {
/** @brief Type used to ignore responses.
* @ingroup high-level-api
*
* For example
* For example:
*
* @code
* response<ignore_t, std::string, ignore_t> resp;
* @endcode
*
* will ignore the first and third responses. RESP3 errors won't be
* This will ignore the first and third responses. RESP3 errors won't be
* ignore but will cause `async_exec` to complete with an error.
*/
using ignore_t = std::decay_t<decltype(std::ignore)>;
/** @brief Global ignore object.
* @ingroup high-level-api
*
* Can be used to ignore responses to a request
* Can be used to ignore responses to a request. For example:
*
* @code
* conn->async_exec(req, ignore, ...);
* co_await conn.async_exec(req, ignore);
* @endcode
*
* RESP3 errors won't be ignore but will cause `async_exec` to
@@ -44,6 +41,6 @@ using ignore_t = std::decay_t<decltype(std::ignore)>;
*/
extern ignore_t ignore;
} // boost::redis
} // namespace boost::redis
#endif // BOOST_REDIS_IGNORE_HPP
#endif // BOOST_REDIS_IGNORE_HPP

View File

@@ -1,39 +1,49 @@
/* Copyright (c) 2018-2024 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2025 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/impl/log_to_file.hpp>
#include <cstddef>
#include <cstdio>
#include <string_view>
#include <utility>
namespace boost::redis {
connection::connection(
executor_type ex,
asio::ssl::context ctx,
std::size_t max_read_size)
: impl_{ex, std::move(ctx), max_read_size}
{ }
connection::connection(
asio::io_context& ioc,
asio::ssl::context ctx,
std::size_t max_read_size)
: impl_{ioc.get_executor(), std::move(ctx), max_read_size}
{ }
void
connection::async_run_impl(
config const& cfg,
logger l,
asio::any_completion_handler<void(boost::system::error_code)> token)
logger detail::make_stderr_logger(logger::level lvl, std::string prefix)
{
impl_.async_run(cfg, l, std::move(token));
return logger(lvl, [prefix = std::move(prefix)](logger::level, std::string_view msg) {
log_to_file(stderr, msg, prefix.c_str());
});
}
void
connection::async_exec_impl(
connection::connection(executor_type ex, asio::ssl::context ctx, logger lgr)
: impl_{std::move(ex), std::move(ctx), std::move(lgr)}
{ }
void connection::async_run_impl(
config const& cfg,
logger&& l,
asio::any_completion_handler<void(boost::system::error_code)> token)
{
// Avoid calling the basic_connection::async_run overload taking a logger
// because it generates deprecated messages when building this file
impl_.set_stderr_logger(l.lvl, cfg);
impl_.async_run(cfg, std::move(token));
}
void connection::async_run_impl(
config const& cfg,
asio::any_completion_handler<void(boost::system::error_code)> token)
{
impl_.async_run(cfg, std::move(token));
}
void connection::async_exec_impl(
request const& req,
any_adapter&& adapter,
asio::any_completion_handler<void(boost::system::error_code, std::size_t)> token)
@@ -41,9 +51,6 @@ connection::async_exec_impl(
impl_.async_exec(req, std::move(adapter), std::move(token));
}
void connection::cancel(operation op)
{
impl_.cancel(op);
}
void connection::cancel(operation op) { impl_.cancel(op); }
} // namespace boost::redis
} // namespace boost::redis

View File

@@ -0,0 +1,216 @@
/* Copyright (c) 2018-2025 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#include <boost/redis/detail/connection_logger.hpp>
#include <boost/redis/logger.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/system/error_code.hpp>
#include <string>
namespace boost::redis::detail {
#define BOOST_REDIS_READER_SWITCH_CASE(elem) \
case reader_fsm::action::type::elem: return "reader_fsm::action::type::" #elem
#define BOOST_REDIS_EXEC_SWITCH_CASE(elem) \
case exec_action_type::elem: return "exec_action_type::" #elem
auto to_string(reader_fsm::action::type t) noexcept -> char const*
{
switch (t) {
BOOST_REDIS_READER_SWITCH_CASE(setup_cancellation);
BOOST_REDIS_READER_SWITCH_CASE(append_some);
BOOST_REDIS_READER_SWITCH_CASE(needs_more);
BOOST_REDIS_READER_SWITCH_CASE(notify_push_receiver);
BOOST_REDIS_READER_SWITCH_CASE(cancel_run);
BOOST_REDIS_READER_SWITCH_CASE(done);
default: return "action::type::<invalid type>";
}
}
auto to_string(exec_action_type t) noexcept -> char const*
{
switch (t) {
BOOST_REDIS_EXEC_SWITCH_CASE(setup_cancellation);
BOOST_REDIS_EXEC_SWITCH_CASE(immediate);
BOOST_REDIS_EXEC_SWITCH_CASE(done);
BOOST_REDIS_EXEC_SWITCH_CASE(notify_writer);
BOOST_REDIS_EXEC_SWITCH_CASE(wait_for_response);
BOOST_REDIS_EXEC_SWITCH_CASE(cancel_run);
default: return "exec_action_type::<invalid type>";
}
}
inline void format_tcp_endpoint(const asio::ip::tcp::endpoint& ep, std::string& to)
{
// This formatting is inspired by Asio's endpoint operator<<
const auto& addr = ep.address();
if (addr.is_v6())
to += '[';
to += addr.to_string();
if (addr.is_v6())
to += ']';
to += ':';
to += std::to_string(ep.port());
}
inline void format_error_code(system::error_code ec, std::string& to)
{
// Using error_code::what() includes any source code info
// that the error may contain, making the messages too long.
// This implementation was taken from error_code::what()
to += ec.message();
to += " [";
to += ec.to_string();
to += ']';
}
void connection_logger::on_resolve(
system::error_code const& ec,
asio::ip::tcp::resolver::results_type const& res)
{
if (logger_.lvl < logger::level::info)
return;
if (ec) {
msg_ = "Error resolving the server hostname: ";
format_error_code(ec, msg_);
} else {
msg_ = "Resolve results: ";
auto iter = res.cbegin();
auto end = res.cend();
if (iter != end) {
format_tcp_endpoint(iter->endpoint(), msg_);
++iter;
for (; iter != end; ++iter) {
msg_ += ", ";
format_tcp_endpoint(iter->endpoint(), msg_);
}
}
}
logger_.fn(logger::level::info, msg_);
}
void connection_logger::on_connect(system::error_code const& ec, asio::ip::tcp::endpoint const& ep)
{
if (logger_.lvl < logger::level::info)
return;
if (ec) {
msg_ = "Failed connecting to the server: ";
format_error_code(ec, msg_);
} else {
msg_ = "Connected to ";
format_tcp_endpoint(ep, msg_);
}
logger_.fn(logger::level::info, msg_);
}
void connection_logger::on_connect(system::error_code const& ec, std::string_view unix_socket_ep)
{
if (logger_.lvl < logger::level::info)
return;
if (ec) {
msg_ = "Failed connecting to the server: ";
format_error_code(ec, msg_);
} else {
msg_ = "Connected to ";
msg_ += unix_socket_ep;
}
logger_.fn(logger::level::info, msg_);
}
void connection_logger::on_ssl_handshake(system::error_code const& ec)
{
if (logger_.lvl < logger::level::info)
return;
msg_ = "SSL handshake: ";
format_error_code(ec, msg_);
logger_.fn(logger::level::info, msg_);
}
void connection_logger::on_write(system::error_code const& ec, std::size_t n)
{
if (logger_.lvl < logger::level::info)
return;
msg_ = "writer_op: ";
if (ec) {
format_error_code(ec, msg_);
} else {
msg_ += std::to_string(n);
msg_ += " bytes written.";
}
logger_.fn(logger::level::info, msg_);
}
void connection_logger::on_fsm_resume(reader_fsm::action const& action)
{
if (logger_.lvl < logger::level::debug)
return;
std::string msg;
msg += "(";
msg += to_string(action.type_);
msg += ", ";
msg += std::to_string(action.push_size_);
msg += ", ";
msg += action.ec_.message();
msg += ")";
logger_.fn(logger::level::debug, msg);
}
void connection_logger::on_hello(system::error_code const& ec, generic_response const& resp)
{
if (logger_.lvl < logger::level::info)
return;
msg_ = "hello_op: ";
if (ec) {
format_error_code(ec, msg_);
if (resp.has_error()) {
msg_ += " (";
msg_ += resp.error().diagnostic;
msg_ += ')';
}
} else {
msg_ += "success";
}
logger_.fn(logger::level::info, msg_);
}
void connection_logger::log(logger::level lvl, std::string_view message)
{
if (logger_.lvl < lvl)
return;
logger_.fn(lvl, message);
}
void connection_logger::log(logger::level lvl, std::string_view op, system::error_code const& ec)
{
if (logger_.lvl < lvl)
return;
msg_ = op;
msg_ += ": ";
format_error_code(ec, msg_);
logger_.fn(lvl, msg_);
}
} // namespace boost::redis::detail

View File

@@ -5,62 +5,70 @@
*/
#include <boost/redis/error.hpp>
#include <boost/assert.hpp>
namespace boost::redis {
namespace detail {
struct error_category_impl : system::error_category {
virtual ~error_category_impl() = default;
auto name() const noexcept -> char const* override
{
return "boost.redis";
}
auto name() const noexcept -> char const* override { return "boost.redis"; }
auto message(int ev) const -> std::string override
{
switch(static_cast<error>(ev)) {
case error::invalid_data_type: return "Invalid resp3 type.";
case error::not_a_number: return "Can't convert string to number (maybe forgot to upgrade to RESP3?).";
case error::exceeeds_max_nested_depth: return "Exceeds the maximum number of nested responses.";
case error::unexpected_bool_value: return "Unexpected bool value.";
case error::empty_field: return "Expected field value is empty.";
case error::expects_resp3_simple_type: return "Expects a resp3 simple type.";
case error::expects_resp3_aggregate: return "Expects resp3 aggregate.";
case error::expects_resp3_map: return "Expects resp3 map.";
case error::expects_resp3_set: return "Expects resp3 set.";
case error::nested_aggregate_not_supported: return "Nested aggregate not_supported.";
case error::resp3_simple_error: return "Got RESP3 simple-error.";
case error::resp3_blob_error: return "Got RESP3 blob-error.";
case error::incompatible_size: return "Aggregate container has incompatible size.";
case error::not_a_double: return "Not a double.";
case error::resp3_null: return "Got RESP3 null.";
case error::not_connected: return "Not connected.";
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.";
case error::resp3_hello: return "RESP3 handshake error (hello command).";
default: BOOST_ASSERT(false); return "Boost.Redis error.";
switch (static_cast<error>(ev)) {
case error::invalid_data_type: return "Invalid resp3 type.";
case error::not_a_number:
return "Can't convert string to number (maybe forgot to upgrade to RESP3?).";
case error::exceeeds_max_nested_depth:
return "Exceeds the maximum number of nested responses.";
case error::unexpected_bool_value: return "Unexpected bool value.";
case error::empty_field: return "Expected field value is empty.";
case error::expects_resp3_simple_type: return "Expects a resp3 simple type.";
case error::expects_resp3_aggregate: return "Expects resp3 aggregate.";
case error::expects_resp3_map: return "Expects resp3 map.";
case error::expects_resp3_set: return "Expects resp3 set.";
case error::nested_aggregate_not_supported: return "Nested aggregate not_supported.";
case error::resp3_simple_error: return "Got RESP3 simple-error.";
case error::resp3_blob_error: return "Got RESP3 blob-error.";
case error::incompatible_size: return "Aggregate container has incompatible size.";
case error::not_a_double: return "Not a double.";
case error::resp3_null: return "Got RESP3 null.";
case error::not_connected: return "Not connected.";
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.";
case error::resp3_hello: return "RESP3 handshake error (hello command).";
case error::unix_sockets_unsupported:
return "The configuration specified a UNIX socket address, but UNIX sockets are not "
"supported by the system.";
case error::unix_sockets_ssl_unsupported:
return "The configuration specified UNIX sockets with SSL, which is not supported.";
case error::exceeds_maximum_read_buffer_size:
return "Reading data from the socket would exceed the maximum size allowed of the read "
"buffer.";
default: BOOST_ASSERT(false); return "Boost.Redis error.";
}
}
};
auto category() -> system::error_category const&
{
static error_category_impl instance;
return instance;
static error_category_impl instance;
return instance;
}
} // detail
} // namespace detail
auto make_error_code(error e) -> system::error_code
{
return system::error_code{static_cast<int>(e), detail::category()};
return system::error_code{static_cast<int>(e), detail::category()};
}
} // boost::redis::detail
} // namespace boost::redis

View File

@@ -0,0 +1,92 @@
//
// Copyright (c) 2025 Marcelo Zimbres Silva (mzimbres@gmail.com),
// Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_REDIS_EXEC_FSM_IPP
#define BOOST_REDIS_EXEC_FSM_IPP
#include <boost/redis/detail/coroutine.hpp>
#include <boost/redis/detail/exec_fsm.hpp>
#include <boost/redis/request.hpp>
#include <boost/asio/error.hpp>
#include <boost/assert.hpp>
namespace boost::redis::detail {
inline bool is_cancellation(asio::cancellation_type_t type)
{
return !!(
type & (asio::cancellation_type_t::total | asio::cancellation_type_t::partial |
asio::cancellation_type_t::terminal));
}
exec_action exec_fsm::resume(bool connection_is_open, asio::cancellation_type_t cancel_state)
{
switch (resume_point_) {
BOOST_REDIS_CORO_INITIAL
// Check whether the user wants to wait for the connection to
// be established.
if (elem_->get_request().get_config().cancel_if_not_connected && !connection_is_open) {
BOOST_REDIS_YIELD(resume_point_, 1, exec_action_type::immediate)
elem_.reset(); // Deallocate memory before finalizing
return system::error_code(error::not_connected);
}
// No more immediate errors. Set up the supported cancellation types.
// This is required to get partial and total cancellations.
// This is a potentially allocating operation, so do it as late as we can.
BOOST_REDIS_YIELD(resume_point_, 2, exec_action_type::setup_cancellation)
// Add the request to the multiplexer
mpx_->add(elem_);
// Notify the writer task that there is work to do. If the task is not
// listening (e.g. it's already writing or the connection is not healthy),
// this is a no-op. Since this is sync, no cancellation can happen here.
BOOST_REDIS_YIELD(resume_point_, 3, exec_action_type::notify_writer)
while (true) {
// Wait until we get notified. This will return once the request completes,
// or upon any kind of cancellation
BOOST_REDIS_YIELD(resume_point_, 4, exec_action_type::wait_for_response)
// If the request has completed (with error or not), we're done
if (elem_->is_done()) {
exec_action act{elem_->get_error(), elem_->get_read_size()};
elem_.reset(); // Deallocate memory before finalizing
return act;
}
// If we're cancelled, try to remove the request from the queue. This will only
// succeed if the request is waiting (wasn't written yet)
if (is_cancellation(cancel_state) && mpx_->remove(elem_)) {
elem_.reset(); // Deallocate memory before finalizing
return exec_action{asio::error::operation_aborted};
}
// If we hit a terminal cancellation, tear down the connection.
// Otherwise, go back to waiting.
// TODO: we could likely do better here and mark the request as cancelled, removing
// the done callback and the adapter. But this requires further exploration
if (!!(cancel_state & asio::cancellation_type_t::terminal)) {
BOOST_REDIS_YIELD(resume_point_, 5, exec_action_type::cancel_run)
elem_.reset(); // Deallocate memory before finalizing
return exec_action{asio::error::operation_aborted};
}
}
}
// We should never get here
BOOST_ASSERT(false);
return exec_action{system::error_code()};
}
} // namespace boost::redis::detail
#endif

View File

@@ -6,7 +6,6 @@
#include <boost/redis/ignore.hpp>
namespace boost::redis
{
namespace boost::redis {
ignore_t ignore;
}

View File

@@ -0,0 +1,37 @@
//
// Copyright (c) 2025 Marcelo Zimbres Silva (mzimbres@gmail.com),
// Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_REDIS_LOG_TO_STDERR_HPP
#define BOOST_REDIS_LOG_TO_STDERR_HPP
#include <algorithm>
#include <cstddef>
#include <cstdio>
#include <string_view>
namespace boost::redis::detail {
// Shared by several ipp files
inline void log_to_file(FILE* f, std::string_view msg, const char* prefix = "(Boost.Redis) ")
{
// If the message is empty, data() might return a null pointer
const char* msg_ptr = msg.empty() ? "" : msg.data();
// Precision should be an int when passed to fprintf. Technically,
// message could be larger than INT_MAX. Impose a sane limit on message sizes
// to prevent memory problems
auto precision = static_cast<int>((std::min)(msg.size(), static_cast<std::size_t>(0xffffu)));
// Log the message. None of our messages should contain NULL bytes, so this should be OK.
// We choose fprintf over std::clog because it's safe in multi-threaded environments.
std::fprintf(f, "%s%.*s\n", prefix, precision, msg_ptr);
}
} // namespace boost::redis::detail
#endif

View File

@@ -1,148 +1,24 @@
/* Copyright (c) 2018-2024 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
//
// Copyright (c) 2025 Marcelo Zimbres Silva (mzimbres@gmail.com),
// Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include <boost/redis/impl/log_to_file.hpp>
#include <boost/redis/logger.hpp>
#include <boost/system/error_code.hpp>
#include <iostream>
#include <iterator>
namespace boost::redis
{
#include <cstdio>
#include <string_view>
void logger::write_prefix()
{
if (!std::empty(prefix_))
std::clog << prefix_;
}
namespace boost::redis {
void logger::on_resolve(system::error_code const& ec, asio::ip::tcp::resolver::results_type const& res)
{
if (level_ < level::info)
return;
logger::logger(level l)
: lvl{l}
, fn{[](level, std::string_view msg) {
detail::log_to_file(stderr, msg);
}}
{ }
write_prefix();
std::clog << "resolve results: ";
if (ec) {
std::clog << ec.message() << std::endl;
} else {
auto begin = std::cbegin(res);
auto end = std::cend(res);
if (begin == end)
return;
std::clog << begin->endpoint();
for (auto iter = std::next(begin); iter != end; ++iter)
std::clog << ", " << iter->endpoint();
}
std::clog << std::endl;
}
void logger::on_connect(system::error_code const& ec, asio::ip::tcp::endpoint const& ep)
{
if (level_ < level::info)
return;
write_prefix();
std::clog << "connected to ";
if (ec)
std::clog << ec.message() << std::endl;
else
std::clog << ep;
std::clog << std::endl;
}
void logger::on_ssl_handshake(system::error_code const& ec)
{
if (level_ < level::info)
return;
write_prefix();
std::clog << "SSL handshake: " << ec.message() << std::endl;
}
void
logger::on_write(
system::error_code const& ec,
std::string const& payload)
{
if (level_ < level::info)
return;
write_prefix();
if (ec)
std::clog << "writer_op: " << ec.message();
else
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_hello(
system::error_code const& ec,
generic_response const& resp)
{
if (level_ < level::info)
return;
write_prefix();
if (ec) {
std::clog << "hello_op: " << ec.message();
if (resp.has_error())
std::clog << " (" << resp.error().diagnostic << ")";
} else {
std::clog << "hello_op: Success";
}
std::clog << std::endl;
}
void logger::trace(std::string_view message)
{
if (level_ < level::debug)
return;
write_prefix();
std::clog << message << std::endl;
}
void logger::trace(std::string_view op, system::error_code const& ec)
{
if (level_ < level::debug)
return;
write_prefix();
std::clog << op << ": " << ec.message() << std::endl;
}
} // boost::redis
} // namespace boost::redis

View File

@@ -0,0 +1,322 @@
/* Copyright (c) 2018-2025 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#include <boost/redis/detail/multiplexer.hpp>
#include <boost/redis/request.hpp>
#include <memory>
namespace boost::redis::detail {
multiplexer::elem::elem(request const& req, any_adapter adapter)
: req_{&req}
, adapter_{std::move(adapter)}
, remaining_responses_{req.get_expected_responses()}
, status_{status::waiting}
, ec_{}
, read_size_{0}
{ }
auto multiplexer::elem::notify_error(system::error_code ec) noexcept -> void
{
if (!ec_) {
ec_ = ec;
}
notify_done();
}
auto multiplexer::elem::commit_response(std::size_t read_size) -> void
{
read_size_ += read_size;
--remaining_responses_;
}
bool multiplexer::remove(std::shared_ptr<elem> const& ptr)
{
if (ptr->is_waiting()) {
reqs_.erase(std::remove(std::begin(reqs_), std::end(reqs_), ptr));
return true;
}
return false;
}
std::size_t multiplexer::commit_write()
{
// We have to clear the payload right after writing it to use it
// as a flag that informs there is no ongoing write.
write_buffer_.clear();
// There is small optimization possible here: traverse only the
// partition of unwritten requests instead of them all.
std::for_each(std::begin(reqs_), std::end(reqs_), [](auto const& ptr) {
BOOST_ASSERT_MSG(ptr != nullptr, "Expects non-null pointer.");
if (ptr->is_staged()) {
ptr->mark_written();
}
});
return release_push_requests();
}
void multiplexer::add(std::shared_ptr<elem> const& info)
{
reqs_.push_back(info);
if (info->get_request().has_hello_priority()) {
auto rend = std::partition_point(std::rbegin(reqs_), std::rend(reqs_), [](auto const& e) {
return e->is_waiting();
});
std::rotate(std::rbegin(reqs_), std::rbegin(reqs_) + 1, rend);
}
}
tribool multiplexer::consume_next_impl(std::string_view data, system::error_code& ec)
{
// We arrive here in two states:
//
// 1. While we are parsing a message. In this case we
// don't want to determine the type of the message in the
// buffer (i.e. response vs push) but leave it untouched
// until the parsing of a complete message ends.
//
// 2. On a new message, in which case we have to determine
// whether the next messag is a push or a response.
//
BOOST_ASSERT(!data.empty());
if (!on_push_) // Prepare for new message.
on_push_ = is_next_push(data);
if (on_push_) {
if (!resp3::parse(parser_, data, receive_adapter_, ec))
return std::nullopt;
return std::make_optional(true);
}
BOOST_ASSERT_MSG(
is_waiting_response(),
"Not waiting for a response (using MONITOR command perhaps?)");
BOOST_ASSERT(!reqs_.empty());
BOOST_ASSERT(reqs_.front() != nullptr);
BOOST_ASSERT(reqs_.front()->get_remaining_responses() != 0);
if (!resp3::parse(parser_, data, reqs_.front()->get_adapter(), ec))
return std::nullopt;
if (ec) {
reqs_.front()->notify_error(ec);
reqs_.pop_front();
return std::make_optional(false);
}
reqs_.front()->commit_response(parser_.get_consumed());
if (reqs_.front()->get_remaining_responses() == 0) {
// Done with this request.
reqs_.front()->notify_done();
reqs_.pop_front();
}
return std::make_optional(false);
}
std::pair<tribool, std::size_t> multiplexer::consume_next(
std::string_view data,
system::error_code& ec)
{
auto const ret = consume_next_impl(data, ec);
auto const consumed = parser_.get_consumed();
if (ec) {
return std::make_pair(ret, consumed);
}
if (ret.has_value()) {
parser_.reset();
commit_usage(ret.value(), consumed);
return std::make_pair(ret, consumed);
}
return std::make_pair(std::nullopt, consumed);
}
void multiplexer::reset()
{
write_buffer_.clear();
parser_.reset();
on_push_ = false;
cancel_run_called_ = false;
}
std::size_t multiplexer::prepare_write()
{
// Coalesces the requests and marks them staged. After a
// successful write staged requests will be marked as written.
auto const point = std::partition_point(
std::cbegin(reqs_),
std::cend(reqs_),
[](auto const& ri) {
return !ri->is_waiting();
});
std::for_each(point, std::cend(reqs_), [this](auto const& ri) {
// Stage the request.
write_buffer_ += ri->get_request().payload();
ri->mark_staged();
usage_.commands_sent += ri->get_request().get_commands();
});
usage_.bytes_sent += std::size(write_buffer_);
auto const d = std::distance(point, std::cend(reqs_));
return static_cast<std::size_t>(d);
}
std::size_t multiplexer::cancel_waiting()
{
auto f = [](auto const& ptr) {
BOOST_ASSERT(ptr != nullptr);
return !ptr->is_waiting();
};
auto point = std::stable_partition(std::begin(reqs_), std::end(reqs_), f);
auto const ret = std::distance(point, std::end(reqs_));
std::for_each(point, std::end(reqs_), [](auto const& ptr) {
ptr->notify_error({asio::error::operation_aborted});
});
reqs_.erase(point, std::end(reqs_));
return ret;
}
auto multiplexer::cancel_on_conn_lost() -> std::size_t
{
// Protects the code below from being called more than
// once, see https://github.com/boostorg/redis/issues/181
if (std::exchange(cancel_run_called_, true)) {
return 0;
}
// Must return false if the request should be removed.
auto cond = [](auto const& ptr) {
BOOST_ASSERT(ptr != nullptr);
if (ptr->is_waiting()) {
return !ptr->get_request().get_config().cancel_on_connection_lost;
} else {
return !ptr->get_request().get_config().cancel_if_unresponded;
}
};
auto point = std::stable_partition(std::begin(reqs_), std::end(reqs_), cond);
auto const ret = std::distance(point, std::end(reqs_));
std::for_each(point, std::end(reqs_), [](auto const& ptr) {
ptr->notify_error({asio::error::operation_aborted});
});
reqs_.erase(point, std::end(reqs_));
std::for_each(std::begin(reqs_), std::end(reqs_), [](auto const& ptr) {
return ptr->mark_waiting();
});
return ret;
}
void multiplexer::commit_usage(bool is_push, std::size_t size)
{
if (is_push) {
usage_.pushes_received += 1;
usage_.push_bytes_received += size;
on_push_ = false;
} else {
usage_.responses_received += 1;
usage_.response_bytes_received += size;
}
}
bool multiplexer::is_next_push(std::string_view data) const noexcept
{
// Useful links to understand the heuristics below.
//
// - https://github.com/redis/redis/issues/11784
// - https://github.com/redis/redis/issues/6426
// - https://github.com/boostorg/redis/issues/170
// Test if the message resp3 type is a push.
BOOST_ASSERT(!data.empty());
if (resp3::to_type(data.front()) == resp3::type::push)
return true;
// This is non-push type and the requests queue is empty. I have
// noticed this is possible, for example with -MISCONF. I don't
// know why they are not sent with a push type so we can
// distinguish them from responses to commands. If we are lucky
// enough to receive them when the command queue is empty they
// can be treated as server pushes, otherwise it is impossible
// to handle them properly
if (reqs_.empty())
return true;
// The request does not expect any response but we got one. This
// may happen if for example, subscribe with wrong syntax.
if (reqs_.front()->get_remaining_responses() == 0)
return true;
// Added to deal with MONITOR and also to fix PR170 which
// happens under load and on low-latency networks, where we
// might start receiving responses before the write operation
// completed and the request is still maked as staged and not
// written.
return reqs_.front()->is_waiting();
}
std::size_t multiplexer::release_push_requests()
{
auto point = std::stable_partition(std::begin(reqs_), std::end(reqs_), [](auto const& ptr) {
return !(ptr->is_written() && ptr->get_request().get_expected_responses() == 0);
});
std::for_each(point, std::end(reqs_), [](auto const& ptr) {
ptr->notify_done();
});
auto const d = std::distance(point, std::end(reqs_));
reqs_.erase(point, std::end(reqs_));
return static_cast<std::size_t>(d);
}
bool multiplexer::is_waiting_response() const noexcept
{
if (std::empty(reqs_))
return false;
// Under load and on low-latency networks we might start
// receiving responses before the write operation completed and
// the request is still maked as staged and not written. See
// https://github.com/boostorg/redis/issues/170
return !reqs_.front()->is_waiting();
}
bool multiplexer::is_writing() const noexcept { return !write_buffer_.empty(); }
void multiplexer::set_receive_adapter(any_adapter adapter)
{
receive_adapter_ = std::move(adapter);
}
auto make_elem(request const& req, any_adapter adapter) -> std::shared_ptr<multiplexer::elem>
{
return std::make_shared<multiplexer::elem>(req, std::move(adapter));
}
} // namespace boost::redis::detail

View File

@@ -0,0 +1,79 @@
/* Copyright (c) 2018-2025 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#include <boost/redis/detail/read_buffer.hpp>
#include <boost/assert.hpp>
#include <boost/core/make_span.hpp>
#include <utility>
namespace boost::redis::detail {
system::error_code read_buffer::prepare_append()
{
BOOST_ASSERT(append_buf_begin_ == buffer_.size());
auto const new_size = append_buf_begin_ + cfg_.read_buffer_append_size;
if (new_size > cfg_.max_read_size) {
return error::exceeds_maximum_read_buffer_size;
}
buffer_.resize(new_size);
return {};
}
void read_buffer::commit_append(std::size_t read_size)
{
BOOST_ASSERT(buffer_.size() >= (append_buf_begin_ + read_size));
buffer_.resize(append_buf_begin_ + read_size);
append_buf_begin_ = buffer_.size();
}
auto read_buffer::get_append_buffer() noexcept -> span_type
{
auto const size = buffer_.size();
return make_span(buffer_.data() + append_buf_begin_, size - append_buf_begin_);
}
auto read_buffer::get_committed_buffer() const noexcept -> std::string_view
{
BOOST_ASSERT(!buffer_.empty());
return {buffer_.data(), append_buf_begin_};
}
auto read_buffer::get_committed_size() const noexcept -> std::size_t { return append_buf_begin_; }
void read_buffer::clear()
{
buffer_.clear();
append_buf_begin_ = 0;
}
std::size_t read_buffer::consume_committed(std::size_t size)
{
// For convenience, if the requested size is larger than the
// committed buffer we cap it to the maximum.
if (size > append_buf_begin_)
size = append_buf_begin_;
buffer_.erase(buffer_.begin(), buffer_.begin() + size);
BOOST_ASSERT(append_buf_begin_ >= size);
append_buf_begin_ -= size;
return size;
}
void read_buffer::reserve(std::size_t n) { buffer_.reserve(n); }
bool operator==(read_buffer const& lhs, read_buffer const& rhs)
{
return lhs.buffer_ == rhs.buffer_ && lhs.append_buf_begin_ == rhs.append_buf_begin_;
}
bool operator!=(read_buffer const& lhs, read_buffer const& rhs) { return !(lhs == rhs); }
} // namespace boost::redis::detail

View File

@@ -0,0 +1,88 @@
/* Copyright (c) 2018-2025 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#include <boost/redis/detail/coroutine.hpp>
#include <boost/redis/detail/multiplexer.hpp>
#include <boost/redis/detail/read_buffer.hpp>
#include <boost/redis/detail/reader_fsm.hpp>
namespace boost::redis::detail {
reader_fsm::reader_fsm(read_buffer& rbuf, multiplexer& mpx) noexcept
: read_buffer_{&rbuf}
, mpx_{&mpx}
{ }
reader_fsm::action reader_fsm::resume(
std::size_t bytes_read,
system::error_code ec,
asio::cancellation_type_t /*cancel_state*/)
{
switch (resume_point_) {
BOOST_REDIS_CORO_INITIAL
BOOST_REDIS_YIELD(resume_point_, 1, action::type::setup_cancellation)
for (;;) {
ec = read_buffer_->prepare_append();
if (ec) {
action_after_resume_ = {action::type::done, 0, ec};
BOOST_REDIS_YIELD(resume_point_, 2, action::type::cancel_run)
return action_after_resume_;
}
BOOST_REDIS_YIELD(resume_point_, 3, next_read_type_)
read_buffer_->commit_append(bytes_read);
if (ec) {
// TODO: If an error occurred but data was read (i.e.
// bytes_read != 0) we should try to process that data and
// deliver it to the user before calling cancel_run.
action_after_resume_ = {action::type::done, bytes_read, ec};
BOOST_REDIS_YIELD(resume_point_, 4, action::type::cancel_run)
return action_after_resume_;
}
next_read_type_ = action::type::append_some;
while (read_buffer_->get_committed_size() != 0) {
res_ = mpx_->consume_next(read_buffer_->get_committed_buffer(), ec);
if (ec) {
// TODO: Perhaps log what has not been consumed to aid
// debugging.
action_after_resume_ = {action::type::done, res_.second, ec};
BOOST_REDIS_YIELD(resume_point_, 5, action::type::cancel_run)
return action_after_resume_;
}
if (!res_.first.has_value()) {
next_read_type_ = action::type::needs_more;
break;
}
read_buffer_->consume_committed(res_.second);
if (res_.first.value()) {
BOOST_REDIS_YIELD(resume_point_, 6, action::type::notify_push_receiver, res_.second)
if (ec) {
action_after_resume_ = {action::type::done, 0u, ec};
BOOST_REDIS_YIELD(resume_point_, 7, action::type::cancel_run)
return action_after_resume_;
}
} else {
// TODO: Here we should notify the exec operation that
// it can be completed. This will improve log clarity
// and will make this code symmetrical in how it
// handles pushes and other messages. The new action
// type can be named notify_exec. To do that we need to
// refactor the multiplexer.
}
}
}
}
BOOST_ASSERT(false);
return {action::type::done, 0, system::error_code()};
}
} // namespace boost::redis::detail

View File

@@ -12,10 +12,13 @@ namespace boost::redis::detail {
auto has_response(std::string_view cmd) -> bool
{
if (cmd == "SUBSCRIBE") return true;
if (cmd == "PSUBSCRIBE") return true;
if (cmd == "UNSUBSCRIBE") return true;
if (cmd == "SUBSCRIBE")
return true;
if (cmd == "PSUBSCRIBE")
return true;
if (cmd == "UNSUBSCRIBE")
return true;
return false;
}
} // boost:redis::detail
} // namespace boost::redis::detail

View File

@@ -6,8 +6,7 @@
#include <boost/redis/detail/resp3_handshaker.hpp>
namespace boost::redis::detail
{
namespace boost::redis::detail {
void push_hello(config const& cfg, request& req)
{
@@ -24,4 +23,4 @@ void push_hello(config const& cfg, request& req)
req.push("SELECT", cfg.database_index.value());
}
} // boost::redis::detail
} // namespace boost::redis::detail

View File

@@ -4,20 +4,20 @@
* accompanying file LICENSE.txt)
*/
#include <boost/redis/response.hpp>
#include <boost/redis/error.hpp>
#include <boost/redis/response.hpp>
#include <boost/assert.hpp>
namespace boost::redis
{
namespace boost::redis {
void consume_one(generic_response& r, system::error_code& ec)
{
if (r.has_error())
return; // Nothing to consume.
return; // Nothing to consume.
if (std::empty(r.value()))
return; // Nothing to consume.
return; // Nothing to consume.
auto const depth = r.value().front().depth;
@@ -29,8 +29,9 @@ void consume_one(generic_response& r, system::error_code& ec)
return;
}
auto f = [depth](auto const& e)
{ return e.depth == depth; };
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);
@@ -45,4 +46,4 @@ void consume_one(generic_response& r)
throw system::system_error(ec);
}
} // boost::redis::resp3
} // namespace boost::redis

View File

@@ -7,28 +7,17 @@
#ifndef BOOST_REDIS_LOGGER_HPP
#define BOOST_REDIS_LOGGER_HPP
#include <boost/redis/response.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <string>
namespace boost::system {class error_code;}
#include <functional>
#include <string_view>
namespace boost::redis {
/** @brief Logger class
* @ingroup high-level-api
/** @brief Defines logging configuration.
*
* 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.
* See the member descriptions for more info.
*/
class logger {
public:
/** @brief Syslog-like log levels
* @ingroup high-level-api
*/
struct logger {
/// Syslog-like log levels.
enum class level
{
/// Disabled
@@ -56,84 +45,53 @@ public:
info,
/// Debug
debug
debug,
};
/** @brief Constructor
* @ingroup high-level-api
/** @brief Constructor from a level.
*
* @param l Log level.
*/
logger(level l = level::debug)
: level_{l}
{}
/** @brief Called when the resolve operation completes.
* @ingroup high-level-api
* Constructs a logger with the specified level
* and a logging function that prints messages to `stderr`.
*
* @param ec Error returned by the resolve operation.
* @param res Resolve results.
*/
void on_resolve(system::error_code const& ec, asio::ip::tcp::resolver::results_type const& res);
/** @brief Called when the connect operation completes.
* @ingroup high-level-api
* @param l The value to set @ref lvl to.
*
* @param ec Error returned by the connect operation.
* @param ep Endpoint to which the connection connected.
* @par Exceptions
* No-throw guarantee.
*/
void on_connect(system::error_code const& ec, asio::ip::tcp::endpoint const& ep);
logger(level l = level::info);
/** @brief Called when the ssl handshake operation completes.
* @ingroup high-level-api
/** @brief Constructor from a level and a function.
*
* @param ec Error returned by the handshake operation.
*/
void on_ssl_handshake(system::error_code const& ec);
/** @brief Called when the write operation completes.
* @ingroup high-level-api
* Constructs a logger by setting its members to the specified values.
*
* @param ec Error code returned by the write operation.
* @param payload The payload written to the socket.
*/
void on_write(system::error_code const& ec, std::string const& payload);
/** @brief Called when the read operation completes.
* @ingroup high-level-api
* @param l The value to set @ref lvl to.
* @param fn The value to set @ref fn to.
*
* @param ec Error code returned by the read operation.
* @param n Number of bytes read.
* @par Exceptions
* No-throw guarantee.
*/
void on_read(system::error_code const& ec, std::size_t n);
logger(level l, std::function<void(level, std::string_view)> fn)
: lvl{l}
, fn{std::move(fn)}
{ }
/** @brief Called when the `HELLO` request completes.
* @ingroup high-level-api
/**
* @brief Defines a severity filter for messages.
*
* @param ec Error code returned by the async_exec operation.
* @param resp Response sent by the Redis server.
* Only messages with a level >= to the one specified by the logger
* will be logged.
*/
void on_hello(system::error_code const& ec, generic_response const& resp);
level lvl;
/** @brief Sets a prefix to every log message
* @ingroup high-level-api
/**
* @brief Defines a severity filter for messages.
*
* @param prefix The prefix.
* Only messages with a level >= to the one specified by the logger
* will be logged.
*/
void set_prefix(std::string_view prefix)
{
prefix_ = prefix;
}
void trace(std::string_view message);
void trace(std::string_view op, system::error_code const& ec);
private:
void write_prefix();
level level_;
std::string_view prefix_;
std::function<void(level, std::string_view)> fn;
};
} // boost::redis
} // namespace boost::redis
#endif // BOOST_REDIS_LOGGER_HPP
#endif // BOOST_REDIS_LOGGER_HPP

View File

@@ -10,12 +10,12 @@
namespace boost::redis {
/** @brief Connection operations that can be cancelled.
* @ingroup high-level-api
*
* The operations listed below can be passed to the
* `boost::redis::connection::cancel` member function.
* @ref boost::redis::connection::cancel member function.
*/
enum class operation {
enum class operation
{
/// Resolve operation.
resolve,
/// Connect operation.
@@ -36,6 +36,6 @@ enum class operation {
all,
};
} // boost::redis
} // namespace boost::redis
#endif // BOOST_REDIS_OPERATION_HPP
#endif // BOOST_REDIS_OPERATION_HPP

View File

@@ -7,28 +7,29 @@
#ifndef BOOST_REDIS_REQUEST_HPP
#define BOOST_REDIS_REQUEST_HPP
#include <boost/redis/resp3/type.hpp>
#include <boost/redis/resp3/serialization.hpp>
#include <boost/redis/resp3/type.hpp>
#include <algorithm>
#include <string>
#include <tuple>
#include <algorithm>
// NOTE: For some commands like hset it would be a good idea to assert
// the value type is a pair.
namespace boost::redis {
namespace detail{
namespace detail {
auto has_response(std::string_view cmd) -> bool;
}
/** \brief Creates Redis requests.
* \ingroup high-level-api
/** @brief Represents a Redis request.
*
* A request is composed of one or more Redis commands and is
* referred to in the redis documentation as a pipeline, see
* https://redis.io/topics/pipelining. For example
* referred to in the redis documentation as a pipeline. See
* <a href="https://redis.io/topics/pipelining"></a> for more info.
*
* For example:
*
* @code
* request r;
@@ -39,64 +40,64 @@ auto has_response(std::string_view cmd) -> bool;
* r.push("QUIT");
* @endcode
*
* \remarks
*
* Uses a std::string for internal storage.
* Uses a `std::string` for internal storage.
*/
class request {
public:
/// Request configuration options.
struct config {
/** \brief If `true` calls to `connection::async_exec` will
/** @brief If `true`, calls to @ref basic_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` `connection::async_exec` will complete with
* `boost::redis::error::not_connected` if the call happens
/** @brief If `true`, @ref basic_connection::async_exec will complete with
* @ref 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` `connection::async_exec` will not
/** @brief If `false`, @ref basic_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 have not been responded when
* @ref boost::redis::connection::async_run completes.
*/
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
/** @brief If this request has a `HELLO` command and this flag
* is `true`, it will be moved to the
* front of the queue of awaiting requests. This makes it
* possible to send `HELLO` and authenticate before other
* possible to send `HELLO` commands and authenticate before other
* commands are sent.
*/
bool hello_with_priority = true;
};
/** \brief Constructor
/** @brief Constructor
*
* \param cfg Configuration options.
* @param cfg Configuration options.
*/
explicit
request(config cfg = config{true, false, true, true})
: cfg_{cfg} {}
explicit request(config cfg = config{true, false, true, true})
: cfg_{cfg}
{ }
//// Returns the number of responses expected for this request.
/// Returns the number of responses expected for this request.
[[nodiscard]] auto get_expected_responses() const noexcept -> std::size_t
{ return expected_responses_;};
{
return expected_responses_;
};
//// Returns the number of commands contained in this request.
[[nodiscard]] auto get_commands() const noexcept -> std::size_t
{ return commands_;};
/// Returns the number of commands contained in this request.
[[nodiscard]] auto get_commands() const noexcept -> std::size_t { return commands_; };
[[nodiscard]] auto payload() const noexcept -> std::string_view
{ return payload_;}
[[nodiscard]] auto payload() const noexcept -> std::string_view { return payload_; }
[[nodiscard]] auto has_hello_priority() const noexcept -> auto const&
{ return has_hello_priority_;}
{
return has_hello_priority_;
}
/// Clears the request preserving allocated memory.
void clear()
@@ -107,40 +108,43 @@ public:
has_hello_priority_ = false;
}
/// Calls std::string::reserve on the internal storage.
void reserve(std::size_t new_cap = 0)
{ payload_.reserve(new_cap); }
/// Returns a const reference to the config object.
[[nodiscard]] auto get_config() const noexcept -> auto const& {return cfg_; }
/// Calls `std::string::reserve` on the internal storage.
void reserve(std::size_t new_cap = 0) { payload_.reserve(new_cap); }
/// Returns a reference to the config object.
[[nodiscard]] auto get_config() noexcept -> auto& {return cfg_; }
[[nodiscard]] auto get_config() const noexcept -> config const& { return cfg_; }
/// Returns a reference to the config object.
[[nodiscard]] auto get_config() noexcept -> config& { return cfg_; }
/** @brief Appends a new command to the end of the request.
*
* For example
* For example:
*
* \code
* @code
* request req;
* req.push("SET", "key", "some string", "EX", "2");
* \endcode
* @endcode
*
* will add the `set` command with value "some string" and an
* This will add a `SET` command with value `"some string"` and an
* expiration of 2 seconds.
*
* \param cmd The command e.g redis or sentinel command.
* \param args Command arguments.
* \tparam Ts Non-string types will be converted to string by calling `boost_redis_to_bulk` on each argument. This function must be made available over ADL and must have the following signature
* Command arguments should either be convertible to `std::string_view`
* or support the `boost_redis_to_bulk` function.
* This function is a customization point that must be made available
* using ADL and must have the following signature:
*
* @code
* void boost_redis_to_bulk(std::string& to, T const& t);
* {
* boost::redis::resp3::boost_redis_to_bulk(to, serialize(t));
* }
* @endcode
*
*
* See cpp20_serialization.cpp
*
* @param cmd The command to execute. It should be a redis or sentinel command, like `"SET"`.
* @param args Command arguments. Non-string types will be converted to string by calling `boost_redis_to_bulk` on each argument.
* @tparam Ts Types of the command arguments.
*
*/
template <class... Ts>
void push(std::string_view cmd, Ts const&... args)
@@ -156,7 +160,7 @@ public:
/** @brief Appends a new command to the end of the request.
*
* This overload is useful for commands that have a key and have a
* dynamic range of arguments. For example
* dynamic range of arguments. For example:
*
* @code
* std::map<std::string, std::string> map
@@ -166,32 +170,34 @@ public:
* };
*
* request req;
* req.push_range("HSET", "key", std::cbegin(map), std::cend(map));
* req.push_range("HSET", "key", map.cbegin(), map.cend());
* @endcode
*
* \param cmd The command e.g. Redis or Sentinel command.
* \param key The command key.
* \param begin Iterator to the begin of the range.
* \param end Iterator to the end of the range.
* \tparam Ts Non-string types will be converted to string by calling `boost_redis_to_bulk` on each argument. This function must be made available over ADL and must have the following signature
*
* Command arguments should either be convertible to `std::string_view`
* or support the `boost_redis_to_bulk` function.
* This function is a customization point that must be made available
* using ADL and must have the following signature:
*
* @code
* void boost_redis_to_bulk(std::string& to, T const& t);
* {
* boost::redis::resp3::boost_redis_to_bulk(to, serialize(t));
* }
* @endcode
*
* @param cmd The command to execute. It should be a redis or sentinel command, like `"SET"`.
* @param key The command key. It will be added as the first argument to the command.
* @param begin Iterator to the begin of the range.
* @param end Iterator to the end of the range.
* @tparam ForwardIterator A forward iterator with an element type that is convertible to `std::string_view`
* or supports `boost_redis_to_bulk`.
*
* See cpp20_serialization.cpp
*/
template <class ForwardIterator>
void
push_range(
std::string_view const& cmd,
std::string_view const& key,
void push_range(
std::string_view cmd,
std::string_view key,
ForwardIterator begin,
ForwardIterator end,
typename std::iterator_traits<ForwardIterator>::value_type * = nullptr)
typename std::iterator_traits<ForwardIterator>::value_type* = nullptr)
{
using value_type = typename std::iterator_traits<ForwardIterator>::value_type;
@@ -205,7 +211,7 @@ public:
resp3::add_bulk(payload_, key);
for (; begin != end; ++begin)
resp3::add_bulk(payload_, *begin);
resp3::add_bulk(payload_, *begin);
check_cmd(cmd);
}
@@ -213,37 +219,39 @@ public:
/** @brief Appends a new command to the end of the request.
*
* This overload is useful for commands that have a dynamic number
* of arguments and don't have a key. For example
* of arguments and don't have a key. For example:
*
* \code
* @code
* std::set<std::string> channels
* { "channel1" , "channel2" , "channel3" }
* { "channel1" , "channel2" , "channel3" };
*
* request req;
* req.push("SUBSCRIBE", std::cbegin(channels), std::cend(channels));
* \endcode
* @endcode
*
* \param cmd The Redis command
* \param begin Iterator to the begin of the range.
* \param end Iterator to the end of the range.
* \tparam ForwardIterator If the value type is not a std::string it will be converted to a string by calling `boost_redis_to_bulk`. This function must be made available over ADL and must have the following signature
* Command arguments should either be convertible to `std::string_view`
* or support the `boost_redis_to_bulk` function.
* This function is a customization point that must be made available
* using ADL and must have the following signature:
*
* @code
* void boost_redis_to_bulk(std::string& to, T const& t);
* {
* boost::redis::resp3::boost_redis_to_bulk(to, serialize(t));
* }
* @endcode
*
* @param cmd The command to execute. It should be a redis or sentinel command, like `"SET"`.
* @param begin Iterator to the begin of the range.
* @param end Iterator to the end of the range.
* @tparam ForwardIterator A forward iterator with an element type that is convertible to `std::string_view`
* or supports `boost_redis_to_bulk`.
*
* See cpp20_serialization.cpp
*/
template <class ForwardIterator>
void
push_range(
std::string_view const& cmd,
void push_range(
std::string_view cmd,
ForwardIterator begin,
ForwardIterator end,
typename std::iterator_traits<ForwardIterator>::value_type * = nullptr)
typename std::iterator_traits<ForwardIterator>::value_type* = nullptr)
{
using value_type = typename std::iterator_traits<ForwardIterator>::value_type;
@@ -256,7 +264,7 @@ public:
resp3::add_bulk(payload_, cmd);
for (; begin != end; ++begin)
resp3::add_bulk(payload_, *begin);
resp3::add_bulk(payload_, *begin);
check_cmd(cmd);
}
@@ -265,19 +273,20 @@ public:
*
* Equivalent to the overload taking a range of begin and end
* iterators.
*
* \param cmd Redis command.
* \param key Redis key.
* \param range Range to send e.g. `std::map`.
* \tparam A type that can be passed to `std::cbegin()` and `std::cend()`.
*
* @param cmd The command to execute. It should be a redis or sentinel command, like `"SET"`.
* @param key The command key. It will be added as the first argument to the command.
* @param range Range containing the command arguments.
* @tparam Range A type that can be passed to `std::begin()` and `std::end()` to obtain
* iterators. The range elements should be convertible to `std::string_view`
* or support `boost_redis_to_bulk`.
*/
template <class Range>
void
push_range(
std::string_view const& cmd,
std::string_view const& key,
void push_range(
std::string_view cmd,
std::string_view key,
Range const& range,
decltype(std::begin(range)) * = nullptr)
decltype(std::begin(range))* = nullptr)
{
using std::begin;
using std::end;
@@ -288,17 +297,18 @@ public:
*
* Equivalent to the overload taking a range of begin and end
* iterators.
*
* \param cmd Redis command.
* \param range Range to send e.g. `std::map`.
* \tparam A type that can be passed to `std::cbegin()` and `std::cend()`.
*
* @param cmd The command to execute. It should be a redis or sentinel command, like `"SET"`.
* @param range Range containing the command arguments.
* @tparam Range A type that can be passed to `std::begin()` and `std::end()` to obtain
* iterators. The range elements should be convertible to `std::string_view`
* or support `boost_redis_to_bulk`.
*/
template <class Range>
void
push_range(
void push_range(
std::string_view cmd,
Range const& range,
decltype(std::cbegin(range)) * = nullptr)
decltype(std::cbegin(range))* = nullptr)
{
using std::cbegin;
using std::cend;
@@ -324,6 +334,6 @@ private:
bool has_hello_priority_ = false;
};
} // boost::redis::resp3
} // namespace boost::redis
#endif // BOOST_REDIS_REQUEST_HPP
#endif // BOOST_REDIS_REQUEST_HPP

View File

@@ -4,11 +4,13 @@
* accompanying file LICENSE.txt)
*/
#include <boost/redis/resp3/parser.hpp>
#include <boost/redis/error.hpp>
#include <boost/redis/resp3/parser.hpp>
#include <boost/assert.hpp>
#include <charconv>
#include <cstddef>
#include <limits>
namespace boost::redis::resp3 {
@@ -20,47 +22,25 @@ void to_int(std::size_t& i, std::string_view sv, system::error_code& ec)
ec = error::not_a_number;
}
parser::parser()
{
reset();
}
parser::parser() { reset(); }
void parser::reset()
{
depth_ = 0;
sizes_ = {{1}};
bulk_length_ = (std::numeric_limits<std::size_t>::max)();
sizes_ = default_sizes;
bulk_length_ = default_bulk_length;
bulk_ = type::invalid;
consumed_ = 0;
sizes_[0] = 2; // The sentinel must be more than 1.
}
std::size_t
parser::get_suggested_buffer_growth(std::size_t hint) const noexcept
{
if (!bulk_expected())
return hint;
std::size_t parser::get_consumed() const noexcept { return consumed_; }
if (hint < bulk_length_ + 2)
return bulk_length_ + 2;
return hint;
}
std::size_t
parser::get_consumed() const noexcept
{
return consumed_;
}
bool
parser::done() const noexcept
bool parser::done() const noexcept
{
return depth_ == 0 && bulk_ == type::invalid && consumed_ != 0;
}
void
parser::commit_elem() noexcept
void parser::commit_elem() noexcept
{
--sizes_[depth_];
while (sizes_[depth_] == 0) {
@@ -69,15 +49,14 @@ parser::commit_elem() noexcept
}
}
auto
parser::consume(std::string_view view, system::error_code& ec) noexcept -> parser::result
auto parser::consume(std::string_view view, system::error_code& ec) noexcept -> parser::result
{
switch (bulk_) {
case type::invalid:
{
auto const pos = view.find(sep, consumed_);
if (pos == std::string::npos)
return {}; // Needs more data to proceeed.
return {}; // Needs more data to proceeed.
auto const t = to_type(view.at(consumed_));
auto const content = view.substr(consumed_ + 1, pos - 1 - consumed_);
@@ -88,14 +67,14 @@ parser::consume(std::string_view view, system::error_code& ec) noexcept -> parse
consumed_ = pos + 2;
if (!bulk_expected())
return ret;
}
[[fallthrough]];
} [[fallthrough]];
default: // Handles bulk.
default: // Handles bulk.
{
auto const span = bulk_length_ + 2;
if ((std::size(view) - consumed_) < span)
return {}; // Needs more data to proceeed.
return {}; // Needs more data to proceeed.
auto const bulk_view = view.substr(consumed_, bulk_length_);
node_type const ret = {bulk_, 1, depth_, bulk_view};
@@ -108,11 +87,8 @@ parser::consume(std::string_view view, system::error_code& ec) noexcept -> parse
}
}
auto
parser::consume_impl(
type t,
std::string_view elem,
system::error_code& ec) -> parser::node_type
auto parser::consume_impl(type t, std::string_view elem, system::error_code& ec)
-> parser::node_type
{
BOOST_ASSERT(!bulk_expected());
@@ -120,13 +96,13 @@ parser::consume_impl(
switch (t) {
case type::streamed_string_part:
{
to_int(bulk_length_ , elem, ec);
to_int(bulk_length_, elem, ec);
if (ec)
return {};
if (bulk_length_ == 0) {
ret = {type::streamed_string_part, 1, depth_, {}};
sizes_[depth_] = 1; // We are done.
sizes_[depth_] = 1; // We are done.
bulk_ = type::invalid;
commit_elem();
} else {
@@ -145,7 +121,7 @@ parser::consume_impl(
sizes_[++depth_] = (std::numeric_limits<std::size_t>::max)();
ret = {type::streamed_string, 0, depth_, {}};
} else {
to_int(bulk_length_ , elem , ec);
to_int(bulk_length_, elem, ec);
if (ec)
return {};
@@ -155,13 +131,13 @@ parser::consume_impl(
case type::boolean:
{
if (std::empty(elem)) {
ec = error::empty_field;
return {};
ec = error::empty_field;
return {};
}
if (elem.at(0) != 'f' && elem.at(0) != 't') {
ec = error::unexpected_bool_value;
return {};
ec = error::unexpected_bool_value;
return {};
}
ret = {t, 1, depth_, elem};
@@ -172,10 +148,11 @@ parser::consume_impl(
case type::number:
{
if (std::empty(elem)) {
ec = error::empty_field;
return {};
ec = error::empty_field;
return {};
}
} [[fallthrough]];
}
[[fallthrough]];
case type::simple_error:
case type::simple_string:
case type::null:
@@ -189,7 +166,7 @@ parser::consume_impl(
case type::attribute:
case type::map:
{
std::size_t l = -1;
std::size_t l = static_cast<std::size_t>(-1);
to_int(l, elem, ec);
if (ec)
return {};
@@ -217,4 +194,13 @@ parser::consume_impl(
return ret;
}
} // boost::redis::resp3
bool parser::is_parsing() const noexcept
{
auto const v = depth_ == 0 && sizes_ == default_sizes && bulk_length_ == default_bulk_length &&
bulk_ == type::invalid && consumed_ == 0;
return !v;
}
} // namespace boost::redis::resp3

View File

@@ -4,8 +4,8 @@
* accompanying file LICENSE.txt)
*/
#include <boost/redis/resp3/serialization.hpp>
#include <boost/redis/resp3/parser.hpp>
#include <boost/redis/resp3/serialization.hpp>
namespace boost::redis::resp3 {
@@ -35,8 +35,5 @@ void add_blob(std::string& payload, std::string_view blob)
payload += parser::sep;
}
void add_separator(std::string& payload)
{
payload += parser::sep;
}
} // boost::redis::resp3
void add_separator(std::string& payload) { payload += parser::sep; }
} // namespace boost::redis::resp3

View File

@@ -5,6 +5,7 @@
*/
#include <boost/redis/resp3/type.hpp>
#include <boost/assert.hpp>
namespace boost::redis::resp3 {
@@ -12,24 +13,24 @@ namespace boost::redis::resp3 {
auto to_string(type t) noexcept -> char const*
{
switch (t) {
case type::array: return "array";
case type::push: return "push";
case type::set: return "set";
case type::map: return "map";
case type::attribute: return "attribute";
case type::simple_string: return "simple_string";
case type::simple_error: return "simple_error";
case type::number: return "number";
case type::doublean: return "doublean";
case type::boolean: return "boolean";
case type::big_number: return "big_number";
case type::null: return "null";
case type::blob_error: return "blob_error";
case type::verbatim_string: return "verbatim_string";
case type::blob_string: return "blob_string";
case type::streamed_string: return "streamed_string";
case type::array: return "array";
case type::push: return "push";
case type::set: return "set";
case type::map: return "map";
case type::attribute: return "attribute";
case type::simple_string: return "simple_string";
case type::simple_error: return "simple_error";
case type::number: return "number";
case type::doublean: return "doublean";
case type::boolean: return "boolean";
case type::big_number: return "big_number";
case type::null: return "null";
case type::blob_error: return "blob_error";
case type::verbatim_string: return "verbatim_string";
case type::blob_string: return "blob_string";
case type::streamed_string: return "streamed_string";
case type::streamed_string_part: return "streamed_string_part";
default: return "invalid";
default: return "invalid";
}
}
@@ -39,4 +40,4 @@ auto operator<<(std::ostream& os, type t) -> std::ostream&
return os;
}
} // boost::redis::resp3
} // namespace boost::redis::resp3

View File

@@ -11,16 +11,13 @@
namespace boost::redis::resp3 {
/** \brief A node in the response tree.
* \ingroup high-level-api
/** @brief A node in the response tree.
*
* RESP3 can contain recursive data structures: A map of sets of
* vector of etc. As it is parsed each element is passed to user
* callbacks (push parser). The signature of this
* callback is `f(resp3::node<std::string_view)`. This class is called a node
* RESP3 can contain recursive data structures, like a map of sets of
* vectors. This class is called a node
* because it can be seen as the element of the response tree. It
* is a template so that users can use it with owing strings e.g.
* `std::string` or `boost::static_string` etc.
* is a template so that users can use it with any string type, like
* `std::string` or `boost::static_string`.
*
* @tparam String A `std::string`-like type.
*/
@@ -48,22 +45,20 @@ struct basic_node {
template <class String>
auto operator==(basic_node<String> const& a, basic_node<String> const& b)
{
// clang-format off
return a.aggregate_size == b.aggregate_size
&& a.depth == b.depth
&& a.data_type == b.data_type
&& a.value == b.value;
// clang-format on
};
/** @brief A node in the response tree that owns its data
* @ingroup high-level-api
*/
/// A node in the response tree that owns its data.
using node = basic_node<std::string>;
/** @brief A node view in the response tree
* @ingroup high-level-api
*/
/// A node in the response tree that does not own its data.
using node_view = basic_node<std::string_view>;
} // boost::redis::resp3
} // namespace boost::redis::resp3
#endif // BOOST_REDIS_RESP3_NODE_HPP
#endif // BOOST_REDIS_RESP3_NODE_HPP

View File

@@ -8,11 +8,13 @@
#define BOOST_REDIS_RESP3_PARSER_HPP
#include <boost/redis/resp3/node.hpp>
#include <boost/system/error_code.hpp>
#include <array>
#include <string_view>
#include <cstdint>
#include <optional>
#include <string_view>
namespace boost::redis::resp3 {
@@ -25,6 +27,14 @@ public:
static constexpr std::string_view sep = "\r\n";
private:
using sizes_type = std::array<std::size_t, max_embedded_depth + 1>;
// sizes_[0] = 2 because the sentinel must be more than 1.
static constexpr sizes_type default_sizes = {
{2, 1, 1, 1, 1, 1}
};
static constexpr auto default_bulk_length = static_cast<std::size_t>(-1);
// 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.
@@ -33,7 +43,7 @@ private:
// The parser supports up to 5 levels of nested structures. The
// first element in the sizes stack is a sentinel and must be
// different from 1.
std::array<std::size_t, max_embedded_depth + 1> sizes_;
sizes_type sizes_;
// Contains the length expected in the next bulk read.
std::size_t bulk_length_;
@@ -54,7 +64,9 @@ private:
// returns type::invalid.
[[nodiscard]]
auto bulk_expected() const noexcept -> bool
{ return bulk_ != type::invalid; }
{
return bulk_ != type::invalid;
}
public:
parser();
@@ -63,26 +75,26 @@ public:
[[nodiscard]]
auto done() const noexcept -> bool;
auto get_suggested_buffer_growth(std::size_t hint) const noexcept -> std::size_t;
auto get_consumed() const noexcept -> std::size_t;
auto consume(std::string_view view, system::error_code& ec) noexcept -> result;
void reset();
bool is_parsing() const noexcept;
};
// 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(
resp3::parser& p,
std::string_view const& msg,
Adapter& adapter,
system::error_code& ec)
bool parse(parser& p, std::string_view const& msg, Adapter& adapter, system::error_code& ec)
{
// This if could be avoid with a state machine that jumps into the
// correct position.
if (!p.is_parsing())
adapter.on_init();
while (!p.done()) {
auto const res = p.consume(msg, ec);
if (ec)
@@ -91,14 +103,15 @@ parse(
if (!res)
return false;
adapter(res.value(), ec);
adapter.on_node(res.value(), ec);
if (ec)
return true;
}
adapter.on_done();
return true;
}
} // boost::redis::resp3
} // namespace boost::redis::resp3
#endif // BOOST_REDIS_RESP3_PARSER_HPP
#endif // BOOST_REDIS_RESP3_PARSER_HPP

View File

@@ -7,10 +7,11 @@
#ifndef BOOST_REDIS_RESP3_SERIALIZATION_HPP
#define BOOST_REDIS_RESP3_SERIALIZATION_HPP
#include <boost/redis/resp3/parser.hpp>
#include <boost/redis/resp3/type.hpp>
#include <boost/system/system_error.hpp>
#include <boost/throw_exception.hpp>
#include <boost/redis/resp3/parser.hpp>
#include <string>
#include <tuple>
@@ -36,8 +37,6 @@ namespace boost::redis::resp3 {
*
* @param payload Storage on which data will be copied into.
* @param data Data that will be serialized and stored in `payload`.
*
* See more in @ref serialization.
*/
void boost_redis_to_bulk(std::string& payload, std::string_view data);
@@ -57,12 +56,11 @@ struct add_bulk_impl {
}
};
template <class ...Ts>
template <class... Ts>
struct add_bulk_impl<std::tuple<Ts...>> {
static void add(std::string& payload, std::tuple<Ts...> const& t)
{
auto f = [&](auto const&... vs)
{
auto f = [&](auto const&... vs) {
using namespace boost::redis::resp3;
(boost_redis_to_bulk(payload, vs), ...);
};
@@ -94,23 +92,24 @@ struct bulk_counter;
template <class>
struct bulk_counter {
static constexpr auto size = 1U;
static constexpr auto size = 1U;
};
template <class T, class U>
struct bulk_counter<std::pair<T, U>> {
static constexpr auto size = 2U;
static constexpr auto size = 2U;
};
void add_blob(std::string& payload, std::string_view blob);
void add_separator(std::string& payload);
namespace detail
{
namespace detail {
template <class Adapter>
void deserialize(std::string_view const& data, Adapter adapter, system::error_code& ec)
{
adapter.on_init();
parser parser;
while (!parser.done()) {
auto const res = parser.consume(data, ec);
@@ -119,12 +118,14 @@ void deserialize(std::string_view const& data, Adapter adapter, system::error_co
BOOST_ASSERT(res.has_value());
adapter(res.value(), ec);
adapter.on_node(res.value(), ec);
if (ec)
return;
}
BOOST_ASSERT(parser.get_consumed() == std::size(data));
adapter.on_done();
}
template <class Adapter>
@@ -134,11 +135,11 @@ void deserialize(std::string_view const& data, Adapter adapter)
deserialize(data, adapter, ec);
if (ec)
BOOST_THROW_EXCEPTION(system::system_error{ec});
BOOST_THROW_EXCEPTION(system::system_error{ec});
}
}
} // namespace detail
} // boost::redis::resp3
} // namespace boost::redis::resp3
#endif // BOOST_REDIS_RESP3_SERIALIZATION_HPP
#endif // BOOST_REDIS_RESP3_SERIALIZATION_HPP

View File

@@ -8,70 +8,77 @@
#define BOOST_REDIS_RESP3_TYPE_HPP
#include <boost/assert.hpp>
#include <ostream>
#include <vector>
#include <string>
#include <vector>
namespace boost::redis::resp3 {
/** \brief RESP3 data types.
\ingroup high-level-api
/** @brief RESP3 data types.
The RESP3 specification can be found at https://github.com/redis/redis-specifications/blob/master/protocol/RESP3.md.
The RESP3 specification can be found at
<a href="https://github.com/redis/redis-specifications/blob/master/protocol/RESP3.md"></a>
*/
enum class type
{ /// Aggregate
array,
/// Aaggregate
push,
/// Aggregate
set,
/// Aggregate
map,
/// Aggregate
attribute,
/// Simple
simple_string,
/// Simple
simple_error,
/// Simple
number,
/// Simple
doublean,
/// Simple
boolean,
/// Simple
big_number,
/// Simple
null,
/// Simple
blob_error,
/// Simple
verbatim_string,
/// Simple
blob_string,
/// Simple
streamed_string,
/// Simple
streamed_string_part,
/// Invalid
invalid
{ /// Aggregate
array,
/// Aaggregate
push,
/// Aggregate
set,
/// Aggregate
map,
/// Aggregate
attribute,
/// Simple
simple_string,
/// Simple
simple_error,
/// Simple
number,
/// Simple
doublean,
/// Simple
boolean,
/// Simple
big_number,
/// Simple
null,
/// Simple
blob_error,
/// Simple
verbatim_string,
/// Simple
blob_string,
/// Simple
streamed_string,
/// Simple
streamed_string_part,
/// Invalid
invalid
};
/** \brief Converts the data type to a string.
* \ingroup high-level-api
* \param t RESP3 type.
/** @brief Converts the data type to a string.
*
* @relates type
* @param t The type to convert.
*/
auto to_string(type t) noexcept -> char const*;
/** \brief Writes the type to the output stream.
* \ingroup high-level-api
* \param os Output stream.
* \param t RESP3 type.
/** @brief Writes the type to the output stream.
*
* @relates type
* @param os Output stream.
* @param t The type to stream.
*/
auto operator<<(std::ostream& os, type t) -> std::ostream&;
/* Checks whether the data type is an aggregate.
/** @brief Checks whether the data type is an aggregate.
*
* @relates type
* @param t The type to check.
* @returns True if the given type is an aggregate.
*/
constexpr auto is_aggregate(type t) noexcept -> bool
{
@@ -81,7 +88,7 @@ constexpr auto is_aggregate(type t) noexcept -> bool
case type::set:
case type::map:
case type::attribute: return true;
default: return false;
default: return false;
}
}
@@ -92,7 +99,7 @@ constexpr auto element_multiplicity(type t) noexcept -> std::size_t
switch (t) {
case type::map:
case type::attribute: return 2ULL;
default: return 1ULL;
default: return 1ULL;
}
}
@@ -141,10 +148,10 @@ constexpr auto to_type(char c) noexcept -> type
case '*': return type::array;
case '|': return type::attribute;
case '%': return type::map;
default: return type::invalid;
default: return type::invalid;
}
}
} // boost::redis::resp3
} // namespace boost::redis::resp3
#endif // BOOST_REDIS_RESP3_TYPE_HPP
#endif // BOOST_REDIS_RESP3_TYPE_HPP

View File

@@ -1,4 +1,4 @@
/* Copyright (c) 2018-2024 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2025 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
@@ -7,25 +7,22 @@
#ifndef BOOST_REDIS_RESPONSE_HPP
#define BOOST_REDIS_RESPONSE_HPP
#include <boost/redis/resp3/node.hpp>
#include <boost/redis/adapter/result.hpp>
#include <boost/system.hpp>
#include <boost/redis/resp3/node.hpp>
#include <boost/system/error_code.hpp>
#include <vector>
#include <string>
#include <tuple>
#include <vector>
namespace boost::redis
{
namespace boost::redis {
/** @brief Response with compile-time size.
* @ingroup high-level-api
*/
/// Response with compile-time size.
template <class... Ts>
using response = std::tuple<adapter::result<Ts>...>;
/** @brief A generic response to a request
* @ingroup high-level-api
*
* This response type can store any type of RESP3 data structure. It
* contains the
@@ -47,7 +44,7 @@ using generic_response = adapter::result<std::vector<resp3::node>>;
* req.push("PING", "three");
*
* generic_response resp;
* co_await conn->async_exec(req, resp, asio::deferred);
* co_await conn.async_exec(req, resp);
*
* std::cout << "PING: " << resp.value().front().value << std::endl;
* consume_one(resp);
@@ -56,7 +53,7 @@ using generic_response = adapter::result<std::vector<resp3::node>>;
* std::cout << "PING: " << resp.value().front().value << std::endl;
* @endcode
*
* is
* Is:
*
* @code
* PING: one
@@ -68,13 +65,20 @@ using generic_response = adapter::result<std::vector<resp3::node>>;
* 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.
* responses might be introduced to consume in O(1) operations.
*
* @param r The response to modify.
* @param ec Will be populated in case of error.
*/
void consume_one(generic_response& r, system::error_code& ec);
/// Throwing overload of `consume_one`.
/**
* @brief Throwing overload of `consume_one`.
*
* @param r The response to modify.
*/
void consume_one(generic_response& r);
} // boost::redis
} // namespace boost::redis
#endif // BOOST_REDIS_RESPONSE_HPP
#endif // BOOST_REDIS_RESPONSE_HPP

View File

@@ -1,16 +1,21 @@
/* Copyright (c) 2018-2024 Marcelo Zimbres Silva (mzimbres@gmail.com)
/* Copyright (c) 2018-2025 Marcelo Zimbres Silva (mzimbres@gmail.com)
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/
#include <boost/redis/impl/error.ipp>
#include <boost/redis/impl/logger.ipp>
#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/connection_logger.ipp>
#include <boost/redis/impl/error.ipp>
#include <boost/redis/impl/exec_fsm.ipp>
#include <boost/redis/impl/ignore.ipp>
#include <boost/redis/impl/logger.ipp>
#include <boost/redis/impl/multiplexer.ipp>
#include <boost/redis/impl/read_buffer.ipp>
#include <boost/redis/impl/reader_fsm.ipp>
#include <boost/redis/impl/request.ipp>
#include <boost/redis/impl/resp3_handshaker.ipp>
#include <boost/redis/resp3/impl/type.ipp>
#include <boost/redis/impl/response.ipp>
#include <boost/redis/resp3/impl/parser.ipp>
#include <boost/redis/resp3/impl/serialization.ipp>
#include <boost/redis/resp3/impl/type.ipp>

View File

@@ -7,15 +7,15 @@
#ifndef BOOST_REDIS_USAGE_HPP
#define BOOST_REDIS_USAGE_HPP
namespace boost::redis
{
#include <cstddef>
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 exactly
* @note To simplify the implementation, @ref commands_sent and
* @ref bytes_sent are computed just before writing to
* the socket. On error, they might not represent exactly
* what has been received by the Redis server.
*/
struct usage {
@@ -38,6 +38,6 @@ struct usage {
std::size_t push_bytes_received = 0;
};
} // boost::redis
} // namespace boost::redis
#endif // BOOST_REDIS_USAGE_HPP
#endif // BOOST_REDIS_USAGE_HPP

View File

@@ -7,7 +7,7 @@
<body>
Automatic redirection failed, please go to
<a href="./doc/html/index.html">./doc/html/index.html</a>
<a href="./doc/html/redis/index.html">./doc/html/redis/index.html</a>
<hr>
<tt>
Boost.Redis<br>

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