redis 3.3.5 → 5.0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (137) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +290 -2
  3. data/README.md +146 -146
  4. data/lib/redis/client.rb +79 -541
  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 +328 -108
  23. data/lib/redis/errors.rb +23 -1
  24. data/lib/redis/hash_ring.rb +36 -79
  25. data/lib/redis/pipeline.rb +69 -83
  26. data/lib/redis/subscribe.rb +26 -19
  27. data/lib/redis/version.rb +3 -1
  28. data/lib/redis.rb +115 -2695
  29. metadata +38 -218
  30. data/.gitignore +0 -16
  31. data/.travis/Gemfile +0 -11
  32. data/.travis.yml +0 -89
  33. data/.yardopts +0 -3
  34. data/Gemfile +0 -4
  35. data/Rakefile +0 -87
  36. data/benchmarking/logging.rb +0 -71
  37. data/benchmarking/pipeline.rb +0 -51
  38. data/benchmarking/speed.rb +0 -21
  39. data/benchmarking/suite.rb +0 -24
  40. data/benchmarking/worker.rb +0 -71
  41. data/examples/basic.rb +0 -15
  42. data/examples/consistency.rb +0 -114
  43. data/examples/dist_redis.rb +0 -43
  44. data/examples/incr-decr.rb +0 -17
  45. data/examples/list.rb +0 -26
  46. data/examples/pubsub.rb +0 -37
  47. data/examples/sentinel/sentinel.conf +0 -9
  48. data/examples/sentinel/start +0 -49
  49. data/examples/sentinel.rb +0 -41
  50. data/examples/sets.rb +0 -36
  51. data/examples/unicorn/config.ru +0 -3
  52. data/examples/unicorn/unicorn.rb +0 -20
  53. data/lib/redis/connection/command_helper.rb +0 -44
  54. data/lib/redis/connection/hiredis.rb +0 -66
  55. data/lib/redis/connection/registry.rb +0 -12
  56. data/lib/redis/connection/ruby.rb +0 -429
  57. data/lib/redis/connection/synchrony.rb +0 -133
  58. data/lib/redis/connection.rb +0 -9
  59. data/redis.gemspec +0 -44
  60. data/test/bitpos_test.rb +0 -69
  61. data/test/blocking_commands_test.rb +0 -42
  62. data/test/client_test.rb +0 -59
  63. data/test/command_map_test.rb +0 -30
  64. data/test/commands_on_hashes_test.rb +0 -21
  65. data/test/commands_on_hyper_log_log_test.rb +0 -21
  66. data/test/commands_on_lists_test.rb +0 -20
  67. data/test/commands_on_sets_test.rb +0 -77
  68. data/test/commands_on_sorted_sets_test.rb +0 -137
  69. data/test/commands_on_strings_test.rb +0 -101
  70. data/test/commands_on_value_types_test.rb +0 -133
  71. data/test/connection_handling_test.rb +0 -277
  72. data/test/connection_test.rb +0 -57
  73. data/test/db/.gitkeep +0 -0
  74. data/test/distributed_blocking_commands_test.rb +0 -46
  75. data/test/distributed_commands_on_hashes_test.rb +0 -10
  76. data/test/distributed_commands_on_hyper_log_log_test.rb +0 -33
  77. data/test/distributed_commands_on_lists_test.rb +0 -22
  78. data/test/distributed_commands_on_sets_test.rb +0 -83
  79. data/test/distributed_commands_on_sorted_sets_test.rb +0 -18
  80. data/test/distributed_commands_on_strings_test.rb +0 -59
  81. data/test/distributed_commands_on_value_types_test.rb +0 -95
  82. data/test/distributed_commands_requiring_clustering_test.rb +0 -164
  83. data/test/distributed_connection_handling_test.rb +0 -23
  84. data/test/distributed_internals_test.rb +0 -79
  85. data/test/distributed_key_tags_test.rb +0 -52
  86. data/test/distributed_persistence_control_commands_test.rb +0 -26
  87. data/test/distributed_publish_subscribe_test.rb +0 -92
  88. data/test/distributed_remote_server_control_commands_test.rb +0 -66
  89. data/test/distributed_scripting_test.rb +0 -102
  90. data/test/distributed_sorting_test.rb +0 -20
  91. data/test/distributed_test.rb +0 -58
  92. data/test/distributed_transactions_test.rb +0 -32
  93. data/test/encoding_test.rb +0 -18
  94. data/test/error_replies_test.rb +0 -59
  95. data/test/fork_safety_test.rb +0 -65
  96. data/test/helper.rb +0 -232
  97. data/test/helper_test.rb +0 -24
  98. data/test/internals_test.rb +0 -417
  99. data/test/lint/blocking_commands.rb +0 -150
  100. data/test/lint/hashes.rb +0 -162
  101. data/test/lint/hyper_log_log.rb +0 -60
  102. data/test/lint/lists.rb +0 -143
  103. data/test/lint/sets.rb +0 -140
  104. data/test/lint/sorted_sets.rb +0 -316
  105. data/test/lint/strings.rb +0 -260
  106. data/test/lint/value_types.rb +0 -122
  107. data/test/persistence_control_commands_test.rb +0 -26
  108. data/test/pipelining_commands_test.rb +0 -242
  109. data/test/publish_subscribe_test.rb +0 -282
  110. data/test/remote_server_control_commands_test.rb +0 -118
  111. data/test/scanning_test.rb +0 -413
  112. data/test/scripting_test.rb +0 -78
  113. data/test/sentinel_command_test.rb +0 -80
  114. data/test/sentinel_test.rb +0 -255
  115. data/test/sorting_test.rb +0 -59
  116. data/test/ssl_test.rb +0 -73
  117. data/test/support/connection/hiredis.rb +0 -1
  118. data/test/support/connection/ruby.rb +0 -1
  119. data/test/support/connection/synchrony.rb +0 -17
  120. data/test/support/redis_mock.rb +0 -130
  121. data/test/support/ssl/gen_certs.sh +0 -31
  122. data/test/support/ssl/trusted-ca.crt +0 -25
  123. data/test/support/ssl/trusted-ca.key +0 -27
  124. data/test/support/ssl/trusted-cert.crt +0 -81
  125. data/test/support/ssl/trusted-cert.key +0 -28
  126. data/test/support/ssl/untrusted-ca.crt +0 -26
  127. data/test/support/ssl/untrusted-ca.key +0 -27
  128. data/test/support/ssl/untrusted-cert.crt +0 -82
  129. data/test/support/ssl/untrusted-cert.key +0 -28
  130. data/test/support/wire/synchrony.rb +0 -24
  131. data/test/support/wire/thread.rb +0 -5
  132. data/test/synchrony_driver.rb +0 -88
  133. data/test/test.conf.erb +0 -9
  134. data/test/thread_safety_test.rb +0 -62
  135. data/test/transactions_test.rb +0 -264
  136. data/test/unknown_commands_test.rb +0 -14
  137. data/test/url_param_test.rb +0 -138
data/lib/redis/errors.rb CHANGED
@@ -1,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Redis
2
4
  # Base error for all redis-rb errors.
3
- class BaseError < RuntimeError
5
+ class BaseError < StandardError
4
6
  end
5
7
 
6
8
  # Raised by the connection when a protocol error occurs.
@@ -18,6 +20,15 @@ class Redis
18
20
  class CommandError < BaseError
19
21
  end
20
22
 
23
+ class PermissionError < CommandError
24
+ end
25
+
26
+ class WrongTypeError < CommandError
27
+ end
28
+
29
+ class OutOfMemoryError < CommandError
30
+ end
31
+
21
32
  # Base error for connection related errors.
22
33
  class BaseConnectionError < BaseError
23
34
  end
@@ -37,4 +48,15 @@ class Redis
37
48
  # Raised when the connection was inherited by a child process.
38
49
  class InheritedError < BaseConnectionError
39
50
  end
51
+
52
+ # Generally raised during Redis failover scenarios
53
+ class ReadOnlyError < BaseConnectionError
54
+ end
55
+
56
+ # Raised when client options are invalid.
57
+ class InvalidClientOptionError < BaseError
58
+ end
59
+
60
+ class SubscriptionError < BaseError
61
+ end
40
62
  end
@@ -1,8 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'zlib'
4
+ require 'digest/md5'
2
5
 
3
6
  class Redis
4
7
  class HashRing
5
-
6
8
  POINTS_PER_SERVER = 160 # this is the default in libmemcached
7
9
 
8
10
  attr_reader :ring, :sorted_keys, :replicas, :nodes
@@ -10,7 +12,7 @@ class Redis
10
12
  # nodes is a list of objects that have a proper to_s representation.
11
13
  # replicas indicates how many virtual points should be used pr. node,
12
14
  # replicas are required to improve the distribution.
13
- def initialize(nodes=[], replicas=POINTS_PER_SERVER)
15
+ def initialize(nodes = [], replicas = POINTS_PER_SERVER)
14
16
  @replicas = replicas
15
17
  @ring = {}
16
18
  @nodes = []
@@ -24,8 +26,7 @@ class Redis
24
26
  def add_node(node)
25
27
  @nodes << node
26
28
  @replicas.times do |i|
27
- key = Zlib.crc32("#{node.id}:#{i}")
28
- raise "Node ID collision" if @ring.has_key?(key)
29
+ key = server_hash_for("#{node.id}:#{i}")
29
30
  @ring[key] = node
30
31
  @sorted_keys << key
31
32
  end
@@ -33,100 +34,56 @@ class Redis
33
34
  end
34
35
 
35
36
  def remove_node(node)
36
- @nodes.reject!{|n| n.id == node.id}
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
- @sorted_keys.reject! {|k| k == key}
41
+ @sorted_keys.reject! { |k| k == key }
41
42
  end
42
43
  end
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.size == 0
51
- crc = Zlib.crc32(key)
52
- idx = HashRing.binary_search(@sorted_keys, crc)
53
- return [@ring[@sorted_keys[idx]], idx]
47
+ hash = hash_for(key)
48
+ idx = binary_search(@sorted_keys, hash)
49
+ @ring[@sorted_keys[idx]]
54
50
  end
55
51
 
56
52
  def iter_nodes(key)
57
- return [nil,nil] if @ring.size == 0
58
- _, pos = get_node_pos(key)
53
+ return [nil, nil] if @ring.empty?
54
+
55
+ crc = hash_for(key)
56
+ pos = binary_search(@sorted_keys, crc)
59
57
  @ring.size.times do |n|
60
- yield @ring[@sorted_keys[(pos+n) % @ring.size]]
58
+ yield @ring[@sorted_keys[(pos + n) % @ring.size]]
61
59
  end
62
60
  end
63
61
 
64
- class << self
65
-
66
- # gem install RubyInline to use this code
67
- # Native extension to perform the binary search within the hashring.
68
- # There's a pure ruby version below so this is purely optional
69
- # for performance. In testing 20k gets and sets, the native
70
- # binary search shaved about 12% off the runtime (9sec -> 8sec).
71
- begin
72
- require 'inline'
73
- inline do |builder|
74
- builder.c <<-EOM
75
- int binary_search(VALUE ary, unsigned int r) {
76
- int upper = RARRAY_LEN(ary) - 1;
77
- int lower = 0;
78
- int idx = 0;
79
-
80
- while (lower <= upper) {
81
- idx = (lower + upper) / 2;
62
+ private
82
63
 
83
- VALUE continuumValue = RARRAY_PTR(ary)[idx];
84
- unsigned int l = NUM2UINT(continuumValue);
85
- if (l == r) {
86
- return idx;
87
- }
88
- else if (l > r) {
89
- upper = idx - 1;
90
- }
91
- else {
92
- lower = idx + 1;
93
- }
94
- }
95
- if (upper < 0) {
96
- upper = RARRAY_LEN(ary) - 1;
97
- }
98
- return upper;
99
- }
100
- EOM
101
- end
102
- rescue Exception
103
- # Find the closest index in HashRing with value <= the given value
104
- def binary_search(ary, value, &block)
105
- upper = ary.size - 1
106
- lower = 0
107
- idx = 0
108
-
109
- while(lower <= upper) do
110
- idx = (lower + upper) / 2
111
- comp = ary[idx] <=> value
64
+ def hash_for(key)
65
+ Zlib.crc32(key)
66
+ end
112
67
 
113
- if comp == 0
114
- return idx
115
- elsif comp > 0
116
- upper = idx - 1
117
- else
118
- lower = idx + 1
119
- end
120
- end
68
+ def server_hash_for(key)
69
+ Digest::MD5.digest(key).unpack1("L>")
70
+ end
121
71
 
122
- if upper < 0
123
- upper = ary.size - 1
124
- end
125
- return upper
72
+ # Find the closest index in HashRing with value <= the given value
73
+ def binary_search(ary, value)
74
+ upper = ary.size
75
+ lower = 0
76
+
77
+ while lower < upper
78
+ mid = (lower + upper) / 2
79
+ if ary[mid] > value
80
+ upper = mid
81
+ else
82
+ lower = mid + 1
126
83
  end
127
-
128
84
  end
129
- end
130
85
 
86
+ upper - 1
87
+ end
131
88
  end
132
89
  end
@@ -1,99 +1,72 @@
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
 
8
- class Pipeline
9
- attr_accessor :db
3
+ require "delegate"
10
4
 
11
- attr :futures
5
+ class Redis
6
+ class PipelinedConnection
7
+ attr_accessor :db
12
8
 
13
- def initialize
14
- @with_reconnect = true
15
- @shutdown = false
16
- @futures = []
9
+ def initialize(pipeline, futures = [])
10
+ @pipeline = pipeline
11
+ @futures = futures
17
12
  end
18
13
 
19
- def with_reconnect?
20
- @with_reconnect
14
+ include Commands
15
+
16
+ def pipelined
17
+ yield self
21
18
  end
22
19
 
23
- def without_reconnect?
24
- !@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
25
31
  end
26
32
 
27
- def shutdown?
28
- @shutdown
33
+ private
34
+
35
+ def synchronize
36
+ yield self
29
37
  end
30
38
 
31
- def call(command, &block)
32
- # A pipeline that contains a shutdown should not raise ECONNRESET when
33
- # the connection is gone.
34
- @shutdown = true if command.first == :shutdown
39
+ def send_command(command, &block)
35
40
  future = Future.new(command, block)
41
+ @pipeline.call_v(command) do |result|
42
+ future._set(result)
43
+ end
36
44
  @futures << future
37
45
  future
38
46
  end
39
47
 
40
- def call_pipeline(pipeline)
41
- @shutdown = true if pipeline.shutdown?
42
- @futures.concat(pipeline.futures)
43
- @db = pipeline.db
44
- nil
45
- end
46
-
47
- def commands
48
- @futures.map { |f| f._command }
49
- end
50
-
51
- def with_reconnect(val=true)
52
- @with_reconnect = false unless val
53
- yield
54
- end
55
-
56
- def without_reconnect(&blk)
57
- with_reconnect(false, &blk)
58
- end
59
-
60
- def finish(replies, &blk)
61
- if blk
62
- futures.each_with_index.map do |future, i|
63
- future._set(blk.call(replies[i]))
64
- end
65
- else
66
- futures.each_with_index.map do |future, i|
67
- future._set(replies[i])
68
- 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)
69
52
  end
53
+ @futures << future
54
+ future
70
55
  end
56
+ end
71
57
 
72
- class Multi < self
73
- def finish(replies)
74
- exec = replies.last
75
-
76
- return if exec.nil? # The transaction failed because of WATCH.
77
-
78
- # EXEC command failed.
79
- raise exec if exec.is_a?(CommandError)
80
-
81
- if exec.size < futures.size
82
- # Some command wasn't recognized by Redis.
83
- raise replies.detect { |r| r.is_a?(CommandError) }
84
- end
58
+ class MultiConnection < PipelinedConnection
59
+ def multi
60
+ raise Redis::Error, "Can't nest multi transaction"
61
+ end
85
62
 
86
- super(exec) do |reply|
87
- # Because an EXEC returns nested replies, hiredis won't be able to
88
- # convert an error reply to a CommandError instance itself. This is
89
- # specific to MULTI/EXEC, so we solve this here.
90
- reply.is_a?(::RuntimeError) ? CommandError.new(reply.message) : reply
91
- end
92
- end
63
+ private
93
64
 
94
- def commands
95
- [[:multi]] + super + [[:exec]]
96
- 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)
97
70
  end
98
71
  end
99
72
 
@@ -106,10 +79,10 @@ class Redis
106
79
  class Future < BasicObject
107
80
  FutureNotReady = ::Redis::FutureNotReady.new
108
81
 
109
- def initialize(command, transformation)
82
+ def initialize(command, coerce)
110
83
  @command = command
111
- @transformation = transformation
112
84
  @object = FutureNotReady
85
+ @coerce = coerce
113
86
  end
114
87
 
115
88
  def inspect
@@ -117,16 +90,12 @@ class Redis
117
90
  end
118
91
 
119
92
  def _set(object)
120
- @object = @transformation ? @transformation.call(object) : object
93
+ @object = @coerce ? @coerce.call(object) : object
121
94
  value
122
95
  end
123
96
 
124
- def _command
125
- @command
126
- end
127
-
128
97
  def value
129
- ::Kernel.raise(@object) if @object.kind_of?(::RuntimeError)
98
+ ::Kernel.raise(@object) if @object.is_a?(::StandardError)
130
99
  @object
131
100
  end
132
101
 
@@ -138,4 +107,21 @@ class Redis
138
107
  Future
139
108
  end
140
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
141
127
  end
@@ -1,11 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Redis
2
4
  class SubscribedClient
3
5
  def initialize(client)
4
6
  @client = client
7
+ @write_monitor = Monitor.new
5
8
  end
6
9
 
7
- def call(command)
8
- @client.process([command])
10
+ def call_v(command)
11
+ @write_monitor.synchronize do
12
+ @client.call_v(command)
13
+ end
9
14
  end
10
15
 
11
16
  def subscribe(*channels, &block)
@@ -25,31 +30,36 @@ class Redis
25
30
  end
26
31
 
27
32
  def unsubscribe(*channels)
28
- call([:unsubscribe, *channels])
33
+ call_v([:unsubscribe, *channels])
29
34
  end
30
35
 
31
36
  def punsubscribe(*channels)
32
- call([:punsubscribe, *channels])
37
+ call_v([:punsubscribe, *channels])
38
+ end
39
+
40
+ def close
41
+ @client.close
33
42
  end
34
43
 
35
- protected
44
+ protected
36
45
 
37
46
  def subscription(start, stop, channels, block, timeout = 0)
38
47
  sub = Subscription.new(&block)
39
48
 
40
- unsubscribed = false
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
41
54
 
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
55
+ type, *rest = event
56
+ if callback = sub.callbacks[type]
57
+ callback.call(*rest)
48
58
  end
49
- ensure
50
- # No need to unsubscribe here. The real client closes the connection
51
- # whenever an exception is raised (see #ensure_connected).
59
+ break if type == stop && rest.last == 0
52
60
  end
61
+ # No need to unsubscribe here. The real client closes the connection
62
+ # whenever an exception is raised (see #ensure_connected).
53
63
  end
54
64
  end
55
65
 
@@ -57,10 +67,7 @@ class Redis
57
67
  attr :callbacks
58
68
 
59
69
  def initialize
60
- @callbacks = Hash.new do |hash, key|
61
- hash[key] = lambda { |*_| }
62
- end
63
-
70
+ @callbacks = {}
64
71
  yield(self)
65
72
  end
66
73
 
data/lib/redis/version.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Redis
2
- VERSION = "3.3.5"
4
+ VERSION = '5.0.7'
3
5
  end