yam-redis-with-retries 2.2.2.1

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 (81) hide show
  1. data/CHANGELOG.md +53 -0
  2. data/LICENSE +20 -0
  3. data/README.md +208 -0
  4. data/Rakefile +277 -0
  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 +1166 -0
  20. data/lib/redis/client.rb +265 -0
  21. data/lib/redis/compat.rb +21 -0
  22. data/lib/redis/connection.rb +9 -0
  23. data/lib/redis/connection/command_helper.rb +45 -0
  24. data/lib/redis/connection/hiredis.rb +49 -0
  25. data/lib/redis/connection/registry.rb +12 -0
  26. data/lib/redis/connection/ruby.rb +135 -0
  27. data/lib/redis/connection/synchrony.rb +129 -0
  28. data/lib/redis/distributed.rb +694 -0
  29. data/lib/redis/hash_ring.rb +131 -0
  30. data/lib/redis/pipeline.rb +34 -0
  31. data/lib/redis/retry.rb +128 -0
  32. data/lib/redis/subscribe.rb +94 -0
  33. data/lib/redis/version.rb +3 -0
  34. data/test/commands_on_hashes_test.rb +20 -0
  35. data/test/commands_on_lists_test.rb +60 -0
  36. data/test/commands_on_sets_test.rb +78 -0
  37. data/test/commands_on_sorted_sets_test.rb +109 -0
  38. data/test/commands_on_strings_test.rb +80 -0
  39. data/test/commands_on_value_types_test.rb +88 -0
  40. data/test/connection_handling_test.rb +88 -0
  41. data/test/distributed_blocking_commands_test.rb +53 -0
  42. data/test/distributed_commands_on_hashes_test.rb +12 -0
  43. data/test/distributed_commands_on_lists_test.rb +24 -0
  44. data/test/distributed_commands_on_sets_test.rb +85 -0
  45. data/test/distributed_commands_on_strings_test.rb +50 -0
  46. data/test/distributed_commands_on_value_types_test.rb +73 -0
  47. data/test/distributed_commands_requiring_clustering_test.rb +148 -0
  48. data/test/distributed_connection_handling_test.rb +25 -0
  49. data/test/distributed_internals_test.rb +27 -0
  50. data/test/distributed_key_tags_test.rb +53 -0
  51. data/test/distributed_persistence_control_commands_test.rb +24 -0
  52. data/test/distributed_publish_subscribe_test.rb +101 -0
  53. data/test/distributed_remote_server_control_commands_test.rb +43 -0
  54. data/test/distributed_sorting_test.rb +21 -0
  55. data/test/distributed_test.rb +59 -0
  56. data/test/distributed_transactions_test.rb +34 -0
  57. data/test/encoding_test.rb +16 -0
  58. data/test/error_replies_test.rb +53 -0
  59. data/test/helper.rb +145 -0
  60. data/test/internals_test.rb +163 -0
  61. data/test/lint/hashes.rb +126 -0
  62. data/test/lint/internals.rb +37 -0
  63. data/test/lint/lists.rb +93 -0
  64. data/test/lint/sets.rb +66 -0
  65. data/test/lint/sorted_sets.rb +167 -0
  66. data/test/lint/strings.rb +137 -0
  67. data/test/lint/value_types.rb +84 -0
  68. data/test/persistence_control_commands_test.rb +22 -0
  69. data/test/pipelining_commands_test.rb +123 -0
  70. data/test/publish_subscribe_test.rb +158 -0
  71. data/test/redis_mock.rb +80 -0
  72. data/test/remote_server_control_commands_test.rb +82 -0
  73. data/test/retry_test.rb +225 -0
  74. data/test/sorting_test.rb +44 -0
  75. data/test/synchrony_driver.rb +57 -0
  76. data/test/test.conf +8 -0
  77. data/test/thread_safety_test.rb +30 -0
  78. data/test/transactions_test.rb +100 -0
  79. data/test/unknown_commands_test.rb +14 -0
  80. data/test/url_param_test.rb +60 -0
  81. metadata +215 -0
data/test/helper.rb ADDED
@@ -0,0 +1,145 @@
1
+ $:.unshift File.expand_path('../lib', File.dirname(__FILE__))
2
+
3
+ require "cutest"
4
+ require "logger"
5
+ require "stringio"
6
+
7
+ begin
8
+ require "ruby-debug"
9
+ rescue LoadError
10
+ end
11
+
12
+ PORT = 6379
13
+ OPTIONS = {:port => PORT, :db => 15, :timeout => 3}
14
+ NODES = ["redis://127.0.0.1:6379/15"]
15
+
16
+ def init(redis)
17
+ begin
18
+ redis.flushdb
19
+ redis.select 14
20
+ redis.flushdb
21
+ redis.select 15
22
+ redis
23
+ rescue Errno::ECONNREFUSED
24
+ puts <<-EOS
25
+
26
+ Cannot connect to Redis.
27
+
28
+ Make sure Redis is running on localhost, port 6379.
29
+ This testing suite connects to the database 15.
30
+
31
+ To install redis:
32
+ visit <http://code.google.com/p/redis/>.
33
+
34
+ To start the server:
35
+ rake start
36
+
37
+ To stop the server:
38
+ rake stop
39
+
40
+ EOS
41
+ exit 1
42
+ end
43
+ end
44
+
45
+ $VERBOSE = true
46
+
47
+ require "redis"
48
+
49
+ def driver
50
+ Redis::Connection.drivers.last.to_s.split("::").last.downcase.to_sym
51
+ end
52
+
53
+ if driver == :synchrony
54
+ # Make cutest fiber + eventmachine aware if the synchrony driver is used.
55
+ undef test if defined? test
56
+ def test(name = nil, &block)
57
+ cutest[:test] = name
58
+
59
+ blk = Proc.new do
60
+ prepare.each { |blk| blk.call }
61
+ block.call(setup && setup.call)
62
+ end
63
+
64
+ t = Thread.current[:cutest]
65
+ if defined? EventMachine
66
+ EM.synchrony do
67
+ Thread.current[:cutest] = t
68
+ blk.call
69
+ EM.stop
70
+ end
71
+ else
72
+ blk.call
73
+ end
74
+ end
75
+
76
+ class Wire < Fiber
77
+ # We cannot run this fiber explicitly because EM schedules it. Resuming the
78
+ # current fiber on the next tick to let the reactor do work.
79
+ def self.pass
80
+ f = Fiber.current
81
+ EM.next_tick { f.resume }
82
+ Fiber.yield
83
+ end
84
+
85
+ def self.sleep(sec)
86
+ EM::Synchrony.sleep(sec)
87
+ end
88
+
89
+ def initialize(&blk)
90
+ super
91
+
92
+ # Schedule run in next tick
93
+ EM.next_tick { resume }
94
+ end
95
+
96
+ def join
97
+ self.class.pass while alive?
98
+ end
99
+ end
100
+ else
101
+ class Wire < Thread
102
+ def self.sleep(sec)
103
+ Kernel.sleep(sec)
104
+ end
105
+ end
106
+ end
107
+
108
+ def capture_stderr
109
+ stderr = $stderr
110
+ $stderr = StringIO.new
111
+
112
+ yield
113
+
114
+ $stderr = stderr
115
+ end
116
+
117
+ def silent
118
+ verbose, $VERBOSE = $VERBOSE, false
119
+
120
+ begin
121
+ yield
122
+ ensure
123
+ $VERBOSE = verbose
124
+ end
125
+ end
126
+
127
+ def with_external_encoding(encoding)
128
+ original_encoding = Encoding.default_external
129
+
130
+ begin
131
+ silent { Encoding.default_external = Encoding.find(encoding) }
132
+ yield
133
+ ensure
134
+ silent { Encoding.default_external = original_encoding }
135
+ end
136
+ end
137
+
138
+ def assert_nothing_raised(*exceptions)
139
+ begin
140
+ yield
141
+ rescue *exceptions
142
+ flunk(caller[1])
143
+ end
144
+ end
145
+
@@ -0,0 +1,163 @@
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
+ setup do
9
+ log = StringIO.new
10
+
11
+ [Redis.new(OPTIONS.merge(:logger => ::Logger.new(log))), log]
12
+ end
13
+
14
+ $TEST_PIPELINING = true
15
+
16
+ load File.expand_path("./lint/internals.rb", File.dirname(__FILE__))
17
+
18
+ test "provides a meaningful inspect" do |r, _|
19
+ assert "#<Redis client v#{Redis::VERSION} connected to redis://127.0.0.1:6379/15 (Redis v#{r.info["redis_version"]})>" == r.inspect
20
+ end
21
+
22
+ test "Redis.current" do
23
+ Redis.current.set("foo", "bar")
24
+
25
+ assert "bar" == Redis.current.get("foo")
26
+
27
+ Redis.current = Redis.new(OPTIONS.merge(:db => 14))
28
+
29
+ assert Redis.current.get("foo").nil?
30
+ end
31
+
32
+ test "Timeout" do
33
+ assert_nothing_raised do
34
+ Redis.new(OPTIONS.merge(:timeout => 0))
35
+ end
36
+ end
37
+
38
+ # Don't use assert_raise because Timeour::Error in 1.8 inherits
39
+ # Exception instead of StandardError (on 1.9).
40
+ test "Connection timeout" do
41
+ # EM immediately raises CONNREFUSED
42
+ next if driver == :synchrony
43
+
44
+ result = false
45
+
46
+ begin
47
+ Redis.new(OPTIONS.merge(:host => "10.255.255.254", :timeout => 0.1)).ping
48
+ rescue Timeout::Error
49
+ result = true
50
+ ensure
51
+ assert result
52
+ end
53
+ end
54
+
55
+ test "Retry when first read raises ECONNRESET" do
56
+ $request = 0
57
+
58
+ command = lambda do
59
+ case ($request += 1)
60
+ when 1; nil # Close on first command
61
+ else "+%d" % $request
62
+ end
63
+ end
64
+
65
+ redis_mock(:ping => command) do
66
+ redis = Redis.connect(:port => 6380, :timeout => 0.1)
67
+ assert "2" == redis.ping
68
+ end
69
+ end
70
+
71
+ test "Don't retry when wrapped inside #without_reconnect" do
72
+ $request = 0
73
+
74
+ command = lambda do
75
+ case ($request += 1)
76
+ when 1; nil # Close on first command
77
+ else "+%d" % $request
78
+ end
79
+ end
80
+
81
+ redis_mock(:ping => command) do
82
+ redis = Redis.connect(:port => 6380, :timeout => 0.1)
83
+ assert_raise Errno::ECONNRESET do
84
+ redis.without_reconnect do
85
+ redis.ping
86
+ end
87
+ end
88
+
89
+ assert !redis.client.connected?
90
+ end
91
+ end
92
+
93
+ test "Retry only once when read raises ECONNRESET" do
94
+ $request = 0
95
+
96
+ command = lambda do
97
+ case ($request += 1)
98
+ when 1; nil # Close on first command
99
+ when 2; nil # Close on second command
100
+ else "+%d" % $request
101
+ end
102
+ end
103
+
104
+ redis_mock(:ping => command) do
105
+ redis = Redis.connect(:port => 6380, :timeout => 0.1)
106
+ assert_raise Errno::ECONNRESET do
107
+ redis.ping
108
+ end
109
+
110
+ assert !redis.client.connected?
111
+ end
112
+ end
113
+
114
+ test "Don't retry when second read in pipeline raises ECONNRESET" do
115
+ $request = 0
116
+
117
+ command = lambda do
118
+ case ($request += 1)
119
+ when 2; nil # Close on second command
120
+ else "+%d" % $request
121
+ end
122
+ end
123
+
124
+ redis_mock(:ping => command) do
125
+ redis = Redis.connect(:port => 6380, :timeout => 0.1)
126
+ assert_raise Errno::ECONNRESET do
127
+ redis.pipelined do
128
+ redis.ping
129
+ redis.ping # Second #read times out
130
+ end
131
+ end
132
+ end
133
+ end
134
+
135
+ test "Connecting to UNIX domain socket" do
136
+ assert_nothing_raised do
137
+ Redis.new(OPTIONS.merge(:path => "/tmp/redis.sock")).ping
138
+ end
139
+ end
140
+
141
+ # Using a mock server in a thread doesn't work here (possibly because blocking
142
+ # socket ops, raw socket timeouts and Ruby's thread scheduling don't mix).
143
+ #
144
+ # I think this is failing because of a race condition?
145
+ #
146
+ # test "Bubble EAGAIN without retrying" do
147
+ # cmd = %{(sleep 0.3; echo "+PONG\r\n") | nc -l 6380}
148
+ # IO.popen(cmd) do |_|
149
+ # sleep 0.1 # Give nc a little time to start listening
150
+ # redis = Redis.connect(:port => 6380, :timeout => 0.1)
151
+
152
+ # begin
153
+ # assert_raise(Errno::EAGAIN) { redis.ping }
154
+ # ensure
155
+ # # Explicitly close connection so nc can quit
156
+ # redis.client.disconnect
157
+
158
+ # # Make the reactor loop do a tick to really close
159
+ # EM::Synchrony.sleep(0) if driver == :synchrony
160
+ # end
161
+ # end
162
+ # end
163
+
@@ -0,0 +1,126 @@
1
+ test "HSET and HGET" do |r|
2
+ r.hset("foo", "f1", "s1")
3
+
4
+ assert "s1" == r.hget("foo", "f1")
5
+ end
6
+
7
+ test "HSETNX" do |r|
8
+ r.hset("foo", "f1", "s1")
9
+ r.hsetnx("foo", "f1", "s2")
10
+
11
+ assert "s1" == r.hget("foo", "f1")
12
+
13
+ r.del("foo")
14
+ r.hsetnx("foo", "f1", "s2")
15
+
16
+ assert "s2" == r.hget("foo", "f1")
17
+ end
18
+
19
+ test "HDEL" do |r|
20
+ r.hset("foo", "f1", "s1")
21
+
22
+ assert "s1" == r.hget("foo", "f1")
23
+
24
+ r.hdel("foo", "f1")
25
+
26
+ assert nil == r.hget("foo", "f1")
27
+ end
28
+
29
+ test "HEXISTS" do |r|
30
+ assert false == r.hexists("foo", "f1")
31
+
32
+ r.hset("foo", "f1", "s1")
33
+
34
+ assert r.hexists("foo", "f1")
35
+ end
36
+
37
+ test "HLEN" do |r|
38
+ assert 0 == r.hlen("foo")
39
+
40
+ r.hset("foo", "f1", "s1")
41
+
42
+ assert 1 == r.hlen("foo")
43
+
44
+ r.hset("foo", "f2", "s2")
45
+
46
+ assert 2 == r.hlen("foo")
47
+ end
48
+
49
+ test "HKEYS" do |r|
50
+ assert [] == r.hkeys("foo")
51
+
52
+ r.hset("foo", "f1", "s1")
53
+ r.hset("foo", "f2", "s2")
54
+
55
+ assert ["f1", "f2"] == r.hkeys("foo")
56
+ end
57
+
58
+ test "HVALS" do |r|
59
+ assert [] == r.hvals("foo")
60
+
61
+ r.hset("foo", "f1", "s1")
62
+ r.hset("foo", "f2", "s2")
63
+
64
+ assert ["s1", "s2"] == r.hvals("foo")
65
+ end
66
+
67
+ test "HGETALL" do |r|
68
+ assert({} == r.hgetall("foo"))
69
+
70
+ r.hset("foo", "f1", "s1")
71
+ r.hset("foo", "f2", "s2")
72
+
73
+ assert({"f1" => "s1", "f2" => "s2"} == r.hgetall("foo"))
74
+ end
75
+
76
+ test "HMSET" do |r|
77
+ r.hmset("hash", "foo1", "bar1", "foo2", "bar2")
78
+
79
+ assert "bar1" == r.hget("hash", "foo1")
80
+ assert "bar2" == r.hget("hash", "foo2")
81
+ end
82
+
83
+ test "HMSET with invalid arguments" do |r|
84
+ assert_raise(RuntimeError) do
85
+ r.hmset("hash", "foo1", "bar1", "foo2", "bar2", "foo3")
86
+ end
87
+ end
88
+
89
+ test "Mapped HMSET" do |r|
90
+ r.mapped_hmset("foo", :f1 => "s1", :f2 => "s2")
91
+
92
+ assert "s1" == r.hget("foo", "f1")
93
+ assert "s2" == r.hget("foo", "f2")
94
+ end
95
+
96
+ test "HMGET" do |r|
97
+ r.hset("foo", "f1", "s1")
98
+ r.hset("foo", "f2", "s2")
99
+ r.hset("foo", "f3", "s3")
100
+
101
+ assert ["s2", "s3"] == r.hmget("foo", "f2", "f3")
102
+ end
103
+
104
+ test "HMGET mapped" do |r|
105
+ r.hset("foo", "f1", "s1")
106
+ r.hset("foo", "f2", "s2")
107
+ r.hset("foo", "f3", "s3")
108
+
109
+ assert({"f1" => "s1"} == r.mapped_hmget("foo", "f1"))
110
+ assert({"f1" => "s1", "f2" => "s2"} == r.mapped_hmget("foo", "f1", "f2"))
111
+ end
112
+
113
+ test "HINCRBY" do |r|
114
+ r.hincrby("foo", "f1", 1)
115
+
116
+ assert "1" == r.hget("foo", "f1")
117
+
118
+ r.hincrby("foo", "f1", 2)
119
+
120
+ assert "3" == r.hget("foo", "f1")
121
+
122
+ r.hincrby("foo", "f1", -1)
123
+
124
+ assert "2" == r.hget("foo", "f1")
125
+ end
126
+
@@ -0,0 +1,37 @@
1
+ test "Logger" do |r, log|
2
+ r.ping
3
+
4
+ assert log.string =~ /Redis >> PING/
5
+ assert log.string =~ /Redis >> \d+\.\d+ms/
6
+ end
7
+
8
+ test "Logger with pipelining" do |r, log|
9
+ r.pipelined do
10
+ r.set "foo", "bar"
11
+ r.get "foo"
12
+ end
13
+
14
+ assert log.string["SET foo bar"]
15
+ assert log.string["GET foo"]
16
+ end if $TEST_PIPELINING
17
+
18
+ test "Recovers from failed commands" do |r, _|
19
+ # See http://github.com/ezmobius/redis-rb/issues#issue/28
20
+
21
+ assert_raise(ArgumentError) do
22
+ r.srem "foo"
23
+ end
24
+
25
+ assert_nothing_raised do
26
+ r.info
27
+ end
28
+ end
29
+
30
+ test "raises on protocol errors" do
31
+ redis_mock(:ping => lambda { |*_| "foo" }) do
32
+ assert_raise(Redis::ProtocolError) do
33
+ Redis.connect(:port => 6380).ping
34
+ end
35
+ end
36
+ end
37
+