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.
- data/.gitignore +8 -0
- data/CHANGELOG.md +34 -0
- data/README.md +190 -0
- data/Rakefile +194 -79
- data/benchmarking/logging.rb +62 -0
- data/benchmarking/pipeline.rb +51 -0
- data/benchmarking/speed.rb +21 -0
- data/benchmarking/suite.rb +24 -0
- data/benchmarking/thread_safety.rb +38 -0
- data/benchmarking/worker.rb +71 -0
- data/examples/basic.rb +15 -0
- data/examples/dist_redis.rb +43 -0
- data/examples/incr-decr.rb +17 -0
- data/examples/list.rb +26 -0
- data/examples/pubsub.rb +31 -0
- data/examples/sets.rb +36 -0
- data/examples/unicorn/config.ru +3 -0
- data/examples/unicorn/unicorn.rb +20 -0
- data/lib/redis.rb +612 -156
- data/lib/redis/client.rb +98 -57
- data/lib/redis/connection.rb +9 -134
- data/lib/redis/connection/command_helper.rb +45 -0
- data/lib/redis/connection/hiredis.rb +49 -0
- data/lib/redis/connection/registry.rb +12 -0
- data/lib/redis/connection/ruby.rb +131 -0
- data/lib/redis/connection/synchrony.rb +125 -0
- data/lib/redis/distributed.rb +161 -5
- data/lib/redis/pipeline.rb +6 -0
- data/lib/redis/version.rb +3 -0
- data/redis.gemspec +24 -0
- data/test/commands_on_hashes_test.rb +32 -0
- data/test/commands_on_lists_test.rb +60 -0
- data/test/commands_on_sets_test.rb +78 -0
- data/test/commands_on_sorted_sets_test.rb +109 -0
- data/test/commands_on_strings_test.rb +80 -0
- data/test/commands_on_value_types_test.rb +88 -0
- data/test/connection_handling_test.rb +87 -0
- data/test/db/.gitignore +1 -0
- data/test/distributed_blocking_commands_test.rb +53 -0
- data/test/distributed_commands_on_hashes_test.rb +12 -0
- data/test/distributed_commands_on_lists_test.rb +24 -0
- data/test/distributed_commands_on_sets_test.rb +85 -0
- data/test/distributed_commands_on_strings_test.rb +50 -0
- data/test/distributed_commands_on_value_types_test.rb +73 -0
- data/test/distributed_commands_requiring_clustering_test.rb +148 -0
- data/test/distributed_connection_handling_test.rb +25 -0
- data/test/distributed_internals_test.rb +18 -0
- data/test/distributed_key_tags_test.rb +53 -0
- data/test/distributed_persistence_control_commands_test.rb +24 -0
- data/test/distributed_publish_subscribe_test.rb +101 -0
- data/test/distributed_remote_server_control_commands_test.rb +31 -0
- data/test/distributed_sorting_test.rb +21 -0
- data/test/distributed_test.rb +60 -0
- data/test/distributed_transactions_test.rb +34 -0
- data/test/encoding_test.rb +16 -0
- data/test/error_replies_test.rb +53 -0
- data/test/helper.rb +145 -0
- data/test/internals_test.rb +157 -0
- data/test/lint/hashes.rb +114 -0
- data/test/lint/internals.rb +41 -0
- data/test/lint/lists.rb +93 -0
- data/test/lint/sets.rb +66 -0
- data/test/lint/sorted_sets.rb +167 -0
- data/test/lint/strings.rb +137 -0
- data/test/lint/value_types.rb +84 -0
- data/test/persistence_control_commands_test.rb +22 -0
- data/test/pipelining_commands_test.rb +123 -0
- data/test/publish_subscribe_test.rb +158 -0
- data/test/redis_mock.rb +80 -0
- data/test/remote_server_control_commands_test.rb +63 -0
- data/test/sorting_test.rb +44 -0
- data/test/synchrony_driver.rb +57 -0
- data/test/test.conf +8 -0
- data/test/thread_safety_test.rb +30 -0
- data/test/transactions_test.rb +100 -0
- data/test/unknown_commands_test.rb +14 -0
- data/test/url_param_test.rb +60 -0
- metadata +128 -19
- 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
|