├── .github ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── .rubocop.yml ├── CHANGELOG.md ├── Gemfile ├── Gemfile.lock ├── LICENSE.md ├── README.md ├── Rakefile ├── benchmark ├── drivers.rb ├── drivers_ruby.md ├── drivers_yjit.md ├── pipelined.rb ├── pipelined_hiredis.md ├── pipelined_ruby.md ├── pipelined_yjit.md ├── profile_large_list.rb ├── profile_large_string.rb ├── profile_pipeline.rb ├── setup.rb ├── single.rb ├── single_hiredis.md ├── single_ruby.md └── single_yjit.md ├── bin ├── console ├── install-toxiproxy ├── megatest └── setup ├── hiredis-client ├── README.md ├── ext │ └── redis_client │ │ └── hiredis │ │ ├── extconf.rb │ │ ├── hiredis_connection.c │ │ └── vendor │ │ ├── .gitignore │ │ ├── .travis.yml │ │ ├── CHANGELOG.md │ │ ├── CMakeLists.txt │ │ ├── COPYING │ │ ├── Makefile │ │ ├── README.md │ │ ├── adapters │ │ ├── ae.h │ │ ├── glib.h │ │ ├── ivykis.h │ │ ├── libev.h │ │ ├── libevent.h │ │ ├── libuv.h │ │ ├── macosx.h │ │ └── qt.h │ │ ├── alloc.c │ │ ├── alloc.h │ │ ├── appveyor.yml │ │ ├── async.c │ │ ├── async.h │ │ ├── async_private.h │ │ ├── dict.c │ │ ├── dict.h │ │ ├── fmacros.h │ │ ├── hiredis-config.cmake.in │ │ ├── hiredis.c │ │ ├── hiredis.h │ │ ├── hiredis.pc.in │ │ ├── hiredis_ssl-config.cmake.in │ │ ├── hiredis_ssl.h │ │ ├── hiredis_ssl.pc.in │ │ ├── net.c │ │ ├── net.h │ │ ├── read.c │ │ ├── read.h │ │ ├── sds.c │ │ ├── sds.h │ │ ├── sdsalloc.h │ │ ├── sockcompat.c │ │ ├── sockcompat.h │ │ ├── ssl.c │ │ ├── test.c │ │ ├── test.sh │ │ └── win32.h ├── hiredis-client.gemspec └── lib │ ├── hiredis-client.rb │ └── redis_client │ └── hiredis_connection.rb ├── lib ├── redis-client.rb ├── redis_client.rb └── redis_client │ ├── circuit_breaker.rb │ ├── command_builder.rb │ ├── config.rb │ ├── connection_mixin.rb │ ├── decorator.rb │ ├── middlewares.rb │ ├── pid_cache.rb │ ├── pooled.rb │ ├── ruby_connection.rb │ ├── ruby_connection │ ├── buffered_io.rb │ └── resp3.rb │ ├── sentinel_config.rb │ ├── url_config.rb │ └── version.rb ├── redis-client.gemspec └── test ├── env.rb ├── fixtures ├── certs │ ├── ca.crt │ ├── ca.key │ ├── ca.txt │ ├── client.crt │ ├── client.key │ ├── openssl.cnf │ ├── redis.crt │ ├── redis.dh │ ├── redis.key │ ├── server.crt │ └── server.key └── generate-certs.sh ├── hiredis └── test_helper.rb ├── redis_client ├── circuit_breaker_test.rb ├── command_builder_test.rb ├── config_test.rb ├── connection_test.rb ├── decorator_test.rb ├── middlewares_test.rb ├── pooled_test.rb ├── ractor_test.rb ├── resp3_test.rb └── subscriptions_test.rb ├── redis_client_test.rb ├── sentinel ├── sentinel_test.rb └── test_helper.rb ├── shared └── redis_client_tests.rb ├── support ├── client_test_helper.rb ├── driver.rb ├── raise_warnings.rb ├── redis_builder.rb ├── server_manager.rb └── servers.rb ├── test_config.rb └── test_helper.rb /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | - package-ecosystem: "bundler" 8 | directory: "/" 9 | schedule: 10 | interval: "weekly" 11 | ignore: 12 | # redis is used for benchmarking, since redis 5.0 use 13 | # redis-client under the hood, there's no point comparing it. 14 | - dependency-name: "redis" 15 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Test 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | lint: 7 | name: Rubocop 8 | timeout-minutes: 15 9 | strategy: 10 | fail-fast: false 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Check out code 14 | uses: actions/checkout@v4 15 | - name: Set up Ruby 16 | uses: ruby/setup-ruby@v1 17 | with: 18 | ruby-version: "3.3" 19 | bundler-cache: true 20 | - name: Lint 21 | run: bundle exec rubocop 22 | 23 | ruby: 24 | name: Ruby ${{ matrix.ruby }} 25 | timeout-minutes: 15 26 | strategy: 27 | fail-fast: false 28 | matrix: 29 | os: ["ubuntu-latest"] 30 | redis: ["6.2"] 31 | ruby: ["ruby-head", "3.4", "3.3", "3.2", "3.1", "3.0", "2.7", "2.6", "jruby", "truffleruby"] 32 | runs-on: ${{ matrix.os }} 33 | steps: 34 | - name: Check out code 35 | uses: actions/checkout@v4 36 | - name: Set up Ruby 37 | uses: ruby/setup-ruby@v1 38 | with: 39 | ruby-version: ${{ matrix.ruby }} 40 | bundler-cache: true 41 | - name: Cache redis build 42 | uses: actions/cache@v4 43 | with: 44 | path: tmp/cache 45 | key: "local-tmp-cache-${{ matrix.redis }}-on-${{ matrix.os }}" 46 | - name: Lower system timeout 47 | run: sudo sysctl -w net.ipv4.tcp_syn_retries=2 48 | - name: Test 49 | run: | 50 | bundle exec rake ci 51 | env: 52 | EXT_PEDANTIC: "1" 53 | REDIS: ${{ matrix.redis }} 54 | 55 | redis: 56 | name: Redis ${{ matrix.redis }} 57 | timeout-minutes: 15 58 | strategy: 59 | fail-fast: false 60 | matrix: 61 | os: ["ubuntu-latest"] 62 | redis: ["7.0", "7.2"] 63 | ruby: ["3.2"] 64 | runs-on: ${{ matrix.os }} 65 | steps: 66 | - name: Check out code 67 | uses: actions/checkout@v4 68 | - name: Set up Ruby 69 | uses: ruby/setup-ruby@v1 70 | with: 71 | ruby-version: ${{ matrix.ruby }} 72 | bundler-cache: true 73 | - name: Cache redis build 74 | uses: actions/cache@v4 75 | with: 76 | path: tmp/cache 77 | key: "local-tmp-cache-${{ matrix.redis }}-on-${{ matrix.os }}" 78 | - name: Lower system timeout 79 | run: sudo sysctl -w net.ipv4.tcp_syn_retries=2 80 | - name: Test 81 | run: | 82 | bundle exec rake ci 83 | env: 84 | EXT_PEDANTIC: "1" 85 | REDIS: ${{ matrix.redis }} 86 | 87 | # Redis sentinel is super slow to setup nad very flaky 88 | # So we run them independently against a single set of versions 89 | # so that they're easier to retry and less likely to flake. 90 | sentinel: 91 | name: Sentinel 92 | timeout-minutes: 15 93 | strategy: 94 | fail-fast: false 95 | matrix: 96 | os: ["ubuntu-latest"] 97 | redis: ["6.2"] 98 | ruby: ["3.1"] 99 | runs-on: ${{ matrix.os }} 100 | steps: 101 | - name: Check out code 102 | uses: actions/checkout@v4 103 | - name: Set up Ruby 104 | uses: ruby/setup-ruby@v1 105 | with: 106 | ruby-version: ${{ matrix.ruby }} 107 | bundler-cache: true 108 | - name: Cache redis build 109 | uses: actions/cache@v4 110 | with: 111 | path: tmp/cache 112 | key: "local-tmp-cache-${{ matrix.redis }}-on-${{ matrix.os }}" 113 | - name: Lower system timeout 114 | run: sudo sysctl -w net.ipv4.tcp_syn_retries=2 115 | - name: Test 116 | run: | 117 | bundle exec rake test:sentinel 118 | env: 119 | EXT_PEDANTIC: "1" 120 | REDIS: ${{ matrix.redis }} 121 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /_yardoc/ 4 | /coverage/ 5 | /doc/ 6 | /pkg/ 7 | /hiredis-client/pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | /log/ 11 | 12 | /hiredis-client/tmp/ 13 | /bin/toxiproxy-server 14 | *.so 15 | *.bundle 16 | *.dll 17 | *.rdb 18 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | TargetRubyVersion: 2.6 3 | NewCops: enable 4 | SuggestExtensions: false 5 | 6 | Bundler/OrderedGems: 7 | Enabled: false 8 | 9 | Layout/LineLength: 10 | Max: 120 11 | Exclude: 12 | - 'test/**/*' 13 | - 'benchmark/**/*' 14 | 15 | Layout/CaseIndentation: 16 | EnforcedStyle: end 17 | 18 | Layout/LineEndStringConcatenationIndentation: 19 | EnforcedStyle: indented 20 | 21 | Layout/ArgumentAlignment: 22 | EnforcedStyle: with_fixed_indentation 23 | 24 | Lint/RescueException: 25 | Enabled: false 26 | 27 | Lint/SuppressedException: 28 | Enabled: false 29 | 30 | Lint/AssignmentInCondition: 31 | Enabled: false 32 | 33 | Lint/UnifiedInteger: 34 | Enabled: false 35 | 36 | Lint/UnderscorePrefixedVariableName: 37 | Enabled: false 38 | 39 | Lint/EmptyBlock: 40 | Enabled: false 41 | 42 | Lint/ShadowedException: 43 | Enabled: false 44 | 45 | Lint/DuplicateBranch: 46 | Enabled: false 47 | 48 | Lint/MissingSuper: 49 | Enabled: false 50 | 51 | Metrics/ClassLength: 52 | Enabled: false 53 | 54 | Metrics/CyclomaticComplexity: 55 | Enabled: false 56 | 57 | Metrics/AbcSize: 58 | Enabled: false 59 | 60 | Metrics/BlockLength: 61 | Enabled: false 62 | 63 | Metrics/MethodLength: 64 | Enabled: false 65 | 66 | Metrics/ModuleLength: 67 | Enabled: false 68 | 69 | Metrics/ParameterLists: 70 | Enabled: false 71 | 72 | Metrics/PerceivedComplexity: 73 | Enabled: false 74 | 75 | Style/InfiniteLoop: 76 | Enabled: false 77 | 78 | Style/WhileUntilModifier: 79 | Enabled: false 80 | 81 | Style/Alias: 82 | EnforcedStyle: prefer_alias_method 83 | 84 | Style/PercentLiteralDelimiters: 85 | Enabled: false 86 | 87 | Style/ParallelAssignment: 88 | Enabled: false 89 | 90 | Style/NumericPredicate: 91 | Enabled: false 92 | 93 | Style/NumericLiterals: 94 | Enabled: false 95 | 96 | Style/IfUnlessModifier: 97 | Enabled: false 98 | 99 | Style/SignalException: 100 | Exclude: 101 | - 'lib/redis/connection/synchrony.rb' 102 | 103 | Style/StringLiterals: 104 | Enabled: false 105 | 106 | Style/DoubleNegation: 107 | Enabled: false 108 | 109 | Style/MultipleComparison: 110 | Enabled: false 111 | 112 | Style/GuardClause: 113 | Enabled: false 114 | 115 | Style/Semicolon: 116 | Enabled: false 117 | 118 | Style/Documentation: 119 | Enabled: false 120 | 121 | Style/FormatStringToken: 122 | Enabled: false 123 | 124 | Style/FormatString: 125 | Enabled: false 126 | 127 | Style/RescueStandardError: 128 | Enabled: false 129 | 130 | Style/WordArray: 131 | Enabled: false 132 | 133 | Style/TrailingCommaInArrayLiteral: 134 | EnforcedStyleForMultiline: consistent_comma 135 | 136 | Style/TrailingCommaInHashLiteral: 137 | EnforcedStyleForMultiline: consistent_comma 138 | 139 | Style/TrailingCommaInArguments: 140 | EnforcedStyleForMultiline: consistent_comma 141 | 142 | Lint/NonLocalExitFromIterator: 143 | Enabled: false 144 | 145 | Layout/FirstArrayElementIndentation: 146 | EnforcedStyle: consistent 147 | 148 | Layout/FirstHashElementIndentation: 149 | EnforcedStyle: consistent 150 | 151 | Layout/EndAlignment: 152 | EnforcedStyleAlignWith: variable 153 | 154 | Layout/ElseAlignment: 155 | Enabled: false 156 | 157 | Layout/RescueEnsureAlignment: 158 | Enabled: false 159 | 160 | Naming/HeredocDelimiterNaming: 161 | Enabled: false 162 | 163 | Naming/FileName: 164 | Enabled: false 165 | 166 | Naming/RescuedExceptionsVariableName: 167 | Enabled: false 168 | 169 | Naming/AccessorMethodName: 170 | Exclude: 171 | - lib/redis/connection/ruby.rb 172 | 173 | Naming/MethodParameterName: 174 | Enabled: false 175 | 176 | Metrics/BlockNesting: 177 | Enabled: false 178 | 179 | Style/HashTransformValues: 180 | Enabled: false 181 | 182 | Style/FetchEnvVar: 183 | Enabled: false 184 | 185 | Style/SymbolProc: 186 | Exclude: 187 | - 'test/**/*' 188 | 189 | Style/SoleNestedConditional: 190 | Enabled: false 191 | 192 | Style/GlobalVars: 193 | Exclude: 194 | - '**/extconf.rb' 195 | - bin/console 196 | 197 | Gemspec/RequireMFA: 198 | Enabled: false 199 | 200 | Style/StderrPuts: 201 | Enabled: false 202 | 203 | Style/ModuleFunction: 204 | Enabled: false 205 | 206 | Style/CombinableLoops: 207 | Enabled: false 208 | 209 | Style/DocumentDynamicEvalDefinition: 210 | Enabled: false 211 | 212 | Style/CaseLikeIf: 213 | Enabled: false 214 | 215 | Style/EmptyMethod: 216 | Enabled: false 217 | 218 | Style/AccessModifierDeclarations: 219 | Enabled: false 220 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | # Specify your gem's dependencies in redis-client.gemspec 6 | gemspec name: "redis-client" 7 | 8 | gem "megatest" 9 | gem "rake", "~> 13.3" 10 | gem "rake-compiler" 11 | gem "rubocop" 12 | gem "rubocop-minitest" 13 | gem "toxiproxy" 14 | gem "benchmark" 15 | 16 | group :benchmark do 17 | gem "benchmark-ips" 18 | gem "hiredis" 19 | gem "redis", "~> 4.6" 20 | gem "cgi", ">= 0.5.0" # For Redis 4.x 21 | gem "stackprof", platform: :mri 22 | end 23 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | redis-client (0.25.0) 5 | connection_pool 6 | 7 | GEM 8 | remote: https://rubygems.org/ 9 | specs: 10 | ast (2.4.2) 11 | benchmark (0.4.1) 12 | benchmark-ips (2.14.0) 13 | cgi (0.5.0) 14 | cgi (0.5.0-java) 15 | connection_pool (2.5.3) 16 | hiredis (0.6.3) 17 | hiredis (0.6.3-java) 18 | json (2.7.6) 19 | json (2.7.6-java) 20 | megatest (0.3.0) 21 | parallel (1.24.0) 22 | parser (3.3.0.5) 23 | ast (~> 2.4.1) 24 | racc 25 | racc (1.7.3) 26 | racc (1.7.3-java) 27 | rainbow (3.1.1) 28 | rake (13.3.0) 29 | rake-compiler (1.3.0) 30 | rake 31 | redis (4.6.0) 32 | regexp_parser (2.9.0) 33 | rexml (3.3.9) 34 | rubocop (1.50.2) 35 | json (~> 2.3) 36 | parallel (~> 1.10) 37 | parser (>= 3.2.0.0) 38 | rainbow (>= 2.2.2, < 4.0) 39 | regexp_parser (>= 1.8, < 3.0) 40 | rexml (>= 3.2.5, < 4.0) 41 | rubocop-ast (>= 1.28.0, < 2.0) 42 | ruby-progressbar (~> 1.7) 43 | unicode-display_width (>= 2.4.0, < 3.0) 44 | rubocop-ast (1.30.0) 45 | parser (>= 3.2.1.0) 46 | rubocop-minitest (0.30.0) 47 | rubocop (>= 1.39, < 2.0) 48 | ruby-progressbar (1.13.0) 49 | stackprof (0.2.27) 50 | toxiproxy (2.0.2) 51 | unicode-display_width (2.5.0) 52 | 53 | PLATFORMS 54 | ruby 55 | universal-java-18 56 | x86_64-darwin-20 57 | x86_64-linux 58 | 59 | DEPENDENCIES 60 | benchmark 61 | benchmark-ips 62 | cgi (>= 0.5.0) 63 | hiredis 64 | megatest 65 | rake (~> 13.3) 66 | rake-compiler 67 | redis (~> 4.6) 68 | redis-client! 69 | rubocop 70 | rubocop-minitest 71 | stackprof 72 | toxiproxy 73 | 74 | BUNDLED WITH 75 | 2.4.22 76 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2022 Shopify 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 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rake/extensiontask" 4 | require 'rubocop/rake_task' 5 | 6 | RuboCop::RakeTask.new 7 | 8 | require "rake/clean" 9 | CLOBBER.include "pkg" 10 | require "bundler/gem_helper" 11 | Bundler::GemHelper.install_tasks(name: "redis-client") 12 | Bundler::GemHelper.install_tasks(dir: "hiredis-client", name: "hiredis-client") 13 | 14 | gemspec = Gem::Specification.load("redis-client.gemspec") 15 | Rake::ExtensionTask.new do |ext| 16 | ext.name = "hiredis_connection" 17 | ext.ext_dir = "hiredis-client/ext/redis_client/hiredis" 18 | ext.lib_dir = "hiredis-client/lib/redis_client" 19 | ext.gem_spec = gemspec 20 | CLEAN.add("#{ext.ext_dir}/vendor/*.{a,o}") 21 | end 22 | 23 | require "megatest/test_task" 24 | 25 | namespace :test do 26 | jobs = ENV["JOBS"] || "4" 27 | jobs = jobs && Process.respond_to?(:fork) ? ["-j", jobs] : [] 28 | extra_args = ["--max-retries", "1"] + jobs 29 | 30 | Megatest::TestTask.create(:ruby) do |t| 31 | t.tests = FileList["test/**/*_test.rb"].exclude("test/sentinel/*_test.rb") 32 | t.extra_args = extra_args 33 | end 34 | 35 | Megatest::TestTask.create(:hiredis) do |t| 36 | t.libs << "test/hiredis" 37 | t.libs << "hiredis-client/lib" 38 | t.tests = FileList["test/hiredis/test_helper.rb"] + FileList["test/**/*_test.rb"].exclude("test/sentinel/*_test.rb") 39 | t.extra_args = extra_args 40 | t.deps << :compile 41 | end 42 | 43 | Megatest::TestTask.create(:sentinel) do |t| 44 | t.libs << "test/sentinel" 45 | t.tests = ["test/sentinel"] 46 | t.extra_args = extra_args 47 | end 48 | end 49 | 50 | hiredis_supported = RUBY_ENGINE == "ruby" && !RUBY_PLATFORM.match?(/mswin/) 51 | if hiredis_supported 52 | task test: %i[test:ruby test:hiredis test:sentinel] 53 | else 54 | task test: %i[test:ruby test:sentinel] 55 | end 56 | 57 | namespace :hiredis do 58 | task :download do 59 | version = "1.0.2" 60 | archive_path = "tmp/hiredis-#{version}.tar.gz" 61 | url = "https://github.com/redis/hiredis/archive/refs/tags/v#{version}.tar.gz" 62 | system("curl", "-L", url, out: archive_path) or raise "Downloading of #{url} failed" 63 | system("rm", "-rf", "hiredis-client/ext/redis_client/hiredis/vendor/") 64 | system("mkdir", "-p", "hiredis-client/ext/redis_client/hiredis/vendor/") 65 | system( 66 | "tar", "xvzf", archive_path, 67 | "-C", "hiredis-client/ext/redis_client/hiredis/vendor", 68 | "--strip-components", "1", 69 | ) 70 | system("rm", "-rf", "hiredis-client/ext/redis_client/hiredis/vendor/examples") 71 | end 72 | end 73 | 74 | benchmark_suites = %w(single pipelined drivers) 75 | benchmark_modes = %i[ruby yjit hiredis] 76 | namespace :benchmark do 77 | benchmark_suites.each do |suite| 78 | benchmark_modes.each do |mode| 79 | next if suite == "drivers" && mode == :hiredis 80 | 81 | name = "#{suite}_#{mode}" 82 | desc name 83 | task name do 84 | output_path = "benchmark/#{name}.md" 85 | sh "rm", "-f", output_path 86 | File.open(output_path, "w+") do |output| 87 | output.puts("ruby: `#{RUBY_DESCRIPTION}`\n\n") 88 | output.puts("redis-server: `#{`redis-server -v`.strip}`\n\n") 89 | output.puts 90 | output.flush 91 | env = {} 92 | args = [] 93 | args << "--yjit" if mode == :yjit 94 | env["DRIVER"] = mode == :hiredis ? "hiredis" : "ruby" 95 | system(env, RbConfig.ruby, *args, "benchmark/#{suite}.rb", out: output) 96 | end 97 | 98 | skipping = false 99 | output = File.readlines(output_path).reject do |line| 100 | if skipping 101 | if line == "Comparison:\n" 102 | skipping = false 103 | true 104 | else 105 | skipping 106 | end 107 | else 108 | skipping = true if line.start_with?("Warming up ---") 109 | skipping 110 | end 111 | end 112 | File.write(output_path, output.join) 113 | end 114 | end 115 | end 116 | 117 | task all: benchmark_suites.flat_map { |s| benchmark_modes.flat_map { |m| "#{s}_#{m}" } } 118 | end 119 | 120 | if hiredis_supported 121 | task default: %i[compile test rubocop] 122 | task ci: %i[compile test:ruby test:hiredis] 123 | else 124 | task default: %i[test rubocop] 125 | task ci: %i[test:ruby] 126 | end 127 | -------------------------------------------------------------------------------- /benchmark/drivers.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "setup" 4 | 5 | ruby = RedisClient.new(host: "localhost", port: Servers::REDIS.real_port, driver: :ruby) 6 | hiredis = RedisClient.new(host: "localhost", port: Servers::REDIS.real_port, driver: :hiredis) 7 | 8 | ruby.call("SET", "key", "value") 9 | ruby.call("SET", "large", "value" * 10_000) 10 | ruby.call("LPUSH", "list", *5.times.to_a) 11 | ruby.call("LPUSH", "large-list", *1000.times.to_a) 12 | ruby.call("HMSET", "hash", *8.times.to_a) 13 | ruby.call("HMSET", "large-hash", *1000.times.to_a) 14 | 15 | benchmark("small string x 100") do |x| 16 | x.report("hiredis") { hiredis.pipelined { |p| 100.times { p.call("GET", "key") } } } 17 | x.report("ruby") { ruby.pipelined { |p| 100.times { p.call("GET", "key") } } } 18 | end 19 | 20 | benchmark("large string") do |x| 21 | x.report("hiredis") { hiredis.call("GET", "large") } 22 | x.report("ruby") { ruby.call("GET", "large") } 23 | end 24 | 25 | benchmark("small list x 100") do |x| 26 | x.report("hiredis") { hiredis.pipelined { |p| 100.times { p.call("LRANGE", "list", 0, -1) } } } 27 | x.report("ruby") { ruby.pipelined { |p| 100.times { p.call("LRANGE", "list", 0, -1) } } } 28 | end 29 | 30 | benchmark("large list") do |x| 31 | x.report("hiredis") { hiredis.call("LRANGE", "large-list", 0, -1) } 32 | x.report("ruby") { ruby.call("LRANGE", "large-list", 0, -1) } 33 | end 34 | 35 | benchmark("small hash x 100") do |x| 36 | x.report("hiredis") { hiredis.pipelined { |p| 100.times { p.call("HGETALL", "hash") } } } 37 | x.report("ruby") { ruby.pipelined { |p| 100.times { p.call("HGETALL", "hash") } } } 38 | end 39 | 40 | benchmark("large hash") do |x| 41 | x.report("hiredis") { ruby.call("HGETALL", "large-hash") } 42 | x.report("ruby") { ruby.call("HGETALL", "large-hash") } 43 | end 44 | -------------------------------------------------------------------------------- /benchmark/drivers_ruby.md: -------------------------------------------------------------------------------- 1 | ruby: `ruby 3.4.0dev (2024-03-19T14:18:56Z master 5c2937733c) [arm64-darwin23]` 2 | 3 | redis-server: `Redis server v=7.0.12 sha=00000000:0 malloc=libc bits=64 build=a11d0151eabf466c` 4 | 5 | 6 | ### small string x 100 7 | 8 | ``` 9 | ruby 3.4.0dev (2024-03-19T14:18:56Z master 5c2937733c) [arm64-darwin23] 10 | hiredis: 5239.0 i/s 11 | ruby: 2957.4 i/s - 1.77x slower 12 | 13 | ``` 14 | 15 | ### large string 16 | 17 | ``` 18 | ruby 3.4.0dev (2024-03-19T14:18:56Z master 5c2937733c) [arm64-darwin23] 19 | hiredis: 12784.9 i/s 20 | ruby: 14948.2 i/s - same-ish: difference falls within error 21 | 22 | ``` 23 | 24 | ### small list x 100 25 | 26 | ``` 27 | ruby 3.4.0dev (2024-03-19T14:18:56Z master 5c2937733c) [arm64-darwin23] 28 | hiredis: 2599.3 i/s 29 | ruby: 1300.0 i/s - 2.00x slower 30 | 31 | ``` 32 | 33 | ### large list 34 | 35 | ``` 36 | ruby 3.4.0dev (2024-03-19T14:18:56Z master 5c2937733c) [arm64-darwin23] 37 | hiredis: 6836.0 i/s 38 | ruby: 1867.6 i/s - 3.66x slower 39 | 40 | ``` 41 | 42 | ### small hash x 100 43 | 44 | ``` 45 | ruby 3.4.0dev (2024-03-19T14:18:56Z master 5c2937733c) [arm64-darwin23] 46 | hiredis: 3392.5 i/s 47 | ruby: 1408.9 i/s - 2.41x slower 48 | 49 | ``` 50 | 51 | ### large hash 52 | 53 | ``` 54 | ruby 3.4.0dev (2024-03-19T14:18:56Z master 5c2937733c) [arm64-darwin23] 55 | hiredis: 1786.2 i/s 56 | ruby: 1811.9 i/s - same-ish: difference falls within error 57 | 58 | ``` 59 | 60 | -------------------------------------------------------------------------------- /benchmark/drivers_yjit.md: -------------------------------------------------------------------------------- 1 | ruby: `ruby 3.4.0dev (2024-03-19T14:18:56Z master 5c2937733c) [arm64-darwin23]` 2 | 3 | redis-server: `Redis server v=7.0.12 sha=00000000:0 malloc=libc bits=64 build=a11d0151eabf466c` 4 | 5 | 6 | ### small string x 100 7 | 8 | ``` 9 | ruby 3.4.0dev (2024-03-19T14:18:56Z master 5c2937733c) +YJIT [arm64-darwin23] 10 | hiredis: 7148.9 i/s 11 | ruby: 5758.6 i/s - 1.24x slower 12 | 13 | ``` 14 | 15 | ### large string 16 | 17 | ``` 18 | ruby 3.4.0dev (2024-03-19T14:18:56Z master 5c2937733c) +YJIT [arm64-darwin23] 19 | hiredis: 13023.5 i/s 20 | ruby: 20246.4 i/s - 1.55x faster 21 | 22 | ``` 23 | 24 | ### small list x 100 25 | 26 | ``` 27 | ruby 3.4.0dev (2024-03-19T14:18:56Z master 5c2937733c) +YJIT [arm64-darwin23] 28 | hiredis: 3973.6 i/s 29 | ruby: 2668.7 i/s - 1.49x slower 30 | 31 | ``` 32 | 33 | ### large list 34 | 35 | ``` 36 | ruby 3.4.0dev (2024-03-19T14:18:56Z master 5c2937733c) +YJIT [arm64-darwin23] 37 | hiredis: 6706.8 i/s 38 | ruby: 6529.3 i/s - same-ish: difference falls within error 39 | 40 | ``` 41 | 42 | ### small hash x 100 43 | 44 | ``` 45 | ruby 3.4.0dev (2024-03-19T14:18:56Z master 5c2937733c) +YJIT [arm64-darwin23] 46 | hiredis: 4001.6 i/s 47 | ruby: 3482.9 i/s - 1.15x slower 48 | 49 | ``` 50 | 51 | ### large hash 52 | 53 | ``` 54 | ruby 3.4.0dev (2024-03-19T14:18:56Z master 5c2937733c) +YJIT [arm64-darwin23] 55 | hiredis: 5511.9 i/s 56 | ruby: 5555.7 i/s - same-ish: difference falls within error 57 | 58 | ``` 59 | 60 | -------------------------------------------------------------------------------- /benchmark/pipelined.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "setup" 4 | 5 | driver = ENV.fetch("DRIVER", "ruby").to_sym 6 | redis_client = RedisClient.new(host: "localhost", port: Servers::REDIS.real_port, driver: driver) 7 | redis = Redis.new(host: "localhost", port: Servers::REDIS.real_port, driver: driver) 8 | 9 | redis_client.call("SET", "key", "value") 10 | redis_client.call("SET", "large", "value" * 10_000) 11 | redis_client.call("LPUSH", "list", *5.times.to_a) 12 | redis_client.call("LPUSH", "large-list", *1000.times.to_a) 13 | redis_client.call("HMSET", "hash", *8.times.to_a) 14 | redis_client.call("HMSET", "large-hash", *1000.times.to_a) 15 | 16 | benchmark("small string") do |x| 17 | x.report("redis-rb") { redis.pipelined { |p| 100.times { p.get("key") } } } 18 | x.report("redis-client") { redis_client.pipelined { |p| 100.times { p.call("GET", "key") } } } 19 | end 20 | 21 | benchmark("large string") do |x| 22 | x.report("redis-rb") { redis.pipelined { |p| 100.times { p.get("large") } }.each(&:valid_encoding?) } 23 | x.report("redis-client") { redis_client.pipelined { |p| 100.times { p.call("GET", "large") } }.each(&:valid_encoding?) } 24 | end 25 | 26 | benchmark("small list") do |x| 27 | x.report("redis-rb") { redis.pipelined { |p| 100.times { p.lrange("list", 0, -1) } } } 28 | x.report("redis-client") { redis_client.pipelined { |p| 100.times { p.call("LRANGE", "list", 0, -1) } } } 29 | end 30 | 31 | benchmark("large list") do |x| 32 | x.report("redis-rb") { redis.pipelined { |p| 100.times { p.lrange("large-list", 0, -1) } } } 33 | x.report("redis-client") { redis_client.pipelined { |p| 100.times { p.call("LRANGE", "large-list", 0, -1) } } } 34 | end 35 | 36 | benchmark("small hash") do |x| 37 | x.report("redis-rb") { redis.pipelined { |p| 100.times { p.hgetall("hash") } } } 38 | x.report("redis-client") { redis_client.pipelined { |p| 100.times { p.call("HGETALL", "hash") } } } 39 | end 40 | 41 | benchmark("large hash") do |x| 42 | x.report("redis-rb") { redis.pipelined { |p| 100.times { p.hgetall("large-hash") } } } 43 | x.report("redis-client") { redis_client.pipelined { |p| 100.times { p.call("HGETALL", "large-hash") } } } 44 | end 45 | -------------------------------------------------------------------------------- /benchmark/pipelined_hiredis.md: -------------------------------------------------------------------------------- 1 | ruby: `ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [arm64-darwin23]` 2 | 3 | redis-server: `Redis server v=7.0.12 sha=00000000:0 malloc=libc bits=64 build=a11d0151eabf466c` 4 | 5 | 6 | ### small string 7 | 8 | ``` 9 | ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [arm64-darwin23] 10 | redis-rb: 5438.6 i/s 11 | redis-client: 5552.9 i/s - same-ish: difference falls within error 12 | 13 | ``` 14 | 15 | ### large string 16 | 17 | ``` 18 | ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [arm64-darwin23] 19 | redis-rb: 354.4 i/s 20 | redis-client: 310.9 i/s - 1.14x slower 21 | 22 | ``` 23 | 24 | ### small list 25 | 26 | ``` 27 | ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [arm64-darwin23] 28 | redis-rb: 3081.4 i/s 29 | redis-client: 2733.0 i/s - 1.13x slower 30 | 31 | ``` 32 | 33 | ### large list 34 | 35 | ``` 36 | ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [arm64-darwin23] 37 | redis-rb: 82.3 i/s 38 | redis-client: 65.0 i/s - 1.26x slower 39 | 40 | ``` 41 | 42 | ### small hash 43 | 44 | ``` 45 | ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [arm64-darwin23] 46 | redis-rb: 2249.0 i/s 47 | redis-client: 3117.0 i/s - 1.39x faster 48 | 49 | ``` 50 | 51 | ### large hash 52 | 53 | ``` 54 | ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [arm64-darwin23] 55 | redis-rb: 46.9 i/s 56 | redis-client: 67.5 i/s - 1.44x faster 57 | 58 | ``` 59 | 60 | -------------------------------------------------------------------------------- /benchmark/pipelined_ruby.md: -------------------------------------------------------------------------------- 1 | ruby: `ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [arm64-darwin23]` 2 | 3 | redis-server: `Redis server v=7.0.12 sha=00000000:0 malloc=libc bits=64 build=a11d0151eabf466c` 4 | 5 | 6 | ### small string 7 | 8 | ``` 9 | ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [arm64-darwin23] 10 | redis-rb: 1212.6 i/s 11 | redis-client: 3007.1 i/s - 2.48x faster 12 | 13 | ``` 14 | 15 | ### large string 16 | 17 | ``` 18 | ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [arm64-darwin23] 19 | redis-rb: 238.9 i/s 20 | redis-client: 306.1 i/s - 1.28x faster 21 | 22 | ``` 23 | 24 | ### small list 25 | 26 | ``` 27 | ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [arm64-darwin23] 28 | redis-rb: 604.3 i/s 29 | redis-client: 1190.9 i/s - 1.97x faster 30 | 31 | ``` 32 | 33 | ### large list 34 | 35 | ``` 36 | ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [arm64-darwin23] 37 | redis-rb: 2.3 i/s 38 | redis-client: 14.2 i/s - 6.16x faster 39 | 40 | ``` 41 | 42 | ### small hash 43 | 44 | ``` 45 | ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [arm64-darwin23] 46 | redis-rb: 401.8 i/s 47 | redis-client: 1123.9 i/s - 2.80x faster 48 | 49 | ``` 50 | 51 | ### large hash 52 | 53 | ``` 54 | ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [arm64-darwin23] 55 | redis-rb: 2.3 i/s 56 | redis-client: 14.5 i/s - 6.41x faster 57 | 58 | ``` 59 | 60 | -------------------------------------------------------------------------------- /benchmark/pipelined_yjit.md: -------------------------------------------------------------------------------- 1 | ruby: `ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [arm64-darwin23]` 2 | 3 | redis-server: `Redis server v=7.0.12 sha=00000000:0 malloc=libc bits=64 build=a11d0151eabf466c` 4 | 5 | 6 | ### small string 7 | 8 | ``` 9 | ruby 3.3.0 (2023-12-25 revision 5124f9ac75) +YJIT [arm64-darwin23] 10 | redis-rb: 1460.6 i/s 11 | redis-client: 5345.6 i/s - 3.66x faster 12 | 13 | ``` 14 | 15 | ### large string 16 | 17 | ``` 18 | ruby 3.3.0 (2023-12-25 revision 5124f9ac75) +YJIT [arm64-darwin23] 19 | redis-rb: 272.9 i/s 20 | redis-client: 338.7 i/s - 1.24x faster 21 | 22 | ``` 23 | 24 | ### small list 25 | 26 | ``` 27 | ruby 3.3.0 (2023-12-25 revision 5124f9ac75) +YJIT [arm64-darwin23] 28 | redis-rb: 732.6 i/s 29 | redis-client: 1985.5 i/s - 2.71x faster 30 | 31 | ``` 32 | 33 | ### large list 34 | 35 | ``` 36 | ruby 3.3.0 (2023-12-25 revision 5124f9ac75) +YJIT [arm64-darwin23] 37 | redis-rb: 2.6 i/s 38 | redis-client: 31.7 i/s - 12.33x faster 39 | 40 | ``` 41 | 42 | ### small hash 43 | 44 | ``` 45 | ruby 3.3.0 (2023-12-25 revision 5124f9ac75) +YJIT [arm64-darwin23] 46 | redis-rb: 471.1 i/s 47 | redis-client: 2190.7 i/s - 4.65x faster 48 | 49 | ``` 50 | 51 | ### large hash 52 | 53 | ``` 54 | ruby 3.3.0 (2023-12-25 revision 5124f9ac75) +YJIT [arm64-darwin23] 55 | redis-rb: 2.5 i/s 56 | redis-client: 30.9 i/s - 12.19x faster 57 | 58 | ``` 59 | 60 | -------------------------------------------------------------------------------- /benchmark/profile_large_list.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "setup" 4 | require "stackprof" 5 | 6 | driver = ENV.fetch("DRIVER", "ruby").to_sym 7 | redis_client = RedisClient.new(host: "localhost", port: Servers::REDIS.real_port, driver: driver) 8 | redis_client.call("LPUSH", "list", *1000.times.to_a) 9 | 10 | StackProf.run(out: "tmp/stackprof-large-list.dump", raw: true) do 11 | 1_000.times do 12 | redis_client.call("LRANGE", "list", 0, -1) 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /benchmark/profile_large_string.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "setup" 4 | require "stackprof" 5 | 6 | driver = ENV.fetch("DRIVER", "ruby").to_sym 7 | redis_client = RedisClient.new(host: "localhost", port: Servers::REDIS.real_port, driver: driver) 8 | redis_client.call("SET", "large", "value" * 10_000) 9 | 10 | StackProf.run(out: "tmp/stackprof-large-string.dump", raw: true) do 11 | 1_000.times do 12 | redis_client.call("GET", "large") 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /benchmark/profile_pipeline.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "setup" 4 | require "stackprof" 5 | 6 | driver = ENV.fetch("DRIVER", "ruby").to_sym 7 | redis_client = RedisClient.new(host: "localhost", port: Servers::REDIS.real_port, driver: driver) 8 | 9 | StackProf.run(out: "tmp/stackprof-pipeline.dump", raw: true) do 10 | 10_000.times do 11 | redis_client.pipelined do |pipeline| 12 | pipeline.call("SET", "foo", "bar") 13 | pipeline.call("GET", "foo") 14 | pipeline.call("INCRBY", "counter", 2) 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /benchmark/setup.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | $LOAD_PATH.unshift(File.expand_path("../lib", __dir__)) 4 | $LOAD_PATH.unshift(File.expand_path("../hiredis-client/lib", __dir__)) 5 | $LOAD_PATH.unshift(File.expand_path("../test/support", __dir__)) 6 | 7 | require "redis" 8 | require "redis-client" 9 | require "hiredis-client" 10 | require "servers" 11 | require "benchmark/ips" 12 | 13 | Servers::BENCHMARK.prepare 14 | at_exit { Servers::BENCHMARK.shutdown } 15 | 16 | class RedisBenchmark 17 | def initialize(x) 18 | @x = x 19 | end 20 | 21 | def report(name, &block) 22 | @x.report(name, &block) 23 | end 24 | end 25 | 26 | def benchmark(name) 27 | if $stdout.tty? 28 | puts "=== #{name} ===" 29 | else 30 | puts "### #{name}\n\n```" 31 | end 32 | 33 | Benchmark.ips do |x| 34 | yield RedisBenchmark.new(x) 35 | x.compare!(order: :baseline) 36 | end 37 | 38 | unless $stdout.tty? 39 | puts "```\n\n" 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /benchmark/single.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "setup" 4 | 5 | driver = ENV.fetch("DRIVER", "ruby").to_sym 6 | redis_client = RedisClient.new(host: "localhost", port: Servers::REDIS.real_port, driver: driver) 7 | redis = Redis.new(host: "localhost", port: Servers::REDIS.real_port, driver: driver) 8 | 9 | redis_client.call("SET", "key", "value") 10 | redis_client.call("SET", "large", "value" * 10_000) 11 | redis_client.call("LPUSH", "list", *5.times.to_a) 12 | redis_client.call("LPUSH", "large-list", *1000.times.to_a) 13 | redis_client.call("HMSET", "hash", *8.times.to_a) 14 | redis_client.call("HMSET", "large-hash", *1000.times.to_a) 15 | 16 | benchmark("small string") do |x| 17 | x.report("redis-rb") { redis.get("key") } 18 | x.report("redis-client") { redis_client.call("GET", "key") } 19 | end 20 | 21 | benchmark("large string") do |x| 22 | x.report("redis-rb") { redis.get("large").valid_encoding? } 23 | x.report("redis-client") { redis_client.call("GET", "large").valid_encoding? } 24 | end 25 | 26 | benchmark("small list") do |x| 27 | x.report("redis-rb") { redis.lrange("list", 0, -1) } 28 | x.report("redis-client") { redis_client.call("LRANGE", "list", 0, -1) } 29 | end 30 | 31 | benchmark("large list") do |x| 32 | x.report("redis-rb") { redis.lrange("large-list", 0, -1) } 33 | x.report("redis-client") { redis_client.call("LRANGE", "large-list", 0, -1) } 34 | end 35 | 36 | benchmark("small hash") do |x| 37 | x.report("redis-rb") { redis.hgetall("hash") } 38 | x.report("redis-client") { redis_client.call("HGETALL", "hash") } 39 | end 40 | 41 | benchmark("large hash") do |x| 42 | x.report("redis-rb") { redis.hgetall("large-hash") } 43 | x.report("redis-client") { redis_client.call("HGETALL", "large-hash") } 44 | end 45 | -------------------------------------------------------------------------------- /benchmark/single_hiredis.md: -------------------------------------------------------------------------------- 1 | ruby: `ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [arm64-darwin23]` 2 | 3 | redis-server: `Redis server v=7.0.12 sha=00000000:0 malloc=libc bits=64 build=a11d0151eabf466c` 4 | 5 | 6 | ### small string 7 | 8 | ``` 9 | ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [arm64-darwin23] 10 | redis-rb: 47841.9 i/s 11 | redis-client: 25336.0 i/s - 1.89x slower 12 | 13 | ``` 14 | 15 | ### large string 16 | 17 | ``` 18 | ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [arm64-darwin23] 19 | redis-rb: 21223.9 i/s 20 | redis-client: 12986.1 i/s - 1.63x slower 21 | 22 | ``` 23 | 24 | ### small list 25 | 26 | ``` 27 | ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [arm64-darwin23] 28 | redis-rb: 43794.1 i/s 29 | redis-client: 24659.6 i/s - 1.78x slower 30 | 31 | ``` 32 | 33 | ### large list 34 | 35 | ``` 36 | ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [arm64-darwin23] 37 | redis-rb: 7014.5 i/s 38 | redis-client: 6820.4 i/s - same-ish: difference falls within error 39 | 40 | ``` 41 | 42 | ### small hash 43 | 44 | ``` 45 | ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [arm64-darwin23] 46 | redis-rb: 41932.1 i/s 47 | redis-client: 23808.4 i/s - 1.76x slower 48 | 49 | ``` 50 | 51 | ### large hash 52 | 53 | ``` 54 | ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [arm64-darwin23] 55 | redis-rb: 4544.7 i/s 56 | redis-client: 5388.2 i/s - 1.19x faster 57 | 58 | ``` 59 | 60 | -------------------------------------------------------------------------------- /benchmark/single_ruby.md: -------------------------------------------------------------------------------- 1 | ruby: `ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [arm64-darwin23]` 2 | 3 | redis-server: `Redis server v=7.0.12 sha=00000000:0 malloc=libc bits=64 build=a11d0151eabf466c` 4 | 5 | 6 | ### small string 7 | 8 | ``` 9 | ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [arm64-darwin23] 10 | redis-rb: 33612.8 i/s 11 | redis-client: 34726.1 i/s - same-ish: difference falls within error 12 | 13 | ``` 14 | 15 | ### large string 16 | 17 | ``` 18 | ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [arm64-darwin23] 19 | redis-rb: 16680.5 i/s 20 | redis-client: 18644.2 i/s - same-ish: difference falls within error 21 | 22 | ``` 23 | 24 | ### small list 25 | 26 | ``` 27 | ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [arm64-darwin23] 28 | redis-rb: 25772.9 i/s 29 | redis-client: 29299.7 i/s - 1.14x faster 30 | 31 | ``` 32 | 33 | ### large list 34 | 35 | ``` 36 | ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [arm64-darwin23] 37 | redis-rb: 356.9 i/s 38 | redis-client: 1342.9 i/s - 3.76x faster 39 | 40 | ``` 41 | 42 | ### small hash 43 | 44 | ``` 45 | ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [arm64-darwin23] 46 | redis-rb: 22903.5 i/s 47 | redis-client: 29003.8 i/s - 1.27x faster 48 | 49 | ``` 50 | 51 | ### large hash 52 | 53 | ``` 54 | ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [arm64-darwin23] 55 | redis-rb: 343.3 i/s 56 | redis-client: 1284.8 i/s - 3.74x faster 57 | 58 | ``` 59 | 60 | -------------------------------------------------------------------------------- /benchmark/single_yjit.md: -------------------------------------------------------------------------------- 1 | ruby: `ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [arm64-darwin23]` 2 | 3 | redis-server: `Redis server v=7.0.12 sha=00000000:0 malloc=libc bits=64 build=a11d0151eabf466c` 4 | 5 | 6 | ### small string 7 | 8 | ``` 9 | ruby 3.3.0 (2023-12-25 revision 5124f9ac75) +YJIT [arm64-darwin23] 10 | redis-rb: 36024.0 i/s 11 | redis-client: 38624.3 i/s - same-ish: difference falls within error 12 | 13 | ``` 14 | 15 | ### large string 16 | 17 | ``` 18 | ruby 3.3.0 (2023-12-25 revision 5124f9ac75) +YJIT [arm64-darwin23] 19 | redis-rb: 18402.4 i/s 20 | redis-client: 21330.5 i/s - same-ish: difference falls within error 21 | 22 | ``` 23 | 24 | ### small list 25 | 26 | ``` 27 | ruby 3.3.0 (2023-12-25 revision 5124f9ac75) +YJIT [arm64-darwin23] 28 | redis-rb: 28625.5 i/s 29 | redis-client: 34434.7 i/s - 1.20x faster 30 | 31 | ``` 32 | 33 | ### large list 34 | 35 | ``` 36 | ruby 3.3.0 (2023-12-25 revision 5124f9ac75) +YJIT [arm64-darwin23] 37 | redis-rb: 404.9 i/s 38 | redis-client: 2856.7 i/s - 7.05x faster 39 | 40 | ``` 41 | 42 | ### small hash 43 | 44 | ``` 45 | ruby 3.3.0 (2023-12-25 revision 5124f9ac75) +YJIT [arm64-darwin23] 46 | redis-rb: 25868.4 i/s 47 | redis-client: 34166.3 i/s - 1.32x faster 48 | 49 | ``` 50 | 51 | ### large hash 52 | 53 | ``` 54 | ruby 3.3.0 (2023-12-25 revision 5124f9ac75) +YJIT [arm64-darwin23] 55 | redis-rb: 378.0 i/s 56 | redis-client: 2432.8 i/s - 6.44x faster 57 | 58 | ``` 59 | 60 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require "bundler/setup" 5 | $LOAD_PATH.unshift(File.expand_path("../hiredis-client/lib", __dir__)) 6 | require "redis-client" 7 | require "hiredis-client" 8 | 9 | $redis = RedisClient.new(driver: :ruby) 10 | $hiredis = RedisClient.new(driver: :hiredis) 11 | 12 | require "irb" 13 | IRB.start(__FILE__) 14 | -------------------------------------------------------------------------------- /bin/install-toxiproxy: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | VERSION='v2.4.0' 4 | OS=$(uname -s | tr '[:upper:]' '[:lower:]') 5 | ARCH=$(uname -m) 6 | DOWNLOAD_TYPE="${OS}-${ARCH}" 7 | if [[ "${ARCH}" = "aarch64" ]]; then 8 | DOWNLOAD_TYPE="${OS}-arm64" 9 | fi 10 | if [[ "${ARCH}" = "x86_64" ]]; then 11 | DOWNLOAD_TYPE="${OS}-amd64" 12 | fi 13 | 14 | CACHE_DIR="./tmp/cache/${ARCH}-${OS}/" 15 | echo "[download toxiproxy for $DOWNLOAD_TYPE into ${CACHE_DIR}]" 16 | mkdir -p "${CACHE_DIR}" 17 | curl --silent -L "https://github.com/Shopify/toxiproxy/releases/download/${VERSION}/toxiproxy-server-${DOWNLOAD_TYPE}" -o "${CACHE_DIR}/toxiproxy-server" 18 | chmod +x "${CACHE_DIR}/toxiproxy-server" 19 | -------------------------------------------------------------------------------- /bin/megatest: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'megatest' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 12 | 13 | bundle_binstub = File.expand_path("bundle", __dir__) 14 | 15 | if File.file?(bundle_binstub) 16 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 17 | load(bundle_binstub) 18 | else 19 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 20 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 21 | end 22 | end 23 | 24 | require "rubygems" 25 | require "bundler/setup" 26 | 27 | load Gem.bin_path("megatest", "megatest") 28 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | 8 | # Do any other automated setup that you need to do here 9 | -------------------------------------------------------------------------------- /hiredis-client/README.md: -------------------------------------------------------------------------------- 1 | # HiredisClient 2 | 3 | `hiredis-client` provides a `hiredis` binding for the native `hiredis` client library. 4 | 5 | See [`redis-client`](https://github.com/redis-rb/redis-client) for details. 6 | -------------------------------------------------------------------------------- /hiredis-client/ext/redis_client/hiredis/extconf.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "mkmf" 4 | 5 | class HiredisConnectionExtconf 6 | def initialize(debug) 7 | @debug = debug 8 | end 9 | 10 | def configure 11 | if RUBY_ENGINE == "ruby" && !Gem.win_platform? 12 | configure_extension 13 | create_makefile("redis_client/hiredis_connection") 14 | else 15 | File.write("Makefile", dummy_makefile($srcdir).join) 16 | end 17 | end 18 | 19 | def configure_extension 20 | build_hiredis 21 | 22 | have_func("rb_hash_new_capa", "ruby.h") 23 | 24 | append_cflags(["-I #{Shellwords.escape(hiredis_dir)}", "-std=c99", "-fvisibility=hidden"]) 25 | $CFLAGS = if @debug 26 | concat_flags($CFLAGS, "-Werror", "-g", RbConfig::CONFIG["debugflags"]) 27 | else 28 | concat_flags($CFLAGS, "-O3") 29 | end 30 | 31 | if @debug 32 | $CPPFLAGS = concat_flags($CPPFLAGS, "-DRUBY_DEBUG=1") 33 | end 34 | 35 | append_cflags("-Wno-declaration-after-statement") # Older compilers 36 | append_cflags("-Wno-compound-token-split-by-macro") # Older rubies on macos 37 | end 38 | 39 | def build_hiredis 40 | env = { 41 | "USE_SSL" => 1, 42 | "CFLAGS" => concat_flags(ENV["CFLAGS"], "-fvisibility=hidden"), 43 | } 44 | env["OPTIMIZATION"] = "-g" if @debug 45 | 46 | env = configure_openssl(env) 47 | 48 | env_args = env.map { |k, v| "#{k}=#{v}" } 49 | Dir.chdir(hiredis_dir) do 50 | unless system(*Shellwords.split(make_program), "static", *env_args) 51 | raise "Building hiredis failed" 52 | end 53 | end 54 | 55 | $LDFLAGS = concat_flags($LDFLAGS, "-lssl", "-lcrypto") 56 | $libs = concat_flags($libs, "#{Shellwords.escape(hiredis_dir)}/libhiredis.a", 57 | "#{Shellwords.escape(hiredis_dir)}/libhiredis_ssl.a",) 58 | end 59 | 60 | def configure_openssl(original_env) 61 | original_env.dup.tap do |env| 62 | config = dir_config("openssl") 63 | if config.none? 64 | config = dir_config("opt").map { |c| detect_openssl_dir(c) } 65 | end 66 | 67 | unless have_header("openssl/ssl.h") 68 | message = "ERROR: OpenSSL library could not be found." 69 | if config.none? 70 | message += "\nUse --with-openssl-dir= option to specify the prefix where OpenSSL is installed." 71 | end 72 | abort message 73 | end 74 | 75 | if config.any? 76 | env["CFLAGS"] = concat_flags(env["CFLAGS"], "-I#{config.first}") 77 | env["SSL_LDFLAGS"] = "-L#{config.last}" 78 | end 79 | end 80 | end 81 | 82 | private 83 | 84 | def detect_openssl_dir(paths) 85 | paths 86 | &.split(File::PATH_SEPARATOR) 87 | &.detect { |dir| dir.include?("openssl") } 88 | end 89 | 90 | def concat_flags(*args) 91 | args.compact.join(" ") 92 | end 93 | 94 | def hiredis_dir 95 | File.expand_path('vendor', __dir__) 96 | end 97 | 98 | def make_program 99 | with_config("make-prog", ENV["MAKE"]) || 100 | case RUBY_PLATFORM 101 | when /(bsd|solaris)/ 102 | 'gmake' 103 | else 104 | 'make' 105 | end 106 | end 107 | end 108 | 109 | HiredisConnectionExtconf.new(ENV["EXT_PEDANTIC"]).configure 110 | -------------------------------------------------------------------------------- /hiredis-client/ext/redis_client/hiredis/vendor/.gitignore: -------------------------------------------------------------------------------- 1 | /hiredis-test 2 | /examples/hiredis-example* 3 | /*.o 4 | /*.so 5 | /*.dylib 6 | /*.a 7 | /*.pc 8 | *.dSYM 9 | tags 10 | -------------------------------------------------------------------------------- /hiredis-client/ext/redis_client/hiredis/vendor/.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | compiler: 3 | - gcc 4 | - clang 5 | 6 | os: 7 | - linux 8 | - osx 9 | 10 | dist: bionic 11 | 12 | branches: 13 | only: 14 | - staging 15 | - trying 16 | - master 17 | - /^release\/.*$/ 18 | 19 | install: 20 | - if [ "$BITS" == "64" ]; then 21 | wget https://github.com/redis/redis/archive/6.0.6.tar.gz; 22 | tar -xzvf 6.0.6.tar.gz; 23 | pushd redis-6.0.6 && BUILD_TLS=yes make && export PATH=$PWD/src:$PATH && popd; 24 | fi 25 | 26 | before_script: 27 | - if [ "$TRAVIS_OS_NAME" == "osx" ]; then 28 | curl -O https://distfiles.macports.org/MacPorts/MacPorts-2.6.2-10.13-HighSierra.pkg; 29 | sudo installer -pkg MacPorts-2.6.2-10.13-HighSierra.pkg -target /; 30 | export PATH=$PATH:/opt/local/bin && sudo port -v selfupdate; 31 | sudo port -N install openssl redis; 32 | fi; 33 | 34 | addons: 35 | apt: 36 | sources: 37 | - sourceline: 'ppa:chris-lea/redis-server' 38 | packages: 39 | - libc6-dbg 40 | - libc6-dev 41 | - libc6:i386 42 | - libc6-dev-i386 43 | - libc6-dbg:i386 44 | - gcc-multilib 45 | - g++-multilib 46 | - libssl-dev 47 | - libssl-dev:i386 48 | - valgrind 49 | - redis 50 | 51 | env: 52 | - BITS="32" 53 | - BITS="64" 54 | 55 | script: 56 | - EXTRA_CMAKE_OPTS="-DENABLE_EXAMPLES:BOOL=ON -DENABLE_SSL:BOOL=ON"; 57 | if [ "$BITS" == "64" ]; then 58 | EXTRA_CMAKE_OPTS="$EXTRA_CMAKE_OPTS -DENABLE_SSL_TESTS:BOOL=ON"; 59 | fi; 60 | if [ "$TRAVIS_OS_NAME" == "osx" ]; then 61 | if [ "$BITS" == "32" ]; then 62 | CFLAGS="-m32 -Werror"; 63 | CXXFLAGS="-m32 -Werror"; 64 | LDFLAGS="-m32"; 65 | EXTRA_CMAKE_OPTS=; 66 | else 67 | CFLAGS="-Werror"; 68 | CXXFLAGS="-Werror"; 69 | fi; 70 | else 71 | TEST_PREFIX="valgrind --track-origins=yes --leak-check=full"; 72 | if [ "$BITS" == "32" ]; then 73 | CFLAGS="-m32 -Werror"; 74 | CXXFLAGS="-m32 -Werror"; 75 | LDFLAGS="-m32"; 76 | EXTRA_CMAKE_OPTS=; 77 | else 78 | CFLAGS="-Werror"; 79 | CXXFLAGS="-Werror"; 80 | fi; 81 | fi; 82 | export CFLAGS CXXFLAGS LDFLAGS TEST_PREFIX EXTRA_CMAKE_OPTS 83 | - make && make clean; 84 | if [ "$TRAVIS_OS_NAME" == "osx" ]; then 85 | if [ "$BITS" == "64" ]; then 86 | OPENSSL_PREFIX="$(ls -d /usr/local/Cellar/openssl@1.1/*)" USE_SSL=1 make; 87 | fi; 88 | else 89 | USE_SSL=1 make; 90 | fi; 91 | - mkdir build/ && cd build/ 92 | - cmake .. ${EXTRA_CMAKE_OPTS} 93 | - make VERBOSE=1 94 | - if [ "$BITS" == "64" ]; then 95 | TEST_SSL=1 SKIPS_AS_FAILS=1 ctest -V; 96 | else 97 | SKIPS_AS_FAILS=1 ctest -V; 98 | fi; 99 | 100 | jobs: 101 | include: 102 | # Windows MinGW cross compile on Linux 103 | - os: linux 104 | dist: xenial 105 | compiler: mingw 106 | addons: 107 | apt: 108 | packages: 109 | - ninja-build 110 | - gcc-mingw-w64-x86-64 111 | - g++-mingw-w64-x86-64 112 | script: 113 | - mkdir build && cd build 114 | - CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++ cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_BUILD_WITH_INSTALL_RPATH=on 115 | - ninja -v 116 | 117 | # Windows MSVC 2017 118 | - os: windows 119 | compiler: msvc 120 | env: 121 | - MATRIX_EVAL="CC=cl.exe && CXX=cl.exe" 122 | before_install: 123 | - eval "${MATRIX_EVAL}" 124 | install: 125 | - choco install ninja 126 | - choco install -y memurai-developer 127 | script: 128 | - mkdir build && cd build 129 | - cmd.exe //C 'C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliary\Build\vcvarsall.bat' amd64 '&&' 130 | cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release -DENABLE_EXAMPLES=ON '&&' ninja -v 131 | - ./hiredis-test.exe 132 | -------------------------------------------------------------------------------- /hiredis-client/ext/redis_client/hiredis/vendor/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | CMAKE_MINIMUM_REQUIRED(VERSION 3.4.0) 2 | INCLUDE(GNUInstallDirs) 3 | PROJECT(hiredis) 4 | 5 | OPTION(ENABLE_SSL "Build hiredis_ssl for SSL support" OFF) 6 | OPTION(DISABLE_TESTS "If tests should be compiled or not" OFF) 7 | OPTION(ENABLE_SSL_TESTS, "Should we test SSL connections" OFF) 8 | 9 | MACRO(getVersionBit name) 10 | SET(VERSION_REGEX "^#define ${name} (.+)$") 11 | FILE(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/hiredis.h" 12 | VERSION_BIT REGEX ${VERSION_REGEX}) 13 | STRING(REGEX REPLACE ${VERSION_REGEX} "\\1" ${name} "${VERSION_BIT}") 14 | ENDMACRO(getVersionBit) 15 | 16 | getVersionBit(HIREDIS_MAJOR) 17 | getVersionBit(HIREDIS_MINOR) 18 | getVersionBit(HIREDIS_PATCH) 19 | getVersionBit(HIREDIS_SONAME) 20 | SET(VERSION "${HIREDIS_MAJOR}.${HIREDIS_MINOR}.${HIREDIS_PATCH}") 21 | MESSAGE("Detected version: ${VERSION}") 22 | 23 | PROJECT(hiredis VERSION "${VERSION}") 24 | 25 | SET(ENABLE_EXAMPLES OFF CACHE BOOL "Enable building hiredis examples") 26 | 27 | SET(hiredis_sources 28 | alloc.c 29 | async.c 30 | dict.c 31 | hiredis.c 32 | net.c 33 | read.c 34 | sds.c 35 | sockcompat.c) 36 | 37 | SET(hiredis_sources ${hiredis_sources}) 38 | 39 | IF(WIN32) 40 | ADD_COMPILE_DEFINITIONS(_CRT_SECURE_NO_WARNINGS WIN32_LEAN_AND_MEAN) 41 | ENDIF() 42 | 43 | ADD_LIBRARY(hiredis SHARED ${hiredis_sources}) 44 | 45 | SET_TARGET_PROPERTIES(hiredis 46 | PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS TRUE 47 | VERSION "${HIREDIS_SONAME}") 48 | IF(WIN32 OR MINGW) 49 | TARGET_LINK_LIBRARIES(hiredis PRIVATE ws2_32) 50 | ENDIF() 51 | 52 | TARGET_INCLUDE_DIRECTORIES(hiredis PUBLIC $ $) 53 | 54 | CONFIGURE_FILE(hiredis.pc.in hiredis.pc @ONLY) 55 | 56 | INSTALL(TARGETS hiredis 57 | EXPORT hiredis-targets 58 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} 59 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 60 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) 61 | 62 | INSTALL(FILES hiredis.h read.h sds.h async.h alloc.h 63 | DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis) 64 | 65 | INSTALL(DIRECTORY adapters 66 | DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis) 67 | 68 | INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis.pc 69 | DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) 70 | 71 | export(EXPORT hiredis-targets 72 | FILE "${CMAKE_CURRENT_BINARY_DIR}/hiredis-targets.cmake" 73 | NAMESPACE hiredis::) 74 | 75 | SET(CMAKE_CONF_INSTALL_DIR share/hiredis) 76 | SET(INCLUDE_INSTALL_DIR include) 77 | include(CMakePackageConfigHelpers) 78 | configure_package_config_file(hiredis-config.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/hiredis-config.cmake 79 | INSTALL_DESTINATION ${CMAKE_CONF_INSTALL_DIR} 80 | PATH_VARS INCLUDE_INSTALL_DIR) 81 | 82 | INSTALL(EXPORT hiredis-targets 83 | FILE hiredis-targets.cmake 84 | NAMESPACE hiredis:: 85 | DESTINATION ${CMAKE_CONF_INSTALL_DIR}) 86 | 87 | INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis-config.cmake 88 | DESTINATION ${CMAKE_CONF_INSTALL_DIR}) 89 | 90 | 91 | IF(ENABLE_SSL) 92 | IF (NOT OPENSSL_ROOT_DIR) 93 | IF (APPLE) 94 | SET(OPENSSL_ROOT_DIR "/usr/local/opt/openssl") 95 | ENDIF() 96 | ENDIF() 97 | FIND_PACKAGE(OpenSSL REQUIRED) 98 | SET(hiredis_ssl_sources 99 | ssl.c) 100 | ADD_LIBRARY(hiredis_ssl SHARED 101 | ${hiredis_ssl_sources}) 102 | 103 | IF (APPLE) 104 | SET_PROPERTY(TARGET hiredis_ssl PROPERTY LINK_FLAGS "-Wl,-undefined -Wl,dynamic_lookup") 105 | ENDIF() 106 | 107 | SET_TARGET_PROPERTIES(hiredis_ssl 108 | PROPERTIES 109 | WINDOWS_EXPORT_ALL_SYMBOLS TRUE 110 | VERSION "${HIREDIS_SONAME}") 111 | 112 | TARGET_INCLUDE_DIRECTORIES(hiredis_ssl PRIVATE "${OPENSSL_INCLUDE_DIR}") 113 | TARGET_LINK_LIBRARIES(hiredis_ssl PRIVATE ${OPENSSL_LIBRARIES}) 114 | IF (WIN32 OR MINGW) 115 | TARGET_LINK_LIBRARIES(hiredis_ssl PRIVATE hiredis) 116 | ENDIF() 117 | CONFIGURE_FILE(hiredis_ssl.pc.in hiredis_ssl.pc @ONLY) 118 | 119 | INSTALL(TARGETS hiredis_ssl 120 | EXPORT hiredis_ssl-targets 121 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} 122 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 123 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) 124 | 125 | INSTALL(FILES hiredis_ssl.h 126 | DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis) 127 | 128 | INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis_ssl.pc 129 | DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) 130 | 131 | export(EXPORT hiredis_ssl-targets 132 | FILE "${CMAKE_CURRENT_BINARY_DIR}/hiredis_ssl-targets.cmake" 133 | NAMESPACE hiredis::) 134 | 135 | SET(CMAKE_CONF_INSTALL_DIR share/hiredis_ssl) 136 | configure_package_config_file(hiredis_ssl-config.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/hiredis_ssl-config.cmake 137 | INSTALL_DESTINATION ${CMAKE_CONF_INSTALL_DIR} 138 | PATH_VARS INCLUDE_INSTALL_DIR) 139 | 140 | INSTALL(EXPORT hiredis_ssl-targets 141 | FILE hiredis_ssl-targets.cmake 142 | NAMESPACE hiredis:: 143 | DESTINATION ${CMAKE_CONF_INSTALL_DIR}) 144 | 145 | INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis_ssl-config.cmake 146 | DESTINATION ${CMAKE_CONF_INSTALL_DIR}) 147 | ENDIF() 148 | 149 | IF(NOT DISABLE_TESTS) 150 | ENABLE_TESTING() 151 | ADD_EXECUTABLE(hiredis-test test.c) 152 | IF(ENABLE_SSL_TESTS) 153 | ADD_DEFINITIONS(-DHIREDIS_TEST_SSL=1) 154 | TARGET_LINK_LIBRARIES(hiredis-test hiredis hiredis_ssl) 155 | ELSE() 156 | TARGET_LINK_LIBRARIES(hiredis-test hiredis) 157 | ENDIF() 158 | ADD_TEST(NAME hiredis-test 159 | COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/test.sh) 160 | ENDIF() 161 | 162 | # Add examples 163 | IF(ENABLE_EXAMPLES) 164 | ADD_SUBDIRECTORY(examples) 165 | ENDIF(ENABLE_EXAMPLES) 166 | -------------------------------------------------------------------------------- /hiredis-client/ext/redis_client/hiredis/vendor/COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009-2011, Salvatore Sanfilippo 2 | Copyright (c) 2010-2011, Pieter Noordhuis 3 | 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of Redis nor the names of its contributors may be used 17 | to endorse or promote products derived from this software without specific 18 | prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 21 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 22 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 24 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 25 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 26 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 27 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /hiredis-client/ext/redis_client/hiredis/vendor/adapters/ae.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010-2011, Pieter Noordhuis 3 | * 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions are met: 8 | * 9 | * * Redistributions of source code must retain the above copyright notice, 10 | * this list of conditions and the following disclaimer. 11 | * * Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in the 13 | * documentation and/or other materials provided with the distribution. 14 | * * Neither the name of Redis nor the names of its contributors may be used 15 | * to endorse or promote products derived from this software without 16 | * specific prior written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 22 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 | * POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | 31 | #ifndef __HIREDIS_AE_H__ 32 | #define __HIREDIS_AE_H__ 33 | #include 34 | #include 35 | #include "../hiredis.h" 36 | #include "../async.h" 37 | 38 | typedef struct redisAeEvents { 39 | redisAsyncContext *context; 40 | aeEventLoop *loop; 41 | int fd; 42 | int reading, writing; 43 | } redisAeEvents; 44 | 45 | static void redisAeReadEvent(aeEventLoop *el, int fd, void *privdata, int mask) { 46 | ((void)el); ((void)fd); ((void)mask); 47 | 48 | redisAeEvents *e = (redisAeEvents*)privdata; 49 | redisAsyncHandleRead(e->context); 50 | } 51 | 52 | static void redisAeWriteEvent(aeEventLoop *el, int fd, void *privdata, int mask) { 53 | ((void)el); ((void)fd); ((void)mask); 54 | 55 | redisAeEvents *e = (redisAeEvents*)privdata; 56 | redisAsyncHandleWrite(e->context); 57 | } 58 | 59 | static void redisAeAddRead(void *privdata) { 60 | redisAeEvents *e = (redisAeEvents*)privdata; 61 | aeEventLoop *loop = e->loop; 62 | if (!e->reading) { 63 | e->reading = 1; 64 | aeCreateFileEvent(loop,e->fd,AE_READABLE,redisAeReadEvent,e); 65 | } 66 | } 67 | 68 | static void redisAeDelRead(void *privdata) { 69 | redisAeEvents *e = (redisAeEvents*)privdata; 70 | aeEventLoop *loop = e->loop; 71 | if (e->reading) { 72 | e->reading = 0; 73 | aeDeleteFileEvent(loop,e->fd,AE_READABLE); 74 | } 75 | } 76 | 77 | static void redisAeAddWrite(void *privdata) { 78 | redisAeEvents *e = (redisAeEvents*)privdata; 79 | aeEventLoop *loop = e->loop; 80 | if (!e->writing) { 81 | e->writing = 1; 82 | aeCreateFileEvent(loop,e->fd,AE_WRITABLE,redisAeWriteEvent,e); 83 | } 84 | } 85 | 86 | static void redisAeDelWrite(void *privdata) { 87 | redisAeEvents *e = (redisAeEvents*)privdata; 88 | aeEventLoop *loop = e->loop; 89 | if (e->writing) { 90 | e->writing = 0; 91 | aeDeleteFileEvent(loop,e->fd,AE_WRITABLE); 92 | } 93 | } 94 | 95 | static void redisAeCleanup(void *privdata) { 96 | redisAeEvents *e = (redisAeEvents*)privdata; 97 | redisAeDelRead(privdata); 98 | redisAeDelWrite(privdata); 99 | hi_free(e); 100 | } 101 | 102 | static int redisAeAttach(aeEventLoop *loop, redisAsyncContext *ac) { 103 | redisContext *c = &(ac->c); 104 | redisAeEvents *e; 105 | 106 | /* Nothing should be attached when something is already attached */ 107 | if (ac->ev.data != NULL) 108 | return REDIS_ERR; 109 | 110 | /* Create container for context and r/w events */ 111 | e = (redisAeEvents*)hi_malloc(sizeof(*e)); 112 | if (e == NULL) 113 | return REDIS_ERR; 114 | 115 | e->context = ac; 116 | e->loop = loop; 117 | e->fd = c->fd; 118 | e->reading = e->writing = 0; 119 | 120 | /* Register functions to start/stop listening for events */ 121 | ac->ev.addRead = redisAeAddRead; 122 | ac->ev.delRead = redisAeDelRead; 123 | ac->ev.addWrite = redisAeAddWrite; 124 | ac->ev.delWrite = redisAeDelWrite; 125 | ac->ev.cleanup = redisAeCleanup; 126 | ac->ev.data = e; 127 | 128 | return REDIS_OK; 129 | } 130 | #endif 131 | -------------------------------------------------------------------------------- /hiredis-client/ext/redis_client/hiredis/vendor/adapters/glib.h: -------------------------------------------------------------------------------- 1 | #ifndef __HIREDIS_GLIB_H__ 2 | #define __HIREDIS_GLIB_H__ 3 | 4 | #include 5 | 6 | #include "../hiredis.h" 7 | #include "../async.h" 8 | 9 | typedef struct 10 | { 11 | GSource source; 12 | redisAsyncContext *ac; 13 | GPollFD poll_fd; 14 | } RedisSource; 15 | 16 | static void 17 | redis_source_add_read (gpointer data) 18 | { 19 | RedisSource *source = (RedisSource *)data; 20 | g_return_if_fail(source); 21 | source->poll_fd.events |= G_IO_IN; 22 | g_main_context_wakeup(g_source_get_context((GSource *)data)); 23 | } 24 | 25 | static void 26 | redis_source_del_read (gpointer data) 27 | { 28 | RedisSource *source = (RedisSource *)data; 29 | g_return_if_fail(source); 30 | source->poll_fd.events &= ~G_IO_IN; 31 | g_main_context_wakeup(g_source_get_context((GSource *)data)); 32 | } 33 | 34 | static void 35 | redis_source_add_write (gpointer data) 36 | { 37 | RedisSource *source = (RedisSource *)data; 38 | g_return_if_fail(source); 39 | source->poll_fd.events |= G_IO_OUT; 40 | g_main_context_wakeup(g_source_get_context((GSource *)data)); 41 | } 42 | 43 | static void 44 | redis_source_del_write (gpointer data) 45 | { 46 | RedisSource *source = (RedisSource *)data; 47 | g_return_if_fail(source); 48 | source->poll_fd.events &= ~G_IO_OUT; 49 | g_main_context_wakeup(g_source_get_context((GSource *)data)); 50 | } 51 | 52 | static void 53 | redis_source_cleanup (gpointer data) 54 | { 55 | RedisSource *source = (RedisSource *)data; 56 | 57 | g_return_if_fail(source); 58 | 59 | redis_source_del_read(source); 60 | redis_source_del_write(source); 61 | /* 62 | * It is not our responsibility to remove ourself from the 63 | * current main loop. However, we will remove the GPollFD. 64 | */ 65 | if (source->poll_fd.fd >= 0) { 66 | g_source_remove_poll((GSource *)data, &source->poll_fd); 67 | source->poll_fd.fd = -1; 68 | } 69 | } 70 | 71 | static gboolean 72 | redis_source_prepare (GSource *source, 73 | gint *timeout_) 74 | { 75 | RedisSource *redis = (RedisSource *)source; 76 | *timeout_ = -1; 77 | return !!(redis->poll_fd.events & redis->poll_fd.revents); 78 | } 79 | 80 | static gboolean 81 | redis_source_check (GSource *source) 82 | { 83 | RedisSource *redis = (RedisSource *)source; 84 | return !!(redis->poll_fd.events & redis->poll_fd.revents); 85 | } 86 | 87 | static gboolean 88 | redis_source_dispatch (GSource *source, 89 | GSourceFunc callback, 90 | gpointer user_data) 91 | { 92 | RedisSource *redis = (RedisSource *)source; 93 | 94 | if ((redis->poll_fd.revents & G_IO_OUT)) { 95 | redisAsyncHandleWrite(redis->ac); 96 | redis->poll_fd.revents &= ~G_IO_OUT; 97 | } 98 | 99 | if ((redis->poll_fd.revents & G_IO_IN)) { 100 | redisAsyncHandleRead(redis->ac); 101 | redis->poll_fd.revents &= ~G_IO_IN; 102 | } 103 | 104 | if (callback) { 105 | return callback(user_data); 106 | } 107 | 108 | return TRUE; 109 | } 110 | 111 | static void 112 | redis_source_finalize (GSource *source) 113 | { 114 | RedisSource *redis = (RedisSource *)source; 115 | 116 | if (redis->poll_fd.fd >= 0) { 117 | g_source_remove_poll(source, &redis->poll_fd); 118 | redis->poll_fd.fd = -1; 119 | } 120 | } 121 | 122 | static GSource * 123 | redis_source_new (redisAsyncContext *ac) 124 | { 125 | static GSourceFuncs source_funcs = { 126 | .prepare = redis_source_prepare, 127 | .check = redis_source_check, 128 | .dispatch = redis_source_dispatch, 129 | .finalize = redis_source_finalize, 130 | }; 131 | redisContext *c = &ac->c; 132 | RedisSource *source; 133 | 134 | g_return_val_if_fail(ac != NULL, NULL); 135 | 136 | source = (RedisSource *)g_source_new(&source_funcs, sizeof *source); 137 | if (source == NULL) 138 | return NULL; 139 | 140 | source->ac = ac; 141 | source->poll_fd.fd = c->fd; 142 | source->poll_fd.events = 0; 143 | source->poll_fd.revents = 0; 144 | g_source_add_poll((GSource *)source, &source->poll_fd); 145 | 146 | ac->ev.addRead = redis_source_add_read; 147 | ac->ev.delRead = redis_source_del_read; 148 | ac->ev.addWrite = redis_source_add_write; 149 | ac->ev.delWrite = redis_source_del_write; 150 | ac->ev.cleanup = redis_source_cleanup; 151 | ac->ev.data = source; 152 | 153 | return (GSource *)source; 154 | } 155 | 156 | #endif /* __HIREDIS_GLIB_H__ */ 157 | -------------------------------------------------------------------------------- /hiredis-client/ext/redis_client/hiredis/vendor/adapters/ivykis.h: -------------------------------------------------------------------------------- 1 | #ifndef __HIREDIS_IVYKIS_H__ 2 | #define __HIREDIS_IVYKIS_H__ 3 | #include 4 | #include "../hiredis.h" 5 | #include "../async.h" 6 | 7 | typedef struct redisIvykisEvents { 8 | redisAsyncContext *context; 9 | struct iv_fd fd; 10 | } redisIvykisEvents; 11 | 12 | static void redisIvykisReadEvent(void *arg) { 13 | redisAsyncContext *context = (redisAsyncContext *)arg; 14 | redisAsyncHandleRead(context); 15 | } 16 | 17 | static void redisIvykisWriteEvent(void *arg) { 18 | redisAsyncContext *context = (redisAsyncContext *)arg; 19 | redisAsyncHandleWrite(context); 20 | } 21 | 22 | static void redisIvykisAddRead(void *privdata) { 23 | redisIvykisEvents *e = (redisIvykisEvents*)privdata; 24 | iv_fd_set_handler_in(&e->fd, redisIvykisReadEvent); 25 | } 26 | 27 | static void redisIvykisDelRead(void *privdata) { 28 | redisIvykisEvents *e = (redisIvykisEvents*)privdata; 29 | iv_fd_set_handler_in(&e->fd, NULL); 30 | } 31 | 32 | static void redisIvykisAddWrite(void *privdata) { 33 | redisIvykisEvents *e = (redisIvykisEvents*)privdata; 34 | iv_fd_set_handler_out(&e->fd, redisIvykisWriteEvent); 35 | } 36 | 37 | static void redisIvykisDelWrite(void *privdata) { 38 | redisIvykisEvents *e = (redisIvykisEvents*)privdata; 39 | iv_fd_set_handler_out(&e->fd, NULL); 40 | } 41 | 42 | static void redisIvykisCleanup(void *privdata) { 43 | redisIvykisEvents *e = (redisIvykisEvents*)privdata; 44 | 45 | iv_fd_unregister(&e->fd); 46 | hi_free(e); 47 | } 48 | 49 | static int redisIvykisAttach(redisAsyncContext *ac) { 50 | redisContext *c = &(ac->c); 51 | redisIvykisEvents *e; 52 | 53 | /* Nothing should be attached when something is already attached */ 54 | if (ac->ev.data != NULL) 55 | return REDIS_ERR; 56 | 57 | /* Create container for context and r/w events */ 58 | e = (redisIvykisEvents*)hi_malloc(sizeof(*e)); 59 | if (e == NULL) 60 | return REDIS_ERR; 61 | 62 | e->context = ac; 63 | 64 | /* Register functions to start/stop listening for events */ 65 | ac->ev.addRead = redisIvykisAddRead; 66 | ac->ev.delRead = redisIvykisDelRead; 67 | ac->ev.addWrite = redisIvykisAddWrite; 68 | ac->ev.delWrite = redisIvykisDelWrite; 69 | ac->ev.cleanup = redisIvykisCleanup; 70 | ac->ev.data = e; 71 | 72 | /* Initialize and install read/write events */ 73 | IV_FD_INIT(&e->fd); 74 | e->fd.fd = c->fd; 75 | e->fd.handler_in = redisIvykisReadEvent; 76 | e->fd.handler_out = redisIvykisWriteEvent; 77 | e->fd.handler_err = NULL; 78 | e->fd.cookie = e->context; 79 | 80 | iv_fd_register(&e->fd); 81 | 82 | return REDIS_OK; 83 | } 84 | #endif 85 | -------------------------------------------------------------------------------- /hiredis-client/ext/redis_client/hiredis/vendor/adapters/libev.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010-2011, Pieter Noordhuis 3 | * 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions are met: 8 | * 9 | * * Redistributions of source code must retain the above copyright notice, 10 | * this list of conditions and the following disclaimer. 11 | * * Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in the 13 | * documentation and/or other materials provided with the distribution. 14 | * * Neither the name of Redis nor the names of its contributors may be used 15 | * to endorse or promote products derived from this software without 16 | * specific prior written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 22 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 | * POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | 31 | #ifndef __HIREDIS_LIBEV_H__ 32 | #define __HIREDIS_LIBEV_H__ 33 | #include 34 | #include 35 | #include 36 | #include "../hiredis.h" 37 | #include "../async.h" 38 | 39 | typedef struct redisLibevEvents { 40 | redisAsyncContext *context; 41 | struct ev_loop *loop; 42 | int reading, writing; 43 | ev_io rev, wev; 44 | ev_timer timer; 45 | } redisLibevEvents; 46 | 47 | static void redisLibevReadEvent(EV_P_ ev_io *watcher, int revents) { 48 | #if EV_MULTIPLICITY 49 | ((void)loop); 50 | #endif 51 | ((void)revents); 52 | 53 | redisLibevEvents *e = (redisLibevEvents*)watcher->data; 54 | redisAsyncHandleRead(e->context); 55 | } 56 | 57 | static void redisLibevWriteEvent(EV_P_ ev_io *watcher, int revents) { 58 | #if EV_MULTIPLICITY 59 | ((void)loop); 60 | #endif 61 | ((void)revents); 62 | 63 | redisLibevEvents *e = (redisLibevEvents*)watcher->data; 64 | redisAsyncHandleWrite(e->context); 65 | } 66 | 67 | static void redisLibevAddRead(void *privdata) { 68 | redisLibevEvents *e = (redisLibevEvents*)privdata; 69 | struct ev_loop *loop = e->loop; 70 | ((void)loop); 71 | if (!e->reading) { 72 | e->reading = 1; 73 | ev_io_start(EV_A_ &e->rev); 74 | } 75 | } 76 | 77 | static void redisLibevDelRead(void *privdata) { 78 | redisLibevEvents *e = (redisLibevEvents*)privdata; 79 | struct ev_loop *loop = e->loop; 80 | ((void)loop); 81 | if (e->reading) { 82 | e->reading = 0; 83 | ev_io_stop(EV_A_ &e->rev); 84 | } 85 | } 86 | 87 | static void redisLibevAddWrite(void *privdata) { 88 | redisLibevEvents *e = (redisLibevEvents*)privdata; 89 | struct ev_loop *loop = e->loop; 90 | ((void)loop); 91 | if (!e->writing) { 92 | e->writing = 1; 93 | ev_io_start(EV_A_ &e->wev); 94 | } 95 | } 96 | 97 | static void redisLibevDelWrite(void *privdata) { 98 | redisLibevEvents *e = (redisLibevEvents*)privdata; 99 | struct ev_loop *loop = e->loop; 100 | ((void)loop); 101 | if (e->writing) { 102 | e->writing = 0; 103 | ev_io_stop(EV_A_ &e->wev); 104 | } 105 | } 106 | 107 | static void redisLibevStopTimer(void *privdata) { 108 | redisLibevEvents *e = (redisLibevEvents*)privdata; 109 | struct ev_loop *loop = e->loop; 110 | ((void)loop); 111 | ev_timer_stop(EV_A_ &e->timer); 112 | } 113 | 114 | static void redisLibevCleanup(void *privdata) { 115 | redisLibevEvents *e = (redisLibevEvents*)privdata; 116 | redisLibevDelRead(privdata); 117 | redisLibevDelWrite(privdata); 118 | redisLibevStopTimer(privdata); 119 | hi_free(e); 120 | } 121 | 122 | static void redisLibevTimeout(EV_P_ ev_timer *timer, int revents) { 123 | ((void)revents); 124 | redisLibevEvents *e = (redisLibevEvents*)timer->data; 125 | redisAsyncHandleTimeout(e->context); 126 | } 127 | 128 | static void redisLibevSetTimeout(void *privdata, struct timeval tv) { 129 | redisLibevEvents *e = (redisLibevEvents*)privdata; 130 | struct ev_loop *loop = e->loop; 131 | ((void)loop); 132 | 133 | if (!ev_is_active(&e->timer)) { 134 | ev_init(&e->timer, redisLibevTimeout); 135 | e->timer.data = e; 136 | } 137 | 138 | e->timer.repeat = tv.tv_sec + tv.tv_usec / 1000000.00; 139 | ev_timer_again(EV_A_ &e->timer); 140 | } 141 | 142 | static int redisLibevAttach(EV_P_ redisAsyncContext *ac) { 143 | redisContext *c = &(ac->c); 144 | redisLibevEvents *e; 145 | 146 | /* Nothing should be attached when something is already attached */ 147 | if (ac->ev.data != NULL) 148 | return REDIS_ERR; 149 | 150 | /* Create container for context and r/w events */ 151 | e = (redisLibevEvents*)hi_calloc(1, sizeof(*e)); 152 | if (e == NULL) 153 | return REDIS_ERR; 154 | 155 | e->context = ac; 156 | #if EV_MULTIPLICITY 157 | e->loop = loop; 158 | #else 159 | e->loop = NULL; 160 | #endif 161 | e->rev.data = e; 162 | e->wev.data = e; 163 | 164 | /* Register functions to start/stop listening for events */ 165 | ac->ev.addRead = redisLibevAddRead; 166 | ac->ev.delRead = redisLibevDelRead; 167 | ac->ev.addWrite = redisLibevAddWrite; 168 | ac->ev.delWrite = redisLibevDelWrite; 169 | ac->ev.cleanup = redisLibevCleanup; 170 | ac->ev.scheduleTimer = redisLibevSetTimeout; 171 | ac->ev.data = e; 172 | 173 | /* Initialize read/write events */ 174 | ev_io_init(&e->rev,redisLibevReadEvent,c->fd,EV_READ); 175 | ev_io_init(&e->wev,redisLibevWriteEvent,c->fd,EV_WRITE); 176 | return REDIS_OK; 177 | } 178 | 179 | #endif 180 | -------------------------------------------------------------------------------- /hiredis-client/ext/redis_client/hiredis/vendor/adapters/libuv.h: -------------------------------------------------------------------------------- 1 | #ifndef __HIREDIS_LIBUV_H__ 2 | #define __HIREDIS_LIBUV_H__ 3 | #include 4 | #include 5 | #include "../hiredis.h" 6 | #include "../async.h" 7 | #include 8 | 9 | typedef struct redisLibuvEvents { 10 | redisAsyncContext* context; 11 | uv_poll_t handle; 12 | int events; 13 | } redisLibuvEvents; 14 | 15 | 16 | static void redisLibuvPoll(uv_poll_t* handle, int status, int events) { 17 | redisLibuvEvents* p = (redisLibuvEvents*)handle->data; 18 | int ev = (status ? p->events : events); 19 | 20 | if (p->context != NULL && (ev & UV_READABLE)) { 21 | redisAsyncHandleRead(p->context); 22 | } 23 | if (p->context != NULL && (ev & UV_WRITABLE)) { 24 | redisAsyncHandleWrite(p->context); 25 | } 26 | } 27 | 28 | 29 | static void redisLibuvAddRead(void *privdata) { 30 | redisLibuvEvents* p = (redisLibuvEvents*)privdata; 31 | 32 | p->events |= UV_READABLE; 33 | 34 | uv_poll_start(&p->handle, p->events, redisLibuvPoll); 35 | } 36 | 37 | 38 | static void redisLibuvDelRead(void *privdata) { 39 | redisLibuvEvents* p = (redisLibuvEvents*)privdata; 40 | 41 | p->events &= ~UV_READABLE; 42 | 43 | if (p->events) { 44 | uv_poll_start(&p->handle, p->events, redisLibuvPoll); 45 | } else { 46 | uv_poll_stop(&p->handle); 47 | } 48 | } 49 | 50 | 51 | static void redisLibuvAddWrite(void *privdata) { 52 | redisLibuvEvents* p = (redisLibuvEvents*)privdata; 53 | 54 | p->events |= UV_WRITABLE; 55 | 56 | uv_poll_start(&p->handle, p->events, redisLibuvPoll); 57 | } 58 | 59 | 60 | static void redisLibuvDelWrite(void *privdata) { 61 | redisLibuvEvents* p = (redisLibuvEvents*)privdata; 62 | 63 | p->events &= ~UV_WRITABLE; 64 | 65 | if (p->events) { 66 | uv_poll_start(&p->handle, p->events, redisLibuvPoll); 67 | } else { 68 | uv_poll_stop(&p->handle); 69 | } 70 | } 71 | 72 | 73 | static void on_close(uv_handle_t* handle) { 74 | redisLibuvEvents* p = (redisLibuvEvents*)handle->data; 75 | 76 | hi_free(p); 77 | } 78 | 79 | 80 | static void redisLibuvCleanup(void *privdata) { 81 | redisLibuvEvents* p = (redisLibuvEvents*)privdata; 82 | 83 | p->context = NULL; // indicate that context might no longer exist 84 | uv_close((uv_handle_t*)&p->handle, on_close); 85 | } 86 | 87 | 88 | static int redisLibuvAttach(redisAsyncContext* ac, uv_loop_t* loop) { 89 | redisContext *c = &(ac->c); 90 | 91 | if (ac->ev.data != NULL) { 92 | return REDIS_ERR; 93 | } 94 | 95 | ac->ev.addRead = redisLibuvAddRead; 96 | ac->ev.delRead = redisLibuvDelRead; 97 | ac->ev.addWrite = redisLibuvAddWrite; 98 | ac->ev.delWrite = redisLibuvDelWrite; 99 | ac->ev.cleanup = redisLibuvCleanup; 100 | 101 | redisLibuvEvents* p = (redisLibuvEvents*)hi_malloc(sizeof(*p)); 102 | if (p == NULL) 103 | return REDIS_ERR; 104 | 105 | memset(p, 0, sizeof(*p)); 106 | 107 | if (uv_poll_init_socket(loop, &p->handle, c->fd) != 0) { 108 | return REDIS_ERR; 109 | } 110 | 111 | ac->ev.data = p; 112 | p->handle.data = p; 113 | p->context = ac; 114 | 115 | return REDIS_OK; 116 | } 117 | #endif 118 | -------------------------------------------------------------------------------- /hiredis-client/ext/redis_client/hiredis/vendor/adapters/macosx.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Дмитрий Бахвалов on 13.07.15. 3 | // Copyright (c) 2015 Dmitry Bakhvalov. All rights reserved. 4 | // 5 | 6 | #ifndef __HIREDIS_MACOSX_H__ 7 | #define __HIREDIS_MACOSX_H__ 8 | 9 | #include 10 | 11 | #include "../hiredis.h" 12 | #include "../async.h" 13 | 14 | typedef struct { 15 | redisAsyncContext *context; 16 | CFSocketRef socketRef; 17 | CFRunLoopSourceRef sourceRef; 18 | } RedisRunLoop; 19 | 20 | static int freeRedisRunLoop(RedisRunLoop* redisRunLoop) { 21 | if( redisRunLoop != NULL ) { 22 | if( redisRunLoop->sourceRef != NULL ) { 23 | CFRunLoopSourceInvalidate(redisRunLoop->sourceRef); 24 | CFRelease(redisRunLoop->sourceRef); 25 | } 26 | if( redisRunLoop->socketRef != NULL ) { 27 | CFSocketInvalidate(redisRunLoop->socketRef); 28 | CFRelease(redisRunLoop->socketRef); 29 | } 30 | hi_free(redisRunLoop); 31 | } 32 | return REDIS_ERR; 33 | } 34 | 35 | static void redisMacOSAddRead(void *privdata) { 36 | RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata; 37 | CFSocketEnableCallBacks(redisRunLoop->socketRef, kCFSocketReadCallBack); 38 | } 39 | 40 | static void redisMacOSDelRead(void *privdata) { 41 | RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata; 42 | CFSocketDisableCallBacks(redisRunLoop->socketRef, kCFSocketReadCallBack); 43 | } 44 | 45 | static void redisMacOSAddWrite(void *privdata) { 46 | RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata; 47 | CFSocketEnableCallBacks(redisRunLoop->socketRef, kCFSocketWriteCallBack); 48 | } 49 | 50 | static void redisMacOSDelWrite(void *privdata) { 51 | RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata; 52 | CFSocketDisableCallBacks(redisRunLoop->socketRef, kCFSocketWriteCallBack); 53 | } 54 | 55 | static void redisMacOSCleanup(void *privdata) { 56 | RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata; 57 | freeRedisRunLoop(redisRunLoop); 58 | } 59 | 60 | static void redisMacOSAsyncCallback(CFSocketRef __unused s, CFSocketCallBackType callbackType, CFDataRef __unused address, const void __unused *data, void *info) { 61 | redisAsyncContext* context = (redisAsyncContext*) info; 62 | 63 | switch (callbackType) { 64 | case kCFSocketReadCallBack: 65 | redisAsyncHandleRead(context); 66 | break; 67 | 68 | case kCFSocketWriteCallBack: 69 | redisAsyncHandleWrite(context); 70 | break; 71 | 72 | default: 73 | break; 74 | } 75 | } 76 | 77 | static int redisMacOSAttach(redisAsyncContext *redisAsyncCtx, CFRunLoopRef runLoop) { 78 | redisContext *redisCtx = &(redisAsyncCtx->c); 79 | 80 | /* Nothing should be attached when something is already attached */ 81 | if( redisAsyncCtx->ev.data != NULL ) return REDIS_ERR; 82 | 83 | RedisRunLoop* redisRunLoop = (RedisRunLoop*) hi_calloc(1, sizeof(RedisRunLoop)); 84 | if (redisRunLoop == NULL) 85 | return REDIS_ERR; 86 | 87 | /* Setup redis stuff */ 88 | redisRunLoop->context = redisAsyncCtx; 89 | 90 | redisAsyncCtx->ev.addRead = redisMacOSAddRead; 91 | redisAsyncCtx->ev.delRead = redisMacOSDelRead; 92 | redisAsyncCtx->ev.addWrite = redisMacOSAddWrite; 93 | redisAsyncCtx->ev.delWrite = redisMacOSDelWrite; 94 | redisAsyncCtx->ev.cleanup = redisMacOSCleanup; 95 | redisAsyncCtx->ev.data = redisRunLoop; 96 | 97 | /* Initialize and install read/write events */ 98 | CFSocketContext socketCtx = { 0, redisAsyncCtx, NULL, NULL, NULL }; 99 | 100 | redisRunLoop->socketRef = CFSocketCreateWithNative(NULL, redisCtx->fd, 101 | kCFSocketReadCallBack | kCFSocketWriteCallBack, 102 | redisMacOSAsyncCallback, 103 | &socketCtx); 104 | if( !redisRunLoop->socketRef ) return freeRedisRunLoop(redisRunLoop); 105 | 106 | redisRunLoop->sourceRef = CFSocketCreateRunLoopSource(NULL, redisRunLoop->socketRef, 0); 107 | if( !redisRunLoop->sourceRef ) return freeRedisRunLoop(redisRunLoop); 108 | 109 | CFRunLoopAddSource(runLoop, redisRunLoop->sourceRef, kCFRunLoopDefaultMode); 110 | 111 | return REDIS_OK; 112 | } 113 | 114 | #endif 115 | 116 | -------------------------------------------------------------------------------- /hiredis-client/ext/redis_client/hiredis/vendor/adapters/qt.h: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (C) 2014 Pietro Cerutti 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions 6 | * are met: 7 | * 1. Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * 2. Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * 13 | * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND 14 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE 17 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 18 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 19 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 20 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 21 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 22 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 23 | * SUCH DAMAGE. 24 | */ 25 | 26 | #ifndef __HIREDIS_QT_H__ 27 | #define __HIREDIS_QT_H__ 28 | #include 29 | #include "../async.h" 30 | 31 | static void RedisQtAddRead(void *); 32 | static void RedisQtDelRead(void *); 33 | static void RedisQtAddWrite(void *); 34 | static void RedisQtDelWrite(void *); 35 | static void RedisQtCleanup(void *); 36 | 37 | class RedisQtAdapter : public QObject { 38 | 39 | Q_OBJECT 40 | 41 | friend 42 | void RedisQtAddRead(void * adapter) { 43 | RedisQtAdapter * a = static_cast(adapter); 44 | a->addRead(); 45 | } 46 | 47 | friend 48 | void RedisQtDelRead(void * adapter) { 49 | RedisQtAdapter * a = static_cast(adapter); 50 | a->delRead(); 51 | } 52 | 53 | friend 54 | void RedisQtAddWrite(void * adapter) { 55 | RedisQtAdapter * a = static_cast(adapter); 56 | a->addWrite(); 57 | } 58 | 59 | friend 60 | void RedisQtDelWrite(void * adapter) { 61 | RedisQtAdapter * a = static_cast(adapter); 62 | a->delWrite(); 63 | } 64 | 65 | friend 66 | void RedisQtCleanup(void * adapter) { 67 | RedisQtAdapter * a = static_cast(adapter); 68 | a->cleanup(); 69 | } 70 | 71 | public: 72 | RedisQtAdapter(QObject * parent = 0) 73 | : QObject(parent), m_ctx(0), m_read(0), m_write(0) { } 74 | 75 | ~RedisQtAdapter() { 76 | if (m_ctx != 0) { 77 | m_ctx->ev.data = NULL; 78 | } 79 | } 80 | 81 | int setContext(redisAsyncContext * ac) { 82 | if (ac->ev.data != NULL) { 83 | return REDIS_ERR; 84 | } 85 | m_ctx = ac; 86 | m_ctx->ev.data = this; 87 | m_ctx->ev.addRead = RedisQtAddRead; 88 | m_ctx->ev.delRead = RedisQtDelRead; 89 | m_ctx->ev.addWrite = RedisQtAddWrite; 90 | m_ctx->ev.delWrite = RedisQtDelWrite; 91 | m_ctx->ev.cleanup = RedisQtCleanup; 92 | return REDIS_OK; 93 | } 94 | 95 | private: 96 | void addRead() { 97 | if (m_read) return; 98 | m_read = new QSocketNotifier(m_ctx->c.fd, QSocketNotifier::Read, 0); 99 | connect(m_read, SIGNAL(activated(int)), this, SLOT(read())); 100 | } 101 | 102 | void delRead() { 103 | if (!m_read) return; 104 | delete m_read; 105 | m_read = 0; 106 | } 107 | 108 | void addWrite() { 109 | if (m_write) return; 110 | m_write = new QSocketNotifier(m_ctx->c.fd, QSocketNotifier::Write, 0); 111 | connect(m_write, SIGNAL(activated(int)), this, SLOT(write())); 112 | } 113 | 114 | void delWrite() { 115 | if (!m_write) return; 116 | delete m_write; 117 | m_write = 0; 118 | } 119 | 120 | void cleanup() { 121 | delRead(); 122 | delWrite(); 123 | } 124 | 125 | private slots: 126 | void read() { redisAsyncHandleRead(m_ctx); } 127 | void write() { redisAsyncHandleWrite(m_ctx); } 128 | 129 | private: 130 | redisAsyncContext * m_ctx; 131 | QSocketNotifier * m_read; 132 | QSocketNotifier * m_write; 133 | }; 134 | 135 | #endif /* !__HIREDIS_QT_H__ */ 136 | -------------------------------------------------------------------------------- /hiredis-client/ext/redis_client/hiredis/vendor/alloc.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, Michael Grunder 3 | * 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions are met: 8 | * 9 | * * Redistributions of source code must retain the above copyright notice, 10 | * this list of conditions and the following disclaimer. 11 | * * Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in the 13 | * documentation and/or other materials provided with the distribution. 14 | * * Neither the name of Redis nor the names of its contributors may be used 15 | * to endorse or promote products derived from this software without 16 | * specific prior written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 22 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 | * POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | 31 | #include "fmacros.h" 32 | #include "alloc.h" 33 | #include 34 | #include 35 | 36 | hiredisAllocFuncs hiredisAllocFns = { 37 | .mallocFn = malloc, 38 | .callocFn = calloc, 39 | .reallocFn = realloc, 40 | .strdupFn = strdup, 41 | .freeFn = free, 42 | }; 43 | 44 | /* Override hiredis' allocators with ones supplied by the user */ 45 | hiredisAllocFuncs hiredisSetAllocators(hiredisAllocFuncs *override) { 46 | hiredisAllocFuncs orig = hiredisAllocFns; 47 | 48 | hiredisAllocFns = *override; 49 | 50 | return orig; 51 | } 52 | 53 | /* Reset allocators to use libc defaults */ 54 | void hiredisResetAllocators(void) { 55 | hiredisAllocFns = (hiredisAllocFuncs) { 56 | .mallocFn = malloc, 57 | .callocFn = calloc, 58 | .reallocFn = realloc, 59 | .strdupFn = strdup, 60 | .freeFn = free, 61 | }; 62 | } 63 | 64 | #ifdef _WIN32 65 | 66 | void *hi_malloc(size_t size) { 67 | return hiredisAllocFns.mallocFn(size); 68 | } 69 | 70 | void *hi_calloc(size_t nmemb, size_t size) { 71 | return hiredisAllocFns.callocFn(nmemb, size); 72 | } 73 | 74 | void *hi_realloc(void *ptr, size_t size) { 75 | return hiredisAllocFns.reallocFn(ptr, size); 76 | } 77 | 78 | char *hi_strdup(const char *str) { 79 | return hiredisAllocFns.strdupFn(str); 80 | } 81 | 82 | void hi_free(void *ptr) { 83 | hiredisAllocFns.freeFn(ptr); 84 | } 85 | 86 | #endif 87 | -------------------------------------------------------------------------------- /hiredis-client/ext/redis_client/hiredis/vendor/alloc.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, Michael Grunder 3 | * 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions are met: 8 | * 9 | * * Redistributions of source code must retain the above copyright notice, 10 | * this list of conditions and the following disclaimer. 11 | * * Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in the 13 | * documentation and/or other materials provided with the distribution. 14 | * * Neither the name of Redis nor the names of its contributors may be used 15 | * to endorse or promote products derived from this software without 16 | * specific prior written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 22 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 | * POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | 31 | #ifndef HIREDIS_ALLOC_H 32 | #define HIREDIS_ALLOC_H 33 | 34 | #include /* for size_t */ 35 | 36 | #ifdef __cplusplus 37 | extern "C" { 38 | #endif 39 | 40 | /* Structure pointing to our actually configured allocators */ 41 | typedef struct hiredisAllocFuncs { 42 | void *(*mallocFn)(size_t); 43 | void *(*callocFn)(size_t,size_t); 44 | void *(*reallocFn)(void*,size_t); 45 | char *(*strdupFn)(const char*); 46 | void (*freeFn)(void*); 47 | } hiredisAllocFuncs; 48 | 49 | hiredisAllocFuncs hiredisSetAllocators(hiredisAllocFuncs *ha); 50 | void hiredisResetAllocators(void); 51 | 52 | #ifndef _WIN32 53 | 54 | /* Hiredis' configured allocator function pointer struct */ 55 | extern hiredisAllocFuncs hiredisAllocFns; 56 | 57 | static inline void *hi_malloc(size_t size) { 58 | return hiredisAllocFns.mallocFn(size); 59 | } 60 | 61 | static inline void *hi_calloc(size_t nmemb, size_t size) { 62 | return hiredisAllocFns.callocFn(nmemb, size); 63 | } 64 | 65 | static inline void *hi_realloc(void *ptr, size_t size) { 66 | return hiredisAllocFns.reallocFn(ptr, size); 67 | } 68 | 69 | static inline char *hi_strdup(const char *str) { 70 | return hiredisAllocFns.strdupFn(str); 71 | } 72 | 73 | static inline void hi_free(void *ptr) { 74 | hiredisAllocFns.freeFn(ptr); 75 | } 76 | 77 | #else 78 | 79 | void *hi_malloc(size_t size); 80 | void *hi_calloc(size_t nmemb, size_t size); 81 | void *hi_realloc(void *ptr, size_t size); 82 | char *hi_strdup(const char *str); 83 | void hi_free(void *ptr); 84 | 85 | #endif 86 | 87 | #ifdef __cplusplus 88 | } 89 | #endif 90 | 91 | #endif /* HIREDIS_ALLOC_H */ 92 | -------------------------------------------------------------------------------- /hiredis-client/ext/redis_client/hiredis/vendor/appveyor.yml: -------------------------------------------------------------------------------- 1 | # Appveyor configuration file for CI build of hiredis on Windows (under Cygwin) 2 | environment: 3 | matrix: 4 | - CYG_BASH: C:\cygwin64\bin\bash 5 | CC: gcc 6 | - CYG_BASH: C:\cygwin\bin\bash 7 | CC: gcc 8 | CFLAGS: -m32 9 | CXXFLAGS: -m32 10 | LDFLAGS: -m32 11 | 12 | clone_depth: 1 13 | 14 | # Attempt to ensure we don't try to convert line endings to Win32 CRLF as this will cause build to fail 15 | init: 16 | - git config --global core.autocrlf input 17 | 18 | # Install needed build dependencies 19 | install: 20 | - '%CYG_BASH% -lc "cygcheck -dc cygwin"' 21 | 22 | build_script: 23 | - 'echo building...' 24 | - '%CYG_BASH% -lc "cd $APPVEYOR_BUILD_FOLDER; exec 0 3 | * Copyright (c) 2010-2011, Pieter Noordhuis 4 | * 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * * Redistributions of source code must retain the above copyright notice, 11 | * this list of conditions and the following disclaimer. 12 | * * Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * * Neither the name of Redis nor the names of its contributors may be used 16 | * to endorse or promote products derived from this software without 17 | * specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 23 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | * POSSIBILITY OF SUCH DAMAGE. 30 | */ 31 | 32 | #ifndef __HIREDIS_ASYNC_PRIVATE_H 33 | #define __HIREDIS_ASYNC_PRIVATE_H 34 | 35 | #define _EL_ADD_READ(ctx) \ 36 | do { \ 37 | refreshTimeout(ctx); \ 38 | if ((ctx)->ev.addRead) (ctx)->ev.addRead((ctx)->ev.data); \ 39 | } while (0) 40 | #define _EL_DEL_READ(ctx) do { \ 41 | if ((ctx)->ev.delRead) (ctx)->ev.delRead((ctx)->ev.data); \ 42 | } while(0) 43 | #define _EL_ADD_WRITE(ctx) \ 44 | do { \ 45 | refreshTimeout(ctx); \ 46 | if ((ctx)->ev.addWrite) (ctx)->ev.addWrite((ctx)->ev.data); \ 47 | } while (0) 48 | #define _EL_DEL_WRITE(ctx) do { \ 49 | if ((ctx)->ev.delWrite) (ctx)->ev.delWrite((ctx)->ev.data); \ 50 | } while(0) 51 | #define _EL_CLEANUP(ctx) do { \ 52 | if ((ctx)->ev.cleanup) (ctx)->ev.cleanup((ctx)->ev.data); \ 53 | ctx->ev.cleanup = NULL; \ 54 | } while(0); 55 | 56 | static inline void refreshTimeout(redisAsyncContext *ctx) { 57 | #define REDIS_TIMER_ISSET(tvp) \ 58 | (tvp && ((tvp)->tv_sec || (tvp)->tv_usec)) 59 | 60 | #define REDIS_EL_TIMER(ac, tvp) \ 61 | if ((ac)->ev.scheduleTimer && REDIS_TIMER_ISSET(tvp)) { \ 62 | (ac)->ev.scheduleTimer((ac)->ev.data, *(tvp)); \ 63 | } 64 | 65 | if (ctx->c.flags & REDIS_CONNECTED) { 66 | REDIS_EL_TIMER(ctx, ctx->c.command_timeout); 67 | } else { 68 | REDIS_EL_TIMER(ctx, ctx->c.connect_timeout); 69 | } 70 | } 71 | 72 | void __redisAsyncDisconnect(redisAsyncContext *ac); 73 | void redisProcessCallbacks(redisAsyncContext *ac); 74 | 75 | #endif /* __HIREDIS_ASYNC_PRIVATE_H */ 76 | -------------------------------------------------------------------------------- /hiredis-client/ext/redis_client/hiredis/vendor/dict.h: -------------------------------------------------------------------------------- 1 | /* Hash table implementation. 2 | * 3 | * This file implements in memory hash tables with insert/del/replace/find/ 4 | * get-random-element operations. Hash tables will auto resize if needed 5 | * tables of power of two in size are used, collisions are handled by 6 | * chaining. See the source code for more information... :) 7 | * 8 | * Copyright (c) 2006-2010, Salvatore Sanfilippo 9 | * All rights reserved. 10 | * 11 | * Redistribution and use in source and binary forms, with or without 12 | * modification, are permitted provided that the following conditions are met: 13 | * 14 | * * Redistributions of source code must retain the above copyright notice, 15 | * this list of conditions and the following disclaimer. 16 | * * Redistributions in binary form must reproduce the above copyright 17 | * notice, this list of conditions and the following disclaimer in the 18 | * documentation and/or other materials provided with the distribution. 19 | * * Neither the name of Redis nor the names of its contributors may be used 20 | * to endorse or promote products derived from this software without 21 | * specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 24 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 25 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 26 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 27 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 28 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 29 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 30 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 31 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 32 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 33 | * POSSIBILITY OF SUCH DAMAGE. 34 | */ 35 | 36 | #ifndef __DICT_H 37 | #define __DICT_H 38 | 39 | #define DICT_OK 0 40 | #define DICT_ERR 1 41 | 42 | /* Unused arguments generate annoying warnings... */ 43 | #define DICT_NOTUSED(V) ((void) V) 44 | 45 | typedef struct dictEntry { 46 | void *key; 47 | void *val; 48 | struct dictEntry *next; 49 | } dictEntry; 50 | 51 | typedef struct dictType { 52 | unsigned int (*hashFunction)(const void *key); 53 | void *(*keyDup)(void *privdata, const void *key); 54 | void *(*valDup)(void *privdata, const void *obj); 55 | int (*keyCompare)(void *privdata, const void *key1, const void *key2); 56 | void (*keyDestructor)(void *privdata, void *key); 57 | void (*valDestructor)(void *privdata, void *obj); 58 | } dictType; 59 | 60 | typedef struct dict { 61 | dictEntry **table; 62 | dictType *type; 63 | unsigned long size; 64 | unsigned long sizemask; 65 | unsigned long used; 66 | void *privdata; 67 | } dict; 68 | 69 | typedef struct dictIterator { 70 | dict *ht; 71 | int index; 72 | dictEntry *entry, *nextEntry; 73 | } dictIterator; 74 | 75 | /* This is the initial size of every hash table */ 76 | #define DICT_HT_INITIAL_SIZE 4 77 | 78 | /* ------------------------------- Macros ------------------------------------*/ 79 | #define dictFreeEntryVal(ht, entry) \ 80 | if ((ht)->type->valDestructor) \ 81 | (ht)->type->valDestructor((ht)->privdata, (entry)->val) 82 | 83 | #define dictSetHashVal(ht, entry, _val_) do { \ 84 | if ((ht)->type->valDup) \ 85 | entry->val = (ht)->type->valDup((ht)->privdata, _val_); \ 86 | else \ 87 | entry->val = (_val_); \ 88 | } while(0) 89 | 90 | #define dictFreeEntryKey(ht, entry) \ 91 | if ((ht)->type->keyDestructor) \ 92 | (ht)->type->keyDestructor((ht)->privdata, (entry)->key) 93 | 94 | #define dictSetHashKey(ht, entry, _key_) do { \ 95 | if ((ht)->type->keyDup) \ 96 | entry->key = (ht)->type->keyDup((ht)->privdata, _key_); \ 97 | else \ 98 | entry->key = (_key_); \ 99 | } while(0) 100 | 101 | #define dictCompareHashKeys(ht, key1, key2) \ 102 | (((ht)->type->keyCompare) ? \ 103 | (ht)->type->keyCompare((ht)->privdata, key1, key2) : \ 104 | (key1) == (key2)) 105 | 106 | #define dictHashKey(ht, key) (ht)->type->hashFunction(key) 107 | 108 | #define dictGetEntryKey(he) ((he)->key) 109 | #define dictGetEntryVal(he) ((he)->val) 110 | #define dictSlots(ht) ((ht)->size) 111 | #define dictSize(ht) ((ht)->used) 112 | 113 | /* API */ 114 | static unsigned int dictGenHashFunction(const unsigned char *buf, int len); 115 | static dict *dictCreate(dictType *type, void *privDataPtr); 116 | static int dictExpand(dict *ht, unsigned long size); 117 | static int dictAdd(dict *ht, void *key, void *val); 118 | static int dictReplace(dict *ht, void *key, void *val); 119 | static int dictDelete(dict *ht, const void *key); 120 | static void dictRelease(dict *ht); 121 | static dictEntry * dictFind(dict *ht, const void *key); 122 | static dictIterator *dictGetIterator(dict *ht); 123 | static dictEntry *dictNext(dictIterator *iter); 124 | static void dictReleaseIterator(dictIterator *iter); 125 | 126 | #endif /* __DICT_H */ 127 | -------------------------------------------------------------------------------- /hiredis-client/ext/redis_client/hiredis/vendor/fmacros.h: -------------------------------------------------------------------------------- 1 | #ifndef __HIREDIS_FMACRO_H 2 | #define __HIREDIS_FMACRO_H 3 | 4 | #define _XOPEN_SOURCE 600 5 | #define _POSIX_C_SOURCE 200112L 6 | 7 | #if defined(__APPLE__) && defined(__MACH__) 8 | /* Enable TCP_KEEPALIVE */ 9 | #define _DARWIN_C_SOURCE 10 | #endif 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /hiredis-client/ext/redis_client/hiredis/vendor/hiredis-config.cmake.in: -------------------------------------------------------------------------------- 1 | @PACKAGE_INIT@ 2 | 3 | set_and_check(hiredis_INCLUDEDIR "@PACKAGE_INCLUDE_INSTALL_DIR@") 4 | 5 | IF (NOT TARGET hiredis::hiredis) 6 | INCLUDE(${CMAKE_CURRENT_LIST_DIR}/hiredis-targets.cmake) 7 | ENDIF() 8 | 9 | SET(hiredis_LIBRARIES hiredis::hiredis) 10 | SET(hiredis_INCLUDE_DIRS ${hiredis_INCLUDEDIR}) 11 | 12 | check_required_components(hiredis) 13 | 14 | -------------------------------------------------------------------------------- /hiredis-client/ext/redis_client/hiredis/vendor/hiredis.pc.in: -------------------------------------------------------------------------------- 1 | prefix=@CMAKE_INSTALL_PREFIX@ 2 | install_libdir=@CMAKE_INSTALL_LIBDIR@ 3 | exec_prefix=${prefix} 4 | libdir=${exec_prefix}/${install_libdir} 5 | includedir=${prefix}/include 6 | pkgincludedir=${includedir}/hiredis 7 | 8 | Name: hiredis 9 | Description: Minimalistic C client library for Redis. 10 | Version: @PROJECT_VERSION@ 11 | Libs: -L${libdir} -lhiredis 12 | Cflags: -I${pkgincludedir} -D_FILE_OFFSET_BITS=64 13 | -------------------------------------------------------------------------------- /hiredis-client/ext/redis_client/hiredis/vendor/hiredis_ssl-config.cmake.in: -------------------------------------------------------------------------------- 1 | @PACKAGE_INIT@ 2 | 3 | set_and_check(hiredis_ssl_INCLUDEDIR "@PACKAGE_INCLUDE_INSTALL_DIR@") 4 | 5 | IF (NOT TARGET hiredis::hiredis_ssl) 6 | INCLUDE(${CMAKE_CURRENT_LIST_DIR}/hiredis_ssl-targets.cmake) 7 | ENDIF() 8 | 9 | SET(hiredis_ssl_LIBRARIES hiredis::hiredis_ssl) 10 | SET(hiredis_ssl_INCLUDE_DIRS ${hiredis_ssl_INCLUDEDIR}) 11 | 12 | check_required_components(hiredis_ssl) 13 | 14 | -------------------------------------------------------------------------------- /hiredis-client/ext/redis_client/hiredis/vendor/hiredis_ssl.h: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (c) 2019, Redis Labs 4 | * 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * * Redistributions of source code must retain the above copyright notice, 11 | * this list of conditions and the following disclaimer. 12 | * * Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * * Neither the name of Redis nor the names of its contributors may be used 16 | * to endorse or promote products derived from this software without 17 | * specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 23 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | * POSSIBILITY OF SUCH DAMAGE. 30 | */ 31 | 32 | #ifndef __HIREDIS_SSL_H 33 | #define __HIREDIS_SSL_H 34 | 35 | #include 36 | 37 | #ifdef __cplusplus 38 | extern "C" { 39 | #endif 40 | 41 | /* This is the underlying struct for SSL in ssl.h, which is not included to 42 | * keep build dependencies short here. 43 | */ 44 | struct ssl_st; 45 | 46 | /* A wrapper around OpenSSL SSL_CTX to allow easy SSL use without directly 47 | * calling OpenSSL. 48 | */ 49 | typedef struct redisSSLContext redisSSLContext; 50 | 51 | /* The SSL connection context is attached to SSL/TLS connections as a privdata. */ 52 | typedef struct redisSSL { 53 | /** 54 | * OpenSSL SSL object. 55 | */ 56 | SSL *ssl; 57 | 58 | /** 59 | * SSL_write() requires to be called again with the same arguments it was 60 | * previously called with in the event of an SSL_read/SSL_write situation 61 | */ 62 | size_t lastLen; 63 | 64 | /** Whether the SSL layer requires read (possibly before a write) */ 65 | int wantRead; 66 | 67 | /** 68 | * Whether a write was requested prior to a read. If set, the write() 69 | * should resume whenever a read takes place, if possible 70 | */ 71 | int pendingWrite; 72 | } redisSSL; 73 | 74 | /** 75 | * Initialization errors that redisCreateSSLContext() may return. 76 | */ 77 | 78 | typedef enum { 79 | REDIS_SSL_CTX_NONE = 0, /* No Error */ 80 | REDIS_SSL_CTX_CREATE_FAILED, /* Failed to create OpenSSL SSL_CTX */ 81 | REDIS_SSL_CTX_CERT_KEY_REQUIRED, /* Client cert and key must both be specified or skipped */ 82 | REDIS_SSL_CTX_CA_CERT_LOAD_FAILED, /* Failed to load CA Certificate or CA Path */ 83 | REDIS_SSL_CTX_CLIENT_CERT_LOAD_FAILED, /* Failed to load client certificate */ 84 | REDIS_SSL_CTX_PRIVATE_KEY_LOAD_FAILED /* Failed to load private key */ 85 | } redisSSLContextError; 86 | 87 | /** 88 | * Return the error message corresponding with the specified error code. 89 | */ 90 | 91 | const char *redisSSLContextGetError(redisSSLContextError error); 92 | 93 | /** 94 | * Helper function to initialize the OpenSSL library. 95 | * 96 | * OpenSSL requires one-time initialization before it can be used. Callers should 97 | * call this function only once, and only if OpenSSL is not directly initialized 98 | * elsewhere. 99 | */ 100 | int redisInitOpenSSL(void); 101 | 102 | /** 103 | * Helper function to initialize an OpenSSL context that can be used 104 | * to initiate SSL connections. 105 | * 106 | * cacert_filename is an optional name of a CA certificate/bundle file to load 107 | * and use for validation. 108 | * 109 | * capath is an optional directory path where trusted CA certificate files are 110 | * stored in an OpenSSL-compatible structure. 111 | * 112 | * cert_filename and private_key_filename are optional names of a client side 113 | * certificate and private key files to use for authentication. They need to 114 | * be both specified or omitted. 115 | * 116 | * server_name is an optional and will be used as a server name indication 117 | * (SNI) TLS extension. 118 | * 119 | * If error is non-null, it will be populated in case the context creation fails 120 | * (returning a NULL). 121 | */ 122 | 123 | redisSSLContext *redisCreateSSLContext(const char *cacert_filename, const char *capath, 124 | const char *cert_filename, const char *private_key_filename, 125 | const char *server_name, redisSSLContextError *error); 126 | 127 | /** 128 | * Free a previously created OpenSSL context. 129 | */ 130 | void redisFreeSSLContext(redisSSLContext *redis_ssl_ctx); 131 | 132 | /** 133 | * Initiate SSL on an existing redisContext. 134 | * 135 | * This is similar to redisInitiateSSL() but does not require the caller 136 | * to directly interact with OpenSSL, and instead uses a redisSSLContext 137 | * previously created using redisCreateSSLContext(). 138 | */ 139 | 140 | int redisInitiateSSLWithContext(redisContext *c, redisSSLContext *redis_ssl_ctx); 141 | 142 | int redisInitiateSSLContinue(redisContext *c); 143 | 144 | /** 145 | * Initiate SSL/TLS negotiation on a provided OpenSSL SSL object. 146 | */ 147 | 148 | int redisInitiateSSL(redisContext *c, struct ssl_st *ssl); 149 | 150 | 151 | redisSSL *redisGetSSLSocket(redisContext *c); 152 | 153 | #ifdef __cplusplus 154 | } 155 | #endif 156 | 157 | #endif /* __HIREDIS_SSL_H */ 158 | -------------------------------------------------------------------------------- /hiredis-client/ext/redis_client/hiredis/vendor/hiredis_ssl.pc.in: -------------------------------------------------------------------------------- 1 | prefix=@CMAKE_INSTALL_PREFIX@ 2 | exec_prefix=${prefix} 3 | libdir=${exec_prefix}/lib 4 | includedir=${prefix}/include 5 | pkgincludedir=${includedir}/hiredis 6 | 7 | Name: hiredis_ssl 8 | Description: SSL Support for hiredis. 9 | Version: @PROJECT_VERSION@ 10 | Requires: hiredis 11 | Libs: -L${libdir} -lhiredis_ssl 12 | Libs.private: -lssl -lcrypto 13 | -------------------------------------------------------------------------------- /hiredis-client/ext/redis_client/hiredis/vendor/net.h: -------------------------------------------------------------------------------- 1 | /* Extracted from anet.c to work properly with Hiredis error reporting. 2 | * 3 | * Copyright (c) 2009-2011, Salvatore Sanfilippo 4 | * Copyright (c) 2010-2014, Pieter Noordhuis 5 | * Copyright (c) 2015, Matt Stancliff , 6 | * Jan-Erik Rediger 7 | * 8 | * All rights reserved. 9 | * 10 | * Redistribution and use in source and binary forms, with or without 11 | * modification, are permitted provided that the following conditions are met: 12 | * 13 | * * Redistributions of source code must retain the above copyright notice, 14 | * this list of conditions and the following disclaimer. 15 | * * Redistributions in binary form must reproduce the above copyright 16 | * notice, this list of conditions and the following disclaimer in the 17 | * documentation and/or other materials provided with the distribution. 18 | * * Neither the name of Redis nor the names of its contributors may be used 19 | * to endorse or promote products derived from this software without 20 | * specific prior written permission. 21 | * 22 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 23 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 25 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 26 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 27 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 28 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 29 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 30 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 31 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 32 | * POSSIBILITY OF SUCH DAMAGE. 33 | */ 34 | 35 | #ifndef __NET_H 36 | #define __NET_H 37 | 38 | #include "hiredis.h" 39 | 40 | void redisNetClose(redisContext *c); 41 | ssize_t redisNetRead(redisContext *c, char *buf, size_t bufcap); 42 | ssize_t redisNetWrite(redisContext *c); 43 | 44 | int redisCheckSocketError(redisContext *c); 45 | int redisContextSetTimeout(redisContext *c, const struct timeval tv); 46 | int redisContextConnectTcp(redisContext *c, const char *addr, int port, const struct timeval *timeout); 47 | int redisContextConnectBindTcp(redisContext *c, const char *addr, int port, 48 | const struct timeval *timeout, 49 | const char *source_addr); 50 | int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout); 51 | int redisKeepAlive(redisContext *c, int ttl, int interval); 52 | int redisCheckConnectDone(redisContext *c, int *completed); 53 | 54 | int redisSetTcpNoDelay(redisContext *c); 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /hiredis-client/ext/redis_client/hiredis/vendor/read.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2009-2011, Salvatore Sanfilippo 3 | * Copyright (c) 2010-2011, Pieter Noordhuis 4 | * 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * * Redistributions of source code must retain the above copyright notice, 11 | * this list of conditions and the following disclaimer. 12 | * * Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * * Neither the name of Redis nor the names of its contributors may be used 16 | * to endorse or promote products derived from this software without 17 | * specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 23 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | * POSSIBILITY OF SUCH DAMAGE. 30 | */ 31 | 32 | 33 | #ifndef __HIREDIS_READ_H 34 | #define __HIREDIS_READ_H 35 | #include /* for size_t */ 36 | 37 | #define REDIS_ERR -1 38 | #define REDIS_OK 0 39 | 40 | /* When an error occurs, the err flag in a context is set to hold the type of 41 | * error that occurred. REDIS_ERR_IO means there was an I/O error and you 42 | * should use the "errno" variable to find out what is wrong. 43 | * For other values, the "errstr" field will hold a description. */ 44 | #define REDIS_ERR_IO 1 /* Error in read or write */ 45 | #define REDIS_ERR_EOF 3 /* End of file */ 46 | #define REDIS_ERR_PROTOCOL 4 /* Protocol error */ 47 | #define REDIS_ERR_OOM 5 /* Out of memory */ 48 | #define REDIS_ERR_TIMEOUT 6 /* Timed out */ 49 | #define REDIS_ERR_OTHER 2 /* Everything else... */ 50 | 51 | #define REDIS_REPLY_STRING 1 52 | #define REDIS_REPLY_ARRAY 2 53 | #define REDIS_REPLY_INTEGER 3 54 | #define REDIS_REPLY_NIL 4 55 | #define REDIS_REPLY_STATUS 5 56 | #define REDIS_REPLY_ERROR 6 57 | #define REDIS_REPLY_DOUBLE 7 58 | #define REDIS_REPLY_BOOL 8 59 | #define REDIS_REPLY_MAP 9 60 | #define REDIS_REPLY_SET 10 61 | #define REDIS_REPLY_ATTR 11 62 | #define REDIS_REPLY_PUSH 12 63 | #define REDIS_REPLY_BIGNUM 13 64 | #define REDIS_REPLY_VERB 14 65 | 66 | /* Default max unused reader buffer. */ 67 | #define REDIS_READER_MAX_BUF (1024*16) 68 | 69 | /* Default multi-bulk element limit */ 70 | #define REDIS_READER_MAX_ARRAY_ELEMENTS ((1LL<<32) - 1) 71 | 72 | #ifdef __cplusplus 73 | extern "C" { 74 | #endif 75 | 76 | typedef struct redisReadTask { 77 | int type; 78 | long long elements; /* number of elements in multibulk container */ 79 | int idx; /* index in parent (array) object */ 80 | void *obj; /* holds user-generated value for a read task */ 81 | struct redisReadTask *parent; /* parent task */ 82 | void *privdata; /* user-settable arbitrary field */ 83 | } redisReadTask; 84 | 85 | typedef struct redisReplyObjectFunctions { 86 | void *(*createString)(const redisReadTask*, char*, size_t); 87 | void *(*createArray)(const redisReadTask*, size_t); 88 | void *(*createInteger)(const redisReadTask*, long long); 89 | void *(*createDouble)(const redisReadTask*, double, char*, size_t); 90 | void *(*createNil)(const redisReadTask*); 91 | void *(*createBool)(const redisReadTask*, int); 92 | void (*freeObject)(void*); 93 | } redisReplyObjectFunctions; 94 | 95 | typedef struct redisReader { 96 | int err; /* Error flags, 0 when there is no error */ 97 | char errstr[128]; /* String representation of error when applicable */ 98 | 99 | char *buf; /* Read buffer */ 100 | size_t pos; /* Buffer cursor */ 101 | size_t len; /* Buffer length */ 102 | size_t maxbuf; /* Max length of unused buffer */ 103 | long long maxelements; /* Max multi-bulk elements */ 104 | 105 | redisReadTask **task; 106 | int tasks; 107 | 108 | int ridx; /* Index of current read task */ 109 | void *reply; /* Temporary reply pointer */ 110 | 111 | redisReplyObjectFunctions *fn; 112 | void *privdata; 113 | } redisReader; 114 | 115 | /* Public API for the protocol parser. */ 116 | redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn); 117 | void redisReaderFree(redisReader *r); 118 | int redisReaderFeed(redisReader *r, const char *buf, size_t len); 119 | int redisReaderGetReply(redisReader *r, void **reply); 120 | 121 | #define redisReaderSetPrivdata(_r, _p) (int)(((redisReader*)(_r))->privdata = (_p)) 122 | #define redisReaderGetObject(_r) (((redisReader*)(_r))->reply) 123 | #define redisReaderGetError(_r) (((redisReader*)(_r))->errstr) 124 | 125 | #ifdef __cplusplus 126 | } 127 | #endif 128 | 129 | #endif 130 | -------------------------------------------------------------------------------- /hiredis-client/ext/redis_client/hiredis/vendor/sdsalloc.h: -------------------------------------------------------------------------------- 1 | /* SDSLib 2.0 -- A C dynamic strings library 2 | * 3 | * Copyright (c) 2006-2015, Salvatore Sanfilippo 4 | * Copyright (c) 2015, Oran Agra 5 | * Copyright (c) 2015, Redis Labs, Inc 6 | * All rights reserved. 7 | * 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions are met: 10 | * 11 | * * Redistributions of source code must retain the above copyright notice, 12 | * this list of conditions and the following disclaimer. 13 | * * Redistributions in binary form must reproduce the above copyright 14 | * notice, this list of conditions and the following disclaimer in the 15 | * documentation and/or other materials provided with the distribution. 16 | * * Neither the name of Redis nor the names of its contributors may be used 17 | * to endorse or promote products derived from this software without 18 | * specific prior written permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 24 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 | * POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | /* SDS allocator selection. 34 | * 35 | * This file is used in order to change the SDS allocator at compile time. 36 | * Just define the following defines to what you want to use. Also add 37 | * the include of your alternate allocator if needed (not needed in order 38 | * to use the default libc allocator). */ 39 | 40 | #include "alloc.h" 41 | 42 | #define s_malloc hi_malloc 43 | #define s_realloc hi_realloc 44 | #define s_free hi_free 45 | -------------------------------------------------------------------------------- /hiredis-client/ext/redis_client/hiredis/vendor/sockcompat.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019, Marcus Geelnard 3 | * 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions are met: 8 | * 9 | * * Redistributions of source code must retain the above copyright notice, 10 | * this list of conditions and the following disclaimer. 11 | * * Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in the 13 | * documentation and/or other materials provided with the distribution. 14 | * * Neither the name of Redis nor the names of its contributors may be used 15 | * to endorse or promote products derived from this software without 16 | * specific prior written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 22 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 | * POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | 31 | #ifndef __SOCKCOMPAT_H 32 | #define __SOCKCOMPAT_H 33 | 34 | #ifndef _WIN32 35 | /* For POSIX systems we use the standard BSD socket API. */ 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #else 46 | /* For Windows we use winsock. */ 47 | #undef _WIN32_WINNT 48 | #define _WIN32_WINNT 0x0600 /* To get WSAPoll etc. */ 49 | #include 50 | #include 51 | #include 52 | #include 53 | 54 | #ifdef _MSC_VER 55 | typedef long long ssize_t; 56 | #endif 57 | 58 | /* Emulate the parts of the BSD socket API that we need (override the winsock signatures). */ 59 | int win32_getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res); 60 | const char *win32_gai_strerror(int errcode); 61 | void win32_freeaddrinfo(struct addrinfo *res); 62 | SOCKET win32_socket(int domain, int type, int protocol); 63 | int win32_ioctl(SOCKET fd, unsigned long request, unsigned long *argp); 64 | int win32_bind(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen); 65 | int win32_connect(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen); 66 | int win32_getsockopt(SOCKET sockfd, int level, int optname, void *optval, socklen_t *optlen); 67 | int win32_setsockopt(SOCKET sockfd, int level, int optname, const void *optval, socklen_t optlen); 68 | int win32_close(SOCKET fd); 69 | ssize_t win32_recv(SOCKET sockfd, void *buf, size_t len, int flags); 70 | ssize_t win32_send(SOCKET sockfd, const void *buf, size_t len, int flags); 71 | typedef ULONG nfds_t; 72 | int win32_poll(struct pollfd *fds, nfds_t nfds, int timeout); 73 | 74 | #ifndef REDIS_SOCKCOMPAT_IMPLEMENTATION 75 | #define getaddrinfo(node, service, hints, res) win32_getaddrinfo(node, service, hints, res) 76 | #undef gai_strerror 77 | #define gai_strerror(errcode) win32_gai_strerror(errcode) 78 | #define freeaddrinfo(res) win32_freeaddrinfo(res) 79 | #define socket(domain, type, protocol) win32_socket(domain, type, protocol) 80 | #define ioctl(fd, request, argp) win32_ioctl(fd, request, argp) 81 | #define bind(sockfd, addr, addrlen) win32_bind(sockfd, addr, addrlen) 82 | #define connect(sockfd, addr, addrlen) win32_connect(sockfd, addr, addrlen) 83 | #define getsockopt(sockfd, level, optname, optval, optlen) win32_getsockopt(sockfd, level, optname, optval, optlen) 84 | #define setsockopt(sockfd, level, optname, optval, optlen) win32_setsockopt(sockfd, level, optname, optval, optlen) 85 | #define close(fd) win32_close(fd) 86 | #define recv(sockfd, buf, len, flags) win32_recv(sockfd, buf, len, flags) 87 | #define send(sockfd, buf, len, flags) win32_send(sockfd, buf, len, flags) 88 | #define poll(fds, nfds, timeout) win32_poll(fds, nfds, timeout) 89 | #endif /* REDIS_SOCKCOMPAT_IMPLEMENTATION */ 90 | #endif /* _WIN32 */ 91 | 92 | #endif /* __SOCKCOMPAT_H */ 93 | -------------------------------------------------------------------------------- /hiredis-client/ext/redis_client/hiredis/vendor/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -ue 2 | 3 | REDIS_SERVER=${REDIS_SERVER:-redis-server} 4 | REDIS_PORT=${REDIS_PORT:-56379} 5 | REDIS_SSL_PORT=${REDIS_SSL_PORT:-56443} 6 | TEST_SSL=${TEST_SSL:-0} 7 | SKIPS_AS_FAILS=${SKIPS_AS_FAILS-:0} 8 | SSL_TEST_ARGS= 9 | SKIPS_ARG= 10 | 11 | tmpdir=$(mktemp -d) 12 | PID_FILE=${tmpdir}/hiredis-test-redis.pid 13 | SOCK_FILE=${tmpdir}/hiredis-test-redis.sock 14 | 15 | if [ "$TEST_SSL" = "1" ]; then 16 | SSL_CA_CERT=${tmpdir}/ca.crt 17 | SSL_CA_KEY=${tmpdir}/ca.key 18 | SSL_CERT=${tmpdir}/redis.crt 19 | SSL_KEY=${tmpdir}/redis.key 20 | 21 | openssl genrsa -out ${tmpdir}/ca.key 4096 22 | openssl req \ 23 | -x509 -new -nodes -sha256 \ 24 | -key ${SSL_CA_KEY} \ 25 | -days 3650 \ 26 | -subj '/CN=Hiredis Test CA' \ 27 | -out ${SSL_CA_CERT} 28 | openssl genrsa -out ${SSL_KEY} 2048 29 | openssl req \ 30 | -new -sha256 \ 31 | -key ${SSL_KEY} \ 32 | -subj '/CN=Hiredis Test Cert' | \ 33 | openssl x509 \ 34 | -req -sha256 \ 35 | -CA ${SSL_CA_CERT} \ 36 | -CAkey ${SSL_CA_KEY} \ 37 | -CAserial ${tmpdir}/ca.txt \ 38 | -CAcreateserial \ 39 | -days 365 \ 40 | -out ${SSL_CERT} 41 | 42 | SSL_TEST_ARGS="--ssl-host 127.0.0.1 --ssl-port ${REDIS_SSL_PORT} --ssl-ca-cert ${SSL_CA_CERT} --ssl-cert ${SSL_CERT} --ssl-key ${SSL_KEY}" 43 | fi 44 | 45 | cleanup() { 46 | set +e 47 | kill $(cat ${PID_FILE}) 48 | rm -rf ${tmpdir} 49 | } 50 | trap cleanup INT TERM EXIT 51 | 52 | cat > ${tmpdir}/redis.conf <> ${tmpdir}/redis.conf < /* for struct timeval */ 6 | 7 | #ifndef inline 8 | #define inline __inline 9 | #endif 10 | 11 | #ifndef strcasecmp 12 | #define strcasecmp stricmp 13 | #endif 14 | 15 | #ifndef strncasecmp 16 | #define strncasecmp strnicmp 17 | #endif 18 | 19 | #ifndef va_copy 20 | #define va_copy(d,s) ((d) = (s)) 21 | #endif 22 | 23 | #ifndef snprintf 24 | #define snprintf c99_snprintf 25 | 26 | __inline int c99_vsnprintf(char* str, size_t size, const char* format, va_list ap) 27 | { 28 | int count = -1; 29 | 30 | if (size != 0) 31 | count = _vsnprintf_s(str, size, _TRUNCATE, format, ap); 32 | if (count == -1) 33 | count = _vscprintf(format, ap); 34 | 35 | return count; 36 | } 37 | 38 | __inline int c99_snprintf(char* str, size_t size, const char* format, ...) 39 | { 40 | int count; 41 | va_list ap; 42 | 43 | va_start(ap, format); 44 | count = c99_vsnprintf(str, size, format, ap); 45 | va_end(ap); 46 | 47 | return count; 48 | } 49 | #endif 50 | #endif /* _MSC_VER */ 51 | 52 | #ifdef _WIN32 53 | #define strerror_r(errno,buf,len) strerror_s(buf,len,errno) 54 | #endif /* _WIN32 */ 55 | 56 | #endif /* _WIN32_HELPER_INCLUDE */ 57 | -------------------------------------------------------------------------------- /hiredis-client/hiredis-client.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../lib/redis_client/version" 4 | 5 | Gem::Specification.new do |spec| 6 | spec.name = "hiredis-client" 7 | spec.version = RedisClient::VERSION 8 | spec.authors = ["Jean Boussier"] 9 | spec.email = ["jean.boussier@gmail.com"] 10 | 11 | spec.summary = "Hiredis binding for redis-client" 12 | spec.homepage = "https://github.com/redis-rb/redis-client" 13 | spec.license = "MIT" 14 | spec.required_ruby_version = ">= 2.6.0" 15 | 16 | spec.metadata["allowed_push_host"] = "https://rubygems.org" 17 | 18 | spec.metadata["homepage_uri"] = spec.homepage 19 | spec.metadata["source_code_uri"] = spec.homepage 20 | spec.metadata["changelog_uri"] = File.join(spec.homepage, "blob/master/CHANGELOG.md") 21 | 22 | # Specify which files should be added to the gem when it is released. 23 | # The `git ls-files -z` loads the files in the RubyGem that have been added into git. 24 | gemspec = File.basename(__FILE__) 25 | spec.files = Dir.chdir(File.expand_path(__dir__)) do 26 | `git ls-files -z`.split("\x0").reject do |f| 27 | (f == gemspec) || f.start_with?(*%w[bin/ test/]) 28 | end 29 | end 30 | spec.require_paths = ["lib"] 31 | spec.extensions = ["ext/redis_client/hiredis/extconf.rb"] 32 | 33 | spec.add_runtime_dependency "redis-client", RedisClient::VERSION 34 | end 35 | -------------------------------------------------------------------------------- /hiredis-client/lib/hiredis-client.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "redis-client" 4 | 5 | begin 6 | require "redis_client/hiredis_connection" 7 | rescue LoadError 8 | else 9 | RedisClient.register_driver(:hiredis) { RedisClient::HiredisConnection } 10 | RedisClient.default_driver = :hiredis 11 | end 12 | -------------------------------------------------------------------------------- /hiredis-client/lib/redis_client/hiredis_connection.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "openssl" 4 | require "redis_client/hiredis_connection.so" 5 | require "redis_client/connection_mixin" 6 | 7 | class RedisClient 8 | class HiredisConnection 9 | include ConnectionMixin 10 | 11 | class << self 12 | def ssl_context(ssl_params) 13 | unless ssl_params[:ca_file] || ssl_params[:ca_path] 14 | default_ca_file = OpenSSL::X509::DEFAULT_CERT_FILE 15 | default_ca_path = OpenSSL::X509::DEFAULT_CERT_DIR 16 | 17 | if File.readable? default_ca_file 18 | ssl_params[:ca_file] = default_ca_file 19 | elsif File.directory? default_ca_path 20 | ssl_params[:ca_path] = default_ca_path 21 | end 22 | end 23 | 24 | HiredisConnection::SSLContext.new( 25 | ca_file: ssl_params[:ca_file], 26 | ca_path: ssl_params[:ca_path], 27 | cert: ssl_params[:cert], 28 | key: ssl_params[:key], 29 | hostname: ssl_params[:hostname], 30 | ) 31 | end 32 | end 33 | 34 | class SSLContext 35 | def initialize(ca_file: nil, ca_path: nil, cert: nil, key: nil, hostname: nil) 36 | if (error = init(ca_file, ca_path, cert, key, hostname)) 37 | raise error 38 | end 39 | end 40 | end 41 | 42 | attr_reader :config 43 | 44 | def initialize(config, connect_timeout:, read_timeout:, write_timeout:) 45 | super() 46 | @config = config 47 | self.connect_timeout = connect_timeout 48 | self.read_timeout = read_timeout 49 | self.write_timeout = write_timeout 50 | connect 51 | end 52 | 53 | def close 54 | _close 55 | super 56 | end 57 | 58 | def reconnect 59 | reconnected = begin 60 | _reconnect(@config.path, @config.ssl_context) 61 | rescue SystemCallError => error 62 | host = @config.path || "#{@config.host}:#{@config.port}" 63 | error_code = error.class.name.split("::").last 64 | raise CannotConnectError, "Failed to reconnect to #{host} (#{error_code})" 65 | end 66 | 67 | if reconnected 68 | true 69 | else 70 | # Reusing the hiredis connection didn't work let's create a fresh one 71 | super 72 | end 73 | end 74 | 75 | def connect_timeout=(timeout) 76 | self.connect_timeout_us = timeout ? (timeout * 1_000_000).to_i : 0 77 | @connect_timeout = timeout 78 | end 79 | 80 | def read_timeout=(timeout) 81 | self.read_timeout_us = timeout ? (timeout * 1_000_000).to_i : 0 82 | @read_timeout = timeout 83 | end 84 | 85 | def write_timeout=(timeout) 86 | self.write_timeout_us = timeout ? (timeout * 1_000_000).to_i : 0 87 | @write_timeout = timeout 88 | end 89 | 90 | def read(timeout = nil) 91 | if timeout.nil? 92 | _read 93 | else 94 | previous_timeout = @read_timeout 95 | self.read_timeout = timeout 96 | begin 97 | _read 98 | ensure 99 | self.read_timeout = previous_timeout 100 | end 101 | end 102 | rescue SystemCallError, IOError => error 103 | raise ConnectionError.with_config(error.message, config) 104 | rescue Error => error 105 | error._set_config(config) 106 | raise error 107 | end 108 | 109 | def write(command) 110 | _write(command) 111 | flush 112 | rescue SystemCallError, IOError => error 113 | raise ConnectionError.with_config(error.message, config) 114 | rescue Error => error 115 | error._set_config(config) 116 | raise error 117 | end 118 | 119 | def write_multi(commands) 120 | commands.each do |command| 121 | _write(command) 122 | end 123 | flush 124 | rescue SystemCallError, IOError => error 125 | raise ConnectionError.with_config(error.message, config) 126 | rescue Error => error 127 | error._set_config(config) 128 | raise error 129 | end 130 | 131 | private 132 | 133 | def connect 134 | _connect(@config.path, @config.host, @config.port, @config.ssl_context) 135 | rescue SystemCallError => error 136 | host = @config.path || "#{@config.host}:#{@config.port}" 137 | error_code = error.class.name.split("::").last 138 | raise CannotConnectError, "Failed to connect to #{host} (#{error_code})" 139 | end 140 | end 141 | end 142 | -------------------------------------------------------------------------------- /lib/redis-client.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "redis_client" 4 | -------------------------------------------------------------------------------- /lib/redis_client/circuit_breaker.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class RedisClient 4 | class CircuitBreaker 5 | module Middleware 6 | def connect(config) 7 | config.circuit_breaker.protect { super } 8 | end 9 | 10 | def call(_command, config) 11 | config.circuit_breaker.protect { super } 12 | end 13 | 14 | def call_pipelined(_commands, config) 15 | config.circuit_breaker.protect { super } 16 | end 17 | end 18 | 19 | OpenCircuitError = Class.new(CannotConnectError) 20 | 21 | attr_reader :error_timeout, :error_threshold, :error_threshold_timeout, :success_threshold 22 | 23 | def initialize(error_threshold:, error_timeout:, error_threshold_timeout: error_timeout, success_threshold: 0) 24 | @error_threshold = Integer(error_threshold) 25 | @error_threshold_timeout = Float(error_threshold_timeout) 26 | @error_timeout = Float(error_timeout) 27 | @success_threshold = Integer(success_threshold) 28 | @errors = [] 29 | @successes = 0 30 | @state = :closed 31 | @lock = Mutex.new 32 | end 33 | 34 | def protect 35 | if @state == :open 36 | refresh_state 37 | end 38 | 39 | case @state 40 | when :open 41 | raise OpenCircuitError, "Too many connection errors happened recently" 42 | when :closed 43 | begin 44 | yield 45 | rescue ConnectionError 46 | record_error 47 | raise 48 | end 49 | when :half_open 50 | begin 51 | result = yield 52 | record_success 53 | result 54 | rescue ConnectionError 55 | record_error 56 | raise 57 | end 58 | else 59 | raise "[BUG] RedisClient::CircuitBreaker unexpected @state (#{@state.inspect}})" 60 | end 61 | end 62 | 63 | private 64 | 65 | def refresh_state 66 | now = RedisClient.now 67 | @lock.synchronize do 68 | if @errors.last < (now - @error_timeout) 69 | if @success_threshold > 0 70 | @state = :half_open 71 | @successes = 0 72 | else 73 | @errors.clear 74 | @state = :closed 75 | end 76 | end 77 | end 78 | end 79 | 80 | def record_error 81 | now = RedisClient.now 82 | expiry = now - @error_timeout 83 | @lock.synchronize do 84 | if @state == :closed 85 | @errors.reject! { |t| t < expiry } 86 | end 87 | @errors << now 88 | @successes = 0 89 | if @state == :half_open || (@state == :closed && @errors.size >= @error_threshold) 90 | @state = :open 91 | end 92 | end 93 | end 94 | 95 | def record_success 96 | return unless @state == :half_open 97 | 98 | @lock.synchronize do 99 | return unless @state == :half_open 100 | 101 | @successes += 1 102 | if @successes >= @success_threshold 103 | @state = :closed 104 | end 105 | end 106 | end 107 | end 108 | end 109 | -------------------------------------------------------------------------------- /lib/redis_client/command_builder.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class RedisClient 4 | module CommandBuilder 5 | extend self 6 | 7 | if Symbol.method_defined?(:name) 8 | def generate(args, kwargs = nil) 9 | command = args.flat_map do |element| 10 | case element 11 | when Hash 12 | element.flatten 13 | else 14 | element 15 | end 16 | end 17 | 18 | kwargs&.each do |key, value| 19 | if value 20 | if value == true 21 | command << key.name 22 | else 23 | command << key.name << value 24 | end 25 | end 26 | end 27 | 28 | command.map! do |element| 29 | case element 30 | when String 31 | element 32 | when Symbol 33 | element.name 34 | when Integer, Float 35 | element.to_s 36 | else 37 | raise TypeError, "Unsupported command argument type: #{element.class}" 38 | end 39 | end 40 | 41 | if command.empty? 42 | raise ArgumentError, "can't issue an empty redis command" 43 | end 44 | 45 | command 46 | end 47 | else 48 | def generate(args, kwargs = nil) 49 | command = args.flat_map do |element| 50 | case element 51 | when Hash 52 | element.flatten 53 | else 54 | element 55 | end 56 | end 57 | 58 | kwargs&.each do |key, value| 59 | if value 60 | if value == true 61 | command << key.to_s 62 | else 63 | command << key.to_s << value 64 | end 65 | end 66 | end 67 | 68 | command.map! do |element| 69 | case element 70 | when String 71 | element 72 | when Integer, Float, Symbol 73 | element.to_s 74 | else 75 | raise TypeError, "Unsupported command argument type: #{element.class}" 76 | end 77 | end 78 | 79 | if command.empty? 80 | raise ArgumentError, "can't issue an empty redis command" 81 | end 82 | 83 | command 84 | end 85 | end 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /lib/redis_client/connection_mixin.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class RedisClient 4 | module ConnectionMixin 5 | def initialize 6 | @pending_reads = 0 7 | end 8 | 9 | def reconnect 10 | close 11 | connect 12 | end 13 | 14 | def close 15 | @pending_reads = 0 16 | nil 17 | end 18 | 19 | def revalidate 20 | if @pending_reads > 0 21 | close 22 | false 23 | else 24 | connected? 25 | end 26 | end 27 | 28 | def call(command, timeout) 29 | @pending_reads += 1 30 | write(command) 31 | result = read(connection_timeout(timeout)) 32 | @pending_reads -= 1 33 | if result.is_a?(Error) 34 | result._set_command(command) 35 | result._set_config(config) 36 | raise result 37 | else 38 | result 39 | end 40 | end 41 | 42 | def call_pipelined(commands, timeouts, exception: true) 43 | first_exception = nil 44 | 45 | size = commands.size 46 | results = Array.new(commands.size) 47 | @pending_reads += size 48 | write_multi(commands) 49 | 50 | size.times do |index| 51 | timeout = timeouts && timeouts[index] 52 | result = read(connection_timeout(timeout)) 53 | @pending_reads -= 1 54 | 55 | # A multi/exec command can return an array of results. 56 | # An error from a multi/exec command is handled in Multi#_coerce!. 57 | if result.is_a?(Array) 58 | result.each do |res| 59 | res._set_config(config) if res.is_a?(Error) 60 | end 61 | elsif result.is_a?(Error) 62 | result._set_command(commands[index]) 63 | result._set_config(config) 64 | first_exception ||= result 65 | end 66 | 67 | results[index] = result 68 | end 69 | 70 | if first_exception && exception 71 | raise first_exception 72 | else 73 | results 74 | end 75 | end 76 | 77 | def connection_timeout(timeout) 78 | return timeout unless timeout && timeout > 0 79 | 80 | # Can't use the command timeout argument as the connection timeout 81 | # otherwise it would be very racy. So we add the regular read_timeout on top 82 | # to account for the network delay. 83 | timeout + config.read_timeout 84 | end 85 | end 86 | end 87 | -------------------------------------------------------------------------------- /lib/redis_client/decorator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class RedisClient 4 | module Decorator 5 | class << self 6 | def create(commands_mixin) 7 | client_decorator = Class.new(Client) 8 | client_decorator.include(commands_mixin) 9 | 10 | pipeline_decorator = Class.new(Pipeline) 11 | pipeline_decorator.include(commands_mixin) 12 | client_decorator.const_set(:Pipeline, pipeline_decorator) 13 | 14 | client_decorator 15 | end 16 | end 17 | 18 | module CommandsMixin 19 | def initialize(client) 20 | @client = client 21 | end 22 | 23 | %i(call call_v call_once call_once_v blocking_call blocking_call_v).each do |method| 24 | class_eval(<<~RUBY, __FILE__, __LINE__ + 1) 25 | def #{method}(*args, &block) 26 | @client.#{method}(*args, &block) 27 | end 28 | ruby2_keywords :#{method} if respond_to?(:ruby2_keywords, true) 29 | RUBY 30 | end 31 | end 32 | 33 | class Pipeline 34 | include CommandsMixin 35 | end 36 | 37 | class Client 38 | include CommandsMixin 39 | 40 | def initialize(_client) 41 | super 42 | @_pipeline_class = self.class::Pipeline 43 | end 44 | 45 | def with(*args) 46 | @client.with(*args) { |c| yield self.class.new(c) } 47 | end 48 | ruby2_keywords :with if respond_to?(:ruby2_keywords, true) 49 | 50 | def pipelined(exception: true) 51 | @client.pipelined(exception: exception) { |p| yield @_pipeline_class.new(p) } 52 | end 53 | 54 | def multi(**kwargs) 55 | @client.multi(**kwargs) { |p| yield @_pipeline_class.new(p) } 56 | end 57 | 58 | %i(close scan hscan sscan zscan).each do |method| 59 | class_eval(<<~RUBY, __FILE__, __LINE__ + 1) 60 | def #{method}(*args, &block) 61 | @client.#{method}(*args, &block) 62 | end 63 | ruby2_keywords :#{method} if respond_to?(:ruby2_keywords, true) 64 | RUBY 65 | end 66 | 67 | %i(id config size connect_timeout read_timeout write_timeout pubsub).each do |reader| 68 | class_eval(<<~RUBY, __FILE__, __LINE__ + 1) 69 | def #{reader} 70 | @client.#{reader} 71 | end 72 | RUBY 73 | end 74 | 75 | %i(timeout connect_timeout read_timeout write_timeout).each do |writer| 76 | class_eval(<<~RUBY, __FILE__, __LINE__ + 1) 77 | def #{writer}=(value) 78 | @client.#{writer} = value 79 | end 80 | RUBY 81 | end 82 | end 83 | end 84 | end 85 | -------------------------------------------------------------------------------- /lib/redis_client/middlewares.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class RedisClient 4 | class BasicMiddleware 5 | attr_reader :client 6 | 7 | def initialize(client) 8 | @client = client 9 | end 10 | 11 | def connect(_config) 12 | yield 13 | end 14 | 15 | def call(command, _config) 16 | yield command 17 | end 18 | alias_method :call_pipelined, :call 19 | end 20 | 21 | class Middlewares < BasicMiddleware 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/redis_client/pid_cache.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class RedisClient 4 | module PIDCache 5 | if !Process.respond_to?(:fork) # JRuby or TruffleRuby 6 | @pid = Process.pid 7 | singleton_class.attr_reader(:pid) 8 | elsif Process.respond_to?(:_fork) # Ruby 3.1+ 9 | class << self 10 | attr_reader :pid 11 | 12 | def update! 13 | @pid = Process.pid 14 | end 15 | end 16 | update! 17 | 18 | module CoreExt 19 | def _fork 20 | child_pid = super 21 | PIDCache.update! if child_pid == 0 22 | child_pid 23 | end 24 | end 25 | Process.singleton_class.prepend(CoreExt) 26 | else # Ruby 3.0 or older 27 | class << self 28 | def pid 29 | Process.pid 30 | end 31 | end 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/redis_client/pooled.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "connection_pool" 4 | 5 | class RedisClient 6 | class Pooled 7 | EMPTY_HASH = {}.freeze 8 | 9 | include Common 10 | 11 | def initialize( 12 | config, 13 | id: config.id, 14 | connect_timeout: config.connect_timeout, 15 | read_timeout: config.read_timeout, 16 | write_timeout: config.write_timeout, 17 | **kwargs 18 | ) 19 | super(config, id: id, connect_timeout: connect_timeout, read_timeout: read_timeout, write_timeout: write_timeout) 20 | @pool_kwargs = kwargs 21 | @pool = new_pool 22 | @mutex = Mutex.new 23 | end 24 | 25 | def with(options = EMPTY_HASH) 26 | pool.with(options) do |client| 27 | client.connect_timeout = connect_timeout 28 | client.read_timeout = read_timeout 29 | client.write_timeout = write_timeout 30 | yield client 31 | end 32 | rescue ConnectionPool::TimeoutError => error 33 | raise CheckoutTimeoutError, "Couldn't checkout a connection in time: #{error.message}" 34 | end 35 | alias_method :then, :with 36 | 37 | def close 38 | if @pool 39 | @mutex.synchronize do 40 | pool = @pool 41 | @pool = nil 42 | pool&.shutdown(&:close) 43 | end 44 | end 45 | nil 46 | end 47 | 48 | def size 49 | pool.size 50 | end 51 | 52 | methods = %w(pipelined multi pubsub call call_v call_once call_once_v blocking_call blocking_call_v) 53 | iterable_methods = %w(scan sscan hscan zscan) 54 | methods.each do |method| 55 | class_eval <<~RUBY, __FILE__, __LINE__ + 1 56 | def #{method}(*args, &block) 57 | with { |r| r.#{method}(*args, &block) } 58 | end 59 | ruby2_keywords :#{method} if respond_to?(:ruby2_keywords, true) 60 | RUBY 61 | end 62 | 63 | iterable_methods.each do |method| 64 | class_eval <<~RUBY, __FILE__, __LINE__ + 1 65 | def #{method}(*args, &block) 66 | unless block_given? 67 | return to_enum(__callee__, *args) 68 | end 69 | 70 | with { |r| r.#{method}(*args, &block) } 71 | end 72 | ruby2_keywords :#{method} if respond_to?(:ruby2_keywords, true) 73 | RUBY 74 | end 75 | 76 | private 77 | 78 | def pool 79 | @pool ||= @mutex.synchronize { new_pool } 80 | end 81 | 82 | def new_pool 83 | ConnectionPool.new(**@pool_kwargs) { @config.new_client } 84 | end 85 | end 86 | end 87 | -------------------------------------------------------------------------------- /lib/redis_client/ruby_connection/resp3.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class RedisClient 4 | module RESP3 5 | module_function 6 | 7 | Error = Class.new(RedisClient::Error) 8 | UnknownType = Class.new(Error) 9 | SyntaxError = Class.new(Error) 10 | 11 | EOL = "\r\n".b.freeze 12 | EOL_SIZE = EOL.bytesize 13 | DUMP_TYPES = { 14 | String => :dump_string, 15 | Symbol => :dump_symbol, 16 | Integer => :dump_numeric, 17 | Float => :dump_numeric, 18 | }.freeze 19 | PARSER_TYPES = { 20 | '#' => :parse_boolean, 21 | '$' => :parse_blob, 22 | '+' => :parse_string, 23 | '=' => :parse_verbatim_string, 24 | '-' => :parse_error, 25 | ':' => :parse_integer, 26 | '(' => :parse_integer, 27 | ',' => :parse_double, 28 | '_' => :parse_null, 29 | '*' => :parse_array, 30 | '%' => :parse_map, 31 | '~' => :parse_set, 32 | '>' => :parse_array, 33 | }.transform_keys(&:ord).freeze 34 | INTEGER_RANGE = ((((2**64) / 2) * -1)..(((2**64) / 2) - 1)).freeze 35 | 36 | def dump(command, buffer = nil) 37 | buffer ||= new_buffer 38 | command = command.flat_map do |element| 39 | case element 40 | when Hash 41 | element.flatten 42 | else 43 | element 44 | end 45 | end 46 | dump_array(command, buffer) 47 | end 48 | 49 | def load(io) 50 | parse(io) 51 | end 52 | 53 | def new_buffer 54 | String.new(encoding: Encoding::BINARY, capacity: 127) 55 | end 56 | 57 | def dump_any(object, buffer) 58 | method = DUMP_TYPES.fetch(object.class) do |unexpected_class| 59 | if superclass = DUMP_TYPES.keys.find { |t| t > unexpected_class } 60 | DUMP_TYPES[superclass] 61 | else 62 | raise TypeError, "Unsupported command argument type: #{unexpected_class}" 63 | end 64 | end 65 | send(method, object, buffer) 66 | end 67 | 68 | def dump_array(array, buffer) 69 | buffer << '*' << array.size.to_s << EOL 70 | array.each do |item| 71 | dump_any(item, buffer) 72 | end 73 | buffer 74 | end 75 | 76 | def dump_set(set, buffer) 77 | buffer << '~' << set.size.to_s << EOL 78 | set.each do |item| 79 | dump_any(item, buffer) 80 | end 81 | buffer 82 | end 83 | 84 | def dump_hash(hash, buffer) 85 | buffer << '%' << hash.size.to_s << EOL 86 | hash.each_pair do |key, value| 87 | dump_any(key, buffer) 88 | dump_any(value, buffer) 89 | end 90 | buffer 91 | end 92 | 93 | def dump_numeric(numeric, buffer) 94 | dump_string(numeric.to_s, buffer) 95 | end 96 | 97 | def dump_string(string, buffer) 98 | string = string.b unless string.ascii_only? 99 | buffer << '$' << string.bytesize.to_s << EOL << string << EOL 100 | end 101 | 102 | if Symbol.method_defined?(:name) 103 | def dump_symbol(symbol, buffer) 104 | dump_string(symbol.name, buffer) 105 | end 106 | else 107 | def dump_symbol(symbol, buffer) 108 | dump_string(symbol.to_s, buffer) 109 | end 110 | end 111 | 112 | def parse(io) 113 | type = io.getbyte 114 | if type == 35 # '#'.ord 115 | parse_boolean(io) 116 | elsif type == 36 # '$'.ord 117 | parse_blob(io) 118 | elsif type == 43 # '+'.ord 119 | parse_string(io) 120 | elsif type == 61 # '='.ord 121 | parse_verbatim_string(io) 122 | elsif type == 45 # '-'.ord 123 | parse_error(io) 124 | elsif type == 58 # ':'.ord 125 | parse_integer(io) 126 | elsif type == 40 # '('.ord 127 | parse_integer(io) 128 | elsif type == 44 # ','.ord 129 | parse_double(io) 130 | elsif type == 95 # '_'.ord 131 | parse_null(io) 132 | elsif type == 42 # '*'.ord 133 | parse_array(io) 134 | elsif type == 37 # '%'.ord 135 | parse_map(io) 136 | elsif type == 126 # '~'.ord 137 | parse_set(io) 138 | elsif type == 62 # '>'.ord 139 | parse_array(io) 140 | else 141 | raise UnknownType, "Unknown sigil type: #{type.chr.inspect}" 142 | end 143 | end 144 | 145 | def parse_string(io) 146 | str = io.gets_chomp 147 | str.force_encoding(Encoding::BINARY) unless str.valid_encoding? 148 | str.freeze 149 | end 150 | 151 | def parse_error(io) 152 | CommandError.parse(parse_string(io)) 153 | end 154 | 155 | def parse_boolean(io) 156 | case value = io.gets_chomp 157 | when "t" 158 | true 159 | when "f" 160 | false 161 | else 162 | raise SyntaxError, "Expected `t` or `f` after `#`, got: #{value}" 163 | end 164 | end 165 | 166 | def parse_array(io) 167 | parse_sequence(io, io.gets_integer) 168 | end 169 | 170 | def parse_set(io) 171 | parse_sequence(io, io.gets_integer) 172 | end 173 | 174 | def parse_map(io) 175 | hash = {} 176 | io.gets_integer.times do 177 | hash[parse(io).freeze] = parse(io) 178 | end 179 | hash 180 | end 181 | 182 | def parse_push(io) 183 | parse_array(io) 184 | end 185 | 186 | def parse_sequence(io, size) 187 | return if size < 0 # RESP2 nil 188 | 189 | array = Array.new(size) 190 | size.times do |index| 191 | array[index] = parse(io) 192 | end 193 | array 194 | end 195 | 196 | def parse_integer(io) 197 | Integer(io.gets_chomp) 198 | end 199 | 200 | def parse_double(io) 201 | case value = io.gets_chomp 202 | when "nan" 203 | Float::NAN 204 | when "inf" 205 | Float::INFINITY 206 | when "-inf" 207 | -Float::INFINITY 208 | else 209 | Float(value) 210 | end 211 | end 212 | 213 | def parse_null(io) 214 | io.skip(EOL_SIZE) 215 | nil 216 | end 217 | 218 | def parse_blob(io) 219 | bytesize = io.gets_integer 220 | return if bytesize < 0 # RESP2 nil type 221 | 222 | str = io.read_chomp(bytesize) 223 | str.force_encoding(Encoding::BINARY) unless str.valid_encoding? 224 | str 225 | end 226 | 227 | def parse_verbatim_string(io) 228 | blob = parse_blob(io) 229 | blob.byteslice(4..-1) 230 | end 231 | end 232 | end 233 | -------------------------------------------------------------------------------- /lib/redis_client/url_config.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "uri" 4 | 5 | class RedisClient 6 | class URLConfig 7 | attr_reader :url, :uri 8 | 9 | def initialize(url) 10 | @url = url 11 | @uri = URI(url) 12 | @unix = false 13 | @ssl = false 14 | case uri.scheme 15 | when "redis" 16 | # expected 17 | when "rediss" 18 | @ssl = true 19 | when "unix", nil 20 | @unix = true 21 | else 22 | raise ArgumentError, "Unknown URL scheme: #{url.inspect}" 23 | end 24 | end 25 | 26 | def ssl? 27 | @ssl 28 | end 29 | 30 | def db 31 | unless @unix 32 | db_path = uri.path&.delete_prefix("/") 33 | return Integer(db_path) if db_path && !db_path.empty? 34 | end 35 | 36 | unless uri.query.nil? || uri.query.empty? 37 | _, db_query = URI.decode_www_form(uri.query).find do |key, _| 38 | key == "db" 39 | end 40 | return Integer(db_query) if db_query && !db_query.empty? 41 | end 42 | end 43 | 44 | def username 45 | uri.user if uri.password && !uri.user.empty? 46 | end 47 | 48 | def password 49 | if uri.user && !uri.password 50 | URI.decode_www_form_component(uri.user) 51 | elsif uri.user && uri.password 52 | URI.decode_www_form_component(uri.password) 53 | end 54 | end 55 | 56 | def host 57 | return if uri.host.nil? || uri.host.empty? 58 | 59 | uri.host.sub(/\A\[(.*)\]\z/, '\1') 60 | end 61 | 62 | def path 63 | if @unix 64 | File.join(*[uri.host, uri.path].compact) 65 | end 66 | end 67 | 68 | def port 69 | return unless uri.port 70 | 71 | Integer(uri.port) 72 | end 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /lib/redis_client/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class RedisClient 4 | VERSION = "0.25.0" 5 | end 6 | -------------------------------------------------------------------------------- /redis-client.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "lib/redis_client/version" 4 | 5 | Gem::Specification.new do |spec| 6 | spec.name = "redis-client" 7 | spec.version = RedisClient::VERSION 8 | spec.authors = ["Jean Boussier"] 9 | spec.email = ["jean.boussier@gmail.com"] 10 | 11 | spec.summary = "Simple low-level client for Redis 6+" 12 | spec.homepage = "https://github.com/redis-rb/redis-client" 13 | spec.license = "MIT" 14 | spec.required_ruby_version = ">= 2.6.0" 15 | 16 | spec.metadata["allowed_push_host"] = "https://rubygems.org" 17 | 18 | spec.metadata["homepage_uri"] = spec.homepage 19 | spec.metadata["source_code_uri"] = spec.homepage 20 | spec.metadata["changelog_uri"] = File.join(spec.homepage, "blob/master/CHANGELOG.md") 21 | 22 | # Specify which files should be added to the gem when it is released. 23 | # The `git ls-files -z` loads the files in the RubyGem that have been added into git. 24 | gemspec = File.basename(__FILE__) 25 | spec.files = Dir.chdir(File.expand_path(__dir__)) do 26 | `git ls-files -z`.split("\x0").reject do |f| 27 | (f == gemspec) || f.start_with?(*%w[bin/ hiredis-client/ test/ benchmark/ .git .rubocop Gemfile Rakefile]) 28 | end 29 | end 30 | spec.require_paths = ["lib"] 31 | 32 | spec.add_runtime_dependency "connection_pool" 33 | end 34 | -------------------------------------------------------------------------------- /test/env.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | $LOAD_PATH.unshift File.expand_path("../lib", __dir__) 4 | $LOAD_PATH.unshift File.expand_path("../hiredis-client/lib", __dir__) 5 | 6 | require "redis-client" 7 | require "redis_client/decorator" 8 | 9 | require "toxiproxy" 10 | require "stringio" 11 | 12 | Dir[File.join(__dir__, "support/**/*.rb")].sort.each { |f| require f } 13 | Dir[File.join(__dir__, "shared/**/*.rb")].sort.each { |f| require f } 14 | -------------------------------------------------------------------------------- /test/fixtures/certs/ca.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFSzCCAzOgAwIBAgIUeyNhqaA/DqUivAK1BILqZO8HDWcwDQYJKoZIhvcNAQEL 3 | BQAwNTETMBEGA1UECgwKUmVkaXMgVGVzdDEeMBwGA1UEAwwVQ2VydGlmaWNhdGUg 4 | QXV0aG9yaXR5MB4XDTI1MDQyMTIzNDYyMFoXDTM1MDQxOTIzNDYyMFowNTETMBEG 5 | A1UECgwKUmVkaXMgVGVzdDEeMBwGA1UEAwwVQ2VydGlmaWNhdGUgQXV0aG9yaXR5 6 | MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAr8kvD8PpZ0jR6YcVQ1BO 7 | qIL8/x1rpchaXroFrborZUPJhoaPhNCHZEbz+rku699XeF1M52F8Gx6UGpA7sz24 8 | 4Fw3EUgLb1Dqg+/cZJ+RX88gdHwdAOK5rVHkv1td1VVxdk48lHpJobbk6oZvLLHV 9 | wrEJqGRfz4d8MSd8yKas0967mDhVfGR+ul0Ty330WoLyS3qwni35BEEGDzL2Kk+8 10 | IdAEmLQJvIb9beTDvEqykFJzrzm7HdgKpj7QFrgMg838OJOJPtrsJj0oYMOL9ImF 11 | DubCAhIkiMX6e1ONRFVF9Pj95dzsG2TwUdktg/vZyzSic168EeURkiZhN+d5vdMY 12 | 56Xm+QONh6V/ncPEyXdMbi8bQI681OJj9IarO/9OXZ5sb/aZRqwzLrtt/ua4Ex5u 13 | uHOtmnTjh4ahA9AtCV7g908EmRltUClN2D8HVnxTUWAkdVDghMAIAGvW6Ryay1aE 14 | Nb6cd8tqusZu5VW1I/8oTzFsxBvUoofOQJYlh44ijjNPp3Or8lH38X5Xm3oPeugd 15 | 9gbbH3EYE/UyfTtaaZ2RZD50dWUeHbr0WP9JwZrSePUGGlURnywgOvuyjswes+RW 16 | xQhPoNOV1pI0llspmDkRbFARgmgjrXd25WhvlPkZB1/mN1APWg7Uyfh8YU8gRoPj 17 | GoKBLEGXN2ZvYaOJVcdJeBECAwEAAaNTMFEwHQYDVR0OBBYEFFuraL4Ktd4MW4Xk 18 | eyLhrO1u6PoEMB8GA1UdIwQYMBaAFFuraL4Ktd4MW4XkeyLhrO1u6PoEMA8GA1Ud 19 | EwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIBAJtMsTigQK0MI4EwFQTCioHX 20 | vQSfr+ddd5JmFMqCGW9D+nd+NFVxATsZyocawbHGx9oFWySjZ/PPNpRnnVC4o/+c 21 | Zyywj/RtxayMH+8irdIqBmuwd4D2grI00huL+AZfiM3zW4WYxklosav6i63MNtOs 22 | 6u+wfaBJqllYql6EAEO4zIirPvmj/xuZGfzlqPwAXHed2Zkv95WYL9A3BeufPxHO 23 | l/800cOtO3l8Exmc2JfsingLEMZJ3kSt7mdf93iPEl/drlmfjbuvL4zpzoU1MzrT 24 | oWo7/ft4rNQ50KM80wmhjkV9+dyDLYxL1hdQDQuEnEt11lUXc2xzi/IROtS2sxXe 25 | lUbKpL45SeQQxQh0Qf7v65WET3h0ekOj6BRrPik90xK1bWMoolyva6EVwNI3EEAg 26 | zqo/x8ZwtLPTjE8AfYLM0in4/kI+vQTv3iZPcUkCMpi5l1N6F16UFnDurU5w4dd+ 27 | vfgGq61MZGmRQhV0csvEu8fWv/wPdEPtVtcUlIQBytb0/m9+vP+6+FEYzmPXOvRq 28 | t8sYwNHtvSmdta/Cz/udNK5D268luh7uFGf9kU4j0uH0+Q9HADpcAGc8P4DRxlAU 29 | hZDkNpR+Kfov+15L6O2S32p2RzegIRxRsvB8XGREW+JpsxDX9Q322BCdsKQGGMQN 30 | eWXLxteYx0NZkz9iSWnB 31 | -----END CERTIFICATE----- 32 | -------------------------------------------------------------------------------- /test/fixtures/certs/ca.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIJKAIBAAKCAgEAr8kvD8PpZ0jR6YcVQ1BOqIL8/x1rpchaXroFrborZUPJhoaP 3 | hNCHZEbz+rku699XeF1M52F8Gx6UGpA7sz244Fw3EUgLb1Dqg+/cZJ+RX88gdHwd 4 | AOK5rVHkv1td1VVxdk48lHpJobbk6oZvLLHVwrEJqGRfz4d8MSd8yKas0967mDhV 5 | fGR+ul0Ty330WoLyS3qwni35BEEGDzL2Kk+8IdAEmLQJvIb9beTDvEqykFJzrzm7 6 | HdgKpj7QFrgMg838OJOJPtrsJj0oYMOL9ImFDubCAhIkiMX6e1ONRFVF9Pj95dzs 7 | G2TwUdktg/vZyzSic168EeURkiZhN+d5vdMY56Xm+QONh6V/ncPEyXdMbi8bQI68 8 | 1OJj9IarO/9OXZ5sb/aZRqwzLrtt/ua4Ex5uuHOtmnTjh4ahA9AtCV7g908EmRlt 9 | UClN2D8HVnxTUWAkdVDghMAIAGvW6Ryay1aENb6cd8tqusZu5VW1I/8oTzFsxBvU 10 | oofOQJYlh44ijjNPp3Or8lH38X5Xm3oPeugd9gbbH3EYE/UyfTtaaZ2RZD50dWUe 11 | Hbr0WP9JwZrSePUGGlURnywgOvuyjswes+RWxQhPoNOV1pI0llspmDkRbFARgmgj 12 | rXd25WhvlPkZB1/mN1APWg7Uyfh8YU8gRoPjGoKBLEGXN2ZvYaOJVcdJeBECAwEA 13 | AQKCAgANre5Hn8tOClCrh6OT9W/plSfzAmsaH5lIvdkrR82Qt9G68kXA5CllGFBs 14 | NnT8TgkUiM4vQ1rREXQdDRRYQnlcnFB8u8qIAxf85HGWMwSxHAE+j1oCc6JXZoQS 15 | kB2hOGD3/+ae91U7jGwMBCIqrDwiRnyl6gm6sKRtftErHC1e33phwiCE6Z0jC5M6 16 | xrZ5RK9uSEHuTU2Pky/Rhvm5GTNevj1dVMVdMnQOVTsWMAntST1PaYKyM9nATisL 17 | WY8/wovaK0EG7yppX2EBBrahdQIxwqteVeMZ8a4oYrwfkTM8eRPpC2QkTZqWA/yS 18 | xMqSEaqCp+Ci09ymLu5p102WBBNv8mSSVGhzpnbI8p7qucSOez5kIckTUssBlMYG 19 | UHHaFrODQVmdabLxseE6wTEP7KI31aFyqcGbCU+x9y4oOWHRITXlB0u2qUbocqD+ 20 | tjAWforqj6t/zNbKprJBt5S86PGExWtumTcy11UdYdzUNhfhHAfBCsouoxByzK3B 21 | s5FLy/I7pNmq4E2cj7Rmm69JnGg3t4s+/buUj/oo2YBA401BFeMCv+ZX8sI8sf4B 22 | 0ypf0ngUT6TrVMBCe9UHYE0FP4p+j7mfOPMQVXvXn0F1XKWYs8JAWok+/T4jdfNF 23 | EebVr1RXFEc+ihFcOS4c30v7Zg4Qf2qx7Ui8xHHv6EpFXab9MQKCAQEA2wZswDq9 24 | ZJlc+3+yuy33WUa46tA+IW9YeWPxkf48FSLGLUCAg71BzMPHuWl8aJIsHvtQOZwR 25 | Aey8nvr0jyfvU2kTM/1Wf01qX63fGOgU7mqj8bRnlPb/bTe75vyW0ZK0HzRT1JVp 26 | IhaT/g2YKGYRyWFXHLfpsrA/+q2gNp+/FmPDMcv1KPLOgu5Q/DHDJdt48WSZAAq0 27 | 9+7+QocXLxvFZVyiiFYex9hEMNSnQUHGwaMDQLdc83AEO0f/8Nh440ouYEWkhMia 28 | 2ccppsXePda8Vb8FHz5Is3YMTngqOOucDHTfFyoXt10K74jnA5O2EpN6WrxMhFNb 29 | DiN1ybEiBGghrwKCAQEAzXYYj+IlqjBfOyATsE8Kn1+0aAejnEu7LzsWs5RFP4T/ 30 | Rv57s5YU2NVaMVT1MdazsTGSjcm7cRdNEkTIvL8hITQBuGyqGpAaq6zs42D2PQyv 31 | tgItNshYdj5guAEqzBmTvx7acdLoGmiLnwXPjqwxkI9Jh+KfrACYJJN+EHvQQVzA 32 | w7dxt41tAWLzWNOdEeEaG/Cs8WzURQH/sRcRMut2IcWrw6DZv8zMgzp7q+B1Uhyt 33 | FEpyujb6gwAunTZKb7G6fEJCDTsy28Gy0trCtc9mn57II0Tj8B6HYLTOR+mTLGOS 34 | BF/MA4uCRUUYCnO7gk3kJZnWtH9kBAg9QfE3lHsyPwKCAQEAmYq0fEHxeW+F3o1T 35 | x6JerwhEI+CeXaQH+vlUZQs8JXj+QsTgEvp/AUQSZGmNnGU0Zve48tn0lkvWowC8 36 | pwrQ3MFhg+XKWG3172MdbgFsgwLhMVVN9AD/aRpUMIbMV9inSuTNC88+J3Z6gvQW 37 | weNj/q+teOV5ABpMj7heA00TkWeYc/VORUmJ+gGFZnagHo7wBxGFrKDU4qZ5Ojwn 38 | xY+LXxaEnlz5MRHsI+s/4SBybFaRtjGVCNdzL/e2danbfUMIpdbMkYVsANV83nwB 39 | 44oA93904MUyBBTyZaQZvVN2Tskzh7Krc5DXVLq9cCWB0x3t/WPZpD4nLA8xyJXT 40 | ihFR4wKCAQBm86EVH+VtpPVjBAy5kLGq8GLOqd1CqPPvk7UpYMdeL79WjJfhgfeK 41 | O0YJaB/AzGuYA1YDNC13Woyk9dB8O21XXN7r0Y9e5gxnL6w3t1NLffrhwa90PumM 42 | vm7qZLNUOBC/eK9Oz7a73NzxXxEE1aW0YQggTd9iaZ3S3hESI2hUCC0TJO2XYwdW 43 | 5YU4YjjXR6s0iuGty7GFrp231+4nTLMR8yHBUe0qXW2w5/ImSr+e2H2lqDRauMfI 44 | MqQo5JZh53WhY/YC+UHfuexoGXPtdDJhE0gH3DI3FKUTQSYIBLNZT79P78yjjhlF 45 | qnyEaD9x6KPEb5SVNywflR1U2JDYFu8zAoIBAGM1qd05fz15r+FcjJ5vIyAdf9Sn 46 | t6bY77jpUfB+JE2v/ZU+GbtntpijYQgHexuPhegvd1neAoY9lLd0yIqNP6BtZ8IT 47 | aBUTiC1MPL9SSv6f2tTfgG9ZqVPmAk4nnGH8AyXItWzGLOdebKrM44ZyHfzR5YbF 48 | Fwr1MAsakzUuxeXLOfYhRugAl6p9PblrvMWZsmBpqeyhq0dSWz8t/8jp/l4Oo9bv 49 | JgrK/dolVU+sBIudgCl1ouzl8QzQpoo21yiVvfdsHFfDcIiayqTWfiAFWwypKil+ 50 | eFpXdXYRqoKjzNo0S2bSmzCaCmHxo7Kf39Rh6cOwfFwgRIlgO3Odyi6DZ0Y= 51 | -----END RSA PRIVATE KEY----- 52 | -------------------------------------------------------------------------------- /test/fixtures/certs/ca.txt: -------------------------------------------------------------------------------- 1 | E8E8B1291EDD3341 2 | -------------------------------------------------------------------------------- /test/fixtures/certs/client.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEQzCCAiugAwIBAgIJAOjosSke3TNAMA0GCSqGSIb3DQEBCwUAMDUxEzARBgNV 3 | BAoMClJlZGlzIFRlc3QxHjAcBgNVBAMMFUNlcnRpZmljYXRlIEF1dGhvcml0eTAe 4 | Fw0yNTA0MjEyMzQ2MjBaFw0yNjA0MjEyMzQ2MjBaMCkxEzARBgNVBAoMClJlZGlz 5 | IFRlc3QxEjAQBgNVBAMMCTEyNy4wLjAuMTCCASIwDQYJKoZIhvcNAQEBBQADggEP 6 | ADCCAQoCggEBAKXpCD+S8fpwUIoPqucA1HNhH0lbtbwxFskD+aY1q155k/skIs5l 7 | 56E0kjPukM54vaDzG7RLiZUsgGk1u7GuwTO3L8LDyE/yFbjdslUdCzSHgf3cuvMB 8 | 4mhZNwFTxmWnlhQ0NJbDX6g060M0zzgY6rjuyfAfGZ3rcvaoRgYIzQCjdni0IQBU 9 | Z0hVPj9GLVSNMiXR5OyTe/VZoBJQkeb7NwAAUPme/c3BmDfHkeSlitCsdltoIoI4 10 | a72u6ShgOqLqT76kvPXIqoxhF83B2Sjk204GMLg3D56Sm6aU4O+hrZku4ngSCiL1 11 | dkrWMi8nVFIDATSgk/ClwmW4URahus0QbjsCAwEAAaNiMGAwCwYDVR0PBAQDAgWg 12 | MBEGCWCGSAGG+EIBAQQEAwIHgDAdBgNVHQ4EFgQU+Ew8a/ddSCqaqmMZPwtv7enK 13 | 8QEwHwYDVR0jBBgwFoAUW6tovgq13gxbheR7IuGs7W7o+gQwDQYJKoZIhvcNAQEL 14 | BQADggIBABcFkuloL/JFgfHMWI30e/3vf+IVGvdurMKvdXn0UwVdavVYUAF+dgfI 15 | srYx6V809psuI1HR0NyU8HsQ0AXGm90SmHIKYcms5x6KmZRdmkw8CzIOwS3C9sCk 16 | DjowvfRaH59tZ5cKn019J2kWJCiKEih5LcyWEBYQxsUhqssq6MxsAKzgQr83XhLT 17 | YRLK4fat23IoGiyuPiIkiGRcB215VZinXN75Ulc7wCnGdFpQ/m8E9v8RQeZSJ9EY 18 | 7kozMmyAeoWv8fNcyK2kgQqSxRS7EElQ8Jwr0AmzqYkU4CIxmgwFS/1jYsmfO/H3 19 | W/Gc5u1uu3HwR6dm7bxjPY8GcUn+FnU0u3Ph2/uGFveyg9I4ja2QrlF02mxaEkr7 20 | jDINUu7p2VU0rWlE0meQD+OLdtDxV8LU89y3OEkqQYlAtgukPRriKf3i1qw9C68x 21 | +eiWO12hPJYmlX1IhqpKSin3zTQoIj7YPX/LmLdhsu/azrUZ2h34rBEsLBK8RTbw 22 | qFaKRUj5J/mI2UXLeHsCGqwgpewvFGjQU+BqwP1p4CEz5TH7oKWpDZTMZiMdcr8M 23 | xGWuoP6GdzZiUrrIzZUnfFrpWqjMLm+mFGhwlVNIpDshgZU9T7QUYAjIo1YGQMVz 24 | BBJcm3PLdUX4UNuxZuSVWP8MLc6hfPRIrIG4JE82bkfij6+RKpR3 25 | -----END CERTIFICATE----- 26 | -------------------------------------------------------------------------------- /test/fixtures/certs/client.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEApekIP5Lx+nBQig+q5wDUc2EfSVu1vDEWyQP5pjWrXnmT+yQi 3 | zmXnoTSSM+6Qzni9oPMbtEuJlSyAaTW7sa7BM7cvwsPIT/IVuN2yVR0LNIeB/dy6 4 | 8wHiaFk3AVPGZaeWFDQ0lsNfqDTrQzTPOBjquO7J8B8Znety9qhGBgjNAKN2eLQh 5 | AFRnSFU+P0YtVI0yJdHk7JN79VmgElCR5vs3AABQ+Z79zcGYN8eR5KWK0Kx2W2gi 6 | gjhrva7pKGA6oupPvqS89ciqjGEXzcHZKOTbTgYwuDcPnpKbppTg76GtmS7ieBIK 7 | IvV2StYyLydUUgMBNKCT8KXCZbhRFqG6zRBuOwIDAQABAoIBAHGZ0GYHbdy3Ts5Z 8 | 0AGAVffyxoNqYlPLoPhe2m/uS7rSsHrD0XlV2XZOEtWwQkK99cng7FVVa41S/VIM 9 | 0snlCLEqe292sw/aiPkeA9+3lVaQeneizfdakPY2MC2eeThduat325JnkHYSVgyc 10 | ek7E8ONTzb227clt0DgIHHpBSG1oZRhMLeRyHZACdoTVzdskBaIyyrNLRFwo0qCm 11 | YxPiuDSZLYgcuQ6U3m4N6oo8Sgy5eHjmYhbq7CYJ9InpANveZPZXUdz4b2LMpTMF 12 | uRGtCNSQ+VSG7nDX9TbkSWq2IbsAJh+KnX6vKBjbecpPu7p0DEa6iprSkxhVVUSX 13 | tsDCV+ECgYEA2s1ixwodPaGHTheXHAxEG/XQ5S8ADT8MlVYZJ5pixjj+skBjE5jr 14 | 2XaLxWQsljsydAcukau/YAOfljIVGhB88FO89ePAdVdaFHnof7iTAiIZ/wHPixVS 15 | PNDSFZ0CnJjJueEJ/3lwKNPT8a/pxCdu2WbLHuWfpoJJWBH7opdMxlkCgYEAwh20 16 | VGbUVOTLGBTmz71Q1Fc6ZWxrMT1ipcAxUvEi4rgBjQIhG1RH00C5UFchXH91ZIqz 17 | kHcaxceaRLZMhrfg0q+UM0F7wIkg2Dk2pWtmyy9+t+2Ot0yeLk36A/pNHobqn4Pq 18 | eR+tFXdDIqaBCj/TLbuP6zHDukXln+5zh8hz7rMCgYAM51nw9Ra+YL1TDK8bt1l4 19 | 8KlOKtRs84/xaq93F3LFz+ytZICzUixumcAqdvruLTWPhDDp5GAX8H7D49Y8wEYu 20 | AjI9qh3ajblBReNBTQhWct5nnJq50BsWfRY2shjKVXRoIu5tA6NqtPtl4IL/z3eJ 21 | GLfX7aDZuAtNR1o4v4WGmQKBgAdZOodrcSRZmPqrZ+V7ZED1oGdQiGpPyZk+wl9C 22 | c7CjiKN+7iPrt+BedeV9tuyagqYwvgV9DM1p9gQd5p2+/krbjL+3/ehXCKBG4jO2 23 | 8ihE/wYVfy6fPum/1/QomJzMPLuXMdwt/85tOmRoa0ApFGSJ0jP0KVW26a95RnRg 24 | eUsTAoGBAKaP9LrD9Cxu4naBW15K14sy/eRyqgvP7T0ScHjBBAHVYv6h9F8O7RhH 25 | VLm2OzPxIUdwuEnBUP5yQuI0VMi7EFgGX3Kg37aoDaZBNm7SCzlViBcEYHllOe90 26 | eJX7XqqFIBbXmB2Tjc7B1QPdP3Psl92D0w6C/q/QMDi/lqL1L9zv 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /test/fixtures/certs/openssl.cnf: -------------------------------------------------------------------------------- 1 | [ server_cert ] 2 | keyUsage = digitalSignature, keyEncipherment 3 | nsCertType = server 4 | [ client_cert ] 5 | keyUsage = digitalSignature, keyEncipherment 6 | nsCertType = client 7 | -------------------------------------------------------------------------------- /test/fixtures/certs/redis.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEIzCCAgugAwIBAgIJAOjosSke3TNBMA0GCSqGSIb3DQEBCwUAMDUxEzARBgNV 3 | BAoMClJlZGlzIFRlc3QxHjAcBgNVBAMMFUNlcnRpZmljYXRlIEF1dGhvcml0eTAe 4 | Fw0yNTA0MjEyMzQ2MjBaFw0yNjA0MjEyMzQ2MjBaMCkxEzARBgNVBAoMClJlZGlz 5 | IFRlc3QxEjAQBgNVBAMMCTEyNy4wLjAuMTCCASIwDQYJKoZIhvcNAQEBBQADggEP 6 | ADCCAQoCggEBAMN9gRk08ats5u753XNmwkDzyqvhBHJ7GvkyKE5BIA6jFlNR+miz 7 | ZhiKxgmfuSa139ojRfgqea3vUr03KdGXCn6uxucIJxzoJdMF3n6pnoE3znIO/SDn 8 | VbRiF773bIP1ssMnowYFXQWNqVLoQwEmJE+PVk1t3cXQ8bsiHEmpn4sAEO2hlztT 9 | ihRdRyDkeese5H/tGdUjNmfbyuSqoiqSHmWajiNSEELG3tzwEsY7mx6VQ8kdC+I5 10 | LHxwoRN/tB0lnlszgVs1CzsK64aPI/UzwLEdT3soytAbb/kwgq29719AS3cz9dgK 11 | Es0/FAbnfE1cbRtA1wkXcGDE594QiT+bNUUCAwEAAaNCMEAwHQYDVR0OBBYEFL9k 12 | kxWhI3Bu7TLZx4QLLzxb+DUwMB8GA1UdIwQYMBaAFFuraL4Ktd4MW4XkeyLhrO1u 13 | 6PoEMA0GCSqGSIb3DQEBCwUAA4ICAQBbp/46IIRwtyiWs3+KLLgpuVC8NRCJicUQ 14 | 7vb8MOusHkj7/bEhSDDiVzQc+FaqHOhAKA54/PUAXZlDV++H7yP+GMRKT59vP6F1 15 | XSG66zMZRC3nNKBZOc7aHLXJl5rrv7jHWowjQy10uhBM5xKVhb2mIKEUmdXCtN7+ 16 | AGw4g1UYe1SMM/PZtW83nq4GUmna2dN1+dTGMYXOn5/fHhxxRZy6TFq7VfuitOps 17 | V3uZ+V6z9IxIZEWxfvuNQbByK+BNaOSIALaWn++1sdfTPphH51c7sGcnonaXZUfn 18 | xiMsfEl6ZUUA54DHKGp9dtB4PySyACm4XzcdcaNV8ax2BcXr8ZQZkYG0o+4pAnvD 19 | Bltlw3YqrSDI2edcD2M1SpMEVqeDpbw5ZmsQjV73lAllVQXnvto7dgyJvp0f3ZqI 20 | xh9q4aDL1tV9MqKAZrd3CtRbjr6sPsrLhu1vPGjEiJho6nMdOef0E0rI4mgUpwQD 21 | Nx/izHwDYHhP6RvClMJ26+tnOE7qfPmbAnvCkVepCufr7zuRlq73LEEHzjGYjrcM 22 | CK2HSk9U4i/pnF77WK7IDL/UeT5b9upoqmDrO05QdTFS51+91cdXfzbLaSfqgaFV 23 | JuLE39nPjyhCnBQk4vyNsXpgxxYL4yUHGMctS+UPHADZW1xj5Zh55d6jg9+ixtpE 24 | oYYePoIHPw== 25 | -----END CERTIFICATE----- 26 | -------------------------------------------------------------------------------- /test/fixtures/certs/redis.dh: -------------------------------------------------------------------------------- 1 | -----BEGIN DH PARAMETERS----- 2 | MIIBCAKCAQEA+oW/vZAKdLFzHVqOuGMU8K3wEMbyG7EtC77s1lwpCO+SCXDoC9d1 3 | E/VHEXWk9IkHetUpYRQRNBMfJntBxh4v2TDYhdqwL9FkHrXsik1dz5jbILAminxV 4 | OJQ5H+qA1y8syQUyXs/DeeVwop3s5J+VeWV7dgNZ4Wi7RoN4rDZTsymIwvzyby9j 5 | TrM+CP+6Mo7OEM5PuDr6G7jEkDvzkbHf2MXImF+OQOiu4S+dfeiYPDqH7rc4X+i9 6 | F8XAsZEUCmIUeapcyuHtTTeXQd+ib/vHilB4WjRzPp7Qo/wjehoIq89J2jatH71K 7 | dHUqy115NDMkYdEM8j60+xQVeuRL5xETSwIBAg== 8 | -----END DH PARAMETERS----- 9 | -------------------------------------------------------------------------------- /test/fixtures/certs/redis.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEAw32BGTTxq2zm7vndc2bCQPPKq+EEcnsa+TIoTkEgDqMWU1H6 3 | aLNmGIrGCZ+5JrXf2iNF+Cp5re9SvTcp0ZcKfq7G5wgnHOgl0wXefqmegTfOcg79 4 | IOdVtGIXvvdsg/WywyejBgVdBY2pUuhDASYkT49WTW3dxdDxuyIcSamfiwAQ7aGX 5 | O1OKFF1HIOR56x7kf+0Z1SM2Z9vK5KqiKpIeZZqOI1IQQsbe3PASxjubHpVDyR0L 6 | 4jksfHChE3+0HSWeWzOBWzULOwrrho8j9TPAsR1PeyjK0Btv+TCCrb3vX0BLdzP1 7 | 2AoSzT8UBud8TVxtG0DXCRdwYMTn3hCJP5s1RQIDAQABAoIBAB3eJgRQ53+Wgu4O 8 | NPx1vcYouVVrar+G+YcLV8clAh3aYwXV55lpl3a2dS1xPtugPBRbAUy6SJ7/ireo 9 | HvaLyimy0GbqAvfSrDzCj8zwY2xAt4ULrzcAwUJvHkuqB+Vde7N/cdPwq9a2XyFw 10 | pRQe3LtfHgN0fsbDdrttqb7DcMHOuwR2QehOBVbJpmPKOJMsd2nNJfz/hR/sW0Ci 11 | UGfk5FsW/SNoG2wq8wptWF1F37pYweYqohnhwuzOBx0yyxVwckf3h18ErMeF0/xX 12 | pgUUImWYk/adWZBQzTyPgR2vvPcLis8ycCHozrbISE08RHfaP4OOFVrSTxFMvfGL 13 | 8GRctL0CgYEA+VReNsEFcByCzyhmGaM3ZVjAkjrWJwqcwVXQfDF0GU/zVZnxIfzI 14 | IelM0EySsr4BWtvPyyR7BSFelsDYerBu0Gb1/BKPjQ/gLFJuIuhnx3GGiG+RdAws 15 | yaR8tQC12ILtm98dxHLeTW6AsyCejtzH1gGHbR/uW6nkBjWbunTCkZMCgYEAyLhl 16 | f8HRMPuwTfQHiE7vIEshzAqNOr1dboV678q0usMxC6GcEjAMnZJYqUNtNZVPQb9y 17 | a41mSSP5rWHD+s5DZz3nLEwl3mWY2ZWMMj9WnCqtDrQ4tjbyn8A+J0hT7eH11MZb 18 | MHC6i6ARHEPeyCkNSBQirKR4nzgzM0J/wH4rRMcCgYEA6q6+C32915wOiF03VVRr 19 | FQroH/wfjRoRGG3k0rFd3WGC4oUHEn20By9o7PvWbUYpUlNqkISjAt45AV89pKYj 20 | eCghy4XQ9u8Fi9J+9n6ZCILUJeIWIAxBr/8SnvCvOb9rVfc6NqoEkw+7NmAyvrgT 21 | pV1FErMmkcMk7a9SCLxUU98CgYBD1fYPsGxHtrhGEDQ/gBXW/y1j7Sj/8iHSiXAb 22 | /JEKEY/Q04SQrQaGdoBabDxLgLOxj8dWzAoGrA7k5wa6C93B1az8Tpv5xrJazuz7 23 | ymY2D0I/lu8XvghPr0QSOKKM4fIYQBVvkJmrOKSvvcxcL2uasZtqZ4eQoAjFyTKt 24 | 1rY+3wKBgAqJ3AwQVSSn/2oyNicJMKiozwtO+rO0rl5+Tbq70RhRQ0CI/7fnsINl 25 | xzYtE4NmNMda6PBNh5YmGfhGiBKWxu5mKrglEA6kCLhd0CbItVCXzTmnTk74+wxh 26 | R6vvajHDXKQKw/50wGjqNZxP1pP6eMh4Cjo50s5mb6EsLxCGcl+P 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /test/fixtures/certs/server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEQzCCAiugAwIBAgIJAOjosSke3TM/MA0GCSqGSIb3DQEBCwUAMDUxEzARBgNV 3 | BAoMClJlZGlzIFRlc3QxHjAcBgNVBAMMFUNlcnRpZmljYXRlIEF1dGhvcml0eTAe 4 | Fw0yNTA0MjEyMzQ2MjBaFw0yNjA0MjEyMzQ2MjBaMCkxEzARBgNVBAoMClJlZGlz 5 | IFRlc3QxEjAQBgNVBAMMCTEyNy4wLjAuMTCCASIwDQYJKoZIhvcNAQEBBQADggEP 6 | ADCCAQoCggEBAMH782VCzecCYzNf1lYFynMcuO6hH9Y2O7pGU9XnKESUz2STyMCI 7 | cUmSEqVGBjGurUrwt5f9ocMci4b6xl7SCw19UTU8wPVOB+CVxMRrH+haWO1Rf3Po 8 | D1L768KM4IAZNrehWmzWkiw36ZFQxykui/B0imF0+S5LFFjv+GnZdnXYKumL8TnU 9 | 6fwFiyfyDpPBiwBBV8aWf2t6n/KXwdM6aTgxgKwQYKmAuYw20N8UrwS+9LzTsKLM 10 | 7NJ3zSf3mbR5B4iZTCksYlwpYmpmGBODCbWcbqCt/4GDFsRiWl519RBTzAa/0OmH 11 | OHBEr2EjktYFp0W5OsP26YtviI4mH1BZrZcCAwEAAaNiMGAwCwYDVR0PBAQDAgWg 12 | MBEGCWCGSAGG+EIBAQQEAwIGQDAdBgNVHQ4EFgQUD6xb1kjJDasoygpvRipcixOs 13 | CXcwHwYDVR0jBBgwFoAUW6tovgq13gxbheR7IuGs7W7o+gQwDQYJKoZIhvcNAQEL 14 | BQADggIBABUjuxDrEy+o+3ozxOaJCy0GRWcdejQg9SBvXZAOCjTN5J5zUc5mRNVK 15 | AUOF9l337c92VRFEj784O/2C21usx5yYHk8ERdNwIhOY20KdhKFlEa9fY/HJ4fbI 16 | 05nZWmWMhatlTYdRlKOFBtMe6GPdHAIAPQE7cEYLibMQyWILhQdvBAVUqeJzQzXN 17 | khYtPx8MDWyGfM5vSx7GT10Zjd8HEAcn3IXG9X4tht2kLL5yB+JK/I+YZzkmTGyh 18 | Jfgqq3nYGBtBFUVv9mhjLfGMz8B9irMUUcqikPIqwoPxHddCPnmzYruozne+Rt/B 19 | v6UODkvjqckc8tKcaBoZFzfGqf4auflz0Gs0IZzwavJR7Pt8i25JA9inVZdjuPHM 20 | JFQ3a98XC6Re2al72RyDL2RkDvFdT7mlYTSyy93Fc40XkQcp/p20CoWTL9HwDlZu 21 | 3Nw2HcSrMG5EPfKiFu5vRvlnuehdZg5I/xqHhliAndaO20YUF/yYKkoWaombDfnT 22 | Owm+OIdfC3CuIN/0fM45+Qoe/3TwB0aPE+NsPJgAG3JxClTtgcQlYyACcitgKWTe 23 | YlNSSf94nF55o80Dk6Yx2fISZFnuj7bLlPytFcsXGYrad8MDDMEwjMwXdrNFnRCe 24 | 1WnYTqaMRiytnfS9Yw2AbSZhhdLsvh47HWgN3QFQex2Gb7cKMaoJ 25 | -----END CERTIFICATE----- 26 | -------------------------------------------------------------------------------- /test/fixtures/certs/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEAwfvzZULN5wJjM1/WVgXKcxy47qEf1jY7ukZT1ecoRJTPZJPI 3 | wIhxSZISpUYGMa6tSvC3l/2hwxyLhvrGXtILDX1RNTzA9U4H4JXExGsf6FpY7VF/ 4 | c+gPUvvrwozggBk2t6FabNaSLDfpkVDHKS6L8HSKYXT5LksUWO/4adl2ddgq6Yvx 5 | OdTp/AWLJ/IOk8GLAEFXxpZ/a3qf8pfB0zppODGArBBgqYC5jDbQ3xSvBL70vNOw 6 | oszs0nfNJ/eZtHkHiJlMKSxiXCliamYYE4MJtZxuoK3/gYMWxGJaXnX1EFPMBr/Q 7 | 6Yc4cESvYSOS1gWnRbk6w/bpi2+IjiYfUFmtlwIDAQABAoIBAB+8equcbFdY+qXT 8 | DhvwwphmoJLZ5X2ETe1ByEF8mgfuWKfZzcRCDla9ATPs6uKB83QJQeAp7KchKmqg 9 | 6Idm0cwZLooJMIBxjbRejFyeMhAvh9D7vmBWHPu0n3Oq3KfYeC0+xq57xFpbo2jU 10 | 0GCabuaeCm27V3ENc3zBdeDLZSgONgoaj5PVFi+MxxNV+oXXtQZdBplFfOuy24zk 11 | w+6KHkVoa3uZDQAK87ca1zrpL7qkLuqzGkckpxr2HOXPXYazhXMXBDkbuV+PlQhI 12 | TXVMxd2PMFVj64cNWVkJbO2C6CtGZUq48+SmrRMCF0OBEI85zohU3FAEqKGj9mo/ 13 | AtDcPgECgYEA/9bHN9XMe/f7dyE//UW0JNsctRqcJ7BN1XhuUEZyOUI2Iex5LRLm 14 | 6W6+LuoQOPBO+tfURbbEl4/RMqu14jfoCo/gMoSsshyQaKyCh2VywGTUpScjbgiT 15 | 0wr4G7yajzKIhWzmQ+jee84qDELQRte75EODFcqsFzABcK3BXP9NGBcCgYEAwhs0 16 | znCr4qjPO4O7z/vptRPzebF3o94iisgWCVLDAsAyQ5Oxi+dmGl5acfjT4cPt3Jnu 17 | QFIzQaS0dHn1VDU/R8jpcR8s+FLd3jEhjcbLNXFSgox+LbHVnK11Xy1MMkohaEmS 18 | h0ghdhAY9ZiorSmK/0TK7NgIkuMgu7WrQ4mQBoECgYEAlNJwgrdYwwhm/E6YNZGV 19 | kBbxpRv8mE3DiRkMOqAwE8TDToqLlr+3GTU1Zn77vtNzbhGcxozh4TRkwfAG1rgk 20 | v/gft+NbviRFkM5BA9fsn6RH2mZhAsH0k8B+wUu+MOx5Y/wMGpbczPIJnaZEF+Go 21 | x8jJ+SQzZS2kuNIqeBl+1DMCgYBg0hVLDCSQ0Mdd1l3uZqeyrRr7jqwwzvLH6voi 22 | +GdRjfEEiD09ndTuPjY7N3To3kRdj2KqLtZmXfOtTdAzisPf2LWouXZC/4Kv/C3S 23 | fGCMbdRMTiv6OwRkPJmZOg0R4Kw9SsWOOUqHi4wHpXgtt9Ufc38NGM1eB3EicIHX 24 | FF0FAQKBgC2R5qQ+4Exktz8+TUFDYoMFr6tFYAVA7+vq6IJcxkb9bq4bvjKOJX54 25 | VDMuzaQ0rgyS7V8eSMBWjzq/up8JT9sfZPasChdU6tcOOXTpbcaDc/K+mgvKbF03 26 | cO/mKPFFcqI5leJOgyPjLW2hySptzzUBNfbQS/Jnse2WyFlXbYjD 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /test/fixtures/generate-certs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Generate some test certificates which are used by the regression test suite: 4 | # 5 | # test/fixtures/certs/ca.{crt,key} Self signed CA certificate. 6 | # test/fixtures/certs/redis.{crt,key} A certificate with no key usage/policy restrictions. 7 | # test/fixtures/certs/client.{crt,key} A certificate restricted for SSL client usage. 8 | # test/fixtures/certs/server.{crt,key} A certificate restricted for SSL server usage. 9 | # test/fixtures/certs/redis.dh DH Params file. 10 | 11 | generate_cert() { 12 | local name=$1 13 | local cn="$2" 14 | local opts="$3" 15 | 16 | local keyfile=test/fixtures/certs/${name}.key 17 | local certfile=test/fixtures/certs/${name}.crt 18 | 19 | [ -f $keyfile ] || openssl genrsa -out $keyfile 2048 20 | openssl req \ 21 | -new -sha256 \ 22 | -subj "/O=Redis Test/CN=$cn" \ 23 | -key $keyfile | \ 24 | openssl x509 \ 25 | -req -sha256 \ 26 | -CA test/fixtures/certs/ca.crt \ 27 | -CAkey test/fixtures/certs/ca.key \ 28 | -CAserial test/fixtures/certs/ca.txt \ 29 | -CAcreateserial \ 30 | -days 365 \ 31 | $opts \ 32 | -out $certfile 33 | } 34 | 35 | mkdir -p tests/tls 36 | [ -f test/fixtures/certs/ca.key ] || openssl genrsa -out test/fixtures/certs/ca.key 4096 37 | openssl req \ 38 | -x509 -new -nodes -sha256 \ 39 | -key test/fixtures/certs/ca.key \ 40 | -days 3650 \ 41 | -subj '/O=Redis Test/CN=Certificate Authority' \ 42 | -out test/fixtures/certs/ca.crt 43 | 44 | cat > test/fixtures/certs/openssl.cnf <<_END_ 45 | [ server_cert ] 46 | keyUsage = digitalSignature, keyEncipherment 47 | nsCertType = server 48 | [ client_cert ] 49 | keyUsage = digitalSignature, keyEncipherment 50 | nsCertType = client 51 | _END_ 52 | 53 | generate_cert server "127.0.0.1" "-extfile test/fixtures/certs/openssl.cnf -extensions server_cert" 54 | generate_cert client "127.0.0.1" "-extfile test/fixtures/certs/openssl.cnf -extensions client_cert" 55 | generate_cert redis "127.0.0.1" 56 | 57 | [ -f test/fixtures/certs/redis.dh ] || openssl dhparam -out test/fixtures/certs/redis.dh 2048 -------------------------------------------------------------------------------- /test/hiredis/test_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../test_helper" 4 | 5 | # See: https://github.com/redis-rb/redis-client/issues/16 6 | # The hiredis-rb gems expose all hiredis symbols, so we must be careful 7 | # about how we link against it. 8 | unless RUBY_PLATFORM == "java" 9 | require "redis" 10 | require "hiredis" 11 | 12 | begin 13 | Redis.new(driver: :hiredis).ping 14 | rescue 15 | nil # does not matter, we just want to load the library 16 | end 17 | end 18 | 19 | require "hiredis-client" 20 | 21 | unless RedisClient.default_driver == RedisClient::HiredisConnection 22 | abort("Hiredis not defined as default driver") 23 | end 24 | 25 | begin 26 | # This method was added in Ruby 3.0.0. Calling it this way asks the GC to 27 | # move objects around, helping to find object movement bugs. 28 | if RUBY_VERSION >= "3.2.0" 29 | GC.verify_compaction_references(expand_heap: true, toward: :empty) 30 | else 31 | GC.verify_compaction_references(double_heap: true, toward: :empty) 32 | end 33 | rescue NoMethodError 34 | end 35 | -------------------------------------------------------------------------------- /test/redis_client/circuit_breaker_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | class RedisClient 6 | class CircuitBreakerTest < RedisClientTestCase 7 | include ClientTestHelper 8 | 9 | def setup 10 | super 11 | @circuit_breaker = CircuitBreaker.new( 12 | error_threshold: 3, 13 | error_threshold_timeout: 2, 14 | success_threshold: 2, 15 | error_timeout: 1, 16 | ) 17 | end 18 | 19 | def test_open_circuit_after_consecutive_errors 20 | open_circuit @circuit_breaker 21 | assert_open @circuit_breaker 22 | end 23 | 24 | def test_allow_use_after_the_errors_timedout 25 | open_circuit @circuit_breaker 26 | assert_open @circuit_breaker 27 | 28 | travel(@circuit_breaker.error_threshold_timeout) do 29 | assert_closed(@circuit_breaker) 30 | end 31 | end 32 | 33 | def test_reopen_immediately_when_half_open 34 | open_circuit @circuit_breaker 35 | assert_open @circuit_breaker 36 | 37 | travel(@circuit_breaker.error_timeout) do 38 | record_error(@circuit_breaker) 39 | assert_open(@circuit_breaker) 40 | end 41 | end 42 | 43 | def test_close_fully_after_success_threshold_is_reached 44 | open_circuit @circuit_breaker 45 | assert_open @circuit_breaker 46 | 47 | travel(@circuit_breaker.error_timeout) do 48 | @circuit_breaker.success_threshold.times do 49 | assert_closed(@circuit_breaker) 50 | end 51 | 52 | record_error(@circuit_breaker) 53 | assert_closed(@circuit_breaker) 54 | end 55 | end 56 | 57 | private 58 | 59 | def assert_open(circuit_breaker) 60 | assert_raises CircuitBreaker::OpenCircuitError do 61 | circuit_breaker.protect do 62 | # noop 63 | end 64 | end 65 | end 66 | 67 | def assert_closed(circuit_breaker) 68 | assert_equal(:result, circuit_breaker.protect { :result }) 69 | end 70 | 71 | def open_circuit(circuit_breaker) 72 | circuit_breaker.error_threshold.times do 73 | record_error(circuit_breaker) 74 | end 75 | end 76 | 77 | def record_error(circuit_breaker) 78 | assert_raises CannotConnectError do 79 | circuit_breaker.protect do 80 | raise CannotConnectError, "Oh no!" 81 | end 82 | end 83 | end 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /test/redis_client/command_builder_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | class RedisClient 6 | class CommandBuilderTest < RedisClientTestCase 7 | def test_positional 8 | assert_equal ["a", "b", "c"], call("a", "b", "c") 9 | end 10 | 11 | def test_array 12 | assert_equal ["a", "b", "c"], call("a", ["b", "c"]) 13 | end 14 | 15 | def test_hash 16 | assert_equal ["a", "b", "c"], call("a", { "b" => "c" }) 17 | end 18 | 19 | def test_symbol 20 | assert_equal ["a", "b", "c", "d"], call(:a, { b: :c }, :d) 21 | end 22 | 23 | def test_numeric 24 | assert_equal ["1", "2.3"], call(1, 2.3) 25 | end 26 | 27 | def test_kwargs_boolean 28 | assert_equal ["withscores"], call(ttl: nil, ex: false, withscores: true) 29 | end 30 | 31 | def test_kwargs_values 32 | assert_equal ["ttl", "42"], call(ttl: 42) 33 | end 34 | 35 | def test_nil_kwargs 36 | assert_equal ["a", "b", "c"], CommandBuilder.generate(%i(a b c)) 37 | end 38 | 39 | private 40 | 41 | def call(*args, **kwargs) 42 | CommandBuilder.generate(args, kwargs) 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /test/redis_client/decorator_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | class RedisClient 6 | module DecoratorTests 7 | include ClientTestHelper 8 | include RedisClientTests 9 | 10 | module Commands 11 | def exists?(key) 12 | call("EXISTS", key) { |c| c > 0 } 13 | end 14 | end 15 | 16 | MyDecorator = Decorator.create(Commands) 17 | class MyDecorator 18 | def test? 19 | true 20 | end 21 | end 22 | 23 | def test_custom_command_helpers 24 | @redis.call("SET", "key", "hello") 25 | assert_equal 1, @redis.call("EXISTS", "key") 26 | assert_equal true, @redis.exists?("key") 27 | assert_equal([true], @redis.pipelined { |p| p.exists?("key") }) 28 | assert_equal([true], @redis.multi { |p| p.exists?("key") }) 29 | end 30 | 31 | def test_client_methods_not_available_on_pipelines 32 | assert_equal true, @redis.test? 33 | 34 | @redis.pipelined do |pipeline| 35 | assert_equal false, pipeline.respond_to?(:test?) 36 | end 37 | 38 | @redis.multi do |pipeline| 39 | assert_equal false, pipeline.respond_to?(:test?) 40 | end 41 | end 42 | end 43 | 44 | class DecoratorTest < RedisClientTestCase 45 | include DecoratorTests 46 | 47 | private 48 | 49 | def new_client(**overrides) 50 | MyDecorator.new(RedisClient.config(**tcp_config.merge(overrides)).new_client) 51 | end 52 | end 53 | 54 | class PooledDecoratorTest < RedisClientTestCase 55 | include DecoratorTests 56 | 57 | private 58 | 59 | def new_client(**overrides) 60 | MyDecorator.new(RedisClient.config(**tcp_config.merge(overrides)).new_pool) 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /test/redis_client/middlewares_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | class RedisClient 6 | class MiddlewaresTest < RedisClientTestCase 7 | include ClientTestHelper 8 | 9 | def setup 10 | @original_module = RedisClient::Middlewares 11 | new_module = @original_module.dup 12 | RedisClient.send(:remove_const, :Middlewares) 13 | RedisClient.const_set(:Middlewares, new_module) 14 | RedisClient.register(TestMiddleware) 15 | super 16 | TestMiddleware.calls.clear 17 | end 18 | 19 | def teardown 20 | if @original_module 21 | RedisClient.send(:remove_const, :Middlewares) 22 | RedisClient.const_set(:Middlewares, @original_module) 23 | end 24 | TestMiddleware.calls.clear 25 | super 26 | end 27 | 28 | def test_call_instrumentation 29 | @redis.call("PING") 30 | assert_call [:call, :success, ["PING"], "PONG", @redis.config] 31 | end 32 | 33 | def test_failing_call_instrumentation 34 | assert_raises CommandError do 35 | @redis.call("PONG") 36 | end 37 | call = TestMiddleware.calls.first 38 | assert_equal [:call, :error, ["PONG"]], call.first(3) 39 | assert_instance_of CommandError, call[3] 40 | end 41 | 42 | def test_call_once_instrumentation 43 | @redis.call_once("PING") 44 | assert_call [:call, :success, ["PING"], "PONG", @redis.config] 45 | end 46 | 47 | def test_blocking_call_instrumentation 48 | @redis.blocking_call(nil, "PING") 49 | assert_call [:call, :success, ["PING"], "PONG", @redis.config] 50 | end 51 | 52 | def test_pipeline_instrumentation 53 | @redis.pipelined do |pipeline| 54 | pipeline.call("PING") 55 | end 56 | assert_call [:pipeline, :success, [["PING"]], ["PONG"], @redis.config] 57 | end 58 | 59 | def test_multi_instrumentation 60 | @redis.multi do |transaction| 61 | transaction.call("PING") 62 | end 63 | assert_call [ 64 | :pipeline, 65 | :success, 66 | [["MULTI"], ["PING"], ["EXEC"]], 67 | ["OK", "QUEUED", ["PONG"]], 68 | @redis.config, 69 | ] 70 | end 71 | 72 | module DummyMiddleware 73 | def call(command, _config, &_) 74 | command 75 | end 76 | 77 | def call_pipelined(commands, _config, &_) 78 | commands 79 | end 80 | end 81 | 82 | def test_instance_middleware 83 | second_client = new_client(middlewares: [DummyMiddleware]) 84 | assert_equal ["GET", "2"], second_client.call("GET", 2) 85 | assert_equal([["GET", "2"]], second_client.pipelined { |p| p.call("GET", 2) }) 86 | end 87 | 88 | private 89 | 90 | def assert_call(call) 91 | assert_equal call, TestMiddleware.calls.first 92 | assert_equal 1, TestMiddleware.calls.size 93 | end 94 | 95 | def assert_calls(calls) 96 | assert_equal calls, TestMiddleware.calls 97 | end 98 | 99 | module TestMiddleware 100 | class << self 101 | attr_accessor :calls 102 | end 103 | @calls = [] 104 | 105 | def connect(config) 106 | result = super 107 | TestMiddleware.calls << [:connect, :success, result, config] 108 | result 109 | rescue => error 110 | TestMiddleware.calls << [:connect, :error, error, config] 111 | raise 112 | end 113 | 114 | def call(command, config) 115 | result = super 116 | TestMiddleware.calls << [:call, :success, command, result, config] 117 | result 118 | rescue => error 119 | TestMiddleware.calls << [:call, :error, command, error, config] 120 | raise 121 | end 122 | 123 | def call_pipelined(commands, config) 124 | result = super 125 | TestMiddleware.calls << [:pipeline, :success, commands, result, config] 126 | result 127 | rescue => error 128 | TestMiddleware.calls << [:pipeline, :error, commands, error, config] 129 | raise 130 | end 131 | end 132 | end 133 | end 134 | -------------------------------------------------------------------------------- /test/redis_client/pooled_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | class RedisPooledClientTest < RedisClientTestCase 6 | include ClientTestHelper 7 | include RedisClientTests 8 | 9 | def test_checkout_timeout 10 | pool = RedisClient.config(**tcp_config).new_pool(size: 1, timeout: 0.01) 11 | Thread.new { pool.instance_variable_get(:@pool).checkout }.join 12 | 13 | error = assert_raises RedisClient::ConnectionError do 14 | pool.with {} 15 | end 16 | assert_includes error.message, "Couldn't checkout a connection in time: Waited 0.01 sec" 17 | end 18 | 19 | private 20 | 21 | def new_client(**overrides) 22 | RedisClient.config(**tcp_config.merge(overrides)).new_pool 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /test/redis_client/ractor_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | class RactorTest < RedisClientTestCase 6 | tag isolated: true 7 | 8 | def setup 9 | skip("Ractors are not supported on this Ruby version") unless defined?(::Ractor) 10 | skip("Hiredis is not Ractor safe") if RedisClient.default_driver.name == "RedisClient::HiredisConnection" 11 | begin 12 | ractor_value(Ractor.new { RedisClient.default_driver.name }) 13 | rescue Ractor::RemoteError 14 | skip("Ractor implementation is too limited (MRI 3.0?)") 15 | end 16 | super 17 | end 18 | 19 | def test_get_and_set_within_ractor 20 | ractor = Ractor.new do 21 | config = Ractor.receive 22 | within_ractor_redis = RedisClient.new(**config) 23 | within_ractor_redis.call("SET", "foo", "bar") 24 | within_ractor_redis.call("GET", "foo") 25 | end 26 | ractor.send(ClientTestHelper.tcp_config.freeze) 27 | 28 | assert_equal("bar", ractor_value(ractor)) 29 | end 30 | 31 | def test_multiple_ractors 32 | ractor1 = Ractor.new do 33 | config = Ractor.receive 34 | within_ractor_redis = RedisClient.new(**config) 35 | within_ractor_redis.call("SET", "foo", "bar") 36 | within_ractor_redis.call("GET", "foo") 37 | end 38 | ractor1.send(ClientTestHelper.tcp_config.freeze) 39 | 40 | ractor_value(ractor1) # We do this to ensure that the SET has been processed 41 | 42 | ractor2 = Ractor.new do 43 | config = Ractor.receive 44 | within_ractor_redis = RedisClient.new(**config) 45 | key = Ractor.receive 46 | within_ractor_redis.call("GET", key) 47 | end 48 | ractor2.send(ClientTestHelper.tcp_config.freeze) 49 | ractor2.send("foo") 50 | 51 | assert_equal("bar", ractor_value(ractor2)) 52 | end 53 | 54 | if defined?(Ractor) && Ractor.method_defined?(:value) # Ruby 3.5+ 55 | def ractor_value(ractor) 56 | ractor.value 57 | end 58 | else 59 | def ractor_value(ractor) 60 | ractor.take 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /test/redis_client/resp3_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | require "redis_client/ruby_connection/buffered_io" 6 | require "redis_client/ruby_connection/resp3" 7 | 8 | class RedisClient 9 | class RESP3Test < RedisClientTestCase 10 | class StringIO < ::StringIO 11 | def skip(offset) 12 | seek(offset, IO::SEEK_CUR) 13 | nil 14 | end 15 | end 16 | 17 | def test_dump_mixed_encoding 18 | assert_dumps ["SET", "fée", "\xC6bIJ"], "*3\r\n$3\r\nSET\r\n$4\r\nfée\r\n$4\r\n\xC6bIJ\r\n" 19 | end 20 | 21 | def test_dump_string 22 | assert_dumps ["Hello World!"], "*1\r\n$12\r\nHello World!\r\n" 23 | assert_dumps ["Hello\r\nWorld!"], "*1\r\n$13\r\nHello\r\nWorld!\r\n" 24 | end 25 | 26 | def test_dump_integer 27 | assert_dumps [42], "*1\r\n$2\r\n42\r\n" 28 | end 29 | 30 | def test_dump_true 31 | assert_raises TypeError do 32 | RESP3.dump([true]) 33 | end 34 | end 35 | 36 | def test_dump_false 37 | assert_raises TypeError do 38 | RESP3.dump([false]) 39 | end 40 | end 41 | 42 | def test_dump_nil 43 | assert_raises TypeError do 44 | RESP3.dump([nil]) 45 | end 46 | end 47 | 48 | def test_dump_big_integer 49 | assert_dumps [1_000_000_000_000_000_000_000], "*1\r\n$22\r\n1000000000000000000000\r\n" 50 | end 51 | 52 | def test_dump_float 53 | assert_dumps [42.42], "*1\r\n$5\r\n42.42\r\n" 54 | # TODO: What about NaN, Infinity, -Infinity 55 | end 56 | 57 | def test_dump_array 58 | assert_dumps ["PRINT", [1, 2, 3]], "*4\r\n$5\r\nPRINT\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n3\r\n" 59 | end 60 | 61 | def test_dump_hash 62 | assert_dumps(["PRINT", { 'first' => 1, 'second' => 2 }], "*5\r\n$5\r\nPRINT\r\n$5\r\nfirst\r\n$1\r\n1\r\n$6\r\nsecond\r\n$1\r\n2\r\n") 63 | end 64 | 65 | def test_dump_subclasses 66 | my_string_class = Class.new(String) 67 | assert_dumps [my_string_class.new("Hello")], "*1\r\n$5\r\nHello\r\n" 68 | end 69 | 70 | def test_load_blob_string 71 | assert_parses "Hello World!", "$12\r\nHello World!\r\n" 72 | end 73 | 74 | def test_load_simple_string 75 | assert_parses "Hello World!", "+Hello World!\r\n" 76 | end 77 | 78 | def test_load_error 79 | assert_parses CommandError.parse("SOMEERROR"), "-SOMEERROR\r\n" 80 | end 81 | 82 | def test_load_integer 83 | assert_parses 42, ":42\r\n" 84 | assert_parses(-42, ":-42\r\n") 85 | assert_parses 3_492_890_328_409_238_509_324_850_943_850_943_825_024_385, "(3492890328409238509324850943850943825024385\r\n" 86 | end 87 | 88 | def test_load_double 89 | assert_parses 42.42, ",42.42\r\n" 90 | assert_parses(-42.42, ",-42.42\r\n") 91 | assert_parses Float::INFINITY, ",inf\r\n" 92 | assert_parses(-Float::INFINITY, ",-inf\r\n") 93 | assert_parses(nil, ",nan\r\n") do |actual| 94 | assert_predicate actual, :nan? 95 | end 96 | end 97 | 98 | def test_load_null 99 | assert_parses nil, "_\r\n" 100 | end 101 | 102 | def test_load_boolean 103 | assert_parses true, "#t\r\n" 104 | assert_parses false, "#f\r\n" 105 | end 106 | 107 | def test_load_array 108 | assert_parses [1, 2, 3], "*3\r\n:1\r\n:2\r\n:3\r\n" 109 | end 110 | 111 | def test_load_multibyte_chars 112 | # Check that the buffer is properly operating over bytes and not characters 113 | assert_parses ["€™€™", 2], "*2\r\n$12\r\n€™€™\r\n:2\r\n" 114 | end 115 | 116 | def test_load_set 117 | assert_parses ['orange', 'apple', true, 100, 999], "~5\r\n+orange\r\n+apple\r\n#t\r\n:100\r\n:999\r\n" 118 | end 119 | 120 | def test_load_map 121 | assert_parses({ 'first' => 1, 'second' => 2 }, "%2\r\n+first\r\n:1\r\n+second\r\n:2\r\n") 122 | end 123 | 124 | def test_load_large_map 125 | entries = 100_000 126 | payload = +"%#{entries}\r\n" 127 | entries.times do |i| 128 | payload << "+#{i}\r\n:#{i}\r\n" 129 | end 130 | expected = entries.times.each_with_object({}) { |i, h| h[i.to_s] = i } 131 | assert_parses(expected, payload) 132 | end 133 | 134 | def test_load_verbatim_string 135 | assert_parses "Some string", "=15\r\ntxt:Some string\r\n" 136 | end 137 | 138 | private 139 | 140 | def assert_parses(expected, payload) 141 | raw_io = StringIO.new(payload.b) 142 | io = RedisClient::RubyConnection::BufferedIO.new(raw_io, read_timeout: 1, write_timeout: 1) 143 | actual = RESP3.load(io) 144 | if block_given? 145 | yield actual 146 | elsif expected.nil? 147 | assert_nil actual 148 | else 149 | assert_equal(expected, actual) 150 | end 151 | 152 | assert io.eof?, "Expected IO to be fully consumed: #{raw_io.read.inspect}" 153 | end 154 | 155 | def assert_dumps(payload, expected) 156 | buffer = RESP3.dump(payload) 157 | assert_equal expected.b, buffer 158 | assert_equal Encoding::BINARY, buffer.encoding 159 | end 160 | end 161 | end 162 | -------------------------------------------------------------------------------- /test/redis_client/subscriptions_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | class RedisClient 6 | class SubscriptionsTest < RedisClientTestCase 7 | include ClientTestHelper 8 | 9 | def setup 10 | super 11 | @subscription = @redis.pubsub 12 | end 13 | 14 | def test_subscribe 15 | assert_nil @subscription.call("SUBSCRIBE", "mychannel") 16 | 17 | @redis.pipelined do |pipeline| 18 | 3.times do |i| 19 | pipeline.call("PUBLISH", "mychannel", "event-#{i}") 20 | end 21 | end 22 | 23 | events = [] 24 | while event = @subscription.next_event 25 | events << event 26 | end 27 | 28 | assert_equal [ 29 | ["subscribe", "mychannel", 1], 30 | ["message", "mychannel", "event-0"], 31 | ["message", "mychannel", "event-1"], 32 | ["message", "mychannel", "event-2"], 33 | ], events 34 | end 35 | 36 | def test_psubscribe 37 | assert_nil @subscription.call("PSUBSCRIBE", "my*") 38 | 39 | @redis.pipelined do |pipeline| 40 | 3.times do |i| 41 | pipeline.call("PUBLISH", "mychannel", "event-#{i}") 42 | end 43 | end 44 | 45 | events = [] 46 | while event = @subscription.next_event 47 | events << event 48 | end 49 | 50 | assert_equal [ 51 | ["psubscribe", "my*", 1], 52 | ["pmessage", "my*", "mychannel", "event-0"], 53 | ["pmessage", "my*", "mychannel", "event-1"], 54 | ["pmessage", "my*", "mychannel", "event-2"], 55 | ], events 56 | end 57 | 58 | def test_connection_lost 59 | assert_nil @subscription.call("SUBSCRIBE", "mychannel") 60 | @redis.call("PUBLISH", "mychannel", "event-0") 61 | assert_equal ["subscribe", "mychannel", 1], @subscription.next_event 62 | assert_equal ["message", "mychannel", "event-0"], @subscription.next_event 63 | 64 | assert_nil @subscription.next_event(0.2) 65 | assert_nil @subscription.next_event(0.2) 66 | end 67 | 68 | def test_close 69 | assert_nil @subscription.call("SUBSCRIBE", "mychannel") 70 | @redis.pipelined do |pipeline| 71 | 3.times do |i| 72 | pipeline.call("PUBLISH", "mychannel", "event-#{i}") 73 | end 74 | end 75 | 76 | assert_equal ["subscribe", "mychannel", 1], @subscription.next_event 77 | assert_equal @subscription, @subscription.close 78 | assert_raises ConnectionError do 79 | @subscription.next_event 80 | end 81 | end 82 | 83 | def test_next_event_timeout 84 | assert_nil @subscription.next_event(0.01) 85 | end 86 | 87 | def test_pubsub_with_disabled_reconnection 88 | @redis.send(:ensure_connected, retryable: false) do 89 | refute_nil @redis.pubsub 90 | end 91 | end 92 | 93 | def test_pubsub_timeout_retry 94 | assert_nil @subscription.call("SUBSCRIBE", "mychannel") 95 | refute_nil @subscription.next_event # subscribed event 96 | 97 | assert_nil @subscription.next_event(0.01) 98 | @redis.call("PUBLISH", "mychannel", "test") 99 | 1.times.each do # rubocop:disable Lint/UselessTimes 100 | # We use 1.times.each to change the stack depth. 101 | # See https://github.com/redis-rb/redis-client/issues/221 102 | refute_nil @subscription.next_event 103 | end 104 | end 105 | end 106 | end 107 | -------------------------------------------------------------------------------- /test/redis_client_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | class RedisClientTest < RedisClientTestCase 6 | include ClientTestHelper 7 | include RedisClientTests 8 | 9 | def test_preselect_database 10 | client = new_client(db: 5) 11 | assert_includes client.call("CLIENT", "INFO"), " db=5 " 12 | client.call("SELECT", 6) 13 | assert_includes client.call("CLIENT", "INFO"), " db=6 " 14 | client.close 15 | assert_includes client.call("CLIENT", "INFO"), " db=5 " 16 | end 17 | 18 | def test_set_client_id 19 | client = new_client(id: "peter") 20 | assert_includes client.call("CLIENT", "INFO"), " name=peter " 21 | client.call("CLIENT", "SETNAME", "steven") 22 | assert_includes client.call("CLIENT", "INFO"), " name=steven " 23 | client.close 24 | assert_includes client.call("CLIENT", "INFO"), " name=peter " 25 | end 26 | 27 | def test_encoding 28 | @redis.call("SET", "str", "fée") 29 | str = @redis.call("GET", "str") 30 | 31 | assert_equal Encoding::UTF_8, str.encoding 32 | assert_predicate str, :valid_encoding? 33 | 34 | bytes = "\xFF\00" 35 | refute_predicate bytes, :valid_encoding? 36 | 37 | @redis.call("SET", "str", bytes.b) 38 | str = @redis.call("GET", "str") 39 | 40 | assert_equal Encoding::BINARY, str.encoding 41 | assert_predicate str, :valid_encoding? 42 | end 43 | 44 | def test_dns_resolution_failure 45 | client = RedisClient.new(host: "does-not-exist.example.com") 46 | error = assert_raises RedisClient::ConnectionError do 47 | client.call("PING") 48 | end 49 | 50 | assert_match(%r{ \(redis://does-not-exist.example.com:.*\)$}, error.message) 51 | end 52 | 53 | def test_older_server 54 | fake_redis5_driver = Class.new(RedisClient::RubyConnection) do 55 | def call_pipelined(commands, *, &_) 56 | if commands.any? { |c| c == ["HELLO", "3"] } 57 | raise RedisClient::CommandError, "ERR unknown command `HELLO`, with args beginning with: `3`" 58 | else 59 | super 60 | end 61 | end 62 | end 63 | client = new_client(driver: fake_redis5_driver) 64 | 65 | error = assert_raises RedisClient::UnsupportedServer do 66 | client.call("PING") 67 | end 68 | assert_includes error.message, "redis-client requires Redis 6+ with HELLO command available" 69 | assert_includes error.message, "(redis://" 70 | end 71 | 72 | def test_redis_6_server_with_missing_hello_command 73 | fake_redis6_driver = Class.new(RedisClient::RubyConnection) do 74 | def call_pipelined(commands, *, &_) 75 | if commands.any? { |c| c == ["HELLO", "3"] } 76 | raise RedisClient::CommandError, "ERR unknown command 'HELLO'" 77 | else 78 | super 79 | end 80 | end 81 | end 82 | client = new_client(driver: fake_redis6_driver) 83 | 84 | error = assert_raises RedisClient::UnsupportedServer do 85 | client.call("PING") 86 | end 87 | assert_includes error.message, "redis-client requires Redis 6+ with HELLO command available" 88 | assert_includes error.message, "(redis://" 89 | end 90 | 91 | def test_handle_async_raise 92 | 10.times do |i| 93 | thread = Thread.new do 94 | loop do 95 | assert_equal "OK", @redis.call("SET", "key#{i}", i) 96 | end 97 | rescue RuntimeError 98 | end 99 | thread.join(rand(0.01..0.2)) 100 | thread.raise("Timeout Error") 101 | refute_predicate thread.join, :alive? 102 | assert_equal i.to_s, @redis.call("GET", "key#{i}") 103 | end 104 | end 105 | 106 | def test_handle_async_thread_kill 107 | 10.times do |i| 108 | thread = Thread.new do 109 | loop do 110 | assert_equal "OK", @redis.call("SET", "key#{i}", i) 111 | end 112 | rescue RuntimeError 113 | end 114 | thread.join(rand(0.01..0.2)) 115 | thread.kill 116 | refute_predicate thread.join, :alive? 117 | assert_equal i.to_s, @redis.call("GET", "key#{i}") 118 | end 119 | end 120 | 121 | def test_measure_round_trip_delay 122 | assert_equal "OK", @redis.call("SET", "foo", "bar") 123 | assert_instance_of Float, @redis.measure_round_trip_delay 124 | assert_equal "OK", @redis.call("SET", "foo", "bar") 125 | @redis.close 126 | assert_instance_of Float, @redis.measure_round_trip_delay 127 | end 128 | 129 | def test_server_url 130 | assert_equal "redis://#{Servers::HOST}:#{Servers::REDIS.port}", @redis.server_url 131 | end 132 | 133 | def test_timeout 134 | assert_equal ClientTestHelper::DEFAULT_TIMEOUT, @redis.timeout 135 | end 136 | 137 | def test_db 138 | assert_equal 0, @redis.db 139 | end 140 | 141 | def test_id 142 | assert_nil @redis.id 143 | end 144 | 145 | def test_host 146 | assert_equal Servers::HOST, @redis.host 147 | end 148 | 149 | def test_port 150 | assert_equal Servers::REDIS.port, @redis.port 151 | end 152 | 153 | def test_path 154 | client = new_client(**unix_config) 155 | assert_equal Servers::REDIS.socket_file.to_s, client.path 156 | end 157 | 158 | def test_username 159 | username = "test" 160 | client = new_client(username: username) 161 | assert_equal username, client.username 162 | end 163 | 164 | def test_password 165 | password = "test" 166 | client = new_client(password: password) 167 | assert_equal password, client.password 168 | end 169 | 170 | if GC.respond_to?(:auto_compact) 171 | def test_gc_safety 172 | gc_stress_was = GC.stress 173 | gc_auto_compact_was = GC.auto_compact 174 | 175 | GC.stress = true 176 | GC.auto_compact = true 177 | 178 | client = new_client 179 | client.call("PING") 180 | 181 | list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(&:to_s) 182 | client.call("LPUSH", "list", list) 183 | 184 | 3.times do 185 | assert_equal list, client.call("LRANGE", "list", 0, -1).reverse 186 | end 187 | ensure 188 | GC.stress = gc_stress_was 189 | GC.auto_compact = gc_auto_compact_was 190 | end 191 | end 192 | end 193 | -------------------------------------------------------------------------------- /test/sentinel/test_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../env" 4 | require_relative "../test_helper" 5 | 6 | Servers.build_redis 7 | Servers::SENTINEL_TESTS.prepare 8 | Servers.all = Servers::SENTINEL_TESTS 9 | -------------------------------------------------------------------------------- /test/support/client_test_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ClientTestHelper 4 | module FlakyDriver 5 | def self.included(base) 6 | base.extend(ClassMethods) 7 | end 8 | 9 | module ClassMethods 10 | attr_accessor :failures 11 | end 12 | 13 | def write(command) 14 | if self.class.failures.first == command.first 15 | self.class.failures.shift 16 | @fail_now = true 17 | end 18 | super 19 | end 20 | 21 | def read(*) 22 | @fail_now ||= false 23 | if @fail_now 24 | raise ::RedisClient::ConnectionError, "simulated failure" 25 | end 26 | 27 | super 28 | end 29 | 30 | def reconnect 31 | @fail_now = false 32 | super 33 | end 34 | 35 | def write_multi(commands) 36 | commands.each { |c| write(c) } 37 | nil 38 | end 39 | end 40 | 41 | def setup 42 | super 43 | 44 | @redis = new_client 45 | check_server 46 | end 47 | 48 | private 49 | 50 | def check_server 51 | retried = false 52 | begin 53 | @redis.call("flushdb", "async") 54 | rescue 55 | if retried 56 | raise 57 | else 58 | retried = true 59 | Servers.reset 60 | retry 61 | end 62 | end 63 | end 64 | 65 | def travel(seconds) 66 | original_now = RedisClient.singleton_class.instance_method(:now) 67 | original_now_ms = RedisClient.singleton_class.instance_method(:now_ms) 68 | begin 69 | RedisClient.singleton_class.alias_method(:now, :now) 70 | RedisClient.define_singleton_method(:now) do 71 | original_now.bind(RedisClient).call + seconds 72 | end 73 | 74 | RedisClient.singleton_class.alias_method(:now_ms, :now_ms) 75 | RedisClient.define_singleton_method(:now_ms) do 76 | original_now_ms.bind(RedisClient).call + (seconds * 1000.0) 77 | end 78 | 79 | yield 80 | ensure 81 | RedisClient.singleton_class.alias_method(:now, :now) 82 | RedisClient.define_singleton_method(:now, original_now) 83 | RedisClient.singleton_class.alias_method(:now_ms, :now_ms) 84 | RedisClient.define_singleton_method(:now_ms, original_now_ms) 85 | end 86 | end 87 | 88 | def simulate_network_errors(client, failures) 89 | client.close 90 | client.instance_variable_set(:@raw_connection, nil) 91 | 92 | original_config = client.config 93 | flaky_driver = Class.new(original_config.driver) 94 | flaky_driver.include(FlakyDriver) 95 | flaky_driver.failures = failures 96 | flaky_config = original_config.dup 97 | flaky_config.instance_variable_set(:@driver, flaky_driver) 98 | begin 99 | client.instance_variable_set(:@config, flaky_config) 100 | yield 101 | ensure 102 | client.instance_variable_set(:@config, original_config) 103 | client.close 104 | client.instance_variable_set(:@raw_connection, nil) 105 | end 106 | end 107 | 108 | module_function 109 | 110 | DEFAULT_TIMEOUT = 0.1 111 | 112 | def tcp_config 113 | { 114 | host: Servers::HOST, 115 | port: Servers::REDIS.port, 116 | timeout: DEFAULT_TIMEOUT, 117 | } 118 | end 119 | 120 | def ssl_config 121 | { 122 | host: Servers::HOST, 123 | port: Servers::REDIS.tls_port, 124 | timeout: DEFAULT_TIMEOUT, 125 | ssl: true, 126 | ssl_params: { 127 | cert: Servers::CERTS_PATH.join("client.crt").to_s, 128 | key: Servers::CERTS_PATH.join("client.key").to_s, 129 | ca_file: Servers::CERTS_PATH.join("ca.crt").to_s, 130 | }, 131 | } 132 | end 133 | 134 | def unix_config 135 | { 136 | path: Servers::REDIS.socket_file.to_s, 137 | timeout: DEFAULT_TIMEOUT, 138 | } 139 | end 140 | 141 | def new_client(**overrides) 142 | RedisClient.new(**tcp_config.merge(overrides)) 143 | end 144 | end 145 | -------------------------------------------------------------------------------- /test/support/driver.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | -------------------------------------------------------------------------------- /test/support/raise_warnings.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | $VERBOSE = true 4 | module RaiseWarnings 5 | def warn(message, *) 6 | return if message.include?('Ractor is experimental') 7 | 8 | super 9 | 10 | raise message 11 | end 12 | ruby2_keywords :warn if respond_to?(:ruby2_keywords, true) 13 | end 14 | Warning.singleton_class.prepend(RaiseWarnings) 15 | -------------------------------------------------------------------------------- /test/support/redis_builder.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require 'digest/sha1' 5 | require 'English' 6 | require 'fileutils' 7 | require 'net/http' 8 | 9 | class RedisBuilder 10 | TARBALL_CACHE_EXPIRATION = 60 * 10 11 | 12 | def initialize(redis_branch, tmp_dir) 13 | @redis_branch = redis_branch 14 | @tmp_dir = tmp_dir 15 | @build_dir = Servers::CACHE_DIR.join("redis-#{redis_branch}").to_s 16 | end 17 | 18 | def bin_path 19 | File.join(@build_dir, "src/redis-server") 20 | end 21 | 22 | def install 23 | download_tarball_if_needed 24 | if old_checkum != checksum 25 | build 26 | update_checksum 27 | end 28 | end 29 | 30 | private 31 | 32 | def download_tarball_if_needed 33 | return if File.exist?(tarball_path) && File.mtime(tarball_path) > Time.now - TARBALL_CACHE_EXPIRATION 34 | 35 | FileUtils.mkdir_p(@tmp_dir) 36 | download(tarball_url, tarball_path) 37 | end 38 | 39 | def download(url, path) 40 | response = Net::HTTP.get_response(URI(url)) 41 | case Integer(response.code) 42 | when 300..399 43 | download(response['Location'], path) 44 | when 200 45 | File.binwrite(tarball_path, response.body) 46 | else 47 | raise "Unexpected HTTP response #{response.code} #{url}" 48 | end 49 | end 50 | 51 | def tarball_path 52 | File.join(@tmp_dir, "redis-#{@redis_branch}.tar.gz") 53 | end 54 | 55 | def tarball_url 56 | "https://github.com/redis/redis/archive/#{@redis_branch}.tar.gz" 57 | end 58 | 59 | def build 60 | FileUtils.rm_rf(@build_dir) 61 | FileUtils.mkdir_p(@build_dir) 62 | command!('tar', 'xf', tarball_path, '-C', File.expand_path('../', @build_dir)) 63 | Dir.chdir(@build_dir) do 64 | command!('make', 'BUILD_TLS=yes') 65 | end 66 | end 67 | 68 | def update_checksum 69 | File.write(checksum_path, checksum) 70 | end 71 | 72 | def old_checkum 73 | File.read(checksum_path) 74 | rescue Errno::ENOENT 75 | nil 76 | end 77 | 78 | def checksum_path 79 | File.join(@build_dir, 'build.checksum') 80 | end 81 | 82 | def checksum 83 | @checksum ||= Digest::SHA1.file(tarball_path).hexdigest 84 | end 85 | 86 | def command!(*args) 87 | puts "$ #{args.join(' ')}" 88 | raise "Command failed with status #{$CHILD_STATUS.exitstatus}" unless system(*args) 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /test/support/server_manager.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "pathname" 4 | 5 | class ServerManager 6 | ROOT = Pathname.new(File.expand_path("../../", __dir__)) 7 | 8 | class << self 9 | def kill_all 10 | Dir[ROOT.join("tmp/**/*.pid").to_s].each do |pid_file| 11 | pid = begin 12 | Integer(File.read(pid_file)) 13 | rescue ArgumentError 14 | nil 15 | end 16 | 17 | if pid 18 | begin 19 | Process.kill(:KILL, pid) 20 | rescue Errno::ESRCH, Errno::ECHILD 21 | nil # It's fine 22 | end 23 | end 24 | 25 | File.unlink(pid_file) 26 | end 27 | end 28 | end 29 | 30 | @worker_index = nil 31 | singleton_class.attr_accessor :worker_index 32 | 33 | module NullIO 34 | extend self 35 | 36 | def puts(_str) 37 | nil 38 | end 39 | 40 | def print(_str) 41 | nil 42 | end 43 | end 44 | 45 | attr_reader :name, :host, :command 46 | attr_accessor :out 47 | 48 | def initialize(name, port:, command: nil, real_port: port, host: "127.0.0.1") 49 | @name = name 50 | @host = host 51 | @port = port 52 | @real_port = real_port 53 | @command = command 54 | @out = $stderr 55 | end 56 | 57 | def worker_index 58 | ServerManager.worker_index 59 | end 60 | 61 | def port_offset 62 | worker_index.to_i * 200 63 | end 64 | 65 | def port 66 | @port + port_offset 67 | end 68 | 69 | def real_port 70 | @real_port + port_offset 71 | end 72 | 73 | def spawn 74 | shutdown 75 | 76 | pid_file.parent.mkpath 77 | pid = Process.spawn(*command.map(&:to_s), out: log_file.to_s, err: log_file.to_s) 78 | pid_file.write(pid.to_s) 79 | @out.puts "started #{name}-#{worker_index.to_i} with pid=#{pid}" 80 | end 81 | 82 | def wait(timeout: 5) 83 | unless wait_until_ready(timeout: 1) 84 | @out.puts "Waiting for #{name}-#{worker_index.to_i} (port #{real_port})..." 85 | end 86 | 87 | if wait_until_ready(timeout: timeout - 1) 88 | @out.puts "#{name}-#{worker_index.to_i} ready." 89 | true 90 | else 91 | @out.puts "#{name}-#{worker_index.to_i} timedout." 92 | false 93 | end 94 | end 95 | 96 | def health_check 97 | TCPSocket.new(host, real_port) 98 | true 99 | rescue Errno::ECONNREFUSED 100 | false 101 | end 102 | 103 | def on_ready 104 | nil 105 | end 106 | 107 | def wait_until_ready(timeout: 5) 108 | (timeout * 100).times do 109 | if health_check 110 | on_ready 111 | return true 112 | else 113 | sleep 0.01 114 | end 115 | end 116 | false 117 | end 118 | 119 | def shutdown 120 | if alive? 121 | pid = self.pid 122 | Process.kill("INT", pid) 123 | Process.wait(pid) 124 | end 125 | true 126 | rescue Errno::ESRCH, Errno::ECHILD 127 | true 128 | end 129 | 130 | def pid 131 | Integer(pid_file.read) 132 | rescue Errno::ENOENT, ArgumentError 133 | nil 134 | end 135 | 136 | def alive? 137 | pid = self.pid 138 | return false unless pid 139 | 140 | pid && Process.kill(0, pid) 141 | true 142 | rescue Errno::ESRCH 143 | false 144 | end 145 | 146 | private 147 | 148 | def dir 149 | ROOT.join("tmp/#{name}-#{worker_index.to_i}").tap(&:mkpath) 150 | end 151 | 152 | def pid_file 153 | dir.join("#{name}.pid") 154 | end 155 | 156 | def log_file 157 | dir.join("#{name}.log") 158 | end 159 | end 160 | 161 | class ServerList 162 | def initialize(*servers) 163 | @servers = servers 164 | end 165 | 166 | def silence 167 | @servers.each { |s| s.out = ServerManager::NullIO } 168 | yield 169 | ensure 170 | @servers.each { |s| s.out = $stderr } 171 | end 172 | 173 | def prepare 174 | shutdown 175 | @servers.each(&:spawn) 176 | @servers.all?(&:wait) 177 | end 178 | 179 | def reset 180 | silence { prepare } 181 | end 182 | 183 | def shutdown 184 | @servers.reverse_each(&:shutdown) 185 | end 186 | end 187 | -------------------------------------------------------------------------------- /test/test_config.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Megatest.config do |c| 4 | c.global_setup do 5 | ServerManager.kill_all 6 | $stderr.puts "Running test suite with driver: #{RedisClient.default_driver}" 7 | end 8 | 9 | c.job_setup do |_, index| 10 | Servers::TESTS.shutdown 11 | Servers::SENTINEL_TESTS.shutdown 12 | 13 | ServerManager.worker_index = index 14 | Toxiproxy.host = "http://#{Servers::HOST}:#{Servers::TOXIPROXY.port}" 15 | unless Servers.all.prepare 16 | puts "worker #{index} failed setup" 17 | exit(1) 18 | end 19 | end 20 | 21 | c.job_teardown do 22 | unless ENV["REDIS_CLIENT_RESTART_SERVER"] == "0" 23 | Servers.all.shutdown 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "env" 4 | 5 | Servers.build_redis 6 | Servers::SENTINEL_TESTS.shutdown 7 | Servers.all = Servers::TESTS 8 | 9 | class RedisClientTestCase < Megatest::Test 10 | end 11 | --------------------------------------------------------------------------------