redis 2.1.1 → 2.2.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 (79) hide show
  1. data/.gitignore +8 -0
  2. data/CHANGELOG.md +34 -0
  3. data/README.md +190 -0
  4. data/Rakefile +194 -79
  5. data/benchmarking/logging.rb +62 -0
  6. data/benchmarking/pipeline.rb +51 -0
  7. data/benchmarking/speed.rb +21 -0
  8. data/benchmarking/suite.rb +24 -0
  9. data/benchmarking/thread_safety.rb +38 -0
  10. data/benchmarking/worker.rb +71 -0
  11. data/examples/basic.rb +15 -0
  12. data/examples/dist_redis.rb +43 -0
  13. data/examples/incr-decr.rb +17 -0
  14. data/examples/list.rb +26 -0
  15. data/examples/pubsub.rb +31 -0
  16. data/examples/sets.rb +36 -0
  17. data/examples/unicorn/config.ru +3 -0
  18. data/examples/unicorn/unicorn.rb +20 -0
  19. data/lib/redis.rb +612 -156
  20. data/lib/redis/client.rb +98 -57
  21. data/lib/redis/connection.rb +9 -134
  22. data/lib/redis/connection/command_helper.rb +45 -0
  23. data/lib/redis/connection/hiredis.rb +49 -0
  24. data/lib/redis/connection/registry.rb +12 -0
  25. data/lib/redis/connection/ruby.rb +131 -0
  26. data/lib/redis/connection/synchrony.rb +125 -0
  27. data/lib/redis/distributed.rb +161 -5
  28. data/lib/redis/pipeline.rb +6 -0
  29. data/lib/redis/version.rb +3 -0
  30. data/redis.gemspec +24 -0
  31. data/test/commands_on_hashes_test.rb +32 -0
  32. data/test/commands_on_lists_test.rb +60 -0
  33. data/test/commands_on_sets_test.rb +78 -0
  34. data/test/commands_on_sorted_sets_test.rb +109 -0
  35. data/test/commands_on_strings_test.rb +80 -0
  36. data/test/commands_on_value_types_test.rb +88 -0
  37. data/test/connection_handling_test.rb +87 -0
  38. data/test/db/.gitignore +1 -0
  39. data/test/distributed_blocking_commands_test.rb +53 -0
  40. data/test/distributed_commands_on_hashes_test.rb +12 -0
  41. data/test/distributed_commands_on_lists_test.rb +24 -0
  42. data/test/distributed_commands_on_sets_test.rb +85 -0
  43. data/test/distributed_commands_on_strings_test.rb +50 -0
  44. data/test/distributed_commands_on_value_types_test.rb +73 -0
  45. data/test/distributed_commands_requiring_clustering_test.rb +148 -0
  46. data/test/distributed_connection_handling_test.rb +25 -0
  47. data/test/distributed_internals_test.rb +18 -0
  48. data/test/distributed_key_tags_test.rb +53 -0
  49. data/test/distributed_persistence_control_commands_test.rb +24 -0
  50. data/test/distributed_publish_subscribe_test.rb +101 -0
  51. data/test/distributed_remote_server_control_commands_test.rb +31 -0
  52. data/test/distributed_sorting_test.rb +21 -0
  53. data/test/distributed_test.rb +60 -0
  54. data/test/distributed_transactions_test.rb +34 -0
  55. data/test/encoding_test.rb +16 -0
  56. data/test/error_replies_test.rb +53 -0
  57. data/test/helper.rb +145 -0
  58. data/test/internals_test.rb +157 -0
  59. data/test/lint/hashes.rb +114 -0
  60. data/test/lint/internals.rb +41 -0
  61. data/test/lint/lists.rb +93 -0
  62. data/test/lint/sets.rb +66 -0
  63. data/test/lint/sorted_sets.rb +167 -0
  64. data/test/lint/strings.rb +137 -0
  65. data/test/lint/value_types.rb +84 -0
  66. data/test/persistence_control_commands_test.rb +22 -0
  67. data/test/pipelining_commands_test.rb +123 -0
  68. data/test/publish_subscribe_test.rb +158 -0
  69. data/test/redis_mock.rb +80 -0
  70. data/test/remote_server_control_commands_test.rb +63 -0
  71. data/test/sorting_test.rb +44 -0
  72. data/test/synchrony_driver.rb +57 -0
  73. data/test/test.conf +8 -0
  74. data/test/thread_safety_test.rb +30 -0
  75. data/test/transactions_test.rb +100 -0
  76. data/test/unknown_commands_test.rb +14 -0
  77. data/test/url_param_test.rb +60 -0
  78. metadata +128 -19
  79. data/README.markdown +0 -129
@@ -0,0 +1,25 @@
1
+ # encoding: UTF-8
2
+
3
+ require File.expand_path("./helper", File.dirname(__FILE__))
4
+ require "redis/distributed"
5
+
6
+ setup do
7
+ log = StringIO.new
8
+ init Redis::Distributed.new(NODES, :logger => ::Logger.new(log))
9
+ end
10
+
11
+ test "PING" do |r|
12
+ assert ["PONG"] == r.ping
13
+ end
14
+
15
+ test "SELECT" do |r|
16
+ r.set "foo", "bar"
17
+
18
+ r.select 14
19
+ assert nil == r.get("foo")
20
+
21
+ r.select 15
22
+
23
+ assert "bar" == r.get("foo")
24
+ end
25
+
@@ -0,0 +1,18 @@
1
+ # encoding: UTF-8
2
+
3
+ require File.expand_path("./helper", File.dirname(__FILE__))
4
+ require File.expand_path("./redis_mock", File.dirname(__FILE__))
5
+
6
+ include RedisMock::Helper
7
+
8
+ require "redis/distributed"
9
+
10
+ setup do
11
+ log = StringIO.new
12
+ [init(Redis::Distributed.new(NODES, :logger => ::Logger.new(log))), log]
13
+ end
14
+
15
+ $TEST_PIPELINING = false
16
+ $TEST_INSPECT = false
17
+
18
+ load File.expand_path("./lint/internals.rb", File.dirname(__FILE__))
@@ -0,0 +1,53 @@
1
+ # encoding: UTF-8
2
+
3
+ require File.expand_path("./helper", File.dirname(__FILE__))
4
+ require "redis/distributed"
5
+
6
+ setup do
7
+ log = StringIO.new
8
+ init Redis::Distributed.new(NODES, :logger => ::Logger.new(log))
9
+ end
10
+
11
+ test "hashes consistently" do
12
+ r1 = Redis::Distributed.new ["redis://localhost:6379/15", *NODES]
13
+ r2 = Redis::Distributed.new ["redis://localhost:6379/15", *NODES]
14
+ r3 = Redis::Distributed.new ["redis://localhost:6379/15", *NODES]
15
+
16
+ assert r1.node_for("foo").id == r2.node_for("foo").id
17
+ assert r1.node_for("foo").id == r3.node_for("foo").id
18
+ end
19
+
20
+ test "allows clustering of keys" do |r|
21
+ r = Redis::Distributed.new(NODES)
22
+ r.add_node("redis://localhost:6379/14")
23
+ r.flushdb
24
+
25
+ 100.times do |i|
26
+ r.set "{foo}users:#{i}", i
27
+ end
28
+
29
+ assert [0, 100] == r.nodes.map { |node| node.keys.size }
30
+ end
31
+
32
+ test "distributes keys if no clustering is used" do |r|
33
+ r.add_node("redis://localhost:6379/14")
34
+ r.flushdb
35
+
36
+ r.set "users:1", 1
37
+ r.set "users:4", 4
38
+
39
+ assert [1, 1] == r.nodes.map { |node| node.keys.size }
40
+ end
41
+
42
+ test "allows passing a custom tag extractor" do |r|
43
+ r = Redis::Distributed.new(NODES, :tag => /^(.+?):/)
44
+ r.add_node("redis://localhost:6379/14")
45
+ r.flushdb
46
+
47
+ 100.times do |i|
48
+ r.set "foo:users:#{i}", i
49
+ end
50
+
51
+ assert [0, 100] == r.nodes.map { |node| node.keys.size }
52
+ end
53
+
@@ -0,0 +1,24 @@
1
+ # encoding: UTF-8
2
+
3
+ require File.expand_path("./helper", File.dirname(__FILE__))
4
+ require "redis/distributed"
5
+
6
+ setup do
7
+ log = StringIO.new
8
+ init Redis::Distributed.new(NODES, :logger => ::Logger.new(log))
9
+ end
10
+
11
+ test "SAVE and BGSAVE" do |r|
12
+ assert_nothing_raised do
13
+ r.save
14
+ end
15
+
16
+ assert_nothing_raised do
17
+ r.bgsave
18
+ end
19
+ end
20
+
21
+ test "LASTSAVE" do |r|
22
+ assert r.lastsave.all? { |t| Time.at(t) <= Time.now }
23
+ end
24
+
@@ -0,0 +1,101 @@
1
+ # encoding: UTF-8
2
+
3
+ require File.expand_path("./helper", File.dirname(__FILE__))
4
+ require "redis/distributed"
5
+
6
+ setup do
7
+ log = StringIO.new
8
+ init Redis::Distributed.new(NODES, :logger => ::Logger.new(log))
9
+ end
10
+
11
+ test "SUBSCRIBE and UNSUBSCRIBE" do |r|
12
+ assert_raise Redis::Distributed::CannotDistribute do
13
+ r.subscribe("foo", "bar") { }
14
+ end
15
+
16
+ assert_raise Redis::Distributed::CannotDistribute do
17
+ r.subscribe("{qux}foo", "bar") { }
18
+ end
19
+ end
20
+
21
+ test "SUBSCRIBE and UNSUBSCRIBE with tags" do |r|
22
+ listening = false
23
+
24
+ wire = Wire.new do
25
+ r.subscribe("foo") do |on|
26
+ on.subscribe do |channel, total|
27
+ @subscribed = true
28
+ @t1 = total
29
+ end
30
+
31
+ on.message do |channel, message|
32
+ if message == "s1"
33
+ r.unsubscribe
34
+ @message = message
35
+ end
36
+ end
37
+
38
+ on.unsubscribe do |channel, total|
39
+ @unsubscribed = true
40
+ @t2 = total
41
+ end
42
+
43
+ listening = true
44
+ end
45
+ end
46
+
47
+ Wire.pass while !listening
48
+
49
+ Redis::Distributed.new(NODES).publish("foo", "s1")
50
+
51
+ wire.join
52
+
53
+ assert @subscribed
54
+ assert 1 == @t1
55
+ assert @unsubscribed
56
+ assert 0 == @t2
57
+ assert "s1" == @message
58
+ end
59
+
60
+ test "SUBSCRIBE within SUBSCRIBE" do |r|
61
+ listening = false
62
+ @channels = []
63
+
64
+ wire = Wire.new do
65
+ r.subscribe("foo") do |on|
66
+ on.subscribe do |channel, total|
67
+ @channels << channel
68
+
69
+ r.subscribe("bar") if channel == "foo"
70
+ r.unsubscribe if channel == "bar"
71
+ end
72
+
73
+ listening = true
74
+ end
75
+ end
76
+
77
+ Wire.pass while !listening
78
+
79
+ Redis::Distributed.new(NODES).publish("foo", "s1")
80
+
81
+ wire.join
82
+
83
+ assert ["foo", "bar"] == @channels
84
+ end
85
+
86
+ test "other commands within a SUBSCRIBE" do |r|
87
+ assert_raise RuntimeError do
88
+ r.subscribe("foo") do |on|
89
+ on.subscribe do |channel, total|
90
+ r.set("bar", "s2")
91
+ end
92
+ end
93
+ end
94
+ end
95
+
96
+ test "SUBSCRIBE without a block" do |r|
97
+ assert_raise LocalJumpError do
98
+ r.subscribe("foo")
99
+ end
100
+ end
101
+
@@ -0,0 +1,31 @@
1
+ # encoding: UTF-8
2
+
3
+ require File.expand_path("./helper", File.dirname(__FILE__))
4
+ require "redis/distributed"
5
+
6
+ setup do
7
+ log = StringIO.new
8
+ init Redis::Distributed.new(NODES, :logger => ::Logger.new(log))
9
+ end
10
+
11
+ test "INFO" do |r|
12
+ %w(last_save_time redis_version total_connections_received connected_clients total_commands_processed connected_slaves uptime_in_seconds used_memory uptime_in_days changes_since_last_save).each do |x|
13
+ r.info.each do |info|
14
+ assert info.keys.include?(x)
15
+ end
16
+ end
17
+ end
18
+
19
+ test "MONITOR" do |r|
20
+ begin
21
+ r.monitor
22
+ rescue Exception => ex
23
+ ensure
24
+ assert ex.kind_of?(NotImplementedError)
25
+ end
26
+ end
27
+
28
+ test "ECHO" do |r|
29
+ assert ["foo bar baz\n"] == r.echo("foo bar baz\n")
30
+ end
31
+
@@ -0,0 +1,21 @@
1
+ # encoding: UTF-8
2
+
3
+ require File.expand_path("./helper", File.dirname(__FILE__))
4
+ require "redis/distributed"
5
+
6
+ setup do
7
+ log = StringIO.new
8
+ init Redis::Distributed.new(NODES, :logger => ::Logger.new(log))
9
+ end
10
+
11
+ test "SORT" do |r|
12
+ assert_raise Redis::Distributed::CannotDistribute do
13
+ r.set("foo:1", "s1")
14
+ r.set("foo:2", "s2")
15
+
16
+ r.rpush("bar", "1")
17
+ r.rpush("bar", "2")
18
+
19
+ r.sort("bar", :get => "foo:*", :limit => [0, 1])
20
+ end
21
+ end
@@ -0,0 +1,60 @@
1
+ # encoding: UTF-8
2
+
3
+ require File.expand_path("./helper", File.dirname(__FILE__))
4
+ require "redis/distributed"
5
+
6
+ setup do
7
+ log = StringIO.new
8
+ init Redis::Distributed.new(NODES, :logger => ::Logger.new(log))
9
+ end
10
+
11
+ test "handle multiple servers" do
12
+ @r = Redis::Distributed.new ["redis://localhost:6379/15", *NODES]
13
+
14
+ 100.times do |idx|
15
+ @r.set(idx.to_s, "foo#{idx}")
16
+ end
17
+
18
+ 100.times do |idx|
19
+ assert "foo#{idx}" == @r.get(idx.to_s)
20
+ end
21
+
22
+ assert "0" == @r.keys("*").sort.first
23
+ assert "string" == @r.type("1")
24
+ end
25
+
26
+ test "add nodes" do
27
+ logger = Logger.new("/dev/null")
28
+
29
+ @r = Redis::Distributed.new NODES, :logger => logger, :timeout => 10
30
+
31
+ assert "127.0.0.1" == @r.nodes[0].client.host
32
+ assert 6379 == @r.nodes[0].client.port
33
+ assert 15 == @r.nodes[0].client.db
34
+ assert 10 == @r.nodes[0].client.timeout
35
+ assert logger == @r.nodes[0].client.logger
36
+
37
+ @r.add_node("redis://localhost:6380/14")
38
+
39
+ assert "localhost" == @r.nodes[1].client.host
40
+ assert 6380 == @r.nodes[1].client.port
41
+ assert 14 == @r.nodes[1].client.db
42
+ assert 10 == @r.nodes[1].client.timeout
43
+ assert logger == @r.nodes[1].client.logger
44
+ end
45
+
46
+ test "Pipelining commands cannot be distributed" do |r|
47
+ assert_raise Redis::Distributed::CannotDistribute do
48
+ r.pipelined do
49
+ r.lpush "foo", "s1"
50
+ r.lpush "foo", "s2"
51
+ end
52
+ end
53
+ end
54
+
55
+ test "Unknown commands does not work by default" do |r|
56
+ assert_raise NoMethodError do
57
+ r.not_yet_implemented_command
58
+ end
59
+ end
60
+
@@ -0,0 +1,34 @@
1
+ # encoding: UTF-8
2
+
3
+ require File.expand_path("./helper", File.dirname(__FILE__))
4
+ require "redis/distributed"
5
+
6
+ setup do
7
+ log = StringIO.new
8
+ init Redis::Distributed.new(NODES, :logger => ::Logger.new(log))
9
+ end
10
+
11
+ test "MULTI/DISCARD" do |r|
12
+ @foo = nil
13
+
14
+ assert_raise Redis::Distributed::CannotDistribute do
15
+ r.multi { @foo = 1 }
16
+ end
17
+
18
+ assert nil == @foo
19
+
20
+ assert_raise Redis::Distributed::CannotDistribute do
21
+ r.discard
22
+ end
23
+ end
24
+
25
+ test "WATCH/UNWATCH" do |r|
26
+ assert_raise Redis::Distributed::CannotDistribute do
27
+ r.watch("foo")
28
+ end
29
+
30
+ assert_raise Redis::Distributed::CannotDistribute do
31
+ r.unwatch
32
+ end
33
+ end
34
+
@@ -0,0 +1,16 @@
1
+ # encoding: UTF-8
2
+
3
+ require File.expand_path("./helper", File.dirname(__FILE__))
4
+
5
+ setup do
6
+ init Redis.new(OPTIONS)
7
+ end
8
+
9
+ test "returns properly encoded strings" do |r|
10
+ with_external_encoding("UTF-8") do
11
+ r.set "foo", "שלום"
12
+
13
+ assert "Shalom שלום" == "Shalom " + r.get("foo")
14
+ end
15
+ end if defined?(Encoding)
16
+
@@ -0,0 +1,53 @@
1
+ # encoding: UTF-8
2
+
3
+ require File.expand_path("./helper", File.dirname(__FILE__))
4
+
5
+ setup do
6
+ init Redis.new(OPTIONS)
7
+ end
8
+
9
+ # Every test shouldn't disconnect from the server. Also, when error replies are
10
+ # in play, the protocol should never get into an invalid state where there are
11
+ # pending replies in the connection. Calling INFO after every test ensures that
12
+ # the protocol is still in a valid state.
13
+ def test_with_reconnection_check(title)
14
+ test(title) do |r|
15
+ before = r.info["total_connections_received"]
16
+ yield(r)
17
+ after = r.info["total_connections_received"]
18
+ assert before == after
19
+ end
20
+ end
21
+
22
+ test_with_reconnection_check "Error reply for single command" do |r|
23
+ begin
24
+ r.unknown_command
25
+ rescue => ex
26
+ ensure
27
+ assert ex.message =~ /unknown command/i
28
+ end
29
+ end
30
+
31
+ test_with_reconnection_check "Raise first error reply in pipeline" do |r|
32
+ begin
33
+ r.pipelined do
34
+ r.set("foo", "s1")
35
+ r.incr("foo") # not an integer
36
+ r.lpush("foo", "value") # wrong kind of value
37
+ end
38
+ rescue => ex
39
+ ensure
40
+ assert ex.message =~ /not an integer/i
41
+ end
42
+ end
43
+
44
+ test_with_reconnection_check "Recover from raise in #call_loop" do |r|
45
+ begin
46
+ r.client.call_loop(:invalid_monitor) do
47
+ assert false # Should never be executed
48
+ end
49
+ rescue => ex
50
+ ensure
51
+ assert ex.message =~ /unknown command/i
52
+ end
53
+ end