├── .clang-format ├── .clang-tidy ├── .codecov.yml ├── .github └── workflows │ └── ci.yml ├── BUILD_STATUS.md ├── CMakeLists.txt ├── CMakePresets.json ├── LICENSE.txt ├── README.md ├── benchmarks ├── CMakeLists.txt ├── benchmarks.tex ├── c │ └── libuv │ │ ├── README.md │ │ └── echo_server_direct.c ├── cpp │ └── asio │ │ ├── echo_server_client.cpp │ │ └── echo_server_direct.cpp ├── go │ ├── echo_server_direct.go │ └── echo_server_over_redis.go ├── java │ └── echo_server_direct │ │ └── TcpEchoServer.java ├── nodejs │ ├── README.txt │ ├── echo_server_direct │ │ ├── echo_server_direct.js │ │ └── package.json │ └── echo_server_over_redis │ │ ├── echo_server_over_redis.js │ │ ├── package-lock.json │ │ └── package.json └── rust │ ├── echo_server_direct │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ └── main.rs │ └── echo_server_over_redis │ ├── Cargo.toml │ └── src │ └── main.rs ├── build.jam ├── doc ├── DoxygenLayout.xml ├── Jamfile ├── doxygen-awesome-sidebar-only.css ├── doxygen-awesome.css ├── footer.html ├── header.html └── on-the-costs-of-async-abstractions.md ├── example ├── CMakeLists.txt ├── cpp17_intro.cpp ├── cpp17_intro_sync.cpp ├── cpp20_chat_room.cpp ├── cpp20_containers.cpp ├── cpp20_echo_server.cpp ├── cpp20_intro.cpp ├── cpp20_intro_tls.cpp ├── cpp20_json.cpp ├── cpp20_protobuf.cpp ├── cpp20_resolve_with_sentinel.cpp ├── cpp20_streams.cpp ├── cpp20_subscriber.cpp ├── main.cpp ├── person.proto └── sync_connection.hpp ├── include └── boost │ ├── redis.hpp │ └── redis │ ├── adapter │ ├── adapt.hpp │ ├── any_adapter.hpp │ ├── detail │ │ ├── adapters.hpp │ │ ├── response_traits.hpp │ │ └── result_traits.hpp │ ├── ignore.hpp │ └── result.hpp │ ├── config.hpp │ ├── connection.hpp │ ├── detail │ ├── connector.hpp │ ├── health_checker.hpp │ ├── helper.hpp │ ├── multiplexer.hpp │ ├── resolver.hpp │ ├── resp3_handshaker.hpp │ ├── runner.hpp │ └── write.hpp │ ├── error.hpp │ ├── ignore.hpp │ ├── impl │ ├── connection.ipp │ ├── error.ipp │ ├── ignore.ipp │ ├── logger.ipp │ ├── multiplexer.ipp │ ├── request.ipp │ ├── resp3_handshaker.ipp │ └── response.ipp │ ├── logger.hpp │ ├── operation.hpp │ ├── request.hpp │ ├── resp3 │ ├── impl │ │ ├── parser.ipp │ │ ├── serialization.ipp │ │ └── type.ipp │ ├── node.hpp │ ├── parser.hpp │ ├── serialization.hpp │ └── type.hpp │ ├── response.hpp │ ├── src.hpp │ └── usage.hpp ├── index.html ├── meta └── libraries.json ├── test ├── CMakeLists.txt ├── Jamfile ├── boost_redis.cpp ├── cmake_b2_test │ ├── CMakeLists.txt │ └── main.cpp ├── cmake_install_test │ ├── CMakeLists.txt │ └── main.cpp ├── cmake_subdir_test │ ├── CMakeLists.txt │ └── main.cpp ├── common.cpp ├── common.hpp ├── test_any_adapter.cpp ├── test_conn_check_health.cpp ├── test_conn_echo_stress.cpp ├── test_conn_exec.cpp ├── test_conn_exec_cancel.cpp ├── test_conn_exec_cancel2.cpp ├── test_conn_exec_error.cpp ├── test_conn_exec_retry.cpp ├── test_conn_push.cpp ├── test_conn_quit.cpp ├── test_conn_reconnect.cpp ├── test_conn_tls.cpp ├── test_conversions.cpp ├── test_issue_181.cpp ├── test_issue_50.cpp ├── test_low_level.cpp ├── test_low_level_sync_sans_io.cpp ├── test_request.cpp └── test_run.cpp └── tools ├── ci.py ├── docker-compose.yml ├── tls ├── ca.crt ├── ca.key ├── server-key.key └── server.crt └── user-config.jam /.codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | max_report_age: off 3 | require_ci_to_pass: yes 4 | notify: 5 | after_n_builds: 1 6 | wait_for_ci: yes 7 | 8 | ignore: 9 | - "benchmarks/cpp/asio/*" 10 | - "example/*" 11 | - "tests/*" 12 | - "/usr/*" 13 | - "**/boost/*" 14 | 15 | parsers: 16 | gcov: 17 | branch_detection: 18 | conditional: no 19 | loop: no 20 | method: no 21 | macro: no 22 | 23 | -------------------------------------------------------------------------------- /BUILD_STATUS.md: -------------------------------------------------------------------------------- 1 | Branch | GH Actions | codecov.io | 2 | :-------------: | ---------- | ---------- | 3 | [`master`](https://github.com/mzimbres/aedis/tree/master) | [![CI](https://github.com/mzimbres/aedis/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/mzimbres/aedis/actions/workflows/ci.yml) | [![codecov](https://codecov.io/gh/mzimbres/aedis/branch/master/graph/badge.svg)](https://codecov.io/gh/mzimbres/aedis/branch/master) 4 | 5 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8...3.20) 2 | 3 | # determine whether it's main/root project 4 | # or being built under another project. 5 | if (NOT DEFINED BOOST_REDIS_MAIN_PROJECT) 6 | set(BOOST_REDIS_MAIN_PROJECT OFF) 7 | if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) 8 | set(BOOST_REDIS_MAIN_PROJECT ON) 9 | endif() 10 | endif() 11 | 12 | project(boost_redis VERSION "${BOOST_SUPERPROJECT_VERSION}" LANGUAGES CXX) 13 | 14 | # Library 15 | add_library(boost_redis INTERFACE) 16 | add_library(Boost::redis ALIAS boost_redis) 17 | target_include_directories(boost_redis INTERFACE include) 18 | target_compile_features(boost_redis INTERFACE cxx_std_17) 19 | 20 | # Dependencies 21 | if (BOOST_REDIS_MAIN_PROJECT) 22 | # TODO: Understand why we have to list all dependencies below 23 | # instead of 24 | #set(BOOST_INCLUDE_LIBRARIES redis) 25 | #set(BOOST_EXCLUDE_LIBRARIES redis) 26 | #add_subdirectory(../.. boostorg/boost EXCLUDE_FROM_ALL) 27 | 28 | set(deps 29 | system 30 | assert 31 | config 32 | throw_exception 33 | asio 34 | variant2 35 | mp11 36 | winapi 37 | predef 38 | align 39 | context 40 | core 41 | static_assert 42 | pool 43 | date_time 44 | smart_ptr 45 | exception 46 | integer 47 | move 48 | type_traits 49 | algorithm 50 | utility 51 | io 52 | lexical_cast 53 | numeric/conversion 54 | mpl 55 | range 56 | tokenizer 57 | tuple 58 | array 59 | bind 60 | concept_check 61 | function 62 | iterator 63 | regex 64 | unordered 65 | preprocessor 66 | container 67 | conversion 68 | container_hash 69 | detail 70 | optional 71 | function_types 72 | fusion 73 | intrusive 74 | describe 75 | typeof 76 | functional 77 | test 78 | json 79 | endian 80 | ) 81 | 82 | foreach(dep IN LISTS deps) 83 | add_subdirectory(../${dep} boostorg/${dep}) 84 | endforeach() 85 | 86 | find_package(Threads REQUIRED) 87 | find_package(OpenSSL REQUIRED) 88 | target_link_libraries(boost_redis 89 | INTERFACE 90 | Boost::system 91 | Boost::asio 92 | Threads::Threads 93 | OpenSSL::Crypto 94 | OpenSSL::SSL 95 | ) 96 | else() 97 | # If we're in the superproject or called from add_subdirectory, 98 | # Boost dependencies should be already available. 99 | # If other dependencies are not found, we bail out 100 | find_package(Threads) 101 | if(NOT Threads_FOUND) 102 | message(STATUS "Boost.Redis has been disabled, because the required package Threads hasn't been found") 103 | return() 104 | endif() 105 | find_package(OpenSSL) 106 | if(NOT OpenSSL_FOUND) 107 | message(STATUS "Boost.Redis has been disabled, because the required package OpenSSL hasn't been found") 108 | return() 109 | endif() 110 | 111 | # This is generated by boostdep 112 | target_link_libraries(boost_redis 113 | INTERFACE 114 | Boost::asio 115 | Boost::assert 116 | Boost::core 117 | Boost::mp11 118 | Boost::system 119 | Boost::throw_exception 120 | Threads::Threads 121 | OpenSSL::Crypto 122 | OpenSSL::SSL 123 | ) 124 | endif() 125 | 126 | # Enable testing. If we're being called from the superproject, this has already been done 127 | if (BOOST_REDIS_MAIN_PROJECT) 128 | include(CTest) 129 | endif() 130 | 131 | # Most tests require a running Redis server, so we only run them if we're the main project 132 | if(BOOST_REDIS_MAIN_PROJECT AND BUILD_TESTING) 133 | # Tests and common utilities 134 | add_subdirectory(test) 135 | 136 | # Benchmarks. Build them with tests to prevent code rotting 137 | add_subdirectory(benchmarks) 138 | 139 | # Examples 140 | add_subdirectory(example) 141 | endif() 142 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /benchmarks/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | add_library(benchmarks_options INTERFACE) 3 | target_link_libraries(benchmarks_options INTERFACE boost_redis_src) 4 | target_link_libraries(benchmarks_options INTERFACE boost_redis_project_options) 5 | target_compile_features(benchmarks_options INTERFACE cxx_std_20) 6 | 7 | add_executable(echo_server_client cpp/asio/echo_server_client.cpp) 8 | target_link_libraries(echo_server_client PRIVATE benchmarks_options) 9 | 10 | add_executable(echo_server_direct cpp/asio/echo_server_direct.cpp) 11 | target_link_libraries(echo_server_direct PRIVATE benchmarks_options) 12 | 13 | # TODO 14 | #======================================================================= 15 | 16 | #.PHONY: bench 17 | #bench: 18 | # pdflatex --jobname=echo-f0 benchmarks/benchmarks.tex 19 | # pdflatex --jobname=echo-f1 benchmarks/benchmarks.tex 20 | # pdftoppm {input.pdf} {output.file} -png -------------------------------------------------------------------------------- /benchmarks/benchmarks.tex: -------------------------------------------------------------------------------- 1 | \documentclass{article} 2 | \usepackage{pgfplots} 3 | \pgfrealjobname{echo} 4 | \pgfplotsset{compat=newest} 5 | 6 | \begin{document} 7 | 8 | \beginpgfgraphicnamed{echo-f0} 9 | % time ./echo_server_client 1000 5000 10 | \begin{tikzpicture}[scale=1.0] 11 | \begin{axis}[ 12 | y dir=reverse, 13 | %xbar stacked, 14 | xbar, xmin=0, 15 | %hide x axis, 16 | bar shift=0pt, 17 | width=15cm, height=6cm, enlarge y limits=0.5, 18 | title={TCP Echo Server Performance}, 19 | xlabel={Seconds}, 20 | symbolic y coords={Asio,Tokio,Go,Libuv,Nodejs}, 21 | ytick=data, 22 | %bar width=1cm, 23 | nodes near coords, 24 | nodes near coords align={horizontal}, 25 | ] 26 | \addplot coordinates { 27 | (29.5,Asio) 28 | (30.7,Tokio) 29 | (35.6,Go) 30 | (43.6,Libuv) 31 | (74.2,Nodejs) 32 | }; 33 | \end{axis} 34 | \end{tikzpicture} 35 | \endpgfgraphicnamed 36 | 37 | \beginpgfgraphicnamed{echo-f1} 38 | %$ time ./echo_server_client 1000 1000 39 | \begin{tikzpicture}[scale=1.0] 40 | \begin{axis}[ 41 | y dir=reverse, 42 | %xbar stacked, 43 | xbar, xmin=0, 44 | %hide x axis, 45 | bar shift=0pt, 46 | width=12cm, height=6cm, enlarge y limits=0.5, 47 | title={TCP Echo Server Performance (over Redis)}, 48 | xlabel={Seconds}, 49 | symbolic y coords={Aedis,Rust-rs,Libuv,Node-redis,Go-redis}, 50 | ytick=data, 51 | %bar width=1cm, 52 | nodes near coords, 53 | nodes near coords align={horizontal}, 54 | ] 55 | \addplot coordinates { 56 | (12.6,Aedis) 57 | (28.8,Node-redis) 58 | (352.4,Go-redis) 59 | }; 60 | %\addplot coordinates { 61 | % (30.0,Asio) 62 | % (90.6,Rust-rs) 63 | % (0.0,Libuv) 64 | % (68.9,Nodejs) 65 | % (0.0,Go) 66 | %}; 67 | \end{axis} 68 | \end{tikzpicture} 69 | \endpgfgraphicnamed 70 | 71 | \end{document} 72 | -------------------------------------------------------------------------------- /benchmarks/c/libuv/README.md: -------------------------------------------------------------------------------- 1 | This example was taken from 2 | 3 | https://github.com/libuv/libuv/tree/v1.x/docs/code/tcp-echo-server 4 | 5 | To build it run, for example 6 | 7 | $ gcc echo_server_direct.c -luv -O2 -o echo_server_direct 8 | -------------------------------------------------------------------------------- /benchmarks/c/libuv/echo_server_direct.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #define DEFAULT_PORT 55555 7 | #define DEFAULT_BACKLOG 1024 8 | 9 | uv_loop_t *loop; 10 | struct sockaddr_in addr; 11 | 12 | typedef struct { 13 | uv_write_t req; 14 | uv_buf_t buf; 15 | } write_req_t; 16 | 17 | void free_write_req(uv_write_t *req) { 18 | write_req_t *wr = (write_req_t*) req; 19 | free(wr->buf.base); 20 | free(wr); 21 | } 22 | 23 | void alloc_buffer(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf) { 24 | buf->base = (char*) malloc(suggested_size); 25 | buf->len = suggested_size; 26 | } 27 | 28 | void on_close(uv_handle_t* handle) { 29 | free(handle); 30 | } 31 | 32 | void echo_write(uv_write_t *req, int status) { 33 | if (status) { 34 | fprintf(stderr, "Write error %s\n", uv_strerror(status)); 35 | } 36 | free_write_req(req); 37 | } 38 | 39 | void echo_read(uv_stream_t *client, ssize_t nread, const uv_buf_t *buf) { 40 | if (nread > 0) { 41 | write_req_t *req = (write_req_t*) malloc(sizeof(write_req_t)); 42 | req->buf = uv_buf_init(buf->base, nread); 43 | uv_write((uv_write_t*) req, client, &req->buf, 1, echo_write); 44 | return; 45 | } 46 | if (nread < 0) { 47 | if (nread != UV_EOF) 48 | fprintf(stderr, "Read error %s\n", uv_err_name(nread)); 49 | uv_close((uv_handle_t*) client, on_close); 50 | } 51 | 52 | free(buf->base); 53 | } 54 | 55 | void on_new_connection(uv_stream_t *server, int status) { 56 | if (status < 0) { 57 | fprintf(stderr, "New connection error %s\n", uv_strerror(status)); 58 | // error! 59 | return; 60 | } 61 | 62 | uv_tcp_t *client = (uv_tcp_t*) malloc(sizeof(uv_tcp_t)); 63 | uv_tcp_init(loop, client); 64 | if (uv_accept(server, (uv_stream_t*) client) == 0) { 65 | uv_read_start((uv_stream_t*) client, alloc_buffer, echo_read); 66 | } 67 | else { 68 | uv_close((uv_handle_t*) client, on_close); 69 | } 70 | } 71 | 72 | int main() { 73 | loop = uv_default_loop(); 74 | 75 | uv_tcp_t server; 76 | uv_tcp_init(loop, &server); 77 | 78 | uv_ip4_addr("0.0.0.0", DEFAULT_PORT, &addr); 79 | 80 | uv_tcp_bind(&server, (const struct sockaddr*)&addr, 0); 81 | int r = uv_listen((uv_stream_t*) &server, DEFAULT_BACKLOG, on_new_connection); 82 | if (r) { 83 | fprintf(stderr, "Listen error %s\n", uv_strerror(r)); 84 | return 1; 85 | } 86 | return uv_run(loop, UV_RUN_DEFAULT); 87 | } 88 | -------------------------------------------------------------------------------- /benchmarks/cpp/asio/echo_server_client.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com) 2 | * 3 | * Distributed under the Boost Software License, Version 1.0. (See 4 | * accompanying file LICENSE.txt) 5 | */ 6 | 7 | #include 8 | 9 | #include 10 | #if defined(BOOST_ASIO_HAS_CO_AWAIT) 11 | 12 | namespace net = boost::asio; 13 | 14 | using net::ip::tcp; 15 | using tcp_socket = net::use_awaitable_t<>::as_default_on_t; 16 | using timer_type = net::use_awaitable_t<>::as_default_on_t; 17 | 18 | auto example(boost::asio::ip::tcp::endpoint ep, std::string msg, int n) -> net::awaitable 19 | { 20 | try { 21 | auto ex = co_await net::this_coro::executor; 22 | 23 | tcp_socket socket{ex}; 24 | co_await socket.async_connect(ep); 25 | 26 | std::string buffer; 27 | auto dbuffer = net::dynamic_buffer(buffer); 28 | for (int i = 0; i < n; ++i) { 29 | co_await net::async_write(socket, net::buffer(msg)); 30 | auto bytes_read = co_await net::async_read_until(socket, dbuffer, "\n"); 31 | //std::printf("> %s", buffer.data()); 32 | dbuffer.consume(bytes_read); 33 | } 34 | 35 | //std::printf("Ok: %s", msg.data()); 36 | } catch (std::exception const& e) { 37 | std::cerr << "Error: " << e.what() << std::endl; 38 | } 39 | } 40 | 41 | int main(int argc, char* argv[]) 42 | { 43 | try { 44 | int sessions = 1; 45 | int msgs = 1; 46 | 47 | if (argc == 3) { 48 | sessions = std::stoi(argv[1]); 49 | msgs = std::stoi(argv[2]); 50 | } 51 | 52 | net::io_context ioc; 53 | 54 | tcp::resolver resv{ioc}; 55 | auto const res = resv.resolve("127.0.0.1", "55555"); 56 | auto ep = *std::begin(res); 57 | 58 | for (int i = 0; i < sessions; ++i) 59 | net::co_spawn(ioc, example(ep, "Some message\n", msgs), net::detached); 60 | 61 | ioc.run(); 62 | } catch (std::exception const& e) { 63 | std::cerr << e.what() << std::endl; 64 | } 65 | } 66 | #else // defined(BOOST_ASIO_HAS_CO_AWAIT) 67 | auto main() -> int 68 | { 69 | std::cout << "Requires coroutine support." << std::endl; 70 | return 1; 71 | } 72 | #endif // defined(BOOST_ASIO_HAS_CO_AWAIT) 73 | -------------------------------------------------------------------------------- /benchmarks/cpp/asio/echo_server_direct.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // echo_server.cpp 3 | // ~~~~~~~~~~~~~~~ 4 | // 5 | // Copyright (c) 2003-2022 Christopher M. Kohlhoff (chris at kohlhoff dot com) 6 | // 7 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 8 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 9 | // 10 | 11 | #include 12 | 13 | #include 14 | #include 15 | #if defined(BOOST_ASIO_HAS_CO_AWAIT) 16 | 17 | namespace net = boost::asio; 18 | namespace this_coro = net::this_coro; 19 | using net::ip::tcp; 20 | using net::detached; 21 | using executor_type = net::io_context::executor_type; 22 | using socket_type = net::basic_stream_socket; 23 | using tcp_socket = net::use_awaitable_t::as_default_on_t; 24 | using acceptor_type = net::basic_socket_acceptor; 25 | using tcp_acceptor = net::use_awaitable_t::as_default_on_t; 26 | using awaitable_type = net::awaitable; 27 | constexpr net::use_awaitable_t use_awaitable; 28 | 29 | awaitable_type echo(tcp_socket socket) 30 | { 31 | try { 32 | char data[1024]; 33 | for (;;) { 34 | std::size_t n = co_await socket.async_read_some(net::buffer(data), use_awaitable); 35 | co_await async_write(socket, net::buffer(data, n), use_awaitable); 36 | } 37 | } catch (std::exception const&) { 38 | //std::printf("echo Exception: %s\n", e.what()); 39 | } 40 | } 41 | 42 | awaitable_type listener() 43 | { 44 | auto ex = co_await this_coro::executor; 45 | tcp_acceptor acceptor(ex, {tcp::v4(), 55555}); 46 | for (;;) { 47 | tcp_socket socket = co_await acceptor.async_accept(use_awaitable); 48 | co_spawn(ex, echo(std::move(socket)), detached); 49 | } 50 | } 51 | 52 | int main() 53 | { 54 | try { 55 | net::io_context io_context{BOOST_ASIO_CONCURRENCY_HINT_UNSAFE_IO}; 56 | co_spawn(io_context, listener(), detached); 57 | io_context.run(); 58 | } catch (std::exception const& e) { 59 | std::printf("Exception: %s\n", e.what()); 60 | } 61 | } 62 | #else // defined(BOOST_ASIO_HAS_CO_AWAIT) 63 | auto main() -> int 64 | { 65 | std::cout << "Requires coroutine support." << std::endl; 66 | return 1; 67 | } 68 | #endif // defined(BOOST_ASIO_HAS_CO_AWAIT) 69 | -------------------------------------------------------------------------------- /benchmarks/go/echo_server_direct.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "os" 7 | "runtime" 8 | ) 9 | 10 | func echo(conn net.Conn) { 11 | buf := make([]byte, 1024) 12 | for { 13 | n, err := conn.Read(buf) 14 | if err != nil { 15 | break; 16 | } 17 | 18 | conn.Write(buf[:n]) 19 | if err != nil { 20 | fmt.Println("ERROR", err) 21 | os.Exit(1) 22 | } 23 | } 24 | } 25 | 26 | func main() { 27 | runtime.GOMAXPROCS(1) 28 | 29 | l, err := net.Listen("tcp", "0.0.0.0:55555") 30 | if err != nil { 31 | fmt.Println("ERROR", err) 32 | os.Exit(1) 33 | } 34 | 35 | for { 36 | conn, err := l.Accept() 37 | if err != nil { 38 | fmt.Println("ERROR", err) 39 | continue 40 | } 41 | go echo(conn) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /benchmarks/go/echo_server_over_redis.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "github.com/go-redis/redis/v8" 6 | "bufio" 7 | "fmt" 8 | "io" 9 | "net" 10 | "os" 11 | ) 12 | 13 | var ctx = context.Background() 14 | var rdb = redis.NewClient(&redis.Options{Addr: "db.occase.de:6379", Password: "", DB: 0,}) 15 | 16 | func echo(conn net.Conn) { 17 | r := bufio.NewReader(conn) 18 | for { 19 | line, err := r.ReadBytes(byte('\n')) 20 | switch err { 21 | case nil: 22 | break 23 | case io.EOF: 24 | default: 25 | fmt.Println("ERROR", err) 26 | } 27 | 28 | err2 := rdb.Ping(ctx).Err() 29 | if err2 != nil { 30 | fmt.Println("ERROR", err2) 31 | panic(err2) 32 | } 33 | 34 | conn.Write(line) 35 | } 36 | } 37 | 38 | func main() { 39 | l, err := net.Listen("tcp", "0.0.0.0:55555") 40 | if err != nil { 41 | fmt.Println("ERROR", err) 42 | os.Exit(1) 43 | } 44 | 45 | for { 46 | conn, err := l.Accept() 47 | if err != nil { 48 | fmt.Println("ERROR", err) 49 | continue 50 | } 51 | 52 | go echo(conn) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /benchmarks/java/echo_server_direct/TcpEchoServer.java: -------------------------------------------------------------------------------- 1 | import java.nio.*; 2 | import java.nio.channels.*; 3 | import java.net.*; 4 | import java.util.*; 5 | import java.io.IOException; 6 | 7 | public class TcpEchoServer { 8 | 9 | public static int DEFAULT_PORT = 55555; 10 | 11 | public static void main(String[] args) { 12 | int port; 13 | try { 14 | port = Integer.parseInt(args[0]); 15 | } 16 | 17 | catch (Exception ex) { 18 | port = DEFAULT_PORT; 19 | } 20 | 21 | //System.out.println("Listening for connections on port " + port); 22 | ServerSocketChannel serverChannel; 23 | Selector selector; 24 | try { 25 | serverChannel = ServerSocketChannel.open( ); 26 | ServerSocket ss = serverChannel.socket( ); 27 | InetSocketAddress address = new InetSocketAddress(port); 28 | ss.bind(address); 29 | serverChannel.configureBlocking(false); 30 | selector = Selector.open( ); 31 | serverChannel.register(selector, SelectionKey.OP_ACCEPT); 32 | } 33 | catch (IOException ex) { 34 | ex.printStackTrace( ); 35 | return; 36 | } 37 | while (true) { 38 | try { 39 | selector.select( ); 40 | } 41 | catch (IOException ex) { 42 | ex.printStackTrace( ); 43 | break; 44 | } 45 | Set readyKeys = selector.selectedKeys( ); 46 | Iterator iterator = readyKeys.iterator( ); 47 | while (iterator.hasNext( )) { 48 | SelectionKey key = (SelectionKey) iterator.next( ); 49 | iterator.remove( ); 50 | try { 51 | if (key.isAcceptable( )) { 52 | ServerSocketChannel server = (ServerSocketChannel ) key.channel( ); 53 | SocketChannel client = server.accept( ); 54 | //System.out.println("Accepted connection from " + client); 55 | client.configureBlocking(false); 56 | SelectionKey clientKey = client.register( 57 | selector, SelectionKey.OP_WRITE | SelectionKey.OP_READ); 58 | ByteBuffer buffer = ByteBuffer.allocate(100); 59 | clientKey.attach(buffer); 60 | //System.out.println(buffer.toString()); 61 | 62 | } 63 | 64 | if (key.isReadable( )) { 65 | SocketChannel client = (SocketChannel) key.channel( ); 66 | ByteBuffer output = (ByteBuffer) key.attachment( ); 67 | client.read(output); 68 | } 69 | 70 | if (key.isWritable( )) { 71 | SocketChannel client = (SocketChannel) key.channel( ); 72 | ByteBuffer output = (ByteBuffer) key.attachment( ); 73 | output.flip( ); 74 | client.write(output); 75 | output.compact( ); 76 | 77 | } 78 | 79 | } 80 | 81 | catch (IOException ex) { 82 | key.cancel( ); 83 | try { 84 | key.channel().close(); 85 | } 86 | 87 | catch (IOException cex) {} 88 | 89 | } 90 | 91 | 92 | 93 | } 94 | 95 | 96 | 97 | } 98 | 99 | 100 | 101 | } 102 | 103 | 104 | 105 | } 106 | -------------------------------------------------------------------------------- /benchmarks/nodejs/README.txt: -------------------------------------------------------------------------------- 1 | 2 | $ npm install 3 | 4 | $ node echo_server_over_redis.js 5 | -------------------------------------------------------------------------------- /benchmarks/nodejs/echo_server_direct/echo_server_direct.js: -------------------------------------------------------------------------------- 1 | var net = require('net'); 2 | 3 | net.createServer(function(socket){ 4 | socket.on('data', function(data){ 5 | socket.write(data.toString()) 6 | }); 7 | }).listen(55555); 8 | -------------------------------------------------------------------------------- /benchmarks/nodejs/echo_server_direct/package.json: -------------------------------------------------------------------------------- 1 | { 2 | } 3 | -------------------------------------------------------------------------------- /benchmarks/nodejs/echo_server_over_redis/echo_server_over_redis.js: -------------------------------------------------------------------------------- 1 | import { createClient } from 'redis'; 2 | import * as net from 'net'; 3 | 4 | const client = createClient({url: 'redis://aedis.occase.de:63799' }); 5 | client.on('error', (err) => console.log('Redis Client Error', err)); 6 | await client.connect(); 7 | 8 | net.createServer(function(socket){ 9 | socket.on('data', async function(data) { 10 | const value = await client.ping(data.toString()); 11 | socket.write(data) 12 | }); 13 | }).listen(55555); 14 | -------------------------------------------------------------------------------- /benchmarks/nodejs/echo_server_over_redis/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "dependencies": { 4 | "redis": "^4.2.0" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /benchmarks/rust/echo_server_direct/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "echo_server_direct" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | tokio = { version = "1.0", features = ["full"] } 10 | -------------------------------------------------------------------------------- /benchmarks/rust/echo_server_direct/src/main.rs: -------------------------------------------------------------------------------- 1 | use tokio::net::TcpListener; 2 | use tokio::io::AsyncReadExt; 3 | use tokio::io::AsyncWriteExt; 4 | 5 | #[tokio::main(flavor = "current_thread")] 6 | async fn main() -> Result<(), Box> { 7 | let listener = TcpListener::bind("127.0.0.1:55555").await?; 8 | 9 | loop { 10 | let (mut socket, _) = listener.accept().await?; 11 | 12 | tokio::spawn(async move { 13 | let mut buf = [0; 1024]; 14 | 15 | // In a loop, read data from the socket and write the data back. 16 | loop { 17 | let n = match socket.read(&mut buf).await { 18 | // socket closed 19 | Ok(n) if n == 0 => return, 20 | Ok(n) => n, 21 | Err(e) => { 22 | eprintln!("failed to read from socket; err = {:?}", e); 23 | return; 24 | } 25 | }; 26 | 27 | // Write the data back 28 | if let Err(e) = socket.write_all(&buf[0..n]).await { 29 | eprintln!("failed to write to socket; err = {:?}", e); 30 | return; 31 | } 32 | } 33 | }); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /benchmarks/rust/echo_server_over_redis/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "echo_server_over_redis" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | tokio = { version = "1.16.1", features = ["full"] } 10 | redis = { version = "0.21.5", features = ["tokio-comp"] } 11 | futures = "0.3" 12 | -------------------------------------------------------------------------------- /benchmarks/rust/echo_server_over_redis/src/main.rs: -------------------------------------------------------------------------------- 1 | use tokio::net::TcpListener; 2 | use tokio::io::AsyncReadExt; 3 | use tokio::io::AsyncWriteExt; 4 | use tokio::sync::Mutex; 5 | use std::sync::{Arc}; 6 | 7 | #[tokio::main] 8 | async fn main() -> Result<(), Box> { 9 | let listener = TcpListener::bind("127.0.0.1:55555").await?; 10 | let client = redis::Client::open("redis://db.occase.de/").unwrap(); 11 | let con = Arc::new(Mutex::new(client.get_async_connection().await?)); 12 | 13 | loop { 14 | let conn = Arc::clone(&con); 15 | let (mut socket, _) = listener.accept().await?; 16 | 17 | tokio::spawn(async move { 18 | let mut buf = [0; 1024]; 19 | 20 | loop { 21 | let n = match socket.read(&mut buf).await { 22 | Ok(n) if n == 0 => return, 23 | Ok(n) => n, 24 | Err(e) => { 25 | eprintln!("failed to read from socket; err = {:?}", e); 26 | return; 27 | } 28 | }; 29 | 30 | let mut local_conn = conn.lock().await; 31 | 32 | let result = 33 | redis::cmd("PING") 34 | .arg(&buf[0..n]) 35 | .query_async::(&mut local_conn).await.unwrap(); 36 | 37 | if let Err(e) = socket.write_all(result.as_bytes()).await { 38 | eprintln!("failed to write to socket; err = {:?}", e); 39 | return; 40 | } 41 | } 42 | }); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /build.jam: -------------------------------------------------------------------------------- 1 | # Copyright René Ferdinand Rivera Morell 2024 2 | # Distributed under the Boost Software License, Version 1.0. 3 | # (See accompanying file LICENSE_1_0.txt or copy at 4 | # http://www.boost.org/LICENSE_1_0.txt) 5 | 6 | require-b2 5.2 ; 7 | 8 | constant boost_dependencies : 9 | /boost/asio//boost_asio 10 | /boost/assert//boost_assert 11 | /boost/core//boost_core 12 | /boost/mp11//boost_mp11 13 | /boost/system//boost_system 14 | /boost/throw_exception//boost_throw_exception ; 15 | 16 | project /boost/redis 17 | : common-requirements 18 | include 19 | ; 20 | 21 | explicit 22 | [ alias boost_redis : : : : $(boost_dependencies) ] 23 | [ alias all : boost_redis test ] 24 | ; 25 | 26 | call-if : boost-library redis 27 | ; 28 | 29 | -------------------------------------------------------------------------------- /doc/Jamfile: -------------------------------------------------------------------------------- 1 | project redis/doc ; 2 | 3 | import doxygen ; 4 | import path ; 5 | import sequence ; 6 | 7 | # All paths must be absolute to work well with the Doxygen rules. 8 | path-constant this_dir : . ; 9 | path-constant target_dir : html ; 10 | path-constant redis_root_dir : .. ; 11 | path-constant include_dir : ../include ; 12 | path-constant examples_dir : ../example ; 13 | path-constant readme : ../README.md ; 14 | path-constant layout_file : DoxygenLayout.xml ; 15 | path-constant header : header.html ; 16 | path-constant footer : footer.html ; 17 | 18 | local stylesheet_files = [ path.glob $(this_dir) : *.css ] ; 19 | local includes = [ path.glob-tree $(include_dir) : *.hpp *.cpp ] ; 20 | local examples = [ path.glob-tree $(examples_dir) : *.hpp *.cpp ] ; 21 | 22 | # If passed directly, several HTML_EXTRA_STYLESHEET tags are generated, 23 | # which is not correct. 24 | local stylesheet_arg = [ sequence.join "\"$(stylesheet_files)\"" : " " ] ; 25 | 26 | # The doxygen rule requires the target name to end in .html to generate HTML files 27 | doxygen doc.html 28 | : 29 | $(includes) $(examples) $(readme) 30 | : 31 | "PROJECT_NAME=Boost.Redis" 32 | PROJECT_NUMBER="1.84.0" 33 | PROJECT_BRIEF="A redis client library" 34 | "STRIP_FROM_PATH=\"$(redis_root_dir)\"" 35 | "STRIP_FROM_INC_PATH=\"$(include_dir)\"" 36 | BUILTIN_STL_SUPPORT=YES 37 | INLINE_SIMPLE_STRUCTS=YES 38 | HIDE_UNDOC_MEMBERS=YES 39 | HIDE_UNDOC_CLASSES=YES 40 | SHOW_HEADERFILE=YES 41 | SORT_BRIEF_DOCS=YES 42 | SORT_MEMBERS_CTORS_1ST=YES 43 | SHOW_FILES=NO 44 | SHOW_NAMESPACES=NO 45 | "LAYOUT_FILE=\"$(layout_file)\"" 46 | WARN_IF_INCOMPLETE_DOC=YES 47 | FILE_PATTERNS="*.hpp *.cpp" 48 | EXCLUDE_SYMBOLS=std 49 | "USE_MDFILE_AS_MAINPAGE=\"$(readme)\"" 50 | SOURCE_BROWSER=YES 51 | "HTML_HEADER=\"$(header)\"" 52 | "HTML_FOOTER=\"$(footer)\"" 53 | "HTML_EXTRA_STYLESHEET=$(stylesheet_arg)" 54 | HTML_TIMESTAMP=YES 55 | GENERATE_TREEVIEW=YES 56 | FULL_SIDEBAR=YES 57 | DISABLE_INDEX=YES 58 | ENUM_VALUES_PER_LINE=0 59 | OBFUSCATE_EMAILS=YES 60 | USE_MATHJAX=YES 61 | MATHJAX_VERSION=MathJax_2 62 | MATHJAX_RELPATH="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/" 63 | MACRO_EXPANSION=YES 64 | HAVE_DOT=NO 65 | CLASS_GRAPH=NO 66 | DIRECTORY_GRAPH=NO 67 | ; 68 | 69 | explicit doc.html ; 70 | 71 | # The doxygen rule only informs b2 about the main HTML file, and not about 72 | # all the doc directory that gets generated. Using the install rule copies 73 | # only a single file, which is incorrect. This is a workaround to copy 74 | # the generated docs to the doc/html directory, where they should be. 75 | make copyhtml.tag : doc.html : @copy_html_dir ; 76 | explicit copyhtml.tag ; 77 | actions copy_html_dir 78 | { 79 | rm -rf $(target_dir) 80 | mkdir -p $(target_dir) 81 | cp -r $(<:D)/html/doc/* $(target_dir)/ 82 | echo "Stamped" > "$(<)" 83 | } 84 | 85 | # These are used to inform the build system of the 86 | # means to build the integrated and stand-alone docs. 87 | 88 | alias boostdoc ; 89 | explicit boostdoc ; 90 | 91 | alias boostrelease : copyhtml.tag ; 92 | explicit boostrelease ; 93 | -------------------------------------------------------------------------------- /doc/doxygen-awesome-sidebar-only.css: -------------------------------------------------------------------------------- 1 | /** 2 | 3 | Doxygen Awesome 4 | https://github.com/jothepro/doxygen-awesome-css 5 | 6 | MIT License 7 | 8 | Copyright (c) 2021 jothepro 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. 27 | 28 | */ 29 | 30 | html { 31 | /* side nav width. MUST be = `TREEVIEW_WIDTH`. 32 | * Make sure it is wide enough to contain the page title (logo + title + version) 33 | */ 34 | --side-nav-fixed-width: 335px; 35 | } 36 | 37 | #projectname { 38 | white-space: nowrap; 39 | } 40 | 41 | #page-wrapper { 42 | height: calc(100vh - 100px); 43 | display: flex; 44 | flex-direction: column; 45 | } 46 | 47 | #content-wrapper { 48 | display: flex; 49 | flex-direction: row; 50 | min-height: 0; 51 | } 52 | 53 | #doc-content { 54 | overflow-y: scroll; 55 | flex: 1; 56 | height: auto !important; 57 | } 58 | 59 | @media (min-width: 768px) { 60 | html { 61 | --searchbar-background: var(--page-background-color); 62 | } 63 | 64 | #sidebar-wrapper { 65 | display: flex; 66 | flex-direction: column; 67 | min-width: var(--side-nav-fixed-width); 68 | max-width: var(--side-nav-fixed-width); 69 | background-color: var(--side-nav-background); 70 | border-right: 1px solid rgb(222, 222, 222); 71 | } 72 | 73 | #search-box-wrapper { 74 | display: flex; 75 | flex-direction: row; 76 | padding-left: 1em; 77 | padding-right: 1em; 78 | } 79 | 80 | #MSearchBox { 81 | flex: 1; 82 | display: flex; 83 | padding-left: 1em; 84 | padding-right: 1em; 85 | } 86 | 87 | 88 | #MSearchBox .left { 89 | display: flex; 90 | flex: 1; 91 | position: static; 92 | align-items: center; 93 | justify-content: flex-start; 94 | width: auto; 95 | height: auto; 96 | } 97 | 98 | #MSearchBox .right { 99 | display: none; 100 | } 101 | 102 | #MSearchSelect { 103 | padding-left: 0.75em; 104 | left: auto; 105 | background-repeat: no-repeat; 106 | } 107 | 108 | #MSearchField { 109 | flex: 1; 110 | position: static; 111 | width: auto; 112 | height: auto; 113 | } 114 | 115 | #nav-tree { 116 | height: auto !important; 117 | } 118 | 119 | #nav-sync { 120 | display: none; 121 | } 122 | 123 | #top { 124 | display: block; 125 | border-bottom: none; 126 | max-width: var(--side-nav-fixed-width); 127 | background: var(--side-nav-background); 128 | } 129 | 130 | .ui-resizable-handle { 131 | cursor: default; 132 | width: 1px !important; 133 | } 134 | 135 | #MSearchResultsWindow { 136 | left: var(--spacing-medium) !important; 137 | right: auto; 138 | } 139 | } 140 | 141 | @media (max-width: 768px) { 142 | #sidebar-wrapper { 143 | display: none; 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /doc/footer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 11 | 12 | 13 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /doc/header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | $projectname: $title 10 | $title 11 | 12 | 13 | 14 | $treeview 15 | $search 16 | $mathjax 17 | 18 | $extrastylesheet 19 | 20 | 21 |
22 |
23 |