redis 4.8.1 → 5.4.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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +82 -0
  3. data/README.md +125 -162
  4. data/lib/redis/client.rb +82 -616
  5. data/lib/redis/commands/bitmaps.rb +14 -4
  6. data/lib/redis/commands/cluster.rb +1 -18
  7. data/lib/redis/commands/connection.rb +5 -10
  8. data/lib/redis/commands/geo.rb +3 -3
  9. data/lib/redis/commands/hashes.rb +13 -6
  10. data/lib/redis/commands/hyper_log_log.rb +1 -1
  11. data/lib/redis/commands/keys.rb +27 -23
  12. data/lib/redis/commands/lists.rb +74 -25
  13. data/lib/redis/commands/pubsub.rb +34 -25
  14. data/lib/redis/commands/server.rb +15 -15
  15. data/lib/redis/commands/sets.rb +35 -40
  16. data/lib/redis/commands/sorted_sets.rb +128 -18
  17. data/lib/redis/commands/streams.rb +48 -21
  18. data/lib/redis/commands/strings.rb +18 -17
  19. data/lib/redis/commands/transactions.rb +7 -31
  20. data/lib/redis/commands.rb +11 -12
  21. data/lib/redis/distributed.rb +136 -72
  22. data/lib/redis/errors.rb +15 -50
  23. data/lib/redis/hash_ring.rb +26 -26
  24. data/lib/redis/pipeline.rb +47 -222
  25. data/lib/redis/subscribe.rb +50 -14
  26. data/lib/redis/version.rb +1 -1
  27. data/lib/redis.rb +77 -184
  28. metadata +10 -57
  29. data/lib/redis/cluster/command.rb +0 -79
  30. data/lib/redis/cluster/command_loader.rb +0 -33
  31. data/lib/redis/cluster/key_slot_converter.rb +0 -72
  32. data/lib/redis/cluster/node.rb +0 -120
  33. data/lib/redis/cluster/node_key.rb +0 -31
  34. data/lib/redis/cluster/node_loader.rb +0 -34
  35. data/lib/redis/cluster/option.rb +0 -100
  36. data/lib/redis/cluster/slot.rb +0 -86
  37. data/lib/redis/cluster/slot_loader.rb +0 -46
  38. data/lib/redis/cluster.rb +0 -315
  39. data/lib/redis/connection/command_helper.rb +0 -41
  40. data/lib/redis/connection/hiredis.rb +0 -68
  41. data/lib/redis/connection/registry.rb +0 -13
  42. data/lib/redis/connection/ruby.rb +0 -437
  43. data/lib/redis/connection/synchrony.rb +0 -148
  44. data/lib/redis/connection.rb +0 -11
@@ -27,9 +27,13 @@ class Redis
27
27
  # @param [String] key
28
28
  # @param [Integer] start start index
29
29
  # @param [Integer] stop stop index
30
+ # @param [String, Symbol] scale the scale of the offset range
31
+ # e.g. 'BYTE' - interpreted as a range of bytes, 'BIT' - interpreted as a range of bits
30
32
  # @return [Integer] the number of bits set to 1
31
- def bitcount(key, start = 0, stop = -1)
32
- send_command([:bitcount, key, start, stop])
33
+ def bitcount(key, start = 0, stop = -1, scale: nil)
34
+ command = [:bitcount, key, start, stop]
35
+ command << scale if scale
36
+ send_command(command)
33
37
  end
34
38
 
35
39
  # Perform a bitwise operation between strings and store the resulting string in a key.
@@ -39,7 +43,10 @@ class Redis
39
43
  # @param [String, Array<String>] keys one or more source keys to perform `operation`
40
44
  # @return [Integer] the length of the string stored in `destkey`
41
45
  def bitop(operation, destkey, *keys)
42
- send_command([:bitop, operation, destkey, *keys])
46
+ keys.flatten!(1)
47
+ command = [:bitop, operation, destkey]
48
+ command.concat(keys)
49
+ send_command(command)
43
50
  end
44
51
 
45
52
  # Return the position of the first bit set to 1 or 0 in a string.
@@ -48,14 +55,17 @@ class Redis
48
55
  # @param [Integer] bit whether to look for the first 1 or 0 bit
49
56
  # @param [Integer] start start index
50
57
  # @param [Integer] stop stop index
58
+ # @param [String, Symbol] scale the scale of the offset range
59
+ # e.g. 'BYTE' - interpreted as a range of bytes, 'BIT' - interpreted as a range of bits
51
60
  # @return [Integer] the position of the first 1/0 bit.
52
61
  # -1 if looking for 1 and it is not found or start and stop are given.
53
- def bitpos(key, bit, start = nil, stop = nil)
62
+ def bitpos(key, bit, start = nil, stop = nil, scale: nil)
54
63
  raise(ArgumentError, 'stop parameter specified without start parameter') if stop && !start
55
64
 
56
65
  command = [:bitpos, key, bit]
57
66
  command << start if start
58
67
  command << stop if stop
68
+ command << scale if scale
59
69
  send_command(command)
60
70
  end
61
71
  end
@@ -12,24 +12,7 @@ class Redis
12
12
  #
13
13
  # @return [Object] depends on the subcommand
14
14
  def cluster(subcommand, *args)
15
- subcommand = subcommand.to_s.downcase
16
- block = case subcommand
17
- when 'slots'
18
- HashifyClusterSlots
19
- when 'nodes'
20
- HashifyClusterNodes
21
- when 'slaves'
22
- HashifyClusterSlaves
23
- when 'info'
24
- HashifyInfo
25
- else
26
- Noop
27
- end
28
-
29
- # @see https://github.com/antirez/redis/blob/unstable/src/redis-trib.rb#L127 raw reply expected
30
- block = Noop unless @cluster_mode
31
-
32
- send_command([:cluster, subcommand] + args, &block)
15
+ send_command([:cluster, subcommand] + args)
33
16
  end
34
17
 
35
18
  # Sends `ASKING` command to random node and returns its reply.
@@ -34,10 +34,7 @@ class Redis
34
34
  # @param [Integer] db zero-based index of the DB to use (0 to 15)
35
35
  # @return [String] `OK`
36
36
  def select(db)
37
- synchronize do |client|
38
- client.db = db
39
- client.call([:select, db])
40
- end
37
+ send_command([:select, db])
41
38
  end
42
39
 
43
40
  # Close the connection.
@@ -45,12 +42,10 @@ class Redis
45
42
  # @return [String] `OK`
46
43
  def quit
47
44
  synchronize do |client|
48
- begin
49
- client.call([:quit])
50
- rescue ConnectionError
51
- ensure
52
- client.disconnect
53
- end
45
+ client.call_v([:quit])
46
+ rescue ConnectionError
47
+ ensure
48
+ client.close
54
49
  end
55
50
  end
56
51
  end
@@ -74,9 +74,9 @@ class Redis
74
74
  private
75
75
 
76
76
  def _geoarguments(*args, options: nil, sort: nil, count: nil)
77
- args.push sort if sort
78
- args.push 'count', count if count
79
- args.push options if options
77
+ args << sort if sort
78
+ args << 'COUNT' << Integer(count) if count
79
+ args << options if options
80
80
  args
81
81
  end
82
82
  end
@@ -63,7 +63,7 @@ class Redis
63
63
  #
64
64
  # @see #hmset
65
65
  def mapped_hmset(key, hash)
66
- hmset(key, hash.to_a.flatten)
66
+ hmset(key, hash.flatten)
67
67
  end
68
68
 
69
69
  # Get the value of a hash field.
@@ -87,7 +87,8 @@ class Redis
87
87
  #
88
88
  # @see #mapped_hmget
89
89
  def hmget(key, *fields, &blk)
90
- send_command([:hmget, key] + fields, &blk)
90
+ fields.flatten!(1)
91
+ send_command([:hmget, key].concat(fields), &blk)
91
92
  end
92
93
 
93
94
  # Get the values of all the given hash fields.
@@ -102,7 +103,8 @@ class Redis
102
103
  #
103
104
  # @see #hmget
104
105
  def mapped_hmget(key, *fields)
105
- hmget(key, *fields) do |reply|
106
+ fields.flatten!(1)
107
+ hmget(key, fields) do |reply|
106
108
  if reply.is_a?(Array)
107
109
  Hash[fields.zip(reply)]
108
110
  else
@@ -152,7 +154,8 @@ class Redis
152
154
  # @param [String, Array<String>] field
153
155
  # @return [Integer] the number of fields that were removed from the hash
154
156
  def hdel(key, *fields)
155
- send_command([:hdel, key, *fields])
157
+ fields.flatten!(1)
158
+ send_command([:hdel, key].concat(fields))
156
159
  end
157
160
 
158
161
  # Determine if a hash field exists.
@@ -171,7 +174,7 @@ class Redis
171
174
  # @param [Integer] increment
172
175
  # @return [Integer] value of the field after incrementing it
173
176
  def hincrby(key, field, increment)
174
- send_command([:hincrby, key, field, increment])
177
+ send_command([:hincrby, key, field, Integer(increment)])
175
178
  end
176
179
 
177
180
  # Increment the numeric value of a hash field by the given float number.
@@ -181,7 +184,7 @@ class Redis
181
184
  # @param [Float] increment
182
185
  # @return [Float] value of the field after incrementing it
183
186
  def hincrbyfloat(key, field, increment)
184
- send_command([:hincrbyfloat, key, field, increment], &Floatify)
187
+ send_command([:hincrbyfloat, key, field, Float(increment)], &Floatify)
185
188
  end
186
189
 
187
190
  # Get all the fields in a hash.
@@ -219,6 +222,8 @@ class Redis
219
222
  # - `:count => Integer`: return count keys at most per iteration
220
223
  #
221
224
  # @return [String, Array<[String, String]>] the next cursor and all found keys
225
+ #
226
+ # See the [Redis Server HSCAN documentation](https://redis.io/docs/latest/commands/hscan/) for further details
222
227
  def hscan(key, cursor, **options)
223
228
  _scan(:hscan, cursor, [key], **options) do |reply|
224
229
  [reply[0], reply[1].each_slice(2).to_a]
@@ -236,6 +241,8 @@ class Redis
236
241
  # - `:count => Integer`: return count keys at most per iteration
237
242
  #
238
243
  # @return [Enumerator] an enumerator for all found keys
244
+ #
245
+ # See the [Redis Server HSCAN documentation](https://redis.io/docs/latest/commands/hscan/) for further details
239
246
  def hscan_each(key, **options, &block)
240
247
  return to_enum(:hscan_each, key, **options) unless block_given?
241
248
 
@@ -20,7 +20,7 @@ class Redis
20
20
  # @param [String, Array<String>] keys
21
21
  # @return [Integer]
22
22
  def pfcount(*keys)
23
- send_command([:pfcount] + keys)
23
+ send_command([:pfcount] + keys.flatten(1))
24
24
  end
25
25
 
26
26
  # Merge multiple HyperLogLog values into an unique value that will approximate the cardinality of the union of
@@ -22,6 +22,8 @@ class Redis
22
22
  # - `:type => String`: return keys only of the given type
23
23
  #
24
24
  # @return [String, Array<String>] the next cursor and all found keys
25
+ #
26
+ # See the [Redis Server SCAN documentation](https://redis.io/docs/latest/commands/scan/) for further details
25
27
  def scan(cursor, **options)
26
28
  _scan(:scan, cursor, [], **options)
27
29
  end
@@ -46,6 +48,8 @@ class Redis
46
48
  # - `:type => String`: return keys only of the given type
47
49
  #
48
50
  # @return [Enumerator] an enumerator for all found keys
51
+ #
52
+ # See the [Redis Server SCAN documentation](https://redis.io/docs/latest/commands/scan/) for further details
49
53
  def scan_each(**options, &block)
50
54
  return to_enum(:scan_each, **options) unless block_given?
51
55
 
@@ -76,7 +80,7 @@ class Redis
76
80
  # - `:lt => true`: Set expiry only when the new expiry is less than current one.
77
81
  # @return [Boolean] whether the timeout was set or not
78
82
  def expire(key, seconds, nx: nil, xx: nil, gt: nil, lt: nil)
79
- args = [:expire, key, seconds]
83
+ args = [:expire, key, Integer(seconds)]
80
84
  args << "NX" if nx
81
85
  args << "XX" if xx
82
86
  args << "GT" if gt
@@ -96,7 +100,7 @@ class Redis
96
100
  # - `:lt => true`: Set expiry only when the new expiry is less than current one.
97
101
  # @return [Boolean] whether the timeout was set or not
98
102
  def expireat(key, unix_time, nx: nil, xx: nil, gt: nil, lt: nil)
99
- args = [:expireat, key, unix_time]
103
+ args = [:expireat, key, Integer(unix_time)]
100
104
  args << "NX" if nx
101
105
  args << "XX" if xx
102
106
  args << "GT" if gt
@@ -105,6 +109,14 @@ class Redis
105
109
  send_command(args, &Boolify)
106
110
  end
107
111
 
112
+ # Get a key's expiry time specified as number of seconds from UNIX Epoch
113
+ #
114
+ # @param [String] key
115
+ # @return [Integer] expiry time specified as number of seconds from UNIX Epoch
116
+ def expiretime(key)
117
+ send_command([:expiretime, key])
118
+ end
119
+
108
120
  # Get the time to live (in seconds) for a key.
109
121
  #
110
122
  # @param [String] key
@@ -132,7 +144,7 @@ class Redis
132
144
  # - `:lt => true`: Set expiry only when the new expiry is less than current one.
133
145
  # @return [Boolean] whether the timeout was set or not
134
146
  def pexpire(key, milliseconds, nx: nil, xx: nil, gt: nil, lt: nil)
135
- args = [:pexpire, key, milliseconds]
147
+ args = [:pexpire, key, Integer(milliseconds)]
136
148
  args << "NX" if nx
137
149
  args << "XX" if xx
138
150
  args << "GT" if gt
@@ -152,7 +164,7 @@ class Redis
152
164
  # - `:lt => true`: Set expiry only when the new expiry is less than current one.
153
165
  # @return [Boolean] whether the timeout was set or not
154
166
  def pexpireat(key, ms_unix_time, nx: nil, xx: nil, gt: nil, lt: nil)
155
- args = [:pexpireat, key, ms_unix_time]
167
+ args = [:pexpireat, key, Integer(ms_unix_time)]
156
168
  args << "NX" if nx
157
169
  args << "XX" if xx
158
170
  args << "GT" if gt
@@ -161,6 +173,14 @@ class Redis
161
173
  send_command(args, &Boolify)
162
174
  end
163
175
 
176
+ # Get a key's expiry time specified as number of milliseconds from UNIX Epoch
177
+ #
178
+ # @param [String] key
179
+ # @return [Integer] expiry time specified as number of milliseconds from UNIX Epoch
180
+ def pexpiretime(key)
181
+ send_command([:pexpiretime, key])
182
+ end
183
+
164
184
  # Get the time to live (in milliseconds) for a key.
165
185
  #
166
186
  # @param [String] key
@@ -249,24 +269,6 @@ class Redis
249
269
  # @param [String, Array<String>] keys
250
270
  # @return [Integer]
251
271
  def exists(*keys)
252
- if !Redis.exists_returns_integer && keys.size == 1
253
- if Redis.exists_returns_integer.nil?
254
- message = "`Redis#exists(key)` will return an Integer in redis-rb 4.3. `exists?` returns a boolean, you " \
255
- "should use it instead. To opt-in to the new behavior now you can set Redis.exists_returns_integer = " \
256
- "true. To disable this message and keep the current (boolean) behaviour of 'exists' you can set " \
257
- "`Redis.exists_returns_integer = false`, but this option will be removed in 5.0.0. " \
258
- "(#{::Kernel.caller(1, 1).first})\n"
259
-
260
- ::Redis.deprecate!(message)
261
- end
262
-
263
- exists?(*keys)
264
- else
265
- _exists(*keys)
266
- end
267
- end
268
-
269
- def _exists(*keys)
270
272
  send_command([:exists, *keys])
271
273
  end
272
274
 
@@ -284,6 +286,8 @@ class Redis
284
286
  #
285
287
  # @param [String] pattern
286
288
  # @return [Array<String>]
289
+ #
290
+ # See the [Redis Server KEYS documentation](https://redis.io/docs/latest/commands/keys/) for further details
287
291
  def keys(pattern = "*")
288
292
  send_command([:keys, pattern]) do |reply|
289
293
  if reply.is_a?(String)
@@ -445,7 +449,7 @@ class Redis
445
449
 
446
450
  args << cursor
447
451
  args << "MATCH" << match if match
448
- args << "COUNT" << count if count
452
+ args << "COUNT" << Integer(count) if count
449
453
  args << "TYPE" << type if type
450
454
 
451
455
  send_command([command] + args, &block)
@@ -48,7 +48,7 @@ class Redis
48
48
  # @param [String, Symbol] where_destination where to push the element to the source list
49
49
  # e.g. 'LEFT' - to head, 'RIGHT' - to tail
50
50
  # @param [Hash] options
51
- # - `:timeout => Numeric`: timeout in seconds, defaults to no timeout
51
+ # - `:timeout => [Float, Integer]`: timeout in seconds, defaults to no timeout
52
52
  #
53
53
  # @return [nil, String] the element, or nil when the source key does not exist or the timeout expired
54
54
  #
@@ -99,10 +99,10 @@ class Redis
99
99
  #
100
100
  # @param [String] key
101
101
  # @param [Integer] count number of elements to remove
102
- # @return [String, Array<String>] the values of the first elements
102
+ # @return [nil, String, Array<String>] the values of the first elements
103
103
  def lpop(key, count = nil)
104
104
  command = [:lpop, key]
105
- command << count if count
105
+ command << Integer(count) if count
106
106
  send_command(command)
107
107
  end
108
108
 
@@ -110,10 +110,10 @@ class Redis
110
110
  #
111
111
  # @param [String] key
112
112
  # @param [Integer] count number of elements to remove
113
- # @return [String, Array<String>] the values of the last elements
113
+ # @return [nil, String, Array<String>] the values of the last elements
114
114
  def rpop(key, count = nil)
115
115
  command = [:rpop, key]
116
- command << count if count
116
+ command << Integer(count) if count
117
117
  send_command(command)
118
118
  end
119
119
 
@@ -142,7 +142,7 @@ class Redis
142
142
  # @param [String, Array<String>] keys one or more keys to perform the
143
143
  # blocking pop on
144
144
  # @param [Hash] options
145
- # - `:timeout => Integer`: timeout in seconds, defaults to no timeout
145
+ # - `:timeout => [Float, Integer]`: timeout in seconds, defaults to no timeout
146
146
  #
147
147
  # @return [nil, [String, String]]
148
148
  # - `nil` when the operation timed out
@@ -156,7 +156,7 @@ class Redis
156
156
  # @param [String, Array<String>] keys one or more keys to perform the
157
157
  # blocking pop on
158
158
  # @param [Hash] options
159
- # - `:timeout => Integer`: timeout in seconds, defaults to no timeout
159
+ # - `:timeout => [Float, Integer]`: timeout in seconds, defaults to no timeout
160
160
  #
161
161
  # @return [nil, [String, String]]
162
162
  # - `nil` when the operation timed out
@@ -173,23 +173,77 @@ class Redis
173
173
  # @param [String] source source key
174
174
  # @param [String] destination destination key
175
175
  # @param [Hash] options
176
- # - `:timeout => Integer`: timeout in seconds, defaults to no timeout
176
+ # - `:timeout => [Float, Integer]`: timeout in seconds, defaults to no timeout
177
177
  #
178
178
  # @return [nil, String]
179
179
  # - `nil` when the operation timed out
180
180
  # - the element was popped and pushed otherwise
181
- def brpoplpush(source, destination, deprecated_timeout = 0, timeout: deprecated_timeout)
181
+ def brpoplpush(source, destination, timeout: 0)
182
182
  command = [:brpoplpush, source, destination, timeout]
183
183
  send_blocking_command(command, timeout)
184
184
  end
185
185
 
186
+ # Pops one or more elements from the first non-empty list key from the list
187
+ # of provided key names. If lists are empty, blocks until timeout has passed.
188
+ #
189
+ # @example Popping a element
190
+ # redis.blmpop(1.0, 'list')
191
+ # #=> ['list', ['a']]
192
+ # @example With count option
193
+ # redis.blmpop(1.0, 'list', count: 2)
194
+ # #=> ['list', ['a', 'b']]
195
+ #
196
+ # @params timeout [Float] a float value specifying the maximum number of seconds to block) elapses.
197
+ # A timeout of zero can be used to block indefinitely.
198
+ # @params key [String, Array<String>] one or more keys with lists
199
+ # @params modifier [String]
200
+ # - when `"LEFT"` - the elements popped are those from the left of the list
201
+ # - when `"RIGHT"` - the elements popped are those from the right of the list
202
+ # @params count [Integer] a number of elements to pop
203
+ #
204
+ # @return [Array<String, Array<String, Float>>] list of popped elements or nil
205
+ def blmpop(timeout, *keys, modifier: "LEFT", count: nil)
206
+ raise ArgumentError, "Pick either LEFT or RIGHT" unless modifier == "LEFT" || modifier == "RIGHT"
207
+
208
+ args = [:blmpop, timeout, keys.size, *keys, modifier]
209
+ args << "COUNT" << Integer(count) if count
210
+
211
+ send_blocking_command(args, timeout)
212
+ end
213
+
214
+ # Pops one or more elements from the first non-empty list key from the list
215
+ # of provided key names.
216
+ #
217
+ # @example Popping a element
218
+ # redis.lmpop('list')
219
+ # #=> ['list', ['a']]
220
+ # @example With count option
221
+ # redis.lmpop('list', count: 2)
222
+ # #=> ['list', ['a', 'b']]
223
+ #
224
+ # @params key [String, Array<String>] one or more keys with lists
225
+ # @params modifier [String]
226
+ # - when `"LEFT"` - the elements popped are those from the left of the list
227
+ # - when `"RIGHT"` - the elements popped are those from the right of the list
228
+ # @params count [Integer] a number of elements to pop
229
+ #
230
+ # @return [Array<String, Array<String, Float>>] list of popped elements or nil
231
+ def lmpop(*keys, modifier: "LEFT", count: nil)
232
+ raise ArgumentError, "Pick either LEFT or RIGHT" unless modifier == "LEFT" || modifier == "RIGHT"
233
+
234
+ args = [:lmpop, keys.size, *keys, modifier]
235
+ args << "COUNT" << Integer(count) if count
236
+
237
+ send_command(args)
238
+ end
239
+
186
240
  # Get an element from a list by its index.
187
241
  #
188
242
  # @param [String] key
189
243
  # @param [Integer] index
190
244
  # @return [String]
191
245
  def lindex(key, index)
192
- send_command([:lindex, key, index])
246
+ send_command([:lindex, key, Integer(index)])
193
247
  end
194
248
 
195
249
  # Insert an element before or after another element in a list.
@@ -211,7 +265,7 @@ class Redis
211
265
  # @param [Integer] stop stop index
212
266
  # @return [Array<String>]
213
267
  def lrange(key, start, stop)
214
- send_command([:lrange, key, start, stop])
268
+ send_command([:lrange, key, Integer(start), Integer(stop)])
215
269
  end
216
270
 
217
271
  # Remove elements from a list.
@@ -224,7 +278,7 @@ class Redis
224
278
  # @param [String] value
225
279
  # @return [Integer] the number of removed elements
226
280
  def lrem(key, count, value)
227
- send_command([:lrem, key, count, value])
281
+ send_command([:lrem, key, Integer(count), value])
228
282
  end
229
283
 
230
284
  # Set the value of an element in a list by its index.
@@ -234,7 +288,7 @@ class Redis
234
288
  # @param [String] value
235
289
  # @return [String] `OK`
236
290
  def lset(key, index, value)
237
- send_command([:lset, key, index, value])
291
+ send_command([:lset, key, Integer(index), value])
238
292
  end
239
293
 
240
294
  # Trim a list to the specified range.
@@ -244,7 +298,7 @@ class Redis
244
298
  # @param [Integer] stop stop index
245
299
  # @return [String] `OK`
246
300
  def ltrim(key, start, stop)
247
- send_command([:ltrim, key, start, stop])
301
+ send_command([:ltrim, key, Integer(start), Integer(stop)])
248
302
  end
249
303
 
250
304
  private
@@ -253,21 +307,16 @@ class Redis
253
307
  timeout = if args.last.is_a?(Hash)
254
308
  options = args.pop
255
309
  options[:timeout]
256
- elsif args.last.respond_to?(:to_int)
257
- last_arg = args.pop
258
- ::Redis.deprecate!(
259
- "Passing the timeout as a positional argument is deprecated, it should be passed as a keyword argument:\n" \
260
- " redis.#{cmd}(#{args.map(&:inspect).join(', ')}, timeout: #{last_arg.to_int})" \
261
- "(called from: #{caller(2, 1).first})"
262
- )
263
- last_arg.to_int
264
310
  end
265
311
 
266
312
  timeout ||= 0
313
+ unless timeout.is_a?(Integer) || timeout.is_a?(Float)
314
+ raise ArgumentError, "timeout must be an Integer or Float, got: #{timeout.class}"
315
+ end
267
316
 
268
- keys = args.flatten
269
-
270
- command = [cmd, keys, timeout]
317
+ args.flatten!(1)
318
+ command = [cmd].concat(args)
319
+ command << timeout
271
320
  send_blocking_command(command, timeout, &blk)
272
321
  end
273
322
 
@@ -9,57 +9,45 @@ class Redis
9
9
  end
10
10
 
11
11
  def subscribed?
12
- synchronize do |client|
13
- client.is_a? SubscribedClient
14
- end
12
+ !@subscription_client.nil?
15
13
  end
16
14
 
17
15
  # Listen for messages published to the given channels.
18
16
  def subscribe(*channels, &block)
19
- synchronize do |_client|
20
- _subscription(:subscribe, 0, channels, block)
21
- end
17
+ _subscription(:subscribe, 0, channels, block)
22
18
  end
23
19
 
24
20
  # Listen for messages published to the given channels. Throw a timeout error
25
21
  # if there is no messages for a timeout period.
26
22
  def subscribe_with_timeout(timeout, *channels, &block)
27
- synchronize do |_client|
28
- _subscription(:subscribe_with_timeout, timeout, channels, block)
29
- end
23
+ _subscription(:subscribe_with_timeout, timeout, channels, block)
30
24
  end
31
25
 
32
26
  # Stop listening for messages posted to the given channels.
33
27
  def unsubscribe(*channels)
34
- synchronize do |client|
35
- raise "Can't unsubscribe if not subscribed." unless subscribed?
36
-
37
- client.unsubscribe(*channels)
38
- end
28
+ _subscription(:unsubscribe, 0, channels, nil)
39
29
  end
40
30
 
41
31
  # Listen for messages published to channels matching the given patterns.
32
+ # See the [Redis Server PSUBSCRIBE documentation](https://redis.io/docs/latest/commands/psubscribe/)
33
+ # for further details
42
34
  def psubscribe(*channels, &block)
43
- synchronize do |_client|
44
- _subscription(:psubscribe, 0, channels, block)
45
- end
35
+ _subscription(:psubscribe, 0, channels, block)
46
36
  end
47
37
 
48
38
  # Listen for messages published to channels matching the given patterns.
49
39
  # Throw a timeout error if there is no messages for a timeout period.
40
+ # See the [Redis Server PSUBSCRIBE documentation](https://redis.io/docs/latest/commands/psubscribe/)
41
+ # for further details
50
42
  def psubscribe_with_timeout(timeout, *channels, &block)
51
- synchronize do |_client|
52
- _subscription(:psubscribe_with_timeout, timeout, channels, block)
53
- end
43
+ _subscription(:psubscribe_with_timeout, timeout, channels, block)
54
44
  end
55
45
 
56
46
  # Stop listening for messages posted to channels matching the given patterns.
47
+ # See the [Redis Server PUNSUBSCRIBE documentation](https://redis.io/docs/latest/commands/punsubscribe/)
48
+ # for further details
57
49
  def punsubscribe(*channels)
58
- synchronize do |client|
59
- raise "Can't unsubscribe if not subscribed." unless subscribed?
60
-
61
- client.punsubscribe(*channels)
62
- end
50
+ _subscription(:punsubscribe, 0, channels, nil)
63
51
  end
64
52
 
65
53
  # Inspect the state of the Pub/Sub subsystem.
@@ -67,6 +55,27 @@ class Redis
67
55
  def pubsub(subcommand, *args)
68
56
  send_command([:pubsub, subcommand] + args)
69
57
  end
58
+
59
+ # Post a message to a channel in a shard.
60
+ def spublish(channel, message)
61
+ send_command([:spublish, channel, message])
62
+ end
63
+
64
+ # Listen for messages published to the given channels in a shard.
65
+ def ssubscribe(*channels, &block)
66
+ _subscription(:ssubscribe, 0, channels, block)
67
+ end
68
+
69
+ # Listen for messages published to the given channels in a shard.
70
+ # Throw a timeout error if there is no messages for a timeout period.
71
+ def ssubscribe_with_timeout(timeout, *channels, &block)
72
+ _subscription(:ssubscribe_with_timeout, timeout, channels, block)
73
+ end
74
+
75
+ # Stop listening for messages posted to the given channels in a shard.
76
+ def sunsubscribe(*channels)
77
+ _subscription(:sunsubscribe, 0, channels, nil)
78
+ end
70
79
  end
71
80
  end
72
81
  end
@@ -36,7 +36,7 @@ class Redis
36
36
  #
37
37
  # @param [String, Symbol] subcommand e.g. `kill`, `list`, `getname`, `setname`
38
38
  # @return [String, Hash] depends on subcommand
39
- def client(subcommand = nil, *args)
39
+ def client(subcommand, *args)
40
40
  send_command([:client, subcommand] + args) do |reply|
41
41
  if subcommand.to_s == "list"
42
42
  reply.lines.map do |line|
@@ -117,9 +117,13 @@ class Redis
117
117
  #
118
118
  # @yield a block to be called for every line of output
119
119
  # @yieldparam [String] line timestamp and command that was executed
120
- def monitor(&block)
120
+ def monitor
121
121
  synchronize do |client|
122
- client.call_loop([:monitor], &block)
122
+ client = client.pubsub
123
+ client.call_v([:monitor])
124
+ loop do
125
+ yield client.next_event
126
+ end
123
127
  end
124
128
  end
125
129
 
@@ -133,13 +137,11 @@ class Redis
133
137
  # Synchronously save the dataset to disk and then shut down the server.
134
138
  def shutdown
135
139
  synchronize do |client|
136
- client.with_reconnect(false) do
137
- begin
138
- client.call([:shutdown])
139
- rescue ConnectionError
140
- # This means Redis has probably exited.
141
- nil
142
- end
140
+ client.disable_reconnection do
141
+ client.call_v([:shutdown])
142
+ rescue ConnectionError
143
+ # This means Redis has probably exited.
144
+ nil
143
145
  end
144
146
  end
145
147
  end
@@ -155,11 +157,9 @@ class Redis
155
157
  # @param [Integer] length maximum number of entries to return
156
158
  # @return [Array<String>, Integer, String] depends on subcommand
157
159
  def slowlog(subcommand, length = nil)
158
- synchronize do |client|
159
- args = [:slowlog, subcommand]
160
- args << length if length
161
- client.call args
162
- end
160
+ args = [:slowlog, subcommand]
161
+ args << Integer(length) if length
162
+ send_command(args)
163
163
  end
164
164
 
165
165
  # Internal command used for replication.