redis-client 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +190 -0
  3. data/CHANGELOG.md +3 -0
  4. data/Gemfile +23 -0
  5. data/Gemfile.lock +67 -0
  6. data/LICENSE.md +21 -0
  7. data/README.md +347 -0
  8. data/Rakefile +86 -0
  9. data/ext/redis_client/hiredis/extconf.rb +54 -0
  10. data/ext/redis_client/hiredis/hiredis_connection.c +696 -0
  11. data/ext/redis_client/hiredis/vendor/.gitignore +9 -0
  12. data/ext/redis_client/hiredis/vendor/.travis.yml +131 -0
  13. data/ext/redis_client/hiredis/vendor/CHANGELOG.md +364 -0
  14. data/ext/redis_client/hiredis/vendor/CMakeLists.txt +165 -0
  15. data/ext/redis_client/hiredis/vendor/COPYING +29 -0
  16. data/ext/redis_client/hiredis/vendor/Makefile +308 -0
  17. data/ext/redis_client/hiredis/vendor/README.md +664 -0
  18. data/ext/redis_client/hiredis/vendor/adapters/ae.h +130 -0
  19. data/ext/redis_client/hiredis/vendor/adapters/glib.h +156 -0
  20. data/ext/redis_client/hiredis/vendor/adapters/ivykis.h +84 -0
  21. data/ext/redis_client/hiredis/vendor/adapters/libev.h +179 -0
  22. data/ext/redis_client/hiredis/vendor/adapters/libevent.h +175 -0
  23. data/ext/redis_client/hiredis/vendor/adapters/libuv.h +117 -0
  24. data/ext/redis_client/hiredis/vendor/adapters/macosx.h +115 -0
  25. data/ext/redis_client/hiredis/vendor/adapters/qt.h +135 -0
  26. data/ext/redis_client/hiredis/vendor/alloc.c +86 -0
  27. data/ext/redis_client/hiredis/vendor/alloc.h +91 -0
  28. data/ext/redis_client/hiredis/vendor/appveyor.yml +24 -0
  29. data/ext/redis_client/hiredis/vendor/async.c +887 -0
  30. data/ext/redis_client/hiredis/vendor/async.h +147 -0
  31. data/ext/redis_client/hiredis/vendor/async_private.h +75 -0
  32. data/ext/redis_client/hiredis/vendor/dict.c +352 -0
  33. data/ext/redis_client/hiredis/vendor/dict.h +126 -0
  34. data/ext/redis_client/hiredis/vendor/fmacros.h +12 -0
  35. data/ext/redis_client/hiredis/vendor/hiredis-config.cmake.in +13 -0
  36. data/ext/redis_client/hiredis/vendor/hiredis.c +1174 -0
  37. data/ext/redis_client/hiredis/vendor/hiredis.h +336 -0
  38. data/ext/redis_client/hiredis/vendor/hiredis.pc.in +12 -0
  39. data/ext/redis_client/hiredis/vendor/hiredis_ssl-config.cmake.in +13 -0
  40. data/ext/redis_client/hiredis/vendor/hiredis_ssl.h +157 -0
  41. data/ext/redis_client/hiredis/vendor/hiredis_ssl.pc.in +12 -0
  42. data/ext/redis_client/hiredis/vendor/net.c +612 -0
  43. data/ext/redis_client/hiredis/vendor/net.h +56 -0
  44. data/ext/redis_client/hiredis/vendor/read.c +739 -0
  45. data/ext/redis_client/hiredis/vendor/read.h +129 -0
  46. data/ext/redis_client/hiredis/vendor/sds.c +1289 -0
  47. data/ext/redis_client/hiredis/vendor/sds.h +278 -0
  48. data/ext/redis_client/hiredis/vendor/sdsalloc.h +44 -0
  49. data/ext/redis_client/hiredis/vendor/sockcompat.c +248 -0
  50. data/ext/redis_client/hiredis/vendor/sockcompat.h +92 -0
  51. data/ext/redis_client/hiredis/vendor/ssl.c +544 -0
  52. data/ext/redis_client/hiredis/vendor/test.c +1401 -0
  53. data/ext/redis_client/hiredis/vendor/test.sh +78 -0
  54. data/ext/redis_client/hiredis/vendor/win32.h +56 -0
  55. data/lib/redis-client.rb +3 -0
  56. data/lib/redis_client/buffered_io.rb +149 -0
  57. data/lib/redis_client/config.rb +174 -0
  58. data/lib/redis_client/connection.rb +86 -0
  59. data/lib/redis_client/hiredis_connection.rb +78 -0
  60. data/lib/redis_client/pooled.rb +86 -0
  61. data/lib/redis_client/resp3.rb +225 -0
  62. data/lib/redis_client/sentinel_config.rb +134 -0
  63. data/lib/redis_client/version.rb +5 -0
  64. data/lib/redis_client.rb +438 -0
  65. data/redis-client.gemspec +34 -0
  66. metadata +125 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: d53aaf68e8bf2bc7160a8b708d86c40d13a229a2bfa88734ada0ae03839d6330
4
+ data.tar.gz: 7ebc1e2e488dbd0bc7895db4714237e893408f017222922cc94faec4b7646e7c
5
+ SHA512:
6
+ metadata.gz: 397746e753c9c44e6b6f7d046b9e8cb389c25c7a8d8dea40e7ab81c099f328d4f34c1d77d7c2c66c0ea7b12056f282a76c3dc5a700b1acee49b89cddc509070d
7
+ data.tar.gz: a2fcda7b8bbef1a0988fcd7434306da5769dbcf1ad91b5f790186646eedd1b21fa289b044bacd3f3b91486f8d06e73c51d608f92662bcb366e3d3e871a138b40
data/.rubocop.yml ADDED
@@ -0,0 +1,190 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.5
3
+ NewCops: enable
4
+ SuggestExtensions: false
5
+
6
+ Layout/LineLength:
7
+ Max: 120
8
+ Exclude:
9
+ - 'test/**/*'
10
+
11
+ Layout/CaseIndentation:
12
+ EnforcedStyle: end
13
+
14
+ Layout/LineEndStringConcatenationIndentation:
15
+ EnforcedStyle: indented
16
+
17
+ Layout/ArgumentAlignment:
18
+ EnforcedStyle: with_fixed_indentation
19
+
20
+ Lint/RescueException:
21
+ Enabled: false
22
+
23
+ Lint/SuppressedException:
24
+ Enabled: false
25
+
26
+ Lint/AssignmentInCondition:
27
+ Enabled: false
28
+
29
+ Lint/UnifiedInteger:
30
+ Enabled: false
31
+
32
+ Lint/UnderscorePrefixedVariableName:
33
+ Enabled: false
34
+
35
+ Lint/EmptyBlock:
36
+ Enabled: false
37
+
38
+ Lint/MissingSuper:
39
+ Enabled: false
40
+
41
+ Metrics/ClassLength:
42
+ Enabled: false
43
+
44
+ Metrics/CyclomaticComplexity:
45
+ Enabled: false
46
+
47
+ Metrics/AbcSize:
48
+ Enabled: false
49
+
50
+ Metrics/BlockLength:
51
+ Enabled: false
52
+
53
+ Metrics/MethodLength:
54
+ Enabled: false
55
+
56
+ Metrics/ModuleLength:
57
+ Enabled: false
58
+
59
+ Metrics/ParameterLists:
60
+ Enabled: false
61
+
62
+ Metrics/PerceivedComplexity:
63
+ Enabled: false
64
+
65
+ Style/Alias:
66
+ EnforcedStyle: prefer_alias_method
67
+
68
+ Style/PercentLiteralDelimiters:
69
+ Enabled: false
70
+
71
+ Style/ParallelAssignment:
72
+ Enabled: false
73
+
74
+ Style/NumericPredicate:
75
+ Enabled: false
76
+
77
+ Style/NumericLiterals:
78
+ Enabled: false
79
+
80
+ Style/IfUnlessModifier:
81
+ Enabled: false
82
+
83
+ Style/SignalException:
84
+ Exclude:
85
+ - 'lib/redis/connection/synchrony.rb'
86
+
87
+ Style/StringLiterals:
88
+ Enabled: false
89
+
90
+ Style/DoubleNegation:
91
+ Enabled: false
92
+
93
+ Style/MultipleComparison:
94
+ Enabled: false
95
+
96
+ Style/GuardClause:
97
+ Enabled: false
98
+
99
+ Style/Semicolon:
100
+ Enabled: false
101
+
102
+ Style/Documentation:
103
+ Enabled: false
104
+
105
+ Style/FormatStringToken:
106
+ Enabled: false
107
+
108
+ Style/FormatString:
109
+ Enabled: false
110
+
111
+ Style/RescueStandardError:
112
+ Enabled: false
113
+
114
+ Style/WordArray:
115
+ Enabled: false
116
+
117
+ Style/TrailingCommaInArrayLiteral:
118
+ EnforcedStyleForMultiline: consistent_comma
119
+
120
+ Style/TrailingCommaInHashLiteral:
121
+ EnforcedStyleForMultiline: consistent_comma
122
+
123
+ Style/TrailingCommaInArguments:
124
+ EnforcedStyleForMultiline: consistent_comma
125
+
126
+ Lint/NonLocalExitFromIterator:
127
+ Enabled: false
128
+
129
+ Layout/FirstArrayElementIndentation:
130
+ EnforcedStyle: consistent
131
+
132
+ Layout/FirstHashElementIndentation:
133
+ EnforcedStyle: consistent
134
+
135
+ Layout/EndAlignment:
136
+ EnforcedStyleAlignWith: variable
137
+
138
+ Layout/ElseAlignment:
139
+ Enabled: false
140
+
141
+ Layout/RescueEnsureAlignment:
142
+ Enabled: false
143
+
144
+ Naming/HeredocDelimiterNaming:
145
+ Enabled: false
146
+
147
+ Naming/FileName:
148
+ Enabled: false
149
+
150
+ Naming/RescuedExceptionsVariableName:
151
+ Enabled: false
152
+
153
+ Naming/AccessorMethodName:
154
+ Exclude:
155
+ - lib/redis/connection/ruby.rb
156
+
157
+ Naming/MethodParameterName:
158
+ Enabled: false
159
+
160
+ Metrics/BlockNesting:
161
+ Enabled: false
162
+
163
+ Style/HashTransformValues:
164
+ Enabled: false
165
+
166
+ Style/SymbolProc:
167
+ Exclude:
168
+ - 'test/**/*'
169
+
170
+ Style/SoleNestedConditional:
171
+ Enabled: false
172
+
173
+ Style/GlobalVars:
174
+ Exclude:
175
+ - 'ext/**/*'
176
+
177
+ Gemspec/RequireMFA:
178
+ Enabled: false
179
+
180
+ Style/StderrPuts:
181
+ Enabled: false
182
+
183
+ Style/ModuleFunction:
184
+ Enabled: false
185
+
186
+ Style/CombinableLoops:
187
+ Enabled: false
188
+
189
+ Style/DocumentDynamicEvalDefinition:
190
+ Enabled: false
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ # 0.1.0
2
+
3
+ - Initial Release
data/Gemfile ADDED
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in redis-client.gemspec
6
+ gemspec
7
+
8
+ gem "connection_pool"
9
+ gem "minitest"
10
+ gem "rake", "~> 13.0"
11
+ gem "rake-compiler"
12
+ gem "rubocop"
13
+ gem "rubocop-minitest"
14
+ gem "toxiproxy"
15
+
16
+ group :benchmark do
17
+ gem "benchmark-ips"
18
+ gem "hiredis"
19
+ gem "redis", "~> 4.6"
20
+ gem "stackprof", platform: :mri
21
+ end
22
+
23
+ gem "byebug", platform: :mri
data/Gemfile.lock ADDED
@@ -0,0 +1,67 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ redis-client (0.0.0)
5
+ connection_pool
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ ast (2.4.2)
11
+ benchmark-ips (2.10.0)
12
+ byebug (11.1.3)
13
+ connection_pool (2.2.5)
14
+ hiredis (0.6.3)
15
+ hiredis (0.6.3-java)
16
+ minitest (5.15.0)
17
+ parallel (1.21.0)
18
+ parser (3.1.1.0)
19
+ ast (~> 2.4.1)
20
+ rainbow (3.1.1)
21
+ rake (13.0.6)
22
+ rake-compiler (1.1.9)
23
+ rake
24
+ redis (4.6.0)
25
+ regexp_parser (2.2.1)
26
+ rexml (3.2.5)
27
+ rubocop (1.25.1)
28
+ parallel (~> 1.10)
29
+ parser (>= 3.1.0.0)
30
+ rainbow (>= 2.2.2, < 4.0)
31
+ regexp_parser (>= 1.8, < 3.0)
32
+ rexml
33
+ rubocop-ast (>= 1.15.1, < 2.0)
34
+ ruby-progressbar (~> 1.7)
35
+ unicode-display_width (>= 1.4.0, < 3.0)
36
+ rubocop-ast (1.16.0)
37
+ parser (>= 3.1.1.0)
38
+ rubocop-minitest (0.14.0)
39
+ rubocop (>= 0.90, < 2.0)
40
+ ruby-progressbar (1.11.0)
41
+ stackprof (0.2.19)
42
+ toxiproxy (2.0.0)
43
+ unicode-display_width (2.1.0)
44
+
45
+ PLATFORMS
46
+ ruby
47
+ universal-java-18
48
+ x86_64-darwin-20
49
+ x86_64-linux
50
+
51
+ DEPENDENCIES
52
+ benchmark-ips
53
+ byebug
54
+ connection_pool
55
+ hiredis
56
+ minitest
57
+ rake (~> 13.0)
58
+ rake-compiler
59
+ redis (~> 4.6)
60
+ redis-client!
61
+ rubocop
62
+ rubocop-minitest
63
+ stackprof
64
+ toxiproxy
65
+
66
+ BUNDLED WITH
67
+ 2.3.8
data/LICENSE.md ADDED
@@ -0,0 +1,21 @@
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.
data/README.md ADDED
@@ -0,0 +1,347 @@
1
+ # RedisClient
2
+
3
+ `redis-client` is a simple, low-level, client for Redis 6+.
4
+
5
+ Contrary to the `redis` gem, `redis-client` doesn't try to map all redis commands to Ruby constructs,
6
+ it merely is a thin wrapper on top of the RESP3 protocol.
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ ```ruby
13
+ gem 'redis-client'
14
+ ```
15
+
16
+ And then execute:
17
+
18
+ $ bundle install
19
+
20
+ Or install it yourself as:
21
+
22
+ $ gem install redis-client
23
+
24
+ ## Usage
25
+
26
+ To use `RedisClient` you first define a connection configuration, from which you can create a connection pool:
27
+
28
+ ```ruby
29
+ redis_config = RedisClient.config(host: "10.0.1.1", port: 6380, db: 15)
30
+ redis = redis_config.new_pool(timeout: 0.5, size: Integer(ENV.fetch("RAILS_MAX_THREADS", 5)))
31
+
32
+ redis.call("PING") # => "PONG"
33
+ ```
34
+
35
+ If you are issuing multiple commands in a raw, but can't pipeline them, it's best to use `#with` to avoid going through the connection checkout
36
+ several times:
37
+
38
+ ```ruby
39
+ redis.with do |r|
40
+ r.call("SET", "mykey", "hello world") # => "OK"
41
+ r.call("GET", "mykey") # => "hello world"
42
+ end
43
+ ```
44
+
45
+ If you are working in a single threaded environment, or wish to use your own connection pooling mechanism,
46
+ you can obtain a raw client with `#new_client`
47
+
48
+ ```ruby
49
+ redis_config = RedisClient.config(host: "10.0.1.1", port: 6380, db: 15)
50
+ redis = redis_config.new_client
51
+ redis.call("PING") # => "PONG"
52
+ ```
53
+
54
+ NOTE: Raw `RedisClient` instances must not be shared between threads. Make sure to read the section on [thread safety](#thread-safety).
55
+
56
+ For simple use cases where only a single connection is needed, you can use the `RedisClient.new` shortcut:
57
+
58
+ ```ruby
59
+ redis = RedisClient.new
60
+ redis.call("GET", "mykey")
61
+ ```
62
+
63
+ ### Configuration
64
+
65
+ - `url`: A Redis connection URL, e.g. `redis://example.com:6379/5`, a `rediss://` scheme enable SSL, and the path is interpreted as a database number.
66
+ Note tht all other configurtions take precedence, e.g. `RedisClient.config(url: "redis://localhost:3000" port: 6380)` will connect on port `6380`.
67
+ - `host`: The server hostname or IP address. Defaults to `"localhost"`.
68
+ - `port`: The server port. Defaults to `6379`.
69
+ - `path`: The path to a UNIX socket, if set `url`, `host` and `port` are ignored.
70
+ - `ssl`: Wether to connect using SSL or not.
71
+ - `ssl_params`: A configuration Hash passed to [`OpenSSL::SSL::SSLContext#set_params`](https://www.rubydoc.info/stdlib/openssl/OpenSSL%2FSSL%2FSSLContext:set_params), notable options include:
72
+ - `cert`: The path to the client certificate (e.g. `client.crt`).
73
+ - `key`: The path to the client key (e.g. `client.key`).
74
+ - `ca_file`: The certificate authority to use, useful for self signed certificates (e.g. `ca.crt`),
75
+ - `db`: The database to select after connecting, defaults to `0`.
76
+ - `id` ID for the client connection, assigns name to current connection by sending `CLIENT SETNAME`.
77
+ - `username` Username to authenticate against server, defaults to `"default"`.
78
+ - `password` Password to authenticate against server.
79
+ - `timeout`: The general timeout in seconds, default to `1.0`.
80
+ - `connect_timeout`: The connection timeout, takes precedence over the general timeout when connecting to the server.
81
+ - `read_timeout`: The read timeout, takes precedence over the general timeout when reading responses from the server.
82
+ - `write_timeout`: The write timeout, takes precedence over the general timeout when sending commands to the server.
83
+ - `reconnect_attempts`: Specify how many times the client should retry to send queries. Defaults to `0`. Makes sure to read the [reconnection section](#reconnection) before enabling it.
84
+
85
+ ### Sentinel support
86
+
87
+ The client is able to perform automatic failover by using [Redis Sentinel](https://redis.io/docs/manual/sentinel/).
88
+
89
+ To connect using Sentinel, use:
90
+
91
+ ```ruby
92
+ redis_config = RedisClient.sentinel(
93
+ name: "mymaster",
94
+ sentinels: [
95
+ { host: "127.0.0.1", port: 26380 },
96
+ { host: "127.0.0.1", port: 26381 },
97
+ ],
98
+ role: :master,
99
+ )
100
+ ```
101
+
102
+ * The name identifies a group of Redis instances composed of a master and one or more replicas (`mymaster` in the example).
103
+
104
+ * It is possible to optionally provide a role. The allowed roles are `:master`
105
+ and `:replica`. When the role is `:replica`, the client will try to connect to a
106
+ random replica of the specified master. If a role is not specified, the client
107
+ will connect to the master.
108
+
109
+ * When using the Sentinel support you need to specify a list of sentinels to
110
+ connect to. The list does not need to enumerate all your Sentinel instances,
111
+ but a few so that if one is down the client will try the next one. The client
112
+ is able to remember the last Sentinel that was able to reply correctly and will
113
+ use it for the next requests.
114
+
115
+ ### Type support
116
+
117
+ Only a select few Ruby types are supported as arguments beside strings.
118
+
119
+ Integer and Float are supported:
120
+
121
+ ```ruby
122
+ redis.call("SET", "mykey", 42)
123
+ redis.call("SET", "mykey", 1.23)
124
+ ```
125
+
126
+ is equivalent to:
127
+
128
+ ```ruby
129
+ redis.call("SET", "mykey", 42.to_s)
130
+ redis.call("SET", "mykey", 1.23.to_s)
131
+ ```
132
+
133
+ Arrays are flattened as arguments:
134
+
135
+ ```ruby
136
+ redis.call("LPUSH", "list", [1, 2, 3], 4)
137
+ ```
138
+
139
+ is equivalent to:
140
+
141
+ ```ruby
142
+ redis.call("LPUSH", "list", "1", "2", "3", "4")
143
+ ```
144
+
145
+ Hashes are flatenned as well:
146
+
147
+ ```ruby
148
+ redis.call("HMSET", "hash", foo: 1, bar: 2)
149
+ redis.call("SET", "key", "value", ex: 5)
150
+ ```
151
+
152
+ is equivalent to:
153
+
154
+ ```ruby
155
+ redis.call("HMSET", "hash", "foo", "1", "bar", "2")
156
+ redis.call("SET", "key", "value", "ex", "5")
157
+ ```
158
+
159
+ Any other type requires the caller to explictly cast the argument as a string.
160
+
161
+ ### Blocking commands
162
+
163
+ For blocking commands such as `BRPOP`, a custom timeout duration can be passed as first argument of the `#blocking_call` method:
164
+
165
+ ```
166
+ redis.blocking_call(timeout, "BRPOP", "key", 0)
167
+ ```
168
+
169
+ If `timeout` is reached, `#blocking_call` returns `nil`.
170
+
171
+ `timeout` is expressed in seconds, you can pass `false` or `0` to mean no timeout.
172
+
173
+ ### Scan commands
174
+
175
+ For easier use of the [`SCAN` family of commands](https://redis.io/commands/scan), `#scan`, `#sscan`, `#hscan` and `#zscan` methods are provided
176
+
177
+ ```ruby
178
+ redis.scan("MATCH", "pattern:*") do |key|
179
+ ...
180
+ end
181
+ ```
182
+
183
+ ```ruby
184
+ redis.sscan("myset", "MATCH", "pattern:*") do |key|
185
+ ...
186
+ end
187
+ ```
188
+
189
+ For `HSCAN` and `ZSCAN`, pairs are yielded
190
+
191
+ ```ruby
192
+ redis.hscan("myhash", "MATCH", "pattern:*") do |key, value|
193
+ ...
194
+ end
195
+ ```
196
+
197
+ ```ruby
198
+ redis.zscan("myzset") do |element, score|
199
+ ...
200
+ end
201
+ ```
202
+
203
+ In all cases the `cursor` parameter must be omitted and starts at `0`.
204
+
205
+ ### Pipelining
206
+
207
+ When multiple commands are executed sequentially, but are not dependent, the calls can be pipelined.
208
+ This means that the client doesn't wait for reply of the first command before sending the next command.
209
+ The advantage is that multiple commands are sent at once, resulting in faster overall execution.
210
+
211
+ The client can be instructed to pipeline commands by using the `#pipelined method`.
212
+ After the block is executed, the client sends all commands to Redis and gathers their replies.
213
+ These replies are returned by the `#pipelined` method.
214
+
215
+ ```ruby
216
+ redis.pipelined do |pipeline|
217
+ pipeline.call("SET", "foo", "bar") # => nil
218
+ pipeline.call("INCR", "baz") # => nil
219
+ end
220
+ # => ["OK", 1]
221
+ ```
222
+
223
+ ### Transactions
224
+
225
+ You can use [`MULTI/EXEC` to run a number of commands in an atomic fashion](https://redis.io/topics/transactions).
226
+ This is similar to executing a pipeline, but the commands are
227
+ preceded by a call to `MULTI`, and followed by a call to `EXEC`. Like
228
+ the regular pipeline, the replies to the commands are returned by the
229
+ `#multi` method.
230
+
231
+ ```ruby
232
+ redis.multi do |transaction|
233
+ transaction.call("SET", "foo", "bar") # => nil
234
+ transaction.call("INCR", "baz") # => nil
235
+ end
236
+ # => ["OK", 1]
237
+ ```
238
+
239
+ For optimistic locking, the watched keys can be passed to the `#multi` method:
240
+
241
+ ```ruby
242
+ redis.multi(watch: ["title"]) do |transaction|
243
+ title = redis.call("GET", "title")
244
+ transaction.call("SET", "title", title.upcase)
245
+ end
246
+ # => ["OK"] / nil
247
+ ```
248
+
249
+ If the transaction wasn't successful, `#multi` will return `nil`.
250
+
251
+ Note that transactions using optimistic locking aren't automatically retried uppon connection errors.
252
+
253
+ ### Publish / Subscribe
254
+
255
+ Pub/Sub related commands must be called on a dedicated `PubSub` object:
256
+
257
+ ```ruby
258
+ redis = RedisClient.new
259
+ pubsub = redis.pubsub
260
+ pubsub.call("SUBSCRIBE", "channel-1", "channel-2")
261
+
262
+ loop do
263
+ if message = pubsub.next_event(timeout)
264
+ message # => ["subscribe", "channel-1", 1]
265
+ else
266
+ # no new message was received in the allocated timeout
267
+ end
268
+ end
269
+ ```
270
+
271
+ ## Production
272
+
273
+ ### Timeouts
274
+
275
+ The client allows you to configure connect, read, and write timeouts.
276
+ Passing a single `timeout` option will set all three values:
277
+
278
+ ```ruby
279
+ RedisClient.config(timeout: 1).new
280
+ ```
281
+
282
+ But you can use specific values for each of them:
283
+
284
+ ```ruby
285
+ RedisClient.config(
286
+ connect_timeout: 0.2,
287
+ read_timeout: 1.0,
288
+ write_timeout: 0.5,
289
+ ).new
290
+ ```
291
+
292
+ All timeout values are specified in seconds.
293
+
294
+ ### Reconnection
295
+
296
+ `redis-client` support automatic reconnection after network errors via the `reconnect_attempts:` configuration option.
297
+
298
+ It can be set as a number of retries:
299
+
300
+ ```ruby
301
+ redis_config = RedisClient.config(reconnect_attempts: 1)
302
+ ```
303
+
304
+ Or as a list of sleep durations for implementing exponential backoff:
305
+
306
+ ```ruby
307
+ redis_config = RedisClient.config(reconnect_attempts: [0, 0.05, 0.1])
308
+ ```
309
+
310
+ **Important Note**: Retrying may cause commands to be issued more than once to the server, so in the case of
311
+ non-idempotent commands such as `LPUSH` or `INCR`, it may cause consistency issues.
312
+
313
+ To selectively disable automatic retries, you can use the `#call_once` method:
314
+
315
+ ```ruby
316
+ redis_config = RedisClient.config(reconnect_attempts: [0, 0.05, 0.1])
317
+ redis = redis_config.new_client
318
+ redis.call("GET", "counter") # Will be retried up to 3 times.
319
+ redis.call_once("INCR", "counter") # Won't be retried.
320
+ ```
321
+
322
+ ## Notable differences with the `redis` gem
323
+
324
+ ### Thread Safety
325
+
326
+ Contrary to the `redis` gem, `redis-client` doesn't protect against concurrent access.
327
+ To use `redis-client` in concurrent environments, you MUST use a connection pool, or
328
+ have one client per Thread or Fiber.
329
+
330
+ ### Fork Safety
331
+
332
+ `redis-client` doesn't try to detect forked processes. You MUST disconnect all clients before forking your process.
333
+
334
+ ```ruby
335
+ redis.close
336
+ Process.fork ...
337
+ ```
338
+
339
+ ## Development
340
+
341
+ After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
342
+
343
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
344
+
345
+ ## Contributing
346
+
347
+ Bug reports and pull requests are welcome on GitHub at https://github.com/redis-rb/redis-client.