redis 3.0.1 → 3.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,9 +1,12 @@
1
1
  require "redis/errors"
2
+ require "socket"
3
+ require "cgi"
2
4
 
3
5
  class Redis
4
6
  class Client
5
7
 
6
8
  DEFAULTS = {
9
+ :url => lambda { ENV["REDIS_URL"] },
7
10
  :scheme => "redis",
8
11
  :host => "127.0.0.1",
9
12
  :port => 6379,
@@ -11,6 +14,9 @@ class Redis
11
14
  :timeout => 5.0,
12
15
  :password => nil,
13
16
  :db => 0,
17
+ :driver => nil,
18
+ :id => nil,
19
+ :tcp_keepalive => 0
14
20
  }
15
21
 
16
22
  def scheme
@@ -45,9 +51,9 @@ class Redis
45
51
  @options[:db] = db.to_i
46
52
  end
47
53
 
48
- attr :logger
49
- attr :connection
50
- attr :command_map
54
+ attr_accessor :logger
55
+ attr_reader :connection
56
+ attr_reader :command_map
51
57
 
52
58
  def initialize(options = {})
53
59
  @options = _parse_options(options)
@@ -295,8 +301,19 @@ class Redis
295
301
 
296
302
  def _parse_options(options)
297
303
  defaults = DEFAULTS.dup
304
+ options = options.dup
298
305
 
299
- url = options[:url] || ENV["REDIS_URL"]
306
+ defaults.keys.each do |key|
307
+ # Fill in defaults if needed
308
+ if defaults[key].respond_to?(:call)
309
+ defaults[key] = defaults[key].call
310
+ end
311
+
312
+ # Symbolize only keys that are needed
313
+ options[key] = options[key.to_s] if options.has_key?(key.to_s)
314
+ end
315
+
316
+ url = options[:url] || defaults[:url]
300
317
 
301
318
  # Override defaults from URL if given
302
319
  if url
@@ -313,12 +330,15 @@ class Redis
313
330
  defaults[:scheme] = uri.scheme
314
331
  defaults[:host] = uri.host
315
332
  defaults[:port] = uri.port if uri.port
316
- defaults[:password] = uri.password if uri.password
333
+ defaults[:password] = CGI.unescape(uri.password) if uri.password
317
334
  defaults[:db] = uri.path[1..-1].to_i if uri.path
318
335
  end
319
336
  end
320
337
 
321
- options = defaults.merge(options)
338
+ # Use default when option is not specified or nil
339
+ defaults.keys.each do |key|
340
+ options[key] ||= defaults[key]
341
+ end
322
342
 
323
343
  if options[:path]
324
344
  options[:scheme] = "unix"
@@ -333,6 +353,26 @@ class Redis
333
353
  options[:db] = options[:db].to_i
334
354
  options[:driver] = _parse_driver(options[:driver]) || Connection.drivers.last
335
355
 
356
+ case options[:tcp_keepalive]
357
+ when Hash
358
+ [:time, :intvl, :probes].each do |key|
359
+ unless options[:tcp_keepalive][key].is_a?(Fixnum)
360
+ raise "Expected the #{key.inspect} key in :tcp_keepalive to be a Fixnum"
361
+ end
362
+ end
363
+
364
+ when Fixnum
365
+ if options[:tcp_keepalive] >= 60
366
+ options[:tcp_keepalive] = {:time => options[:tcp_keepalive] - 20, :intvl => 10, :probes => 2}
367
+
368
+ elsif options[:tcp_keepalive] >= 30
369
+ options[:tcp_keepalive] = {:time => options[:tcp_keepalive] - 10, :intvl => 5, :probes => 2}
370
+
371
+ elsif options[:tcp_keepalive] >= 5
372
+ options[:tcp_keepalive] = {:time => options[:tcp_keepalive] - 2, :intvl => 2, :probes => 1}
373
+ end
374
+ end
375
+
336
376
  options
337
377
  end
338
378
 
@@ -79,22 +79,26 @@ class Redis
79
79
  end
80
80
  end
81
81
 
82
- class UNIXSocket < ::UNIXSocket
82
+ if defined?(::UNIXSocket)
83
83
 
84
- # This class doesn't include the mixin, because JRuby raises
85
- # Errno::EAGAIN on #read_nonblock even when IO.select says it is
86
- # readable. This behavior shows in 1.6.6 in both 1.8 and 1.9 mode.
87
- # Therefore, fall back on the default Unix socket implementation,
88
- # without timeouts.
84
+ class UNIXSocket < ::UNIXSocket
89
85
 
90
- def self.connect(path, timeout)
91
- Timeout.timeout(timeout) do
92
- sock = new(path)
93
- sock
86
+ # This class doesn't include the mixin, because JRuby raises
87
+ # Errno::EAGAIN on #read_nonblock even when IO.select says it is
88
+ # readable. This behavior shows in 1.6.6 in both 1.8 and 1.9 mode.
89
+ # Therefore, fall back on the default Unix socket implementation,
90
+ # without timeouts.
91
+
92
+ def self.connect(path, timeout)
93
+ Timeout.timeout(timeout) do
94
+ sock = new(path)
95
+ sock
96
+ end
97
+ rescue Timeout::Error
98
+ raise TimeoutError
94
99
  end
95
- rescue Timeout::Error
96
- raise TimeoutError
97
100
  end
101
+
98
102
  end
99
103
 
100
104
  else
@@ -172,9 +176,37 @@ class Redis
172
176
 
173
177
  instance = new(sock)
174
178
  instance.timeout = config[:timeout]
179
+ instance.set_tcp_keepalive config[:tcp_keepalive]
175
180
  instance
176
181
  end
177
182
 
183
+ if [:SOL_SOCKET, :SO_KEEPALIVE, :SOL_TCP, :TCP_KEEPIDLE, :TCP_KEEPINTVL, :TCP_KEEPCNT].all?{|c| Socket.const_defined? c}
184
+ def set_tcp_keepalive(keepalive)
185
+ return unless keepalive.is_a?(Hash)
186
+
187
+ @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true)
188
+ @sock.setsockopt(Socket::SOL_TCP, Socket::TCP_KEEPIDLE, keepalive[:time])
189
+ @sock.setsockopt(Socket::SOL_TCP, Socket::TCP_KEEPINTVL, keepalive[:intvl])
190
+ @sock.setsockopt(Socket::SOL_TCP, Socket::TCP_KEEPCNT, keepalive[:probes])
191
+ end
192
+
193
+ def get_tcp_keepalive
194
+ {
195
+ :time => @sock.getsockopt(Socket::SOL_TCP, Socket::TCP_KEEPIDLE).int,
196
+ :intvl => @sock.getsockopt(Socket::SOL_TCP, Socket::TCP_KEEPINTVL).int,
197
+ :probes => @sock.getsockopt(Socket::SOL_TCP, Socket::TCP_KEEPCNT).int,
198
+ }
199
+ end
200
+ else
201
+ def set_tcp_keepalive(keepalive)
202
+ end
203
+
204
+ def get_tcp_keepalive
205
+ {
206
+ }
207
+ end
208
+ end
209
+
178
210
  def initialize(sock)
179
211
  @sock = sock
180
212
  end
@@ -27,13 +27,18 @@ class Redis
27
27
  def receive_data(data)
28
28
  @reader.feed(data)
29
29
 
30
- begin
31
- until (reply = @reader.gets) == false
32
- reply = CommandError.new(reply.message) if reply.is_a?(RuntimeError)
33
- @req.succeed [:reply, reply]
30
+ loop do
31
+ begin
32
+ reply = @reader.gets
33
+ rescue RuntimeError => err
34
+ @req.fail [:error, ProtocolError.new(err.message)]
35
+ break
34
36
  end
35
- rescue RuntimeError => err
36
- @req.fail [:error, ProtocolError.new(err.message)]
37
+
38
+ break if reply == false
39
+
40
+ reply = CommandError.new(reply.message) if reply.is_a?(RuntimeError)
41
+ @req.succeed [:reply, reply]
37
42
  end
38
43
  end
39
44
 
@@ -15,10 +15,11 @@ class Redis
15
15
 
16
16
  attr_reader :ring
17
17
 
18
- def initialize(urls, options = {})
18
+ def initialize(node_configs, options = {})
19
19
  @tag = options.delete(:tag) || /^\{(.+?)\}/
20
20
  @default_options = options
21
- @ring = HashRing.new urls.map { |url| Redis.new(options.merge(:url => url)) }
21
+ @ring = HashRing.new
22
+ node_configs.each { |node_config| add_node(node_config) }
22
23
  @subscribed_node = nil
23
24
  end
24
25
 
@@ -30,8 +31,10 @@ class Redis
30
31
  @ring.nodes
31
32
  end
32
33
 
33
- def add_node(url)
34
- @ring.add_node Redis.new(@default_options.merge(:url => url))
34
+ def add_node(options)
35
+ options = { :url => options } if options.is_a?(String)
36
+ options = @default_options.merge(options)
37
+ @ring.add_node Redis.new( options )
35
38
  end
36
39
 
37
40
  # Change the selected database for the current connection.
@@ -68,14 +68,19 @@ class Redis
68
68
 
69
69
  class Multi < self
70
70
  def finish(replies)
71
- return if replies.last.nil? # The transaction failed because of WATCH.
71
+ exec = replies.last
72
72
 
73
- if replies.last.size < futures.size - 2
73
+ return if exec.nil? # The transaction failed because of WATCH.
74
+
75
+ # EXEC command failed.
76
+ raise exec if exec.is_a?(CommandError)
77
+
78
+ if exec.size < futures.size - 2
74
79
  # Some command wasn't recognized by Redis.
75
- raise replies.detect { |r| r.kind_of?(::RuntimeError) }
80
+ raise replies.detect { |r| r.is_a?(CommandError) }
76
81
  end
77
82
 
78
- super(replies.last) do |reply|
83
+ super(exec) do |reply|
79
84
  # Because an EXEC returns nested replies, hiredis won't be able to
80
85
  # convert an error reply to a CommandError instance itself. This is
81
86
  # specific to MULTI/EXEC, so we solve this here.
@@ -17,11 +17,11 @@ class Redis
17
17
  end
18
18
 
19
19
  def unsubscribe(*channels)
20
- call [:unsubscribe, *channels]
20
+ call([:unsubscribe, *channels])
21
21
  end
22
22
 
23
23
  def punsubscribe(*channels)
24
- call [:punsubscribe, *channels]
24
+ call([:punsubscribe, *channels])
25
25
  end
26
26
 
27
27
  protected
@@ -1,3 +1,3 @@
1
1
  class Redis
2
- VERSION = "3.0.1"
2
+ VERSION = "3.0.2"
3
3
  end
@@ -12,4 +12,30 @@ class TestDistributedInternals < Test::Unit::TestCase
12
12
 
13
13
  assert_equal "#<Redis client v#{Redis::VERSION} for #{redis.nodes.map(&:id).join(', ')}>", redis.inspect
14
14
  end
15
+
16
+ def test_default_as_urls
17
+ nodes = ["redis://localhost:#{PORT}/15", *NODES]
18
+ redis = Redis::Distributed.new nodes
19
+ assert_equal ["redis://localhost:#{PORT}/15", *NODES], redis.nodes.map { |node| node.client.id}
20
+ end
21
+
22
+ def test_default_as_config_hashes
23
+ nodes = [OPTIONS.merge(:host => 'localhost'), OPTIONS.merge(:host => 'localhost', :port => PORT.next)]
24
+ redis = Redis::Distributed.new nodes
25
+ assert_equal ["redis://localhost:#{PORT}/15","redis://localhost:#{PORT.next}/15"], redis.nodes.map { |node| node.client.id }
26
+ end
27
+
28
+ def test_as_mix_and_match
29
+ nodes = ["redis://localhost:7389/15", OPTIONS.merge(:host => 'localhost'), OPTIONS.merge(:host => 'localhost', :port => PORT.next)]
30
+ redis = Redis::Distributed.new nodes
31
+ assert_equal ["redis://localhost:7389/15", "redis://localhost:#{PORT}/15", "redis://localhost:#{PORT.next}/15"], redis.nodes.map { |node| node.client.id }
32
+ end
33
+
34
+ def test_override_id
35
+ nodes = [OPTIONS.merge(:host => 'localhost', :id => "test"), OPTIONS.merge( :host => 'localhost', :port => PORT.next, :id => "test1")]
36
+ redis = Redis::Distributed.new nodes
37
+ assert_equal redis.nodes.first.client.id, "test"
38
+ assert_equal redis.nodes.last.client.id, "test1"
39
+ assert_equal "#<Redis client v#{Redis::VERSION} for #{redis.nodes.map(&:id).join(', ')}>", redis.inspect
40
+ end
15
41
  end
@@ -28,7 +28,7 @@ class TestDistributedRemoteServerControlCommands < Test::Unit::TestCase
28
28
  end
29
29
 
30
30
  def test_info_commandstats
31
- return if version < "2.9.0"
31
+ return if version < "2.5.7"
32
32
 
33
33
  r.nodes.each { |n| n.config(:resetstat) }
34
34
  r.ping # Executed on every node
@@ -98,6 +98,24 @@ class TestInternals < Test::Unit::TestCase
98
98
  end
99
99
  end
100
100
 
101
+ driver(:ruby) do
102
+ def test_tcp_keepalive
103
+ keepalive = {:time => 20, :intvl => 10, :probes => 5}
104
+
105
+ redis = Redis.new(OPTIONS.merge(:tcp_keepalive => keepalive))
106
+ redis.ping
107
+
108
+ connection = redis.client.connection
109
+ actual_keepalive = connection.get_tcp_keepalive
110
+
111
+ [:time, :intvl, :probes].each do |key|
112
+ if actual_keepalive.has_key?(key)
113
+ assert_equal actual_keepalive[key], keepalive[key]
114
+ end
115
+ end
116
+ end
117
+ end
118
+
101
119
  def test_time
102
120
  return if version < "2.5.4"
103
121
 
@@ -2,6 +2,8 @@ module Lint
2
2
 
3
3
  module SortedSets
4
4
 
5
+ Infinity = 1.0/0.0
6
+
5
7
  def test_zadd
6
8
  assert_equal 0, r.zcard("foo")
7
9
  assert_equal true, r.zadd("foo", 1, "s1")
@@ -61,6 +63,12 @@ module Lint
61
63
 
62
64
  rv = r.zincrby "foo", 10, "s1"
63
65
  assert_equal 11.0, rv
66
+
67
+ rv = r.zincrby "bar", "-inf", "s1"
68
+ assert_equal(-Infinity, rv)
69
+
70
+ rv = r.zincrby "bar", "+inf", "s2"
71
+ assert_equal(+Infinity, rv)
64
72
  end
65
73
 
66
74
  def test_zrank
@@ -87,6 +95,11 @@ module Lint
87
95
  assert_equal ["s1", "s2"], r.zrange("foo", 0, 1)
88
96
  assert_equal [["s1", 1.0], ["s2", 2.0]], r.zrange("foo", 0, 1, :with_scores => true)
89
97
  assert_equal [["s1", 1.0], ["s2", 2.0]], r.zrange("foo", 0, 1, :withscores => true)
98
+
99
+ r.zadd "bar", "-inf", "s1"
100
+ r.zadd "bar", "+inf", "s2"
101
+ assert_equal [["s1", -Infinity], ["s2", +Infinity]], r.zrange("bar", 0, 1, :with_scores => true)
102
+ assert_equal [["s1", -Infinity], ["s2", +Infinity]], r.zrange("bar", 0, 1, :withscores => true)
90
103
  end
91
104
 
92
105
  def test_zrevrange
@@ -97,6 +110,11 @@ module Lint
97
110
  assert_equal ["s3", "s2"], r.zrevrange("foo", 0, 1)
98
111
  assert_equal [["s3", 3.0], ["s2", 2.0]], r.zrevrange("foo", 0, 1, :with_scores => true)
99
112
  assert_equal [["s3", 3.0], ["s2", 2.0]], r.zrevrange("foo", 0, 1, :withscores => true)
113
+
114
+ r.zadd "bar", "-inf", "s1"
115
+ r.zadd "bar", "+inf", "s2"
116
+ assert_equal [["s2", +Infinity], ["s1", -Infinity]], r.zrevrange("bar", 0, 1, :with_scores => true)
117
+ assert_equal [["s2", +Infinity], ["s1", -Infinity]], r.zrevrange("bar", 0, 1, :withscores => true)
100
118
  end
101
119
 
102
120
  def test_zrangebyscore
@@ -147,6 +165,13 @@ module Lint
147
165
  assert_equal [["s3", 3.0]], r.zrangebyscore("foo", 2, 4, :limit => [1, 1], :with_scores => true)
148
166
  assert_equal [["s2", 2.0]], r.zrangebyscore("foo", 2, 4, :limit => [0, 1], :withscores => true)
149
167
  assert_equal [["s3", 3.0]], r.zrangebyscore("foo", 2, 4, :limit => [1, 1], :withscores => true)
168
+
169
+ r.zadd "bar", "-inf", "s1"
170
+ r.zadd "bar", "+inf", "s2"
171
+ assert_equal [["s1", -Infinity]], r.zrangebyscore("bar", -Infinity, +Infinity, :limit => [0, 1], :with_scores => true)
172
+ assert_equal [["s2", +Infinity]], r.zrangebyscore("bar", -Infinity, +Infinity, :limit => [1, 1], :with_scores => true)
173
+ assert_equal [["s1", -Infinity]], r.zrangebyscore("bar", -Infinity, +Infinity, :limit => [0, 1], :withscores => true)
174
+ assert_equal [["s2", +Infinity]], r.zrangebyscore("bar", -Infinity, +Infinity, :limit => [1, 1], :withscores => true)
150
175
  end
151
176
 
152
177
  def test_zrevrangebyscore_with_withscores
@@ -159,6 +184,13 @@ module Lint
159
184
  assert_equal [["s3", 3.0]], r.zrevrangebyscore("foo", 4, 2, :limit => [1, 1], :with_scores => true)
160
185
  assert_equal [["s4", 4.0]], r.zrevrangebyscore("foo", 4, 2, :limit => [0, 1], :withscores => true)
161
186
  assert_equal [["s3", 3.0]], r.zrevrangebyscore("foo", 4, 2, :limit => [1, 1], :withscores => true)
187
+
188
+ r.zadd "bar", "-inf", "s1"
189
+ r.zadd "bar", "+inf", "s2"
190
+ assert_equal [["s2", +Infinity]], r.zrevrangebyscore("bar", +Infinity, -Infinity, :limit => [0, 1], :with_scores => true)
191
+ assert_equal [["s1", -Infinity]], r.zrevrangebyscore("bar", +Infinity, -Infinity, :limit => [1, 1], :with_scores => true)
192
+ assert_equal [["s2", +Infinity]], r.zrevrangebyscore("bar", +Infinity, -Infinity, :limit => [0, 1], :withscores => true)
193
+ assert_equal [["s1", -Infinity]], r.zrevrangebyscore("bar", +Infinity, -Infinity, :limit => [1, 1], :withscores => true)
162
194
  end
163
195
 
164
196
  def test_zcard
@@ -176,6 +208,11 @@ module Lint
176
208
 
177
209
  assert_equal nil, r.zscore("foo", "s2")
178
210
  assert_equal nil, r.zscore("bar", "s1")
211
+
212
+ r.zadd "bar", "-inf", "s1"
213
+ r.zadd "bar", "+inf", "s2"
214
+ assert_equal(-Infinity, r.zscore("bar", "s1"))
215
+ assert_equal(+Infinity, r.zscore("bar", "s2"))
179
216
  end
180
217
 
181
218
  def test_zremrangebyrank
@@ -32,30 +32,30 @@ module Lint
32
32
 
33
33
  def test_expire
34
34
  r.set("foo", "s1")
35
- assert r.expire("foo", 1)
36
- assert_in_range 0..1, r.ttl("foo")
35
+ assert r.expire("foo", 2)
36
+ assert_in_range 0..2, r.ttl("foo")
37
37
  end
38
38
 
39
39
  def test_pexpire
40
40
  return if version < "2.5.4"
41
41
 
42
42
  r.set("foo", "s1")
43
- assert r.pexpire("foo", 1000)
44
- assert_in_range 0..1, r.ttl("foo")
43
+ assert r.pexpire("foo", 2000)
44
+ assert_in_range 0..2, r.ttl("foo")
45
45
  end
46
46
 
47
47
  def test_expireat
48
48
  r.set("foo", "s1")
49
- assert r.expireat("foo", (Time.now + 1).to_i)
50
- assert_in_range 0..1, r.ttl("foo")
49
+ assert r.expireat("foo", (Time.now + 2).to_i)
50
+ assert_in_range 0..2, r.ttl("foo")
51
51
  end
52
52
 
53
53
  def test_pexpireat
54
54
  return if version < "2.5.4"
55
55
 
56
56
  r.set("foo", "s1")
57
- assert r.pexpireat("foo", (Time.now + 1).to_i * 1_000)
58
- assert_in_range 0..1, r.ttl("foo")
57
+ assert r.pexpireat("foo", (Time.now + 2).to_i * 1_000)
58
+ assert_in_range 0..2, r.ttl("foo")
59
59
  end
60
60
 
61
61
  def test_persist
@@ -68,16 +68,16 @@ module Lint
68
68
 
69
69
  def test_ttl
70
70
  r.set("foo", "s1")
71
- r.expire("foo", 1)
72
- assert_in_range 0..1, r.ttl("foo")
71
+ r.expire("foo", 2)
72
+ assert_in_range 0..2, r.ttl("foo")
73
73
  end
74
74
 
75
75
  def test_pttl
76
76
  return if version < "2.5.4"
77
77
 
78
78
  r.set("foo", "s1")
79
- r.expire("foo", 1)
80
- assert_in_range 1..1000, r.pttl("foo")
79
+ r.expire("foo", 2)
80
+ assert_in_range 1..2000, r.pttl("foo")
81
81
  end
82
82
 
83
83
  def test_move