├── .clang-format ├── .cmake-format.yaml ├── .github └── workflows │ └── main.yml ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── CPPLINT.cfg ├── LICENSE ├── README.md ├── atframework └── Repository.cmake ├── ci ├── do_ci.ps1 ├── do_ci.sh ├── enable_windows_long_path.reg ├── format.sh └── requirements.txt ├── cmake_dev.sh ├── doc ├── Redis全异步(HA)Driver设计稿.md └── res │ └── 2015 │ ├── Redis全异步(HA)Driver设计稿.vsdx │ ├── redis-ha-cluster.png │ ├── redis-ha-sentinel.png │ └── redis-rb-cluster.png ├── include ├── detail │ ├── crc16.h │ ├── happ_cluster.h │ ├── happ_cmd.h │ ├── happ_connection.h │ ├── happ_raw.h │ └── hiredis_happ_config.h.in └── hiredis_happ.h ├── project └── cmake │ ├── FetchDependeny.cmake │ └── ProjectBuildOption.cmake ├── sample ├── CMakeLists.txt ├── sample.happ-macro.cmake ├── sample_cluster_cli │ ├── CMakeLists.txt │ └── main.cpp └── sample_raw_cli │ ├── CMakeLists.txt │ └── main.cpp ├── src ├── detail │ ├── crc16.cpp │ ├── happ_cluster.cpp │ ├── happ_cmd.cpp │ ├── happ_connection.cpp │ └── happ_raw.cpp └── hiredis_happ.cpp ├── test ├── CMakeLists.txt ├── case │ ├── hiredis_happ_cluster_test.cpp │ ├── hiredis_happ_cmd_test.cpp │ └── hiredis_happ_connection_test.cpp └── test.happ-macro.cmake └── third_party └── Repository.cmake /.clang-format: -------------------------------------------------------------------------------- 1 | Language: Cpp 2 | BasedOnStyle: Google 3 | ColumnLimit: 120 4 | DerivePointerAlignment: true 5 | 6 | # 默认对齐到类型名 7 | PointerAlignment: Left 8 | 9 | # Only sort headers in each include block 10 | SortIncludes: true 11 | IncludeBlocks: Preserve 12 | 13 | # 让 #if/#else/#endif 宏中的内容保持缩进,比如: 14 | # #if defined(WIN) 15 | # # include 16 | # #else 17 | # # include 18 | # #endif 19 | IndentPPDirectives: AfterHash -------------------------------------------------------------------------------- /.cmake-format.yaml: -------------------------------------------------------------------------------- 1 | # @see https://cmake-format.readthedocs.io/en/latest/configuration.html for more details 2 | 3 | format: 4 | line_width: 120 5 | tab_size: 2 6 | use_tabchars: false 7 | line_ending: unix 8 | 9 | markup: 10 | first_comment_is_literal: True 11 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: "main" 2 | 3 | on: # @see https://help.github.com/en/articles/events-that-trigger-workflows#webhook-events 4 | push: 5 | branches: # Array of patterns that match refs/heads 6 | - main 7 | pull_request: 8 | branches: [ main ] 9 | 10 | jobs: 11 | format: 12 | name: Format 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v2 17 | - name: CI Job 18 | shell: bash 19 | run: | 20 | bash ci/do_ci.sh format ; 21 | unix_build: # job id, can be any string 22 | name: Unix Build 23 | # This job runs on Linux 24 | strategy: 25 | matrix: 26 | include: 27 | - os: ubuntu-latest 28 | triplet: x64-linux 29 | cc: gcc 30 | - os: ubuntu-latest 31 | triplet: x64-linux 32 | cc: gcc-latest 33 | - os: ubuntu-18.04 34 | triplet: x64-linux 35 | cc: gcc-4.8 36 | - os: ubuntu-latest 37 | triplet: x64-linux 38 | cc: clang-latest 39 | - os: macos-latest 40 | triplet: x64-osx 41 | cc: clang-latest 42 | runs-on: ${{ matrix.os }} 43 | steps: 44 | - name: Checkout 45 | uses: actions/checkout@v2 46 | - name: Build & Test 47 | shell: bash 48 | env: 49 | USE_CC: ${{ matrix.cc }} 50 | VCPKG_TARGET_TRIPLET: ${{ matrix.triplet }} 51 | USE_SSL: ${{ matrix.ssl }} 52 | run: | 53 | if [[ "xgcc-4.8" == "x$USE_CC" ]]; then 54 | sudo apt-get update ; 55 | sudo apt-get install --no-install-recommends --no-install-suggests -y g++-4.8 ; 56 | bash ci/do_ci.sh gcc.legacy.test ; 57 | else 58 | bash ci/do_ci.sh ssl.openssl ; 59 | fi 60 | - name: Cache packages 61 | uses: actions/cache@v2 62 | with: 63 | path: | 64 | /usr/local/share/vcpkg/installed 65 | key: ${{ runner.os }}-${{ hashFiles('/usr/local/share/vcpkg/installed/**') }} 66 | vs2019_build: # job id, can be any string 67 | name: Visual Studio 2019 Build 68 | strategy: 69 | matrix: 70 | include: 71 | - os: windows-latest 72 | generator: "Visual Studio 16 2019" 73 | build_shared_libs: "ON" 74 | platform: x64 75 | - os: windows-latest 76 | generator: "Visual Studio 16 2019" 77 | build_shared_libs: "OFF" 78 | platform: x64 79 | runs-on: ${{ matrix.os }} 80 | steps: 81 | - name: Checkout 82 | uses: actions/checkout@v2 83 | - name: Build & Test 84 | shell: pwsh 85 | env: 86 | CMAKE_GENERATOR: ${{ matrix.generator }} 87 | CMAKE_PLATFORM: ${{ matrix.platform }} 88 | BUILD_SHARED_LIBS: ${{ matrix.build_shared_libs }} 89 | CONFIGURATION: RelWithDebInfo 90 | run: | 91 | pwsh ci/do_ci.ps1 msvc.2019.test ; 92 | vs2017_build: # job id, can be any string 93 | name: Visual Studio 2017 Build 94 | strategy: 95 | matrix: 96 | include: 97 | - os: windows-2016 98 | generator: "Visual Studio 15 2017 Win64" 99 | build_shared_libs: "OFF" 100 | runs-on: ${{ matrix.os }} 101 | steps: 102 | - name: Checkout 103 | uses: actions/checkout@v2 104 | - name: Build & Test 105 | shell: pwsh 106 | env: 107 | CMAKE_GENERATOR: ${{ matrix.generator }} 108 | BUILD_SHARED_LIBS: ${{ matrix.build_shared_libs }} 109 | CONFIGURATION: RelWithDebInfo 110 | run: | 111 | pwsh ci/do_ci.ps1 msvc.2017.test ; 112 | mingw_build: # job id, can be any string 113 | name: MinGW Build 114 | strategy: 115 | matrix: 116 | include: 117 | - os: windows-latest 118 | build_shared_libs: "ON" 119 | - os: windows-latest 120 | build_shared_libs: "OFF" 121 | runs-on: ${{ matrix.os }} 122 | steps: 123 | - name: Checkout 124 | uses: actions/checkout@v2 125 | - name: Build & Test 126 | shell: bash 127 | env: 128 | BUILD_SHARED_LIBS: ${{ matrix.build_shared_libs }} 129 | run: | 130 | C:/msys64/msys2_shell.cmd -mingw64 -defterm -no-start -here -lc "ci/do_ci.sh msys2.mingw.test" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | *.obj 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Compiled Dynamic libraries 12 | *.so 13 | *.dylib 14 | *.dll 15 | 16 | # Fortran module files 17 | *.mod 18 | 19 | # Compiled Static libraries 20 | *.lai 21 | *.la 22 | *.a 23 | *.lib 24 | 25 | # Executables 26 | *.exe 27 | *.out 28 | *.app 29 | 30 | /build 31 | /.idea 32 | /.vscode 33 | /third_party/install 34 | /third_party/packages 35 | /include/detail/hiredis_happ_config.h 36 | /build_jobs_* 37 | /build 38 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "atframework/cmake-toolset"] 2 | path = atframework/cmake-toolset 3 | url = https://github.com/atframework/cmake-toolset.git 4 | branch = main 5 | [submodule "atframework/atframe_utils"] 6 | path = atframework/atframe_utils 7 | url = https://github.com/atframework/atframe_utils.git 8 | branch = main 9 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16.0) 2 | cmake_policy(SET CMP0054 NEW) 3 | cmake_policy(SET CMP0022 NEW) 4 | cmake_policy(SET CMP0067 NEW) 5 | cmake_policy(SET CMP0074 NEW) 6 | 7 | enable_testing() 8 | 9 | project( 10 | hiredis-happ 11 | VERSION "0.9.0" 12 | HOMEPAGE_URL "https://github.com/owent/hiredis-happ" 13 | LANGUAGES C CXX) 14 | 15 | include("${CMAKE_CURRENT_LIST_DIR}/project/cmake/ProjectBuildOption.cmake") 16 | include(MaybePopulateSubmodule) 17 | include("${CMAKE_CURRENT_LIST_DIR}/third_party/Repository.cmake") 18 | if(PROJECT_HIREDIS_HAPP_ENABLE_UNITTEST OR BUILD_TESTING) 19 | include("${CMAKE_CURRENT_LIST_DIR}/atframework/Repository.cmake") 20 | endif() 21 | echowithcolor(COLOR GREEN "-- Build Type: ${CMAKE_BUILD_TYPE}") 22 | 23 | # ###################################################################################################################### 24 | # 导入项目配置 导入所有 macro 定义 25 | set(PROJECT_HIREDIS_HAPP_PUBLIC_LINK_NAMES ${ATFRAMEWORK_CMAKE_TOOLSET_THIRD_PARTY_HIREDIS_LINK_NAME}) 26 | unset(PROJECT_HIREDIS_HAPP_INTERFACE_LINK_NAMES) 27 | unset(PROJECT_HIREDIS_HAPP_PRIVATE_COMPILE_OPTIONS) 28 | 29 | # ================ multi thread ================ 30 | find_package(Threads) 31 | if(CMAKE_USE_PTHREADS_INIT) 32 | if(NOT ANDROID) 33 | if(TARGET Threads::Threads) 34 | list(APPEND PROJECT_HIREDIS_HAPP_PUBLIC_LINK_NAMES Threads::Threads) 35 | endif() 36 | endif() 37 | endif() 38 | 39 | # 设置输出目录 40 | set(PROJECT_HIREDIS_HAPP_INCLUDE_DIR "${PROJECT_SOURCE_DIR}/include") 41 | set(PROJECT_HIREDIS_HAPP_SOURCE_DIR "${PROJECT_SOURCE_DIR}/src") 42 | set(HIREDIS_HAPP_VERSION "${PROJECT_VERSION}") 43 | 44 | configure_file("${PROJECT_HIREDIS_HAPP_INCLUDE_DIR}/detail/hiredis_happ_config.h.in" 45 | "${PROJECT_HIREDIS_HAPP_INCLUDE_DIR}/detail/hiredis_happ_config.h" @ONLY) 46 | 47 | file(GLOB_RECURSE PROJECT_HIREDIS_HAPP_SRC_LIST "${PROJECT_HIREDIS_HAPP_SOURCE_DIR}/*.cpp") 48 | file(GLOB_RECURSE PROJECT_HIREDIS_HAPP_HEADER_LIST "${PROJECT_HIREDIS_HAPP_INCLUDE_DIR}/*.h") 49 | 50 | include(GNUInstallDirs) 51 | 52 | if(BUILD_SHARED_LIBS OR ATFRAMEWORK_USE_DYNAMIC_LIBRARY) 53 | add_library(hiredis-happ SHARED ${PROJECT_HIREDIS_HAPP_SRC_LIST} ${PROJECT_HIREDIS_HAPP_HEADER_LIST}) 54 | set_target_properties( 55 | hiredis-happ 56 | PROPERTIES C_VISIBILITY_PRESET "hidden" 57 | CXX_VISIBILITY_PRESET "hidden" 58 | VERSION ${HIREDIS_HAPP_VERSION} 59 | SOVERSION ${HIREDIS_HAPP_VERSION} 60 | INTERFACE_COMPILE_DEFINITIONS HIREDIS_HAPP_API_DLL=1) 61 | target_compile_definitions(hiredis-happ PRIVATE HIREDIS_HAPP_API_NATIVE=1 HIREDIS_HAPP_API_DLL=1) 62 | target_compile_options(hiredis-happ PRIVATE ${COMPILER_STRICT_EXTRA_CFLAGS} ${COMPILER_STRICT_CFLAGS}) 63 | else() 64 | add_library(hiredis-happ STATIC ${PROJECT_HIREDIS_HAPP_SRC_LIST} ${PROJECT_HIREDIS_HAPP_HEADER_LIST}) 65 | set_target_properties( 66 | hiredis-happ 67 | PROPERTIES C_VISIBILITY_PRESET "hidden" 68 | CXX_VISIBILITY_PRESET "hidden" 69 | VERSION ${HIREDIS_HAPP_VERSION}) 70 | target_compile_definitions(hiredis-happ PRIVATE HIREDIS_HAPP_API_NATIVE=1) 71 | target_compile_options(hiredis-happ PRIVATE ${COMPILER_STRICT_EXTRA_CFLAGS} ${COMPILER_STRICT_CFLAGS}) 72 | endif() 73 | 74 | set_target_properties( 75 | hiredis-happ 76 | PROPERTIES INSTALL_RPATH_USE_LINK_PATH YES 77 | BUILD_WITH_INSTALL_RPATH NO 78 | BUILD_RPATH_USE_ORIGIN YES) 79 | 80 | target_include_directories(hiredis-happ PUBLIC "$" 81 | "$") 82 | 83 | if(PROJECT_THIRD_PARTY_INSTALL_DIR) 84 | target_include_directories(hiredis-happ PRIVATE "$") 85 | endif() 86 | 87 | if(WIN32) 88 | list(APPEND PROJECT_HIREDIS_HAPP_INTERFACE_LINK_NAMES psapi iphlpapi userenv ws2_32) 89 | elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") 90 | list(APPEND PROJECT_HIREDIS_HAPP_INTERFACE_LINK_NAMES dl rt) 91 | elseif(CMAKE_SYSTEM_NAME MATCHES "DragonFly|FreeBSD|NetBSD|OpenBSD") 92 | list(APPEND PROJECT_HIREDIS_HAPP_INTERFACE_LINK_NAMES kvm) 93 | elseif(CMAKE_SYSTEM_NAME STREQUAL "SunOS") 94 | list(APPEND PROJECT_HIREDIS_HAPP_INTERFACE_LINK_NAMES kstat nsl sendfile socket) 95 | endif() 96 | 97 | target_link_libraries( 98 | hiredis-happ 99 | PUBLIC ${PROJECT_HIREDIS_HAPP_PUBLIC_LINK_NAMES} 100 | INTERFACE ${PROJECT_HIREDIS_HAPP_INTERFACE_LINK_NAMES}) 101 | 102 | if(PROJECT_HIREDIS_HAPP_PRIVATE_COMPILE_OPTIONS) 103 | target_compile_options(hiredis-happ PRIVATE ${PROJECT_HIREDIS_HAPP_PRIVATE_COMPILE_OPTIONS}) 104 | endif() 105 | 106 | if(MSVC) 107 | add_target_properties(hiredis-happ COMPILE_OPTIONS /ZI) 108 | endif() 109 | 110 | install( 111 | TARGETS hiredis-happ 112 | EXPORT hiredis-happ-target 113 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} 114 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 115 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) 116 | 117 | install( 118 | DIRECTORY ${PROJECT_HIREDIS_HAPP_INCLUDE_DIR} 119 | DESTINATION . 120 | FILES_MATCHING 121 | REGEX ".+\\.h(pp)?$" 122 | PATTERN ".svn" EXCLUDE 123 | PATTERN ".git" EXCLUDE) 124 | 125 | export( 126 | TARGETS hiredis-happ 127 | FILE "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/cmake/hiredis-happ-target.cmake" 128 | NAMESPACE hiredis::) 129 | 130 | install( 131 | EXPORT hiredis-happ-target 132 | NAMESPACE "hiredis::" 133 | DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake") 134 | 135 | if(PROJECT_HIREDIS_HAPP_ENABLE_SAMPLE) 136 | include("${CMAKE_CURRENT_LIST_DIR}/sample/sample.happ-macro.cmake") 137 | add_subdirectory("${PROJECT_SOURCE_DIR}/sample") 138 | endif() 139 | 140 | if(PROJECT_HIREDIS_HAPP_ENABLE_UNITTEST OR BUILD_TESTING) 141 | include("${CMAKE_CURRENT_LIST_DIR}/test/test.happ-macro.cmake") 142 | add_subdirectory("${PROJECT_SOURCE_DIR}/test") 143 | endif() 144 | -------------------------------------------------------------------------------- /CPPLINT.cfg: -------------------------------------------------------------------------------- 1 | linelength=120 2 | filter=-build/c++11,-build/c++14,-runtime/references 3 | exclude=third_party,atframework/atframe_utils -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021 owent 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hiredis-happ 2 | 3 | Redis HA connector 4 | 5 | [![ci-badge]][ci-link] 6 | 7 | [ci-badge]: https://github.com/owent/hiredis-happ/actions/workflows/main.yml/badge.svg "Github action build status" 8 | [ci-link]: https://github.com/owent/hiredis-happ/actions/workflows/main.yml "Github action build status" 9 | 10 | ## CI Job Matrix 11 | 12 | | Target System | Toolchain | Note | 13 | | ------------- | ------------------ | --------------------- | 14 | | Linux | GCC | 15 | | Linux | GCC-11 | 16 | | Linux | Clang | With libc++ | 17 | | Linux | GCC 4.8 | 18 | | MinGW64 | GCC | Static linking | 19 | | MinGW64 | GCC | Dynamic linking | 20 | | Windows | Visual Studio 2019 | Static linking | 21 | | Windows | Visual Studio 2019 | Dynamic linking | 22 | | Windows | Visual Studio 2017 | Legacy,Static linking | 23 | | macOS | AppleClang | With libc++ | 24 | 25 | ## Tips 26 | 27 | 1. auto reconnect 28 | 2. support redis cluster 29 | 3. ~~[TODO] support redis sential~~ 30 | 4. support raw redis connection 31 | 32 | ## Usage 33 | 34 | 35 | ```bash 36 | git clone https://github.com/owent/hiredis-happ.git; 37 | mkdir -p hiredis-happ/build_jobs_dir && cd hiredis-happ/build_jobs_dir; 38 | # cmake -DDCMAKE_INSTALL_PREFIX=/usr 39 | # cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo 40 | cmake .. ; 41 | cmake --build . ; 42 | 43 | # for install only, if not installed, all header files are in [BUILDDIR]/include, all libraries files are in [BUILDDIR]/lib 44 | cmake --build . -- install 45 | ``` 46 | 47 | ### Custom CMake Options 48 | 49 | + **CMAKE_BUILD_TYPE**: This cmake option will be default set to **RelWithDebInfo**.(Please add -DCMAKE_BUILD_TYPE=RelWithDebInfo when used in product environment) 50 | + **BUILD_SHARED_LIBS**: Default set to OFF 51 | + **ENABLE_BOOST_UNIT_TEST**: If using [boost.unittest](http://www.boost.org/libs/test/doc/html/index.html) for test framework(default: OFF) 52 | + **PROJECT_HIREDIS_HAPP_ENABLE_SAMPLE**: If building samples(default: OFF) 53 | + **PROJECT_HIREDIS_HAPP_ENABLE_UNITTEST**: If building unittest(default: OFF) 54 | 55 | ### Sample 56 | 57 | See [sample_cluster_cli](sample/sample_cluster_cli) for redis cluster practice and [sample_raw_cli](sample/sample_raw_cli) for raw redis connection. 58 | 59 | Both [happ_cluster](include/detail/happ_cluster.h) and [happ_raw](include/detail/happ_raw.h) support auto reconnecting and retry when cmd failed. 60 | 61 | You can also custom how to print log by using *set_log_writer* to help you to find any problem. 62 | 63 | ## Document 64 | 65 | See [doc](doc) 66 | 67 | ## Notice 68 | 69 | This lib only support Request-Response commands now.(means every request should has only one response). 70 | 71 | ### Unsupport Commands 72 | 73 | + subscribe 74 | + unsubscribe 75 | + monitor 76 | 77 | ### Directory list 78 | 79 | **3rd_party** -- script for 3rd party libraries 80 | 81 | **doc** -- document 82 | 83 | **include** -- include files 84 | 85 | **project** -- project configure 86 | 87 | **src** -- source 88 | 89 | **sample** -- samples 90 | 91 | **test** -- unit test 92 | 93 | **tools** -- misc tools -------------------------------------------------------------------------------- /atframework/Repository.cmake: -------------------------------------------------------------------------------- 1 | include_guard(GLOBAL) 2 | 3 | if(TARGET atframe_utils) 4 | set(ATFRAMEWORK_ATFRAME_UTILS_LINK_NAME atframe_utils) 5 | elseif(TARGET atframework::atframe_utils) 6 | set(ATFRAMEWORK_ATFRAME_UTILS_LINK_NAME atframework::atframe_utils) 7 | else() 8 | set(ATFRAMEWORK_ATFRAME_UTILS_LINK_NAME atframe_utils) 9 | if(NOT EXISTS "${CMAKE_CURRENT_BINARY_DIR}/_deps/${ATFRAMEWORK_ATFRAME_UTILS_LINK_NAME}") 10 | file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/_deps/${ATFRAMEWORK_ATFRAME_UTILS_LINK_NAME}") 11 | endif() 12 | maybe_populate_submodule(ATFRAMEWORK_ATFRAME_UTILS "atframework/atframe_utils" 13 | "${PROJECT_SOURCE_DIR}/atframework/atframe_utils") 14 | add_subdirectory("${ATFRAMEWORK_ATFRAME_UTILS_REPO_DIR}" 15 | "${CMAKE_CURRENT_BINARY_DIR}/_deps/${ATFRAMEWORK_ATFRAME_UTILS_LINK_NAME}") 16 | endif() 17 | -------------------------------------------------------------------------------- /ci/do_ci.ps1: -------------------------------------------------------------------------------- 1 | $PSDefaultParameterValues['*:Encoding'] = 'UTF-8' 2 | 3 | $OutputEncoding = [System.Text.UTF8Encoding]::new() 4 | 5 | $SCRIPT_DIR = Split-Path -Parent $MyInvocation.MyCommand.Definition 6 | $WORK_DIR = Get-Location 7 | 8 | if ($IsWindows) { 9 | # See https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=cmd 10 | New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem" ` 11 | -Name "LongPathsEnabled" -Value 1 -PropertyType DWORD -Force 12 | 13 | if (Test-Path "${Env:USERPROFILE}/scoop/apps/perl/current/perl/bin") { 14 | $Env:PATH = $Env:PATH + [IO.Path]::PathSeparator + "${Env:USERPROFILE}/scoop/apps/perl/current/perl/bin" 15 | } 16 | 17 | function Invoke-Environment { 18 | param 19 | ( 20 | [Parameter(Mandatory = $true)] 21 | [string] $Command 22 | ) 23 | cmd /c "$Command > nul 2>&1 && set" | . { process { 24 | if ($_ -match '^([^=]+)=(.*)') { 25 | [System.Environment]::SetEnvironmentVariable($matches[1], $matches[2]) 26 | } 27 | } } 28 | } 29 | $vswhere = "${Env:ProgramFiles(x86)}/Microsoft Visual Studio/Installer/vswhere.exe" 30 | $vsInstallationPath = & $vswhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath 31 | $winSDKDir = $(Get-ItemPropertyValue -Path "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Microsoft SDKs\Windows\v10.0" -Name "InstallationFolder") 32 | if ([string]::IsNullOrEmpty($winSDKDir)) { 33 | $winSDKDir = "${Env:ProgramFiles(x86)}/Windows Kits/10/Include/" 34 | } 35 | else { 36 | $winSDKDir = "$winSDKDir/Include/" 37 | } 38 | foreach ($sdk in $(Get-ChildItem $winSDKDir | Sort-Object -Property Name)) { 39 | if ($sdk.Name -match "[0-9]+\.[0-9]+\.[0-9\.]+") { 40 | $selectWinSDKVersion = $sdk.Name 41 | } 42 | } 43 | if (!(Test-Path Env:WindowsSDKVersion)) { 44 | $Env:WindowsSDKVersion = $selectWinSDKVersion 45 | } 46 | # Maybe using $selectWinSDKVersion = "10.0.18362.0" for better compatible 47 | Write-Output "Window SDKs:(Latest: $selectWinSDKVersion)" 48 | foreach ($sdk in $(Get-ChildItem $winSDKDir | Sort-Object -Property Name)) { 49 | Write-Output " - $sdk" 50 | } 51 | } 52 | 53 | Set-Location "$SCRIPT_DIR/.." 54 | $RUN_MODE = $args[0] 55 | 56 | if ( $RUN_MODE -eq "msvc.2019.test" ) { 57 | Invoke-Environment "call ""$vsInstallationPath/VC/Auxiliary/Build/vcvars64.bat""" 58 | New-Item -Path "build_jobs_ci" -ItemType "directory" -Force 59 | Set-Location "build_jobs_ci" 60 | & cmake ".." "-G" "$Env:CMAKE_GENERATOR" "-A" $Env:CMAKE_PLATFORM "-DBUILD_SHARED_LIBS=$Env:BUILD_SHARED_LIBS" ` 61 | "-DPROJECT_HIREDIS_HAPP_ENABLE_UNITTEST=ON" "-DPROJECT_HIREDIS_HAPP_ENABLE_SAMPLE=ON" ` 62 | "-DCMAKE_SYSTEM_VERSION=$selectWinSDKVersion" "-DATFRAMEWORK_CMAKE_TOOLSET_THIRD_PARTY_LOW_MEMORY_MODE=ON" 63 | if ( $LastExitCode -ne 0 ) { 64 | exit $LastExitCode 65 | } 66 | 67 | $ALL_DLL_FILES = Get-ChildItem -Path "../third_party/install/*.dll" -Recurse 68 | $ALL_DLL_DIRS = $(foreach ($dll_file in $ALL_DLL_FILES) { 69 | $dll_file.Directory.FullName 70 | }) | Sort-Object | Get-Unique 71 | $Env:PATH = ($ALL_DLL_DIRS + $Env:PATH) -Join [IO.Path]::PathSeparator 72 | 73 | & cmake --build . --config $Env:CONFIGURATION 74 | if ( $LastExitCode -ne 0 ) { 75 | exit $LastExitCode 76 | } 77 | & ctest . -V -C $Env:CONFIGURATION -R hiredis-happ-run-test 78 | if ( $LastExitCode -ne 0 ) { 79 | exit $LastExitCode 80 | } 81 | } 82 | elseif ( $RUN_MODE -eq "msvc.2017.test" ) { 83 | Invoke-Environment "call ""$vsInstallationPath/VC/Auxiliary/Build/vcvars64.bat""" 84 | New-Item -Path "build_jobs_ci" -ItemType "directory" -Force 85 | Set-Location "build_jobs_ci" 86 | & cmake ".." "-G" "$Env:CMAKE_GENERATOR" "-DBUILD_SHARED_LIBS=$ENV:BUILD_SHARED_LIBS" ` 87 | "-DPROJECT_HIREDIS_HAPP_ENABLE_UNITTEST=ON" "-DPROJECT_HIREDIS_HAPP_ENABLE_SAMPLE=ON" ` 88 | "-DCMAKE_SYSTEM_VERSION=$selectWinSDKVersion" "-DATFRAMEWORK_CMAKE_TOOLSET_THIRD_PARTY_LOW_MEMORY_MODE=ON" 89 | if ( $LastExitCode -ne 0 ) { 90 | exit $LastExitCode 91 | } 92 | 93 | $ALL_DLL_FILES = Get-ChildItem -Path "../third_party/install/*.dll" -Recurse 94 | $ALL_DLL_DIRS = $(foreach ($dll_file in $ALL_DLL_FILES) { 95 | $dll_file.Directory.FullName 96 | }) | Sort-Object | Get-Unique 97 | $Env:PATH = ($ALL_DLL_DIRS + $Env:PATH) -Join [IO.Path]::PathSeparator 98 | 99 | & cmake --build . --config $Env:CONFIGURATION 100 | if ( $LastExitCode -ne 0 ) { 101 | exit $LastExitCode 102 | } 103 | & ctest . -V -C $Env:CONFIGURATION -R hiredis-happ-run-test 104 | if ( $LastExitCode -ne 0 ) { 105 | exit $LastExitCode 106 | } 107 | } 108 | 109 | Set-Location $WORK_DIR 110 | -------------------------------------------------------------------------------- /ci/do_ci.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd "$(cd "$(dirname $0)" && pwd)/.."; 4 | 5 | set -ex ; 6 | 7 | if [[ "x$USE_CC" == "xclang-latest" ]]; then 8 | echo '#include 9 | int main() { std::cout<<"Hello"; }' > test-libc++.cpp 10 | SELECT_CLANG_VERSION=""; 11 | SELECT_CLANG_HAS_LIBCXX=1; 12 | clang -x c++ -stdlib=libc++ test-libc++.cpp -lc++ -lc++abi || SELECT_CLANG_HAS_LIBCXX=0; 13 | if [[ $SELECT_CLANG_HAS_LIBCXX -eq 0 ]]; then 14 | CURRENT_CLANG_VERSION=$(clang -x c /dev/null -dM -E | grep __clang_major__ | awk '{print $NF}'); 15 | for ((i=$CURRENT_CLANG_VERSION+5;$i>=$CURRENT_CLANG_VERSION;--i)); do 16 | SELECT_CLANG_HAS_LIBCXX=1; 17 | SELECT_CLANG_VERSION="-$i"; 18 | clang$SELECT_CLANG_VERSION -x c++ -stdlib=libc++ test-libc++.cpp -lc++ -lc++abi || SELECT_CLANG_HAS_LIBCXX=0; 19 | if [[ $SELECT_CLANG_HAS_LIBCXX -eq 1 ]]; then 20 | break; 21 | fi 22 | done 23 | fi 24 | SELECT_CLANGPP_BIN=clang++$SELECT_CLANG_VERSION; 25 | LINK_CLANGPP_BIN=0; 26 | which $SELECT_CLANGPP_BIN || LINK_CLANGPP_BIN=1; 27 | if [[ $LINK_CLANGPP_BIN -eq 1 ]]; then 28 | mkdir -p .local/bin ; 29 | ln -s "$(which "clang$SELECT_CLANG_VERSION")" "$PWD/.local/bin/clang++$SELECT_CLANG_VERSION" ; 30 | export PATH="$PWD/.local/bin:$PATH"; 31 | fi 32 | export USE_CC=clang$SELECT_CLANG_VERSION; 33 | elif [[ "x$USE_CC" == "xgcc-latest" ]]; then 34 | CURRENT_GCC_VERSION=$(gcc -x c /dev/null -dM -E | grep __GNUC__ | awk '{print $NF}'); 35 | echo '#include 36 | int main() { std::cout<<"Hello"; }' > test-gcc-version.cpp ; 37 | let LAST_GCC_VERSION=$CURRENT_GCC_VERSION+10 ; 38 | for ((i=$CURRENT_GCC_VERSION;$i<=$LAST_GCC_VERSION;++i)); do 39 | TEST_GCC_VERSION=1; 40 | g++-$i -x c++ test-gcc-version.cpp || TEST_GCC_VERSION=0; 41 | if [[ $TEST_GCC_VERSION -eq 0 ]]; then 42 | break; 43 | fi 44 | CURRENT_GCC_VERSION=$i; 45 | done 46 | export USE_CC=gcc-$CURRENT_GCC_VERSION ; 47 | echo "Using $USE_CC" ; 48 | fi 49 | 50 | if [[ "$1" == "format" ]]; then 51 | python3 -m pip install --user -r ./ci/requirements.txt ; 52 | export PATH="$HOME/.local/bin:$PATH" 53 | bash ./ci/format.sh ; 54 | CHANGED="$(git -c core.autocrlf=true ls-files --modified)" ; 55 | if [[ ! -z "$CHANGED" ]]; then 56 | echo "The following files have changes:" ; 57 | echo "$CHANGED" ; 58 | git diff ; 59 | # exit 1 ; # Just warning, some versions of clang-format have different default style for unsupport syntax 60 | fi 61 | exit 0 ; 62 | elif [[ "$1" == "ssl.openssl" ]]; then 63 | CRYPTO_OPTIONS="-DATFRAMEWORK_CMAKE_TOOLSET_THIRD_PARTY_CRYPTO_USE_OPENSSL=ON" ; 64 | vcpkg install --triplet=$VCPKG_TARGET_TRIPLET fmt openssl ; 65 | bash cmake_dev.sh -lus -b Debug -r build_jobs_ci -c $USE_CC -- $CRYPTO_OPTIONS -DVCPKG_TARGET_TRIPLET=$VCPKG_TARGET_TRIPLET \ 66 | -DCMAKE_TOOLCHAIN_FILE=$VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake "-DATFRAMEWORK_CMAKE_TOOLSET_THIRD_PARTY_LOW_MEMORY_MODE=ON"; 67 | cd build_jobs_ci ; 68 | cmake --build . -j ; 69 | ctest . -V -R hiredis-happ-run-test; 70 | elif [[ "$1" == "gcc.legacy.test" ]]; then 71 | bash cmake_dev.sh -lus -b Debug -r build_jobs_ci -c $USE_CC ; 72 | cd build_jobs_ci ; 73 | cmake --build . -j ; 74 | ctest . -V -R hiredis-happ-run-test; 75 | elif [[ "$1" == "msys2.mingw.test" ]]; then 76 | pacman -S --needed --noconfirm mingw-w64-x86_64-cmake git m4 curl wget tar autoconf automake \ 77 | mingw-w64-x86_64-git-lfs mingw-w64-x86_64-toolchain mingw-w64-x86_64-libtool \ 78 | mingw-w64-x86_64-python mingw-w64-x86_64-python-pip mingw-w64-x86_64-python-setuptools || true ; 79 | git config --global http.sslBackend openssl ; 80 | mkdir -p build_jobs_ci ; 81 | cd build_jobs_ci ; 82 | cmake .. -G 'MinGW Makefiles' "-DBUILD_SHARED_LIBS=$BUILD_SHARED_LIBS" "-DPROJECT_HIREDIS_HAPP_ENABLE_UNITTEST=YES" \ 83 | "-DPROJECT_HIREDIS_HAPP_ENABLE_SAMPLE=YES" "-DATFRAMEWORK_CMAKE_TOOLSET_THIRD_PARTY_LOW_MEMORY_MODE=ON"; 84 | cmake --build . -j ; 85 | for EXT_PATH in $(find ../third_party/install/ -name "*.dll" | xargs dirname | sort -u); do 86 | export PATH="$PWD/$EXT_PATH:$PATH" 87 | done 88 | ctest . -V -R hiredis-happ-run-test; 89 | fi 90 | -------------------------------------------------------------------------------- /ci/enable_windows_long_path.reg: -------------------------------------------------------------------------------- 1 | Windows Registry Editor Version 5.00 2 | 3 | [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem] 4 | "LongPathsEnabled"=dword:00000001 5 | -------------------------------------------------------------------------------- /ci/format.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | find . -type f \ 4 | -regex ".*third_party/packages/.*" -prune \ 5 | -o -regex ".*third_party/install/.*" -prune \ 6 | -o -regex ".*build_jobs_.*" -prune \ 7 | -o -regex ".*atframework/cmake-toolset/.*" -prune \ 8 | -o -regex ".*atframework/atframe_utils/.*" -prune \ 9 | -o -name "*.cmake" -print \ 10 | -o -name "*.cmake.in" -print \ 11 | -o -name 'CMakeLists.txt' -print \ 12 | | xargs cmake-format -i 13 | 14 | find . -type f \ 15 | -regex ".*third_party/packages/.*" -prune \ 16 | -o -regex ".*third_party/install/.*" -prune \ 17 | -o -regex ".*build_jobs_.*" -prune \ 18 | -o -regex ".*atframework/cmake-toolset/.*" -prune \ 19 | -o -regex ".*atframework/atframe_utils/.*" -prune \ 20 | -o -name "*.h" -print \ 21 | -o -name "*.hpp" -print \ 22 | -o -name "*.cxx" -print \ 23 | -o -name '*.cpp' -print \ 24 | -o -name '*.cc' -print \ 25 | -o -name '*.c' -print \ 26 | | xargs -r -n 32 clang-format -i --style=file --fallback-style=none 27 | -------------------------------------------------------------------------------- /ci/requirements.txt: -------------------------------------------------------------------------------- 1 | cmake-format>=0.6.13 2 | PyYAML>=5.4.1 3 | -------------------------------------------------------------------------------- /cmake_dev.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | SYS_NAME="$(uname -s)"; 4 | SYS_NAME="$(basename $SYS_NAME)"; 5 | CC=gcc; 6 | CXX=g++; 7 | CCACHE="$(which ccache)"; 8 | 9 | CMAKE_OPTIONS=""; 10 | CMAKE_CLANG_TIDY=""; 11 | CMAKE_CLANG_ANALYZER=0; 12 | CMAKE_CLANG_ANALYZER_PATH=""; 13 | BUILD_DIR=$(echo "build_jobs_$SYS_NAME" | tr '[:upper:]' '[:lower:]'); 14 | CUSTOM_BUILD_DIR=; 15 | CMAKE_BUILD_TYPE=Debug; 16 | 17 | if [ ! -z "$MSYSTEM" ]; then 18 | CHECK_MSYS=$(echo "${MSYSTEM:0:5}" | tr '[:upper:]' '[:lower:]'); 19 | else 20 | CHECK_MSYS=""; 21 | fi 22 | 23 | while getopts "ab:c:d:e:hlr:tus-" OPTION; do 24 | case $OPTION in 25 | a) 26 | echo "Ready to check ccc-analyzer and c++-analyzer, please do not use -c to change the compiler when using clang-analyzer."; 27 | CC=$(which ccc-analyzer); 28 | CXX=$(which c++-analyzer); 29 | if [ 0 -ne $? ]; then 30 | # check mingw path 31 | if [ "mingw" == "$CHECK_MSYS" ]; then 32 | if [ ! -z "$MINGW_MOUNT_POINT" ] && [ -e "$MINGW_MOUNT_POINT/libexec/ccc-analyzer.bat" ] && [ -e "$MINGW_MOUNT_POINT/libexec/ccc-analyzer.bat" ]; then 33 | echo "clang-analyzer found in $MINGW_MOUNT_POINT"; 34 | export PATH=$PATH:$MINGW_MOUNT_POINT/libexec ; 35 | CC="$MINGW_MOUNT_POINT/libexec/ccc-analyzer.bat"; 36 | CXX="$MINGW_MOUNT_POINT/libexec/ccc-analyzer.bat"; 37 | CMAKE_CLANG_ANALYZER_PATH="$MINGW_MOUNT_POINT/libexec"; 38 | elif [ ! -z "$MINGW_PREFIX" ] && [ -e "$MINGW_PREFIX/libexec/ccc-analyzer.bat" ] && [ -e "$MINGW_PREFIX/libexec/c++-analyzer.bat" ]; then 39 | echo "clang-analyzer found in $MINGW_PREFIX"; 40 | export PATH=$PATH:$MINGW_PREFIX/libexec ; 41 | CC="$MINGW_PREFIX/libexec/ccc-analyzer.bat"; 42 | CXX="$MINGW_PREFIX/libexec/ccc-analyzer.bat"; 43 | CMAKE_CLANG_ANALYZER_PATH="$MINGW_PREFIX/libexec"; 44 | fi 45 | fi 46 | fi 47 | 48 | if [ -z "$CC" ] || [ -z "$CXX" ]; then 49 | echo "ccc-analyzer=$CC"; 50 | echo "c++-analyzer=$CXX"; 51 | echo "clang-analyzer not found, failed."; 52 | exit 1; 53 | fi 54 | echo "ccc-analyzer=$CC"; 55 | echo "c++-analyzer=$CXX"; 56 | echo "clang-analyzer setup completed."; 57 | CMAKE_CLANG_ANALYZER=1; 58 | BUILD_DIR="${BUILD_DIR}_analyzer"; 59 | ;; 60 | b) 61 | CMAKE_BUILD_TYPE="$OPTARG"; 62 | ;; 63 | c) 64 | if [[ $CMAKE_CLANG_ANALYZER -ne 0 ]]; then 65 | CCC_CC="$OPTARG"; 66 | CCC_CXX="${CCC_CC/%clang/clang++}"; 67 | CCC_CXX="${CCC_CXX/%gcc/g++}"; 68 | export CCC_CC; 69 | export CCC_CXX; 70 | else 71 | CC="$OPTARG"; 72 | CXX="$(echo "$CC" | sed 's/\(.*\)clang/\1clang++/')"; 73 | CXX="$(echo "$CXX" | sed 's/\(.*\)gcc/\1g++/')"; 74 | fi 75 | ;; 76 | d) 77 | if [ ! -z "$OPTARG" ]; then 78 | CMAKE_OPTIONS="$CMAKE_OPTIONS -DLIBSODIUM_ROOT=$OPTARG"; 79 | fi 80 | ;; 81 | e) 82 | CCACHE="$OPTARG"; 83 | ;; 84 | h) 85 | echo "usage: $0 [options] [-- [cmake options...] ]"; 86 | echo "options:"; 87 | echo "-a using clang-analyzer."; 88 | echo "-b set build type(Debug, RelWithDebINfo, Release, MinSizeRel)."; 89 | echo "-c compiler toolchains(gcc, clang or others)."; 90 | echo "-d [libsodium root] set root of libsodium."; 91 | echo "-e try to use specify ccache to speed up building."; 92 | echo "-h help message."; 93 | echo "-l enable tools of submodules."; 94 | echo "-t enable clang-tidy."; 95 | echo "-u enable unit test."; 96 | echo "-s enable sample."; 97 | exit 0; 98 | ;; 99 | l) 100 | CMAKE_OPTIONS="$CMAKE_OPTIONS -DPROJECT_ENABLE_TOOLS=YES"; 101 | ;; 102 | r) 103 | CUSTOM_BUILD_DIR="$OPTARG"; 104 | ;; 105 | t) 106 | CMAKE_CLANG_TIDY="-D -checks=* --"; 107 | ;; 108 | u) 109 | CMAKE_OPTIONS="$CMAKE_OPTIONS -DPROJECT_ENABLE_UNITTEST=YES -DPROJECT_HIREDIS_HAPP_ENABLE_UNITTEST=YES"; 110 | ;; 111 | s) 112 | CMAKE_OPTIONS="$CMAKE_OPTIONS -DPROJECT_ENABLE_SAMPLE=YES -DPROJECT_HIREDIS_HAPP_ENABLE_SAMPLE=YES"; 113 | ;; 114 | -) 115 | break; 116 | ;; 117 | ?) 118 | echo "unkonw argument detected"; 119 | exit 1; 120 | ;; 121 | esac 122 | done 123 | 124 | shift $(($OPTIND - 1)); 125 | SCRIPT_DIR="$(cd $(dirname $0) && pwd)"; 126 | 127 | if [[ "x$CUSTOM_BUILD_DIR" != "x" ]]; then 128 | BUILD_DIR="$CUSTOM_BUILD_DIR"; 129 | fi 130 | 131 | mkdir -p "$SCRIPT_DIR/$BUILD_DIR"; 132 | cd "$SCRIPT_DIR/$BUILD_DIR"; 133 | 134 | if [ ! -z "$CCACHE" ] && [ "$CCACHE" != "disable" ] && [ "$CCACHE" != "disabled" ] && [ "$CCACHE" != "no" ] && [ "$CCACHE" != "false" ] && [ -e "$CCACHE" ]; then 135 | #CMAKE_OPTIONS="$CMAKE_OPTIONS -DCMAKE_C_COMPILER=$CCACHE -DCMAKE_CXX_COMPILER=$CCACHE -DCMAKE_C_COMPILER_ARG1=$CC -DCMAKE_CXX_COMPILER_ARG1=$CXX"; 136 | CMAKE_OPTIONS="$CMAKE_OPTIONS -DCMAKE_C_COMPILER_LAUNCHER=$CCACHE -DCMAKE_CXX_COMPILER_LAUNCHER=$CCACHE -DCMAKE_C_COMPILER=$CC -DCMAKE_CXX_COMPILER=$CXX"; 137 | else 138 | CMAKE_OPTIONS="$CMAKE_OPTIONS -DCMAKE_C_COMPILER=$CC -DCMAKE_CXX_COMPILER=$CXX"; 139 | fi 140 | 141 | if [ "$CHECK_MSYS" == "mingw" ]; then 142 | cmake .. -G "MSYS Makefiles" -DCMAKE_BUILD_TYPE=$CMAKE_BUILD_TYPE $CMAKE_OPTIONS "$@"; 143 | else 144 | cmake .. -DCMAKE_BUILD_TYPE=$CMAKE_BUILD_TYPE $CMAKE_OPTIONS "$@"; 145 | fi 146 | 147 | 148 | if [ 1 -eq $CMAKE_CLANG_ANALYZER ]; then 149 | echo "========================================================================================================="; 150 | CMAKE_CLANG_ANALYZER_OPTIONS=""; 151 | if [ -e "$SCRIPT_DIR/.scan-build.enable" ]; then 152 | for OPT in $(cat "$SCRIPT_DIR/.scan-build.enable"); do 153 | CMAKE_CLANG_ANALYZER_OPTIONS="$CMAKE_CLANG_ANALYZER_OPTIONS -enable-checker $OPT"; 154 | done 155 | fi 156 | 157 | if [ -e "$SCRIPT_DIR/.scan-build.disable" ]; then 158 | for OPT in $(cat "$SCRIPT_DIR/.scan-build.disable"); do 159 | CMAKE_CLANG_ANALYZER_OPTIONS="$CMAKE_CLANG_ANALYZER_OPTIONS -disable-checker $OPT"; 160 | done 161 | fi 162 | 163 | if [ -z "$CMAKE_CLANG_ANALYZER_PATH" ]; then 164 | echo "cd '$SCRIPT_DIR/$BUILD_DIR' && scan-build -o report --html-title='atframe_utils static analysis' $CMAKE_CLANG_ANALYZER_OPTIONS make -j4"; 165 | else 166 | echo "cd '$SCRIPT_DIR/$BUILD_DIR' && env PATH=\"\$PATH:$CMAKE_CLANG_ANALYZER_PATH\" scan-build -o report --html-title='libmt_core static analysis' $CMAKE_CLANG_ANALYZER_OPTIONS make -j4"; 167 | fi 168 | echo "Now, you can run those code above to get a static analysis report"; 169 | echo "You can get help and binary of clang-analyzer and scan-build at http://clang-analyzer.llvm.org/scan-build.html" 170 | fi 171 | -------------------------------------------------------------------------------- /doc/Redis全异步(HA)Driver设计稿.md: -------------------------------------------------------------------------------- 1 | Redis全异步高可用Driver设计稿 2 | ====== 3 | 4 | [TOC] 5 | 6 | 前言 7 | ------ 8 | 现在Redis的集群功能已经Release。但是并没有一个官方直接提供的高可用性的API可以使用。有的只有解决方案,Sentinel和Cluster。所以有必要自己设计一套高可用的Driver层以供业务使用。 9 | 10 | ### Cluster 11 | Redis 3.X已经release,这个版本提供了一个重要的功能,那就是集群(Cluster)。但是虽然redis的集群功能已经提供,但是目前还没有一个非常成熟的操作Redis集群的连接driver。而在我们游戏项目中,会需要一个稳健的driver来屏蔽底层细节,并且必须要使用全异步接口。 12 | 13 | 在开始前,我在某个论坛里看到[cpp-hiredis-cluster](https://github.com/shinberg/cpp-hiredis-cluster) 对redis的cluster支持得不错,就先大概看了一下[cpp-hiredis-cluster](https://github.com/shinberg/cpp-hiredis-cluster) 的实现,发现了几个问题。 14 | 15 | 1. 第一次连接集群节点采用的是同步操作,不过只有第一次是这样,问题也不大。 16 | 2. 执行**redisAsyncConnect**函数以后并没有关心是否连接成功,也就是说,即便连接失败了,连接仍然保存到了连接池中。 17 | 3. 没有关心断线的情况,也就是说,如果连接断开了,既不会重连,也不可能发送成功。 18 | 4. 没有支持slaver,也没有关心断线问题,如果master挂了,就没有然后了。 19 | 5. **retry**机制有问题,我看到的逻辑大概是这样:如果发现CLUSTERDOWN消息,则重试,并且是立即重试。并且重试的时候仍然用了原来的连接(还是没支持slaver导致的)。一般情况下,集群出现故障的时候必然会有一定的恢复时间,并且按照redis的规则,会把slaver节点提升会master,立即用原来的master连接重试毫无意义。 20 | 6. 每次检测到MOVE或者ASK消息都创建了一个新redis context,其实可以重用已有的。 21 | 7. 它里面使用了std::map来保存slot到redis context的映射关系,key是一个slot区间。搜索算法是找到第一个最小区间满足要求的slot。再加上上一条问题,导致redis如果在做扩容,原先已经失效的索引仍然存在,并且之后对这个key一定每次都会收到MOVED消息,然后每次都重新创建redis context和新连接,并覆盖原先的连接,并且覆盖先没有释放前一个redis context,造成严重的资源泄露。 22 | 23 | 总而言之,实现得很渣渣,所以还是需要自己维护一个连接redis cluster的工具。 24 | 25 | ### Sentinel 26 | 27 | 有一些服务,其实不需要Cluster那么复杂的操作。比如积分排行榜,直接主+备就够了。而要做到这方面数据安全,还是需要Sentinel来监控。 28 | 29 | 要解决的问题 30 | ------ 31 | 1. 支持自动断线重连; 32 | 2. Redis Cluster不支持多个DB分区(一定要用DB0)并且最好对于不同类型业务可以部署在不同集群里,以便减少不同模块之间的影响,所以也需要提供多集群功能(类似SQL中的多个数据库); 33 | 3. 要支持对Cluster的监控和统计; 34 | 4. 要支持多个通道(Channel)的设计(类似Redis的多个DB库)要实现不同通道之间完全隔离。 35 | 36 | 37 | Redis-Cluster适配设计 38 | ------ 39 | 40 | ### Redis-Cluster环境条件 41 | 1. 因为Slot只有16384(16K)个,即便把所有的Slot按Index放在数组里缓存也不会消耗太大的内存,并且这样查找是O(1)的 42 | 2. 尽量减少类似MSET的多key指令使用(包括涉及多个key的脚本),因为可能不同的Key会分布在不同的Slot上。在使用Cluster时,涉及多个key的指令,这些key必须拥有相同的hash值或hash tag,详见 http://redis.io/topics/cluster-tutorial#migrating-to-redis-cluster 43 | 3. 要内部完成 **指令跳转和重试** - 需要自动处理MOVED命令、ASK命令和TRYAGAIN错误 44 | > **MOVED** 命令出现在扩容和故障迁移时。这时候要永久更换Slot对应的连接。 45 | > **ASK** 表示临时跳转。仅仅这一次消息使用新的连接。比如正在扩容的过程中(某个slot由A转向了B),数据可能还没全部转移完,那么访问A节点的这个slot时,可能找不到数据,这时候ASK跳转可以把目标指向当前有数据的节点(B)。 46 | > ASK跳转还有一个特别的步骤是客户端先要发送一个**ASKING**命令,然后再重发这次的命令,不然处于导入转态的槽会被拒绝访问 47 | > 在重新分片过程中的多个键值操作核能导致**TRYAGAIN**错误,这时候需要尝试重发命令 48 | > 49 | 50 | 4. 基本操作参考[redis-rb-cluster](https://github.com/antirez/redis-rb-cluster)这是redis作者写得ruby版本支持redis的cluster的driver 51 | 52 | ### [redis-rb-cluster](https://github.com/antirez/redis-rb-cluster) 分析 53 | redis作者建议对cluster的支持仿照[redis-rb-cluster](https://github.com/antirez/redis-rb-cluster) 进行,那么我们来对这个库的实现流程做一个简单的分析。 54 | 55 | 1. 首先, [redis-rb-cluster](https://github.com/antirez/redis-rb-cluster) 和我们上面想得一样,是缓存了16384个槽。 56 | 2. 它实现了按需连接,就是说当第一次连接某个server时,仅仅会拉取Slot和服务器的关系列表,并没有真正建立连接,而是等第一次需要这个连接的时候才建立连接。 57 | 3. 限制了最大重定向次数,防止重定向死循环 58 | 4. 按需连接的时候,如果出现超时、连接被拒绝、连接失败的错误0.1秒后重试 59 | 5. 如果按slot查找连接没找到,则会返回一个随机的连接,然后根据ASK或者MOVED跳转来处理 60 | 6. ASK和MOVED跳转都会启动拉取所有Slot信息的行为,来更新Slot缓存 61 | 7. 拥有最大连接数限制,如果新建连接的时候超出最大连接数,随机关闭一个连接 62 | 63 | 64 | 执行Redis指令流程: 65 | 66 | ![redis-rb-cluster.png](res/2015/redis-rb-cluster.png) 67 | 68 | ### 设计要点 69 | 70 | [redis-rb-cluster](https://github.com/antirez/redis-rb-cluster) 写得确实比较漂亮,简单清晰。连我这种完全不懂ruby的人都能看懂。但是他的实现是全同步的操作。我们这里要求全异步操作时就会更加麻烦一点。 71 | 72 | + 首先是整个操作过程可以直接使用hiredis,使用起来比较简单,全程单线程,避免不必要的加解锁和逻辑复杂度; 73 | + 其次所有操作都要转为异步模式,因为不能预估操作的流程,所以还必须增加一层调用包装,用来包裹指令数据,这点和[cpp-hiredis-cluster](https://github.com/shinberg/cpp-hiredis-cluster)的Command一样; 74 | + 然后,要增加集群Server的概念,以便用一套接口操纵多个集群。这样的话,所有的数据结构中不能出现单例; 75 | + 直接利用hiredis的adapter来做事件绑定,方便工具迁移; 76 | + 使用**redisFormatSdsCommandArgv**和**redisAsyncFormattedCommand**来保存命令和执行命令(和[cpp-hiredis-cluster](https://github.com/shinberg/cpp-hiredis-cluster)一致)执行的命令保存为Sds后放到**Command**的数据包装里; 77 | + 为保证简单,我们的driver也可以使用主循环的模式(和 [redis-rb-cluster](https://github.com/antirez/redis-rb-cluster) 一样)。因为出现异常的情况会是少数,而正常的情况下,主循环只会执行一个循环; 78 | + 同样,是用主循环就需要设定最大循环次数,并且失败次数过高时休眠一段时间,用以避免逻辑死循环; 79 | + 使用按需建立连接,全局只保存**Slot-服务器地址缓存**和**服务器地址-连接池缓存**; 80 | + hiredis里大量使用了malloc,所以还是必须上jemalloc或者tcmalloc才比较靠谱; 81 | + 第一次连上以后应该像[redis-rb-cluster](https://github.com/antirez/redis-rb-cluster)发送一次拉取所有Slot信息的操作; 82 | + 某些命令和[redis-rb-cluster](https://github.com/antirez/redis-rb-cluster)一样,随机选取发送目标。 83 | 84 | 最后有一个要特别注意的是**丢包和超时**。 85 | 86 | 1. 丢包问题:虽然说TCP连接能保证数据包的顺序和并且自带网络包重发,但是在**连接断开的时候仍然会出现丢包**的情况。 87 | 2. 超时问题:hiredis的异步API里没有超时的判定,但是因为TCP包底层的重传机制,超时只有一种可能,那就是连接断开。然后要么是上面提到的情况,没有发送成功,要么是回包丢失。 88 | 89 | 无论上诉哪种情况,都会导致连接异常。根据对hiredis源代码的分析,(除了subscribe和unsubscribe命令外)这时候hiredis一定会回调所有没有完成的callback,然后响应disconnect事件,并且这时候redisReply *reply=NULL。 90 | > subscribe和unsubscribe命令外,订阅命令的回调是个字典 91 | > 92 | > subscribe命令的回调会在每次收到消息的时候都调用 93 | 94 | **上层应用逻辑需要自己有一个超时机制和对超时后又收到回包的容错机制**。 95 | 96 | 97 | ### 定时器 98 | 由于异步API不允许sleep操作,所以所有延迟操作都应该在定时器回调中执行。然而为了保证像hiredis一样支持多种binding机制,只能由使用方来创建和设置定时器回调,并**在回调中调用提供的proc方法**。 99 | 100 | 为了简化设计,我们定义以下规则: 101 | 102 | 1. 所有定时器的间隔一致(定时器队列可以直接链表实现); 103 | 2. 每个Channel有自己的定时器,并且定时器接口调用至少一次以后才会开启带定时器的功能(例如:sleep); 104 | 3. 假设系统中的Channel数量不会很多,这样定时器就不会很多,性能开销比较小。 105 | 106 | ### 连接和重连等待 107 | 108 | 异步操作的另一个问题是连接和重连的时候的等待问题,因为在连接完成期间,可能会收到新的命令请求。hiredis的做法是每次来了一个请求以后就放到缓冲区里,并且在Context可写时立即写出。 109 | 110 | 我们这里可以直接利用它的这个机制。但是在重新拉取并建立Slot缓存的时候,没有Redis连接可以用于保存,命令,所以可以在Channel里使用一个链表保存更新完Slot缓存后的执行命令集。 111 | 112 | 然后额外需要做的就是支持断线后的重连功能了。 113 | 114 | ### 设计总结 115 | 简单地说,就是需要在hiredis上包一层,来完成对Cluster中的内部操作。实现的过程中会导致多一次malloc和多一次sds复制操作。流程图如下: 116 | 117 | ![redis-ha-cluster.png](res/2015/redis-ha-cluster.png) 118 | 119 | Sentinel适配设计 120 | ------ 121 | 122 | ### 设计思路 123 | Sentinel比较简单,大体上和Cluster一致,有几个不一样的地方如下: 124 | 125 | 1. 第一次连接的是Sentinel节点而不是数据节点; 126 | 2. 连接的Channel要附带,并且要通过**SENTINEL sentinels **拉取并连接Sentinel; 127 | 3. 连接完毕后需要先通过**SENTINEL master **拉取master数据; 128 | 4. 发送失败的重试流程是重新走**SENTINEL master **拉取master; 129 | 5. **SENTINEL master **失败和Sentinel连接出现问题需要先执行***2***,再重试; 130 | 6. 由于Sentinel拉取master地址之前,还不能建立到master的连接,所以Channel里要保存需要发送的命令Sds队列,并等待连接成功后发送。如果Sential连接失败或者拉取不到服务器地址,要执行回调并出错。 131 | 132 | ### 更进一步 133 | 1. 更好的实现方式是可以订阅Sentinel,从而更快地响应故障转移; 134 | 2. 读操作在配置允许的情况下可以走slave,以减小master压力; 135 | 3. Sentinel的连接可以共享。(这里貌似会涉及到单例管理器)。 136 | 137 | 不过这些功能可选,以后可以有时间再加。 138 | 139 | ### 设计总结 140 | 流程图如下: 141 | 142 | ![redis-ha-sentinel.png](res/2015/redis-ha-sentinel.png) 143 | 144 | 集群健康报告 145 | ------ 146 | 对于Cluster而言,使用**CLUSTER \* **命令就可以完成这些功能,并且总是随机取发送目标。 147 | 148 | 对于Sentinel而言,Sentinel提供了简单地方式获取master的状态。要获取更详细的信息,可能得用比较麻烦的方法,一次访问多个master和slaves(这里没仔细研究,以后再看) 149 | 150 | 另外Driver层可以提供一些事件给上层用于统计重连、断线等情况。 151 | 152 | 写在最后 153 | ------ 154 | 155 | 整体上最重要的思路就是用主循环来简化逻辑复杂度。而主循环增加的那部分CPU消耗几乎可以忽略,不过这种异步传参方式对应着大量的malloc操作,以后看需要可以优化成c++ allocator的机制,这样就能支持自定义内存池。 156 | 157 | 流程图中建立连接后的命令发送流程比较特别,因为hiredis的异步发送接口是向缓冲区中添加数据,并且等fd可写后才实际执行,所以可以不等connect完成就直接调用发送接口。 158 | 159 | 另外Cluster更新服务器连接池的方式比较讨巧,既能避免频繁更新地址池,又可以及时更新Slot缓存,需要注意一下。 160 | 161 | 以上思路我会先在 https://github.com/owt5008137/hiredis-happ 中进行实现和测试,然后用于生产环境。 162 | 163 | > Written with [StackEdit](https://stackedit.io/). 164 | -------------------------------------------------------------------------------- /doc/res/2015/Redis全异步(HA)Driver设计稿.vsdx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owent/hiredis-happ/33b0c02f353ec364be50585cfad00a1ff552a43a/doc/res/2015/Redis全异步(HA)Driver设计稿.vsdx -------------------------------------------------------------------------------- /doc/res/2015/redis-ha-cluster.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owent/hiredis-happ/33b0c02f353ec364be50585cfad00a1ff552a43a/doc/res/2015/redis-ha-cluster.png -------------------------------------------------------------------------------- /doc/res/2015/redis-ha-sentinel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owent/hiredis-happ/33b0c02f353ec364be50585cfad00a1ff552a43a/doc/res/2015/redis-ha-sentinel.png -------------------------------------------------------------------------------- /doc/res/2015/redis-rb-cluster.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owent/hiredis-happ/33b0c02f353ec364be50585cfad00a1ff552a43a/doc/res/2015/redis-rb-cluster.png -------------------------------------------------------------------------------- /include/detail/crc16.h: -------------------------------------------------------------------------------- 1 | // Copyright 2021 owent 2 | // Created by OWenT on 2015/08/19. 3 | // 4 | 5 | #ifndef HIREDIS_HAPP_HIREDIS_HAPP_DETAIL_CRC16_H 6 | #define HIREDIS_HAPP_HIREDIS_HAPP_DETAIL_CRC16_H 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | 13 | namespace hiredis { 14 | namespace happ { 15 | uint16_t crc16(const char *buf, size_t len); 16 | } 17 | } // namespace hiredis 18 | 19 | #endif -------------------------------------------------------------------------------- /include/detail/happ_cluster.h: -------------------------------------------------------------------------------- 1 | // Copyright 2021 owent 2 | // Created by owent on 2015/08/18. 3 | // 4 | 5 | #ifndef HIREDIS_HAPP_HIREDIS_HAPP_CLUSTER_H 6 | #define HIREDIS_HAPP_HIREDIS_HAPP_CLUSTER_H 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #include "hiredis_happ_config.h" 15 | 16 | #include "happ_connection.h" 17 | 18 | namespace hiredis { 19 | namespace happ { 20 | class cluster { 21 | public: 22 | typedef cmd_exec cmd_t; 23 | 24 | struct HIREDIS_HAPP_API_HEAD_ONLY slot_t { 25 | int index; 26 | std::vector hosts; 27 | }; 28 | 29 | typedef connection connection_t; 30 | typedef HIREDIS_HAPP_MAP(std::string, std::unique_ptr) connection_map_t; 31 | 32 | typedef std::function onconnect_fn_t; 33 | typedef std::function onconnected_fn_t; 34 | typedef std::function ondisconnected_fn_t; 35 | typedef std::function log_fn_t; 36 | 37 | struct config_t { 38 | connection::key_t init_connection; 39 | log_fn_t log_fn_info; 40 | log_fn_t log_fn_debug; 41 | char *log_buffer; 42 | size_t log_max_size; 43 | 44 | time_t timer_interval_sec; 45 | time_t timer_interval_usec; 46 | time_t timer_timeout_sec; 47 | 48 | size_t cmd_buffer_size; 49 | }; 50 | 51 | struct timer_t { 52 | time_t last_update_sec; 53 | time_t last_update_usec; 54 | 55 | struct delay_t { 56 | time_t sec; 57 | time_t usec; 58 | cmd_t *cmd; 59 | }; 60 | std::list timer_pending; 61 | 62 | struct conn_timetout_t { 63 | std::string name; 64 | uint64_t sequence; 65 | time_t timeout; 66 | }; 67 | std::list timer_conns; 68 | }; 69 | 70 | private: 71 | cluster(const cluster &); 72 | cluster &operator=(const cluster &); 73 | 74 | public: 75 | HIREDIS_HAPP_API cluster(); 76 | HIREDIS_HAPP_API ~cluster(); 77 | 78 | HIREDIS_HAPP_API int init(const std::string &ip, uint16_t port); 79 | 80 | HIREDIS_HAPP_API const std::string &get_auth_password(); 81 | HIREDIS_HAPP_API void set_auth_password(const std::string &passwd); 82 | 83 | HIREDIS_HAPP_API const connection::auth_fn_t &get_auth_fn(); 84 | HIREDIS_HAPP_API void set_auth_fn(connection::auth_fn_t fn); 85 | 86 | HIREDIS_HAPP_API int start(); 87 | 88 | HIREDIS_HAPP_API int reset(); 89 | 90 | /** 91 | * @breif send a request to redis server 92 | * @param key the key used to calculate slot id 93 | * @param ks key size 94 | * @param cbk callback 95 | * @param priv_data private data passed to callback 96 | * @param argc argument count 97 | * @param argv pointer of every argument 98 | * @param argvlen size of every argument 99 | * 100 | * @note it can not be used to send subscribe, unsubscribe or monitor command.(because they are 101 | * not request-response message) hiredis deal with these command without notify event, so you can 102 | * only use connection::redis_raw_cmd to do these when connection finished or disconnected 103 | * 104 | * @see connection::redis_raw_cmd 105 | * @see connection::redis_cmd 106 | * @return command wrapper of this message, NULL if failed 107 | */ 108 | HIREDIS_HAPP_API cmd_t *exec(const char *key, size_t ks, cmd_t::callback_fn_t cbk, void *priv_data, int argc, 109 | const char **argv, const size_t *argvlen); 110 | 111 | /** 112 | * @breif send a request to redis server 113 | * @param key the key used to calculate slot id 114 | * @param ks key size 115 | * @param cbk callback 116 | * @param priv_data private data passed to callback 117 | * @param fmt format string 118 | * @param ... format data 119 | * 120 | * @note it can not be used to send subscribe, unsubscribe or monitor command.(because they are 121 | * not request-response message) hiredis deal with these command without notify event, so you can 122 | * only use connection::redis_raw_cmd to do these when connection finished or disconnected 123 | * 124 | * @see connection::redis_raw_cmd 125 | * @see connection::redis_cmd 126 | * @return command wrapper of this message, NULL if failed 127 | */ 128 | HIREDIS_HAPP_API cmd_t *exec(const char *key, size_t ks, cmd_t::callback_fn_t cbk, void *priv_data, const char *fmt, 129 | ...); 130 | 131 | /** 132 | * @breif send a request to redis server 133 | * @param key the key used to calculate slot id 134 | * @param ks key size 135 | * @param cbk callback 136 | * @param priv_data private data passed to callback 137 | * @param fmt format string 138 | * @param ap format data 139 | * 140 | * @note it can not be used to send subscribe, unsubscribe or monitor command.(because they are 141 | * not request-response message) hiredis deal with these command without notify event, so you can 142 | * only use connection::redis_raw_cmd to do these when connection finished or disconnected 143 | * 144 | * @see connection::redis_raw_cmd 145 | * @see connection::redis_cmd 146 | * @return command wrapper of this message, NULL if failed 147 | */ 148 | HIREDIS_HAPP_API cmd_t *exec(const char *key, size_t ks, cmd_t::callback_fn_t cbk, void *priv_data, const char *fmt, 149 | va_list ap); 150 | 151 | /** 152 | * @breif send a request to redis server 153 | * @param key the key used to calculate slot id 154 | * @param ks key size 155 | * @param cmd cmd wrapper 156 | * 157 | * @note it can not be used to send subscribe, unsubscribe or monitor command.(because they are 158 | * not request-response message) hiredis deal with these command without notify event, so you can 159 | * only use connection::redis_raw_cmd to do these when connection finished or disconnected 160 | * 161 | * @see connection::redis_raw_cmd 162 | * @see connection::redis_cmd 163 | * @return command wrapper of this message, NULL if failed 164 | */ 165 | HIREDIS_HAPP_API cmd_t *exec(const char *key, size_t ks, cmd_t *cmd); 166 | 167 | /** 168 | * @breif send a request to specifed redis server 169 | * @param conn which connect to sent to 170 | * @param cmd cmd wrapper 171 | * 172 | * @note it can not be used to send subscribe, unsubscribe or monitor command.(because they are 173 | * not request-response message) hiredis deal with these command without notify event, so you can 174 | * only use connection::redis_raw_cmd to do these when connection finished or disconnected 175 | * 176 | * @see connection::redis_raw_cmd 177 | * @see connection::redis_cmd 178 | * @return command wrapper of this message, NULL if failed 179 | */ 180 | HIREDIS_HAPP_API cmd_t *exec(connection_t *conn, cmd_t *cmd); 181 | 182 | /** 183 | * @breif retry to send a request to redis server 184 | * @param cmd cmd wrapper 185 | * @param conn which connect to sent to(pass NULL to try to get one using the key in cmd) 186 | * 187 | * @note it can not be used to send subscribe, unsubscribe or monitor command.(because they are 188 | * not request-response message) hiredis deal with these command without notify event, so you can 189 | * only use connection::redis_raw_cmd to do these when connection finished or disconnected 190 | * 191 | * @see connection::redis_raw_cmd 192 | * @see connection::redis_cmd 193 | * @return command wrapper of this message, NULL if failed 194 | */ 195 | HIREDIS_HAPP_API cmd_t *retry(cmd_t *cmd, connection_t *conn = NULL); 196 | 197 | HIREDIS_HAPP_API bool reload_slots(); 198 | 199 | HIREDIS_HAPP_API const connection::key_t *get_slot_master(int index); 200 | 201 | /** 202 | * @breif get slot info of a key 203 | * @param key the key used to calculate slot id 204 | * @param ks key size 205 | * @return slot info of this key 206 | */ 207 | HIREDIS_HAPP_API const slot_t *get_slot_by_key(const char *key, size_t ks) const; 208 | 209 | HIREDIS_HAPP_API const connection_t *get_connection(const std::string &key) const; 210 | HIREDIS_HAPP_API connection_t *get_connection(const std::string &key); 211 | 212 | HIREDIS_HAPP_API const connection_t *get_connection(const std::string &ip, uint16_t port) const; 213 | HIREDIS_HAPP_API connection_t *get_connection(const std::string &ip, uint16_t port); 214 | 215 | HIREDIS_HAPP_API connection_t *make_connection(const connection::key_t &key); 216 | HIREDIS_HAPP_API bool release_connection(const connection::key_t &key, bool close_fd, int status); 217 | 218 | HIREDIS_HAPP_API size_t get_connection_size() const; 219 | 220 | HIREDIS_HAPP_API onconnect_fn_t set_on_connect(onconnect_fn_t cbk); 221 | HIREDIS_HAPP_API onconnected_fn_t set_on_connected(onconnected_fn_t cbk); 222 | HIREDIS_HAPP_API ondisconnected_fn_t set_on_disconnected(ondisconnected_fn_t cbk); 223 | 224 | HIREDIS_HAPP_API void set_cmd_buffer_size(size_t s); 225 | 226 | HIREDIS_HAPP_API size_t get_cmd_buffer_size() const; 227 | 228 | HIREDIS_HAPP_API bool is_timer_active() const; 229 | 230 | HIREDIS_HAPP_API void set_timer_interval(time_t sec, time_t usec); 231 | 232 | HIREDIS_HAPP_API void set_timeout(time_t sec); 233 | 234 | HIREDIS_HAPP_API void add_timer_cmd(cmd_t *cmd); 235 | 236 | HIREDIS_HAPP_API int proc(time_t sec, time_t usec); 237 | 238 | HIREDIS_HAPP_API void set_log_writer(log_fn_t info_fn, log_fn_t debug_fn, size_t max_size = 65536); 239 | 240 | HIREDIS_HAPP_API const timer_t &get_timer_actions() const; 241 | 242 | private: 243 | HIREDIS_HAPP_API cmd_t *create_cmd(cmd_t::callback_fn_t cbk, void *pridata); 244 | HIREDIS_HAPP_API void destroy_cmd(cmd_t *c); 245 | HIREDIS_HAPP_API int call_cmd(cmd_t *c, int err, redisAsyncContext *context, void *reply); 246 | 247 | static void on_reply_wrapper(redisAsyncContext *c, void *r, void *privdata); 248 | static void on_reply_update_slot(cmd_exec *cmd, redisAsyncContext *c, void *r, void *privdata); 249 | static void on_reply_asking(redisAsyncContext *c, void *r, void *privdata); 250 | static void on_connected_wrapper(const struct redisAsyncContext *, int status); 251 | static void on_disconnected_wrapper(const struct redisAsyncContext *, int status); 252 | 253 | static void on_reply_auth(cmd_exec *cmd, redisAsyncContext *c, void *r, void *privdata); 254 | 255 | void remove_connection_key(const std::string &name); 256 | 257 | private: 258 | void log_debug(const char *fmt, ...); 259 | 260 | void log_info(const char *fmt, ...); 261 | 262 | private: 263 | config_t conf_; 264 | 265 | // authorization information 266 | connection::auth_info_t auth_; 267 | 268 | // slot information 269 | struct slot_status { 270 | enum type { INVALID = 0, UPDATING, OK }; 271 | }; 272 | slot_t slots_[HIREDIS_HAPP_SLOT_NUMBER]; 273 | slot_status::type slot_flag_; 274 | // retry cmd queue after slots_ reloaded 275 | std::list slot_pending_; 276 | 277 | // connection pool 278 | connection_map_t connections_; 279 | 280 | // timer 281 | timer_t timer_actions_; 282 | 283 | // callbacks_ 284 | struct callback_set_t { 285 | onconnect_fn_t on_connect; 286 | onconnected_fn_t on_connected; 287 | ondisconnected_fn_t on_disconnected; 288 | }; 289 | callback_set_t callbacks_; 290 | }; 291 | } // namespace happ 292 | } // namespace hiredis 293 | 294 | #endif // HIREDIS_HAPP_HIREDIS_HAPP_CLUSTER_H -------------------------------------------------------------------------------- /include/detail/happ_cmd.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by OWenT on 2015/08/19. 3 | // 4 | 5 | #ifndef HIREDIS_HAPP_HIREDIS_HAPP_CMD_H 6 | #define HIREDIS_HAPP_HIREDIS_HAPP_CMD_H 7 | 8 | #pragma once 9 | 10 | #include 11 | 12 | #include "hiredis_happ_config.h" 13 | 14 | namespace hiredis { 15 | namespace happ { 16 | class cluster; 17 | class raw; 18 | class connection; 19 | 20 | union HIREDIS_HAPP_API_HEAD_ONLY holder_t { 21 | cluster *clu; 22 | raw *r; 23 | }; 24 | 25 | struct HIREDIS_HAPP_API_HEAD_ONLY cmd_content { 26 | size_t raw_len; 27 | union { 28 | char *raw; 29 | sds redis_sds; 30 | } content; 31 | }; 32 | 33 | class cmd_exec { 34 | public: 35 | typedef void (*callback_fn_t)(cmd_exec *, struct redisAsyncContext *, void *, void *); 36 | 37 | HIREDIS_HAPP_API int64_t vformat(int argc, const char **argv, const size_t *argvlen); 38 | 39 | HIREDIS_HAPP_API int format(const char *fmt, ...); 40 | 41 | HIREDIS_HAPP_API int vformat(const char *fmt, va_list ap); 42 | 43 | HIREDIS_HAPP_API int vformat(const sds *src); 44 | 45 | HIREDIS_HAPP_API int call_reply(int rcode, redisAsyncContext *context, void *reply); 46 | 47 | HIREDIS_HAPP_API int result() const; 48 | 49 | HIREDIS_HAPP_API void *buffer(); 50 | 51 | HIREDIS_HAPP_API const void *buffer() const; 52 | 53 | HIREDIS_HAPP_API void *private_data() const; 54 | 55 | HIREDIS_HAPP_API void private_data(void *pd); 56 | 57 | HIREDIS_HAPP_API const char *pick_argument(const char *start, const char **str, size_t *len); 58 | 59 | HIREDIS_HAPP_API const char *pick_cmd(const char **str, size_t *len); 60 | 61 | static HIREDIS_HAPP_API void dump(std::ostream &out, redisReply *reply, int ident = 0); 62 | 63 | HIREDIS_HAPP_API holder_t get_holder() const; 64 | 65 | HIREDIS_HAPP_API callback_fn_t get_callback_fn() const; 66 | 67 | HIREDIS_HAPP_API cmd_content get_cmd_raw_content() const; 68 | 69 | HIREDIS_HAPP_API int get_error_code() const; 70 | 71 | /** 72 | * @brief create raw_cmd_content_ object(This function is public only for unit test, please don't 73 | * use it directly) 74 | * @param holder_ owner of this 75 | * @param cbk callback_ when raw_cmd_content_ finished 76 | * @param pridata private data 77 | * @param buff_len alloacte some memory inner raw_cmd_content_(this can be used to store some more 78 | * data for later usage) 79 | * @return address of raw_cmd_content_ object if success 80 | */ 81 | static HIREDIS_HAPP_API cmd_exec *create(holder_t holder_, callback_fn_t cbk, void *pridata, size_t buffer_len); 82 | 83 | /** 84 | * @brief destroy raw_cmd_content_ object(This function is public only for unit test, please don't 85 | * use it directly) 86 | * @param c destroy raw_cmd_content_ object 87 | */ 88 | static HIREDIS_HAPP_API void destroy(cmd_exec *c); 89 | 90 | private: 91 | friend class cluster; 92 | friend class raw; 93 | friend class connection; 94 | 95 | holder_t holder_; // holder_ 96 | cmd_content raw_cmd_content_; 97 | size_t ttl_; // left retry times(just like network ttl_) 98 | callback_fn_t callback_; // user callback_ function 99 | 100 | // ========= exec data ========= 101 | int error_code_; // error code, just like redisAsyncContext::error_code_ 102 | union { 103 | int slot; // slot index if in cluster, -1 means random 104 | } engine_; 105 | 106 | void *private_data_; // user pri data 107 | }; 108 | } // namespace happ 109 | } // namespace hiredis 110 | 111 | #endif // HIREDIS_HAPP_HIREDIS_HAPP_CMD_H 112 | -------------------------------------------------------------------------------- /include/detail/happ_connection.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by OWenT on 2015/08/20. 3 | // 4 | 5 | #ifndef HIREDIS_HAPP_HIREDIS_HAPP_CONNECTION_H 6 | #define HIREDIS_HAPP_HIREDIS_HAPP_CONNECTION_H 7 | 8 | #pragma once 9 | 10 | #include 11 | 12 | #include "hiredis_happ_config.h" 13 | 14 | #include "happ_cmd.h" 15 | 16 | namespace hiredis { 17 | namespace happ { 18 | class cluster; 19 | 20 | class connection { 21 | public: 22 | struct HIREDIS_HAPP_API_HEAD_ONLY status { 23 | enum type { DISCONNECTED = 0, CONNECTING, CONNECTED }; 24 | }; 25 | 26 | struct HIREDIS_HAPP_API_HEAD_ONLY key_t { 27 | std::string name; 28 | uint16_t port; 29 | std::string ip; 30 | }; 31 | 32 | typedef std::function auth_fn_t; 33 | struct HIREDIS_HAPP_API_HEAD_ONLY auth_info_t { 34 | auth_fn_t auth_fn; 35 | std::string password; 36 | }; 37 | 38 | HIREDIS_HAPP_API connection(); 39 | HIREDIS_HAPP_API ~connection(); 40 | 41 | HIREDIS_HAPP_API uint64_t get_sequence() const; 42 | 43 | HIREDIS_HAPP_API void init(holder_t h, const std::string &ip, uint16_t port); 44 | 45 | HIREDIS_HAPP_API void init(holder_t h, const key_t &k); 46 | 47 | HIREDIS_HAPP_API status::type set_connecting(redisAsyncContext *c); 48 | 49 | HIREDIS_HAPP_API status::type set_disconnected(bool close_fd); 50 | 51 | HIREDIS_HAPP_API status::type set_connected(); 52 | 53 | /** 54 | * @brief send message wrapped with cmd_exec to redis server 55 | * @param c cmd data 56 | * @param fn callback 57 | * @return 0 or error code 58 | */ 59 | HIREDIS_HAPP_API int redis_cmd(cmd_exec *c, redisCallbackFn fn); 60 | 61 | /** 62 | * @brief send raw message redis server 63 | * @param fn callback 64 | * @param priv_data private data passed to callback 65 | * @param fmt format string 66 | * @param ... format data 67 | * @return 0 or error code 68 | */ 69 | HIREDIS_HAPP_API int redis_raw_cmd(redisCallbackFn *fn, void *priv_data, const char *fmt, ...); 70 | 71 | /** 72 | * @brief send raw message redis server 73 | * @param fn callback 74 | * @param priv_data private data passed to callback 75 | * @param fmt format string 76 | * @param ap format data 77 | * @return 0 or error code 78 | */ 79 | HIREDIS_HAPP_API int redis_raw_cmd(redisCallbackFn *fn, void *priv_data, const char *fmt, va_list ap); 80 | 81 | /** 82 | * @brief send raw message redis server 83 | * @param fn callback 84 | * @param priv_data private data passed to callback 85 | * @param src formated sds object 86 | * @return 0 or error code 87 | */ 88 | HIREDIS_HAPP_API int redis_raw_cmd(redisCallbackFn *fn, void *priv_data, const sds *src); 89 | 90 | /** 91 | * @brief send raw message redis server 92 | * @param fn callback 93 | * @param priv_data private data passed to callback 94 | * @param argc argument count 95 | * @param argv pointer of every argument 96 | * @param argvlen size of every argument 97 | * @return 0 or error code 98 | */ 99 | HIREDIS_HAPP_API int redis_raw_cmd(redisCallbackFn *fn, void *priv_data, int argc, const char **argv, 100 | const size_t *argvlen); 101 | 102 | /** 103 | * @brief call reply callback of c with reply 104 | * @note if c!=NULL, it will always call callback and be freed 105 | */ 106 | HIREDIS_HAPP_API int call_reply(cmd_exec *c, void *reply); 107 | 108 | /** 109 | * @brief pop specify cmd from pending list, do nothing if c!=NULL and is not in pending list 110 | * @note if c!=NULL, all cmds before c will trigger timeout 111 | * @return first cmd or c 112 | */ 113 | HIREDIS_HAPP_API cmd_exec *pop_reply(cmd_exec *c); 114 | 115 | HIREDIS_HAPP_API redisAsyncContext *get_context() const; 116 | 117 | HIREDIS_HAPP_API void release(bool close_fd); 118 | 119 | HIREDIS_HAPP_API const key_t &get_key() const; 120 | 121 | HIREDIS_HAPP_API holder_t get_holder() const; 122 | 123 | HIREDIS_HAPP_API status::type get_status() const; 124 | 125 | private: 126 | connection(const connection &); 127 | connection &operator=(const connection &); 128 | 129 | void make_sequence(); 130 | 131 | public: 132 | static HIREDIS_HAPP_API std::string make_name(const std::string &ip, uint16_t port); 133 | static HIREDIS_HAPP_API void set_key(connection::key_t &k, const std::string &ip, uint16_t port); 134 | static HIREDIS_HAPP_API bool pick_name(const std::string &name, std::string &ip, uint16_t &port); 135 | 136 | private: 137 | key_t key_; 138 | uint64_t sequence_; 139 | 140 | holder_t holder_; 141 | redisAsyncContext *context_; 142 | 143 | // cmds inner this connection 144 | std::list reply_list_; 145 | status::type conn_status_; 146 | }; 147 | } // namespace happ 148 | } // namespace hiredis 149 | 150 | #endif // HIREDIS_HAPP_HIREDIS_HAPP_CONNECTION_H 151 | -------------------------------------------------------------------------------- /include/detail/happ_raw.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by owent on 2016/04/20. 3 | // 4 | 5 | #ifndef HIREDIS_HAPP_HIREDIS_HAPP_RAW_H 6 | #define HIREDIS_HAPP_HIREDIS_HAPP_RAW_H 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | 13 | #include "hiredis_happ_config.h" 14 | 15 | #include "happ_connection.h" 16 | 17 | namespace hiredis { 18 | namespace happ { 19 | class raw { 20 | public: 21 | typedef cmd_exec cmd_t; 22 | 23 | typedef connection connection_t; 24 | typedef std::unique_ptr connection_ptr_t; 25 | 26 | typedef std::function onconnect_fn_t; 27 | typedef std::function onconnected_fn_t; 28 | typedef std::function ondisconnected_fn_t; 29 | typedef std::function log_fn_t; 30 | 31 | struct config_t { 32 | connection::key_t init_connection; 33 | log_fn_t log_fn_info; 34 | log_fn_t log_fn_debug; 35 | char *log_buffer; 36 | size_t log_max_size; 37 | 38 | time_t timer_interval_sec; 39 | time_t timer_interval_usec; 40 | time_t timer_timeout_sec; 41 | 42 | size_t cmd_buffer_size; 43 | }; 44 | 45 | struct timer_t { 46 | time_t last_update_sec; 47 | time_t last_update_usec; 48 | 49 | struct delay_t { 50 | time_t sec; 51 | time_t usec; 52 | cmd_t *cmd; 53 | }; 54 | std::list timer_pending; 55 | 56 | struct conn_timetout_t { 57 | uint64_t sequence; 58 | time_t timeout; 59 | }; 60 | conn_timetout_t timer_conn; 61 | }; 62 | 63 | private: 64 | raw(const raw &); 65 | raw &operator=(const raw &); 66 | 67 | public: 68 | HIREDIS_HAPP_API raw(); 69 | HIREDIS_HAPP_API ~raw(); 70 | 71 | HIREDIS_HAPP_API int init(const std::string &ip, uint16_t port); 72 | 73 | HIREDIS_HAPP_API const std::string &get_auth_password(); 74 | HIREDIS_HAPP_API void set_auth_password(const std::string &passwd); 75 | 76 | HIREDIS_HAPP_API const connection::auth_fn_t &get_auth_fn(); 77 | HIREDIS_HAPP_API void set_auth_fn(connection::auth_fn_t fn); 78 | 79 | HIREDIS_HAPP_API int start(); 80 | 81 | HIREDIS_HAPP_API int reset(); 82 | 83 | /** 84 | * @breif send a request to redis server 85 | * @param cbk callback 86 | * @param priv_data private data passed to callback 87 | * @param argc argument count 88 | * @param argv pointer of every argument 89 | * @param argvlen size of every argument 90 | * 91 | * @note it can not be used to send subscribe, unsubscribe or monitor command.(because they are 92 | * not request-response message) hiredis deal with these command without notify event, so you can 93 | * only use connection::redis_raw_cmd to do these when connection finished or disconnected 94 | * 95 | * @see connection::redis_raw_cmd 96 | * @see connection::redis_cmd 97 | * @return command wrapper of this message, NULL if failed 98 | */ 99 | HIREDIS_HAPP_API cmd_t *exec(cmd_t::callback_fn_t cbk, void *priv_data, int argc, const char **argv, 100 | const size_t *argvlen); 101 | 102 | /** 103 | * @breif send a request to redis server 104 | * @param cbk callback 105 | * @param priv_data private data passed to callback 106 | * @param fmt format string 107 | * @param ... format data 108 | * 109 | * @note it can not be used to send subscribe, unsubscribe or monitor command.(because they are 110 | * not request-response message) hiredis deal with these command without notify event, so you can 111 | * only use connection::redis_raw_cmd to do these when connection finished or disconnected 112 | * 113 | * @see connection::redis_raw_cmd 114 | * @see connection::redis_cmd 115 | * @return command wrapper of this message, NULL if failed 116 | */ 117 | HIREDIS_HAPP_API cmd_t *exec(cmd_t::callback_fn_t cbk, void *priv_data, const char *fmt, ...); 118 | 119 | /** 120 | * @breif send a request to redis server 121 | * @param cbk callback 122 | * @param priv_data private data passed to callback 123 | * @param fmt format string 124 | * @param ap format data 125 | * 126 | * @note it can not be used to send subscribe, unsubscribe or monitor command.(because they are 127 | * not request-response message) hiredis deal with these command without notify event, so you can 128 | * only use connection::redis_raw_cmd to do these when connection finished or disconnected 129 | * 130 | * @see connection::redis_raw_cmd 131 | * @see connection::redis_cmd 132 | * @return command wrapper of this message, NULL if failed 133 | */ 134 | HIREDIS_HAPP_API cmd_t *exec(cmd_t::callback_fn_t cbk, void *priv_data, const char *fmt, va_list ap); 135 | 136 | /** 137 | * @breif send a request to redis server 138 | * @param cmd cmd wrapper 139 | * 140 | * @note it can not be used to send subscribe, unsubscribe or monitor command.(because they are 141 | * not request-response message) hiredis deal with these command without notify event, so you can 142 | * only use connection::redis_raw_cmd to do these when connection finished or disconnected 143 | * 144 | * @see connection::redis_raw_cmd 145 | * @see connection::redis_cmd 146 | * @return command wrapper of this message, NULL if failed 147 | */ 148 | HIREDIS_HAPP_API cmd_t *exec(cmd_t *cmd); 149 | 150 | /** 151 | * @breif send a request to specifed redis server 152 | * @param conn which connect to sent to 153 | * @param cmd cmd wrapper 154 | * 155 | * @note it can not be used to send subscribe, unsubscribe or monitor command.(because they are 156 | * not request-response message) hiredis deal with these command without notify event, so you can 157 | * only use connection::redis_raw_cmd to do these when connection finished or disconnected 158 | * 159 | * @see connection::redis_raw_cmd 160 | * @see connection::redis_cmd 161 | * @return command wrapper of this message, NULL if failed 162 | */ 163 | HIREDIS_HAPP_API cmd_t *exec(connection_t *conn, cmd_t *cmd); 164 | 165 | /** 166 | * @breif retry to send a request to redis server 167 | * @param cmd cmd wrapper 168 | * @param conn which connect to sent to(pass NULL to try to get one using the key in cmd) 169 | * 170 | * @note it can not be used to send subscribe, unsubscribe or monitor command.(because they are 171 | * not request-response message) hiredis deal with these command without notify event, so you can 172 | * only use connection::redis_raw_cmd to do these when connection finished or disconnected 173 | * 174 | * @see connection::redis_raw_cmd 175 | * @see connection::redis_cmd 176 | * @return command wrapper of this message, NULL if failed 177 | */ 178 | HIREDIS_HAPP_API cmd_t *retry(cmd_t *cmd, connection_t *conn = NULL); 179 | 180 | HIREDIS_HAPP_API const connection_t *get_connection() const; 181 | HIREDIS_HAPP_API connection_t *get_connection(); 182 | 183 | HIREDIS_HAPP_API connection_t *make_connection(); 184 | HIREDIS_HAPP_API bool release_connection(bool close_fd, int status); 185 | 186 | HIREDIS_HAPP_API onconnect_fn_t set_on_connect(onconnect_fn_t cbk); 187 | HIREDIS_HAPP_API onconnected_fn_t set_on_connected(onconnected_fn_t cbk); 188 | HIREDIS_HAPP_API ondisconnected_fn_t set_on_disconnected(ondisconnected_fn_t cbk); 189 | 190 | HIREDIS_HAPP_API void set_cmd_buffer_size(size_t s); 191 | 192 | HIREDIS_HAPP_API size_t get_cmd_buffer_size() const; 193 | 194 | HIREDIS_HAPP_API bool is_timer_active() const; 195 | 196 | HIREDIS_HAPP_API void set_timer_interval(time_t sec, time_t usec); 197 | 198 | HIREDIS_HAPP_API void set_timeout(time_t sec); 199 | 200 | HIREDIS_HAPP_API void add_timer_cmd(cmd_t *cmd); 201 | 202 | HIREDIS_HAPP_API int proc(time_t sec, time_t usec); 203 | 204 | HIREDIS_HAPP_API void set_log_writer(log_fn_t info_fn, log_fn_t debug_fn, size_t max_size = 65536); 205 | 206 | private: 207 | HIREDIS_HAPP_API cmd_t *create_cmd(cmd_t::callback_fn_t cbk, void *pridata); 208 | HIREDIS_HAPP_API void destroy_cmd(cmd_t *c); 209 | HIREDIS_HAPP_API int call_cmd(cmd_t *c, int err, redisAsyncContext *context, void *reply); 210 | 211 | private: 212 | static void on_reply_wrapper(redisAsyncContext *c, void *r, void *privdata); 213 | static void on_connected_wrapper(const struct redisAsyncContext *, int status); 214 | static void on_disconnected_wrapper(const struct redisAsyncContext *, int status); 215 | 216 | static void on_reply_auth(cmd_exec *cmd, redisAsyncContext *c, void *r, void *privdata); 217 | 218 | void log_debug(const char *fmt, ...); 219 | 220 | void log_info(const char *fmt, ...); 221 | 222 | private: 223 | config_t conf_; 224 | 225 | // authorization information 226 | connection::auth_info_t auth_; 227 | 228 | // current connection 229 | connection_ptr_t conn_; 230 | 231 | // timers 232 | timer_t timer_actions_; 233 | 234 | // callbacks_ 235 | struct callback_set_t { 236 | onconnect_fn_t on_connect; 237 | onconnected_fn_t on_connected; 238 | ondisconnected_fn_t on_disconnected; 239 | }; 240 | callback_set_t callbacks_; 241 | }; 242 | } // namespace happ 243 | } // namespace hiredis 244 | 245 | #endif // HIREDIS_HAPP_HIREDIS_HAPP_CLUSTER_H -------------------------------------------------------------------------------- /include/detail/hiredis_happ_config.h.in: -------------------------------------------------------------------------------- 1 | // Copyright 2021 owent 2 | // Created by OWenT on 2015/08/18. 3 | // 4 | 5 | #ifndef HIREDIS_HAPP_HIREDIS_HAPP_CONFIG_H 6 | #define HIREDIS_HAPP_HIREDIS_HAPP_CONFIG_H 7 | 8 | #pragma once 9 | 10 | #if defined(_WIN32) 11 | # ifndef WIN32_LEAN_AND_MEAN 12 | # define WIN32_LEAN_AND_MEAN 13 | # endif 14 | # ifndef NOMINMAX 15 | # define NOMINMAX 16 | # endif 17 | #endif 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #if defined(__cplusplus) && __cplusplus >= 201103L 29 | # include 30 | # define HIREDIS_HAPP_ATOMIC_STD 31 | #elif defined(__clang__) && (__clang_major__ > 3 || (__clang_major__ == 3 && __clang_minor__ >= 1)) && \ 32 | __cplusplus >= 201103L 33 | # include 34 | # define HIREDIS_HAPP_ATOMIC_STD 35 | #elif defined(_MSC_VER) && _MSC_VER > 1700 36 | # include 37 | # define HIREDIS_HAPP_ATOMIC_STD 38 | #elif defined(__GNUC__) 39 | # if ((__GNUC__ == 4 && __GNUC_MINOR__ >= 5) || __GNUC__ > 4) && defined(__GXX_EXPERIMENTAL_CXX0X__) 40 | # include 41 | # define HIREDIS_HAPP_ATOMIC_STD 42 | # endif 43 | #elif defined(__clang__) || defined(__clang__) || defined(__INTEL_COMPILER) 44 | # if defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 1)) 45 | # error GCC version must be greater or equal than 4.1 46 | # endif 47 | # if defined(__INTEL_COMPILER) && __INTEL_COMPILER < 1100 48 | # error Intel Compiler version must be greater or equal than 11.0 49 | # endif 50 | 51 | # if defined(__GCC_ATOMIC_INT_LOCK_FREE) 52 | # define HIREDIS_HAPP_ATOMIC_GCC_ATOMIC 1 53 | # else 54 | # define HIREDIS_HAPP_ATOMIC_GCC 1 55 | # endif 56 | #elif defined(_MSC_VER) 57 | # include 58 | # define HIREDIS_HAPP_ATOMIC_MSVC 1 59 | #endif 60 | 61 | #if defined(__cplusplus) 62 | extern "C" { 63 | #endif 64 | 65 | #if defined(_MSC_VER) 66 | # pragma warning(push) 67 | 68 | # if ((defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)) 69 | # pragma warning(disable : 4996) 70 | # pragma warning(disable : 4309) 71 | # endif 72 | 73 | # if _MSC_VER < 1910 74 | # pragma warning(disable : 4800) 75 | # endif 76 | # pragma warning(disable : 4251) 77 | # pragma warning(disable : 4200) 78 | # pragma warning(disable : 4005) 79 | #endif 80 | 81 | #if defined(__GNUC__) && !defined(__clang__) && !defined(__apple_build_version__) 82 | # if (__GNUC__ * 100 + __GNUC_MINOR__ * 10) >= 460 83 | # pragma GCC diagnostic push 84 | # endif 85 | # pragma GCC diagnostic ignored "-Wunused-parameter" 86 | #elif defined(__clang__) || defined(__apple_build_version__) 87 | # pragma clang diagnostic push 88 | # pragma clang diagnostic ignored "-Wunused-parameter" 89 | #endif 90 | 91 | #include 92 | #include 93 | #include 94 | 95 | #if defined(__GNUC__) && !defined(__clang__) && !defined(__apple_build_version__) 96 | # if (__GNUC__ * 100 + __GNUC_MINOR__ * 10) >= 460 97 | # pragma GCC diagnostic pop 98 | # endif 99 | #elif defined(__clang__) || defined(__apple_build_version__) 100 | # pragma clang diagnostic pop 101 | #endif 102 | 103 | #if defined(_MSC_VER) 104 | # pragma warning(pop) 105 | #endif 106 | 107 | #if defined(__cplusplus) 108 | } 109 | #endif 110 | 111 | #define HIREDIS_HAPP_SLOT_NUMBER 16384 112 | 113 | #if (defined(__cplusplus) && __cplusplus >= 201103L) || (defined(_MSC_VER) && _MSC_VER >= 1600) 114 | # include 115 | # define HIREDIS_HAPP_MAP(...) std::unordered_map<__VA_ARGS__> 116 | 117 | #else 118 | # include 119 | # define HIREDIS_HAPP_MAP(...) std::map<__VA_ARGS__> 120 | #endif 121 | 122 | #ifndef HIREDIS_HAPP_TTL 123 | # define HIREDIS_HAPP_TTL 16 124 | #endif 125 | 126 | #ifndef HIREDIS_HAPP_TIMER_INTERVAL_SEC 127 | // 0 s 128 | # define HIREDIS_HAPP_TIMER_INTERVAL_SEC 0 129 | #endif 130 | 131 | #ifndef HIREDIS_HAPP_TIMER_INTERVAL_USEC 132 | // 100 ms 133 | # define HIREDIS_HAPP_TIMER_INTERVAL_USEC 100000 134 | #endif 135 | 136 | #ifndef HIREDIS_HAPP_TIMER_TIMEOUT_SEC 137 | // 30 s 138 | # define HIREDIS_HAPP_TIMER_TIMEOUT_SEC 30 139 | #endif 140 | 141 | #if defined(_MSC_VER) && _MSC_VER >= 1600 142 | # define HIREDIS_HAPP_STRCASE_CMP(l, r) _stricmp(l, r) 143 | # define HIREDIS_HAPP_STRNCASE_CMP(l, r, s) _strnicmp(l, r, s) 144 | # define HIREDIS_HAPP_STRCMP(l, r) strcmp(l, r) 145 | # define HIREDIS_HAPP_STRNCMP(l, r, s) strncmp(l, r, s) 146 | #else 147 | # define HIREDIS_HAPP_STRCASE_CMP(l, r) strcasecmp(l, r) 148 | # define HIREDIS_HAPP_STRNCASE_CMP(l, r, s) strncasecmp(l, r, s) 149 | # define HIREDIS_HAPP_STRCMP(l, r) strcmp(l, r) 150 | # define HIREDIS_HAPP_STRNCMP(l, r, s) strncmp(l, r, s) 151 | #endif 152 | 153 | #if (defined(_MSC_VER) && _MSC_VER >= 1600) || (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L) || \ 154 | defined(__STDC_LIB_EXT1__) 155 | # define HIREDIS_HAPP_SSCANF(...) sscanf_s(__VA_ARGS__) 156 | 157 | # ifdef _MSC_VER 158 | # define HIREDIS_HAPP_VSNPRINTF(buffer, bufsz, fmt, arg) \ 159 | vsnprintf_s(buffer, static_cast(bufsz), _TRUNCATE, fmt, arg) 160 | # define HIREDIS_HAPP_SNPRINTF(...) sprintf_s(__VA_ARGS__) 161 | # else 162 | # define HIREDIS_HAPP_VSNPRINTF(buffer, bufsz, fmt, arg) vsnprintf_s(buffer, static_cast(bufsz), fmt, arg) 163 | # define HIREDIS_HAPP_SNPRINTF(...) snprintf_s(__VA_ARGS__) 164 | # endif 165 | 166 | #else 167 | # define HIREDIS_HAPP_SSCANF(...) sscanf(__VA_ARGS__) 168 | # define HIREDIS_HAPP_SNPRINTF(...) snprintf(__VA_ARGS__) 169 | # define HIREDIS_HAPP_VSNPRINTF(buffer, bufsz, fmt, arg) vsnprintf(buffer, static_cast(bufsz), fmt, arg) 170 | #endif 171 | 172 | #ifdef _MSC_VER 173 | 174 | # ifndef inline 175 | # define inline __inline 176 | # endif 177 | 178 | # ifndef va_copy 179 | # define va_copy(d, s) ((d) = (s)) 180 | # endif 181 | 182 | # ifndef snprintf 183 | # define snprintf c99_snprintf 184 | # define vsnprintf c99_vsnprintf 185 | 186 | __inline int c99_vsnprintf(char *str, size_t size, const char *format, va_list ap) { 187 | int count = -1; 188 | 189 | if (size != 0) count = _vsnprintf_s(str, size, _TRUNCATE, format, ap); 190 | if (count == -1) count = _vscprintf(format, ap); 191 | 192 | return count; 193 | } 194 | 195 | __inline int c99_snprintf(char *str, size_t size, const char *format, ...) { 196 | int count; 197 | va_list ap; 198 | 199 | va_start(ap, format); 200 | count = c99_vsnprintf(str, size, format, ap); 201 | va_end(ap); 202 | 203 | return count; 204 | } 205 | # endif 206 | 207 | #endif 208 | 209 | // ================ import/export ================ 210 | // @see https://gcc.gnu.org/wiki/Visibility 211 | // @see http://releases.llvm.org/9.0.0/tools/clang/docs/AttributeReference.html 212 | // Do not support borland/sunpro_cc/xlcpp 213 | 214 | // ================ import/export: for compilers ================ 215 | #if defined(__GNUC__) && !defined(__ibmxl__) 216 | // GNU C++/Clang 217 | // 218 | // Dynamic shared object (DSO) and dynamic-link library (DLL) support 219 | // 220 | # if __GNUC__ >= 4 221 | # if defined(_WIN32) || defined(__WIN32__) || defined(WIN32) || defined(__CYGWIN__) 222 | // All Win32 development environments, including 64-bit Windows and MinGW, define 223 | // _WIN32 or one of its variant spellings. Note that Cygwin is a POSIX environment, 224 | // so does not define _WIN32 or its variants. 225 | # ifndef HIREDIS_HAPP_SYMBOL_EXPORT 226 | # define HIREDIS_HAPP_SYMBOL_EXPORT __attribute__((__dllexport__)) 227 | # endif 228 | # ifndef HIREDIS_HAPP_SYMBOL_IMPORT 229 | # define HIREDIS_HAPP_SYMBOL_IMPORT __attribute__((__dllimport__)) 230 | # endif 231 | 232 | # else 233 | 234 | # ifndef HIREDIS_HAPP_SYMBOL_EXPORT 235 | # define HIREDIS_HAPP_SYMBOL_EXPORT __attribute__((visibility("default"))) 236 | # endif 237 | # ifndef HIREDIS_HAPP_SYMBOL_IMPORT 238 | # define HIREDIS_HAPP_SYMBOL_IMPORT __attribute__((visibility("default"))) 239 | # endif 240 | # ifndef HIREDIS_HAPP_SYMBOL_VISIBLE 241 | # define HIREDIS_HAPP_SYMBOL_VISIBLE __attribute__((visibility("default"))) 242 | # endif 243 | # ifndef HIREDIS_HAPP_SYMBOL_LOCAL 244 | # define HIREDIS_HAPP_SYMBOL_LOCAL __attribute__((visibility("hidden"))) 245 | # endif 246 | 247 | # endif 248 | 249 | # else 250 | // config/platform/win32.hpp will define HIREDIS_HAPP_SYMBOL_EXPORT, etc., unless already defined 251 | # ifndef HIREDIS_HAPP_SYMBOL_EXPORT 252 | # define HIREDIS_HAPP_SYMBOL_EXPORT 253 | # endif 254 | 255 | # ifndef HIREDIS_HAPP_SYMBOL_IMPORT 256 | # define HIREDIS_HAPP_SYMBOL_IMPORT 257 | # endif 258 | # ifndef HIREDIS_HAPP_SYMBOL_VISIBLE 259 | # define HIREDIS_HAPP_SYMBOL_VISIBLE 260 | # endif 261 | # ifndef HIREDIS_HAPP_SYMBOL_LOCAL 262 | # define HIREDIS_HAPP_SYMBOL_LOCAL 263 | # endif 264 | 265 | # endif 266 | 267 | #elif defined(_MSC_VER) 268 | // Microsoft Visual C++ 269 | // 270 | // Must remain the last #elif since some other vendors (Metrowerks, for 271 | // example) also #define _MSC_VER 272 | #else 273 | #endif 274 | // ---------------- import/export: for compilers ---------------- 275 | 276 | // ================ import/export: for platform ================ 277 | // Default defines for HIREDIS_HAPP_SYMBOL_EXPORT and HIREDIS_HAPP_SYMBOL_IMPORT 278 | // If a compiler doesn't support __declspec(dllexport)/__declspec(dllimport), 279 | // its boost/config/compiler/ file must define HIREDIS_HAPP_SYMBOL_EXPORT and 280 | // HIREDIS_HAPP_SYMBOL_IMPORT 281 | #if !defined(HIREDIS_HAPP_SYMBOL_EXPORT) && \ 282 | (defined(_WIN32) || defined(__WIN32__) || defined(WIN32) || defined(__CYGWIN__)) 283 | 284 | # ifndef HIREDIS_HAPP_SYMBOL_EXPORT 285 | # define HIREDIS_HAPP_SYMBOL_EXPORT __declspec(dllexport) 286 | # endif 287 | # ifndef HIREDIS_HAPP_SYMBOL_IMPORT 288 | # define HIREDIS_HAPP_SYMBOL_IMPORT __declspec(dllimport) 289 | # endif 290 | #endif 291 | // ---------------- import/export: for platform ---------------- 292 | 293 | #ifndef HIREDIS_HAPP_SYMBOL_EXPORT 294 | # define HIREDIS_HAPP_SYMBOL_EXPORT 295 | #endif 296 | #ifndef HIREDIS_HAPP_SYMBOL_IMPORT 297 | # define HIREDIS_HAPP_SYMBOL_IMPORT 298 | #endif 299 | #ifndef HIREDIS_HAPP_SYMBOL_VISIBLE 300 | # define HIREDIS_HAPP_SYMBOL_VISIBLE 301 | #endif 302 | #ifndef HIREDIS_HAPP_SYMBOL_LOCAL 303 | # define HIREDIS_HAPP_SYMBOL_LOCAL 304 | #endif 305 | 306 | // ---------------- import/export ---------------- 307 | 308 | #if defined(HIREDIS_HAPP_API_NATIVE) && HIREDIS_HAPP_API_NATIVE 309 | # if defined(HIREDIS_HAPP_API_DLL) && HIREDIS_HAPP_API_DLL 310 | # define HIREDIS_HAPP_API HIREDIS_HAPP_SYMBOL_EXPORT 311 | # else 312 | # define HIREDIS_HAPP_API 313 | # endif 314 | #else 315 | # if defined(HIREDIS_HAPP_API_DLL) && HIREDIS_HAPP_API_DLL 316 | # define HIREDIS_HAPP_API HIREDIS_HAPP_SYMBOL_IMPORT 317 | # else 318 | # define HIREDIS_HAPP_API 319 | # endif 320 | #endif 321 | #define HIREDIS_HAPP_API_HEAD_ONLY HIREDIS_HAPP_SYMBOL_VISIBLE 322 | 323 | // error code 324 | namespace hiredis { 325 | namespace happ { 326 | struct error_code { 327 | enum type { 328 | REDIS_HAPP_OK = REDIS_OK, 329 | REDIS_HAPP_UNKNOWD = -1001, // unknown error 330 | REDIS_HAPP_HIREDIS = -1002, // error happened in hiredis 331 | REDIS_HAPP_TTL = -1003, // try more than ttl times 332 | REDIS_HAPP_CONNECTION = -1004, // connect lost or connect failed 333 | REDIS_HAPP_SLOT_UNAVAILABLE = -1005, // slot unavailable 334 | REDIS_HAPP_CREATE = -1006, // create failed 335 | REDIS_HAPP_PARAM = -1007, // param error 336 | REDIS_HAPP_TIMEOUT = -1008, // timeout 337 | REDIS_HAPP_NOT_FOUND = -1009, // not found 338 | }; 339 | }; 340 | } // namespace happ 341 | } // namespace hiredis 342 | 343 | #endif // HIREDIS_HAPP_HIREDIS_HAPP_CONFIG_H 344 | -------------------------------------------------------------------------------- /include/hiredis_happ.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by owent on 2015/7/5. 3 | // 4 | 5 | #ifndef HIREDIS_HAPP_HIREDIS_HAPP_H 6 | #define HIREDIS_HAPP_HIREDIS_HAPP_H 7 | 8 | #pragma once 9 | 10 | #include "detail/happ_cluster.h" 11 | #include "detail/happ_raw.h" 12 | 13 | #endif // HIREDIS_HAPP_HIREDIS_HAPP_H 14 | -------------------------------------------------------------------------------- /project/cmake/FetchDependeny.cmake: -------------------------------------------------------------------------------- 1 | include_guard(GLOBAL) 2 | 3 | # Patch for `FindGit.cmake` on windows 4 | find_program(GIT_EXECUTABLE NAMES git git.cmd) 5 | find_package(Git REQUIRED) 6 | 7 | set(ATFRAMEWORK_CMAKE_TOOLSET_DIR 8 | "${PROJECT_SOURCE_DIR}/atframework/cmake-toolset" 9 | CACHE PATH "PATH to cmake-toolset") 10 | 11 | if(NOT ATFRAMEWORK_CMAKE_TOOLSET_EXECUTE_PROCESS_OUTPUT_OPTIONS) 12 | unset(ATFRAMEWORK_CMAKE_TOOLSET_EXECUTE_PROCESS_OUTPUT_OPTIONS) 13 | if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.15") 14 | list(APPEND ATFRAMEWORK_CMAKE_TOOLSET_EXECUTE_PROCESS_OUTPUT_OPTIONS COMMAND_ECHO STDOUT) 15 | endif() 16 | if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.18") 17 | list(APPEND ATFRAMEWORK_CMAKE_TOOLSET_EXECUTE_PROCESS_OUTPUT_OPTIONS ECHO_OUTPUT_VARIABLE ECHO_ERROR_VARIABLE) 18 | endif() 19 | endif() 20 | 21 | if(NOT EXISTS "${ATFRAMEWORK_CMAKE_TOOLSET_DIR}/Import.cmake") 22 | execute_process( 23 | COMMAND ${GIT_EXECUTABLE} submodule update --depth 100 --recommend-shallow -f --init -- atframework/cmake-toolset 24 | WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" ${ATFRAMEWORK_CMAKE_TOOLSET_EXECUTE_PROCESS_OUTPUT_OPTIONS}) 25 | set(ATFRAMEWORK_CMAKE_TOOLSET_DIR 26 | "${PROJECT_SOURCE_DIR}/atframework/cmake-toolset" 27 | CACHE PATH "PATH to cmake-toolset" FORCE) 28 | endif() 29 | 30 | include("${ATFRAMEWORK_CMAKE_TOOLSET_DIR}/Import.cmake") 31 | -------------------------------------------------------------------------------- /project/cmake/ProjectBuildOption.cmake: -------------------------------------------------------------------------------- 1 | # 默认配置选项 2 | ##################################################################### 3 | option(BUILD_SHARED_LIBS "Build shared libraries (DLLs)." OFF) 4 | option(ENABLE_BOOST_UNIT_TEST "Enable boost unit test." OFF) 5 | 6 | # ============ fetch cmake toolset ============ 7 | include("${CMAKE_CURRENT_LIST_DIR}/FetchDependeny.cmake") 8 | include(IncludeDirectoryRecurse) 9 | include(EchoWithColor) 10 | -------------------------------------------------------------------------------- /sample/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # ============ sample - [...] ============ 2 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/sample") 3 | 4 | file(GLOB ALL_SAMPLE_DIRS "${PROJECT_SAMPLE_BAS_DIR}/*") 5 | foreach(SAMPLE_DIR ${ALL_SAMPLE_DIRS}) 6 | get_filename_component(SAMPLE_NAME ${SAMPLE_DIR} NAME) 7 | string(SUBSTRING ${SAMPLE_NAME} 0 7 SAMPLE_NAME_PREFIX) 8 | if(${SAMPLE_NAME_PREFIX} STREQUAL "sample_") 9 | message(STATUS "Sample Found: ${SAMPLE_NAME}") 10 | add_subdirectory(${SAMPLE_DIR}) 11 | endif() 12 | endforeach() 13 | -------------------------------------------------------------------------------- /sample/sample.happ-macro.cmake: -------------------------------------------------------------------------------- 1 | # =========== sample =========== 2 | set(PROJECT_SAMPLE_BAS_DIR ${CMAKE_CURRENT_LIST_DIR}) 3 | set(PROJECT_SAMPLE_INC_DIR ${PROJECT_SAMPLE_BAS_DIR}) 4 | set(PROJECT_SAMPLE_SRC_DIR ${PROJECT_SAMPLE_BAS_DIR}) 5 | -------------------------------------------------------------------------------- /sample/sample_cluster_cli/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package(Libevent) 2 | find_package(Libuv) 3 | 4 | if(Libuv_FOUND OR Libevent_FOUND) 5 | 6 | get_filename_component(SAMPLE_NAME ${CMAKE_CURRENT_LIST_DIR} NAME) 7 | set(SAMPLE_NAME "hiredis-happ-${SAMPLE_NAME}") 8 | unset(SAMPLE_EXT_LIBS) 9 | 10 | include_directories(${CMAKE_CURRENT_LIST_DIR}) 11 | 12 | aux_source_directory(${CMAKE_CURRENT_LIST_DIR} SAMPLE_SRC_FILES) 13 | 14 | if(TARGET uv_a) 15 | add_compiler_define(HIREDIS_HAPP_ENABLE_LIBUV=1) 16 | list(APPEND SAMPLE_EXT_LIBS uv_a) 17 | elseif(TARGET uv) 18 | add_compiler_define(HIREDIS_HAPP_ENABLE_LIBUV=1) 19 | list(APPEND SAMPLE_EXT_LIBS uv) 20 | elseif(TARGET libuv) 21 | add_compiler_define(HIREDIS_HAPP_ENABLE_LIBUV=1) 22 | list(APPEND SAMPLE_EXT_LIBS libuv) 23 | elseif(TARGET libuv::libuv) 24 | add_compiler_define(HIREDIS_HAPP_ENABLE_LIBUV=1) 25 | list(APPEND SAMPLE_EXT_LIBS libuv::libuv) 26 | elseif(Libuv_FOUND OR LIBUV_FOUND) 27 | add_compiler_define(HIREDIS_HAPP_ENABLE_LIBUV=1) 28 | if(Libuv_INCLUDE_DIRS) 29 | include_directories(${Libuv_INCLUDE_DIRS}) 30 | endif() 31 | if(Libuv_LIBRARIES) 32 | list(APPEND SAMPLE_EXT_LIBS ${Libuv_LIBRARIES}) 33 | endif() 34 | if(WIN32) 35 | list(APPEND SAMPLE_EXT_LIBS psapi iphlpapi userenv ws2_32) 36 | endif() 37 | elseif(Libevent_FOUND) 38 | add_compiler_define(HIREDIS_HAPP_ENABLE_LIBEVENT=1) 39 | set(SAMPLE_EXT_LIBS ${Libevent_LIBRARIES}) 40 | include_directories(${Libevent_INCLUDE_DIRS}) 41 | endif() 42 | 43 | if(NOT MSVC) 44 | set(SAMPLE_EXT_LIBS ${SAMPLE_EXT_LIBS} pthread) 45 | endif() 46 | 47 | add_executable(${SAMPLE_NAME} ${SAMPLE_SRC_FILES}) 48 | target_link_libraries(${SAMPLE_NAME} hiredis-happ ${SAMPLE_EXT_LIBS} ${COMPILER_OPTION_EXTERN_CXX_LIBS}) 49 | 50 | endif() 51 | -------------------------------------------------------------------------------- /sample/sample_cluster_cli/main.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 owent 2 | // Created by owent on 2015/7/5. 3 | // 4 | 5 | #if defined(_WIN32) 6 | # ifndef WIN32_LEAN_AND_MEAN 7 | # define WIN32_LEAN_AND_MEAN 8 | # endif 9 | # ifndef NOMINMAX 10 | # define NOMINMAX 11 | # endif 12 | #endif 13 | 14 | #ifdef _MSC_VER 15 | # include 16 | #else 17 | # include 18 | #endif 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #if defined(HIREDIS_HAPP_ENABLE_LIBUV) 33 | # include "hiredis/adapters/libuv.h" 34 | #elif defined(HIREDIS_HAPP_ENABLE_LIBEVENT) 35 | # include "hiredis/adapters/libevent.h" 36 | #endif 37 | 38 | #include "hiredis_happ.h" 39 | 40 | #ifdef __cplusplus 41 | extern "C" { 42 | #endif 43 | 44 | #if defined(_MSC_VER) 45 | # include 46 | # include 47 | # include 48 | # include 49 | # define THREAD_SPIN_COUNT 2000 50 | 51 | typedef HANDLE sample_thread_t; 52 | # define THREAD_FUNC unsigned __stdcall 53 | # define THREAD_CREATE(threadvar, fn, arg) \ 54 | do { \ 55 | uintptr_t threadhandle = _beginthreadex(NULL, 0, fn, (arg), 0, NULL); \ 56 | (threadvar) = (sample_thread_t)threadhandle; \ 57 | } while (0) 58 | # define THREAD_JOIN(th) WaitForSingleObject(th, INFINITE) 59 | # define THREAD_RETURN return (0) 60 | 61 | # define THREAD_SLEEP_MS(TM) Sleep(TM) 62 | #elif defined(__GNUC__) || defined(__clang__) 63 | # include 64 | # include 65 | # include 66 | 67 | typedef pthread_t sample_thread_t; 68 | # define THREAD_FUNC void * 69 | # define THREAD_CREATE(threadvar, fn, arg) pthread_create(&(threadvar), NULL, fn, arg) 70 | # define THREAD_JOIN(th) pthread_join(th, NULL) 71 | # define THREAD_RETURN \ 72 | pthread_exit(NULL); \ 73 | return NULL 74 | 75 | # define THREAD_SLEEP_MS(TM) \ 76 | ((TM > 1000) ? sleep(TM / 1000) : usleep(0)); \ 77 | usleep((TM % 1000) * 1000) 78 | #endif 79 | 80 | #ifdef __cplusplus 81 | } 82 | #endif 83 | 84 | static hiredis::happ::cluster g_clu; 85 | 86 | #if defined(HIREDIS_HAPP_ENABLE_LIBUV) 87 | static uv_loop_t *main_loop; 88 | #elif defined(HIREDIS_HAPP_ENABLE_LIBEVENT) 89 | static event_base *main_loop; 90 | #endif 91 | 92 | static std::mutex g_mutex; 93 | static std::list g_cmds; 94 | 95 | static void on_connect_cbk(hiredis::happ::cluster *, hiredis::happ::connection *conn) { 96 | #if defined(HIREDIS_HAPP_ENABLE_LIBUV) 97 | redisLibuvAttach(conn->get_context(), main_loop); 98 | #elif defined(HIREDIS_HAPP_ENABLE_LIBEVENT) 99 | redisLibeventAttach(conn->get_context(), main_loop); 100 | #endif 101 | 102 | if (NULL != conn) { 103 | printf("start connect to %s\n", conn->get_key().name.c_str()); 104 | } else { 105 | printf("error: connection not found when connect\n"); 106 | } 107 | } 108 | 109 | static void on_connected_cbk(hiredis::happ::cluster *, hiredis::happ::connection *conn, 110 | const struct redisAsyncContext *c, int status) { 111 | if (NULL == conn) { 112 | printf("error: connection not found when connected\n"); 113 | return; 114 | } 115 | 116 | if (REDIS_OK == status) { 117 | printf("%s connected success\n", conn->get_key().name.c_str()); 118 | } else { 119 | char no_msg[] = "none"; 120 | printf("%s connected failed, status %d, err: %d, msg %s\n", conn->get_key().name.c_str(), status, c ? c->err : 0, 121 | (c && c->errstr) ? c->errstr : no_msg); 122 | } 123 | } 124 | 125 | static void on_disconnected_cbk(hiredis::happ::cluster *, hiredis::happ::connection *conn, 126 | const struct redisAsyncContext *c, int status) { 127 | if (NULL == conn) { 128 | printf("error: connection not found when connected\n"); 129 | return; 130 | } 131 | 132 | if (REDIS_OK == status) { 133 | printf("%s disconnected\n", conn->get_key().name.c_str()); 134 | } else { 135 | char no_msg[] = "none"; 136 | printf("%s disconnected status %d, err: %d, msg %s\n", conn->get_key().name.c_str(), status, c ? c->err : 0, 137 | (c && c->errstr) ? c->errstr : no_msg); 138 | } 139 | } 140 | 141 | static std::vector split_word(const std::string &cmd_line) { 142 | std::vector ret; 143 | 144 | std::string seg; 145 | for (size_t i = 0; i < cmd_line.size(); ++i) { 146 | if (cmd_line[i] == '\r' || cmd_line[i] == '\n' || cmd_line[i] == '\t' || cmd_line[i] == ' ') { 147 | if (seg.empty()) { 148 | continue; 149 | } 150 | 151 | ret.push_back(seg); 152 | seg.clear(); 153 | } else { 154 | char c = cmd_line[i]; 155 | if ('\'' == c || '\"' == c) { 156 | for (++i; i < cmd_line.size() && c != cmd_line[i]; ++i) { 157 | if (c == '\"' && '\\' == cmd_line[i]) { 158 | ++i; 159 | if (i < cmd_line.size()) { 160 | seg.push_back(cmd_line[i]); 161 | } 162 | } else { 163 | seg.push_back(cmd_line[i]); 164 | } 165 | } 166 | 167 | ++i; 168 | } else { 169 | seg.push_back(c); 170 | } 171 | } 172 | } 173 | 174 | if (!seg.empty()) { 175 | ret.push_back(seg); 176 | } 177 | 178 | return ret; 179 | } 180 | 181 | static void dump_callback(hiredis::happ::cmd_exec *cmd, struct redisAsyncContext *, void *r, void *p) { 182 | assert(p == reinterpret_cast(dump_callback)); 183 | 184 | if (cmd->result() != hiredis::happ::error_code::REDIS_HAPP_OK) { 185 | printf("cmd_exec result: %d\n", cmd->result()); 186 | } 187 | hiredis::happ::cmd_exec::dump(std::cout, reinterpret_cast(r), 0); 188 | } 189 | 190 | static void subscribe_callback(struct redisAsyncContext *, void *r, void *p) { 191 | assert(p == reinterpret_cast(subscribe_callback)); 192 | 193 | printf(" ===== subscribe message received =====\n"); 194 | hiredis::happ::cmd_exec::dump(std::cout, reinterpret_cast(r), 0); 195 | } 196 | 197 | static void monitor_callback(struct redisAsyncContext *, void *r, void *p) { 198 | assert(p == reinterpret_cast(monitor_callback)); 199 | 200 | printf(" ----- monitor message received ----- \n"); 201 | hiredis::happ::cmd_exec::dump(std::cout, reinterpret_cast(r), 0); 202 | } 203 | 204 | static void on_timer_proc( 205 | #if defined(HIREDIS_HAPP_ENABLE_LIBUV) 206 | uv_timer_t *handle 207 | #elif defined(HIREDIS_HAPP_ENABLE_LIBEVENT) 208 | evutil_socket_t fd, short event, void *arg 209 | #endif 210 | ) { 211 | static time_t sec = time(NULL); 212 | static time_t usec = 0; 213 | 214 | usec += 100000; 215 | if (usec > 10000000) { 216 | sec += usec / 10000000; 217 | usec %= 10000000; 218 | } 219 | 220 | // get command for cli 221 | g_mutex.lock(); 222 | std::list pending_cmds; 223 | if (!g_cmds.empty()) { 224 | pending_cmds.swap(g_cmds); 225 | } 226 | g_mutex.unlock(); 227 | 228 | // run command 229 | while (!pending_cmds.empty()) { 230 | std::string cmd_line = pending_cmds.front(); 231 | pending_cmds.pop_front(); 232 | 233 | std::vector cmds = split_word(cmd_line); 234 | std::string cmd = cmds.empty() ? "" : cmds.front(); 235 | std::transform(cmd.begin(), cmd.end(), cmd.begin(), ::toupper); 236 | hiredis::happ::cmd_exec::callback_fn_t cbk = dump_callback; 237 | redisCallbackFn *raw_cbk; 238 | bool is_raw = false; 239 | int k = 1; 240 | if ("INFO" == cmd || "MULTI" == cmd || "EXEC" == cmd || "SLAVEOF" == cmd || "CONFIG" == cmd || "SHUTDOWN" == cmd || 241 | "CLUSTER" == cmd) { 242 | k = -1; 243 | } else if ("SCRIPT" == cmd) { 244 | k = -1; 245 | } else if ("EVALSHA" == cmd || "EVAL" == cmd) { 246 | k = 3; 247 | } else if ("SUBSCRIBE" == cmd || "UNSUBSCRIBE" == cmd || "PSUBSCRIBE" == cmd || "PUNSUBSCRIBE" == cmd) { 248 | raw_cbk = subscribe_callback; 249 | is_raw = true; 250 | } else if ("MONITOR" == cmd) { 251 | raw_cbk = monitor_callback; 252 | is_raw = true; 253 | } 254 | 255 | // get parameters 256 | std::vector pc; 257 | std::vector ps; 258 | for (size_t i = 0; i < cmds.size(); ++i) { 259 | pc.push_back(cmds[i].c_str()); 260 | ps.push_back(cmds[i].size()); 261 | } 262 | 263 | if (is_raw) { // run special command 264 | 265 | const hiredis::happ::cluster::slot_t *slot_info = NULL; 266 | if (cmds.size() > 1) { 267 | slot_info = g_clu.get_slot_by_key(cmds[1].c_str(), cmds[1].size()); 268 | assert(slot_info); 269 | } 270 | 271 | const hiredis::happ::connection::key_t *conn_key = 272 | g_clu.get_slot_master(NULL == slot_info ? -1 : slot_info->index); 273 | if (NULL == conn_key) { 274 | printf("connection not found.\n"); 275 | continue; 276 | } 277 | 278 | hiredis::happ::connection *conn = g_clu.get_connection(conn_key->name); 279 | if (NULL == conn) { 280 | conn = g_clu.make_connection(*conn_key); 281 | } 282 | 283 | if (NULL == conn) { 284 | printf("connect to %s failed.\n", conn_key->name.c_str()); 285 | continue; 286 | } 287 | conn->redis_raw_cmd(raw_cbk, reinterpret_cast(raw_cbk), static_cast(cmds.size()), &pc[0], &ps[0]); 288 | 289 | } else { // run Request-Response command 290 | if (k >= 0 && k < static_cast(cmds.size())) { 291 | cmd = cmds[k]; 292 | g_clu.exec(cmd.c_str(), cmd.size(), cbk, reinterpret_cast(cbk), static_cast(cmds.size()), &pc[0], 293 | &ps[0]); 294 | } else { 295 | g_clu.exec(NULL, 0, cbk, reinterpret_cast(cbk), static_cast(cmds.size()), &pc[0], &ps[0]); 296 | } 297 | } 298 | } 299 | 300 | g_clu.proc(sec, usec); 301 | } 302 | 303 | static THREAD_FUNC proc_uv_thd(void *) { 304 | #if defined(HIREDIS_HAPP_ENABLE_LIBUV) 305 | uv_run(main_loop, UV_RUN_DEFAULT); 306 | #elif defined(HIREDIS_HAPP_ENABLE_LIBEVENT) 307 | event_base_dispatch(main_loop); 308 | #endif 309 | 310 | THREAD_RETURN; 311 | } 312 | 313 | static void on_log_fn(const char *content) { puts(content); } 314 | 315 | int main(int argc, char *argv[]) { 316 | if (argc < 3) { 317 | printf("usage: %s [passowrd]\n", argv[0]); 318 | return 0; 319 | } 320 | 321 | #ifdef _MSC_VER 322 | { 323 | WORD wVersionRequested; 324 | WSADATA wsaData; 325 | 326 | wVersionRequested = MAKEWORD(2, 2); 327 | 328 | (void)WSAStartup(wVersionRequested, &wsaData); 329 | } 330 | #endif 331 | 332 | const char *ip = argv[1]; 333 | long lport = 0; 334 | lport = strtol(argv[2], NULL, 10); 335 | uint16_t port = static_cast(lport); 336 | 337 | g_clu.init(ip, port); 338 | 339 | if (argc > 3) { 340 | g_clu.set_auth_password(argv[3]); 341 | } 342 | 343 | #if defined(HIREDIS_HAPP_ENABLE_LIBUV) 344 | main_loop = uv_default_loop(); 345 | #elif defined(HIREDIS_HAPP_ENABLE_LIBEVENT) 346 | main_loop = event_init(); 347 | #endif 348 | 349 | // set callbacks 350 | g_clu.set_on_connect(on_connect_cbk); 351 | g_clu.set_on_connected(on_connected_cbk); 352 | g_clu.set_on_disconnected(on_disconnected_cbk); 353 | g_clu.set_timeout(5); // set timeout 354 | 355 | #if defined(HIREDIS_HAPP_ENABLE_LIBUV) 356 | // setup timer using libuv 357 | uv_timer_t timer_obj; 358 | uv_timer_init(main_loop, &timer_obj); 359 | uv_timer_start(&timer_obj, on_timer_proc, 1000, 100); 360 | 361 | #elif defined(HIREDIS_HAPP_ENABLE_LIBEVENT) 362 | // setup timer using libevent 363 | struct timeval tv; 364 | struct event ev; 365 | event_assign(&ev, main_loop, -1, EV_PERSIST, on_timer_proc, NULL); 366 | tv.tv_sec = 0; 367 | tv.tv_usec = 100000; 368 | evtimer_add(&ev, &tv); 369 | #endif 370 | 371 | // set log writer to write everything to console 372 | g_clu.set_log_writer(on_log_fn, on_log_fn, 65536); 373 | 374 | g_clu.start(); 375 | 376 | sample_thread_t uv_thd; 377 | THREAD_CREATE(uv_thd, proc_uv_thd, NULL); 378 | 379 | std::string cmd; 380 | while (std::getline(std::cin, cmd)) { 381 | if (cmd.empty()) { 382 | continue; 383 | } 384 | 385 | // append command 386 | g_mutex.lock(); 387 | g_cmds.push_back(cmd); 388 | g_mutex.unlock(); 389 | 390 | cmd.clear(); 391 | } 392 | 393 | #if defined(HIREDIS_HAPP_ENABLE_LIBUV) 394 | uv_stop(main_loop); 395 | #elif defined(HIREDIS_HAPP_ENABLE_LIBEVENT) 396 | event_base_loopbreak(main_loop); 397 | #endif 398 | 399 | THREAD_JOIN(uv_thd); 400 | 401 | g_clu.reset(); 402 | 403 | return 0; 404 | } 405 | -------------------------------------------------------------------------------- /sample/sample_raw_cli/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package(Libevent) 2 | find_package(Libuv) 3 | 4 | if(Libuv_FOUND OR Libevent_FOUND) 5 | 6 | get_filename_component(SAMPLE_NAME ${CMAKE_CURRENT_LIST_DIR} NAME) 7 | set(SAMPLE_NAME "hiredis-happ-${SAMPLE_NAME}") 8 | 9 | include_directories(${CMAKE_CURRENT_LIST_DIR}) 10 | 11 | aux_source_directory(${CMAKE_CURRENT_LIST_DIR} SAMPLE_SRC_FILES) 12 | 13 | if(TARGET uv_a) 14 | add_compiler_define(HIREDIS_HAPP_ENABLE_LIBUV=1) 15 | list(APPEND SAMPLE_EXT_LIBS uv_a) 16 | elseif(TARGET uv) 17 | add_compiler_define(HIREDIS_HAPP_ENABLE_LIBUV=1) 18 | list(APPEND SAMPLE_EXT_LIBS uv) 19 | elseif(TARGET libuv) 20 | add_compiler_define(HIREDIS_HAPP_ENABLE_LIBUV=1) 21 | list(APPEND SAMPLE_EXT_LIBS libuv) 22 | elseif(TARGET libuv::libuv) 23 | add_compiler_define(HIREDIS_HAPP_ENABLE_LIBUV=1) 24 | list(APPEND SAMPLE_EXT_LIBS libuv::libuv) 25 | elseif(Libuv_FOUND OR LIBUV_FOUND) 26 | add_compiler_define(HIREDIS_HAPP_ENABLE_LIBUV=1) 27 | if(Libuv_INCLUDE_DIRS) 28 | include_directories(${Libuv_INCLUDE_DIRS}) 29 | endif() 30 | if(Libuv_LIBRARIES) 31 | list(APPEND SAMPLE_EXT_LIBS ${Libuv_LIBRARIES}) 32 | endif() 33 | if(WIN32) 34 | list(APPEND SAMPLE_EXT_LIBS psapi iphlpapi userenv ws2_32) 35 | endif() 36 | elseif(Libevent_FOUND) 37 | add_compiler_define(HIREDIS_HAPP_ENABLE_LIBEVENT=1) 38 | set(SAMPLE_EXT_LIBS ${Libevent_LIBRARIES}) 39 | include_directories(${Libevent_INCLUDE_DIRS}) 40 | endif() 41 | 42 | if(NOT MSVC) 43 | set(SAMPLE_EXT_LIBS ${SAMPLE_EXT_LIBS} pthread) 44 | endif() 45 | 46 | add_executable(${SAMPLE_NAME} ${SAMPLE_SRC_FILES}) 47 | target_link_libraries(${SAMPLE_NAME} hiredis-happ ${SAMPLE_EXT_LIBS} ${COMPILER_OPTION_EXTERN_CXX_LIBS}) 48 | 49 | endif() 50 | -------------------------------------------------------------------------------- /sample/sample_raw_cli/main.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 owent 2 | // Created by owent on 2015/7/5. 3 | // 4 | 5 | #if defined(_WIN32) 6 | # ifndef WIN32_LEAN_AND_MEAN 7 | # define WIN32_LEAN_AND_MEAN 8 | # endif 9 | # ifndef NOMINMAX 10 | # define NOMINMAX 11 | # endif 12 | #endif 13 | 14 | #ifdef _MSC_VER 15 | # include 16 | #else 17 | # include 18 | #endif 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #if defined(HIREDIS_HAPP_ENABLE_LIBUV) 33 | # include "hiredis/adapters/libuv.h" 34 | #elif defined(HIREDIS_HAPP_ENABLE_LIBEVENT) 35 | # include "hiredis/adapters/libevent.h" 36 | #endif 37 | 38 | #include "hiredis_happ.h" 39 | 40 | #ifdef __cplusplus 41 | extern "C" { 42 | #endif 43 | 44 | #if defined(_MSC_VER) 45 | # include 46 | # include 47 | # include 48 | # include 49 | # define THREAD_SPIN_COUNT 2000 50 | 51 | typedef HANDLE sample_thread_t; 52 | # define THREAD_FUNC unsigned __stdcall 53 | # define THREAD_CREATE(threadvar, fn, arg) \ 54 | do { \ 55 | uintptr_t threadhandle = _beginthreadex(NULL, 0, fn, (arg), 0, NULL); \ 56 | (threadvar) = (sample_thread_t)threadhandle; \ 57 | } while (0) 58 | # define THREAD_JOIN(th) WaitForSingleObject(th, INFINITE) 59 | # define THREAD_RETURN return (0) 60 | 61 | # define THREAD_SLEEP_MS(TM) Sleep(TM) 62 | #elif defined(__GNUC__) || defined(__clang__) 63 | # include 64 | # include 65 | # include 66 | 67 | typedef pthread_t sample_thread_t; 68 | # define THREAD_FUNC void * 69 | # define THREAD_CREATE(threadvar, fn, arg) pthread_create(&(threadvar), NULL, fn, arg) 70 | # define THREAD_JOIN(th) pthread_join(th, NULL) 71 | # define THREAD_RETURN \ 72 | pthread_exit(NULL); \ 73 | return NULL 74 | 75 | # define THREAD_SLEEP_MS(TM) \ 76 | ((TM > 1000) ? sleep(TM / 1000) : usleep(0)); \ 77 | usleep((TM % 1000) * 1000) 78 | #endif 79 | 80 | #ifdef __cplusplus 81 | } 82 | #endif 83 | 84 | static hiredis::happ::raw g_raw; 85 | 86 | #if defined(HIREDIS_HAPP_ENABLE_LIBUV) 87 | static uv_loop_t *main_loop; 88 | #elif defined(HIREDIS_HAPP_ENABLE_LIBEVENT) 89 | static event_base *main_loop; 90 | #endif 91 | 92 | static std::mutex g_mutex; 93 | static std::list g_cmds; 94 | 95 | static void on_connect_cbk(hiredis::happ::raw *, hiredis::happ::connection *conn) { 96 | #if defined(HIREDIS_HAPP_ENABLE_LIBUV) 97 | redisLibuvAttach(conn->get_context(), main_loop); 98 | #elif defined(HIREDIS_HAPP_ENABLE_LIBEVENT) 99 | redisLibeventAttach(conn->get_context(), main_loop); 100 | #endif 101 | 102 | if (NULL != conn) { 103 | printf("start connect to %s\n", conn->get_key().name.c_str()); 104 | } else { 105 | printf("error: connection not found when connect\n"); 106 | } 107 | } 108 | 109 | static void on_connected_cbk(hiredis::happ::raw *, hiredis::happ::connection *conn, const struct redisAsyncContext *c, 110 | int status) { 111 | if (NULL == conn) { 112 | printf("error: connection not found when connected\n"); 113 | return; 114 | } 115 | 116 | if (REDIS_OK == status) { 117 | printf("%s connected success\n", conn->get_key().name.c_str()); 118 | } else { 119 | char no_msg[] = "none"; 120 | printf("%s connected failed, status %d, err: %d, msg %s\n", conn->get_key().name.c_str(), status, c ? c->err : 0, 121 | (c && c->errstr) ? c->errstr : no_msg); 122 | } 123 | } 124 | 125 | static void on_disconnected_cbk(hiredis::happ::raw *, hiredis::happ::connection *conn, 126 | const struct redisAsyncContext *c, int status) { 127 | if (NULL == conn) { 128 | printf("error: connection not found when connected\n"); 129 | return; 130 | } 131 | 132 | if (REDIS_OK == status) { 133 | printf("%s disconnected\n", conn->get_key().name.c_str()); 134 | } else { 135 | char no_msg[] = "none"; 136 | printf("%s disconnected status %d, err: %d, msg %s\n", conn->get_key().name.c_str(), status, c ? c->err : 0, 137 | (c && c->errstr) ? c->errstr : no_msg); 138 | } 139 | } 140 | 141 | static std::vector split_word(const std::string &cmd_line) { 142 | std::vector ret; 143 | 144 | std::string seg; 145 | for (size_t i = 0; i < cmd_line.size(); ++i) { 146 | if (cmd_line[i] == '\r' || cmd_line[i] == '\n' || cmd_line[i] == '\t' || cmd_line[i] == ' ') { 147 | if (seg.empty()) { 148 | continue; 149 | } 150 | 151 | ret.push_back(seg); 152 | seg.clear(); 153 | } else { 154 | char c = cmd_line[i]; 155 | if ('\'' == c || '\"' == c) { 156 | for (++i; i < cmd_line.size() && c != cmd_line[i]; ++i) { 157 | if (c == '\"' && '\\' == cmd_line[i]) { 158 | ++i; 159 | if (i < cmd_line.size()) { 160 | seg.push_back(cmd_line[i]); 161 | } 162 | } else { 163 | seg.push_back(cmd_line[i]); 164 | } 165 | } 166 | 167 | ++i; 168 | } else { 169 | seg.push_back(c); 170 | } 171 | } 172 | } 173 | 174 | if (!seg.empty()) { 175 | ret.push_back(seg); 176 | } 177 | 178 | return ret; 179 | } 180 | 181 | static void dump_callback(hiredis::happ::cmd_exec *cmd, struct redisAsyncContext *, void *r, void *p) { 182 | assert(p == reinterpret_cast(dump_callback)); 183 | 184 | if (cmd->result() != hiredis::happ::error_code::REDIS_HAPP_OK) { 185 | printf("cmd_exec result: %d\n", cmd->result()); 186 | } 187 | hiredis::happ::cmd_exec::dump(std::cout, reinterpret_cast(r), 0); 188 | } 189 | 190 | static void subscribe_callback(struct redisAsyncContext *, void *r, void *p) { 191 | assert(p == reinterpret_cast(subscribe_callback)); 192 | 193 | printf(" ===== subscribe message received =====\n"); 194 | hiredis::happ::cmd_exec::dump(std::cout, reinterpret_cast(r), 0); 195 | } 196 | 197 | static void monitor_callback(struct redisAsyncContext *, void *r, void *p) { 198 | assert(p == reinterpret_cast(monitor_callback)); 199 | 200 | printf(" ----- monitor message received ----- \n"); 201 | hiredis::happ::cmd_exec::dump(std::cout, reinterpret_cast(r), 0); 202 | } 203 | 204 | static void on_timer_proc( 205 | #if defined(HIREDIS_HAPP_ENABLE_LIBUV) 206 | uv_timer_t *handle 207 | #elif defined(HIREDIS_HAPP_ENABLE_LIBEVENT) 208 | evutil_socket_t fd, short event, void *arg 209 | #endif 210 | ) { 211 | static time_t sec = time(NULL); 212 | static time_t usec = 0; 213 | 214 | usec += 100000; 215 | if (usec > 10000000) { 216 | sec += usec / 10000000; 217 | usec %= 10000000; 218 | } 219 | 220 | // get command for cli 221 | g_mutex.lock(); 222 | std::list pending_cmds; 223 | if (!g_cmds.empty()) { 224 | pending_cmds.swap(g_cmds); 225 | } 226 | g_mutex.unlock(); 227 | 228 | // run command 229 | while (!pending_cmds.empty()) { 230 | std::string cmd_line = pending_cmds.front(); 231 | pending_cmds.pop_front(); 232 | 233 | std::vector cmds = split_word(cmd_line); 234 | std::string cmd = cmds.empty() ? "" : cmds.front(); 235 | std::transform(cmd.begin(), cmd.end(), cmd.begin(), ::toupper); 236 | hiredis::happ::cmd_exec::callback_fn_t cbk = dump_callback; 237 | redisCallbackFn *raw_cbk; 238 | bool is_raw = false; 239 | if ("SUBSCRIBE" == cmd || "UNSUBSCRIBE" == cmd || "PSUBSCRIBE" == cmd || "PUNSUBSCRIBE" == cmd) { 240 | raw_cbk = subscribe_callback; 241 | is_raw = true; 242 | } else if ("MONITOR" == cmd) { 243 | raw_cbk = monitor_callback; 244 | is_raw = true; 245 | } 246 | 247 | // get parameters 248 | std::vector pc; 249 | std::vector ps; 250 | for (size_t i = 0; i < cmds.size(); ++i) { 251 | pc.push_back(cmds[i].c_str()); 252 | ps.push_back(cmds[i].size()); 253 | } 254 | 255 | if (is_raw) { // run special command 256 | hiredis::happ::connection *conn = g_raw.get_connection(); 257 | if (NULL == conn) { 258 | conn = g_raw.make_connection(); 259 | } 260 | 261 | if (NULL == conn) { 262 | printf("connect to redis failed.\n"); 263 | continue; 264 | } 265 | conn->redis_raw_cmd(raw_cbk, reinterpret_cast(raw_cbk), static_cast(cmds.size()), &pc[0], &ps[0]); 266 | 267 | } else { // run Request-Response command 268 | g_raw.exec(cbk, reinterpret_cast(cbk), static_cast(cmds.size()), &pc[0], &ps[0]); 269 | } 270 | } 271 | 272 | g_raw.proc(sec, usec); 273 | } 274 | 275 | static THREAD_FUNC proc_uv_thd(void *) { 276 | #if defined(HIREDIS_HAPP_ENABLE_LIBUV) 277 | uv_run(main_loop, UV_RUN_DEFAULT); 278 | #elif defined(HIREDIS_HAPP_ENABLE_LIBEVENT) 279 | event_base_dispatch(main_loop); 280 | #endif 281 | 282 | THREAD_RETURN; 283 | } 284 | 285 | static void on_log_fn(const char *content) { puts(content); } 286 | 287 | int main(int argc, char *argv[]) { 288 | if (argc < 3) { 289 | printf("usage: %s [password]\n", argv[0]); 290 | return 0; 291 | } 292 | 293 | #ifdef _MSC_VER 294 | { 295 | WORD wVersionRequested; 296 | WSADATA wsaData; 297 | 298 | wVersionRequested = MAKEWORD(2, 2); 299 | 300 | (void)WSAStartup(wVersionRequested, &wsaData); 301 | } 302 | #endif 303 | 304 | const char *ip = argv[1]; 305 | long lport = 0; 306 | lport = strtol(argv[2], NULL, 10); 307 | uint16_t port = static_cast(lport); 308 | 309 | g_raw.init(ip, port); 310 | 311 | if (argc > 3) { 312 | g_raw.set_auth_password(argv[3]); 313 | } 314 | 315 | #if defined(HIREDIS_HAPP_ENABLE_LIBUV) 316 | main_loop = uv_default_loop(); 317 | #elif defined(HIREDIS_HAPP_ENABLE_LIBEVENT) 318 | main_loop = event_init(); 319 | #endif 320 | 321 | // set callbacks 322 | g_raw.set_on_connect(on_connect_cbk); 323 | g_raw.set_on_connected(on_connected_cbk); 324 | g_raw.set_on_disconnected(on_disconnected_cbk); 325 | g_raw.set_timeout(5); // set timeout 326 | 327 | #if defined(HIREDIS_HAPP_ENABLE_LIBUV) 328 | // setup timer using libuv 329 | uv_timer_t timer_obj; 330 | uv_timer_init(main_loop, &timer_obj); 331 | uv_timer_start(&timer_obj, on_timer_proc, 1000, 100); 332 | 333 | #elif defined(HIREDIS_HAPP_ENABLE_LIBEVENT) 334 | // setup timer using libevent 335 | struct timeval tv; 336 | struct event ev; 337 | event_assign(&ev, main_loop, -1, EV_PERSIST, on_timer_proc, NULL); 338 | tv.tv_sec = 0; 339 | tv.tv_usec = 100000; 340 | evtimer_add(&ev, &tv); 341 | #endif 342 | 343 | // set log writer to write everything to console 344 | g_raw.set_log_writer(on_log_fn, on_log_fn, 65536); 345 | 346 | g_raw.start(); 347 | 348 | sample_thread_t uv_thd; 349 | THREAD_CREATE(uv_thd, proc_uv_thd, NULL); 350 | 351 | std::string cmd; 352 | while (std::getline(std::cin, cmd)) { 353 | if (cmd.empty()) { 354 | continue; 355 | } 356 | 357 | // append command 358 | g_mutex.lock(); 359 | g_cmds.push_back(cmd); 360 | g_mutex.unlock(); 361 | 362 | cmd.clear(); 363 | } 364 | 365 | #if defined(HIREDIS_HAPP_ENABLE_LIBUV) 366 | uv_stop(main_loop); 367 | #elif defined(HIREDIS_HAPP_ENABLE_LIBEVENT) 368 | event_base_loopbreak(main_loop); 369 | #endif 370 | 371 | THREAD_JOIN(uv_thd); 372 | 373 | g_raw.reset(); 374 | 375 | return 0; 376 | } 377 | -------------------------------------------------------------------------------- /src/detail/crc16.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | static const uint16_t crc16tab[256] = { 5 | 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 6 | 0xe1ce, 0xf1ef, 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, 0x9339, 0x8318, 0xb37b, 0xa35a, 7 | 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, 0xa56a, 0xb54b, 8 | 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, 9 | 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 10 | 0x2802, 0x3823, 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 11 | 0x1a71, 0x0a50, 0x3a33, 0x2a12, 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, 0x6ca6, 0x7c87, 12 | 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, 13 | 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 14 | 0x9f59, 0x8f78, 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, 0x1080, 0x00a1, 0x30c2, 0x20e3, 15 | 0x5004, 0x4025, 0x7046, 0x6067, 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, 0x02b1, 0x1290, 16 | 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, 17 | 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 18 | 0xc71d, 0xd73c, 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, 0xd94c, 0xc96d, 0xf90e, 0xe92f, 19 | 0x99c8, 0x89e9, 0xb98a, 0xa9ab, 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, 0xcb7d, 0xdb5c, 20 | 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, 21 | 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 22 | 0x1ce0, 0x0cc1, 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 0x6e17, 0x7e36, 0x4e55, 0x5e74, 23 | 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0}; 24 | 25 | namespace hiredis { 26 | namespace happ { 27 | uint16_t crc16(const char *buf, size_t len) { 28 | size_t counter; 29 | uint16_t crc = 0; 30 | for (counter = 0; counter < len; counter++) crc = (crc << 8) ^ crc16tab[((crc >> 8) ^ *buf++) & 0x00FF]; 31 | return crc; 32 | } 33 | } // namespace happ 34 | } // namespace hiredis 35 | -------------------------------------------------------------------------------- /src/detail/happ_cluster.cpp: -------------------------------------------------------------------------------- 1 | #if defined(_WIN32) 2 | # ifndef WIN32_LEAN_AND_MEAN 3 | # define WIN32_LEAN_AND_MEAN 4 | # endif 5 | # ifndef NOMINMAX 6 | # define NOMINMAX 7 | # endif 8 | #endif 9 | 10 | #ifdef _MSC_VER 11 | # include 12 | #else 13 | # include 14 | #endif 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include "detail/crc16.h" 25 | #include "detail/happ_cluster.h" 26 | 27 | namespace hiredis { 28 | namespace happ { 29 | namespace detail { 30 | static int random() { 31 | #if defined(__cplusplus) && __cplusplus >= 201103L 32 | static std::mt19937 g; 33 | return static_cast(g()); 34 | #else 35 | static bool inited = false; 36 | if (!inited) { 37 | inited = true; 38 | srand(static_cast(time(NULL))); 39 | } 40 | 41 | return rand(); 42 | #endif 43 | } 44 | 45 | static char NONE_MSG[] = "none"; 46 | } // namespace detail 47 | 48 | HIREDIS_HAPP_API cluster::cluster() : slot_flag_(slot_status::INVALID) { 49 | conf_.log_fn_debug = conf_.log_fn_info = NULL; 50 | conf_.log_buffer = NULL; 51 | conf_.log_max_size = 0; 52 | conf_.timer_interval_sec = HIREDIS_HAPP_TIMER_INTERVAL_SEC; 53 | conf_.timer_interval_usec = HIREDIS_HAPP_TIMER_INTERVAL_USEC; 54 | conf_.timer_timeout_sec = HIREDIS_HAPP_TIMER_TIMEOUT_SEC; 55 | conf_.cmd_buffer_size = 0; 56 | 57 | for (int i = 0; i < HIREDIS_HAPP_SLOT_NUMBER; ++i) { 58 | slots_[i].index = i; 59 | } 60 | 61 | callbacks_.on_connect = NULL; 62 | callbacks_.on_connected = NULL; 63 | callbacks_.on_disconnected = NULL; 64 | 65 | timer_actions_.last_update_sec = 0; 66 | timer_actions_.last_update_usec = 0; 67 | } 68 | 69 | HIREDIS_HAPP_API cluster::~cluster() { 70 | reset(); 71 | 72 | // log buffer 73 | if (NULL != conf_.log_buffer) { 74 | free(conf_.log_buffer); 75 | conf_.log_buffer = NULL; 76 | } 77 | } 78 | 79 | HIREDIS_HAPP_API int cluster::init(const std::string &ip, uint16_t port) { 80 | connection::set_key(conf_.init_connection, ip, port); 81 | 82 | return error_code::REDIS_HAPP_OK; 83 | } 84 | 85 | HIREDIS_HAPP_API const std::string &cluster::get_auth_password() { return auth_.password; } 86 | 87 | HIREDIS_HAPP_API void cluster::set_auth_password(const std::string &passwd) { auth_.password = passwd; } 88 | 89 | HIREDIS_HAPP_API const connection::auth_fn_t &cluster::get_auth_fn() { return auth_.auth_fn; } 90 | 91 | HIREDIS_HAPP_API void cluster::set_auth_fn(connection::auth_fn_t fn) { auth_.auth_fn = fn; } 92 | 93 | HIREDIS_HAPP_API int cluster::start() { 94 | reload_slots(); 95 | return error_code::REDIS_HAPP_OK; 96 | } 97 | 98 | HIREDIS_HAPP_API int cluster::reset() { 99 | std::vector all_contexts; 100 | all_contexts.reserve(connections_.size()); 101 | 102 | // Store connections_ first, the iterator may be invalid when connections_ changed 103 | { 104 | connection_map_t::const_iterator it_b = connections_.begin(); 105 | connection_map_t::const_iterator it_e = connections_.end(); 106 | for (; it_b != it_e; ++it_b) { 107 | if (NULL != it_b->second->get_context()) { 108 | all_contexts.push_back(it_b->second->get_context()); 109 | } 110 | } 111 | } 112 | 113 | // disable slot update 114 | slot_flag_ = slot_status::UPDATING; 115 | 116 | // disconnect all connections_. 117 | // the connected/disconnected callback_ will be triggered if not in callback_ 118 | for (size_t i = 0; i < all_contexts.size(); ++i) { 119 | redisAsyncDisconnect(all_contexts[i]); 120 | } 121 | 122 | // release slot pending list 123 | while (!slot_pending_.empty()) { 124 | cmd_t *cmd = slot_pending_.front(); 125 | slot_pending_.pop_front(); 126 | 127 | call_cmd(cmd, error_code::REDIS_HAPP_SLOT_UNAVAILABLE, NULL, NULL); 128 | destroy_cmd(cmd); 129 | } 130 | 131 | for (int i = 0; i < HIREDIS_HAPP_SLOT_NUMBER; ++i) { 132 | slots_[i].hosts.clear(); 133 | } 134 | 135 | // release timer pending list 136 | while (!timer_actions_.timer_pending.empty()) { 137 | cmd_t *cmd = timer_actions_.timer_pending.front().cmd; 138 | timer_actions_.timer_pending.pop_front(); 139 | 140 | call_cmd(cmd, error_code::REDIS_HAPP_TIMEOUT, NULL, NULL); 141 | destroy_cmd(cmd); 142 | } 143 | 144 | // connection timeout 145 | // while(!timer_actions_.timer_conns.empty()) { 146 | // timer_t::conn_timetout_t& conn_expire = timer_actions_.timer_conns.front(); 147 | 148 | // connection_t* conn = get_connection(conn_expire.name); 149 | // if (NULL != conn && conn->get_sequence() == conn_expire.sequence) { 150 | // // if connection is in callback_ mode, the cmds in it will not finish 151 | // // so the connection can not be released right now 152 | // // this will be released after callback_ in disconnect event 153 | // if (!(conn->get_context()->c.flags & REDIS_IN_CALLBACK)) { 154 | // release_connection(conn->get_key(), true, error_code::REDIS_HAPP_TIMEOUT); 155 | // } 156 | // } 157 | 158 | // timer_actions_.timer_conns.pop_front(); 159 | //} 160 | 161 | // all connections_ are marked disconnection or disconnected, so timeout timers are useless 162 | timer_actions_.timer_conns.clear(); 163 | timer_actions_.last_update_sec = 0; 164 | timer_actions_.last_update_usec = 0; 165 | 166 | // If in a callback_, cmds in this connection will not finished, so it can not be freed. 167 | // In this case, it will call disconnect callback_ after callback_ is finished and then release 168 | // the connection. If not in a callback_, this connection is already freed at the begining 169 | // "redisAsyncDisconnect(all_contexts[i]);" connections_.clear(); // can not clear connections_ 170 | // here 171 | 172 | // reset slot status 173 | slot_flag_ = slot_status::INVALID; 174 | 175 | return 0; 176 | } 177 | 178 | HIREDIS_HAPP_API cluster::cmd_t *cluster::exec(const char *key, size_t ks, cmd_t::callback_fn_t cbk, void *priv_data, 179 | int argc, const char **argv, const size_t *argvlen) { 180 | cmd_t *cmd = create_cmd(cbk, priv_data); 181 | if (NULL == cmd) { 182 | return NULL; 183 | } 184 | 185 | int64_t len = cmd->vformat(argc, argv, argvlen); 186 | if (len <= 0) { 187 | log_info("format cmd with argc=%d failed", argc); 188 | destroy_cmd(cmd); 189 | return NULL; 190 | } 191 | 192 | return exec(key, ks, cmd); 193 | } 194 | 195 | HIREDIS_HAPP_API cluster::cmd_t *cluster::exec(const char *key, size_t ks, cmd_t::callback_fn_t cbk, void *priv_data, 196 | const char *fmt, ...) { 197 | cmd_t *cmd = create_cmd(cbk, priv_data); 198 | if (NULL == cmd) { 199 | return NULL; 200 | } 201 | 202 | va_list ap; 203 | va_start(ap, fmt); 204 | int len = cmd->vformat(fmt, ap); 205 | va_end(ap); 206 | if (len <= 0) { 207 | log_info("format cmd with format=%s failed", fmt); 208 | destroy_cmd(cmd); 209 | return NULL; 210 | } 211 | 212 | return exec(key, ks, cmd); 213 | } 214 | 215 | HIREDIS_HAPP_API cluster::cmd_t *cluster::exec(const char *key, size_t ks, cmd_t::callback_fn_t cbk, void *priv_data, 216 | const char *fmt, va_list ap) { 217 | cmd_t *cmd = create_cmd(cbk, priv_data); 218 | if (NULL == cmd) { 219 | return NULL; 220 | } 221 | 222 | int len = cmd->vformat(fmt, ap); 223 | if (len <= 0) { 224 | log_info("format cmd with format=%s failed", fmt); 225 | destroy_cmd(cmd); 226 | return NULL; 227 | } 228 | 229 | return exec(key, ks, cmd); 230 | } 231 | 232 | HIREDIS_HAPP_API cluster::cmd_t *cluster::exec(const char *key, size_t ks, cmd_t *cmd) { 233 | if (NULL == cmd) { 234 | return NULL; 235 | } 236 | 237 | // calculate the slot index 238 | if (NULL != key && 0 != ks) { 239 | cmd->engine_.slot = static_cast(crc16(key, ks) % HIREDIS_HAPP_SLOT_NUMBER); 240 | } 241 | 242 | // ttl_ pre-judge 243 | if (0 == cmd->ttl_) { 244 | log_debug("cmd %p at slot %d ttl_ expired", cmd, cmd->engine_.slot); 245 | call_cmd(cmd, error_code::REDIS_HAPP_TTL, NULL, NULL); 246 | destroy_cmd(cmd); 247 | return NULL; 248 | } 249 | 250 | // update slot 251 | if (slot_status::INVALID == slot_flag_ || slot_status::UPDATING == slot_flag_) { 252 | log_debug("transfer cmd at slot %d to slot update pending list", cmd->engine_.slot); 253 | slot_pending_.push_back(cmd); 254 | 255 | reload_slots(); 256 | return cmd; 257 | } 258 | 259 | // get a connection in the specified slot 260 | const connection::key_t *conn_key = get_slot_master(cmd->engine_.slot); 261 | 262 | if (NULL == conn_key) { 263 | log_info("get connect of slot %d failed", cmd->engine_.slot); 264 | call_cmd(cmd, error_code::REDIS_HAPP_CONNECTION, NULL, NULL); 265 | destroy_cmd(cmd); 266 | return NULL; 267 | } 268 | 269 | // move cmd into connection 270 | connection_t *conn_inst = get_connection(conn_key->name); 271 | if (NULL == conn_inst) { 272 | conn_inst = make_connection(*conn_key); 273 | } 274 | 275 | if (NULL == conn_inst) { 276 | log_info("connect to %s failed", conn_key->name.c_str()); 277 | 278 | call_cmd(cmd, error_code::REDIS_HAPP_CONNECTION, NULL, NULL); 279 | destroy_cmd(cmd); 280 | 281 | return NULL; 282 | } 283 | 284 | return exec(conn_inst, cmd); 285 | } 286 | 287 | HIREDIS_HAPP_API cluster::cmd_t *cluster::exec(connection_t *conn, cmd_t *cmd) { 288 | if (NULL == cmd) { 289 | return NULL; 290 | } 291 | 292 | // ttl_ 293 | if (0 == cmd->ttl_) { 294 | log_debug("cmd %p at slot %d ttl_ expired", cmd, cmd->engine_.slot); 295 | call_cmd(cmd, error_code::REDIS_HAPP_TTL, NULL, NULL); 296 | destroy_cmd(cmd); 297 | return NULL; 298 | } 299 | 300 | // ttl_ 301 | --cmd->ttl_; 302 | 303 | if (NULL == conn) { 304 | call_cmd(cmd, error_code::REDIS_HAPP_CONNECTION, NULL, NULL); 305 | destroy_cmd(cmd); 306 | return NULL; 307 | } 308 | 309 | // main loop 310 | int res = conn->redis_cmd(cmd, on_reply_wrapper); 311 | 312 | if (REDIS_OK != res) { 313 | // some version of hiredis will miss onDisconnect, patch it 314 | // other situation should trigger error 315 | if (conn->get_context()->c.flags & (REDIS_DISCONNECTING | REDIS_FREEING)) { 316 | // remove the invalid connection, so this connection will not be selected next time. 317 | remove_connection_key(conn->get_key().name); 318 | 319 | // Patch: hiredis will miss onDisconnect in some older version 320 | // If not in hiredis's callback_, REDIS_DISCONNECTING or REDIS_FREEING means resource is freed 321 | // If in hiredis's callback_, disconnect will be called after callback_ finished, so do 322 | // nothing here 323 | if (!(conn->get_context()->c.flags & REDIS_IN_CALLBACK)) { 324 | release_connection(conn->get_key(), false, error_code::REDIS_HAPP_CONNECTION); 325 | } 326 | 327 | // conn = NULL; 328 | // retry and reload slot information if the connection lost 329 | cmd->engine_.slot = -1; 330 | return retry(cmd, NULL); 331 | } else { 332 | call_cmd(cmd, error_code::REDIS_HAPP_HIREDIS, conn->get_context(), NULL); 333 | destroy_cmd(cmd); 334 | } 335 | return NULL; 336 | } 337 | 338 | log_debug("exec cmd %p at slot %d, connection %s", cmd, cmd->engine_.slot, conn->get_key().name.c_str()); 339 | return cmd; 340 | } 341 | 342 | HIREDIS_HAPP_API cluster::cmd_t *cluster::retry(cmd_t *cmd, connection_t *conn) { 343 | if (NULL == cmd) { 344 | return NULL; 345 | } 346 | 347 | // First, retry immediately for several times. 348 | if (false == is_timer_active() || cmd->ttl_ > HIREDIS_HAPP_TTL / 2) { 349 | if (NULL == conn) { 350 | return exec(NULL, 0, cmd); 351 | } else { 352 | return exec(conn, cmd); 353 | } 354 | } 355 | 356 | // If it's still failed, maybe it will take some more time to recover the connection, 357 | // so wait for a while and retry again. 358 | add_timer_cmd(cmd); 359 | return cmd; 360 | } 361 | 362 | HIREDIS_HAPP_API bool cluster::reload_slots() { 363 | if (slot_status::UPDATING == slot_flag_) { 364 | return false; 365 | } 366 | 367 | const connection::key_t *conn_key = get_slot_master(-1); 368 | if (NULL == conn_key) { 369 | return false; 370 | } 371 | 372 | connection_t *conn = get_connection(conn_key->name); 373 | if (NULL == conn) { 374 | conn = make_connection(*conn_key); 375 | } 376 | 377 | if (NULL == conn) { 378 | return false; 379 | } 380 | 381 | // CLUSTER SLOTS cmd 382 | cmd_t *cmd = create_cmd(on_reply_update_slot, NULL); 383 | if (NULL == cmd) { 384 | log_info("create cmd CLUSTER SLOTS failed"); 385 | return false; 386 | } 387 | 388 | int len = cmd->format("CLUSTER SLOTS"); 389 | if (len <= 0) { 390 | log_info("format cmd CLUSTER SLOTS failed"); 391 | destroy_cmd(cmd); 392 | return false; 393 | } 394 | 395 | if (NULL != exec(conn, cmd)) { 396 | slot_flag_ = slot_status::UPDATING; 397 | } 398 | 399 | return true; 400 | } 401 | 402 | HIREDIS_HAPP_API const connection::key_t *cluster::get_slot_master(int index) { 403 | if (index >= 0 && index < HIREDIS_HAPP_SLOT_NUMBER && !slots_[index].hosts.empty()) { 404 | return &slots_[index].hosts.front(); 405 | } 406 | 407 | // random a address 408 | index = (detail::random() & 0xFFFF) % HIREDIS_HAPP_SLOT_NUMBER; 409 | if (slots_[index].hosts.empty()) { 410 | return &conf_.init_connection; 411 | } 412 | 413 | return &slots_[index].hosts.front(); 414 | } 415 | 416 | HIREDIS_HAPP_API const cluster::slot_t *cluster::get_slot_by_key(const char *key, size_t ks) const { 417 | int index = static_cast(crc16(key, ks) % HIREDIS_HAPP_SLOT_NUMBER); 418 | return &slots_[index]; 419 | } 420 | 421 | HIREDIS_HAPP_API const cluster::connection_t *cluster::get_connection(const std::string &key) const { 422 | connection_map_t::const_iterator it = connections_.find(key); 423 | if (it == connections_.end()) { 424 | return NULL; 425 | } 426 | 427 | return it->second.get(); 428 | } 429 | 430 | HIREDIS_HAPP_API cluster::connection_t *cluster::get_connection(const std::string &key) { 431 | connection_map_t::iterator it = connections_.find(key); 432 | if (it == connections_.end()) { 433 | return NULL; 434 | } 435 | 436 | return it->second.get(); 437 | } 438 | 439 | HIREDIS_HAPP_API const cluster::connection_t *cluster::get_connection(const std::string &ip, uint16_t port) const { 440 | return get_connection(connection::make_name(ip, port)); 441 | } 442 | 443 | HIREDIS_HAPP_API cluster::connection_t *cluster::get_connection(const std::string &ip, uint16_t port) { 444 | return get_connection(connection::make_name(ip, port)); 445 | } 446 | 447 | HIREDIS_HAPP_API cluster::connection_t *cluster::make_connection(const connection::key_t &key) { 448 | holder_t h; 449 | connection_map_t::iterator check_it = connections_.find(key.name); 450 | if (check_it != connections_.end()) { 451 | log_debug("connection %s already exists", key.name.c_str()); 452 | return NULL; 453 | } 454 | 455 | redisAsyncContext *c = redisAsyncConnect(key.ip.c_str(), static_cast(key.port)); 456 | if (NULL == c || c->err) { 457 | log_info("redis connect to %s failed, msg: %s", key.name.c_str(), NULL == c ? detail::NONE_MSG : c->errstr); 458 | return NULL; 459 | } 460 | 461 | h.clu = this; 462 | redisAsyncSetConnectCallback(c, on_connected_wrapper); 463 | redisAsyncSetDisconnectCallback(c, on_disconnected_wrapper); 464 | redisEnableKeepAlive(&c->c); 465 | if (conf_.timer_timeout_sec > 0) { 466 | struct timeval tv; 467 | tv.tv_sec = static_cast(conf_.timer_timeout_sec); 468 | tv.tv_usec = 0; 469 | redisSetTimeout(&c->c, tv); 470 | } 471 | 472 | std::unique_ptr ret_ptr(new connection_t()); 473 | connection_t &ret = *ret_ptr; 474 | swap(connections_[key.name], ret_ptr); 475 | ret.init(h, key); 476 | ret.set_connecting(c); 477 | 478 | c->data = &ret; 479 | 480 | // timeout timer 481 | if (conf_.timer_timeout_sec > 0 && is_timer_active()) { 482 | timer_actions_.timer_conns.push_back(timer_t::conn_timetout_t()); 483 | timer_t::conn_timetout_t &conn_expire = timer_actions_.timer_conns.back(); 484 | conn_expire.name = key.name; 485 | conn_expire.sequence = ret.get_sequence(); 486 | conn_expire.timeout = timer_actions_.last_update_sec + conf_.timer_timeout_sec; 487 | } 488 | 489 | // auth_ command 490 | if (auth_.auth_fn || !auth_.password.empty()) { 491 | // AUTH cmd 492 | cmd_t *cmd = create_cmd(on_reply_auth, NULL); 493 | if (NULL != cmd) { 494 | int len = 0; 495 | if (auth_.auth_fn) { 496 | const std::string &passwd = auth_.auth_fn(&ret, auth_.password); 497 | len = cmd->format("AUTH %b", passwd.c_str(), passwd.size()); 498 | } else if (!auth_.password.empty()) { 499 | len = cmd->format("AUTH %b", auth_.password.c_str(), auth_.password.size()); 500 | } 501 | 502 | if (len <= 0) { 503 | log_info("format cmd AUTH failed"); 504 | destroy_cmd(cmd); 505 | return NULL; 506 | } 507 | 508 | exec(&ret, cmd); 509 | } 510 | } 511 | 512 | // event callback_ must be call at the last 513 | if (callbacks_.on_connect) { 514 | callbacks_.on_connect(this, &ret); 515 | } 516 | 517 | log_debug("redis make connection to %s ", key.name.c_str()); 518 | return &ret; 519 | } 520 | 521 | HIREDIS_HAPP_API bool cluster::release_connection(const connection::key_t &key, bool close_fd, int status) { 522 | connection_map_t::iterator it = connections_.find(key.name); 523 | if (connections_.end() == it) { 524 | log_debug("connection %s not found", key.name.c_str()); 525 | return false; 526 | } 527 | 528 | connection_t::status::type from_status = it->second->set_disconnected(close_fd); 529 | switch (from_status) { 530 | // recursion, exit 531 | case connection_t::status::DISCONNECTED: 532 | return true; 533 | 534 | // connecting, call on_connected event 535 | case connection_t::status::CONNECTING: 536 | if (callbacks_.on_connected) { 537 | callbacks_.on_connected(this, it->second.get(), it->second->get_context(), 538 | error_code::REDIS_HAPP_OK == status ? error_code::REDIS_HAPP_CONNECTION : status); 539 | } 540 | break; 541 | 542 | // connecting, call on_disconnected event 543 | case connection_t::status::CONNECTED: 544 | if (callbacks_.on_disconnected) { 545 | callbacks_.on_disconnected(this, it->second.get(), it->second->get_context(), status); 546 | } 547 | break; 548 | 549 | default: 550 | log_info("unknown connection status %d", static_cast(from_status)); 551 | break; 552 | } 553 | 554 | log_debug("release connection %s", key.name.c_str()); 555 | 556 | // can not use key any more 557 | connections_.erase(it); 558 | 559 | return true; 560 | } 561 | 562 | HIREDIS_HAPP_API size_t cluster::get_connection_size() const { return connections_.size(); } 563 | 564 | HIREDIS_HAPP_API cluster::onconnect_fn_t cluster::set_on_connect(onconnect_fn_t cbk) { 565 | using std::swap; 566 | swap(cbk, callbacks_.on_connect); 567 | return cbk; 568 | } 569 | 570 | HIREDIS_HAPP_API cluster::onconnected_fn_t cluster::set_on_connected(onconnected_fn_t cbk) { 571 | using std::swap; 572 | swap(cbk, callbacks_.on_connected); 573 | return cbk; 574 | } 575 | 576 | HIREDIS_HAPP_API cluster::ondisconnected_fn_t cluster::set_on_disconnected(ondisconnected_fn_t cbk) { 577 | using std::swap; 578 | swap(cbk, callbacks_.on_disconnected); 579 | return cbk; 580 | } 581 | 582 | HIREDIS_HAPP_API void cluster::set_cmd_buffer_size(size_t s) { conf_.cmd_buffer_size = s; } 583 | 584 | HIREDIS_HAPP_API size_t cluster::get_cmd_buffer_size() const { return conf_.cmd_buffer_size; } 585 | 586 | HIREDIS_HAPP_API bool cluster::is_timer_active() const { 587 | return (timer_actions_.last_update_sec != 0 || timer_actions_.last_update_usec != 0) && 588 | (conf_.timer_interval_sec > 0 || conf_.timer_interval_usec > 0); 589 | } 590 | 591 | HIREDIS_HAPP_API void cluster::set_timer_interval(time_t sec, time_t usec) { 592 | conf_.timer_interval_sec = sec; 593 | conf_.timer_interval_usec = usec; 594 | } 595 | 596 | HIREDIS_HAPP_API void cluster::set_timeout(time_t sec) { conf_.timer_timeout_sec = sec; } 597 | 598 | HIREDIS_HAPP_API void cluster::add_timer_cmd(cmd_t *cmd) { 599 | if (NULL == cmd) { 600 | return; 601 | } 602 | 603 | if (is_timer_active()) { 604 | timer_actions_.timer_pending.push_back(timer_t::delay_t()); 605 | timer_t::delay_t &d = timer_actions_.timer_pending.back(); 606 | d.sec = timer_actions_.last_update_sec + conf_.timer_interval_sec; 607 | d.usec = timer_actions_.last_update_usec + conf_.timer_interval_usec; 608 | d.cmd = cmd; 609 | } else { 610 | exec(NULL, 0, cmd); 611 | } 612 | } 613 | 614 | HIREDIS_HAPP_API int cluster::proc(time_t sec, time_t usec) { 615 | int ret = 0; 616 | 617 | timer_actions_.last_update_sec = sec; 618 | timer_actions_.last_update_usec = usec; 619 | 620 | while (!timer_actions_.timer_pending.empty()) { 621 | timer_t::delay_t &rd = timer_actions_.timer_pending.front(); 622 | if (rd.sec > sec || (rd.sec == sec && rd.usec > usec)) { 623 | break; 624 | } 625 | 626 | timer_t::delay_t d = rd; 627 | timer_actions_.timer_pending.pop_front(); 628 | 629 | exec(NULL, 0, d.cmd); 630 | 631 | ++ret; 632 | } 633 | 634 | // connection timeout 635 | // this can not be call in callback_ 636 | while (!timer_actions_.timer_conns.empty() && sec >= timer_actions_.timer_conns.front().timeout) { 637 | timer_t::conn_timetout_t &conn_expire = timer_actions_.timer_conns.front(); 638 | 639 | connection_t *conn = get_connection(conn_expire.name); 640 | if (NULL != conn && conn->get_sequence() == conn_expire.sequence) { 641 | assert(!(conn->get_context()->c.flags & REDIS_IN_CALLBACK)); 642 | release_connection(conn->get_key(), true, error_code::REDIS_HAPP_TIMEOUT); 643 | } 644 | 645 | timer_actions_.timer_conns.pop_front(); 646 | } 647 | 648 | return ret; 649 | } 650 | 651 | HIREDIS_HAPP_API void cluster::set_log_writer(log_fn_t info_fn, log_fn_t debug_fn, size_t max_size) { 652 | using std::swap; 653 | conf_.log_fn_info = info_fn; 654 | conf_.log_fn_debug = debug_fn; 655 | conf_.log_max_size = max_size; 656 | 657 | if (NULL != conf_.log_buffer) { 658 | free(conf_.log_buffer); 659 | conf_.log_buffer = NULL; 660 | } 661 | } 662 | 663 | HIREDIS_HAPP_API const cluster::timer_t &cluster::get_timer_actions() const { return timer_actions_; } 664 | 665 | HIREDIS_HAPP_API cluster::cmd_t *cluster::create_cmd(cmd_t::callback_fn_t cbk, void *pridata) { 666 | holder_t h; 667 | h.clu = this; 668 | cmd_t *ret = cmd_t::create(h, cbk, pridata, conf_.cmd_buffer_size); 669 | return ret; 670 | } 671 | 672 | HIREDIS_HAPP_API void cluster::destroy_cmd(cmd_t *c) { 673 | if (NULL == c) { 674 | log_debug("can not destroy null cmd"); 675 | return; 676 | } 677 | 678 | // lost connection 679 | if (NULL != c->callback_) { 680 | call_cmd(c, error_code::REDIS_HAPP_UNKNOWD, NULL, NULL); 681 | } 682 | 683 | cmd_t::destroy(c); 684 | } 685 | 686 | HIREDIS_HAPP_API int cluster::call_cmd(cmd_t *c, int err, redisAsyncContext *context, void *reply) { 687 | if (NULL == c) { 688 | log_debug("can not call cmd without cmd object"); 689 | return error_code::REDIS_HAPP_UNKNOWD; 690 | } 691 | 692 | return c->call_reply(err, context, reply); 693 | } 694 | 695 | void cluster::on_reply_wrapper(redisAsyncContext *c, void *r, void *privdata) { 696 | connection_t *conn = reinterpret_cast(c->data); 697 | cmd_t *cmd = reinterpret_cast(privdata); 698 | cluster *self = cmd->holder_.clu; 699 | 700 | // retry if disconnecting will lead to a infinite loop 701 | if (c->c.flags & REDIS_DISCONNECTING) { 702 | self->log_debug("redis cmd %p reply when disconnecting context err %d,msg %s", cmd, c->err, 703 | NULL == c->errstr ? detail::NONE_MSG : c->errstr); 704 | cmd->error_code_ = error_code::REDIS_HAPP_CONNECTION; 705 | conn->call_reply(cmd, r); 706 | return; 707 | } 708 | 709 | if (REDIS_ERR_IO == c->err && REDIS_ERR_EOF == c->err) { 710 | self->log_debug("redis cmd %p reply context err %d and will retry, %s", cmd, c->err, c->errstr); 711 | // retry if it's a network error 712 | conn->pop_reply(cmd); 713 | self->retry(cmd); 714 | return; 715 | } 716 | 717 | if (REDIS_OK != c->err || NULL == r) { 718 | self->log_debug("redis cmd %p reply context err %d and abort, %s", cmd, c->err, 719 | NULL == c->errstr ? detail::NONE_MSG : c->errstr); 720 | // other errors will be passed to caller 721 | conn->call_reply(cmd, r); 722 | return; 723 | } 724 | 725 | redisReply *reply = reinterpret_cast(r); 726 | 727 | // error handler 728 | if (REDIS_REPLY_ERROR == reply->type) { 729 | int slot_index = 0; 730 | char addr[260] = {0}; 731 | 732 | // detect MOVED,ASK and CLUSTERDOWN 733 | if (0 == HIREDIS_HAPP_STRNCASE_CMP("ASK", reply->str, 3)) { 734 | self->log_debug("redis cmd %p %s", cmd, reply->str); 735 | // send ASK to another connection 736 | #if defined(_MSC_VER) 737 | HIREDIS_HAPP_SSCANF(reply->str + 4, "%d %s", &slot_index, addr, static_cast(sizeof(addr))); 738 | #else 739 | HIREDIS_HAPP_SSCANF(reply->str + 4, "%d %s", &slot_index, addr); 740 | #endif 741 | std::string ip; 742 | uint16_t port; 743 | if (connection::pick_name(addr, ip, port)) { 744 | connection::key_t conn_key; 745 | connection::set_key(conn_key, ip, port); 746 | 747 | // ASKING request 748 | connection_t *ask_conn = self->get_connection(conn_key.name); 749 | if (NULL == ask_conn) { 750 | ask_conn = self->make_connection(conn_key); 751 | } 752 | 753 | // pop from old connection, and run it 754 | conn->pop_reply(cmd); 755 | 756 | if (NULL != ask_conn) { 757 | if (REDIS_OK == redisAsyncCommand(ask_conn->get_context(), on_reply_asking, cmd, "ASKING")) { 758 | return; 759 | } 760 | } 761 | 762 | // retry if ASK failed 763 | self->retry(cmd); 764 | return; 765 | } 766 | } else if (0 == HIREDIS_HAPP_STRNCASE_CMP("MOVED", reply->str, 5)) { 767 | self->log_debug("redis cmd %p %s", cmd, reply->str); 768 | 769 | #if defined(_MSC_VER) 770 | HIREDIS_HAPP_SSCANF(reply->str + 6, "%d %s", &slot_index, addr, static_cast(sizeof(addr))); 771 | #else 772 | HIREDIS_HAPP_SSCANF(reply->str + 6, "%d %s", &slot_index, addr); 773 | #endif 774 | 775 | if (cmd->engine_.slot >= 0 && cmd->engine_.slot != slot_index) { 776 | self->log_info("cluster cmd key error, expect slot: %d, real slot: %d", cmd->engine_.slot, slot_index); 777 | cmd->engine_.slot = slot_index; 778 | } 779 | 780 | std::string ip; 781 | uint16_t port; 782 | if (connection::pick_name(addr, ip, port)) { 783 | // update slot 784 | self->slots_[slot_index].hosts.clear(); 785 | self->slots_[slot_index].hosts.push_back(connection::key_t()); 786 | connection::set_key(self->slots_[slot_index].hosts.back(), ip, port); 787 | 788 | // retry 789 | conn->pop_reply(cmd); 790 | self->retry(cmd); 791 | 792 | // reload all slots_ 793 | // FIXME: Is it necessary to reload all slots_ here? 794 | // If we don't reload all slots_, many slot may be expired and will make many cmd has 795 | // a long delay later. But if we reload all slots_, there may be too often to do this 796 | // if network is not stable for a short time Reload slots_ will use much more CPU 797 | // resource than a cmd (clear and copy 16384 lists) 798 | self->reload_slots(); 799 | return; 800 | } else { 801 | self->slot_flag_ = slot_status::INVALID; 802 | } 803 | } else if (0 == HIREDIS_HAPP_STRNCASE_CMP("CLUSTERDOWN", reply->str, 11)) { 804 | self->log_info("cluster down reset all connection, cmd and replys"); 805 | conn->call_reply(cmd, r); 806 | self->reset(); 807 | return; 808 | } 809 | 810 | self->log_debug("redis cmd %p reply error and abort, msg: %s", cmd, 811 | NULL == reply->str ? detail::NONE_MSG : reply->str); 812 | // other errors will be passed to caller 813 | conn->call_reply(cmd, r); 814 | return; 815 | } 816 | 817 | // success and call callback_ 818 | self->log_debug("redis cmd %p got reply success.(ttl_=%3d)", cmd, NULL == cmd ? -1 : static_cast(cmd->ttl_)); 819 | conn->call_reply(cmd, r); 820 | } 821 | 822 | void cluster::on_reply_update_slot(cmd_exec *cmd, redisAsyncContext *, void *r, void * /*privdata*/) { 823 | redisReply *reply = reinterpret_cast(r); 824 | cluster *self = cmd->holder_.clu; 825 | 826 | // failed and retry 827 | if (NULL == reply || reply->elements <= 0 || REDIS_REPLY_ARRAY != reply->element[0]->type) { 828 | self->slot_flag_ = slot_status::INVALID; 829 | 830 | if (!self->slot_pending_.empty()) { 831 | self->log_info("update slots_ failed and try to retry again."); 832 | 833 | // Wait for a while if it's network problem 834 | // this message will also retry for TTL times 835 | // update slots_ always random a connection 836 | cmd->engine_.slot = -1; 837 | self->add_timer_cmd(cmd); 838 | } else { 839 | self->log_info("update slots_ failed and will retry later."); 840 | } 841 | 842 | return; 843 | } 844 | 845 | // clear and reset slots_ ... 846 | for (size_t i = 0; i < HIREDIS_HAPP_SLOT_NUMBER; ++i) { 847 | self->slots_[i].hosts.clear(); 848 | } 849 | 850 | for (size_t i = 0; i < reply->elements; ++i) { 851 | redisReply *slot_node = reply->element[i]; 852 | if (slot_node->elements >= 3) { 853 | long long si = slot_node->element[0]->integer; 854 | long long ei = slot_node->element[1]->integer; 855 | 856 | std::vector hosts; 857 | for (size_t j = 2; j < slot_node->elements; ++j) { 858 | redisReply *addr = slot_node->element[j]; 859 | // redis cluster may response a empty list when some error happened 860 | if (addr->elements >= 2 && REDIS_REPLY_STRING == addr->element[0]->type && addr->element[0]->str[0] && 861 | REDIS_REPLY_INTEGER == addr->element[1]->type) { 862 | hosts.push_back(connection::key_t()); 863 | connection::set_key(hosts.back(), addr->element[0]->str, static_cast(addr->element[1]->integer)); 864 | } 865 | } 866 | 867 | // log 868 | if (NULL != self->conf_.log_fn_debug && self->conf_.log_max_size > 0) { 869 | self->log_debug("slot update: [%lld-%lld]", si, ei); 870 | for (size_t j = 0; j < hosts.size(); ++j) { 871 | self->log_debug(" -- %s", hosts[j].name.c_str()); 872 | } 873 | } 874 | // copy for 16384 times 875 | for (; si <= ei; ++si) { 876 | self->slots_[si].hosts = hosts; 877 | } 878 | } 879 | } 880 | 881 | // set status first and then retry, or there will be a infinite loop 882 | self->slot_flag_ = slot_status::OK; 883 | 884 | self->log_info("update %d slots_ done", static_cast(reply->elements)); 885 | 886 | // run pending list 887 | while (!self->slot_pending_.empty()) { 888 | cmd_t *first_cmd = self->slot_pending_.front(); 889 | self->slot_pending_.pop_front(); 890 | self->retry(first_cmd); 891 | } 892 | } 893 | 894 | void cluster::on_reply_asking(redisAsyncContext *c, void *r, void *privdata) { 895 | cmd_t *cmd = reinterpret_cast(privdata); 896 | redisReply *reply = reinterpret_cast(r); 897 | connection_t *conn = reinterpret_cast(c->data); 898 | cluster *self = conn->get_holder().clu; 899 | 900 | // cmd in ask command is not in any connection 901 | // so there is no need to pop it, directly retry will be OK 902 | 903 | if (REDIS_ERR_IO == c->err && REDIS_ERR_EOF == c->err) { 904 | self->log_debug("redis asking err %d and will retry, %s", c->err, c->errstr); 905 | // retry if network error 906 | self->retry(cmd); 907 | return; 908 | } 909 | 910 | if (REDIS_OK != c->err || NULL == r) { 911 | self->log_debug("redis asking err %d and abort, %s", c->err, NULL == c->errstr ? detail::NONE_MSG : c->errstr); 912 | 913 | if (c->c.flags & REDIS_DISCONNECTING) { 914 | cmd->error_code_ = error_code::REDIS_HAPP_CONNECTION; 915 | } 916 | 917 | // other errors should be passed to callback_ 918 | self->call_cmd(cmd, error_code::REDIS_HAPP_CONNECTION, conn->get_context(), r); 919 | self->destroy_cmd(cmd); 920 | return; 921 | } 922 | 923 | if (NULL != reply->str && 0 == HIREDIS_HAPP_STRNCASE_CMP("OK", reply->str, 2)) { 924 | self->retry(cmd, conn); 925 | return; 926 | } 927 | 928 | self->log_debug("redis reply asking err %d and abort, %s", reply->type, 929 | NULL == reply->str ? detail::NONE_MSG : reply->str); 930 | // other errors should be passed to callback_ 931 | self->call_cmd(cmd, error_code::REDIS_HAPP_CONNECTION, conn->get_context(), r); 932 | self->destroy_cmd(cmd); 933 | } 934 | 935 | void cluster::on_connected_wrapper(const struct redisAsyncContext *c, int status) { 936 | connection_t *conn = reinterpret_cast(c->data); 937 | cluster *self = conn->get_holder().clu; 938 | 939 | // hiredis bug, sometimes 0 == status but c is already closed 940 | if (REDIS_OK == status && hiredis::happ::connection::status::DISCONNECTED == conn->get_status()) { 941 | status = REDIS_ERR_OTHER; 942 | } 943 | 944 | // event callback_ 945 | if (self->callbacks_.on_connected) { 946 | self->callbacks_.on_connected(self, conn, c, status); 947 | } 948 | 949 | // failed, release resource 950 | if (REDIS_OK != status) { 951 | self->log_debug("connect to %s failed, status: %d, msg: %s", conn->get_key().name.c_str(), status, c->errstr); 952 | self->release_connection(conn->get_key(), false, status); 953 | 954 | // update slots_ if connect failed 955 | self->reload_slots(); 956 | } else { 957 | conn->set_connected(); 958 | 959 | self->log_debug("connect to %s success", conn->get_key().name.c_str()); 960 | 961 | // reload slots_ 962 | if (slot_status::INVALID == self->slot_flag_) { 963 | self->reload_slots(); 964 | } 965 | } 966 | } 967 | 968 | void cluster::on_disconnected_wrapper(const struct redisAsyncContext *c, int status) { 969 | connection_t *conn = reinterpret_cast(c->data); 970 | cluster *self = conn->get_holder().clu; 971 | 972 | // We should update slots_ on next cmd if there is any connection disconnected 973 | if (REDIS_OK != status) { 974 | self->remove_connection_key(conn->get_key().name); 975 | } 976 | 977 | // release resource 978 | self->release_connection(conn->get_key(), false, status); 979 | } 980 | 981 | void cluster::on_reply_auth(cmd_exec *cmd, redisAsyncContext *rctx, void *r, void * /*privdata*/) { 982 | redisReply *reply = reinterpret_cast(r); 983 | cluster *self = cmd->holder_.clu; 984 | assert(rctx); 985 | 986 | // error and log 987 | if (NULL == reply || 0 != HIREDIS_HAPP_STRNCASE_CMP("OK", reply->str, 2)) { 988 | const char *error_text = ""; 989 | if (NULL != reply && NULL != reply->str) { 990 | error_text = reply->str; 991 | } 992 | if (REDIS_CONN_TCP == rctx->c.connection_type) { 993 | self->log_info( 994 | "tcp:%s:%d AUTH failed. %s", 995 | rctx->c.tcp.host ? rctx->c.tcp.host : (rctx->c.tcp.source_addr ? rctx->c.tcp.source_addr : "UNKNOWN"), 996 | rctx->c.tcp.port, error_text); 997 | } else if (REDIS_CONN_UNIX == rctx->c.connection_type) { 998 | self->log_info("unix:%s AUTH failed. %s", rctx->c.unix_sock.path ? rctx->c.unix_sock.path : "NULL", error_text); 999 | } else { 1000 | self->log_info("AUTH failed. %s", error_text); 1001 | } 1002 | } else { 1003 | if (REDIS_CONN_TCP == rctx->c.connection_type) { 1004 | self->log_info( 1005 | "tcp:%s:%d AUTH success.", 1006 | rctx->c.tcp.host ? rctx->c.tcp.host : (rctx->c.tcp.source_addr ? rctx->c.tcp.source_addr : "UNKNOWN"), 1007 | rctx->c.tcp.port); 1008 | } else if (REDIS_CONN_UNIX == rctx->c.connection_type) { 1009 | self->log_info("unix:%s AUTH success.", rctx->c.unix_sock.path ? rctx->c.unix_sock.path : "NULL"); 1010 | } else { 1011 | self->log_info("AUTH success."); 1012 | } 1013 | } 1014 | } 1015 | 1016 | void cluster::remove_connection_key(const std::string &name) { 1017 | slot_flag_ = slot_status::INVALID; 1018 | 1019 | for (int i = 0; i < HIREDIS_HAPP_SLOT_NUMBER; ++i) { 1020 | std::vector &hosts = slots_[i].hosts; 1021 | if (!hosts.empty() && hosts[0].name == name) { 1022 | if (hosts.size() > 1) { 1023 | using std::swap; 1024 | swap(hosts[0], hosts[hosts.size() - 1]); 1025 | } 1026 | 1027 | hosts.pop_back(); 1028 | } 1029 | } 1030 | } 1031 | 1032 | void cluster::log_debug(const char *fmt, ...) { 1033 | if (NULL == conf_.log_fn_debug || 0 == conf_.log_max_size) { 1034 | return; 1035 | } 1036 | 1037 | if (NULL == conf_.log_buffer) { 1038 | conf_.log_buffer = reinterpret_cast(malloc(conf_.log_max_size)); 1039 | } 1040 | 1041 | va_list ap; 1042 | va_start(ap, fmt); 1043 | int len = vsnprintf(conf_.log_buffer, conf_.log_max_size, fmt, ap); 1044 | va_end(ap); 1045 | 1046 | conf_.log_buffer[conf_.log_max_size - 1] = 0; 1047 | if (len > 0) { 1048 | conf_.log_buffer[len] = 0; 1049 | } 1050 | 1051 | conf_.log_fn_debug(conf_.log_buffer); 1052 | } 1053 | 1054 | void cluster::log_info(const char *fmt, ...) { 1055 | if (NULL == conf_.log_fn_info || 0 == conf_.log_max_size) { 1056 | return; 1057 | } 1058 | 1059 | if (NULL == conf_.log_buffer) { 1060 | conf_.log_buffer = reinterpret_cast(malloc(conf_.log_max_size)); 1061 | } 1062 | 1063 | va_list ap; 1064 | va_start(ap, fmt); 1065 | int len = vsnprintf(conf_.log_buffer, conf_.log_max_size, fmt, ap); 1066 | va_end(ap); 1067 | 1068 | conf_.log_buffer[conf_.log_max_size - 1] = 0; 1069 | if (len > 0) { 1070 | conf_.log_buffer[len] = 0; 1071 | } 1072 | 1073 | conf_.log_fn_info(conf_.log_buffer); 1074 | } 1075 | } // namespace happ 1076 | } // namespace hiredis 1077 | -------------------------------------------------------------------------------- /src/detail/happ_cmd.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "detail/happ_cmd.h" 9 | 10 | #if (defined(__cplusplus) && __cplusplus >= 201103L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201103L) 11 | # include 12 | 13 | # if (defined(__cplusplus) && __cplusplus >= 201402L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201402L) 14 | static_assert(std::is_trivially_copyable::value, 15 | "hiredis::happ::cmd_exec should be trivially copyable"); 16 | # elif (defined(__cplusplus) && __cplusplus >= 201103L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201103L) 17 | static_assert(std::is_trivial::value, "hiredis::happ::cmd_exec should be trivially"); 18 | # else 19 | static_assert(std::is_pod::value, "hiredis::happ::cmd_exec should be a POD type"); 20 | # endif 21 | #endif 22 | 23 | namespace hiredis { 24 | namespace happ { 25 | 26 | HIREDIS_HAPP_API cmd_exec *cmd_exec::create(holder_t holder_, callback_fn_t cbk, void *pridata, size_t buffer_len) { 27 | size_t sum_len = sizeof(cmd_exec) + buffer_len; 28 | // padding to sizeof(void*) 29 | sum_len = (sum_len + sizeof(void *) - 1) & (~(sizeof(void *) - 1)); 30 | 31 | cmd_exec *ret = reinterpret_cast(malloc(sum_len)); 32 | 33 | if (NULL == ret) { 34 | return NULL; 35 | } 36 | 37 | memset(ret, 0, sizeof(cmd_exec)); 38 | 39 | ret->holder_ = holder_; 40 | ret->callback_ = cbk; 41 | ret->private_data_ = pridata; 42 | ret->ttl_ = HIREDIS_HAPP_TTL; 43 | 44 | ret->engine_.slot = -1; 45 | return ret; 46 | } 47 | 48 | static void free_cmd_content(cmd_content *c) { 49 | if (NULL == c) { 50 | return; 51 | } 52 | 53 | if (0 == c->raw_len) { 54 | if (NULL != c->content.redis_sds) { 55 | redisFreeSdsCommand(c->content.redis_sds); 56 | c->content.redis_sds = NULL; 57 | } 58 | } else { 59 | if (NULL != c->content.raw) { 60 | redisFreeCommand(c->content.raw); 61 | c->content.raw = NULL; 62 | } 63 | c->raw_len = 0; 64 | } 65 | } 66 | 67 | HIREDIS_HAPP_API void cmd_exec::destroy(cmd_exec *c) { 68 | if (NULL == c) { 69 | return; 70 | } 71 | 72 | free_cmd_content(&c->raw_cmd_content_); 73 | 74 | free(c); 75 | } 76 | 77 | HIREDIS_HAPP_API int64_t cmd_exec::vformat(int argc, const char **argv, const size_t *argvlen) { 78 | free_cmd_content(&raw_cmd_content_); 79 | 80 | raw_cmd_content_.raw_len = 0; 81 | return redisFormatSdsCommandArgv(&raw_cmd_content_.content.redis_sds, argc, argv, argvlen); 82 | } 83 | 84 | HIREDIS_HAPP_API int cmd_exec::format(const char *fmt, ...) { 85 | va_list ap; 86 | 87 | free_cmd_content(&raw_cmd_content_); 88 | va_start(ap, fmt); 89 | int ret = redisvFormatCommand(&raw_cmd_content_.content.raw, fmt, ap); 90 | va_end(ap); 91 | 92 | raw_cmd_content_.raw_len = ret; 93 | return ret; 94 | } 95 | 96 | HIREDIS_HAPP_API int cmd_exec::vformat(const char *fmt, va_list ap) { 97 | free_cmd_content(&raw_cmd_content_); 98 | 99 | va_list ap_c; 100 | va_copy(ap_c, ap); 101 | 102 | int ret = redisvFormatCommand(&raw_cmd_content_.content.raw, fmt, ap_c); 103 | raw_cmd_content_.raw_len = ret; 104 | return ret; 105 | } 106 | 107 | HIREDIS_HAPP_API int cmd_exec::vformat(const sds *src) { 108 | free_cmd_content(&raw_cmd_content_); 109 | 110 | if (NULL == src) { 111 | return 0; 112 | } 113 | 114 | raw_cmd_content_.content.redis_sds = sdsdup(*src); 115 | raw_cmd_content_.raw_len = 0; 116 | 117 | return static_cast(sdslen(raw_cmd_content_.content.redis_sds)); 118 | } 119 | 120 | HIREDIS_HAPP_API int cmd_exec::call_reply(int rcode, redisAsyncContext *context, void *reply) { 121 | if (NULL == callback_) { 122 | return error_code::REDIS_HAPP_OK; 123 | } 124 | 125 | error_code_ = rcode; 126 | callback_fn_t tc = callback_; 127 | callback_ = NULL; 128 | tc(this, context, reply, private_data_); 129 | 130 | return error_code::REDIS_HAPP_OK; 131 | } 132 | 133 | HIREDIS_HAPP_API int cmd_exec::result() const { return error_code_; } 134 | 135 | HIREDIS_HAPP_API void *cmd_exec::buffer() { return reinterpret_cast(this + 1); } 136 | 137 | HIREDIS_HAPP_API const void *cmd_exec::buffer() const { return reinterpret_cast(this + 1); } 138 | 139 | HIREDIS_HAPP_API void *cmd_exec::private_data() const { return private_data_; } 140 | 141 | HIREDIS_HAPP_API void cmd_exec::private_data(void *pd) { private_data_ = pd; } 142 | 143 | HIREDIS_HAPP_API const char *cmd_exec::pick_argument(const char *start, const char **str, size_t *len) { 144 | if (NULL == start) { 145 | if (0 == raw_cmd_content_.raw_len) { 146 | // because sds is typedefed to be a char*, so we can only use it directly here. 147 | start = raw_cmd_content_.content.redis_sds; 148 | } else { 149 | start = raw_cmd_content_.content.raw; 150 | } 151 | } 152 | 153 | if (NULL == start) { 154 | return NULL; 155 | } 156 | 157 | // @see http://redis.io/topics/protocol 158 | // Clients send commands to a Redis server as a RESP Array of Bulk Strings 159 | if (start[0] != '$') { 160 | start = strchr(start, '$'); 161 | if (NULL == start) return NULL; 162 | } 163 | 164 | if (NULL == len || NULL == str) { 165 | return start; 166 | } 167 | 168 | // redis bulk strings can not be greater than 512MB 169 | *len = static_cast(strtol(start + 1, NULL, 10)); 170 | start = strchr(start, '\r'); 171 | assert(start); 172 | 173 | // bulk string format: $[LENGTH]\r\n[CONTENT]\r\n 174 | *str = start + 2; 175 | return start + 2 + (*len) + 2; 176 | } 177 | 178 | HIREDIS_HAPP_API const char *cmd_exec::pick_cmd(const char **str, size_t *len) { return pick_argument(NULL, str, len); } 179 | 180 | HIREDIS_HAPP_API void cmd_exec::dump(std::ostream &out, redisReply *reply, int ident) { 181 | if (NULL == reply) { 182 | return; 183 | } 184 | 185 | // dump reply 186 | switch (reply->type) { 187 | case REDIS_REPLY_NIL: { 188 | out << "[NIL]" << std::endl; 189 | break; 190 | } 191 | case REDIS_REPLY_STATUS: { 192 | out << "[STATUS]: " << reply->str << std::endl; 193 | break; 194 | } 195 | case REDIS_REPLY_ERROR: { 196 | out << "[ERROR]: " << reply->str << std::endl; 197 | break; 198 | } 199 | case REDIS_REPLY_INTEGER: { 200 | out << reply->integer << std::endl; 201 | break; 202 | } 203 | case REDIS_REPLY_STRING: { 204 | out << reply->str << std::endl; 205 | break; 206 | } 207 | case REDIS_REPLY_ARRAY: { 208 | std::string ident_str; 209 | ident_str.assign(static_cast(ident), ' '); 210 | 211 | out << "[ARRAY]: " << std::endl; 212 | for (size_t i = 0; i < reply->elements; ++i) { 213 | out << ident_str << std::setw(7) << (i + 1) << ": "; 214 | dump(out, reply->element[i], ident + 2); 215 | } 216 | 217 | break; 218 | } 219 | default: { 220 | out << "[UNKNOWN]" << std::endl; 221 | break; 222 | } 223 | } 224 | } 225 | 226 | HIREDIS_HAPP_API holder_t cmd_exec::get_holder() const { return holder_; } 227 | 228 | HIREDIS_HAPP_API cmd_exec::callback_fn_t cmd_exec::get_callback_fn() const { return callback_; } 229 | 230 | HIREDIS_HAPP_API cmd_content cmd_exec::get_cmd_raw_content() const { return raw_cmd_content_; } 231 | 232 | HIREDIS_HAPP_API int cmd_exec::get_error_code() const { return error_code_; } 233 | } // namespace happ 234 | } // namespace hiredis 235 | -------------------------------------------------------------------------------- /src/detail/happ_connection.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "detail/happ_connection.h" 8 | 9 | namespace hiredis { 10 | namespace happ { 11 | HIREDIS_HAPP_API connection::connection() : sequence_(0), context_(NULL), conn_status_(status::DISCONNECTED) { 12 | make_sequence(); 13 | holder_.clu = NULL; 14 | } 15 | 16 | HIREDIS_HAPP_API connection::~connection() { release(true); } 17 | 18 | HIREDIS_HAPP_API uint64_t connection::get_sequence() const { return sequence_; } 19 | 20 | HIREDIS_HAPP_API void connection::init(holder_t h, const std::string &ip, uint16_t port) { 21 | set_key(key_, ip, port); 22 | init(h, key_); 23 | } 24 | 25 | HIREDIS_HAPP_API void connection::init(holder_t h, const key_t &k) { 26 | if (&key_ != &k) { 27 | key_ = k; 28 | } 29 | 30 | holder_ = h; 31 | } 32 | 33 | HIREDIS_HAPP_API connection::status::type connection::set_connecting(redisAsyncContext *c) { 34 | status::type ret = conn_status_; 35 | if (status::CONNECTING == conn_status_) { 36 | return ret; 37 | } 38 | 39 | if (c == context_) { 40 | return ret; 41 | } 42 | 43 | context_ = c; 44 | conn_status_ = status::CONNECTING; 45 | c->data = this; 46 | 47 | // new operation sequence_ 48 | make_sequence(); 49 | return ret; 50 | } 51 | 52 | HIREDIS_HAPP_API connection::status::type connection::set_disconnected(bool close_fd) { 53 | status::type ret = conn_status_; 54 | if (status::DISCONNECTED == conn_status_) { 55 | return ret; 56 | } 57 | 58 | // 先设置,防止重入 59 | conn_status_ = status::DISCONNECTED; 60 | 61 | release(close_fd); 62 | 63 | // new operation sequence_ 64 | make_sequence(); 65 | return ret; 66 | } 67 | 68 | HIREDIS_HAPP_API connection::status::type connection::set_connected() { 69 | status::type ret = conn_status_; 70 | if (status::CONNECTING != conn_status_ || NULL == context_) { 71 | return ret; 72 | } 73 | 74 | conn_status_ = status::CONNECTED; 75 | 76 | // new operation sequence_ 77 | make_sequence(); 78 | 79 | return ret; 80 | } 81 | 82 | HIREDIS_HAPP_API int connection::redis_cmd(cmd_exec *c, redisCallbackFn fn) { 83 | if (NULL == c) { 84 | return error_code::REDIS_HAPP_PARAM; 85 | } 86 | 87 | if (NULL == context_) { 88 | return error_code::REDIS_HAPP_CREATE; 89 | } 90 | 91 | switch (conn_status_) { 92 | case status::DISCONNECTED: { // failed if diconnected 93 | return error_code::REDIS_HAPP_CONNECTION; 94 | } 95 | 96 | // call redisAsyncFormattedCommand if connecting or connected. 97 | // this request will be added to hiredis's request queue 98 | // we should send data in order to trigger callback 99 | case status::CONNECTING: 100 | case status::CONNECTED: { 101 | int res = 0; 102 | const char *cstr = NULL; 103 | size_t clen = 0; 104 | if (0 == c->raw_cmd_content_.raw_len) { 105 | res = redisAsyncFormattedCommand(context_, fn, c, c->raw_cmd_content_.content.redis_sds, 106 | sdslen(c->raw_cmd_content_.content.redis_sds)); 107 | } else { 108 | res = redisAsyncFormattedCommand(context_, fn, c, c->raw_cmd_content_.content.raw, c->raw_cmd_content_.raw_len); 109 | } 110 | 111 | if (REDIS_OK == res) { 112 | c->pick_cmd(&cstr, &clen); 113 | if (NULL == cstr) { 114 | reply_list_.push_back(c); 115 | } else { 116 | bool is_pattern = tolower(cstr[0]) == 'p'; 117 | if (is_pattern) { 118 | ++cstr; 119 | } 120 | 121 | // according to the hiredis code, we can not use both monitor and subscribe in the same 122 | // connection 123 | // @note hiredis use a tricky way to check if a reply is subscribe message or 124 | // request-response message, 125 | // so it 's recommanded not to use both subscribe message and request-response 126 | // message at a connection. 127 | if (0 == HIREDIS_HAPP_STRNCASE_CMP(cstr, "subscribe\r\n", 11)) { 128 | // subscribe message has not reply 129 | cmd_exec::destroy(c); 130 | } else if (0 == HIREDIS_HAPP_STRNCASE_CMP(cstr, "unsubscribe\r\n", 13)) { 131 | // unsubscribe message has not reply 132 | cmd_exec::destroy(c); 133 | } else if (0 == HIREDIS_HAPP_STRNCASE_CMP(cstr, "monitor\r\n", 9)) { 134 | // monitor message has not reply 135 | cmd_exec::destroy(c); 136 | } else { 137 | // request-response message 138 | reply_list_.push_back(c); 139 | } 140 | } 141 | } 142 | 143 | return res; 144 | } 145 | default: { 146 | assert(0); 147 | break; 148 | } 149 | } 150 | 151 | // unknown error, recycle cmd 152 | c->call_reply(error_code::REDIS_HAPP_UNKNOWD, context_, NULL); 153 | cmd_exec::destroy(c); 154 | 155 | return error_code::REDIS_HAPP_OK; 156 | } 157 | 158 | HIREDIS_HAPP_API int connection::redis_raw_cmd(redisCallbackFn *fn, void *priv_data, const char *fmt, ...) { 159 | if (NULL == context_) { 160 | return error_code::REDIS_HAPP_CREATE; 161 | } 162 | 163 | va_list ap; 164 | va_start(ap, fmt); 165 | int res = redisvAsyncCommand(context_, fn, priv_data, fmt, ap); 166 | va_end(ap); 167 | 168 | return res; 169 | } 170 | 171 | HIREDIS_HAPP_API int connection::redis_raw_cmd(redisCallbackFn *fn, void *priv_data, const char *fmt, va_list ap) { 172 | if (NULL == context_) { 173 | return error_code::REDIS_HAPP_CREATE; 174 | } 175 | 176 | return redisvAsyncCommand(context_, fn, priv_data, fmt, ap); 177 | } 178 | 179 | HIREDIS_HAPP_API int connection::redis_raw_cmd(redisCallbackFn *fn, void *priv_data, const sds *src) { 180 | if (NULL == context_) { 181 | return error_code::REDIS_HAPP_CREATE; 182 | } 183 | 184 | return redisAsyncFormattedCommand(context_, fn, priv_data, *src, sdslen(*src)); 185 | } 186 | 187 | HIREDIS_HAPP_API int connection::redis_raw_cmd(redisCallbackFn *fn, void *priv_data, int argc, const char **argv, 188 | const size_t *argvlen) { 189 | if (NULL == context_) { 190 | return error_code::REDIS_HAPP_CREATE; 191 | } 192 | 193 | return redisAsyncCommandArgv(context_, fn, priv_data, argc, argv, argvlen); 194 | } 195 | 196 | HIREDIS_HAPP_API int connection::call_reply(cmd_exec *c, void *r) { 197 | if (NULL == context_) { 198 | // make sure to destroy cmd 199 | if (NULL != c) { 200 | c->error_code_ = error_code::REDIS_HAPP_NOT_FOUND; 201 | c->call_reply(c->error_code_, context_, r); 202 | cmd_exec::destroy(c); 203 | } 204 | 205 | return error_code::REDIS_HAPP_CREATE; 206 | } 207 | 208 | cmd_exec *sc = pop_reply(c); 209 | 210 | if (NULL == sc) { 211 | // make sure to destroy cmd 212 | if (NULL != c) { 213 | c->error_code_ = error_code::REDIS_HAPP_NOT_FOUND; 214 | c->call_reply(c->error_code_, context_, r); 215 | cmd_exec::destroy(c); 216 | } 217 | return error_code::REDIS_HAPP_NOT_FOUND; 218 | } 219 | 220 | // translate error code 221 | if (REDIS_OK != context_->err) { 222 | sc->error_code_ = error_code::REDIS_HAPP_HIREDIS; 223 | } else if (r) { 224 | redisReply *reply = reinterpret_cast(r); 225 | if (REDIS_REPLY_ERROR == reply->type) { 226 | sc->error_code_ = error_code::REDIS_HAPP_HIREDIS; 227 | } 228 | } 229 | 230 | int res = sc->call_reply(sc->error_code_, context_, r); 231 | 232 | cmd_exec::destroy(sc); 233 | return res; 234 | } 235 | 236 | HIREDIS_HAPP_API cmd_exec *connection::pop_reply(cmd_exec *c) { 237 | if (NULL == c) { 238 | if (reply_list_.empty()) { 239 | return NULL; 240 | } 241 | 242 | c = reply_list_.front(); 243 | reply_list_.pop_front(); 244 | return c; 245 | } 246 | 247 | std::list::iterator it = std::find(reply_list_.begin(), reply_list_.end(), c); 248 | if (it == reply_list_.end()) { 249 | return NULL; 250 | } 251 | 252 | // first, deal with all expired cmd 253 | while (!reply_list_.empty() && reply_list_.front() != c) { 254 | cmd_exec *expired_c = reply_list_.front(); 255 | reply_list_.pop_front(); 256 | 257 | expired_c->call_reply(error_code::REDIS_HAPP_TIMEOUT, context_, NULL); 258 | cmd_exec::destroy(expired_c); 259 | } 260 | 261 | // now, c == reply_list_.front() 262 | c = reply_list_.front(); 263 | reply_list_.pop_front(); 264 | return c; 265 | } 266 | 267 | HIREDIS_HAPP_API redisAsyncContext *connection::get_context() const { return context_; } 268 | 269 | HIREDIS_HAPP_API void connection::release(bool close_fd) { 270 | if (NULL != context_ && close_fd) { 271 | redisAsyncDisconnect(context_); 272 | } 273 | 274 | // reply list 275 | while (!reply_list_.empty()) { 276 | cmd_exec *expired_c = reply_list_.front(); 277 | reply_list_.pop_front(); 278 | 279 | // context_ may already be closed here 280 | expired_c->call_reply(error_code::REDIS_HAPP_CONNECTION, NULL, NULL); 281 | cmd_exec::destroy(expired_c); 282 | } 283 | 284 | context_ = NULL; 285 | conn_status_ = status::DISCONNECTED; 286 | } 287 | 288 | HIREDIS_HAPP_API const connection::key_t &connection::get_key() const { return key_; } 289 | 290 | HIREDIS_HAPP_API holder_t connection::get_holder() const { return holder_; } 291 | 292 | HIREDIS_HAPP_API connection::status::type connection::get_status() const { return conn_status_; } 293 | 294 | void connection::make_sequence() { 295 | do { 296 | // sequence_ will be used to make a distinction between connections when address is reused 297 | // it can not be duplicated in a short time 298 | #if defined(HIREDIS_HAPP_ATOMIC_STD) 299 | static std::atomic seq_alloc(0); 300 | sequence_ = seq_alloc.fetch_add(1); 301 | 302 | #elif defined(HIREDIS_HAPP_ATOMIC_MSVC) 303 | static LONGLONG volatile seq_alloc = 0; 304 | sequence_ = static_cast(InterlockedAdd64(&seq_alloc, 1)); 305 | 306 | #elif defined(HIREDIS_HAPP_ATOMIC_GCC_ATOMIC) 307 | static volatile uint64_t seq_alloc = 0; 308 | sequence_ = __atomic_add_fetch(&seq_alloc, 1, __ATOMIC_SEQ_CST); 309 | #elif defined(HIREDIS_HAPP_ATOMIC_GCC) 310 | static volatile uint64_t seq_alloc = 0; 311 | sequence_ = __sync_fetch_and_add(&seq_alloc, 1); 312 | #else 313 | static volatile uint64_t seq_alloc = 0; 314 | sequence_ = ++seq_alloc; 315 | #endif 316 | } while (0 == sequence_); 317 | } 318 | 319 | HIREDIS_HAPP_API std::string connection::make_name(const std::string &ip, uint16_t port) { 320 | std::string ret; 321 | ret.clear(); 322 | ret.reserve(ip.size() + 8); 323 | 324 | ret = ip; 325 | ret += ":"; 326 | 327 | char buf[8] = {0}; 328 | int i = 7; // XXXXXXX0 329 | while (port) { 330 | buf[--i] = port % 10 + '0'; 331 | port /= 10; 332 | } 333 | 334 | ret += &buf[i]; 335 | 336 | return ret; 337 | } 338 | 339 | HIREDIS_HAPP_API void connection::set_key(connection::key_t &k, const std::string &ip, uint16_t port) { 340 | k.name = make_name(ip, port); 341 | k.ip = ip; 342 | k.port = port; 343 | } 344 | 345 | HIREDIS_HAPP_API bool connection::pick_name(const std::string &name, std::string &ip, uint16_t &port) { 346 | size_t it = name.find_first_of(':'); 347 | if (it == std::string::npos) { 348 | return false; 349 | } 350 | 351 | if (it == name.size() - 1) { 352 | return false; 353 | } 354 | 355 | size_t s = 0; 356 | while (' ' == name[s] || '\t' == name[s] || '\r' == name[s] || '\n' == name[s]) { 357 | ++s; 358 | } 359 | 360 | ip = name.substr(s, it - s); 361 | int p = 0; 362 | 363 | HIREDIS_HAPP_SSCANF(name.c_str() + it + 1, "%d", &p); 364 | port = static_cast(p); 365 | 366 | return true; 367 | } 368 | } // namespace happ 369 | } // namespace hiredis 370 | -------------------------------------------------------------------------------- /src/detail/happ_raw.cpp: -------------------------------------------------------------------------------- 1 | #if defined(_WIN32) 2 | # ifndef WIN32_LEAN_AND_MEAN 3 | # define WIN32_LEAN_AND_MEAN 4 | # endif 5 | # ifndef NOMINMAX 6 | # define NOMINMAX 7 | # endif 8 | #endif 9 | 10 | #ifdef _MSC_VER 11 | # include 12 | #else 13 | # include 14 | #endif 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include "detail/happ_raw.h" 25 | 26 | namespace hiredis { 27 | namespace happ { 28 | namespace detail { 29 | static char NONE_MSG[] = "none"; 30 | } 31 | 32 | HIREDIS_HAPP_API raw::raw() { 33 | conf_.log_fn_debug = conf_.log_fn_info = NULL; 34 | conf_.log_buffer = NULL; 35 | conf_.log_max_size = 0; 36 | conf_.timer_interval_sec = HIREDIS_HAPP_TIMER_INTERVAL_SEC; 37 | conf_.timer_interval_usec = HIREDIS_HAPP_TIMER_INTERVAL_USEC; 38 | conf_.timer_timeout_sec = HIREDIS_HAPP_TIMER_TIMEOUT_SEC; 39 | conf_.cmd_buffer_size = 0; 40 | 41 | callbacks_.on_connect = NULL; 42 | callbacks_.on_connected = NULL; 43 | callbacks_.on_disconnected = NULL; 44 | 45 | timer_actions_.last_update_sec = 0; 46 | timer_actions_.last_update_usec = 0; 47 | 48 | timer_actions_.timer_conn.sequence = 0; 49 | timer_actions_.timer_conn.timeout = 0; 50 | } 51 | 52 | HIREDIS_HAPP_API raw::~raw() { 53 | reset(); 54 | 55 | // log buffer 56 | if (NULL != conf_.log_buffer) { 57 | free(conf_.log_buffer); 58 | conf_.log_buffer = NULL; 59 | } 60 | } 61 | 62 | HIREDIS_HAPP_API int raw::init(const std::string &ip, uint16_t port) { 63 | connection::set_key(conf_.init_connection, ip, port); 64 | 65 | return error_code::REDIS_HAPP_OK; 66 | } 67 | 68 | HIREDIS_HAPP_API const std::string &raw::get_auth_password() { return auth_.password; } 69 | 70 | HIREDIS_HAPP_API void raw::set_auth_password(const std::string &passwd) { auth_.password = passwd; } 71 | 72 | HIREDIS_HAPP_API const connection::auth_fn_t &raw::get_auth_fn() { return auth_.auth_fn; } 73 | 74 | HIREDIS_HAPP_API void raw::set_auth_fn(connection::auth_fn_t fn) { auth_.auth_fn = fn; } 75 | 76 | HIREDIS_HAPP_API int raw::start() { 77 | // just do nothing 78 | return error_code::REDIS_HAPP_OK; 79 | } 80 | 81 | HIREDIS_HAPP_API int raw::reset() { 82 | // close connection if it's available 83 | if (conn_ && NULL != conn_->get_context()) { 84 | redisAsyncDisconnect(conn_->get_context()); 85 | } 86 | 87 | // release timer pending list 88 | while (!timer_actions_.timer_pending.empty()) { 89 | cmd_t *cmd = timer_actions_.timer_pending.front().cmd; 90 | timer_actions_.timer_pending.pop_front(); 91 | 92 | call_cmd(cmd, error_code::REDIS_HAPP_TIMEOUT, NULL, NULL); 93 | destroy_cmd(cmd); 94 | } 95 | 96 | timer_actions_.last_update_sec = 0; 97 | timer_actions_.last_update_usec = 0; 98 | 99 | // reset timeout 100 | timer_actions_.timer_conn.sequence = 0; 101 | timer_actions_.timer_conn.timeout = 0; 102 | 103 | // If in a callback_, cmds in this connection will not finished, so it can not be freed. 104 | // In this case, it will call disconnect callback_ after callback_ is finished and then release 105 | // the connection. If not in a callback_, this connection is already freed at the begining 106 | // "redisAsyncDisconnect(conn_->get_context());" conn_.reset(); // can not reset connection 107 | 108 | return 0; 109 | } 110 | 111 | HIREDIS_HAPP_API raw::cmd_t *raw::exec(cmd_t::callback_fn_t cbk, void *priv_data, int argc, const char **argv, 112 | const size_t *argvlen) { 113 | cmd_t *cmd = create_cmd(cbk, priv_data); 114 | if (NULL == cmd) { 115 | return NULL; 116 | } 117 | 118 | int64_t len = cmd->vformat(argc, argv, argvlen); 119 | if (len <= 0) { 120 | log_info("format cmd with argc=%d failed", argc); 121 | destroy_cmd(cmd); 122 | return NULL; 123 | } 124 | 125 | return exec(cmd); 126 | } 127 | 128 | HIREDIS_HAPP_API raw::cmd_t *raw::exec(cmd_t::callback_fn_t cbk, void *priv_data, const char *fmt, ...) { 129 | cmd_t *cmd = create_cmd(cbk, priv_data); 130 | if (NULL == cmd) { 131 | return NULL; 132 | } 133 | 134 | va_list ap; 135 | va_start(ap, fmt); 136 | int len = cmd->vformat(fmt, ap); 137 | va_end(ap); 138 | if (len <= 0) { 139 | log_info("format cmd with format=%s failed", fmt); 140 | destroy_cmd(cmd); 141 | return NULL; 142 | } 143 | 144 | return exec(cmd); 145 | } 146 | 147 | HIREDIS_HAPP_API raw::cmd_t *raw::exec(cmd_t::callback_fn_t cbk, void *priv_data, const char *fmt, va_list ap) { 148 | cmd_t *cmd = create_cmd(cbk, priv_data); 149 | if (NULL == cmd) { 150 | return NULL; 151 | } 152 | 153 | int len = cmd->vformat(fmt, ap); 154 | if (len <= 0) { 155 | log_info("format cmd with format=%s failed", fmt); 156 | destroy_cmd(cmd); 157 | return NULL; 158 | } 159 | 160 | return exec(cmd); 161 | } 162 | 163 | HIREDIS_HAPP_API raw::cmd_t *raw::exec(cmd_t *cmd) { 164 | if (NULL == cmd) { 165 | return NULL; 166 | } 167 | 168 | // ttl_ pre judge 169 | if (0 == cmd->ttl_) { 170 | log_debug("cmd %p ttl_ expired", cmd); 171 | call_cmd(cmd, error_code::REDIS_HAPP_TTL, NULL, NULL); 172 | destroy_cmd(cmd); 173 | return NULL; 174 | } 175 | 176 | // move cmd into connection 177 | connection_t *conn_inst = get_connection(); 178 | if (NULL == conn_inst) { 179 | conn_inst = make_connection(); 180 | } 181 | 182 | if (NULL == conn_inst) { 183 | log_info("connect to %s failed", conf_.init_connection.name.c_str()); 184 | 185 | call_cmd(cmd, error_code::REDIS_HAPP_CONNECTION, NULL, NULL); 186 | destroy_cmd(cmd); 187 | 188 | return NULL; 189 | } 190 | 191 | return exec(conn_inst, cmd); 192 | } 193 | 194 | HIREDIS_HAPP_API raw::cmd_t *raw::exec(connection_t *conn, cmd_t *cmd) { 195 | if (NULL == cmd) { 196 | return NULL; 197 | } 198 | 199 | // ttl_ judge 200 | if (0 == cmd->ttl_) { 201 | log_debug("cmd %p at connection %s ttl_ expired", cmd, conf_.init_connection.name.c_str()); 202 | call_cmd(cmd, error_code::REDIS_HAPP_TTL, NULL, NULL); 203 | destroy_cmd(cmd); 204 | return NULL; 205 | } 206 | 207 | // ttl_ 208 | --cmd->ttl_; 209 | 210 | if (NULL == conn) { 211 | call_cmd(cmd, error_code::REDIS_HAPP_CONNECTION, NULL, NULL); 212 | destroy_cmd(cmd); 213 | return NULL; 214 | } 215 | 216 | // main loop 217 | int res = conn->redis_cmd(cmd, on_reply_wrapper); 218 | 219 | if (REDIS_OK != res) { 220 | // some version of hiredis will miss onDisconnect, patch it 221 | // other situation should trigger error 222 | if (conn->get_context()->c.flags & (REDIS_DISCONNECTING | REDIS_FREEING)) { 223 | // Patch: hiredis will miss onDisconnect in some older version 224 | // If not in hiredis's callback_, REDIS_DISCONNECTING or REDIS_FREEING means resource is freed 225 | // If in hiredis's callback_, disconnect will be called after callback_ finished, so do 226 | // nothing here 227 | if (conn_.get() == conn && !(conn->get_context()->c.flags & REDIS_IN_CALLBACK)) { 228 | release_connection(false, error_code::REDIS_HAPP_CONNECTION); 229 | } 230 | 231 | // conn = NULL; 232 | // retry if the connection lost 233 | return retry(cmd, NULL); 234 | } else { 235 | call_cmd(cmd, error_code::REDIS_HAPP_HIREDIS, conn->get_context(), NULL); 236 | destroy_cmd(cmd); 237 | } 238 | return NULL; 239 | } 240 | 241 | log_debug("exec cmd %p, connection %s", cmd, conn->get_key().name.c_str()); 242 | return cmd; 243 | } 244 | 245 | HIREDIS_HAPP_API raw::cmd_t *raw::retry(cmd_t *cmd, connection_t *conn) { 246 | if (NULL == cmd) { 247 | return NULL; 248 | } 249 | 250 | // First, retry immediately for several times. 251 | if (false == is_timer_active() || cmd->ttl_ > HIREDIS_HAPP_TTL / 2) { 252 | if (NULL == conn) { 253 | return exec(cmd); 254 | } else { 255 | return exec(conn, cmd); 256 | } 257 | } 258 | 259 | // If it's still failed, maybe it will take some more time to recover the connection, 260 | // so wait for a while and retry again. 261 | add_timer_cmd(cmd); 262 | return cmd; 263 | } 264 | 265 | HIREDIS_HAPP_API const raw::connection_t *raw::get_connection() const { return conn_.get(); } 266 | 267 | HIREDIS_HAPP_API raw::connection_t *raw::get_connection() { return conn_.get(); } 268 | 269 | HIREDIS_HAPP_API raw::connection_t *raw::make_connection() { 270 | holder_t h; 271 | if (conn_) { 272 | log_debug("connection %s already exists", conf_.init_connection.name.c_str()); 273 | return NULL; 274 | } 275 | 276 | redisAsyncContext *c = 277 | redisAsyncConnect(conf_.init_connection.ip.c_str(), static_cast(conf_.init_connection.port)); 278 | if (NULL == c || c->err) { 279 | log_info("redis connect to %s failed, msg: %s", conf_.init_connection.name.c_str(), 280 | NULL == c ? detail::NONE_MSG : c->errstr); 281 | return NULL; 282 | } 283 | 284 | h.r = this; 285 | redisAsyncSetConnectCallback(c, on_connected_wrapper); 286 | redisAsyncSetDisconnectCallback(c, on_disconnected_wrapper); 287 | redisEnableKeepAlive(&c->c); 288 | if (conf_.timer_timeout_sec > 0) { 289 | struct timeval tv; 290 | tv.tv_sec = static_cast(conf_.timer_timeout_sec); 291 | tv.tv_usec = 0; 292 | redisSetTimeout(&c->c, tv); 293 | } 294 | 295 | connection_ptr_t ret_ptr(new connection_t()); 296 | connection_t &ret = *ret_ptr; 297 | swap(conn_, ret_ptr); 298 | ret.init(h, conf_.init_connection); 299 | ret.set_connecting(c); 300 | 301 | c->data = &ret; 302 | 303 | // timeout timer 304 | if (conf_.timer_timeout_sec > 0 && is_timer_active()) { 305 | timer_actions_.timer_conn.sequence = ret.get_sequence(); 306 | timer_actions_.timer_conn.timeout = timer_actions_.last_update_sec + conf_.timer_timeout_sec; 307 | } 308 | 309 | // auth_ command 310 | if (auth_.auth_fn || !auth_.password.empty()) { 311 | // AUTH cmd 312 | cmd_t *cmd = create_cmd(on_reply_auth, NULL); 313 | if (NULL != cmd) { 314 | int len = 0; 315 | if (auth_.auth_fn) { 316 | const std::string &passwd = auth_.auth_fn(&ret, auth_.password); 317 | len = cmd->format("AUTH %b", passwd.c_str(), passwd.size()); 318 | } else if (!auth_.password.empty()) { 319 | len = cmd->format("AUTH %b", auth_.password.c_str(), auth_.password.size()); 320 | } 321 | 322 | if (len <= 0) { 323 | log_info("format cmd AUTH failed"); 324 | destroy_cmd(cmd); 325 | return NULL; 326 | } 327 | 328 | exec(&ret, cmd); 329 | } 330 | } 331 | 332 | // event callback_ 333 | if (callbacks_.on_connect) { 334 | callbacks_.on_connect(this, &ret); 335 | } 336 | 337 | log_debug("redis make connection to %s ", conf_.init_connection.name.c_str()); 338 | return &ret; 339 | } 340 | 341 | HIREDIS_HAPP_API bool raw::release_connection(bool close_fd, int status) { 342 | if (!conn_) { 343 | log_debug("connection %s not found", conf_.init_connection.name.c_str()); 344 | return false; 345 | } 346 | 347 | connection_t::status::type from_status = conn_->set_disconnected(close_fd); 348 | switch (from_status) { 349 | // recursion, exit 350 | case connection_t::status::DISCONNECTED: 351 | return true; 352 | 353 | // connecting, call on_connected event 354 | case connection_t::status::CONNECTING: 355 | if (callbacks_.on_connected) { 356 | callbacks_.on_connected(this, conn_.get(), conn_->get_context(), 357 | error_code::REDIS_HAPP_OK == status ? error_code::REDIS_HAPP_CONNECTION : status); 358 | } 359 | break; 360 | 361 | // connecting, call on_disconnected event 362 | case connection_t::status::CONNECTED: 363 | if (callbacks_.on_disconnected) { 364 | callbacks_.on_disconnected(this, conn_.get(), conn_->get_context(), status); 365 | } 366 | break; 367 | 368 | default: 369 | log_info("unknown connection status %d", static_cast(from_status)); 370 | break; 371 | } 372 | 373 | log_debug("release connection %s", conf_.init_connection.name.c_str()); 374 | 375 | // can not use conf_.init_connection any more 376 | conn_.reset(); 377 | timer_actions_.timer_conn.sequence = 0; 378 | timer_actions_.timer_conn.timeout = 0; 379 | 380 | return true; 381 | } 382 | 383 | HIREDIS_HAPP_API raw::onconnect_fn_t raw::set_on_connect(onconnect_fn_t cbk) { 384 | using std::swap; 385 | swap(cbk, callbacks_.on_connect); 386 | return cbk; 387 | } 388 | 389 | HIREDIS_HAPP_API raw::onconnected_fn_t raw::set_on_connected(onconnected_fn_t cbk) { 390 | using std::swap; 391 | swap(cbk, callbacks_.on_connected); 392 | return cbk; 393 | } 394 | 395 | HIREDIS_HAPP_API raw::ondisconnected_fn_t raw::set_on_disconnected(ondisconnected_fn_t cbk) { 396 | using std::swap; 397 | swap(cbk, callbacks_.on_disconnected); 398 | return cbk; 399 | } 400 | 401 | HIREDIS_HAPP_API void raw::set_cmd_buffer_size(size_t s) { conf_.cmd_buffer_size = s; } 402 | 403 | HIREDIS_HAPP_API size_t raw::get_cmd_buffer_size() const { return conf_.cmd_buffer_size; } 404 | 405 | HIREDIS_HAPP_API bool raw::is_timer_active() const { 406 | return (timer_actions_.last_update_sec != 0 || timer_actions_.last_update_usec != 0) && 407 | (conf_.timer_interval_sec > 0 || conf_.timer_interval_usec > 0); 408 | } 409 | 410 | HIREDIS_HAPP_API void raw::set_timer_interval(time_t sec, time_t usec) { 411 | conf_.timer_interval_sec = sec; 412 | conf_.timer_interval_usec = usec; 413 | } 414 | 415 | HIREDIS_HAPP_API void raw::set_timeout(time_t sec) { conf_.timer_timeout_sec = sec; } 416 | 417 | HIREDIS_HAPP_API void raw::add_timer_cmd(cmd_t *cmd) { 418 | if (NULL == cmd) { 419 | return; 420 | } 421 | 422 | if (is_timer_active()) { 423 | timer_actions_.timer_pending.push_back(timer_t::delay_t()); 424 | timer_t::delay_t &d = timer_actions_.timer_pending.back(); 425 | d.sec = timer_actions_.last_update_sec + conf_.timer_interval_sec; 426 | d.usec = timer_actions_.last_update_usec + conf_.timer_interval_usec; 427 | d.cmd = cmd; 428 | } else { 429 | exec(cmd); 430 | } 431 | } 432 | 433 | HIREDIS_HAPP_API int raw::proc(time_t sec, time_t usec) { 434 | int ret = 0; 435 | 436 | timer_actions_.last_update_sec = sec; 437 | timer_actions_.last_update_usec = usec; 438 | 439 | while (!timer_actions_.timer_pending.empty()) { 440 | timer_t::delay_t &rd = timer_actions_.timer_pending.front(); 441 | if (rd.sec > sec || (rd.sec == sec && rd.usec > usec)) { 442 | break; 443 | } 444 | 445 | timer_t::delay_t d = rd; 446 | timer_actions_.timer_pending.pop_front(); 447 | 448 | exec(d.cmd); 449 | 450 | ++ret; 451 | } 452 | 453 | // connection timeout 454 | // can not be call in any callback_ 455 | while (0 != timer_actions_.timer_conn.timeout && sec >= timer_actions_.timer_conn.timeout) { 456 | // sequence expired skip 457 | if (conn_ && conn_->get_sequence() == timer_actions_.timer_conn.sequence) { 458 | assert(!(conn_->get_context()->c.flags & REDIS_IN_CALLBACK)); 459 | release_connection(true, error_code::REDIS_HAPP_TIMEOUT); 460 | } 461 | 462 | timer_actions_.timer_conn.timeout = 0; 463 | timer_actions_.timer_conn.sequence = 0; 464 | } 465 | 466 | return ret; 467 | } 468 | 469 | HIREDIS_HAPP_API void raw::set_log_writer(log_fn_t info_fn, log_fn_t debug_fn, size_t max_size) { 470 | using std::swap; 471 | conf_.log_fn_info = info_fn; 472 | conf_.log_fn_debug = debug_fn; 473 | conf_.log_max_size = max_size; 474 | 475 | if (NULL != conf_.log_buffer) { 476 | free(conf_.log_buffer); 477 | conf_.log_buffer = NULL; 478 | } 479 | } 480 | 481 | HIREDIS_HAPP_API raw::cmd_t *raw::create_cmd(cmd_t::callback_fn_t cbk, void *pridata) { 482 | holder_t h; 483 | h.r = this; 484 | cmd_t *ret = cmd_t::create(h, cbk, pridata, conf_.cmd_buffer_size); 485 | return ret; 486 | } 487 | 488 | HIREDIS_HAPP_API void raw::destroy_cmd(cmd_t *c) { 489 | if (NULL == c) { 490 | log_debug("can not destroy null cmd"); 491 | return; 492 | } 493 | 494 | // lost connection 495 | if (NULL != c->callback_) { 496 | call_cmd(c, error_code::REDIS_HAPP_UNKNOWD, NULL, NULL); 497 | } 498 | 499 | cmd_t::destroy(c); 500 | } 501 | 502 | HIREDIS_HAPP_API int raw::call_cmd(cmd_t *c, int err, redisAsyncContext *context, void *reply) { 503 | if (NULL == c) { 504 | log_debug("can not call cmd without cmd object"); 505 | return error_code::REDIS_HAPP_UNKNOWD; 506 | } 507 | 508 | return c->call_reply(err, context, reply); 509 | } 510 | 511 | void raw::on_reply_wrapper(redisAsyncContext *c, void *r, void *privdata) { 512 | connection_t *conn = reinterpret_cast(c->data); 513 | cmd_t *cmd = reinterpret_cast(privdata); 514 | raw *self = cmd->holder_.r; 515 | 516 | // retry if disconnecting will lead to a infinite loop 517 | if (c->c.flags & REDIS_DISCONNECTING) { 518 | self->log_debug("redis cmd %p reply when disconnecting context err %d,msg %s", cmd, c->err, 519 | NULL == c->errstr ? detail::NONE_MSG : c->errstr); 520 | cmd->error_code_ = error_code::REDIS_HAPP_CONNECTION; 521 | conn->call_reply(cmd, r); 522 | return; 523 | } 524 | 525 | if (REDIS_ERR_IO == c->err && REDIS_ERR_EOF == c->err) { 526 | self->log_debug("redis cmd %p reply context err %d and will retry, %s", cmd, c->err, c->errstr); 527 | // retry if it's a network error 528 | conn->pop_reply(cmd); 529 | self->retry(cmd); 530 | return; 531 | } 532 | 533 | if (REDIS_OK != c->err || NULL == r) { 534 | self->log_debug("redis cmd %p reply context err %d and abort, %s", cmd, c->err, 535 | NULL == c->errstr ? detail::NONE_MSG : c->errstr); 536 | // other errors will be passed to caller 537 | conn->call_reply(cmd, r); 538 | return; 539 | } 540 | 541 | redisReply *reply = reinterpret_cast(r); 542 | 543 | // error handler 544 | if (REDIS_REPLY_ERROR == reply->type) { 545 | self->log_debug("redis cmd %p reply error and abort, msg: %s", cmd, 546 | NULL == reply->str ? detail::NONE_MSG : reply->str); 547 | // other errors will be passed to caller 548 | conn->call_reply(cmd, r); 549 | return; 550 | } 551 | 552 | // success and call callback_ 553 | self->log_debug("redis cmd %p got reply success.(ttl_=%3d)", cmd, NULL == cmd ? -1 : static_cast(cmd->ttl_)); 554 | conn->call_reply(cmd, r); 555 | } 556 | 557 | void raw::on_connected_wrapper(const struct redisAsyncContext *c, int status) { 558 | connection_t *conn = reinterpret_cast(c->data); 559 | raw *self = conn->get_holder().r; 560 | 561 | // hiredis bug, sometimes 0 == status but c is already closed 562 | if (REDIS_OK == status && hiredis::happ::connection::status::DISCONNECTED == conn->get_status()) { 563 | status = REDIS_ERR_OTHER; 564 | } 565 | 566 | // event callback_ 567 | if (self->callbacks_.on_connected) { 568 | self->callbacks_.on_connected(self, conn, c, status); 569 | } 570 | 571 | // failed, release resource 572 | if (REDIS_OK != status) { 573 | self->log_debug("connect to %s failed, status: %d, msg: %s", conn->get_key().name.c_str(), status, c->errstr); 574 | self->release_connection(false, status); 575 | 576 | } else { 577 | conn->set_connected(); 578 | 579 | self->log_debug("connect to %s success", conn->get_key().name.c_str()); 580 | } 581 | } 582 | 583 | void raw::on_disconnected_wrapper(const struct redisAsyncContext *c, int status) { 584 | connection_t *conn = reinterpret_cast(c->data); 585 | raw *self = conn->get_holder().r; 586 | 587 | // release rreource 588 | self->release_connection(false, status); 589 | } 590 | 591 | void raw::on_reply_auth(cmd_exec *cmd, redisAsyncContext *rctx, void *r, void * /*privdata*/) { 592 | redisReply *reply = reinterpret_cast(r); 593 | raw *self = cmd->holder_.r; 594 | assert(rctx); 595 | 596 | // error and log 597 | if (NULL == reply || 0 != HIREDIS_HAPP_STRNCASE_CMP("OK", reply->str, 2)) { 598 | const char *error_text = ""; 599 | if (NULL != reply && NULL != reply->str) { 600 | error_text = reply->str; 601 | } 602 | if (REDIS_CONN_TCP == rctx->c.connection_type) { 603 | self->log_info( 604 | "tcp:%s:%d AUTH failed. %s", 605 | rctx->c.tcp.host ? rctx->c.tcp.host : (rctx->c.tcp.source_addr ? rctx->c.tcp.source_addr : "UNKNOWN"), 606 | rctx->c.tcp.port, error_text); 607 | } else if (REDIS_CONN_UNIX == rctx->c.connection_type) { 608 | self->log_info("unix:%s AUTH failed. %s", rctx->c.unix_sock.path ? rctx->c.unix_sock.path : "NULL", error_text); 609 | } else { 610 | self->log_info("AUTH failed. %s", error_text); 611 | } 612 | } else { 613 | if (REDIS_CONN_TCP == rctx->c.connection_type) { 614 | self->log_info( 615 | "tcp:%s:%d AUTH success.", 616 | rctx->c.tcp.host ? rctx->c.tcp.host : (rctx->c.tcp.source_addr ? rctx->c.tcp.source_addr : "UNKNOWN"), 617 | rctx->c.tcp.port); 618 | } else if (REDIS_CONN_UNIX == rctx->c.connection_type) { 619 | self->log_info("unix:%s AUTH success.", rctx->c.unix_sock.path ? rctx->c.unix_sock.path : "NULL"); 620 | } else { 621 | self->log_info("AUTH success."); 622 | } 623 | } 624 | } 625 | 626 | void raw::log_debug(const char *fmt, ...) { 627 | if (NULL == conf_.log_fn_debug || 0 == conf_.log_max_size) { 628 | return; 629 | } 630 | 631 | if (NULL == conf_.log_buffer) { 632 | conf_.log_buffer = reinterpret_cast(malloc(conf_.log_max_size)); 633 | } 634 | 635 | va_list ap; 636 | va_start(ap, fmt); 637 | int len = vsnprintf(conf_.log_buffer, conf_.log_max_size, fmt, ap); 638 | va_end(ap); 639 | 640 | conf_.log_buffer[conf_.log_max_size - 1] = 0; 641 | if (len > 0) { 642 | conf_.log_buffer[len] = 0; 643 | } 644 | 645 | conf_.log_fn_debug(conf_.log_buffer); 646 | } 647 | 648 | void raw::log_info(const char *fmt, ...) { 649 | if (NULL == conf_.log_fn_info || 0 == conf_.log_max_size) { 650 | return; 651 | } 652 | 653 | if (NULL == conf_.log_buffer) { 654 | conf_.log_buffer = reinterpret_cast(malloc(conf_.log_max_size)); 655 | } 656 | 657 | va_list ap; 658 | va_start(ap, fmt); 659 | int len = vsnprintf(conf_.log_buffer, conf_.log_max_size, fmt, ap); 660 | va_end(ap); 661 | 662 | conf_.log_buffer[conf_.log_max_size - 1] = 0; 663 | if (len > 0) { 664 | conf_.log_buffer[len] = 0; 665 | } 666 | 667 | conf_.log_fn_info(conf_.log_buffer); 668 | } 669 | } // namespace happ 670 | } // namespace hiredis 671 | -------------------------------------------------------------------------------- /src/hiredis_happ.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by owent on 2015/7/5. 3 | // 4 | 5 | #include "hiredis_happ.h" 6 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | echowithcolor(COLOR GREEN "-- Configure Unit Test ${CMAKE_CURRENT_LIST_DIR}") 2 | 3 | include("${PROJECT_TEST_BAS_DIR}/test.build_bin.cmake") 4 | 5 | file( 6 | GLOB_RECURSE 7 | PROJECT_TEST_SRC_LIST 8 | ${PROJECT_TEST_SRC_DIR}/app/*.cpp 9 | ${PROJECT_TEST_SRC_DIR}/frame/*.h 10 | ${PROJECT_TEST_SRC_DIR}/frame/*.cpp 11 | ${CMAKE_CURRENT_LIST_DIR}/*.hpp 12 | ${CMAKE_CURRENT_LIST_DIR}/*.h 13 | ${CMAKE_CURRENT_LIST_DIR}/*.c 14 | ${CMAKE_CURRENT_LIST_DIR}/*.cpp 15 | ${CMAKE_CURRENT_LIST_DIR}/*.cc 16 | ${CMAKE_CURRENT_LIST_DIR}/*.cxx) 17 | source_group_by_dir(PROJECT_TEST_SRC_LIST) 18 | 19 | # ============ test - coroutine test frame ============ 20 | if(NOT (WIN32 AND BUILD_SHARED_LIBS)) 21 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/test") 22 | endif() 23 | 24 | add_compiler_define(_CRT_SECURE_NO_WARNINGS=1) 25 | add_compiler_define(HIREDIS_HAPP_UNIT_TEST_HACK=1) 26 | 27 | atframe_add_test_executable(hiredis-happ-test ${PROJECT_TEST_SRC_LIST}) 28 | 29 | set_target_properties( 30 | hiredis-happ-test 31 | PROPERTIES INSTALL_RPATH_USE_LINK_PATH YES 32 | BUILD_WITH_INSTALL_RPATH NO 33 | BUILD_RPATH_USE_ORIGIN YES) 34 | 35 | target_link_libraries(hiredis-happ-test hiredis-happ ${ATFRAMEWORK_ATFRAME_UTILS_LINK_NAME} 36 | ${COMPILER_OPTION_EXTERN_CXX_LIBS}) 37 | 38 | add_test(NAME hiredis-happ-run-test COMMAND hiredis-happ-test) 39 | -------------------------------------------------------------------------------- /test/case/hiredis_happ_cluster_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "frame/test_macros.h" 9 | #include "hiredis_happ.h" 10 | 11 | static int happ_cluster_f = 0; 12 | static void on_connect_cbk_1(hiredis::happ::cluster *clu, hiredis::happ::connection *conn) { 13 | CASE_EXPECT_NE(nullptr, conn); 14 | CASE_EXPECT_NE(nullptr, clu); 15 | 16 | CASE_EXPECT_EQ(1, happ_cluster_f); 17 | ++happ_cluster_f; 18 | } 19 | 20 | static void on_connected_cbk_1(hiredis::happ::cluster *clu, hiredis::happ::connection *conn, 21 | const struct redisAsyncContext *c, int status) { 22 | CASE_EXPECT_NE(nullptr, conn); 23 | CASE_EXPECT_NE(nullptr, clu); 24 | 25 | CASE_EXPECT_EQ(2, happ_cluster_f); 26 | ++happ_cluster_f; 27 | } 28 | 29 | static void on_disconnected_cbk_1(hiredis::happ::cluster *clu, hiredis::happ::connection *conn, 30 | const struct redisAsyncContext *c, int status) { 31 | CASE_EXPECT_NE(nullptr, conn); 32 | CASE_EXPECT_NE(nullptr, clu); 33 | 34 | CASE_EXPECT_EQ(2, happ_cluster_f); 35 | ++happ_cluster_f; 36 | } 37 | 38 | CASE_TEST(happ_cluster, connection_callback) { 39 | hiredis::happ::cluster clu; 40 | 41 | clu.init("127.0.0.1", 6370); 42 | 43 | happ_cluster_f = 1; 44 | 45 | clu.set_on_connect(on_connect_cbk_1); 46 | clu.set_on_connected(on_connected_cbk_1); 47 | clu.set_on_disconnected(on_disconnected_cbk_1); 48 | 49 | clu.proc(1, 0); 50 | CASE_EXPECT_EQ(1, happ_cluster_f); 51 | clu.set_timeout(57); 52 | clu.start(); 53 | CASE_EXPECT_EQ(static_cast(1), clu.get_timer_actions().timer_conns.size()); 54 | CASE_EXPECT_TRUE("127.0.0.1:6370" == clu.get_timer_actions().timer_conns.front().name); 55 | CASE_EXPECT_EQ(static_cast(58), clu.get_timer_actions().timer_conns.front().timeout); 56 | CASE_EXPECT_EQ(2, happ_cluster_f); 57 | 58 | clu.proc(57, 0); 59 | CASE_EXPECT_EQ(2, happ_cluster_f); 60 | 61 | clu.proc(58, 0); 62 | CASE_EXPECT_EQ(3, happ_cluster_f); 63 | 64 | CASE_EXPECT_EQ(static_cast(0), clu.get_timer_actions().timer_conns.size()); 65 | CASE_EXPECT_EQ(static_cast(0), clu.get_connection_size()); 66 | 67 | clu.reset(); 68 | } 69 | -------------------------------------------------------------------------------- /test/case/hiredis_happ_cmd_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "frame/test_macros.h" 9 | #include "hiredis_happ.h" 10 | 11 | static void happ_cmd_basic_1(hiredis::happ::cmd_exec *cmd, struct redisAsyncContext *c, void *r, void *pridata) { 12 | CASE_EXPECT_EQ(cmd, pridata); 13 | CASE_EXPECT_EQ(c, r); 14 | 15 | CASE_EXPECT_NE(r, nullptr); 16 | CASE_EXPECT_NE(pridata, nullptr); 17 | 18 | CASE_EXPECT_EQ(131313131, *reinterpret_cast(cmd->buffer())); 19 | } 20 | 21 | CASE_TEST(happ_cmd, basic) { 22 | hiredis::happ::holder_t h; 23 | hiredis::happ::cluster clu; 24 | redisAsyncContext vir_ontext; 25 | h.clu = &clu; 26 | 27 | hiredis::happ::cmd_exec *cmd = hiredis::happ::cmd_exec::create(h, happ_cmd_basic_1, &clu, sizeof(int)); 28 | 29 | CASE_EXPECT_EQ(cmd->get_holder().clu, &clu); 30 | CASE_EXPECT_EQ(cmd->private_data(), &clu); 31 | CASE_EXPECT_EQ(cmd->get_callback_fn(), happ_cmd_basic_1); 32 | 33 | cmd->private_data(cmd); 34 | *reinterpret_cast(cmd->buffer()) = 131313131; 35 | 36 | int len = cmd->format("GET %s", "HERO"); 37 | CASE_EXPECT_EQ(static_cast(len), cmd->get_cmd_raw_content().raw_len); 38 | 39 | std::string cmd_content_1, cmd_content_2; 40 | cmd_content_1.assign(cmd->get_cmd_raw_content().content.raw, cmd->get_cmd_raw_content().raw_len); 41 | 42 | const char *argv[] = {"GET", "HERO"}; 43 | size_t argvlen[] = {strlen(argv[0]), strlen(argv[1])}; 44 | len = cmd->vformat(2, argv, argvlen); 45 | CASE_EXPECT_EQ(static_cast(0), cmd->get_cmd_raw_content().raw_len); 46 | 47 | cmd_content_2.assign(cmd->get_cmd_raw_content().content.redis_sds, static_cast(len)); 48 | CASE_EXPECT_EQ(static_cast(len), sdslen(cmd->get_cmd_raw_content().content.redis_sds)); 49 | CASE_EXPECT_EQ(cmd_content_1, cmd_content_2); 50 | 51 | int res = cmd->call_reply(hiredis::happ::error_code::REDIS_HAPP_TTL, &vir_ontext, &vir_ontext); 52 | CASE_EXPECT_EQ(res, hiredis::happ::error_code::REDIS_HAPP_OK); 53 | CASE_EXPECT_EQ(cmd->get_error_code(), hiredis::happ::error_code::REDIS_HAPP_TTL); 54 | 55 | CASE_EXPECT_EQ(cmd->get_callback_fn(), nullptr); 56 | 57 | res = cmd->call_reply(hiredis::happ::error_code::REDIS_HAPP_TTL, &vir_ontext, nullptr); 58 | CASE_EXPECT_EQ(res, hiredis::happ::error_code::REDIS_HAPP_OK); 59 | 60 | hiredis::happ::cmd_exec::destroy(cmd); 61 | } 62 | -------------------------------------------------------------------------------- /test/case/hiredis_happ_connection_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "frame/test_macros.h" 8 | #include "hiredis_happ.h" 9 | 10 | template 11 | struct AutoPtr { 12 | typedef T value_type; 13 | T* v; 14 | 15 | AutoPtr(T* v_) : v(v_) {} 16 | ~AutoPtr() { 17 | if (NULL != v) { 18 | delete v; 19 | } 20 | } 21 | 22 | AutoPtr(AutoPtr& other) { 23 | v = other.v; 24 | other.v = NULL; 25 | } 26 | 27 | AutoPtr& operator=(AutoPtr& other) { 28 | v = other.v; 29 | other.v = NULL; 30 | return (*this); 31 | } 32 | 33 | T* get() { return v; } 34 | const T* get() const { return v; } 35 | 36 | T* operator->() { return v; } 37 | const T* operator->() const { return v; } 38 | 39 | T& operator*() { return *v; } 40 | const T& operator*() const { return *v; } 41 | }; 42 | 43 | CASE_TEST(happ_connection, basic) { 44 | AutoPtr conn1(new hiredis::happ::connection()); 45 | AutoPtr conn2(new hiredis::happ::connection()); 46 | 47 | CASE_EXPECT_EQ(conn1->get_sequence() + 1, conn2->get_sequence()); 48 | 49 | hiredis::happ::holder_t h; 50 | redisAsyncContext vir_context; 51 | 52 | h.clu = nullptr; 53 | conn1->init(h, "127.0.0.2", 1234); 54 | conn2->init(h, "127.0.0.3", 1234); 55 | 56 | CASE_EXPECT_TRUE("127.0.0.2" == conn1->get_key().ip); 57 | CASE_EXPECT_TRUE(1234 == conn1->get_key().port); 58 | CASE_EXPECT_TRUE("127.0.0.2:1234" == conn1->get_key().name); 59 | 60 | CASE_EXPECT_EQ(hiredis::happ::connection::status::DISCONNECTED, conn1->get_status()); 61 | hiredis::happ::cmd_exec* cmd = hiredis::happ::cmd_exec::create(h, NULL, &vir_context, 0); 62 | 63 | int res = conn1->redis_cmd(cmd, NULL); 64 | CASE_EXPECT_EQ(hiredis::happ::error_code::REDIS_HAPP_CREATE, res); 65 | 66 | conn1->set_connecting(&vir_context); 67 | 68 | CASE_EXPECT_EQ(conn1->get_context(), &vir_context); 69 | CASE_EXPECT_EQ(hiredis::happ::connection::status::CONNECTING, conn1->get_status()); 70 | conn1->set_connected(); 71 | CASE_EXPECT_EQ(hiredis::happ::connection::status::CONNECTED, conn1->get_status()); 72 | 73 | CASE_EXPECT_EQ(conn1->get_context(), &vir_context); 74 | 75 | cmd->private_data(conn2.get()); 76 | conn2->set_connecting(&vir_context); 77 | 78 | conn1->release(false); 79 | conn2->release(false); 80 | 81 | CASE_EXPECT_EQ(conn1->get_context(), NULL); 82 | CASE_EXPECT_EQ(conn2->get_context(), NULL); 83 | 84 | hiredis::happ::cmd_exec::destroy(cmd); 85 | } 86 | -------------------------------------------------------------------------------- /test/test.happ-macro.cmake: -------------------------------------------------------------------------------- 1 | # =========== test =========== 2 | set(PROJECT_TEST_BAS_DIR ${CMAKE_CURRENT_LIST_DIR}) 3 | set(PROJECT_TEST_SRC_DIR ${PROJECT_TEST_BAS_DIR}) 4 | set(PROJECT_TEST_INC_DIR ${PROJECT_TEST_BAS_DIR}) 5 | 6 | include("${ATFRAMEWORK_ATFRAME_UTILS_REPO_DIR}/test/test.utils-macro.cmake") 7 | -------------------------------------------------------------------------------- /third_party/Repository.cmake: -------------------------------------------------------------------------------- 1 | include("${ATFRAMEWORK_CMAKE_TOOLSET_DIR}/ports/ssl/port.cmake") 2 | include("${ATFRAMEWORK_CMAKE_TOOLSET_DIR}/ports/redis/hiredis.cmake") 3 | --------------------------------------------------------------------------------