redis 3.3.5 → 5.0.7

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 (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