redis 3.3.5 → 4.1.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/CHANGELOG.md +84 -2
- data/README.md +131 -76
- data/lib/redis.rb +912 -200
- data/lib/redis/client.rb +71 -29
- data/lib/redis/cluster.rb +291 -0
- data/lib/redis/cluster/command.rb +81 -0
- data/lib/redis/cluster/command_loader.rb +34 -0
- data/lib/redis/cluster/key_slot_converter.rb +72 -0
- data/lib/redis/cluster/node.rb +104 -0
- data/lib/redis/cluster/node_key.rb +31 -0
- data/lib/redis/cluster/node_loader.rb +37 -0
- data/lib/redis/cluster/option.rb +87 -0
- data/lib/redis/cluster/slot.rb +72 -0
- data/lib/redis/cluster/slot_loader.rb +50 -0
- data/lib/redis/connection.rb +3 -2
- data/lib/redis/connection/command_helper.rb +3 -8
- data/lib/redis/connection/hiredis.rb +3 -2
- data/lib/redis/connection/registry.rb +1 -0
- data/lib/redis/connection/ruby.rb +48 -32
- data/lib/redis/connection/synchrony.rb +13 -4
- data/lib/redis/distributed.rb +39 -15
- data/lib/redis/errors.rb +47 -0
- data/lib/redis/hash_ring.rb +21 -64
- data/lib/redis/pipeline.rb +54 -12
- data/lib/redis/subscribe.rb +1 -0
- data/lib/redis/version.rb +2 -1
- metadata +40 -198
- data/.gitignore +0 -16
- data/.travis.yml +0 -89
- data/.travis/Gemfile +0 -11
- data/.yardopts +0 -3
- data/Gemfile +0 -4
- data/Rakefile +0 -87
- data/benchmarking/logging.rb +0 -71
- data/benchmarking/pipeline.rb +0 -51
- data/benchmarking/speed.rb +0 -21
- data/benchmarking/suite.rb +0 -24
- data/benchmarking/worker.rb +0 -71
- data/examples/basic.rb +0 -15
- data/examples/consistency.rb +0 -114
- data/examples/dist_redis.rb +0 -43
- data/examples/incr-decr.rb +0 -17
- data/examples/list.rb +0 -26
- data/examples/pubsub.rb +0 -37
- data/examples/sentinel.rb +0 -41
- data/examples/sentinel/sentinel.conf +0 -9
- data/examples/sentinel/start +0 -49
- data/examples/sets.rb +0 -36
- data/examples/unicorn/config.ru +0 -3
- data/examples/unicorn/unicorn.rb +0 -20
- data/redis.gemspec +0 -44
- data/test/bitpos_test.rb +0 -69
- data/test/blocking_commands_test.rb +0 -42
- data/test/client_test.rb +0 -59
- data/test/command_map_test.rb +0 -30
- data/test/commands_on_hashes_test.rb +0 -21
- data/test/commands_on_hyper_log_log_test.rb +0 -21
- data/test/commands_on_lists_test.rb +0 -20
- data/test/commands_on_sets_test.rb +0 -77
- data/test/commands_on_sorted_sets_test.rb +0 -137
- data/test/commands_on_strings_test.rb +0 -101
- data/test/commands_on_value_types_test.rb +0 -133
- data/test/connection_handling_test.rb +0 -277
- data/test/connection_test.rb +0 -57
- data/test/db/.gitkeep +0 -0
- data/test/distributed_blocking_commands_test.rb +0 -46
- data/test/distributed_commands_on_hashes_test.rb +0 -10
- data/test/distributed_commands_on_hyper_log_log_test.rb +0 -33
- data/test/distributed_commands_on_lists_test.rb +0 -22
- data/test/distributed_commands_on_sets_test.rb +0 -83
- data/test/distributed_commands_on_sorted_sets_test.rb +0 -18
- data/test/distributed_commands_on_strings_test.rb +0 -59
- data/test/distributed_commands_on_value_types_test.rb +0 -95
- data/test/distributed_commands_requiring_clustering_test.rb +0 -164
- data/test/distributed_connection_handling_test.rb +0 -23
- data/test/distributed_internals_test.rb +0 -79
- data/test/distributed_key_tags_test.rb +0 -52
- data/test/distributed_persistence_control_commands_test.rb +0 -26
- data/test/distributed_publish_subscribe_test.rb +0 -92
- data/test/distributed_remote_server_control_commands_test.rb +0 -66
- data/test/distributed_scripting_test.rb +0 -102
- data/test/distributed_sorting_test.rb +0 -20
- data/test/distributed_test.rb +0 -58
- data/test/distributed_transactions_test.rb +0 -32
- data/test/encoding_test.rb +0 -18
- data/test/error_replies_test.rb +0 -59
- data/test/fork_safety_test.rb +0 -65
- data/test/helper.rb +0 -232
- data/test/helper_test.rb +0 -24
- data/test/internals_test.rb +0 -417
- data/test/lint/blocking_commands.rb +0 -150
- data/test/lint/hashes.rb +0 -162
- data/test/lint/hyper_log_log.rb +0 -60
- data/test/lint/lists.rb +0 -143
- data/test/lint/sets.rb +0 -140
- data/test/lint/sorted_sets.rb +0 -316
- data/test/lint/strings.rb +0 -260
- data/test/lint/value_types.rb +0 -122
- data/test/persistence_control_commands_test.rb +0 -26
- data/test/pipelining_commands_test.rb +0 -242
- data/test/publish_subscribe_test.rb +0 -282
- data/test/remote_server_control_commands_test.rb +0 -118
- data/test/scanning_test.rb +0 -413
- data/test/scripting_test.rb +0 -78
- data/test/sentinel_command_test.rb +0 -80
- data/test/sentinel_test.rb +0 -255
- data/test/sorting_test.rb +0 -59
- data/test/ssl_test.rb +0 -73
- data/test/support/connection/hiredis.rb +0 -1
- data/test/support/connection/ruby.rb +0 -1
- data/test/support/connection/synchrony.rb +0 -17
- data/test/support/redis_mock.rb +0 -130
- data/test/support/ssl/gen_certs.sh +0 -31
- data/test/support/ssl/trusted-ca.crt +0 -25
- data/test/support/ssl/trusted-ca.key +0 -27
- data/test/support/ssl/trusted-cert.crt +0 -81
- data/test/support/ssl/trusted-cert.key +0 -28
- data/test/support/ssl/untrusted-ca.crt +0 -26
- data/test/support/ssl/untrusted-ca.key +0 -27
- data/test/support/ssl/untrusted-cert.crt +0 -82
- data/test/support/ssl/untrusted-cert.key +0 -28
- data/test/support/wire/synchrony.rb +0 -24
- data/test/support/wire/thread.rb +0 -5
- data/test/synchrony_driver.rb +0 -88
- data/test/test.conf.erb +0 -9
- data/test/thread_safety_test.rb +0 -62
- data/test/transactions_test.rb +0 -264
- data/test/unknown_commands_test.rb +0 -14
- data/test/url_param_test.rb +0 -138
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
class Redis
|
6
|
+
class Cluster
|
7
|
+
# Keep slot and node key map for Redis Cluster Client
|
8
|
+
class Slot
|
9
|
+
ROLE_SLAVE = 'slave'
|
10
|
+
|
11
|
+
def initialize(available_slots, node_flags = {}, with_replica = false)
|
12
|
+
@with_replica = with_replica
|
13
|
+
@node_flags = node_flags
|
14
|
+
@map = build_slot_node_key_map(available_slots)
|
15
|
+
end
|
16
|
+
|
17
|
+
def exists?(slot)
|
18
|
+
@map.key?(slot)
|
19
|
+
end
|
20
|
+
|
21
|
+
def find_node_key_of_master(slot)
|
22
|
+
return nil unless exists?(slot)
|
23
|
+
|
24
|
+
@map[slot][:master]
|
25
|
+
end
|
26
|
+
|
27
|
+
def find_node_key_of_slave(slot)
|
28
|
+
return nil unless exists?(slot)
|
29
|
+
return find_node_key_of_master(slot) if replica_disabled?
|
30
|
+
|
31
|
+
@map[slot][:slaves].to_a.sample
|
32
|
+
end
|
33
|
+
|
34
|
+
def put(slot, node_key)
|
35
|
+
assign_node_key(@map, slot, node_key)
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def replica_disabled?
|
42
|
+
!@with_replica
|
43
|
+
end
|
44
|
+
|
45
|
+
def master?(node_key)
|
46
|
+
!slave?(node_key)
|
47
|
+
end
|
48
|
+
|
49
|
+
def slave?(node_key)
|
50
|
+
@node_flags[node_key] == ROLE_SLAVE
|
51
|
+
end
|
52
|
+
|
53
|
+
# available_slots is mapping of node_key to list of slot ranges
|
54
|
+
def build_slot_node_key_map(available_slots)
|
55
|
+
available_slots.each_with_object({}) do |(node_key, slots_arr), acc|
|
56
|
+
slots_arr.each do |slots|
|
57
|
+
slots.each { |slot| assign_node_key(acc, slot, node_key) }
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def assign_node_key(mappings, slot, node_key)
|
63
|
+
mappings[slot] ||= { master: nil, slaves: ::Set.new }
|
64
|
+
if master?(node_key)
|
65
|
+
mappings[slot][:master] = node_key
|
66
|
+
else
|
67
|
+
mappings[slot][:slaves].add(node_key)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../errors'
|
4
|
+
require_relative 'node_key'
|
5
|
+
|
6
|
+
class Redis
|
7
|
+
class Cluster
|
8
|
+
# Load and hashify slot info for Redis Cluster Client
|
9
|
+
module SlotLoader
|
10
|
+
module_function
|
11
|
+
|
12
|
+
def load(nodes)
|
13
|
+
info = {}
|
14
|
+
|
15
|
+
nodes.each do |node|
|
16
|
+
info = fetch_slot_info(node)
|
17
|
+
info.empty? ? next : break
|
18
|
+
end
|
19
|
+
|
20
|
+
return info unless info.empty?
|
21
|
+
|
22
|
+
raise CannotConnectError, 'Redis client could not connect to any cluster nodes'
|
23
|
+
end
|
24
|
+
|
25
|
+
def fetch_slot_info(node)
|
26
|
+
hash_with_default_arr = Hash.new { |h, k| h[k] = [] }
|
27
|
+
node.call(%i[cluster slots])
|
28
|
+
.flat_map { |arr| parse_slot_info(arr, default_ip: node.host) }
|
29
|
+
.each_with_object(hash_with_default_arr) { |arr, h| h[arr[0]] << arr[1] }
|
30
|
+
|
31
|
+
rescue CannotConnectError, ConnectionError, CommandError
|
32
|
+
{} # can retry on another node
|
33
|
+
end
|
34
|
+
|
35
|
+
def parse_slot_info(arr, default_ip:)
|
36
|
+
first_slot, last_slot = arr[0..1]
|
37
|
+
slot_range = (first_slot..last_slot).freeze
|
38
|
+
arr[2..-1].map { |addr| [stringify_node_key(addr, default_ip), slot_range] }
|
39
|
+
end
|
40
|
+
|
41
|
+
def stringify_node_key(arr, default_ip)
|
42
|
+
ip, port = arr
|
43
|
+
ip = default_ip if ip.empty? # When cluster is down
|
44
|
+
NodeKey.build_from_host_port(ip, port)
|
45
|
+
end
|
46
|
+
|
47
|
+
private_class_method :fetch_slot_info, :parse_slot_info, :stringify_node_key
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/lib/redis/connection.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative "connection/registry"
|
2
3
|
|
3
4
|
# If a connection driver was required before this file, the array
|
4
5
|
# Redis::Connection.drivers will contain one or more classes. The last driver
|
@@ -6,4 +7,4 @@ require "redis/connection/registry"
|
|
6
7
|
# the plain Ruby driver as our default. Another driver can be required at a
|
7
8
|
# later point in time, causing it to be the last element of the #drivers array
|
8
9
|
# and therefore be chosen by default.
|
9
|
-
|
10
|
+
require_relative "connection/ruby" if Redis::Connection.drivers.empty?
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
class Redis
|
2
3
|
module Connection
|
3
4
|
module CommandHelper
|
@@ -30,14 +31,8 @@ class Redis
|
|
30
31
|
|
31
32
|
protected
|
32
33
|
|
33
|
-
|
34
|
-
|
35
|
-
string.force_encoding(Encoding::default_external)
|
36
|
-
end
|
37
|
-
else
|
38
|
-
def encode(string)
|
39
|
-
string
|
40
|
-
end
|
34
|
+
def encode(string)
|
35
|
+
string.force_encoding(Encoding.default_external)
|
41
36
|
end
|
42
37
|
end
|
43
38
|
end
|
@@ -1,6 +1,7 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative "registry"
|
3
|
+
require_relative "command_helper"
|
4
|
+
require_relative "../errors"
|
4
5
|
require "socket"
|
5
6
|
require "timeout"
|
6
7
|
|
@@ -10,36 +11,17 @@ rescue LoadError
|
|
10
11
|
# Not all systems have OpenSSL support
|
11
12
|
end
|
12
13
|
|
13
|
-
if RUBY_VERSION < "1.9.3"
|
14
|
-
class String
|
15
|
-
# Ruby 1.8.7 does not have byteslice, but it handles encodings differently anyway.
|
16
|
-
# We can simply slice the string, which is a byte array there.
|
17
|
-
def byteslice(*args)
|
18
|
-
slice(*args)
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
14
|
class Redis
|
24
15
|
module Connection
|
25
16
|
module SocketMixin
|
26
17
|
|
27
18
|
CRLF = "\r\n".freeze
|
28
19
|
|
29
|
-
# Exceptions raised during non-blocking I/O ops that require retrying the op
|
30
|
-
if RUBY_VERSION >= "1.9.3"
|
31
|
-
NBIO_READ_EXCEPTIONS = [IO::WaitReadable]
|
32
|
-
NBIO_WRITE_EXCEPTIONS = [IO::WaitWritable]
|
33
|
-
else
|
34
|
-
NBIO_READ_EXCEPTIONS = [Errno::EWOULDBLOCK, Errno::EAGAIN]
|
35
|
-
NBIO_WRITE_EXCEPTIONS = [Errno::EWOULDBLOCK, Errno::EAGAIN]
|
36
|
-
end
|
37
|
-
|
38
20
|
def initialize(*args)
|
39
21
|
super(*args)
|
40
22
|
|
41
23
|
@timeout = @write_timeout = nil
|
42
|
-
@buffer = ""
|
24
|
+
@buffer = "".dup
|
43
25
|
end
|
44
26
|
|
45
27
|
def timeout=(timeout)
|
@@ -72,7 +54,7 @@ class Redis
|
|
72
54
|
crlf = nil
|
73
55
|
|
74
56
|
while (crlf = @buffer.index(CRLF)) == nil
|
75
|
-
@buffer << _read_from_socket(
|
57
|
+
@buffer << _read_from_socket(16384)
|
76
58
|
end
|
77
59
|
|
78
60
|
@buffer.slice!(0, crlf + CRLF.bytesize)
|
@@ -83,13 +65,13 @@ class Redis
|
|
83
65
|
begin
|
84
66
|
read_nonblock(nbytes)
|
85
67
|
|
86
|
-
rescue
|
68
|
+
rescue IO::WaitReadable
|
87
69
|
if IO.select([self], nil, nil, @timeout)
|
88
70
|
retry
|
89
71
|
else
|
90
72
|
raise Redis::TimeoutError
|
91
73
|
end
|
92
|
-
rescue
|
74
|
+
rescue IO::WaitWritable
|
93
75
|
if IO.select(nil, [self], nil, @timeout)
|
94
76
|
retry
|
95
77
|
else
|
@@ -105,13 +87,13 @@ class Redis
|
|
105
87
|
begin
|
106
88
|
write_nonblock(data)
|
107
89
|
|
108
|
-
rescue
|
90
|
+
rescue IO::WaitWritable
|
109
91
|
if IO.select(nil, [self], nil, @write_timeout)
|
110
92
|
retry
|
111
93
|
else
|
112
94
|
raise Redis::TimeoutError
|
113
95
|
end
|
114
|
-
rescue
|
96
|
+
rescue IO::WaitReadable
|
115
97
|
if IO.select([self], nil, nil, @write_timeout)
|
116
98
|
retry
|
117
99
|
else
|
@@ -285,8 +267,32 @@ class Redis
|
|
285
267
|
|
286
268
|
ssl_sock = new(tcp_sock, ctx)
|
287
269
|
ssl_sock.hostname = host
|
288
|
-
|
289
|
-
|
270
|
+
|
271
|
+
begin
|
272
|
+
# Initiate the socket connection in the background. If it doesn't fail
|
273
|
+
# immediately it will raise an IO::WaitWritable (Errno::EINPROGRESS)
|
274
|
+
# indicating the connection is in progress.
|
275
|
+
# Unlike waiting for a tcp socket to connect, you can't time out ssl socket
|
276
|
+
# connections during the connect phase properly, because IO.select only partially works.
|
277
|
+
# Instead, you have to retry.
|
278
|
+
ssl_sock.connect_nonblock
|
279
|
+
rescue Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitReadable
|
280
|
+
if IO.select([ssl_sock], nil, nil, timeout)
|
281
|
+
retry
|
282
|
+
else
|
283
|
+
raise TimeoutError
|
284
|
+
end
|
285
|
+
rescue IO::WaitWritable
|
286
|
+
if IO.select(nil, [ssl_sock], nil, timeout)
|
287
|
+
retry
|
288
|
+
else
|
289
|
+
raise TimeoutError
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
unless ctx.verify_mode == OpenSSL::SSL::VERIFY_NONE || (ctx.respond_to?(:verify_hostname) && !ctx.verify_hostname)
|
294
|
+
ssl_sock.post_connection_check(host)
|
295
|
+
end
|
290
296
|
|
291
297
|
ssl_sock
|
292
298
|
end
|
@@ -307,16 +313,16 @@ class Redis
|
|
307
313
|
raise ArgumentError, "SSL incompatible with unix sockets" if config[:ssl]
|
308
314
|
sock = UNIXSocket.connect(config[:path], config[:connect_timeout])
|
309
315
|
elsif config[:scheme] == "rediss" || config[:ssl]
|
310
|
-
raise ArgumentError, "This library does not support SSL on Ruby < 1.9" if RUBY_VERSION < "1.9.3"
|
311
316
|
sock = SSLSocket.connect(config[:host], config[:port], config[:connect_timeout], config[:ssl_params])
|
312
317
|
else
|
313
318
|
sock = TCPSocket.connect(config[:host], config[:port], config[:connect_timeout])
|
314
319
|
end
|
315
320
|
|
316
321
|
instance = new(sock)
|
317
|
-
instance.timeout = config[:
|
322
|
+
instance.timeout = config[:read_timeout]
|
318
323
|
instance.write_timeout = config[:write_timeout]
|
319
324
|
instance.set_tcp_keepalive config[:tcp_keepalive]
|
325
|
+
instance.set_tcp_nodelay if sock.is_a? TCPSocket
|
320
326
|
instance
|
321
327
|
end
|
322
328
|
|
@@ -347,6 +353,16 @@ class Redis
|
|
347
353
|
end
|
348
354
|
end
|
349
355
|
|
356
|
+
# disables Nagle's Algorithm, prevents multiple round trips with MULTI
|
357
|
+
if [:IPPROTO_TCP, :TCP_NODELAY].all?{|c| Socket.const_defined? c}
|
358
|
+
def set_tcp_nodelay
|
359
|
+
@sock.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
360
|
+
end
|
361
|
+
else
|
362
|
+
def set_tcp_nodelay
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
350
366
|
def initialize(sock)
|
351
367
|
@sock = sock
|
352
368
|
end
|
@@ -1,6 +1,7 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative "command_helper"
|
3
|
+
require_relative "registry"
|
4
|
+
require_relative "../errors"
|
4
5
|
require "em-synchrony"
|
5
6
|
require "hiredis/reader"
|
6
7
|
|
@@ -72,7 +73,15 @@ class Redis
|
|
72
73
|
|
73
74
|
def self.connect(config)
|
74
75
|
if config[:scheme] == "unix"
|
75
|
-
|
76
|
+
begin
|
77
|
+
conn = EventMachine.connect_unix_domain(config[:path], RedisClient)
|
78
|
+
rescue RuntimeError => e
|
79
|
+
if e.message == "no connection"
|
80
|
+
raise Errno::ECONNREFUSED
|
81
|
+
else
|
82
|
+
raise e
|
83
|
+
end
|
84
|
+
end
|
76
85
|
elsif config[:scheme] == "rediss" || config[:ssl]
|
77
86
|
raise NotImplementedError, "SSL not supported by synchrony driver"
|
78
87
|
else
|
data/lib/redis/distributed.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative "hash_ring"
|
2
3
|
|
3
4
|
class Redis
|
4
5
|
class Distributed
|
@@ -144,8 +145,8 @@ class Redis
|
|
144
145
|
end
|
145
146
|
|
146
147
|
# Create a key using the serialized value, previously obtained using DUMP.
|
147
|
-
def restore(key, ttl, serialized_value)
|
148
|
-
node_for(key).restore(key, ttl, serialized_value)
|
148
|
+
def restore(key, ttl, serialized_value, options = {})
|
149
|
+
node_for(key).restore(key, ttl, serialized_value, options)
|
149
150
|
end
|
150
151
|
|
151
152
|
# Transfer a key from the connected instance to another instance.
|
@@ -161,6 +162,14 @@ class Redis
|
|
161
162
|
end
|
162
163
|
end
|
163
164
|
|
165
|
+
# Unlink keys.
|
166
|
+
def unlink(*args)
|
167
|
+
keys_per_node = args.group_by { |key| node_for(key) }
|
168
|
+
keys_per_node.inject(0) do |sum, (node, keys)|
|
169
|
+
sum + node.unlink(*keys)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
164
173
|
# Determine if a key exists.
|
165
174
|
def exists(key)
|
166
175
|
node_for(key).exists(key)
|
@@ -277,13 +286,16 @@ class Redis
|
|
277
286
|
node_for(key).get(key)
|
278
287
|
end
|
279
288
|
|
280
|
-
# Get the values of all the given keys.
|
289
|
+
# Get the values of all the given keys as an Array.
|
281
290
|
def mget(*keys)
|
282
|
-
|
291
|
+
mapped_mget(*keys).values_at(*keys)
|
283
292
|
end
|
284
293
|
|
294
|
+
# Get the values of all the given keys as a Hash.
|
285
295
|
def mapped_mget(*keys)
|
286
|
-
|
296
|
+
keys.group_by { |k| node_for k }.inject({}) do |results, (node, subkeys)|
|
297
|
+
results.merge! node.mapped_mget(*subkeys)
|
298
|
+
end
|
287
299
|
end
|
288
300
|
|
289
301
|
# Overwrite part of a string at key starting at the specified offset.
|
@@ -390,14 +402,12 @@ class Redis
|
|
390
402
|
end
|
391
403
|
|
392
404
|
def _bpop(cmd, args)
|
393
|
-
|
394
|
-
|
395
|
-
case args.last
|
396
|
-
when Hash
|
405
|
+
timeout = if args.last.is_a?(Hash)
|
397
406
|
options = args.pop
|
398
|
-
|
407
|
+
options[:timeout]
|
408
|
+
elsif args.last.respond_to?(:to_int)
|
399
409
|
# Issue deprecation notice in obnoxious mode...
|
400
|
-
|
410
|
+
args.pop.to_int
|
401
411
|
end
|
402
412
|
|
403
413
|
if args.size > 1
|
@@ -407,7 +417,11 @@ class Redis
|
|
407
417
|
keys = args.flatten
|
408
418
|
|
409
419
|
ensure_same_node(cmd, keys) do |node|
|
410
|
-
|
420
|
+
if timeout
|
421
|
+
node.__send__(cmd, keys, timeout: timeout)
|
422
|
+
else
|
423
|
+
node.__send__(cmd, keys)
|
424
|
+
end
|
411
425
|
end
|
412
426
|
end
|
413
427
|
|
@@ -509,6 +523,16 @@ class Redis
|
|
509
523
|
node_for(key).smembers(key)
|
510
524
|
end
|
511
525
|
|
526
|
+
# Scan a set
|
527
|
+
def sscan(key, cursor, options={})
|
528
|
+
node_for(key).sscan(key, cursor, options)
|
529
|
+
end
|
530
|
+
|
531
|
+
# Scan a set and return an enumerator
|
532
|
+
def sscan_each(key, options={}, &block)
|
533
|
+
node_for(key).sscan_each(key, options, &block)
|
534
|
+
end
|
535
|
+
|
512
536
|
# Subtract multiple sets.
|
513
537
|
def sdiff(*keys)
|
514
538
|
ensure_same_node(:sdiff, keys) do |node|
|
@@ -679,8 +703,8 @@ class Redis
|
|
679
703
|
end
|
680
704
|
|
681
705
|
# Delete one or more hash fields.
|
682
|
-
def hdel(key,
|
683
|
-
node_for(key).hdel(key,
|
706
|
+
def hdel(key, *fields)
|
707
|
+
node_for(key).hdel(key, *fields)
|
684
708
|
end
|
685
709
|
|
686
710
|
# Determine if a hash field exists.
|