2
0
mirror of https://github.com/boostorg/redis.git synced 2026-01-19 04:42:09 +00:00

Migrates the documentation to Asciidoc/MrDocs (#276)

Fixes some typos

close #247
This commit is contained in:
Anarthal (Rubén Pérez)
2025-07-02 23:27:33 +02:00
committed by GitHub
parent 963ae8d145
commit adf17f2b3b
46 changed files with 3918 additions and 4263 deletions

View File

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

1062
README.md

File diff suppressed because it is too large Load Diff

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

@@ -9,16 +9,7 @@
#include <boost/redis/adapter/detail/response_traits.hpp>
#include <boost/redis/adapter/detail/result_traits.hpp>
#include <boost/redis/resp3/node.hpp>
#include <boost/redis/response.hpp>
#include <boost/mp11.hpp>
#include <boost/system.hpp>
#include <limits>
#include <string_view>
#include <tuple>
#include <variant>
#include <boost/redis/ignore.hpp>
namespace boost::redis::adapter {
@@ -26,13 +17,13 @@ namespace boost::redis::adapter {
*
* 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>
auto boost_redis_adapt(T& t) noexcept
@@ -40,36 +31,9 @@ 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>
auto adapt2(T& t = redis::ignore) noexcept

View File

@@ -20,17 +20,17 @@
namespace boost::redis {
/** @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 {
public:

View File

@@ -12,12 +12,9 @@
#include <boost/system/error_code.hpp>
#include <string>
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.
*/

View File

@@ -17,9 +17,7 @@
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;
@@ -47,12 +45,15 @@ inline bool operator==(error const& a, error const& 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>;
/**
* @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;

View File

@@ -14,9 +14,7 @@
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,40 +22,42 @@ 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"};
/// The UNIX domain socket path where the server is listening. If non-empty,
/// communication with the server will happen using UNIX domain sockets, and addr will be ignored.
/// UNIX domain sockets can't be used with SSL: if `unix_socket` is non-empty, `use_ssl` must be false.
/**
* @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](https://redis.io/commands/hello/) command. If left
* empty `HELLO` will be sent without authentication parameters.
/** @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";
/**
@@ -69,28 +69,26 @@ struct config {
*/
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 a socket read.
/** @brief Maximum size of a socket read, 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.

View File

@@ -407,30 +407,25 @@ logger make_stderr_logger(logger::level lvl, std::string prefix);
} // namespace detail
/** @brief A SSL connection to the Redis server.
* @ingroup high-level-api
*
* This class keeps a healthy connection to the Redis instance where
* commands can be sent at any time. For more details, please see the
* documentation of each individual function.
*
* @tparam Socket The socket type e.g. asio::ip::tcp::socket.
*
* @tparam Executor The executor type used to create any required I/O objects.
*/
template <class Executor>
class basic_connection {
public:
using this_type = basic_connection<Executor>;
/// Type of the next layer
/// (Deprecated) Type of the next layer
BOOST_DEPRECATED("This typedef is deprecated, and will be removed with next_layer().")
typedef asio::ssl::stream<asio::basic_stream_socket<asio::ip::tcp, Executor>> next_layer_type;
/// Executor type
/// The type of the executor associated to this object.
using executor_type = Executor;
/// Returns the associated executor.
executor_type get_executor() noexcept { return writer_timer_.get_executor(); }
/// Rebinds the socket type to another executor.
template <class Executor1>
struct rebind_executor {
@@ -438,9 +433,9 @@ public:
using other = basic_connection<Executor1>;
};
/** @brief Constructor
/** @brief Constructor from an executor.
*
* @param ex Executor on which connection operation will run.
* @param ex Executor used to create all internal I/O objects.
* @param ctx SSL context.
* @param lgr Logger configuration. It can be used to filter messages by level
* and customize logging. By default, `logger::level::info` messages
@@ -461,9 +456,9 @@ public:
writer_timer_.expires_at((std::chrono::steady_clock::time_point::max)());
}
/** @brief Constructor
/** @brief Constructor from an executor and a logger.
*
* @param ex Executor on which connection operation will run.
* @param ex Executor used to create all internal I/O objects.
* @param lgr Logger configuration. It can be used to filter messages by level
* and customize logging. By default, `logger::level::info` messages
* and higher are logged to `stderr`.
@@ -477,7 +472,15 @@ public:
std::move(lgr))
{ }
/// Constructs from a context.
/**
* @brief Constructor from an `io_context`.
*
* @param ioc I/O context used to create all internal I/O objects.
* @param ctx SSL context.
* @param lgr Logger configuration. It can be used to filter messages by level
* and customize logging. By default, `logger::level::info` messages
* and higher are logged to `stderr`.
*/
explicit basic_connection(
asio::io_context& ioc,
asio::ssl::context ctx = asio::ssl::context{asio::ssl::context::tlsv12_client},
@@ -485,40 +488,52 @@ public:
: basic_connection(ioc.get_executor(), std::move(ctx), std::move(lgr))
{ }
/// Constructs from a context.
basic_connection(asio::io_context& ctx, logger lgr)
/**
* @brief Constructor from an `io_context` and a logger.
*
* @param ioc I/O context used to create all internal I/O objects.
* @param lgr Logger configuration. It can be used to filter messages by level
* and customize logging. By default, `logger::level::info` messages
* and higher are logged to `stderr`.
*/
basic_connection(asio::io_context& ioc, logger lgr)
: basic_connection(
ctx.get_executor(),
ioc.get_executor(),
asio::ssl::context{asio::ssl::context::tlsv12_client},
std::move(lgr))
{ }
/** @brief Starts underlying connection operations.
/// Returns the associated executor.
executor_type get_executor() noexcept { return writer_timer_.get_executor(); }
/** @brief Starts the underlying connection operations.
*
* This member function provides the following functionality
* This function establishes a connection to the Redis server and keeps
* it healthy by performing the following operations:
*
* 1. Resolve the address passed on `boost::redis::config::addr`.
* 2. Connect to one of the results obtained in the resolve operation.
* 3. Send a [HELLO](https://redis.io/commands/hello/) command where each of its parameters are read from `cfg`.
* 4. Start a health-check operation where ping commands are sent
* at intervals specified in
* `boost::redis::config::health_check_interval`. The message passed to
* `PING` will be `boost::redis::config::health_check_id`. Passing a
* timeout with value zero will disable health-checks. If the Redis
* @li For TCP connections, resolves the server hostname passed in
* @ref boost::redis::config::addr.
* @li Establishes a physical connection to the server. For TCP connections,
* connects to one of the endpoints obtained during name resolution.
* For UNIX domain socket connections, it connects to @ref boost::redis::config::unix_socket.
* @li If @ref boost::redis::config::use_ssl is `true`, performs the TLS handshake.
* @li Sends a `HELLO` command where
* each of its parameters are read from `cfg`.
* @li Starts a health-check operation where ping commands are sent
* at intervals specified by
* @ref boost::redis::config::health_check_interval.
* The message passed to `PING` will be @ref boost::redis::config::health_check_id.
* Passing an interval with a zero value will disable health-checks. If the Redis
* server does not respond to a health-check within two times the value
* specified here, it will be considered unresponsive and the connection
* will be closed and a new connection will be stablished.
* 5. Starts read and write operations with the Redis
* server. More specifically it will trigger the write of all
* requests i.e. calls to `async_exec` that happened prior to this
* call.
* will be closed.
* @li Starts read and write operations. Requests issued using @ref async_exec
* before `async_run` is called will be written to the server immediately.
*
* When a connection is lost for any reason, a new one is
* stablished automatically. To disable reconnection call
* `boost::redis::connection::cancel(operation::reconnection)`.
*
* @param cfg Configuration paramters.
* @param token Completion token.
* established automatically. To disable reconnection call
* `boost::redis::connection::cancel(operation::reconnection)`
* or set @ref boost::redis::config::reconnect_wait_interval to zero.
*
* The completion token must have the following signature
*
@@ -528,6 +543,9 @@ public:
*
* For example on how to call this function refer to
* cpp20_intro.cpp or any other example.
*
* @param cfg Configuration parameters.
* @param token Completion token.
*/
template <class CompletionToken = asio::default_completion_token_t<executor_type>>
auto async_run(config const& cfg, CompletionToken&& token = {})
@@ -543,7 +561,8 @@ public:
}
/**
* @copydoc async_run
* @brief (Deprecated) Starts the underlying connection operations.
* @copydetail async_run
*
* This function accepts an extra logger parameter. The passed `logger::lvl`
* will be used, but `logger::fn` will be ignored. Instead, a function
@@ -555,6 +574,10 @@ public:
* The logger should be passed to the connection's constructor instead of using this
* function. Use the overload without a logger parameter, instead. This function is
* deprecated and will be removed in subsequent releases.
*
* @param cfg Configuration parameters.
* @param l Logger.
* @param token Completion token.
*/
template <class CompletionToken = asio::default_completion_token_t<executor_type>>
BOOST_DEPRECATED(
@@ -568,13 +591,16 @@ public:
}
/**
* @copydoc async_run
* @brief (Deprecated) Starts the underlying connection operations.
* @copydetail async_run
*
* Uses a default-constructed config object to run the connection.
*
* @par Deprecated
* This function is deprecated and will be removed in subsequent releases.
* Use the overload taking an explicit config object, instead.
*
* @param token Completion token.
*/
template <class CompletionToken = asio::default_completion_token_t<executor_type>>
BOOST_DEPRECATED(
@@ -593,9 +619,7 @@ public:
* to call `async_receive` in a loop.
*
* To cancel an ongoing receive operation apps should call
* `connection::cancel(operation::receive)`.
*
* @param token Completion token.
* `basic_connection::cancel(operation::receive)`.
*
* For an example see cpp20_subscriber.cpp. The completion token must
* have the following signature
@@ -606,6 +630,8 @@ public:
*
* Where the second parameter is the size of the push received in
* bytes.
*
* @param token Completion token.
*/
template <class CompletionToken = asio::default_completion_token_t<executor_type>>
auto async_receive(CompletionToken&& token = {})
@@ -618,10 +644,9 @@ public:
* Receives a server push synchronously by calling `try_receive` on
* the underlying channel. If the operation fails because
* `try_receive` returns `false`, `ec` will be set to
* `boost::redis::error::sync_receive_push_failed`.
* @ref boost::redis::error::sync_receive_push_failed.
*
* @param ec Contains the error if any occurred.
*
* @returns The number of bytes read from the socket.
*/
std::size_t receive(system::error_code& ec)
@@ -652,12 +677,9 @@ public:
* underlying stream. Multiple concurrent calls to this function
* will be automatically queued by the implementation.
*
* @param req Request.
* @param resp Response.
* @param token Completion token.
* For an example see cpp20_echo_server.cpp.
*
* For an example see cpp20_echo_server.cpp. The completion token must
* have the following signature
* The completion token must have the following signature:
*
* @code
* void f(system::error_code, std::size_t);
@@ -670,11 +692,18 @@ public:
* This operation supports per-operation cancellation. The following cancellation types
* are supported:
*
* - `asio::cancellation_type_t::terminal`. Always supported. May cause the current
* @li `asio::cancellation_type_t::terminal`. Always supported. May cause the current
* `async_run` operation to be cancelled.
* - `asio::cancellation_type_t::partial` and `asio::cancellation_type_t::total`.
* @li `asio::cancellation_type_t::partial` and `asio::cancellation_type_t::total`.
* Supported only if the request hasn't been written to the network yet.
*
* @par Object lifetimes
* Both `req` and `res` should be kept alive until the operation completes.
* No copies of the request object are made.
*
* @param req The request to be executed.
* @param resp The response object to parse data into.
* @param token Completion token.
*/
template <
class Response = ignore_t,
@@ -684,10 +713,43 @@ public:
return this->async_exec(req, any_adapter(resp), std::forward<CompletionToken>(token));
}
/** @copydoc async_exec
/** @brief Executes commands on the Redis server asynchronously.
*
* @details This function uses the type-erased @ref any_adapter class, which
* encapsulates a reference to a response object.
* This function sends a request to the Redis server and waits for
* the responses to each individual command in the request. If the
* request contains only commands that don't expect a response,
* the completion occurs after it has been written to the
* underlying stream. Multiple concurrent calls to this function
* will be automatically queued by the implementation.
*
* For an example see cpp20_echo_server.cpp.
*
* The completion token must have the following signature:
*
* @code
* void f(system::error_code, std::size_t);
* @endcode
*
* Where the second parameter is the size of the response received
* in bytes.
*
* @par Per-operation cancellation
* This operation supports per-operation cancellation. The following cancellation types
* are supported:
*
* @li `asio::cancellation_type_t::terminal`. Always supported. May cause the current
* `async_run` operation to be cancelled.
* @li `asio::cancellation_type_t::partial` and `asio::cancellation_type_t::total`.
* Supported only if the request hasn't been written to the network yet.
*
* @par Object lifetimes
* Both `req` and any response object referenced by `adapter`
* should be kept alive until the operation completes.
* No copies of the request object are made.
*
* @param req The request to be executed.
* @param adapter An adapter object referencing a response to place data into.
* @param token Completion token.
*/
template <class CompletionToken = asio::default_completion_token_t<executor_type>>
auto async_exec(request const& req, any_adapter adapter, CompletionToken&& token = {})
@@ -714,14 +776,14 @@ public:
/** @brief Cancel operations.
*
* @li `operation::exec`: Cancels operations started with
* @li `operation::exec`: cancels operations started with
* `async_exec`. Affects only requests that haven't been written
* yet.
* @li operation::run: Cancels the `async_run` operation.
* @li operation::receive: Cancels any ongoing calls to `async_receive`.
* @li operation::all: Cancels all operations listed above.
* @li `operation::run`: cancels the `async_run` operation.
* @li `operation::receive`: cancels any ongoing calls to `async_receive`.
* @li `operation::all`: cancels all operations listed above.
*
* @param op: The operation to be cancelled.
* @param op The operation to be cancelled.
*/
void cancel(operation op = operation::all)
{
@@ -748,37 +810,69 @@ public:
auto run_is_canceled() const noexcept { return mpx_.get_cancel_run_state(); }
/// Returns true if the connection was canceled.
/// Returns true if the connection will try to reconnect if an error is encountered.
bool will_reconnect() const noexcept
{
return cfg_.reconnect_wait_interval != std::chrono::seconds::zero();
}
/// Returns the ssl context.
/**
* @brief (Deprecated) Returns the ssl context.
*
* `ssl::context` has no const methods, so this function should not be called.
* Any TLS configuration should be set up by passing an `ssl::context`
* to the connection's constructor.
*
* @returns The SSL context.
*/
BOOST_DEPRECATED(
"ssl::context has no const methods, so this function should not be called. Set up any "
"required TLS configuration before passing the ssl::context to the connection's constructor.")
auto const& get_ssl_context() const noexcept { return stream_.get_ssl_context(); }
asio::ssl::context const& get_ssl_context() const noexcept { return stream_.get_ssl_context(); }
/// Resets the underlying stream.
/**
* @brief (Deprecated) Resets the underlying stream.
*
* This function is no longer necessary and is currently a no-op.
*/
BOOST_DEPRECATED(
"This function is no longer necessary and is currently a no-op. connection resets the stream "
"internally as required. This function will be removed in subsequent releases")
void reset_stream() { }
/// Returns a reference to the next layer.
/**
* @brief (Deprecated) Returns a reference to the next layer.
*
* This function returns a dummy object for connections using UNIX domain sockets.
*
* @par Deprecated
* Accessing the underlying stream is deprecated and will be removed in the next release.
* Use the other member functions to interact with the connection.
*
* @returns A reference to the underlying SSL stream object.
*/
BOOST_DEPRECATED(
"Accessing the underlying stream is deprecated and will be removed in the next release. Use "
"the other member functions to interact with the connection.")
auto& next_layer() noexcept { return stream_.next_layer(); }
/// Returns a const reference to the next layer.
/**
* @brief (Deprecated) Returns a reference to the next layer.
*
* This function returns a dummy object for connections using UNIX domain sockets.
*
* @par Deprecated
* Accessing the underlying stream is deprecated and will be removed in the next release.
* Use the other member functions to interact with the connection.
*
* @returns A reference to the underlying SSL stream object.
*/
BOOST_DEPRECATED(
"Accessing the underlying stream is deprecated and will be removed in the next release. Use "
"the other member functions to interact with the connection.")
auto const& next_layer() const noexcept { return stream_.next_layer(); }
/// Sets the response object of `async_receive` operations.
/// Sets the response object of @ref async_receive operations.
template <class Response>
void set_receive_response(Response& response)
{
@@ -860,23 +954,22 @@ private:
detail::connection_logger logger_;
};
/** \brief A basic_connection that type erases the executor.
* \ingroup high-level-api
/** @brief A basic_connection that type erases the executor.
*
* This connection type uses the asio::any_io_executor and
* asio::any_completion_token to reduce compilation times.
* This connection type uses `asio::any_io_executor` and
* `asio::any_completion_token` to reduce compilation times.
*
* For documentation of each member function see
* `boost::redis::basic_connection`.
* @ref boost::redis::basic_connection.
*/
class connection {
public:
/// Executor type.
using executor_type = asio::any_io_executor;
/** @brief Constructor
/** @brief Constructor from an executor.
*
* @param ex Executor on which connection operation will run.
* @param ex Executor used to create all internal I/O objects.
* @param ctx SSL context.
* @param lgr Logger configuration. It can be used to filter messages by level
* and customize logging. By default, `logger::level::info` messages
@@ -887,9 +980,9 @@ public:
asio::ssl::context ctx = asio::ssl::context{asio::ssl::context::tlsv12_client},
logger lgr = {});
/** @brief Constructor
/** @brief Constructor from an executor and a logger.
*
* @param ex Executor on which connection operation will run.
* @param ex Executor used to create all internal I/O objects.
* @param lgr Logger configuration. It can be used to filter messages by level
* and customize logging. By default, `logger::level::info` messages
* and higher are logged to `stderr`.
@@ -903,7 +996,15 @@ public:
std::move(lgr))
{ }
/// Constructs from a context.
/**
* @brief Constructor from an `io_context`.
*
* @param ioc I/O context used to create all internal I/O objects.
* @param ctx SSL context.
* @param lgr Logger configuration. It can be used to filter messages by level
* and customize logging. By default, `logger::level::info` messages
* and higher are logged to `stderr`.
*/
explicit connection(
asio::io_context& ioc,
asio::ssl::context ctx = asio::ssl::context{asio::ssl::context::tlsv12_client},
@@ -911,7 +1012,14 @@ public:
: connection(ioc.get_executor(), std::move(ctx), std::move(lgr))
{ }
/// Constructs from a context.
/**
* @brief Constructor from an `io_context` and a logger.
*
* @param ioc I/O context used to create all internal I/O objects.
* @param lgr Logger configuration. It can be used to filter messages by level
* and customize logging. By default, `logger::level::info` messages
* and higher are logged to `stderr`.
*/
connection(asio::io_context& ioc, logger lgr)
: connection(
ioc.get_executor(),
@@ -923,7 +1031,25 @@ public:
executor_type get_executor() noexcept { return impl_.get_executor(); }
/**
* @brief Calls `boost::redis::basic_connection::async_run`.
* @brief Calls @ref boost::redis::basic_connection::async_run.
*
* @param cfg Configuration parameters.
* @param token Completion token.
*/
template <class CompletionToken = asio::deferred_t>
auto async_run(config const& cfg, CompletionToken&& token = {})
{
return asio::async_initiate<CompletionToken, void(boost::system::error_code)>(
[](auto handler, connection* self, config const* cfg) {
self->async_run_impl(*cfg, std::move(handler));
},
token,
this,
&cfg);
}
/**
* @brief (Deprecated) Calls @ref boost::redis::basic_connection::async_run.
*
* This function accepts an extra logger parameter. The passed logger
* will be used by the connection, overwriting any logger passed to the connection's
@@ -933,6 +1059,10 @@ public:
* The logger should be passed to the connection's constructor instead of using this
* function. Use the overload without a logger parameter, instead. This function is
* deprecated and will be removed in subsequent releases.
*
* @param cfg Configuration parameters.
* @param l Logger.
* @param token Completion token.
*/
template <class CompletionToken = asio::deferred_t>
BOOST_DEPRECATED(
@@ -951,37 +1081,36 @@ public:
std::move(l));
}
/// Calls `boost::redis::basic_connection::async_run`.
template <class CompletionToken = asio::deferred_t>
auto async_run(config const& cfg, CompletionToken&& token = {})
{
return asio::async_initiate<CompletionToken, void(boost::system::error_code)>(
[](auto handler, connection* self, config const* cfg) {
self->async_run_impl(*cfg, std::move(handler));
},
token,
this,
&cfg);
}
/// Calls `boost::redis::basic_connection::async_receive`.
/// @copydoc basic_connection::async_receive
template <class CompletionToken = asio::deferred_t>
auto async_receive(CompletionToken&& token = {})
{
return impl_.async_receive(std::forward<CompletionToken>(token));
}
/// Calls `boost::redis::basic_connection::receive`.
/// @copydoc basic_connection::receive
std::size_t receive(system::error_code& ec) { return impl_.receive(ec); }
/// Calls `boost::redis::basic_connection::async_exec`.
/**
* @brief Calls @ref boost::redis::basic_connection::async_exec.
*
* @param req The request to be executed.
* @param resp The response object to parse data into.
* @param token Completion token.
*/
template <class Response = ignore_t, class CompletionToken = asio::deferred_t>
auto async_exec(request const& req, Response& resp = ignore, CompletionToken&& token = {})
{
return async_exec(req, any_adapter(resp), std::forward<CompletionToken>(token));
}
/// Calls `boost::redis::basic_connection::async_exec`.
/**
* @brief Calls @ref boost::redis::basic_connection::async_exec.
*
* @param req The request to be executed.
* @param adapter An adapter object referencing a response to place data into.
* @param token Completion token.
*/
template <class CompletionToken = asio::deferred_t>
auto async_exec(request const& req, any_adapter adapter, CompletionToken&& token = {})
{
@@ -995,45 +1124,54 @@ public:
std::move(adapter));
}
/// Calls `boost::redis::basic_connection::cancel`.
/// @copydoc basic_connection::cancel
void cancel(operation op = operation::all);
/// Calls `boost::redis::basic_connection::will_reconnect`.
/// @copydoc basic_connection::will_reconnect
bool will_reconnect() const noexcept { return impl_.will_reconnect(); }
/// Calls `boost::redis::basic_connection::next_layer`.
/// (Deprecated) Calls @ref boost::redis::basic_connection::next_layer.
BOOST_DEPRECATED(
"Accessing the underlying stream is deprecated and will be removed in the next release. Use "
"the other member functions to interact with the connection.")
auto& next_layer() noexcept { return impl_.stream_.next_layer(); }
asio::ssl::stream<asio::ip::tcp::socket>& next_layer() noexcept
{
return impl_.stream_.next_layer();
}
/// Calls `boost::redis::basic_connection::next_layer`.
/// (Deprecated) Calls @ref boost::redis::basic_connection::next_layer.
BOOST_DEPRECATED(
"Accessing the underlying stream is deprecated and will be removed in the next release. Use "
"the other member functions to interact with the connection.")
auto const& next_layer() const noexcept { return impl_.stream_.next_layer(); }
asio::ssl::stream<asio::ip::tcp::socket> const& next_layer() const noexcept
{
return impl_.stream_.next_layer();
}
/// Calls `boost::redis::basic_connection::reset_stream`.
/// @copydoc basic_connection::reset_stream
BOOST_DEPRECATED(
"This function is no longer necessary and is currently a no-op. connection resets the stream "
"internally as required. This function will be removed in subsequent releases")
void reset_stream() { }
/// Sets the response object of `async_receive` operations.
/// @copydoc basic_connection::set_receive_response
template <class Response>
void set_receive_response(Response& response)
{
impl_.set_receive_response(response);
}
/// Returns connection usage information.
/// @copydoc basic_connection::get_usage
usage get_usage() const noexcept { return impl_.get_usage(); }
/// Returns the ssl context.
/// @copydoc basic_connection::get_ssl_context
BOOST_DEPRECATED(
"ssl::context has no const methods, so this function should not be called. Set up any "
"required TLS configuration before passing the ssl::context to the connection's constructor.")
auto const& get_ssl_context() const noexcept { return impl_.stream_.get_ssl_context(); }
asio::ssl::context const& get_ssl_context() const noexcept
{
return impl_.stream_.get_ssl_context();
}
private:
void async_run_impl(

View File

@@ -13,11 +13,10 @@
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>
auto write(SyncWriteStream& stream, request const& req)
@@ -31,12 +30,11 @@ 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 <
class AsyncWriteStream,

View File

@@ -11,9 +11,7 @@
namespace boost::redis {
/** \brief Generic errors.
* \ingroup high-level-api
*/
/// Generic errors.
enum class error
{
/// Invalid RESP3 type.
@@ -92,10 +90,10 @@ enum class error
unix_sockets_ssl_unsupported,
};
/** \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;

View File

@@ -16,26 +16,24 @@
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

View File

@@ -12,15 +12,12 @@
namespace boost::redis {
/** @brief Defines logging configuration
* @ingroup high-level-api
/** @brief Defines logging configuration.
*
* See the member descriptions for more info.
*/
struct logger {
/** @brief Syslog-like log levels
* @ingroup high-level-api
*/
/// Syslog-like log levels.
enum class level
{
/// Disabled
@@ -54,7 +51,7 @@ struct logger {
/** @brief Constructor from a level.
*
* Constructs a logger with the specified level
* and a logging function that prints messages to stderr.
* and a logging function that prints messages to `stderr`.
*
* @param l The value to set @ref lvl to.
*

View File

@@ -10,10 +10,9 @@
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
{

View File

@@ -23,12 +23,13 @@ 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,58 +40,56 @@ 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}
{ }
//// 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_;
};
//// Returns the number of commands contained in this request.
/// 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_; }
@@ -109,39 +108,43 @@ public:
has_hello_priority_ = false;
}
/// Calls std::string::reserve on the internal storage.
/// Calls `std::string::reserve` on the internal storage.
void reserve(std::size_t new_cap = 0) { payload_.reserve(new_cap); }
/// Returns a const reference to the config object.
[[nodiscard]] auto get_config() const noexcept -> auto const& { return cfg_; }
/// Returns a reference to the config object.
[[nodiscard]] auto get_config() const noexcept -> config const& { return cfg_; }
/// Returns a reference to the config object.
[[nodiscard]] auto get_config() noexcept -> auto& { return cfg_; }
[[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)
@@ -157,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
@@ -167,28 +170,31 @@ 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,
std::string_view cmd,
std::string_view key,
ForwardIterator begin,
ForwardIterator end,
typename std::iterator_traits<ForwardIterator>::value_type* = nullptr)
@@ -213,33 +219,36 @@ 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,
std::string_view cmd,
ForwardIterator begin,
ForwardIterator end,
typename std::iterator_traits<ForwardIterator>::value_type* = nullptr)
@@ -265,15 +274,17 @@ 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,
std::string_view cmd,
std::string_view key,
Range const& range,
decltype(std::begin(range))* = nullptr)
{
@@ -287,9 +298,11 @@ 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(

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.
*/
@@ -56,14 +53,10 @@ auto operator==(basic_node<String> const& a, basic_node<String> const& b)
// 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>;
} // namespace boost::redis::resp3

View File

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

View File

@@ -15,10 +15,10 @@
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
@@ -59,20 +59,26 @@ enum class type
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
{

View File

@@ -18,14 +18,11 @@
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
@@ -69,10 +66,17 @@ using generic_response = adapter::result<std::vector<resp3::node>>;
* 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.
*
* @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);
} // namespace boost::redis

View File

@@ -7,14 +7,15 @@
#ifndef BOOST_REDIS_USAGE_HPP
#define BOOST_REDIS_USAGE_HPP
#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 {

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>

View File

@@ -109,7 +109,7 @@ def _setup_boost(
copytree(
str(source_dir),
str(lib_dir),
ignore=ignore_patterns('__build*__', '.git')
ignore=ignore_patterns('__build*__')
)
# Install Boost dependencies