redis-client 0.5.0 → 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 770636c5814252674d680c45f75af4ea291b698c18382b49d93bec4e3d7ab45b
4
- data.tar.gz: bdef4f0f80574a9aaf29613ec23d31d7a0c3d916ca3945ac412b36b55eecd73a
3
+ metadata.gz: c3b26d6232b408310d32fda820a8d89263dccd3d3af1ab9518f2f398fdcb94c5
4
+ data.tar.gz: af204813327a82cf9a80dd22ee7b5528f67df200765561bcb0e399bdd5b0e829
5
5
  SHA512:
6
- metadata.gz: 3367281b907b6e38e73ba1e9a74f63404e40f422b319c551645fec1efa694e87c09cd96c67d308c362407c84be59eb62435b9951740cd515727a554050ae3137
7
- data.tar.gz: 9dc6f5e6638405c8841b267d0c9cf882d0721cbd04655984af71bddb66cc7e2f4698e7199e752582064e4db3ca8e3f45a77b56241df309ae0c2e231ebff1cf2b
6
+ metadata.gz: 3754bcdfb70484753fb67e69cbc0b071eb7dacbef67a48edb59dd55a8c79094a84b3ca96caa7f0f8ac0969ce24433688d81eba2bca3b27be79bb60600a1ff1a8
7
+ data.tar.gz: 998dd6e7b0cf8d92880b473aee858f19e3bff1da2ce670f4c4fae3e43e2f534a8cf2cdd1aca064036bc7c20cea660ad69d3611d80c15f5679ca78bef1747e637
data/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # Unreleased
2
2
 
3
+ # 0.6.1
4
+
5
+ - Fix `REDIS_REPLY_SET` parsing in `hiredis`.
6
+
7
+ # 0.6.0
8
+
9
+ - Added `protocol: 2` options to talk with Redis 5 and older servers.
10
+ - Added `_v` versions of `call` methods to make it easier to pass commands as arrays without splating.
11
+ - Fix calling `blocking_call` with a block in a pipeline.
12
+ - `blocking_call` now raise `ReadTimeoutError` if the command didn't complete in time.
13
+ - Fix `blocking_call` to not respect `retry_attempts` on timeout.
14
+ - Stop parsing RESP3 sets as Ruby Set instances.
15
+ - Fix `SystemStackError` when parsing very large hashes. Fix: #30
16
+ - `hiredis` now more properly release the GVL when doing IOs.
17
+
18
+ # 0.5.1
19
+
20
+ - Fix a regression in the `scan` familly of methods, they would raise with `ArgumentError: can't issue an empty redis command`. Fix: #24
21
+
22
+ # 0.5.0
23
+
3
24
  - Fix handling of connection URLs with empty passwords (`redis://:pass@example.com`).
4
25
  - Handle URLs with IPv6 hosts.
5
26
  - Add `RedisClient::Config#server_url` as a quick way to identify which server the client is pointing to.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- redis-client (0.5.0)
4
+ redis-client (0.6.1)
5
5
  connection_pool
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -81,6 +81,7 @@ redis.call("GET", "mykey")
81
81
  - `read_timeout`: The read timeout, takes precedence over the general timeout when reading responses from the server.
82
82
  - `write_timeout`: The write timeout, takes precedence over the general timeout when sending commands to the server.
83
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
+ - `protocol:` The version of the RESP protocol to use. Default to `3`.
84
85
 
85
86
  ### Sentinel support
86
87
 
@@ -207,6 +208,16 @@ redis.call("EXISTS", "counter") # => 1
207
208
  redis.call("EXISTS", "counter") { |c| c > 0 } # => true
208
209
  ```
209
210
 
211
+ ### `*_v` methods
212
+
213
+ In some it's more convenient to pass commands as arrays, for that `_v` versions of `call` methods are available.
214
+
215
+ ```ruby
216
+ redis.call_v(["MGET"] + keys)
217
+ redis.blocking_call_v(1, ["MGET"] + keys)
218
+ redis.call_once_v(1, ["MGET"] + keys)
219
+ ```
220
+
210
221
  ### Blocking commands
211
222
 
212
223
  For blocking commands such as `BRPOP`, a custom timeout duration can be passed as first argument of the `#blocking_call` method:
@@ -215,7 +226,7 @@ For blocking commands such as `BRPOP`, a custom timeout duration can be passed a
215
226
  redis.blocking_call(timeout, "BRPOP", "key", 0)
216
227
  ```
217
228
 
218
- If `timeout` is reached, `#blocking_call` returns `nil`.
229
+ If `timeout` is reached, `#blocking_call` raises `RedisClient::ReadTimeoutError` and doesn't retry regardless of the `reconnect_attempts` configuration.
219
230
 
220
231
  `timeout` is expressed in seconds, you can pass `false` or `0` to mean no timeout.
221
232
 
data/Rakefile CHANGED
@@ -107,8 +107,8 @@ end
107
107
 
108
108
  if hiredis_supported
109
109
  task default: %i[compile test rubocop]
110
- task ci: %i[compile test]
110
+ task ci: %i[compile test:ruby test:hiredis]
111
111
  else
112
112
  task default: %i[test rubocop]
113
- task ci: %i[test]
113
+ task ci: %i[test:ruby]
114
114
  end
@@ -5,19 +5,17 @@ class RedisClient
5
5
  extend self
6
6
 
7
7
  if Symbol.method_defined?(:name)
8
- def generate!(args, kwargs)
8
+ def generate(args, kwargs = nil)
9
9
  command = args.flat_map do |element|
10
10
  case element
11
11
  when Hash
12
12
  element.flatten
13
- when Set
14
- element.to_a
15
13
  else
16
14
  element
17
15
  end
18
16
  end
19
17
 
20
- kwargs.each do |key, value|
18
+ kwargs&.each do |key, value|
21
19
  if value
22
20
  if value == true
23
21
  command << key.name
@@ -47,19 +45,17 @@ class RedisClient
47
45
  command
48
46
  end
49
47
  else
50
- def generate!(args, kwargs)
48
+ def generate(args, kwargs = nil)
51
49
  command = args.flat_map do |element|
52
50
  case element
53
51
  when Hash
54
52
  element.flatten
55
- when Set
56
- element.to_a
57
53
  else
58
54
  element
59
55
  end
60
56
  end
61
57
 
62
- kwargs.each do |key, value|
58
+ kwargs&.each do |key, value|
63
59
  if value
64
60
  if value == true
65
61
  command << key.to_s
@@ -12,8 +12,8 @@ class RedisClient
12
12
  DEFAULT_DB = 0
13
13
 
14
14
  module Common
15
- attr_reader :db, :username, :password, :id, :ssl, :ssl_params, :command_builder,
16
- :connect_timeout, :read_timeout, :write_timeout, :driver, :connection_prelude
15
+ attr_reader :db, :password, :id, :ssl, :ssl_params, :command_builder,
16
+ :connect_timeout, :read_timeout, :write_timeout, :driver, :connection_prelude, :protocol
17
17
 
18
18
  alias_method :ssl?, :ssl
19
19
 
@@ -29,10 +29,12 @@ class RedisClient
29
29
  ssl: nil,
30
30
  ssl_params: nil,
31
31
  driver: nil,
32
+ protocol: 3,
33
+ client_implementation: RedisClient,
32
34
  command_builder: CommandBuilder,
33
35
  reconnect_attempts: false
34
36
  )
35
- @username = username || DEFAULT_USERNAME
37
+ @username = username
36
38
  @password = password
37
39
  @db = db || DEFAULT_DB
38
40
  @id = id
@@ -45,14 +47,23 @@ class RedisClient
45
47
 
46
48
  @driver = driver ? RedisClient.driver(driver) : RedisClient.default_driver
47
49
 
50
+ @client_implementation = client_implementation
51
+ @protocol = protocol
52
+ unless protocol == 2 || protocol == 3
53
+ raise ArgumentError, "Unknown protocol version #{protocol.inspect}, expected 2 or 3"
54
+ end
55
+
48
56
  @command_builder = command_builder
49
57
 
50
58
  reconnect_attempts = Array.new(reconnect_attempts, 0).freeze if reconnect_attempts.is_a?(Integer)
51
59
  @reconnect_attempts = reconnect_attempts
52
-
53
60
  @connection_prelude = build_connection_prelude
54
61
  end
55
62
 
63
+ def username
64
+ @username || DEFAULT_USERNAME
65
+ end
66
+
56
67
  def sentinel?
57
68
  false
58
69
  end
@@ -63,7 +74,7 @@ class RedisClient
63
74
  end
64
75
 
65
76
  def new_client(**kwargs)
66
- RedisClient.new(self, **kwargs)
77
+ @client_implementation.new(self, **kwargs)
67
78
  end
68
79
 
69
80
  def retry_connecting?(attempt, _error)
@@ -79,7 +90,7 @@ class RedisClient
79
90
  end
80
91
 
81
92
  def ssl_context
82
- @ssl_context ||= @driver.ssl_context(@ssl_params)
93
+ @ssl_context ||= @driver.ssl_context(@ssl_params || {})
83
94
  end
84
95
 
85
96
  def server_url
@@ -94,10 +105,18 @@ class RedisClient
94
105
 
95
106
  def build_connection_prelude
96
107
  prelude = []
97
- prelude << if @password
98
- ["HELLO", "3", "AUTH", @username, @password]
99
- else
100
- ["HELLO", "3"]
108
+ if protocol == 3
109
+ prelude << if @password
110
+ ["HELLO", "3", "AUTH", @username || DEFAULT_USERNAME, @password]
111
+ else
112
+ ["HELLO", "3"]
113
+ end
114
+ elsif @password
115
+ prelude << if @username && !@username.empty?
116
+ ["AUTH", @username, @password]
117
+ else
118
+ ["AUTH", @password]
119
+ end
101
120
  end
102
121
 
103
122
  if @db && @db != 0
@@ -136,7 +155,7 @@ class RedisClient
136
155
  super(**kwargs)
137
156
 
138
157
  @host = host || uri&.host&.sub(/\A\[(.*)\]\z/, '\1') || DEFAULT_HOST
139
- @port = port || uri&.port || DEFAULT_PORT
158
+ @port = Integer(port || uri&.port || DEFAULT_PORT)
140
159
  @path = path
141
160
  end
142
161
  end
@@ -20,7 +20,7 @@ class RedisClient
20
20
  @client = client
21
21
  end
22
22
 
23
- %i(call call_once blocking_call).each do |method|
23
+ %i(call call_v call_once call_once_v blocking_call blocking_call_v).each do |method|
24
24
  class_eval(<<~RUBY, __FILE__, __LINE__ + 1)
25
25
  def #{method}(*args, &block)
26
26
  @client.#{method}(*args, &block)
@@ -64,7 +64,7 @@ class RedisClient
64
64
  RUBY
65
65
  end
66
66
 
67
- %i(id config size connect_timeout read_timeout write_timeout).each do |reader|
67
+ %i(id config size connect_timeout read_timeout write_timeout pubsub).each do |reader|
68
68
  class_eval(<<~RUBY, __FILE__, __LINE__ + 1)
69
69
  def #{reader}
70
70
  @client.#{reader}
@@ -49,7 +49,7 @@ class RedisClient
49
49
  pool.size
50
50
  end
51
51
 
52
- methods = %w(pipelined multi pubsub call call_once blocking_call)
52
+ methods = %w(pipelined multi pubsub call call_v call_once call_once_v blocking_call blocking_call_v)
53
53
  iterable_methods = %w(scan sscan hscan zscan)
54
54
  begin
55
55
  methods.each do |method|
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "set"
4
-
5
3
  class RedisClient
6
4
  module RESP3
7
5
  module_function
@@ -41,8 +39,6 @@ class RedisClient
41
39
  case element
42
40
  when Hash
43
41
  element.flatten
44
- when Set
45
- element.to_a
46
42
  else
47
43
  element
48
44
  end
@@ -55,7 +51,7 @@ class RedisClient
55
51
  end
56
52
 
57
53
  def new_buffer
58
- String.new(encoding: Encoding::BINARY, capacity: 128)
54
+ String.new(encoding: Encoding::BINARY, capacity: 127)
59
55
  end
60
56
 
61
57
  def dump_any(object, buffer)
@@ -144,11 +140,15 @@ class RedisClient
144
140
  end
145
141
 
146
142
  def parse_set(io)
147
- parse_sequence(io, parse_integer(io)).to_set
143
+ parse_sequence(io, parse_integer(io))
148
144
  end
149
145
 
150
146
  def parse_map(io)
151
- Hash[*parse_sequence(io, parse_integer(io) * 2)]
147
+ hash = {}
148
+ parse_integer(io).times do
149
+ hash[parse(io)] = parse(io)
150
+ end
151
+ hash
152
152
  end
153
153
 
154
154
  def parse_push(io)
@@ -156,6 +156,8 @@ class RedisClient
156
156
  end
157
157
 
158
158
  def parse_sequence(io, size)
159
+ return if size < 0 # RESP2 nil
160
+
159
161
  array = Array.new(size)
160
162
  size.times do |index|
161
163
  array[index] = parse(io)
@@ -185,6 +187,8 @@ class RedisClient
185
187
 
186
188
  def parse_blob(io)
187
189
  bytesize = parse_integer(io)
190
+ return if bytesize < 0 # RESP2 nil type
191
+
188
192
  str = io.read_chomp(bytesize)
189
193
  str.force_encoding(Encoding.default_external)
190
194
  str.force_encoding(Encoding::BINARY) unless str.valid_encoding?
@@ -60,9 +60,9 @@ class RedisClient
60
60
  loop do
61
61
  case status = socket.connect_nonblock(exception: false)
62
62
  when :wait_readable
63
- socket.to_io.wait_readable(connect_timeout) or raise ReadTimeoutError
63
+ socket.to_io.wait_readable(connect_timeout) or raise CannotConnectError
64
64
  when :wait_writable
65
- socket.to_io.wait_writable(connect_timeout) or raise WriteTimeoutError
65
+ socket.to_io.wait_writable(connect_timeout) or raise CannotConnectError
66
66
  when socket
67
67
  break
68
68
  else
@@ -76,10 +76,8 @@ class RedisClient
76
76
  read_timeout: read_timeout,
77
77
  write_timeout: write_timeout,
78
78
  )
79
- rescue Errno::ETIMEDOUT => error
80
- raise ConnectTimeoutError, error.message
81
79
  rescue SystemCallError, OpenSSL::SSL::SSLError, SocketError => error
82
- raise ConnectionError, error.message
80
+ raise CannotConnectError, error.message, error.backtrace
83
81
  end
84
82
 
85
83
  def connected?
@@ -125,6 +123,8 @@ class RedisClient
125
123
  else
126
124
  @io.with_timeout(timeout) { RESP3.load(@io) }
127
125
  end
126
+ rescue RedisClient::RESP3::UnknownType => error
127
+ raise RedisClient::ProtocolError, error.message
128
128
  rescue SystemCallError, IOError, OpenSSL::SSL::SSLError => error
129
129
  raise ConnectionError, error.message
130
130
  end
@@ -12,8 +12,21 @@ class RedisClient
12
12
  raise ArgumentError, "Expected role to be either :master or :replica, got: #{role.inspect}"
13
13
  end
14
14
 
15
+ @to_list_of_hash = @to_hash = nil
16
+ extra_config = {}
17
+ if client_config[:protocol] == 2
18
+ extra_config[:protocol] = client_config[:protocol]
19
+ @to_list_of_hash = lambda do |may_be_a_list|
20
+ if may_be_a_list.is_a?(Array)
21
+ may_be_a_list.map { |l| l.each_slice(2).to_h }
22
+ else
23
+ may_be_a_list
24
+ end
25
+ end
26
+ end
27
+
15
28
  @name = name
16
- @sentinel_configs = sentinels.map { |s| Config.new(**s) }
29
+ @sentinel_configs = sentinels.map { |s| Config.new(**extra_config, **s) }
17
30
  @sentinels = {}.compare_by_identity
18
31
  @role = role
19
32
  @mutex = Mutex.new
@@ -90,7 +103,10 @@ class RedisClient
90
103
  return Config.new(host: host, port: Integer(port), **@client_config)
91
104
  end
92
105
  end
93
- raise ConnectionError, "Couldn't locate a master for role: #{@name}"
106
+ rescue ConnectionError
107
+ raise ConnectionError, "No sentinels available"
108
+ else
109
+ raise ConnectionError, "Couldn't locate a replica for role: #{@name}"
94
110
  end
95
111
 
96
112
  def sentinel_client(sentinel_config)
@@ -99,13 +115,16 @@ class RedisClient
99
115
 
100
116
  def resolve_replica
101
117
  each_sentinel do |sentinel_client|
102
- replicas = sentinel_client.call("SENTINEL", "replicas", @name)
118
+ replicas = sentinel_client.call("SENTINEL", "replicas", @name, &@to_list_of_hash)
103
119
  next if replicas.empty?
104
120
 
105
121
  replica = replicas.reject { |r| r["flags"].to_s.split(",").include?("disconnected") }.sample
106
122
  replica ||= replicas.sample
107
123
  return Config.new(host: replica["ip"], port: Integer(replica["port"]), **@client_config)
108
124
  end
125
+ rescue ConnectionError
126
+ raise ConnectionError, "No sentinels available"
127
+ else
109
128
  raise ConnectionError, "Couldn't locate a replica for role: #{@name}"
110
129
  end
111
130
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class RedisClient
4
- VERSION = "0.5.0"
4
+ VERSION = "0.6.1"
5
5
  end
data/lib/redis_client.rb CHANGED
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "set"
4
-
5
3
  require "redis_client/version"
6
4
  require "redis_client/command_builder"
7
5
  require "redis_client/config"
@@ -78,17 +76,18 @@ class RedisClient
78
76
 
79
77
  Error = Class.new(StandardError)
80
78
 
79
+ ProtocolError = Class.new(Error)
81
80
  UnsupportedServer = Class.new(Error)
82
81
 
83
82
  ConnectionError = Class.new(Error)
83
+ CannotConnectError = Class.new(ConnectionError)
84
84
 
85
85
  FailoverError = Class.new(ConnectionError)
86
86
 
87
87
  TimeoutError = Class.new(ConnectionError)
88
88
  ReadTimeoutError = Class.new(TimeoutError)
89
89
  WriteTimeoutError = Class.new(TimeoutError)
90
- ConnectTimeoutError = Class.new(TimeoutError)
91
- CheckoutTimeoutError = Class.new(ConnectTimeoutError)
90
+ CheckoutTimeoutError = Class.new(TimeoutError)
92
91
 
93
92
  class CommandError < Error
94
93
  attr_reader :command
@@ -120,11 +119,11 @@ class RedisClient
120
119
 
121
120
  class << self
122
121
  def config(**kwargs)
123
- Config.new(**kwargs)
122
+ Config.new(client_implementation: self, **kwargs)
124
123
  end
125
124
 
126
125
  def sentinel(**kwargs)
127
- SentinelConfig.new(**kwargs)
126
+ SentinelConfig.new(client_implementation: self, **kwargs)
128
127
  end
129
128
 
130
129
  def new(arg = nil, **kwargs)
@@ -184,7 +183,22 @@ class RedisClient
184
183
  end
185
184
 
186
185
  def call(*command, **kwargs)
187
- command = @command_builder.generate!(command, kwargs)
186
+ command = @command_builder.generate(command, kwargs)
187
+ result = ensure_connected do |connection|
188
+ Middlewares.call(command, config) do
189
+ connection.call(command, nil)
190
+ end
191
+ end
192
+
193
+ if block_given?
194
+ yield result
195
+ else
196
+ result
197
+ end
198
+ end
199
+
200
+ def call_v(command)
201
+ command = @command_builder.generate(command)
188
202
  result = ensure_connected do |connection|
189
203
  Middlewares.call(command, config) do
190
204
  connection.call(command, nil)
@@ -199,7 +213,22 @@ class RedisClient
199
213
  end
200
214
 
201
215
  def call_once(*command, **kwargs)
202
- command = @command_builder.generate!(command, kwargs)
216
+ command = @command_builder.generate(command, kwargs)
217
+ result = ensure_connected(retryable: false) do |connection|
218
+ Middlewares.call(command, config) do
219
+ connection.call(command, nil)
220
+ end
221
+ end
222
+
223
+ if block_given?
224
+ yield result
225
+ else
226
+ result
227
+ end
228
+ end
229
+
230
+ def call_once_v(command)
231
+ command = @command_builder.generate(command)
203
232
  result = ensure_connected(retryable: false) do |connection|
204
233
  Middlewares.call(command, config) do
205
234
  connection.call(command, nil)
@@ -214,14 +243,39 @@ class RedisClient
214
243
  end
215
244
 
216
245
  def blocking_call(timeout, *command, **kwargs)
217
- command = @command_builder.generate!(command, kwargs)
246
+ command = @command_builder.generate(command, kwargs)
247
+ error = nil
218
248
  result = ensure_connected do |connection|
219
249
  Middlewares.call(command, config) do
220
250
  connection.call(command, timeout)
221
251
  end
252
+ rescue ReadTimeoutError => error
253
+ break
222
254
  end
223
255
 
224
- if block_given?
256
+ if error
257
+ raise error
258
+ elsif block_given?
259
+ yield result
260
+ else
261
+ result
262
+ end
263
+ end
264
+
265
+ def blocking_call_v(timeout, command)
266
+ command = @command_builder.generate(command)
267
+ error = nil
268
+ result = ensure_connected do |connection|
269
+ Middlewares.call(command, config) do
270
+ connection.call(command, timeout)
271
+ end
272
+ rescue ReadTimeoutError => error
273
+ break
274
+ end
275
+
276
+ if error
277
+ raise error
278
+ elsif block_given?
225
279
  yield result
226
280
  else
227
281
  result
@@ -233,8 +287,8 @@ class RedisClient
233
287
  return to_enum(__callee__, *args, **kwargs)
234
288
  end
235
289
 
236
- args = @command_builder.generate!(args, kwargs)
237
- scan_list(1, ["SCAN", 0, *args], &block)
290
+ args = @command_builder.generate(["SCAN", 0] + args, kwargs)
291
+ scan_list(1, args, &block)
238
292
  end
239
293
 
240
294
  def sscan(key, *args, **kwargs, &block)
@@ -242,8 +296,8 @@ class RedisClient
242
296
  return to_enum(__callee__, key, *args, **kwargs)
243
297
  end
244
298
 
245
- args = @command_builder.generate!(args, kwargs)
246
- scan_list(2, ["SSCAN", key, 0, *args], &block)
299
+ args = @command_builder.generate(["SSCAN", key, 0] + args, kwargs)
300
+ scan_list(2, args, &block)
247
301
  end
248
302
 
249
303
  def hscan(key, *args, **kwargs, &block)
@@ -251,8 +305,8 @@ class RedisClient
251
305
  return to_enum(__callee__, key, *args, **kwargs)
252
306
  end
253
307
 
254
- args = @command_builder.generate!(args, kwargs)
255
- scan_pairs(2, ["HSCAN", key, 0, *args], &block)
308
+ args = @command_builder.generate(["HSCAN", key, 0] + args, kwargs)
309
+ scan_pairs(2, args, &block)
256
310
  end
257
311
 
258
312
  def zscan(key, *args, **kwargs, &block)
@@ -260,8 +314,8 @@ class RedisClient
260
314
  return to_enum(__callee__, key, *args, **kwargs)
261
315
  end
262
316
 
263
- args = @command_builder.generate!(args, kwargs)
264
- scan_pairs(2, ["ZSCAN", key, 0, *args], &block)
317
+ args = @command_builder.generate(["ZSCAN", key, 0] + args, kwargs)
318
+ scan_pairs(2, args, &block)
265
319
  end
266
320
 
267
321
  def connected?
@@ -343,7 +397,12 @@ class RedisClient
343
397
  end
344
398
 
345
399
  def call(*command, **kwargs)
346
- raw_connection.write(@command_builder.generate!(command, kwargs))
400
+ raw_connection.write(@command_builder.generate(command, kwargs))
401
+ nil
402
+ end
403
+
404
+ def call_v(command)
405
+ raw_connection.write(@command_builder.generate(command))
347
406
  nil
348
407
  end
349
408
 
@@ -378,14 +437,29 @@ class RedisClient
378
437
  end
379
438
 
380
439
  def call(*command, **kwargs, &block)
381
- command = @command_builder.generate!(command, kwargs)
440
+ command = @command_builder.generate(command, kwargs)
441
+ (@blocks ||= [])[@commands.size] = block if block_given?
442
+ @commands << command
443
+ nil
444
+ end
445
+
446
+ def call_v(command, &block)
447
+ command = @command_builder.generate(command)
382
448
  (@blocks ||= [])[@commands.size] = block if block_given?
383
449
  @commands << command
384
450
  nil
385
451
  end
386
452
 
387
- def call_once(*command, **kwargs)
388
- command = @command_builder.generate!(command, kwargs)
453
+ def call_once(*command, **kwargs, &block)
454
+ command = @command_builder.generate(command, kwargs)
455
+ @retryable = false
456
+ (@blocks ||= [])[@commands.size] = block if block_given?
457
+ @commands << command
458
+ nil
459
+ end
460
+
461
+ def call_once_v(command, &block)
462
+ command = @command_builder.generate(command)
389
463
  @retryable = false
390
464
  (@blocks ||= [])[@commands.size] = block if block_given?
391
465
  @commands << command
@@ -417,18 +491,14 @@ class RedisClient
417
491
  end
418
492
 
419
493
  def _coerce!(results)
420
- if results
421
- results.each_with_index do |result, index|
422
- if result.is_a?(CommandError)
423
- result._set_command(@commands[index + 1])
424
- raise result
425
- end
494
+ results&.each_with_index do |result, index|
495
+ if result.is_a?(CommandError)
496
+ result._set_command(@commands[index + 1])
497
+ raise result
426
498
  end
427
499
 
428
- @blocks&.each_with_index do |block, index|
429
- if block
430
- results[index - 1] = block.call(results[index - 1])
431
- end
500
+ if @blocks && block = @blocks[index + 1]
501
+ results[index] = block.call(result)
432
502
  end
433
503
  end
434
504
 
@@ -442,8 +512,17 @@ class RedisClient
442
512
  @timeouts = nil
443
513
  end
444
514
 
445
- def blocking_call(timeout, *command, **kwargs)
446
- command = @command_builder.generate!(command, kwargs)
515
+ def blocking_call(timeout, *command, **kwargs, &block)
516
+ command = @command_builder.generate(command, kwargs)
517
+ @timeouts ||= []
518
+ @timeouts[@commands.size] = timeout
519
+ (@blocks ||= [])[@commands.size] = block if block_given?
520
+ @commands << command
521
+ nil
522
+ end
523
+
524
+ def blocking_call_v(timeout, command, &block)
525
+ command = @command_builder.generate(command)
447
526
  @timeouts ||= []
448
527
  @timeouts[@commands.size] = timeout
449
528
  (@blocks ||= [])[@commands.size] = block if block_given?
@@ -521,7 +600,7 @@ class RedisClient
521
600
  else
522
601
  connection
523
602
  end
524
- rescue ConnectionError => error
603
+ rescue ConnectionError, ProtocolError => error
525
604
  connection&.close
526
605
  close
527
606
 
@@ -538,6 +617,10 @@ class RedisClient
538
617
  begin
539
618
  @disable_reconnection = true
540
619
  yield connection
620
+ rescue ConnectionError, ProtocolError
621
+ connection&.close
622
+ close
623
+ raise
541
624
  ensure
542
625
  @disable_reconnection = previous_disable_reconnection
543
626
  end
@@ -568,10 +651,16 @@ class RedisClient
568
651
  role, = connection.call_pipelined(prelude, nil).last
569
652
  config.check_role!(role)
570
653
  else
571
- connection.call_pipelined(prelude, nil)
654
+ unless prelude.empty?
655
+ connection.call_pipelined(prelude, nil)
656
+ end
572
657
  end
573
658
 
574
659
  connection
660
+ rescue FailoverError
661
+ raise
662
+ rescue ConnectionError => error
663
+ raise CannotConnectError, error.message, error.backtrace
575
664
  rescue CommandError => error
576
665
  if error.message.include?("ERR unknown command `HELLO`")
577
666
  raise UnsupportedServer,
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redis-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jean Boussier
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-06-02 00:00:00.000000000 Z
11
+ date: 2022-08-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: connection_pool