redis 4.0.0.rc1 → 4.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.
- checksums.yaml +5 -5
- data/CHANGELOG.md +143 -3
- data/README.md +127 -18
- data/lib/redis/client.rb +150 -93
- 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 +108 -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 +93 -0
- data/lib/redis/cluster/slot.rb +86 -0
- data/lib/redis/cluster/slot_loader.rb +49 -0
- data/lib/redis/cluster.rb +291 -0
- data/lib/redis/connection/command_helper.rb +3 -2
- data/lib/redis/connection/hiredis.rb +4 -3
- data/lib/redis/connection/registry.rb +2 -1
- data/lib/redis/connection/ruby.rb +123 -105
- data/lib/redis/connection/synchrony.rb +18 -5
- data/lib/redis/connection.rb +2 -0
- data/lib/redis/distributed.rb +955 -0
- data/lib/redis/errors.rb +48 -0
- data/lib/redis/hash_ring.rb +89 -0
- data/lib/redis/pipeline.rb +55 -9
- data/lib/redis/subscribe.rb +11 -12
- data/lib/redis/version.rb +3 -1
- data/lib/redis.rb +1242 -381
- metadata +34 -141
- data/.gitignore +0 -16
- data/.travis/Gemfile +0 -11
- data/.travis.yml +0 -71
- data/.yardopts +0 -3
- data/Gemfile +0 -3
- 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/incr-decr.rb +0 -17
- data/examples/list.rb +0 -26
- data/examples/pubsub.rb +0 -37
- data/examples/sentinel/sentinel.conf +0 -9
- data/examples/sentinel/start +0 -49
- data/examples/sentinel.rb +0 -41
- data/examples/sets.rb +0 -36
- data/examples/unicorn/config.ru +0 -3
- data/examples/unicorn/unicorn.rb +0 -20
- data/makefile +0 -42
- data/redis.gemspec +0 -40
- data/test/bitpos_test.rb +0 -63
- data/test/blocking_commands_test.rb +0 -183
- data/test/client_test.rb +0 -59
- data/test/command_map_test.rb +0 -28
- data/test/commands_on_hashes_test.rb +0 -174
- data/test/commands_on_hyper_log_log_test.rb +0 -70
- data/test/commands_on_lists_test.rb +0 -154
- data/test/commands_on_sets_test.rb +0 -208
- data/test/commands_on_sorted_sets_test.rb +0 -444
- data/test/commands_on_strings_test.rb +0 -338
- data/test/commands_on_value_types_test.rb +0 -246
- data/test/connection_handling_test.rb +0 -275
- data/test/db/.gitkeep +0 -0
- data/test/encoding_test.rb +0 -14
- data/test/error_replies_test.rb +0 -57
- data/test/fork_safety_test.rb +0 -60
- data/test/helper.rb +0 -179
- data/test/helper_test.rb +0 -22
- data/test/internals_test.rb +0 -435
- data/test/persistence_control_commands_test.rb +0 -24
- data/test/pipelining_commands_test.rb +0 -238
- data/test/publish_subscribe_test.rb +0 -280
- data/test/remote_server_control_commands_test.rb +0 -175
- data/test/scanning_test.rb +0 -407
- data/test/scripting_test.rb +0 -76
- data/test/sentinel_command_test.rb +0 -78
- data/test/sentinel_test.rb +0 -253
- data/test/sorting_test.rb +0 -57
- data/test/ssl_test.rb +0 -69
- 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 -85
- data/test/test.conf.erb +0 -9
- data/test/thread_safety_test.rb +0 -60
- data/test/transactions_test.rb +0 -262
- data/test/unknown_commands_test.rb +0 -12
- data/test/url_param_test.rb +0 -136
data/lib/redis/errors.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class Redis
|
2
4
|
# Base error for all redis-rb errors.
|
3
5
|
class BaseError < RuntimeError
|
@@ -37,4 +39,50 @@ class Redis
|
|
37
39
|
# Raised when the connection was inherited by a child process.
|
38
40
|
class InheritedError < BaseConnectionError
|
39
41
|
end
|
42
|
+
|
43
|
+
# Raised when client options are invalid.
|
44
|
+
class InvalidClientOptionError < BaseError
|
45
|
+
end
|
46
|
+
|
47
|
+
class Cluster
|
48
|
+
# Raised when client connected to redis as cluster mode
|
49
|
+
# and some cluster subcommands were called.
|
50
|
+
class OrchestrationCommandNotSupported < BaseError
|
51
|
+
def initialize(command, subcommand = '')
|
52
|
+
str = [command, subcommand].map(&:to_s).reject(&:empty?).join(' ').upcase
|
53
|
+
msg = "#{str} command should be used with care "\
|
54
|
+
'only by applications orchestrating Redis Cluster, like redis-trib, '\
|
55
|
+
'and the command if used out of the right context can leave the cluster '\
|
56
|
+
'in a wrong state or cause data loss.'
|
57
|
+
super(msg)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Raised when error occurs on any node of cluster.
|
62
|
+
class CommandErrorCollection < BaseError
|
63
|
+
attr_reader :errors
|
64
|
+
|
65
|
+
# @param errors [Hash{String => Redis::CommandError}]
|
66
|
+
# @param error_message [String]
|
67
|
+
def initialize(errors, error_message = 'Command errors were replied on any node')
|
68
|
+
@errors = errors
|
69
|
+
super(error_message)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Raised when cluster client can't select node.
|
74
|
+
class AmbiguousNodeError < BaseError
|
75
|
+
def initialize(command)
|
76
|
+
super("Cluster client doesn't know which node the #{command} command should be sent to.")
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# Raised when commands in pipelining include cross slot keys.
|
81
|
+
class CrossSlotPipeliningError < BaseError
|
82
|
+
def initialize(keys)
|
83
|
+
super("Cluster client couldn't send pipelining to single node. "\
|
84
|
+
"The commands include cross slot keys. #{keys}")
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
40
88
|
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'zlib'
|
4
|
+
|
5
|
+
class Redis
|
6
|
+
class HashRing
|
7
|
+
POINTS_PER_SERVER = 160 # this is the default in libmemcached
|
8
|
+
|
9
|
+
attr_reader :ring, :sorted_keys, :replicas, :nodes
|
10
|
+
|
11
|
+
# nodes is a list of objects that have a proper to_s representation.
|
12
|
+
# replicas indicates how many virtual points should be used pr. node,
|
13
|
+
# replicas are required to improve the distribution.
|
14
|
+
def initialize(nodes = [], replicas = POINTS_PER_SERVER)
|
15
|
+
@replicas = replicas
|
16
|
+
@ring = {}
|
17
|
+
@nodes = []
|
18
|
+
@sorted_keys = []
|
19
|
+
nodes.each do |node|
|
20
|
+
add_node(node)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Adds a `node` to the hash ring (including a number of replicas).
|
25
|
+
def add_node(node)
|
26
|
+
@nodes << node
|
27
|
+
@replicas.times do |i|
|
28
|
+
key = Zlib.crc32("#{node.id}:#{i}")
|
29
|
+
@ring[key] = node
|
30
|
+
@sorted_keys << key
|
31
|
+
end
|
32
|
+
@sorted_keys.sort!
|
33
|
+
end
|
34
|
+
|
35
|
+
def remove_node(node)
|
36
|
+
@nodes.reject! { |n| n.id == node.id }
|
37
|
+
@replicas.times do |i|
|
38
|
+
key = Zlib.crc32("#{node.id}:#{i}")
|
39
|
+
@ring.delete(key)
|
40
|
+
@sorted_keys.reject! { |k| k == key }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# get the node in the hash ring for this key
|
45
|
+
def get_node(key)
|
46
|
+
get_node_pos(key)[0]
|
47
|
+
end
|
48
|
+
|
49
|
+
def get_node_pos(key)
|
50
|
+
return [nil, nil] if @ring.empty?
|
51
|
+
|
52
|
+
crc = Zlib.crc32(key)
|
53
|
+
idx = HashRing.binary_search(@sorted_keys, crc)
|
54
|
+
[@ring[@sorted_keys[idx]], idx]
|
55
|
+
end
|
56
|
+
|
57
|
+
def iter_nodes(key)
|
58
|
+
return [nil, nil] if @ring.empty?
|
59
|
+
|
60
|
+
_, pos = get_node_pos(key)
|
61
|
+
@ring.size.times do |n|
|
62
|
+
yield @ring[@sorted_keys[(pos + n) % @ring.size]]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Find the closest index in HashRing with value <= the given value
|
67
|
+
def self.binary_search(ary, value)
|
68
|
+
upper = ary.size - 1
|
69
|
+
lower = 0
|
70
|
+
idx = 0
|
71
|
+
|
72
|
+
while lower <= upper
|
73
|
+
idx = (lower + upper) / 2
|
74
|
+
comp = ary[idx] <=> value
|
75
|
+
|
76
|
+
if comp == 0
|
77
|
+
return idx
|
78
|
+
elsif comp > 0
|
79
|
+
upper = idx - 1
|
80
|
+
else
|
81
|
+
lower = idx + 1
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
upper = ary.size - 1 if upper < 0
|
86
|
+
upper
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
data/lib/redis/pipeline.rb
CHANGED
@@ -1,15 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class Redis
|
2
4
|
class Pipeline
|
3
5
|
attr_accessor :db
|
6
|
+
attr_reader :client
|
4
7
|
|
5
8
|
attr :futures
|
6
9
|
|
7
|
-
def initialize
|
10
|
+
def initialize(client)
|
11
|
+
@client = client.is_a?(Pipeline) ? client.client : client
|
8
12
|
@with_reconnect = true
|
9
13
|
@shutdown = false
|
10
14
|
@futures = []
|
11
15
|
end
|
12
16
|
|
17
|
+
def timeout
|
18
|
+
client.timeout
|
19
|
+
end
|
20
|
+
|
13
21
|
def with_reconnect?
|
14
22
|
@with_reconnect
|
15
23
|
end
|
@@ -22,15 +30,23 @@ class Redis
|
|
22
30
|
@shutdown
|
23
31
|
end
|
24
32
|
|
25
|
-
def
|
33
|
+
def empty?
|
34
|
+
@futures.empty?
|
35
|
+
end
|
36
|
+
|
37
|
+
def call(command, timeout: nil, &block)
|
26
38
|
# A pipeline that contains a shutdown should not raise ECONNRESET when
|
27
39
|
# the connection is gone.
|
28
40
|
@shutdown = true if command.first == :shutdown
|
29
|
-
future = Future.new(command, block)
|
41
|
+
future = Future.new(command, block, timeout)
|
30
42
|
@futures << future
|
31
43
|
future
|
32
44
|
end
|
33
45
|
|
46
|
+
def call_with_timeout(command, timeout, &block)
|
47
|
+
call(command, timeout: timeout, &block)
|
48
|
+
end
|
49
|
+
|
34
50
|
def call_pipeline(pipeline)
|
35
51
|
@shutdown = true if pipeline.shutdown?
|
36
52
|
@futures.concat(pipeline.futures)
|
@@ -39,10 +55,14 @@ class Redis
|
|
39
55
|
end
|
40
56
|
|
41
57
|
def commands
|
42
|
-
@futures.map
|
58
|
+
@futures.map(&:_command)
|
59
|
+
end
|
60
|
+
|
61
|
+
def timeouts
|
62
|
+
@futures.map(&:timeout)
|
43
63
|
end
|
44
64
|
|
45
|
-
def with_reconnect(val=true)
|
65
|
+
def with_reconnect(val = true)
|
46
66
|
@with_reconnect = false unless val
|
47
67
|
yield
|
48
68
|
end
|
@@ -74,7 +94,8 @@ class Redis
|
|
74
94
|
|
75
95
|
if exec.size < futures.size
|
76
96
|
# Some command wasn't recognized by Redis.
|
77
|
-
|
97
|
+
command_error = replies.detect { |r| r.is_a?(CommandError) }
|
98
|
+
raise command_error
|
78
99
|
end
|
79
100
|
|
80
101
|
super(exec) do |reply|
|
@@ -85,8 +106,20 @@ class Redis
|
|
85
106
|
end
|
86
107
|
end
|
87
108
|
|
109
|
+
def timeouts
|
110
|
+
if empty?
|
111
|
+
[]
|
112
|
+
else
|
113
|
+
[nil, *super, nil]
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
88
117
|
def commands
|
89
|
-
|
118
|
+
if empty?
|
119
|
+
[]
|
120
|
+
else
|
121
|
+
[[:multi]] + super + [[:exec]]
|
122
|
+
end
|
90
123
|
end
|
91
124
|
end
|
92
125
|
end
|
@@ -100,12 +133,25 @@ class Redis
|
|
100
133
|
class Future < BasicObject
|
101
134
|
FutureNotReady = ::Redis::FutureNotReady.new
|
102
135
|
|
103
|
-
|
136
|
+
attr_reader :timeout
|
137
|
+
|
138
|
+
def initialize(command, transformation, timeout)
|
104
139
|
@command = command
|
105
140
|
@transformation = transformation
|
141
|
+
@timeout = timeout
|
106
142
|
@object = FutureNotReady
|
107
143
|
end
|
108
144
|
|
145
|
+
def ==(_other)
|
146
|
+
message = +"The methods == and != are deprecated for Redis::Future and will be removed in 4.2.0"
|
147
|
+
message << " - You probably meant to call .value == or .value !="
|
148
|
+
message << " (#{::Kernel.caller(1, 1).first})\n"
|
149
|
+
|
150
|
+
::Kernel.warn(message)
|
151
|
+
|
152
|
+
super
|
153
|
+
end
|
154
|
+
|
109
155
|
def inspect
|
110
156
|
"<Redis::Future #{@command.inspect}>"
|
111
157
|
end
|
@@ -120,7 +166,7 @@ class Redis
|
|
120
166
|
end
|
121
167
|
|
122
168
|
def value
|
123
|
-
::Kernel.raise(@object) if @object.
|
169
|
+
::Kernel.raise(@object) if @object.is_a?(::RuntimeError)
|
124
170
|
@object
|
125
171
|
end
|
126
172
|
|
data/lib/redis/subscribe.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class Redis
|
2
4
|
class SubscribedClient
|
3
5
|
def initialize(client)
|
@@ -32,24 +34,21 @@ class Redis
|
|
32
34
|
call([:punsubscribe, *channels])
|
33
35
|
end
|
34
36
|
|
35
|
-
|
37
|
+
protected
|
36
38
|
|
37
39
|
def subscription(start, stop, channels, block, timeout = 0)
|
38
40
|
sub = Subscription.new(&block)
|
39
41
|
|
40
42
|
unsubscribed = false
|
41
43
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
break if unsubscribed
|
48
|
-
end
|
49
|
-
ensure
|
50
|
-
# No need to unsubscribe here. The real client closes the connection
|
51
|
-
# whenever an exception is raised (see #ensure_connected).
|
44
|
+
@client.call_loop([start, *channels], timeout) do |line|
|
45
|
+
type, *rest = line
|
46
|
+
sub.callbacks[type].call(*rest)
|
47
|
+
unsubscribed = type == stop && rest.last == 0
|
48
|
+
break if unsubscribed
|
52
49
|
end
|
50
|
+
# No need to unsubscribe here. The real client closes the connection
|
51
|
+
# whenever an exception is raised (see #ensure_connected).
|
53
52
|
end
|
54
53
|
end
|
55
54
|
|
@@ -58,7 +57,7 @@ class Redis
|
|
58
57
|
|
59
58
|
def initialize
|
60
59
|
@callbacks = Hash.new do |hash, key|
|
61
|
-
hash[key] =
|
60
|
+
hash[key] = ->(*_) {}
|
62
61
|
end
|
63
62
|
|
64
63
|
yield(self)
|
data/lib/redis/version.rb
CHANGED