redis 3.0.0 → 4.5.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 (106) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +315 -0
  3. data/README.md +301 -58
  4. data/lib/redis/client.rb +383 -88
  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 +7 -10
  16. data/lib/redis/connection/hiredis.rb +12 -8
  17. data/lib/redis/connection/registry.rb +2 -1
  18. data/lib/redis/connection/ruby.rb +266 -74
  19. data/lib/redis/connection/synchrony.rb +41 -14
  20. data/lib/redis/connection.rb +4 -2
  21. data/lib/redis/distributed.rb +258 -76
  22. data/lib/redis/errors.rb +48 -0
  23. data/lib/redis/hash_ring.rb +31 -73
  24. data/lib/redis/pipeline.rb +74 -18
  25. data/lib/redis/subscribe.rb +24 -13
  26. data/lib/redis/version.rb +3 -1
  27. data/lib/redis.rb +2068 -464
  28. metadata +63 -160
  29. data/.gitignore +0 -10
  30. data/.order +0 -169
  31. data/.travis/Gemfile +0 -11
  32. data/.travis.yml +0 -50
  33. data/.yardopts +0 -3
  34. data/Rakefile +0 -392
  35. data/benchmarking/logging.rb +0 -62
  36. data/benchmarking/pipeline.rb +0 -51
  37. data/benchmarking/speed.rb +0 -21
  38. data/benchmarking/suite.rb +0 -24
  39. data/benchmarking/worker.rb +0 -71
  40. data/examples/basic.rb +0 -15
  41. data/examples/dist_redis.rb +0 -43
  42. data/examples/incr-decr.rb +0 -17
  43. data/examples/list.rb +0 -26
  44. data/examples/pubsub.rb +0 -31
  45. data/examples/sets.rb +0 -36
  46. data/examples/unicorn/config.ru +0 -3
  47. data/examples/unicorn/unicorn.rb +0 -20
  48. data/redis.gemspec +0 -41
  49. data/test/blocking_commands_test.rb +0 -42
  50. data/test/command_map_test.rb +0 -30
  51. data/test/commands_on_hashes_test.rb +0 -21
  52. data/test/commands_on_lists_test.rb +0 -20
  53. data/test/commands_on_sets_test.rb +0 -77
  54. data/test/commands_on_sorted_sets_test.rb +0 -109
  55. data/test/commands_on_strings_test.rb +0 -83
  56. data/test/commands_on_value_types_test.rb +0 -99
  57. data/test/connection_handling_test.rb +0 -189
  58. data/test/db/.gitignore +0 -1
  59. data/test/distributed_blocking_commands_test.rb +0 -46
  60. data/test/distributed_commands_on_hashes_test.rb +0 -10
  61. data/test/distributed_commands_on_lists_test.rb +0 -22
  62. data/test/distributed_commands_on_sets_test.rb +0 -83
  63. data/test/distributed_commands_on_sorted_sets_test.rb +0 -18
  64. data/test/distributed_commands_on_strings_test.rb +0 -48
  65. data/test/distributed_commands_on_value_types_test.rb +0 -87
  66. data/test/distributed_commands_requiring_clustering_test.rb +0 -148
  67. data/test/distributed_connection_handling_test.rb +0 -23
  68. data/test/distributed_internals_test.rb +0 -15
  69. data/test/distributed_key_tags_test.rb +0 -52
  70. data/test/distributed_persistence_control_commands_test.rb +0 -26
  71. data/test/distributed_publish_subscribe_test.rb +0 -92
  72. data/test/distributed_remote_server_control_commands_test.rb +0 -53
  73. data/test/distributed_scripting_test.rb +0 -102
  74. data/test/distributed_sorting_test.rb +0 -20
  75. data/test/distributed_test.rb +0 -58
  76. data/test/distributed_transactions_test.rb +0 -32
  77. data/test/encoding_test.rb +0 -18
  78. data/test/error_replies_test.rb +0 -59
  79. data/test/helper.rb +0 -188
  80. data/test/helper_test.rb +0 -22
  81. data/test/internals_test.rb +0 -214
  82. data/test/lint/blocking_commands.rb +0 -124
  83. data/test/lint/hashes.rb +0 -162
  84. data/test/lint/lists.rb +0 -143
  85. data/test/lint/sets.rb +0 -96
  86. data/test/lint/sorted_sets.rb +0 -201
  87. data/test/lint/strings.rb +0 -157
  88. data/test/lint/value_types.rb +0 -106
  89. data/test/persistence_control_commands_test.rb +0 -26
  90. data/test/pipelining_commands_test.rb +0 -195
  91. data/test/publish_subscribe_test.rb +0 -153
  92. data/test/remote_server_control_commands_test.rb +0 -104
  93. data/test/scripting_test.rb +0 -78
  94. data/test/sorting_test.rb +0 -45
  95. data/test/support/connection/hiredis.rb +0 -1
  96. data/test/support/connection/ruby.rb +0 -1
  97. data/test/support/connection/synchrony.rb +0 -17
  98. data/test/support/redis_mock.rb +0 -92
  99. data/test/support/wire/synchrony.rb +0 -24
  100. data/test/support/wire/thread.rb +0 -5
  101. data/test/synchrony_driver.rb +0 -57
  102. data/test/test.conf +0 -9
  103. data/test/thread_safety_test.rb +0 -32
  104. data/test/transactions_test.rb +0 -244
  105. data/test/unknown_commands_test.rb +0 -14
  106. data/test/url_param_test.rb +0 -64
@@ -1,8 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'zlib'
2
4
 
3
5
  class Redis
4
6
  class HashRing
5
-
6
7
  POINTS_PER_SERVER = 160 # this is the default in libmemcached
7
8
 
8
9
  attr_reader :ring, :sorted_keys, :replicas, :nodes
@@ -10,7 +11,7 @@ class Redis
10
11
  # nodes is a list of objects that have a proper to_s representation.
11
12
  # replicas indicates how many virtual points should be used pr. node,
12
13
  # replicas are required to improve the distribution.
13
- def initialize(nodes=[], replicas=POINTS_PER_SERVER)
14
+ def initialize(nodes = [], replicas = POINTS_PER_SERVER)
14
15
  @replicas = replicas
15
16
  @ring = {}
16
17
  @nodes = []
@@ -32,11 +33,11 @@ class Redis
32
33
  end
33
34
 
34
35
  def remove_node(node)
35
- @nodes.reject!{|n| n.id == node.id}
36
+ @nodes.reject! { |n| n.id == node.id }
36
37
  @replicas.times do |i|
37
38
  key = Zlib.crc32("#{node.id}:#{i}")
38
39
  @ring.delete(key)
39
- @sorted_keys.reject! {|k| k == key}
40
+ @sorted_keys.reject! { |k| k == key }
40
41
  end
41
42
  end
42
43
 
@@ -46,86 +47,43 @@ class Redis
46
47
  end
47
48
 
48
49
  def get_node_pos(key)
49
- return [nil,nil] if @ring.size == 0
50
+ return [nil, nil] if @ring.empty?
51
+
50
52
  crc = Zlib.crc32(key)
51
53
  idx = HashRing.binary_search(@sorted_keys, crc)
52
- return [@ring[@sorted_keys[idx]], idx]
54
+ [@ring[@sorted_keys[idx]], idx]
53
55
  end
54
56
 
55
57
  def iter_nodes(key)
56
- return [nil,nil] if @ring.size == 0
58
+ return [nil, nil] if @ring.empty?
59
+
57
60
  _, pos = get_node_pos(key)
58
- @sorted_keys[pos..-1].each do |k|
59
- yield @ring[k]
61
+ @ring.size.times do |n|
62
+ yield @ring[@sorted_keys[(pos + n) % @ring.size]]
60
63
  end
61
64
  end
62
65
 
63
- class << self
64
-
65
- # gem install RubyInline to use this code
66
- # Native extension to perform the binary search within the hashring.
67
- # There's a pure ruby version below so this is purely optional
68
- # for performance. In testing 20k gets and sets, the native
69
- # binary search shaved about 12% off the runtime (9sec -> 8sec).
70
- begin
71
- require 'inline'
72
- inline do |builder|
73
- builder.c <<-EOM
74
- int binary_search(VALUE ary, unsigned int r) {
75
- int upper = RARRAY_LEN(ary) - 1;
76
- int lower = 0;
77
- int idx = 0;
78
-
79
- while (lower <= upper) {
80
- idx = (lower + upper) / 2;
81
-
82
- VALUE continuumValue = RARRAY_PTR(ary)[idx];
83
- unsigned int l = NUM2UINT(continuumValue);
84
- if (l == r) {
85
- return idx;
86
- }
87
- else if (l > r) {
88
- upper = idx - 1;
89
- }
90
- else {
91
- lower = idx + 1;
92
- }
93
- }
94
- if (upper < 0) {
95
- upper = RARRAY_LEN(ary) - 1;
96
- }
97
- return upper;
98
- }
99
- EOM
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
100
82
  end
101
- rescue Exception
102
- # Find the closest index in HashRing with value <= the given value
103
- def binary_search(ary, value, &block)
104
- upper = ary.size - 1
105
- lower = 0
106
- idx = 0
107
-
108
- while(lower <= upper) do
109
- idx = (lower + upper) / 2
110
- comp = ary[idx] <=> value
111
-
112
- if comp == 0
113
- return idx
114
- elsif comp > 0
115
- upper = idx - 1
116
- else
117
- lower = idx + 1
118
- end
119
- end
120
-
121
- if upper < 0
122
- upper = ary.size - 1
123
- end
124
- return upper
125
- end
126
-
127
83
  end
128
- end
129
84
 
85
+ upper = ary.size - 1 if upper < 0
86
+ upper
87
+ end
130
88
  end
131
89
  end
@@ -1,19 +1,23 @@
1
- class Redis
2
- unless defined?(::BasicObject)
3
- class BasicObject
4
- instance_methods.each { |meth| undef_method(meth) unless meth =~ /\A(__|instance_eval)/ }
5
- end
6
- end
1
+ # frozen_string_literal: true
7
2
 
3
+ class Redis
8
4
  class Pipeline
5
+ attr_accessor :db
6
+ attr_reader :client
7
+
9
8
  attr :futures
10
9
 
11
- def initialize
10
+ def initialize(client)
11
+ @client = client.is_a?(Pipeline) ? client.client : client
12
12
  @with_reconnect = true
13
13
  @shutdown = false
14
14
  @futures = []
15
15
  end
16
16
 
17
+ def timeout
18
+ client.timeout
19
+ end
20
+
17
21
  def with_reconnect?
18
22
  @with_reconnect
19
23
  end
@@ -26,26 +30,39 @@ class Redis
26
30
  @shutdown
27
31
  end
28
32
 
29
- def call(command, &block)
33
+ def empty?
34
+ @futures.empty?
35
+ end
36
+
37
+ def call(command, timeout: nil, &block)
30
38
  # A pipeline that contains a shutdown should not raise ECONNRESET when
31
39
  # the connection is gone.
32
40
  @shutdown = true if command.first == :shutdown
33
- future = Future.new(command, block)
41
+ future = Future.new(command, block, timeout)
34
42
  @futures << future
35
43
  future
36
44
  end
37
45
 
46
+ def call_with_timeout(command, timeout, &block)
47
+ call(command, timeout: timeout, &block)
48
+ end
49
+
38
50
  def call_pipeline(pipeline)
39
51
  @shutdown = true if pipeline.shutdown?
40
52
  @futures.concat(pipeline.futures)
53
+ @db = pipeline.db
41
54
  nil
42
55
  end
43
56
 
44
57
  def commands
45
- @futures.map { |f| f._command }
58
+ @futures.map(&:_command)
46
59
  end
47
60
 
48
- def with_reconnect(val=true)
61
+ def timeouts
62
+ @futures.map(&:timeout)
63
+ end
64
+
65
+ def with_reconnect(val = true)
49
66
  @with_reconnect = false unless val
50
67
  yield
51
68
  end
@@ -68,14 +85,20 @@ class Redis
68
85
 
69
86
  class Multi < self
70
87
  def finish(replies)
71
- return if replies.last.nil? # The transaction failed because of WATCH.
88
+ exec = replies.last
89
+
90
+ return if exec.nil? # The transaction failed because of WATCH.
72
91
 
73
- if replies.last.size < futures.size - 2
92
+ # EXEC command failed.
93
+ raise exec if exec.is_a?(CommandError)
94
+
95
+ if exec.size < futures.size
74
96
  # Some command wasn't recognized by Redis.
75
- raise replies.detect { |r| r.kind_of?(::RuntimeError) }
97
+ command_error = replies.detect { |r| r.is_a?(CommandError) }
98
+ raise command_error
76
99
  end
77
100
 
78
- super(replies.last) do |reply|
101
+ super(exec) do |reply|
79
102
  # Because an EXEC returns nested replies, hiredis won't be able to
80
103
  # convert an error reply to a CommandError instance itself. This is
81
104
  # specific to MULTI/EXEC, so we solve this here.
@@ -83,8 +106,20 @@ class Redis
83
106
  end
84
107
  end
85
108
 
109
+ def timeouts
110
+ if empty?
111
+ []
112
+ else
113
+ [nil, *super, nil]
114
+ end
115
+ end
116
+
86
117
  def commands
87
- [[:multi]] + super + [[:exec]]
118
+ if empty?
119
+ []
120
+ else
121
+ [[:multi]] + super + [[:exec]]
122
+ end
88
123
  end
89
124
  end
90
125
  end
@@ -98,12 +133,25 @@ class Redis
98
133
  class Future < BasicObject
99
134
  FutureNotReady = ::Redis::FutureNotReady.new
100
135
 
101
- def initialize(command, transformation)
136
+ attr_reader :timeout
137
+
138
+ def initialize(command, transformation, timeout)
102
139
  @command = command
103
140
  @transformation = transformation
141
+ @timeout = timeout
104
142
  @object = FutureNotReady
105
143
  end
106
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
+
107
155
  def inspect
108
156
  "<Redis::Future #{@command.inspect}>"
109
157
  end
@@ -118,8 +166,16 @@ class Redis
118
166
  end
119
167
 
120
168
  def value
121
- ::Kernel.raise(@object) if @object.kind_of?(::RuntimeError)
169
+ ::Kernel.raise(@object) if @object.is_a?(::RuntimeError)
122
170
  @object
123
171
  end
172
+
173
+ def is_a?(other)
174
+ self.class.ancestors.include?(other)
175
+ end
176
+
177
+ def class
178
+ Future
179
+ end
124
180
  end
125
181
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Redis
2
4
  class SubscribedClient
3
5
  def initialize(client)
@@ -12,32 +14,41 @@ class Redis
12
14
  subscription("subscribe", "unsubscribe", channels, block)
13
15
  end
14
16
 
17
+ def subscribe_with_timeout(timeout, *channels, &block)
18
+ subscription("subscribe", "unsubscribe", channels, block, timeout)
19
+ end
20
+
15
21
  def psubscribe(*channels, &block)
16
22
  subscription("psubscribe", "punsubscribe", channels, block)
17
23
  end
18
24
 
25
+ def psubscribe_with_timeout(timeout, *channels, &block)
26
+ subscription("psubscribe", "punsubscribe", channels, block, timeout)
27
+ end
28
+
19
29
  def unsubscribe(*channels)
20
- call [:unsubscribe, *channels]
30
+ call([:unsubscribe, *channels])
21
31
  end
22
32
 
23
33
  def punsubscribe(*channels)
24
- call [:punsubscribe, *channels]
34
+ call([:punsubscribe, *channels])
25
35
  end
26
36
 
27
- protected
37
+ protected
28
38
 
29
- def subscription(start, stop, channels, block)
39
+ def subscription(start, stop, channels, block, timeout = 0)
30
40
  sub = Subscription.new(&block)
31
41
 
32
- begin
33
- @client.call_loop([start, *channels]) do |line|
34
- type, *rest = line
35
- sub.callbacks[type].call(*rest)
36
- break if type == stop && rest.last == 0
37
- end
38
- ensure
39
- send(stop)
42
+ unsubscribed = false
43
+
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
40
49
  end
50
+ # No need to unsubscribe here. The real client closes the connection
51
+ # whenever an exception is raised (see #ensure_connected).
41
52
  end
42
53
  end
43
54
 
@@ -46,7 +57,7 @@ class Redis
46
57
 
47
58
  def initialize
48
59
  @callbacks = Hash.new do |hash, key|
49
- hash[key] = lambda { |*_| }
60
+ hash[key] = ->(*_) {}
50
61
  end
51
62
 
52
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 = "3.0.0"
4
+ VERSION = '4.5.0'
3
5
  end