redis-client 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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.