redis 4.4.0 → 5.0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +150 -0
  3. data/README.md +95 -160
  4. data/lib/redis/client.rb +84 -608
  5. data/lib/redis/commands/bitmaps.rb +66 -0
  6. data/lib/redis/commands/cluster.rb +28 -0
  7. data/lib/redis/commands/connection.rb +53 -0
  8. data/lib/redis/commands/geo.rb +84 -0
  9. data/lib/redis/commands/hashes.rb +254 -0
  10. data/lib/redis/commands/hyper_log_log.rb +37 -0
  11. data/lib/redis/commands/keys.rb +437 -0
  12. data/lib/redis/commands/lists.rb +339 -0
  13. data/lib/redis/commands/pubsub.rb +54 -0
  14. data/lib/redis/commands/scripting.rb +114 -0
  15. data/lib/redis/commands/server.rb +188 -0
  16. data/lib/redis/commands/sets.rb +214 -0
  17. data/lib/redis/commands/sorted_sets.rb +884 -0
  18. data/lib/redis/commands/streams.rb +402 -0
  19. data/lib/redis/commands/strings.rb +314 -0
  20. data/lib/redis/commands/transactions.rb +115 -0
  21. data/lib/redis/commands.rb +237 -0
  22. data/lib/redis/distributed.rb +208 -70
  23. data/lib/redis/errors.rb +15 -41
  24. data/lib/redis/hash_ring.rb +26 -26
  25. data/lib/redis/pipeline.rb +66 -120
  26. data/lib/redis/subscribe.rb +23 -15
  27. data/lib/redis/version.rb +1 -1
  28. data/lib/redis.rb +109 -3546
  29. metadata +27 -54
  30. data/lib/redis/cluster/command.rb +0 -81
  31. data/lib/redis/cluster/command_loader.rb +0 -34
  32. data/lib/redis/cluster/key_slot_converter.rb +0 -72
  33. data/lib/redis/cluster/node.rb +0 -108
  34. data/lib/redis/cluster/node_key.rb +0 -31
  35. data/lib/redis/cluster/node_loader.rb +0 -37
  36. data/lib/redis/cluster/option.rb +0 -93
  37. data/lib/redis/cluster/slot.rb +0 -86
  38. data/lib/redis/cluster/slot_loader.rb +0 -49
  39. data/lib/redis/cluster.rb +0 -291
  40. data/lib/redis/connection/command_helper.rb +0 -39
  41. data/lib/redis/connection/hiredis.rb +0 -67
  42. data/lib/redis/connection/registry.rb +0 -13
  43. data/lib/redis/connection/ruby.rb +0 -427
  44. data/lib/redis/connection/synchrony.rb +0 -146
  45. data/lib/redis/connection.rb +0 -11
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'zlib'
4
+ require 'digest/md5'
4
5
 
5
6
  class Redis
6
7
  class HashRing
@@ -25,7 +26,7 @@ class Redis
25
26
  def add_node(node)
26
27
  @nodes << node
27
28
  @replicas.times do |i|
28
- key = Zlib.crc32("#{node.id}:#{i}")
29
+ key = server_hash_for("#{node.id}:#{i}")
29
30
  @ring[key] = node
30
31
  @sorted_keys << key
31
32
  end
@@ -35,7 +36,7 @@ class Redis
35
36
  def remove_node(node)
36
37
  @nodes.reject! { |n| n.id == node.id }
37
38
  @replicas.times do |i|
38
- key = Zlib.crc32("#{node.id}:#{i}")
39
+ key = server_hash_for("#{node.id}:#{i}")
39
40
  @ring.delete(key)
40
41
  @sorted_keys.reject! { |k| k == key }
41
42
  end
@@ -43,47 +44,46 @@ class Redis
43
44
 
44
45
  # get the node in the hash ring for this key
45
46
  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]
47
+ hash = hash_for(key)
48
+ idx = binary_search(@sorted_keys, hash)
49
+ @ring[@sorted_keys[idx]]
55
50
  end
56
51
 
57
52
  def iter_nodes(key)
58
53
  return [nil, nil] if @ring.empty?
59
54
 
60
- _, pos = get_node_pos(key)
55
+ crc = hash_for(key)
56
+ pos = binary_search(@sorted_keys, crc)
61
57
  @ring.size.times do |n|
62
58
  yield @ring[@sorted_keys[(pos + n) % @ring.size]]
63
59
  end
64
60
  end
65
61
 
62
+ private
63
+
64
+ def hash_for(key)
65
+ Zlib.crc32(key)
66
+ end
67
+
68
+ def server_hash_for(key)
69
+ Digest::MD5.digest(key).unpack1("L>")
70
+ end
71
+
66
72
  # Find the closest index in HashRing with value <= the given value
67
- def self.binary_search(ary, value)
68
- upper = ary.size - 1
73
+ def binary_search(ary, value)
74
+ upper = ary.size
69
75
  lower = 0
70
- idx = 0
71
-
72
- while lower <= upper
73
- idx = (lower + upper) / 2
74
- comp = ary[idx] <=> value
75
76
 
76
- if comp == 0
77
- return idx
78
- elsif comp > 0
79
- upper = idx - 1
77
+ while lower < upper
78
+ mid = (lower + upper) / 2
79
+ if ary[mid] > value
80
+ upper = mid
80
81
  else
81
- lower = idx + 1
82
+ lower = mid + 1
82
83
  end
83
84
  end
84
85
 
85
- upper = ary.size - 1 if upper < 0
86
- upper
86
+ upper - 1
87
87
  end
88
88
  end
89
89
  end
@@ -1,126 +1,72 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "delegate"
4
+
3
5
  class Redis
4
- class Pipeline
6
+ class PipelinedConnection
5
7
  attr_accessor :db
6
- attr_reader :client
7
-
8
- attr :futures
9
8
 
10
- def initialize(client)
11
- @client = client.is_a?(Pipeline) ? client.client : client
12
- @with_reconnect = true
13
- @shutdown = false
14
- @futures = []
9
+ def initialize(pipeline, futures = [])
10
+ @pipeline = pipeline
11
+ @futures = futures
15
12
  end
16
13
 
17
- def timeout
18
- client.timeout
19
- end
14
+ include Commands
20
15
 
21
- def with_reconnect?
22
- @with_reconnect
16
+ def pipelined
17
+ yield self
23
18
  end
24
19
 
25
- def without_reconnect?
26
- !@with_reconnect
20
+ def multi
21
+ transaction = MultiConnection.new(@pipeline, @futures)
22
+ send_command([:multi])
23
+ size = @futures.size
24
+ yield transaction
25
+ multi_future = MultiFuture.new(@futures[size..-1])
26
+ @pipeline.call_v([:exec]) do |result|
27
+ multi_future._set(result)
28
+ end
29
+ @futures << multi_future
30
+ multi_future
27
31
  end
28
32
 
29
- def shutdown?
30
- @shutdown
31
- end
33
+ private
32
34
 
33
- def empty?
34
- @futures.empty?
35
+ def synchronize
36
+ yield self
35
37
  end
36
38
 
37
- def call(command, timeout: nil, &block)
38
- # A pipeline that contains a shutdown should not raise ECONNRESET when
39
- # the connection is gone.
40
- @shutdown = true if command.first == :shutdown
41
- future = Future.new(command, block, timeout)
39
+ def send_command(command, &block)
40
+ future = Future.new(command, block)
41
+ @pipeline.call_v(command) do |result|
42
+ future._set(result)
43
+ end
42
44
  @futures << future
43
45
  future
44
46
  end
45
47
 
46
- def call_with_timeout(command, timeout, &block)
47
- call(command, timeout: timeout, &block)
48
- end
49
-
50
- def call_pipeline(pipeline)
51
- @shutdown = true if pipeline.shutdown?
52
- @futures.concat(pipeline.futures)
53
- @db = pipeline.db
54
- nil
55
- end
56
-
57
- def commands
58
- @futures.map(&:_command)
59
- end
60
-
61
- def timeouts
62
- @futures.map(&:timeout)
63
- end
64
-
65
- def with_reconnect(val = true)
66
- @with_reconnect = false unless val
67
- yield
68
- end
69
-
70
- def without_reconnect(&blk)
71
- with_reconnect(false, &blk)
72
- end
73
-
74
- def finish(replies, &blk)
75
- if blk
76
- futures.each_with_index.map do |future, i|
77
- future._set(blk.call(replies[i]))
78
- end
79
- else
80
- futures.each_with_index.map do |future, i|
81
- future._set(replies[i])
82
- end
48
+ def send_blocking_command(command, timeout, &block)
49
+ future = Future.new(command, block)
50
+ @pipeline.blocking_call_v(timeout, command) do |result|
51
+ future._set(result)
83
52
  end
53
+ @futures << future
54
+ future
84
55
  end
56
+ end
85
57
 
86
- class Multi < self
87
- def finish(replies)
88
- exec = replies.last
89
-
90
- return if exec.nil? # The transaction failed because of WATCH.
91
-
92
- # EXEC command failed.
93
- raise exec if exec.is_a?(CommandError)
94
-
95
- if exec.size < futures.size
96
- # Some command wasn't recognized by Redis.
97
- command_error = replies.detect { |r| r.is_a?(CommandError) }
98
- raise command_error
99
- end
58
+ class MultiConnection < PipelinedConnection
59
+ def multi
60
+ raise Redis::Error, "Can't nest multi transaction"
61
+ end
100
62
 
101
- super(exec) do |reply|
102
- # Because an EXEC returns nested replies, hiredis won't be able to
103
- # convert an error reply to a CommandError instance itself. This is
104
- # specific to MULTI/EXEC, so we solve this here.
105
- reply.is_a?(::RuntimeError) ? CommandError.new(reply.message) : reply
106
- end
107
- end
63
+ private
108
64
 
109
- def timeouts
110
- if empty?
111
- []
112
- else
113
- [nil, *super, nil]
114
- end
115
- end
116
-
117
- def commands
118
- if empty?
119
- []
120
- else
121
- [[:multi]] + super + [[:exec]]
122
- end
123
- end
65
+ # Blocking commands inside transaction behave like non-blocking.
66
+ # It shouldn't be done though.
67
+ # https://redis.io/commands/blpop/#blpop-inside-a-multi--exec-transaction
68
+ def send_blocking_command(command, _timeout, &block)
69
+ send_command(command, &block)
124
70
  end
125
71
  end
126
72
 
@@ -133,23 +79,10 @@ class Redis
133
79
  class Future < BasicObject
134
80
  FutureNotReady = ::Redis::FutureNotReady.new
135
81
 
136
- attr_reader :timeout
137
-
138
- def initialize(command, transformation, timeout)
82
+ def initialize(command, coerce)
139
83
  @command = command
140
- @transformation = transformation
141
- @timeout = timeout
142
84
  @object = FutureNotReady
143
- end
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
85
+ @coerce = coerce
153
86
  end
154
87
 
155
88
  def inspect
@@ -157,16 +90,12 @@ class Redis
157
90
  end
158
91
 
159
92
  def _set(object)
160
- @object = @transformation ? @transformation.call(object) : object
93
+ @object = @coerce ? @coerce.call(object) : object
161
94
  value
162
95
  end
163
96
 
164
- def _command
165
- @command
166
- end
167
-
168
97
  def value
169
- ::Kernel.raise(@object) if @object.is_a?(::RuntimeError)
98
+ ::Kernel.raise(@object) if @object.is_a?(::StandardError)
170
99
  @object
171
100
  end
172
101
 
@@ -178,4 +107,21 @@ class Redis
178
107
  Future
179
108
  end
180
109
  end
110
+
111
+ class MultiFuture < Future
112
+ def initialize(futures)
113
+ @futures = futures
114
+ @command = [:exec]
115
+ @object = FutureNotReady
116
+ end
117
+
118
+ def _set(replies)
119
+ if replies
120
+ @futures.each_with_index do |future, index|
121
+ future._set(replies[index])
122
+ end
123
+ end
124
+ @object = replies
125
+ end
126
+ end
181
127
  end
@@ -4,10 +4,13 @@ class Redis
4
4
  class SubscribedClient
5
5
  def initialize(client)
6
6
  @client = client
7
+ @write_monitor = Monitor.new
7
8
  end
8
9
 
9
- def call(command)
10
- @client.process([command])
10
+ def call_v(command)
11
+ @write_monitor.synchronize do
12
+ @client.call_v(command)
13
+ end
11
14
  end
12
15
 
13
16
  def subscribe(*channels, &block)
@@ -27,11 +30,15 @@ class Redis
27
30
  end
28
31
 
29
32
  def unsubscribe(*channels)
30
- call([:unsubscribe, *channels])
33
+ call_v([:unsubscribe, *channels])
31
34
  end
32
35
 
33
36
  def punsubscribe(*channels)
34
- call([:punsubscribe, *channels])
37
+ call_v([:punsubscribe, *channels])
38
+ end
39
+
40
+ def close
41
+ @client.close
35
42
  end
36
43
 
37
44
  protected
@@ -39,13 +46,17 @@ class Redis
39
46
  def subscription(start, stop, channels, block, timeout = 0)
40
47
  sub = Subscription.new(&block)
41
48
 
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
49
+ call_v([start, *channels])
50
+ while event = @client.next_event(timeout)
51
+ if event.is_a?(::RedisClient::CommandError)
52
+ raise Client::ERROR_MAPPING.fetch(event.class), event.message
53
+ end
54
+
55
+ type, *rest = event
56
+ if callback = sub.callbacks[type]
57
+ callback.call(*rest)
58
+ end
59
+ break if type == stop && rest.last == 0
49
60
  end
50
61
  # No need to unsubscribe here. The real client closes the connection
51
62
  # whenever an exception is raised (see #ensure_connected).
@@ -56,10 +67,7 @@ class Redis
56
67
  attr :callbacks
57
68
 
58
69
  def initialize
59
- @callbacks = Hash.new do |hash, key|
60
- hash[key] = ->(*_) {}
61
- end
62
-
70
+ @callbacks = {}
63
71
  yield(self)
64
72
  end
65
73
 
data/lib/redis/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Redis
4
- VERSION = '4.4.0'
4
+ VERSION = '5.0.7'
5
5
  end