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.
Files changed (101) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +143 -3
  3. data/README.md +127 -18
  4. data/lib/redis/client.rb +150 -93
  5. data/lib/redis/cluster/command.rb +81 -0
  6. data/lib/redis/cluster/command_loader.rb +34 -0
  7. data/lib/redis/cluster/key_slot_converter.rb +72 -0
  8. data/lib/redis/cluster/node.rb +108 -0
  9. data/lib/redis/cluster/node_key.rb +31 -0
  10. data/lib/redis/cluster/node_loader.rb +37 -0
  11. data/lib/redis/cluster/option.rb +93 -0
  12. data/lib/redis/cluster/slot.rb +86 -0
  13. data/lib/redis/cluster/slot_loader.rb +49 -0
  14. data/lib/redis/cluster.rb +291 -0
  15. data/lib/redis/connection/command_helper.rb +3 -2
  16. data/lib/redis/connection/hiredis.rb +4 -3
  17. data/lib/redis/connection/registry.rb +2 -1
  18. data/lib/redis/connection/ruby.rb +123 -105
  19. data/lib/redis/connection/synchrony.rb +18 -5
  20. data/lib/redis/connection.rb +2 -0
  21. data/lib/redis/distributed.rb +955 -0
  22. data/lib/redis/errors.rb +48 -0
  23. data/lib/redis/hash_ring.rb +89 -0
  24. data/lib/redis/pipeline.rb +55 -9
  25. data/lib/redis/subscribe.rb +11 -12
  26. data/lib/redis/version.rb +3 -1
  27. data/lib/redis.rb +1242 -381
  28. metadata +34 -141
  29. data/.gitignore +0 -16
  30. data/.travis/Gemfile +0 -11
  31. data/.travis.yml +0 -71
  32. data/.yardopts +0 -3
  33. data/Gemfile +0 -3
  34. data/benchmarking/logging.rb +0 -71
  35. data/benchmarking/pipeline.rb +0 -51
  36. data/benchmarking/speed.rb +0 -21
  37. data/benchmarking/suite.rb +0 -24
  38. data/benchmarking/worker.rb +0 -71
  39. data/examples/basic.rb +0 -15
  40. data/examples/consistency.rb +0 -114
  41. data/examples/incr-decr.rb +0 -17
  42. data/examples/list.rb +0 -26
  43. data/examples/pubsub.rb +0 -37
  44. data/examples/sentinel/sentinel.conf +0 -9
  45. data/examples/sentinel/start +0 -49
  46. data/examples/sentinel.rb +0 -41
  47. data/examples/sets.rb +0 -36
  48. data/examples/unicorn/config.ru +0 -3
  49. data/examples/unicorn/unicorn.rb +0 -20
  50. data/makefile +0 -42
  51. data/redis.gemspec +0 -40
  52. data/test/bitpos_test.rb +0 -63
  53. data/test/blocking_commands_test.rb +0 -183
  54. data/test/client_test.rb +0 -59
  55. data/test/command_map_test.rb +0 -28
  56. data/test/commands_on_hashes_test.rb +0 -174
  57. data/test/commands_on_hyper_log_log_test.rb +0 -70
  58. data/test/commands_on_lists_test.rb +0 -154
  59. data/test/commands_on_sets_test.rb +0 -208
  60. data/test/commands_on_sorted_sets_test.rb +0 -444
  61. data/test/commands_on_strings_test.rb +0 -338
  62. data/test/commands_on_value_types_test.rb +0 -246
  63. data/test/connection_handling_test.rb +0 -275
  64. data/test/db/.gitkeep +0 -0
  65. data/test/encoding_test.rb +0 -14
  66. data/test/error_replies_test.rb +0 -57
  67. data/test/fork_safety_test.rb +0 -60
  68. data/test/helper.rb +0 -179
  69. data/test/helper_test.rb +0 -22
  70. data/test/internals_test.rb +0 -435
  71. data/test/persistence_control_commands_test.rb +0 -24
  72. data/test/pipelining_commands_test.rb +0 -238
  73. data/test/publish_subscribe_test.rb +0 -280
  74. data/test/remote_server_control_commands_test.rb +0 -175
  75. data/test/scanning_test.rb +0 -407
  76. data/test/scripting_test.rb +0 -76
  77. data/test/sentinel_command_test.rb +0 -78
  78. data/test/sentinel_test.rb +0 -253
  79. data/test/sorting_test.rb +0 -57
  80. data/test/ssl_test.rb +0 -69
  81. data/test/support/connection/hiredis.rb +0 -1
  82. data/test/support/connection/ruby.rb +0 -1
  83. data/test/support/connection/synchrony.rb +0 -17
  84. data/test/support/redis_mock.rb +0 -130
  85. data/test/support/ssl/gen_certs.sh +0 -31
  86. data/test/support/ssl/trusted-ca.crt +0 -25
  87. data/test/support/ssl/trusted-ca.key +0 -27
  88. data/test/support/ssl/trusted-cert.crt +0 -81
  89. data/test/support/ssl/trusted-cert.key +0 -28
  90. data/test/support/ssl/untrusted-ca.crt +0 -26
  91. data/test/support/ssl/untrusted-ca.key +0 -27
  92. data/test/support/ssl/untrusted-cert.crt +0 -82
  93. data/test/support/ssl/untrusted-cert.key +0 -28
  94. data/test/support/wire/synchrony.rb +0 -24
  95. data/test/support/wire/thread.rb +0 -5
  96. data/test/synchrony_driver.rb +0 -85
  97. data/test/test.conf.erb +0 -9
  98. data/test/thread_safety_test.rb +0 -60
  99. data/test/transactions_test.rb +0 -262
  100. data/test/unknown_commands_test.rb +0 -12
  101. 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
@@ -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 call(command, &block)
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 { |f| f._command }
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
- raise replies.detect { |r| r.is_a?(CommandError) }
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
- [[:multi]] + super + [[:exec]]
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
- def initialize(command, transformation)
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.kind_of?(::RuntimeError)
169
+ ::Kernel.raise(@object) if @object.is_a?(::RuntimeError)
124
170
  @object
125
171
  end
126
172
 
@@ -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
- protected
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
- begin
43
- @client.call_loop([start, *channels], timeout) do |line|
44
- type, *rest = line
45
- sub.callbacks[type].call(*rest)
46
- unsubscribed = type == stop && rest.last == 0
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] = lambda { |*_| }
60
+ hash[key] = ->(*_) {}
62
61
  end
63
62
 
64
63
  yield(self)
data/lib/redis/version.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Redis
2
- VERSION = "4.0.0.rc1"
4
+ VERSION = '4.4.0'
3
5
  end