├── .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 |
--------------------------------------------------------------------------------