redis 4.0.0.rc1 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +8 -6
  3. data/.travis/Gemfile +3 -1
  4. data/CHANGELOG.md +6 -2
  5. data/README.md +5 -5
  6. data/bors.toml +14 -0
  7. data/examples/dist_redis.rb +43 -0
  8. data/lib/redis.rb +37 -6
  9. data/lib/redis/client.rb +5 -6
  10. data/lib/redis/connection/ruby.rb +1 -1
  11. data/lib/redis/connection/synchrony.rb +9 -1
  12. data/lib/redis/distributed.rb +873 -0
  13. data/lib/redis/hash_ring.rb +88 -0
  14. data/lib/redis/version.rb +1 -1
  15. data/redis.gemspec +3 -1
  16. data/test/blocking_commands_test.rb +2 -145
  17. data/test/commands_on_hashes_test.rb +2 -157
  18. data/test/commands_on_hyper_log_log_test.rb +2 -53
  19. data/test/commands_on_lists_test.rb +2 -138
  20. data/test/commands_on_sets_test.rb +2 -135
  21. data/test/commands_on_sorted_sets_test.rb +13 -307
  22. data/test/commands_on_strings_test.rb +2 -241
  23. data/test/commands_on_value_types_test.rb +2 -117
  24. data/test/distributed_blocking_commands_test.rb +44 -0
  25. data/test/distributed_commands_on_hashes_test.rb +8 -0
  26. data/test/distributed_commands_on_hyper_log_log_test.rb +31 -0
  27. data/test/distributed_commands_on_lists_test.rb +20 -0
  28. data/test/distributed_commands_on_sets_test.rb +81 -0
  29. data/test/distributed_commands_on_sorted_sets_test.rb +16 -0
  30. data/test/distributed_commands_on_strings_test.rb +57 -0
  31. data/test/distributed_commands_on_value_types_test.rb +93 -0
  32. data/test/distributed_commands_requiring_clustering_test.rb +162 -0
  33. data/test/distributed_connection_handling_test.rb +21 -0
  34. data/test/distributed_internals_test.rb +68 -0
  35. data/test/distributed_key_tags_test.rb +50 -0
  36. data/test/distributed_persistence_control_commands_test.rb +24 -0
  37. data/test/distributed_publish_subscribe_test.rb +90 -0
  38. data/test/distributed_remote_server_control_commands_test.rb +64 -0
  39. data/test/distributed_scripting_test.rb +100 -0
  40. data/test/distributed_sorting_test.rb +18 -0
  41. data/test/distributed_test.rb +56 -0
  42. data/test/distributed_transactions_test.rb +30 -0
  43. data/test/helper.rb +22 -0
  44. data/test/internals_test.rb +7 -13
  45. data/test/lint/blocking_commands.rb +150 -0
  46. data/test/lint/hashes.rb +162 -0
  47. data/test/lint/hyper_log_log.rb +60 -0
  48. data/test/lint/lists.rb +143 -0
  49. data/test/lint/sets.rb +140 -0
  50. data/test/lint/sorted_sets.rb +316 -0
  51. data/test/lint/strings.rb +246 -0
  52. data/test/lint/value_types.rb +130 -0
  53. data/test/remote_server_control_commands_test.rb +1 -1
  54. metadata +66 -8
@@ -0,0 +1,88 @@
1
+ require 'zlib'
2
+
3
+ class Redis
4
+ class HashRing
5
+
6
+ POINTS_PER_SERVER = 160 # this is the default in libmemcached
7
+
8
+ attr_reader :ring, :sorted_keys, :replicas, :nodes
9
+
10
+ # nodes is a list of objects that have a proper to_s representation.
11
+ # replicas indicates how many virtual points should be used pr. node,
12
+ # replicas are required to improve the distribution.
13
+ def initialize(nodes=[], replicas=POINTS_PER_SERVER)
14
+ @replicas = replicas
15
+ @ring = {}
16
+ @nodes = []
17
+ @sorted_keys = []
18
+ nodes.each do |node|
19
+ add_node(node)
20
+ end
21
+ end
22
+
23
+ # Adds a `node` to the hash ring (including a number of replicas).
24
+ def add_node(node)
25
+ @nodes << node
26
+ @replicas.times do |i|
27
+ key = Zlib.crc32("#{node.id}:#{i}")
28
+ @ring[key] = node
29
+ @sorted_keys << key
30
+ end
31
+ @sorted_keys.sort!
32
+ end
33
+
34
+ def remove_node(node)
35
+ @nodes.reject!{|n| n.id == node.id}
36
+ @replicas.times do |i|
37
+ key = Zlib.crc32("#{node.id}:#{i}")
38
+ @ring.delete(key)
39
+ @sorted_keys.reject! {|k| k == key}
40
+ end
41
+ end
42
+
43
+ # get the node in the hash ring for this key
44
+ def get_node(key)
45
+ get_node_pos(key)[0]
46
+ end
47
+
48
+ def get_node_pos(key)
49
+ return [nil,nil] if @ring.size == 0
50
+ crc = Zlib.crc32(key)
51
+ idx = HashRing.binary_search(@sorted_keys, crc)
52
+ return [@ring[@sorted_keys[idx]], idx]
53
+ end
54
+
55
+ def iter_nodes(key)
56
+ return [nil,nil] if @ring.size == 0
57
+ _, pos = get_node_pos(key)
58
+ @ring.size.times do |n|
59
+ yield @ring[@sorted_keys[(pos+n) % @ring.size]]
60
+ end
61
+ end
62
+
63
+ # Find the closest index in HashRing with value <= the given value
64
+ def self.binary_search(ary, value, &block)
65
+ upper = ary.size - 1
66
+ lower = 0
67
+ idx = 0
68
+
69
+ while(lower <= upper) do
70
+ idx = (lower + upper) / 2
71
+ comp = ary[idx] <=> value
72
+
73
+ if comp == 0
74
+ return idx
75
+ elsif comp > 0
76
+ upper = idx - 1
77
+ else
78
+ lower = idx + 1
79
+ end
80
+ end
81
+
82
+ if upper < 0
83
+ upper = ary.size - 1
84
+ end
85
+ return upper
86
+ end
87
+ end
88
+ end
@@ -1,3 +1,3 @@
1
1
  class Redis
2
- VERSION = "4.0.0.rc1"
2
+ VERSION = "4.0.0"
3
3
  end
@@ -34,7 +34,9 @@ Gem::Specification.new do |s|
34
34
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
35
35
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
36
36
 
37
- s.add_development_dependency("test-unit", "3.1.5")
37
+ s.required_ruby_version = '>= 2.2.2'
38
+
39
+ s.add_development_dependency("test-unit", ">= 3.1.5")
38
40
  s.add_development_dependency("hiredis")
39
41
  s.add_development_dependency("em-synchrony")
40
42
  end
@@ -1,153 +1,10 @@
1
1
  require_relative "helper"
2
+ require_relative "lint/blocking_commands"
2
3
 
3
4
  class TestBlockingCommands < Test::Unit::TestCase
4
5
 
5
6
  include Helper::Client
6
-
7
- def setup
8
- super
9
-
10
- r.rpush("{zap}foo", "s1")
11
- r.rpush("{zap}foo", "s2")
12
- r.rpush("{zap}bar", "s1")
13
- r.rpush("{zap}bar", "s2")
14
- end
15
-
16
- def to_protocol(obj)
17
- case obj
18
- when String
19
- "$#{obj.length}\r\n#{obj}\r\n"
20
- when Array
21
- "*#{obj.length}\r\n" + obj.map { |e| to_protocol(e) }.join
22
- else
23
- fail
24
- end
25
- end
26
-
27
- def mock(options = {}, &blk)
28
- commands = {
29
- :blpop => lambda do |*args|
30
- sleep options[:delay] if options.has_key?(:delay)
31
- to_protocol([args.first, args.last])
32
- end,
33
- :brpop => lambda do |*args|
34
- sleep options[:delay] if options.has_key?(:delay)
35
- to_protocol([args.first, args.last])
36
- end,
37
- :brpoplpush => lambda do |*args|
38
- sleep options[:delay] if options.has_key?(:delay)
39
- to_protocol(args.last)
40
- end
41
- }
42
-
43
- redis_mock(commands, &blk)
44
- end
45
-
46
- def test_blpop
47
- assert_equal ["{zap}foo", "s1"], r.blpop("{zap}foo")
48
- assert_equal ["{zap}foo", "s2"], r.blpop(["{zap}foo"])
49
- assert_equal ["{zap}bar", "s1"], r.blpop(["{zap}bar", "{zap}foo"])
50
- assert_equal ["{zap}bar", "s2"], r.blpop(["{zap}foo", "{zap}bar"])
51
- end
52
-
53
- def test_blpop_timeout
54
- mock do |r|
55
- assert_equal ["{zap}foo", "0"], r.blpop("{zap}foo")
56
- assert_equal ["{zap}foo", "1"], r.blpop("{zap}foo", :timeout => 1)
57
- end
58
- end
59
-
60
- def test_blpop_with_old_prototype
61
- assert_equal ["{zap}foo", "s1"], r.blpop("{zap}foo", 0)
62
- assert_equal ["{zap}foo", "s2"], r.blpop("{zap}foo", 0)
63
- assert_equal ["{zap}bar", "s1"], r.blpop("{zap}bar", "{zap}foo", 0)
64
- assert_equal ["{zap}bar", "s2"], r.blpop("{zap}foo", "{zap}bar", 0)
65
- end
66
-
67
- def test_blpop_timeout_with_old_prototype
68
- mock do |r|
69
- assert_equal ["{zap}foo", "0"], r.blpop("{zap}foo", 0)
70
- assert_equal ["{zap}foo", "1"], r.blpop("{zap}foo", 1)
71
- end
72
- end
73
-
74
- def test_brpop
75
- assert_equal ["{zap}foo", "s2"], r.brpop("{zap}foo")
76
- assert_equal ["{zap}foo", "s1"], r.brpop(["{zap}foo"])
77
- assert_equal ["{zap}bar", "s2"], r.brpop(["{zap}bar", "{zap}foo"])
78
- assert_equal ["{zap}bar", "s1"], r.brpop(["{zap}foo", "{zap}bar"])
79
- end
80
-
81
- def test_brpop_timeout
82
- mock do |r|
83
- assert_equal ["{zap}foo", "0"], r.brpop("{zap}foo")
84
- assert_equal ["{zap}foo", "1"], r.brpop("{zap}foo", :timeout => 1)
85
- end
86
- end
87
-
88
- def test_brpop_with_old_prototype
89
- assert_equal ["{zap}foo", "s2"], r.brpop("{zap}foo", 0)
90
- assert_equal ["{zap}foo", "s1"], r.brpop("{zap}foo", 0)
91
- assert_equal ["{zap}bar", "s2"], r.brpop("{zap}bar", "{zap}foo", 0)
92
- assert_equal ["{zap}bar", "s1"], r.brpop("{zap}foo", "{zap}bar", 0)
93
- end
94
-
95
- def test_brpop_timeout_with_old_prototype
96
- mock do |r|
97
- assert_equal ["{zap}foo", "0"], r.brpop("{zap}foo", 0)
98
- assert_equal ["{zap}foo", "1"], r.brpop("{zap}foo", 1)
99
- end
100
- end
101
-
102
- def test_brpoplpush
103
- assert_equal "s2", r.brpoplpush("{zap}foo", "{zap}qux")
104
- assert_equal ["s2"], r.lrange("{zap}qux", 0, -1)
105
- end
106
-
107
- def test_brpoplpush_timeout
108
- mock do |r|
109
- assert_equal "0", r.brpoplpush("{zap}foo", "{zap}bar")
110
- assert_equal "1", r.brpoplpush("{zap}foo", "{zap}bar", :timeout => 1)
111
- end
112
- end
113
-
114
- def test_brpoplpush_with_old_prototype
115
- assert_equal "s2", r.brpoplpush("{zap}foo", "{zap}qux", 0)
116
- assert_equal ["s2"], r.lrange("{zap}qux", 0, -1)
117
- end
118
-
119
- def test_brpoplpush_timeout_with_old_prototype
120
- mock do |r|
121
- assert_equal "0", r.brpoplpush("{zap}foo", "{zap}bar", 0)
122
- assert_equal "1", r.brpoplpush("{zap}foo", "{zap}bar", 1)
123
- end
124
- end
125
-
126
- driver(:ruby, :hiredis) do
127
- def test_blpop_socket_timeout
128
- mock(:delay => 1 + OPTIONS[:timeout] * 2) do |r|
129
- assert_raises(Redis::TimeoutError) do
130
- r.blpop("{zap}foo", :timeout => 1)
131
- end
132
- end
133
- end
134
-
135
- def test_brpop_socket_timeout
136
- mock(:delay => 1 + OPTIONS[:timeout] * 2) do |r|
137
- assert_raises(Redis::TimeoutError) do
138
- r.brpop("{zap}foo", :timeout => 1)
139
- end
140
- end
141
- end
142
-
143
- def test_brpoplpush_socket_timeout
144
- mock(:delay => 1 + OPTIONS[:timeout] * 2) do |r|
145
- assert_raises(Redis::TimeoutError) do
146
- r.brpoplpush("{zap}foo", "{zap}bar", :timeout => 1)
147
- end
148
- end
149
- end
150
- end
7
+ include Lint::BlockingCommands
151
8
 
152
9
  def assert_takes_longer_than_client_timeout
153
10
  timeout = OPTIONS[:timeout]
@@ -1,165 +1,10 @@
1
1
  require_relative "helper"
2
+ require_relative "lint/hashes"
2
3
 
3
4
  class TestCommandsOnHashes < Test::Unit::TestCase
4
5
 
5
6
  include Helper::Client
6
-
7
- def test_hset_and_hget
8
- r.hset("foo", "f1", "s1")
9
-
10
- assert_equal "s1", r.hget("foo", "f1")
11
- end
12
-
13
- def test_hsetnx
14
- r.hset("foo", "f1", "s1")
15
- r.hsetnx("foo", "f1", "s2")
16
-
17
- assert_equal "s1", r.hget("foo", "f1")
18
-
19
- r.del("foo")
20
- r.hsetnx("foo", "f1", "s2")
21
-
22
- assert_equal "s2", r.hget("foo", "f1")
23
- end
24
-
25
- def test_hdel
26
- r.hset("foo", "f1", "s1")
27
-
28
- assert_equal "s1", r.hget("foo", "f1")
29
-
30
- assert_equal 1, r.hdel("foo", "f1")
31
-
32
- assert_equal nil, r.hget("foo", "f1")
33
- end
34
-
35
- def test_variadic_hdel
36
- target_version "2.3.9" do
37
- r.hset("foo", "f1", "s1")
38
- r.hset("foo", "f2", "s2")
39
-
40
- assert_equal "s1", r.hget("foo", "f1")
41
- assert_equal "s2", r.hget("foo", "f2")
42
-
43
- assert_equal 2, r.hdel("foo", ["f1", "f2"])
44
-
45
- assert_equal nil, r.hget("foo", "f1")
46
- assert_equal nil, r.hget("foo", "f2")
47
- end
48
- end
49
-
50
- def test_hexists
51
- assert_equal false, r.hexists("foo", "f1")
52
-
53
- r.hset("foo", "f1", "s1")
54
-
55
- assert r.hexists("foo", "f1")
56
- end
57
-
58
- def test_hlen
59
- assert_equal 0, r.hlen("foo")
60
-
61
- r.hset("foo", "f1", "s1")
62
-
63
- assert_equal 1, r.hlen("foo")
64
-
65
- r.hset("foo", "f2", "s2")
66
-
67
- assert_equal 2, r.hlen("foo")
68
- end
69
-
70
- def test_hkeys
71
- assert_equal [], r.hkeys("foo")
72
-
73
- r.hset("foo", "f1", "s1")
74
- r.hset("foo", "f2", "s2")
75
-
76
- assert_equal ["f1", "f2"], r.hkeys("foo")
77
- end
78
-
79
- def test_hvals
80
- assert_equal [], r.hvals("foo")
81
-
82
- r.hset("foo", "f1", "s1")
83
- r.hset("foo", "f2", "s2")
84
-
85
- assert_equal ["s1", "s2"], r.hvals("foo")
86
- end
87
-
88
- def test_hgetall
89
- assert({} == r.hgetall("foo"))
90
-
91
- r.hset("foo", "f1", "s1")
92
- r.hset("foo", "f2", "s2")
93
-
94
- assert({"f1" => "s1", "f2" => "s2"} == r.hgetall("foo"))
95
- end
96
-
97
- def test_hmset
98
- r.hmset("hash", "foo1", "bar1", "foo2", "bar2")
99
-
100
- assert_equal "bar1", r.hget("hash", "foo1")
101
- assert_equal "bar2", r.hget("hash", "foo2")
102
- end
103
-
104
- def test_hmset_with_invalid_arguments
105
- assert_raise(Redis::CommandError) do
106
- r.hmset("hash", "foo1", "bar1", "foo2", "bar2", "foo3")
107
- end
108
- end
109
-
110
- def test_mapped_hmset
111
- r.mapped_hmset("foo", :f1 => "s1", :f2 => "s2")
112
-
113
- assert_equal "s1", r.hget("foo", "f1")
114
- assert_equal "s2", r.hget("foo", "f2")
115
- end
116
-
117
- def test_hmget
118
- r.hset("foo", "f1", "s1")
119
- r.hset("foo", "f2", "s2")
120
- r.hset("foo", "f3", "s3")
121
-
122
- assert_equal ["s2", "s3"], r.hmget("foo", "f2", "f3")
123
- end
124
-
125
- def test_hmget_mapped
126
- r.hset("foo", "f1", "s1")
127
- r.hset("foo", "f2", "s2")
128
- r.hset("foo", "f3", "s3")
129
-
130
- assert({"f1" => "s1"} == r.mapped_hmget("foo", "f1"))
131
- assert({"f1" => "s1", "f2" => "s2"} == r.mapped_hmget("foo", "f1", "f2"))
132
- end
133
-
134
- def test_hincrby
135
- r.hincrby("foo", "f1", 1)
136
-
137
- assert_equal "1", r.hget("foo", "f1")
138
-
139
- r.hincrby("foo", "f1", 2)
140
-
141
- assert_equal "3", r.hget("foo", "f1")
142
-
143
- r.hincrby("foo", "f1", -1)
144
-
145
- assert_equal "2", r.hget("foo", "f1")
146
- end
147
-
148
- def test_hincrbyfloat
149
- target_version "2.5.4" do
150
- r.hincrbyfloat("foo", "f1", 1.23)
151
-
152
- assert_equal "1.23", r.hget("foo", "f1")
153
-
154
- r.hincrbyfloat("foo", "f1", 0.77)
155
-
156
- assert_equal "2", r.hget("foo", "f1")
157
-
158
- r.hincrbyfloat("foo", "f1", -0.1)
159
-
160
- assert_equal "1.9", r.hget("foo", "f1")
161
- end
162
- end
7
+ include Lint::Hashes
163
8
 
164
9
  def test_mapped_hmget_in_a_pipeline_returns_hash
165
10
  r.hset("foo", "f1", "s1")
@@ -1,61 +1,10 @@
1
1
  require_relative "helper"
2
+ require_relative "lint/hyper_log_log"
2
3
 
3
4
  class TestCommandsOnHyperLogLog < Test::Unit::TestCase
4
5
 
5
6
  include Helper::Client
6
-
7
- def test_pfadd
8
- target_version "2.8.9" do
9
- assert_equal true, r.pfadd("foo", "s1")
10
- assert_equal true, r.pfadd("foo", "s2")
11
- assert_equal false, r.pfadd("foo", "s1")
12
-
13
- assert_equal 2, r.pfcount("foo")
14
- end
15
- end
16
-
17
- def test_variadic_pfadd
18
- target_version "2.8.9" do
19
- assert_equal true, r.pfadd("foo", ["s1", "s2"])
20
- assert_equal true, r.pfadd("foo", ["s1", "s2", "s3"])
21
-
22
- assert_equal 3, r.pfcount("foo")
23
- end
24
- end
25
-
26
- def test_pfcount
27
- target_version "2.8.9" do
28
- assert_equal 0, r.pfcount("foo")
29
-
30
- assert_equal true, r.pfadd("foo", "s1")
31
-
32
- assert_equal 1, r.pfcount("foo")
33
- end
34
- end
35
-
36
- def test_variadic_pfcount
37
- target_version "2.8.9" do
38
- assert_equal 0, r.pfcount(["{1}foo", "{1}bar"])
39
-
40
- assert_equal true, r.pfadd("{1}foo", "s1")
41
- assert_equal true, r.pfadd("{1}bar", "s1")
42
- assert_equal true, r.pfadd("{1}bar", "s2")
43
-
44
- assert_equal 2, r.pfcount("{1}foo", "{1}bar")
45
- end
46
- end
47
-
48
- def test_variadic_pfcount_expanded
49
- target_version "2.8.9" do
50
- assert_equal 0, r.pfcount("{1}foo", "{1}bar")
51
-
52
- assert_equal true, r.pfadd("{1}foo", "s1")
53
- assert_equal true, r.pfadd("{1}bar", "s1")
54
- assert_equal true, r.pfadd("{1}bar", "s2")
55
-
56
- assert_equal 2, r.pfcount("{1}foo", "{1}bar")
57
- end
58
- end
7
+ include Lint::HyperLogLog
59
8
 
60
9
  def test_pfmerge
61
10
  target_version "2.8.9" do