redis 4.0.0.rc1 → 4.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (101) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +164 -3
  3. data/README.md +127 -18
  4. data/lib/redis/client.rb +164 -93
  5. data/lib/redis/cluster/command.rb +81 -0
  6. data/lib/redis/cluster/command_loader.rb +33 -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 +5 -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 +125 -106
  19. data/lib/redis/connection/synchrony.rb +18 -5
  20. data/lib/redis/connection.rb +2 -0
  21. data/lib/redis/distributed.rb +995 -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 +1433 -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.5.1'
3
5
  end